From c64f13a0eb9cf6f1af64efadc24a2d1bf4352bdd Mon Sep 17 00:00:00 2001 From: wrapperup Date: Mon, 16 Dec 2024 18:34:22 -0500 Subject: [PATCH 01/64] use bit sets in miniaudio for flags --- vendor/miniaudio/device_io_types.odin | 15 +++++--- vendor/miniaudio/engine.odin | 52 +++++++++++++------------- vendor/miniaudio/job_queue.odin | 14 ++++--- vendor/miniaudio/node_graph.odin | 24 ++++++++---- vendor/miniaudio/resource_manager.odin | 20 ++++++---- vendor/miniaudio/utilities.odin | 10 ++++- vendor/miniaudio/vfs.odin | 16 ++++---- 7 files changed, 90 insertions(+), 61 deletions(-) diff --git a/vendor/miniaudio/device_io_types.odin b/vendor/miniaudio/device_io_types.odin index eae804720..0571714d2 100644 --- a/vendor/miniaudio/device_io_types.odin +++ b/vendor/miniaudio/device_io_types.odin @@ -351,8 +351,13 @@ device_id :: struct #raw_union { nullbackend: c.int, /* The null backend uses an integer for device IDs. */ } +data_format_flag :: enum c.int { + EXCLUSIVE_MODE = 1, /* If set, this is supported in exclusive mode. Otherwise not natively supported by exclusive mode. */ +} -DATA_FORMAT_FLAG_EXCLUSIVE_MODE :: 1 << 1 /* If set, this is supported in exclusive mode. Otherwise not natively supported by exclusive mode. */ +data_format_flags :: bit_set[data_format_flag; u32] + +DATA_FORMAT_FLAG_EXCLUSIVE_MODE :: data_format_flags{.EXCLUSIVE_MODE} MAX_DEVICE_NAME_LENGTH :: 255 @@ -364,10 +369,10 @@ device_info :: struct { nativeDataFormatCount: u32, nativeDataFormats: [/*len(format_count) * standard_sample_rate.rate_count * MAX_CHANNELS*/ 64]struct { /* Not sure how big to make this. There can be *many* permutations for virtual devices which can support anything. */ - format: format, /* Sample format. If set to ma_format_unknown, all sample formats are supported. */ - channels: u32, /* If set to 0, all channels are supported. */ - sampleRate: u32, /* If set to 0, all sample rates are supported. */ - flags: u32, /* A combination of MA_DATA_FORMAT_FLAG_* flags. */ + format: format, /* Sample format. If set to ma_format_unknown, all sample formats are supported. */ + channels: u32, /* If set to 0, all channels are supported. */ + sampleRate: u32, /* If set to 0, all sample rates are supported. */ + flags: data_format_flags, /* A combination of MA_DATA_FORMAT_FLAG_* flags. */ }, } diff --git a/vendor/miniaudio/engine.odin b/vendor/miniaudio/engine.odin index ecd3fb39d..467bde583 100644 --- a/vendor/miniaudio/engine.odin +++ b/vendor/miniaudio/engine.odin @@ -11,20 +11,22 @@ Engine ************************************************************************************************************************************************************/ /* Sound flags. */ -sound_flags :: enum c.int { +sound_flag :: enum c.int { /* Resource manager flags. */ - STREAM = 0x00000001, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM */ - DECODE = 0x00000002, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_DECODE */ - ASYNC = 0x00000004, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC */ - WAIT_INIT = 0x00000008, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT */ - UNKNOWN_LENGTH = 0x00000010, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_UNKNOWN_LENGTH */ + STREAM = 0, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_STREAM */ + DECODE = 1, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_DECODE */ + ASYNC = 2, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_ASYNC */ + WAIT_INIT = 3, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_WAIT_INIT */ + UNKNOWN_LENGTH = 4, /* MA_RESOURCE_MANAGER_DATA_SOURCE_FLAG_UNKNOWN_LENGTH */ /* ma_sound specific flags. */ - NO_DEFAULT_ATTACHMENT = 0x00001000, /* Do not attach to the endpoint by default. Useful for when setting up nodes in a complex graph system. */ - NO_PITCH = 0x00002000, /* Disable pitch shifting with ma_sound_set_pitch() and ma_sound_group_set_pitch(). This is an optimization. */ - NO_SPATIALIZATION = 0x00004000, /* Disable spatialization. */ + NO_DEFAULT_ATTACHMENT = 12, /* Do not attach to the endpoint by default. Useful for when setting up nodes in a complex graph system. */ + NO_PITCH = 13, /* Disable pitch shifting with ma_sound_set_pitch() and ma_sound_group_set_pitch(). This is an optimization. */ + NO_SPATIALIZATION = 14, /* Disable spatialization. */ } +sound_flags :: bit_set[sound_flag; u32] + ENGINE_MAX_LISTENERS :: 4 LISTENER_INDEX_CLOSEST :: 255 @@ -81,7 +83,7 @@ engine_node :: struct { @(default_calling_convention="c", link_prefix="ma_") foreign lib { - engine_node_config_init :: proc(pEngine: ^engine, type: engine_node_type, flags: u32) -> engine_node_config --- + engine_node_config_init :: proc(pEngine: ^engine, type: engine_node_type, flags: sound_flags) -> engine_node_config --- engine_node_get_heap_size :: proc(pConfig: ^engine_node_config, pHeapSizeInBytes: ^c.size_t) -> result --- engine_node_init_preallocated :: proc(pConfig: ^engine_node_config, pHeap: rawptr, pEngineNode: ^engine_node) -> result --- @@ -96,17 +98,17 @@ SOUND_SOURCE_CHANNEL_COUNT :: 0xFFFFFFFF sound_end_proc :: #type proc "c" (pUserData: rawptr, pSound: ^sound) sound_config :: struct { - pFilePath: cstring, /* Set this to load from the resource manager. */ - pFilePathW: [^]c.wchar_t, /* Set this to load from the resource manager. */ - pDataSource: ^data_source, /* Set this to load from an existing data source. */ - pInitialAttachment: ^node, /* If set, the sound will be attached to an input of this node. This can be set to a ma_sound. If set to NULL, the sound will be attached directly to the endpoint unless MA_SOUND_FLAG_NO_DEFAULT_ATTACHMENT is set in `flags`. */ - initialAttachmentInputBusIndex: u32, /* The index of the input bus of pInitialAttachment to attach the sound to. */ - channelsIn: u32, /* Ignored if using a data source as input (the data source's channel count will be used always). Otherwise, setting to 0 will cause the engine's channel count to be used. */ - channelsOut: u32, /* Set this to 0 (default) to use the engine's channel count. Set to MA_SOUND_SOURCE_CHANNEL_COUNT to use the data source's channel count (only used if using a data source as input). */ + pFilePath: cstring, /* Set this to load from the resource manager. */ + pFilePathW: [^]c.wchar_t, /* Set this to load from the resource manager. */ + pDataSource: ^data_source, /* Set this to load from an existing data source. */ + pInitialAttachment: ^node, /* If set, the sound will be attached to an input of this node. This can be set to a ma_sound. If set to NULL, the sound will be attached directly to the endpoint unless MA_SOUND_FLAG_NO_DEFAULT_ATTACHMENT is set in `flags`. */ + initialAttachmentInputBusIndex: u32, /* The index of the input bus of pInitialAttachment to attach the sound to. */ + channelsIn: u32, /* Ignored if using a data source as input (the data source's channel count will be used always). Otherwise, setting to 0 will cause the engine's channel count to be used. */ + channelsOut: u32, /* Set this to 0 (default) to use the engine's channel count. Set to MA_SOUND_SOURCE_CHANNEL_COUNT to use the data source's channel count (only used if using a data source as input). */ monoExpansionMode: mono_expansion_mode, /* Controls how the mono channel should be expanded to other channels when spatialization is disabled on a sound. */ - flags: u32, /* A combination of MA_SOUND_FLAG_* flags. */ - volumeSmoothTimeInPCMFrames: u32, /* The number of frames to smooth over volume changes. Defaults to 0 in which case no smoothing is used. */ - initialSeekPointInPCMFrames: u64, /* Initializes the sound such that it's seeked to this location by default. */ + flags: sound_flags, /* A combination of MA_SOUND_FLAG_* flags. */ + volumeSmoothTimeInPCMFrames: u32, /* The number of frames to smooth over volume changes. Defaults to 0 in which case no smoothing is used. */ + initialSeekPointInPCMFrames: u64, /* Initializes the sound such that it's seeked to this location by default. */ rangeBegInPCMFrames: u64, rangeEndInPCMFrames: u64, loopPointBegInPCMFrames: u64, @@ -152,10 +154,10 @@ foreign lib { sound_config_init :: proc() -> sound_config --- sound_config_init2 :: proc(pEngine: ^engine) -> sound_config --- /* Will be renamed to sound_config_init() in version 0.12. */ - sound_init_from_file :: proc(pEngine: ^engine, pFilePath: cstring, flags: u32, pGroup: ^sound_group, pDoneFence: ^fence, pSound: ^sound) -> result --- - sound_init_from_file_w :: proc(pEngine: ^engine, pFilePath: [^]c.wchar_t, flags: u32, pGroup: ^sound_group, pDoneFence: ^fence, pSound: ^sound) -> result --- - sound_init_copy :: proc(pEngine: ^engine, pExistingSound: ^sound, flags: u32, pGroup: ^sound_group, pSound: ^sound) -> result --- - sound_init_from_data_source :: proc(pEngine: ^engine, pDataSource: ^data_source, flags: u32, pGroup: ^sound_group, pSound: ^sound) -> result --- + sound_init_from_file :: proc(pEngine: ^engine, pFilePath: cstring, flags: sound_flags, pGroup: ^sound_group, pDoneFence: ^fence, pSound: ^sound) -> result --- + sound_init_from_file_w :: proc(pEngine: ^engine, pFilePath: [^]c.wchar_t, flags: sound_flags, pGroup: ^sound_group, pDoneFence: ^fence, pSound: ^sound) -> result --- + sound_init_copy :: proc(pEngine: ^engine, pExistingSound: ^sound, flags: sound_flags, pGroup: ^sound_group, pSound: ^sound) -> result --- + sound_init_from_data_source :: proc(pEngine: ^engine, pDataSource: ^data_source, flags: sound_flags, pGroup: ^sound_group, pSound: ^sound) -> result --- sound_init_ex :: proc(pEngine: ^engine, pConfig: ^sound_config, pSound: ^sound) -> result --- sound_uninit :: proc(pSound: ^sound) --- sound_get_engine :: proc(pSound: ^sound) -> ^engine --- @@ -243,7 +245,7 @@ foreign lib { sound_group_config_init :: proc() -> sound_group_config --- sound_group_config_init2 :: proc(pEngine: ^engine) -> sound_group_config --- - sound_group_init :: proc(pEngine: ^engine, flags: u32, pParentGroup, pGroup: ^sound_group) -> result --- + sound_group_init :: proc(pEngine: ^engine, flags: sound_flags, pParentGroup, pGroup: ^sound_group) -> result --- sound_group_init_ex :: proc(pEngine: ^engine, pConfig: ^sound_group_config, pGroup: ^sound_group) -> result --- sound_group_uninit :: proc(pGroup: ^sound_group) --- sound_group_get_engine :: proc(pGroup: ^sound_group) -> ^engine --- diff --git a/vendor/miniaudio/job_queue.odin b/vendor/miniaudio/job_queue.odin index 01ee31216..b5816a95a 100644 --- a/vendor/miniaudio/job_queue.odin +++ b/vendor/miniaudio/job_queue.odin @@ -108,7 +108,7 @@ job :: struct { pDataBufferNode: rawptr /*ma_resource_manager_data_buffer_node**/, pFilePath: cstring, pFilePathW: [^]c.wchar_t, - flags: u32, /* Resource manager data source flags that were used when initializing the data buffer. */ + flags: resource_manager_data_source_flags, /* Resource manager data source flags that were used when initializing the data buffer. */ pInitNotification: ^async_notification, /* Signalled when the data buffer has been initialized and the format/channels/rate can be retrieved. */ pDoneNotification: ^async_notification, /* Signalled when the data buffer has been fully decoded. Will be passed through to MA_JOB_TYPE_RESOURCE_MANAGER_PAGE_DATA_BUFFER_NODE when decoding. */ pInitFence: ^fence, /* Released when initialization of the decoder is complete. */ @@ -194,19 +194,21 @@ ma_job_queue_post(). ma_job_queue_next() will return MA_NO_DATA_AVAILABLE if not This flag should always be used for platforms that do not support multithreading. */ -job_queue_flags :: enum c.int { - NON_BLOCKING = 0x00000001, +job_queue_flag :: enum c.int { + NON_BLOCKING = 0, } +job_queue_flags :: bit_set[job_queue_flag; u32] + job_queue_config :: struct { - flags: u32, + flags: job_queue_flags, capacity: u32, /* The maximum number of jobs that can fit in the queue at a time. */ } USE_EXPERIMENTAL_LOCK_FREE_JOB_QUEUE :: false job_queue :: struct { - flags: u32, /* Flags passed in at initialization time. */ + flags: job_queue_flags, /* Flags passed in at initialization time. */ capacity: u32, /* The maximum number of jobs that can fit in the queue at a time. Set by the config. */ head: u64, /*atomic*/ /* The first item in the list. Required for removing from the top of the list. */ tail: u64, /*atomic*/ /* The last item in the list. Required for appending to the end of the list. */ @@ -222,7 +224,7 @@ job_queue :: struct { @(default_calling_convention="c", link_prefix="ma_") foreign lib { - job_queue_config_init :: proc(flags, capacity: u32) -> job_queue_config --- + job_queue_config_init :: proc(flags: job_queue_flags, capacity: u32) -> job_queue_config --- job_queue_get_heap_size :: proc(pConfig: ^job_queue_config, pHeapSizeInBytes: ^c.size_t) -> result --- job_queue_init_preallocated :: proc(pConfig: ^job_queue_config, pHeap: rawptr, pQueue: ^job_queue) -> result --- diff --git a/vendor/miniaudio/node_graph.odin b/vendor/miniaudio/node_graph.odin index 63482413b..610ada7a8 100644 --- a/vendor/miniaudio/node_graph.odin +++ b/vendor/miniaudio/node_graph.odin @@ -22,14 +22,16 @@ NODE_BUS_COUNT_UNKNOWN :: 255 node :: struct {} /* Node flags. */ -node_flags :: enum c.int { - PASSTHROUGH = 0x00000001, - CONTINUOUS_PROCESSING = 0x00000002, - ALLOW_NULL_INPUT = 0x00000004, - DIFFERENT_PROCESSING_RATES = 0x00000008, - SILENT_OUTPUT = 0x00000010, +node_flag :: enum c.int { + PASSTHROUGH = 0, + CONTINUOUS_PROCESSING = 1, + ALLOW_NULL_INPUT = 2, + DIFFERENT_PROCESSING_RATES = 3, + SILENT_OUTPUT = 4, } +node_flags :: bit_set[node_flag; u32] + /* The playback state of a node. Either started or stopped. */ node_state :: enum c.int { started = 0, @@ -75,7 +77,7 @@ node_vtable :: struct { Flags describing characteristics of the node. This is currently just a placeholder for some ideas for later on. */ - flags: u32, + flags: node_flags, } node_config :: struct { @@ -87,6 +89,12 @@ node_config :: struct { pOutputChannels: ^u32, /* The number of elements are determined by the output bus count as determined by the vtable, or `outputBusCount` if the vtable specifies `MA_NODE_BUS_COUNT_UNKNOWN`. */ } +node_output_bus_flag :: enum c.int { + HAS_READ = 0, /* 0x01 */ +} + +node_output_bus_flags :: bit_set[node_output_bus_flag; u32] + /* A node has multiple output buses. An output bus is attached to an input bus as an item in a linked list. Think of the input bus as a linked list, with the output bus being an item in that list. @@ -99,7 +107,7 @@ node_output_bus :: struct { /* Mutable via multiple threads. Must be used atomically. The weird ordering here is for packing reasons. */ inputNodeInputBusIndex: u8, /* The index of the input bus on the input. Required for detaching. Will only be used in the spinlock so does not need to be atomic. */ - flags: u32, /*atomic*/ /* Some state flags for tracking the read state of the output buffer. A combination of MA_NODE_OUTPUT_BUS_FLAG_*. */ + flags: node_output_bus_flags, /*atomic*/ /* Some state flags for tracking the read state of the output buffer. A combination of MA_NODE_OUTPUT_BUS_FLAG_*. */ refCount: u32, /*atomic*/ /* Reference count for some thread-safety when detaching. */ isAttached: b32, /*atomic*/ /* This is used to prevent iteration of nodes that are in the middle of being detached. Used for thread safety. */ lock: spinlock, /*atomic*/ /* Unfortunate lock, but significantly simplifies the implementation. Required for thread-safe attaching and detaching. */ diff --git a/vendor/miniaudio/resource_manager.odin b/vendor/miniaudio/resource_manager.odin index 0284db86b..0c0a309d1 100644 --- a/vendor/miniaudio/resource_manager.odin +++ b/vendor/miniaudio/resource_manager.odin @@ -10,14 +10,16 @@ Resource Manager ************************************************************************************************************************************************************/ -resource_manager_data_source_flags :: enum c.int { - STREAM = 0x00000001, /* When set, does not load the entire data source in memory. Disk I/O will happen on job threads. */ - DECODE = 0x00000002, /* Decode data before storing in memory. When set, decoding is done at the resource manager level rather than the mixing thread. Results in faster mixing, but higher memory usage. */ - ASYNC = 0x00000004, /* When set, the resource manager will load the data source asynchronously. */ - WAIT_INIT = 0x00000008, /* When set, waits for initialization of the underlying data source before returning from ma_resource_manager_data_source_init(). */ - UNKNOWN_LENGTH = 0x00000010, /* Gives the resource manager a hint that the length of the data source is unknown and calling `ma_data_source_get_length_in_pcm_frames()` should be avoided. */ +resource_manager_data_source_flag :: enum c.int { + STREAM = 0, /* When set, does not load the entire data source in memory. Disk I/O will happen on job threads. */ + DECODE = 1, /* Decode data before storing in memory. When set, decoding is done at the resource manager level rather than the mixing thread. Results in faster mixing, but higher memory usage. */ + ASYNC = 2, /* When set, the resource manager will load the data source asynchronously. */ + WAIT_INIT = 3, /* When set, waits for initialization of the underlying data source before returning from ma_resource_manager_data_source_init(). */ + UNKNOWN_LENGTH = 4, /* Gives the resource manager a hint that the length of the data source is unknown and calling `ma_data_source_get_length_in_pcm_frames()` should be avoided. */ } +resource_manager_data_source_flags :: bit_set[resource_manager_data_source_flag; u32] + /* Pipeline notifications used by the resource manager. Made up of both an async notification and a fence, both of which are optional. */ @@ -58,7 +60,7 @@ resource_manager_job_queue_next :: job_queue_next /* Maximum job thread count will be restricted to this, but this may be removed later and replaced with a heap allocation thereby removing any limitation. */ RESOURCE_MANAGER_MAX_JOB_THREAD_COUNT :: 64 -resource_manager_flags :: enum c.int { +resource_manager_flag :: enum c.int { /* Indicates ma_resource_manager_next_job() should not block. Only valid when the job thread count is 0. */ NON_BLOCKING = 0x00000001, @@ -66,6 +68,8 @@ resource_manager_flags :: enum c.int { NO_THREADING = 0x00000002, } +resource_manager_flags :: bit_set[resource_manager_flag; u32] + resource_manager_data_source_config :: struct { pFilePath: cstring, pFilePathW: [^]c.wchar_t, @@ -126,7 +130,7 @@ resource_manager_data_buffer :: struct { ds: data_source_base, /* Base data source. A data buffer is a data source. */ pResourceManager: ^resource_manager, /* A pointer to the resource manager that owns this buffer. */ pNode: ^resource_manager_data_buffer_node, /* The data node. This is reference counted and is what supplies the data. */ - flags: u32, /* The flags that were passed used to initialize the buffer. */ + flags: resource_manager_flags, /* The flags that were passed used to initialize the buffer. */ executionCounter: u32, /*atomic*/ /* For allocating execution orders for jobs. */ executionPointer: u32, /*atomic*/ /* For managing the order of execution for asynchronous jobs relating to this object. Incremented as jobs complete processing. */ seekTargetInPCMFrames: u64, /* Only updated by the public API. Never written nor read from the job thread. */ diff --git a/vendor/miniaudio/utilities.odin b/vendor/miniaudio/utilities.odin index 8728f40dc..d9d23ad83 100644 --- a/vendor/miniaudio/utilities.odin +++ b/vendor/miniaudio/utilities.odin @@ -119,7 +119,13 @@ offset_pcm_frames_const_ptr_f32 :: #force_inline proc "c" (p: [^]f32, offsetInFr data_source :: struct {} -DATA_SOURCE_SELF_MANAGED_RANGE_AND_LOOP_POINT :: 0x00000001 +data_source_flag :: enum c.int { + SELF_MANAGED_RANGE_AND_LOOP_POINT = 0, +} + +data_source_flags :: bit_set[data_source_flag; u32] + +DATA_SOURCE_SELF_MANAGED_RANGE_AND_LOOP_POINT :: data_source_flags{.SELF_MANAGED_RANGE_AND_LOOP_POINT} data_source_vtable :: struct { onRead: proc "c" (pDataSource: ^data_source, pFramesOut: rawptr, frameCount: u64, pFramesRead: ^u64) -> result, @@ -128,7 +134,7 @@ data_source_vtable :: struct { onGetCursor: proc "c" (pDataSource: ^data_source, pCursor: ^u64) -> result, onGetLength: proc "c" (pDataSource: ^data_source, pLength: ^u64) -> result, onSetLooping: proc "c" (pDataSource: ^data_source, isLooping: b32) -> result, - flags: u32, + flags: data_source_flags, } data_source_get_next_proc :: proc "c" (pDataSource: ^data_source) -> ^data_source diff --git a/vendor/miniaudio/vfs.odin b/vendor/miniaudio/vfs.odin index b045a1501..2a538c6e3 100644 --- a/vendor/miniaudio/vfs.odin +++ b/vendor/miniaudio/vfs.odin @@ -16,11 +16,13 @@ appropriate for a given situation. vfs :: struct {} vfs_file :: distinct handle -open_mode_flags :: enum c.int { - READ = 0x00000001, - WRITE = 0x00000002, +open_mode_flag :: enum c.int { + READ = 0, + WRITE = 1, } +open_mode_flags :: bit_set[open_mode_flag; u32] + seek_origin :: enum c.int { start, current, @@ -32,8 +34,8 @@ file_info :: struct { } vfs_callbacks :: struct { - onOpen: proc "c" (pVFS: ^vfs, pFilePath: cstring, openMode: u32, pFile: ^vfs_file) -> result, - onOpenW: proc "c" (pVFS: ^vfs, pFilePath: [^]c.wchar_t, openMode: u32, pFile: ^vfs_file) -> result, + onOpen: proc "c" (pVFS: ^vfs, pFilePath: cstring, openMode: open_mode_flags, pFile: ^vfs_file) -> result, + onOpenW: proc "c" (pVFS: ^vfs, pFilePath: [^]c.wchar_t, openMode: open_mode_flags, pFile: ^vfs_file) -> result, onClose: proc "c" (pVFS: ^vfs, file: vfs_file) -> result, onRead: proc "c" (pVFS: ^vfs, file: vfs_file, pDst: rawptr, sizeInBytes: c.size_t, pBytesRead: ^c.size_t) -> result, onWrite: proc "c" (pVFS: ^vfs, file: vfs_file, pSrc: rawptr, sizeInBytes: c.size_t, pBytesWritten: ^c.size_t) -> result, @@ -54,8 +56,8 @@ ma_tell_proc :: proc "c" (pUserData: rawptr, pCursor: ^i64) -> result @(default_calling_convention="c", link_prefix="ma_") foreign lib { - vfs_open :: proc(pVFS: ^vfs, pFilePath: cstring, openMode: u32, pFile: ^vfs_file) -> result --- - vfs_open_w :: proc(pVFS: ^vfs, pFilePath: [^]c.wchar_t, openMode: u32, pFile: ^vfs_file) -> result --- + vfs_open :: proc(pVFS: ^vfs, pFilePath: cstring, openMode: open_mode_flags, pFile: ^vfs_file) -> result --- + vfs_open_w :: proc(pVFS: ^vfs, pFilePath: [^]c.wchar_t, openMode: open_mode_flags, pFile: ^vfs_file) -> result --- vfs_close :: proc(pVFS: ^vfs, file: vfs_file) -> result --- vfs_read :: proc(pVFS: ^vfs, file: vfs_file, pDst: rawptr, sizeInBytes: c.size_t, pBytesRead: ^c.size_t) -> result --- vfs_write :: proc(pVFS: ^vfs, file: vfs_file, pSrc: rawptr, sizeInBytes: c.size_t, pBytesWritten: ^c.size_t) -> result --- From 6753946b7a7e7259d7c49f2712761acde1b39123 Mon Sep 17 00:00:00 2001 From: wrapperup Date: Mon, 16 Dec 2024 22:12:20 -0500 Subject: [PATCH 02/64] fix resource_manager_flag enum --- vendor/miniaudio/resource_manager.odin | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vendor/miniaudio/resource_manager.odin b/vendor/miniaudio/resource_manager.odin index 0c0a309d1..495a02c5d 100644 --- a/vendor/miniaudio/resource_manager.odin +++ b/vendor/miniaudio/resource_manager.odin @@ -62,10 +62,10 @@ RESOURCE_MANAGER_MAX_JOB_THREAD_COUNT :: 64 resource_manager_flag :: enum c.int { /* Indicates ma_resource_manager_next_job() should not block. Only valid when the job thread count is 0. */ - NON_BLOCKING = 0x00000001, + NON_BLOCKING = 0, /* Disables any kind of multithreading. Implicitly enables MA_RESOURCE_MANAGER_FLAG_NON_BLOCKING. */ - NO_THREADING = 0x00000002, + NO_THREADING = 1, } resource_manager_flags :: bit_set[resource_manager_flag; u32] From f761dc21022da139656720eb6b3a5890b80d7ea6 Mon Sep 17 00:00:00 2001 From: wrapperup Date: Mon, 16 Dec 2024 22:58:12 -0500 Subject: [PATCH 03/64] cleanup redundant constants --- vendor/miniaudio/device_io_types.odin | 2 -- 1 file changed, 2 deletions(-) diff --git a/vendor/miniaudio/device_io_types.odin b/vendor/miniaudio/device_io_types.odin index 0571714d2..b52a3f423 100644 --- a/vendor/miniaudio/device_io_types.odin +++ b/vendor/miniaudio/device_io_types.odin @@ -357,8 +357,6 @@ data_format_flag :: enum c.int { data_format_flags :: bit_set[data_format_flag; u32] -DATA_FORMAT_FLAG_EXCLUSIVE_MODE :: data_format_flags{.EXCLUSIVE_MODE} - MAX_DEVICE_NAME_LENGTH :: 255 device_info :: struct { From 26f9688c69caa1ea8211da7ef072ea6ec292d8f7 Mon Sep 17 00:00:00 2001 From: misomosi Date: Sat, 21 Dec 2024 16:53:31 -0500 Subject: [PATCH 04/64] Fix early overwrite of dst w/ exp_u64 --- src/big_int.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/big_int.cpp b/src/big_int.cpp index 8e476f090..0b0a9a400 100644 --- a/src/big_int.cpp +++ b/src/big_int.cpp @@ -251,7 +251,10 @@ gb_internal void big_int_from_string(BigInt *dst, String const &s, bool *success exp *= 10; exp += v; } - big_int_exp_u64(dst, &b, exp, success); + BigInt tmp = {}; + mp_init(&tmp); + big_int_exp_u64(&tmp, &b, exp, success); + big_int_mul_eq(dst, &tmp); } if (is_negative) { From fa7ef28acf6443073c1429c7e6df400eeb8f36af Mon Sep 17 00:00:00 2001 From: jason Date: Fri, 10 Jan 2025 20:54:09 -0500 Subject: [PATCH 05/64] Implement _read_directory_iterator in os2. Also, fix minor bug in linux.dirent_name. --- core/os/os2/dir_linux.odin | 87 ++++++++++++++++++++++++++++++++++-- core/sys/linux/wrappers.odin | 28 +++++------- 2 files changed, 96 insertions(+), 19 deletions(-) diff --git a/core/os/os2/dir_linux.odin b/core/os/os2/dir_linux.odin index f26b4fc79..f7723936b 100644 --- a/core/os/os2/dir_linux.odin +++ b/core/os/os2/dir_linux.odin @@ -1,20 +1,101 @@ #+private package os2 +import "core:sys/linux" + Read_Directory_Iterator_Impl :: struct { - + prev_fi: File_Info, + dirent_backing: []u8, + dirent_buflen: int, + dirent_off: int, + index: int, } - @(require_results) _read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, index: int, ok: bool) { + scan_entries :: proc(dfd: linux.Fd, entries: []u8, offset: ^int) -> (fd: linux.Fd, file_name: string) { + for d in linux.dirent_iterate_buf(entries, offset) { + file_name = linux.dirent_name(d) + if file_name == "." || file_name == ".." { + continue + } + + file_name_cstr := cstring(raw_data(file_name)) + entry_fd, errno := linux.openat(dfd, file_name_cstr, {.NOFOLLOW, .PATH}) + if errno == .NONE { + return entry_fd, file_name + } + } + return -1, "" + } + + index = it.impl.index + it.impl.index += 1 + + dfd := linux.Fd(_fd(it.f)) + + entries := it.impl.dirent_backing[:it.impl.dirent_buflen] + entry_fd, file_name := scan_entries(dfd, entries, &it.impl.dirent_off) + + for entry_fd == -1 { + if len(it.impl.dirent_backing) == 0 { + it.impl.dirent_backing = make([]u8, 512, file_allocator()) + } + + loop: for { + buflen, errno := linux.getdents(linux.Fd(dfd), it.impl.dirent_backing[:]) + #partial switch errno { + case .EINVAL: + delete(it.impl.dirent_backing, file_allocator()) + n := len(it.impl.dirent_backing) * 2 + it.impl.dirent_backing = make([]u8, n, file_allocator()) + continue + case .NONE: + if buflen == 0 { + return + } + it.impl.dirent_off = 0 + it.impl.dirent_buflen = buflen + entries = it.impl.dirent_backing[:buflen] + break loop + case: // error + return + } + } + + entry_fd, file_name = scan_entries(dfd, entries, &it.impl.dirent_off) + } + defer linux.close(entry_fd) + + file_info_delete(it.impl.prev_fi, file_allocator()) + fi, _ = _fstat_internal(entry_fd, file_allocator()) + it.impl.prev_fi = fi + + ok = true return } @(require_results) _read_directory_iterator_create :: proc(f: ^File) -> (Read_Directory_Iterator, Error) { - return {}, .Unsupported + if f == nil || f.impl == nil { + return {}, .Invalid_File + } + + stat: linux.Stat + errno := linux.fstat(linux.Fd(fd(f)), &stat) + if errno != .NONE { + return {}, _get_platform_error(errno) + } + if (stat.mode & linux.S_IFMT) != linux.S_IFDIR { + return {}, .Invalid_Dir + } + return {f = f}, nil } _read_directory_iterator_destroy :: proc(it: ^Read_Directory_Iterator) { + if it == nil { + return + } + delete(it.impl.dirent_backing, file_allocator()) + file_info_delete(it.impl.prev_fi, file_allocator()) } diff --git a/core/sys/linux/wrappers.odin b/core/sys/linux/wrappers.odin index 4f6118c80..e367a4db4 100644 --- a/core/sys/linux/wrappers.odin +++ b/core/sys/linux/wrappers.odin @@ -86,22 +86,18 @@ dirent_iterate_buf :: proc "contextless" (buf: []u8, offs: ^int) -> (d: ^Dirent, /// The lifetime of the string is bound to the lifetime of the provided dirent structure dirent_name :: proc "contextless" (dirent: ^Dirent) -> string #no_bounds_check { str := ([^]u8)(&dirent.name) - // Note(flysand): The string size calculated above applies only to the ideal case - // we subtract 1 byte from the string size, because a null terminator is guaranteed - // to be present. But! That said, the dirents are aligned to 8 bytes and the padding - // between the null terminator and the start of the next struct may be not initialized - // which means we also have to scan these garbage bytes. - str_size := int(dirent.reclen) - 1 - cast(int)offset_of(Dirent, name) - // This skips *only* over the garbage, since if we're not garbage we're at nul terminator, - // which skips this loop - for str[str_size] != 0 { - str_size -= 1 + // Dirents are aligned to 8 bytes, so there is guaranteed to be a null + // terminator in the last 8 bytes. + str_size := int(dirent.reclen) - cast(int)offset_of(Dirent, name) + + trunc := min(str_size, 8) + str_size -= trunc + for i in 0.. u64 { return u64(id) | (u64(op) << 8) | (u64(res) << 16) -} \ No newline at end of file +} From c11dccf210f4992b0ec289d2a884aadc9625bab1 Mon Sep 17 00:00:00 2001 From: jason Date: Fri, 10 Jan 2025 20:59:48 -0500 Subject: [PATCH 06/64] make -vet happy --- core/sys/linux/wrappers.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/sys/linux/wrappers.odin b/core/sys/linux/wrappers.odin index e367a4db4..ab1992a57 100644 --- a/core/sys/linux/wrappers.odin +++ b/core/sys/linux/wrappers.odin @@ -92,7 +92,7 @@ dirent_name :: proc "contextless" (dirent: ^Dirent) -> string #no_bounds_check { trunc := min(str_size, 8) str_size -= trunc - for i in 0.. Date: Sat, 11 Jan 2025 15:03:12 +0100 Subject: [PATCH 07/64] vendor/glfw: add GetMonitorWorkarea binding --- vendor/glfw/bindings/bindings.odin | 1 + vendor/glfw/wrapper.odin | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/vendor/glfw/bindings/bindings.odin b/vendor/glfw/bindings/bindings.odin index a4be006b0..e59239483 100644 --- a/vendor/glfw/bindings/bindings.odin +++ b/vendor/glfw/bindings/bindings.odin @@ -71,6 +71,7 @@ foreign glfw { GetPrimaryMonitor :: proc() -> MonitorHandle --- GetMonitors :: proc(count: ^c.int) -> [^]MonitorHandle --- GetMonitorPos :: proc(monitor: MonitorHandle, xpos, ypos: ^c.int) --- + GetMonitorWorkarea :: proc(monitor: MonitorHandle, xpos, ypos, width, height: ^c.int) --- GetMonitorPhysicalSize :: proc(monitor: MonitorHandle, widthMM, heightMM: ^c.int) --- GetMonitorContentScale :: proc(monitor: MonitorHandle, xscale, yscale: ^f32) --- diff --git a/vendor/glfw/wrapper.odin b/vendor/glfw/wrapper.odin index fa9329aa7..854dcdf9a 100644 --- a/vendor/glfw/wrapper.odin +++ b/vendor/glfw/wrapper.odin @@ -33,6 +33,10 @@ GetMonitorPos :: proc "c" (monitor: MonitorHandle) -> (xpos, ypos: c.int) { glfw.GetMonitorPos(monitor, &xpos, &ypos) return } +GetMonitorWorkarea :: proc "c" (monitor: MonitorHandle) -> (xpos, ypos, width, height: c.int) { + glfw.GetMonitorWorkarea(monitor, &xpos, &ypos, &width, &height) + return +} GetMonitorPhysicalSize :: proc "c" (monitor: MonitorHandle) -> (widthMM, heightMM: c.int) { glfw.GetMonitorPhysicalSize(monitor, &widthMM, &heightMM) return From 600e0ebed0c8d1b27de266edcee5cc392cfc306a Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Sun, 12 Jan 2025 12:13:29 +0100 Subject: [PATCH 08/64] Fix stray space vs. tab --- core/encoding/base32/base32.odin | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/encoding/base32/base32.odin b/core/encoding/base32/base32.odin index 8629491b1..2267a872b 100644 --- a/core/encoding/base32/base32.odin +++ b/core/encoding/base32/base32.odin @@ -118,10 +118,10 @@ _encode :: proc(out, data: []byte, ENC_TBL := ENC_TABLE, allocator := context.al @(optimization_mode="favor_size") decode :: proc( - data: string, - DEC_TBL := DEC_TABLE, - validate: Validate_Proc = _validate_default, - allocator := context.allocator) -> (out: []byte, err: Error) { + data: string, + DEC_TBL := DEC_TABLE, + validate: Validate_Proc = _validate_default, + allocator := context.allocator) -> (out: []byte, err: Error) { if len(data) == 0 { return nil, .None } From 9d4fa39daa55d0fe775068c123cba8ce5f2950e9 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Mon, 13 Jan 2025 20:33:49 +0100 Subject: [PATCH 09/64] add ensure and ensuref to fmt and log, fix some inconsistencies --- core/fmt/fmt.odin | 26 ++++++++++++++++++++++++-- core/log/log.odin | 35 +++++++++++++++++++++++++++++++++-- 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/core/fmt/fmt.odin b/core/fmt/fmt.odin index 49e9f2e6d..da3b419d5 100644 --- a/core/fmt/fmt.odin +++ b/core/fmt/fmt.odin @@ -314,7 +314,29 @@ assertf :: proc(condition: bool, fmt: string, args: ..any, loc := #caller_locati p = runtime.default_assertion_failure_proc } message := tprintf(fmt, ..args) - p("Runtime assertion", message, loc) + p("runtime assertion", message, loc) + } + internal(loc, fmt, ..args) + } +} +// Runtime ensure with a formatted message +// +// Inputs: +// - condition: The boolean condition to be asserted +// - fmt: A format string with placeholders for the provided arguments +// - args: A variadic list of arguments to be formatted +// - loc: The location of the caller +// +ensuref :: proc(condition: bool, fmt: string, args: ..any, loc := #caller_location) { + if !condition { + @(cold) + internal :: proc(loc: runtime.Source_Code_Location, fmt: string, args: ..any) { + p := context.assertion_failure_proc + if p == nil { + p = runtime.default_assertion_failure_proc + } + message := tprintf(fmt, ..args) + p("unsatisfied ensure", message, loc) } internal(loc, fmt, ..args) } @@ -332,7 +354,7 @@ panicf :: proc(fmt: string, args: ..any, loc := #caller_location) -> ! { p = runtime.default_assertion_failure_proc } message := tprintf(fmt, ..args) - p("Panic", message, loc) + p("panic", message, loc) } // Creates a formatted C string diff --git a/core/log/log.odin b/core/log/log.odin index cbb2e922b..2b6317060 100644 --- a/core/log/log.odin +++ b/core/log/log.odin @@ -115,7 +115,7 @@ panicf :: proc(fmt_str: string, args: ..any, location := #caller_location) -> ! } @(disabled=ODIN_DISABLE_ASSERT) -assert :: proc(condition: bool, message := "", loc := #caller_location) { +assert :: proc(condition: bool, message := #caller_expression(condition), loc := #caller_location) { if !condition { @(cold) internal :: proc(message: string, loc: runtime.Source_Code_Location) { @@ -145,7 +145,38 @@ assertf :: proc(condition: bool, fmt_str: string, args: ..any, loc := #caller_lo } message := fmt.tprintf(fmt_str, ..args) log(.Fatal, message, location=loc) - p("Runtime assertion", message, loc) + p("runtime assertion", message, loc) + } + internal(loc, fmt_str, ..args) + } +} + +ensure :: proc(condition: bool, message := #caller_expression(condition), loc := #caller_location) { + if !condition { + @(cold) + internal :: proc(message: string, loc: runtime.Source_Code_Location) { + p := context.assertion_failure_proc + if p == nil { + p = runtime.default_assertion_failure_proc + } + log(.Fatal, message, location=loc) + p("unsatisfied ensure", message, loc) + } + internal(message, loc) + } +} + +ensuref :: proc(condition: bool, fmt_str: string, args: ..any, loc := #caller_location) { + if !condition { + @(cold) + internal :: proc(loc: runtime.Source_Code_Location, fmt_str: string, args: ..any) { + p := context.assertion_failure_proc + if p == nil { + p = runtime.default_assertion_failure_proc + } + message := fmt.tprintf(fmt_str, ..args) + log(.Fatal, message, location=loc) + p("unsatisfied ensure", message, loc) } internal(loc, fmt_str, ..args) } From 1613728a649dafbfe04f870b3813594604cea9c1 Mon Sep 17 00:00:00 2001 From: Karl Zylinski Date: Mon, 13 Jan 2025 23:37:36 +0100 Subject: [PATCH 10/64] d3d12 bindings -vet-tabs fix --- vendor/directx/d3d12/d3d12.odin | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/vendor/directx/d3d12/d3d12.odin b/vendor/directx/d3d12/d3d12.odin index 3e078a5ed..1110289e4 100644 --- a/vendor/directx/d3d12/d3d12.odin +++ b/vendor/directx/d3d12/d3d12.odin @@ -1580,14 +1580,14 @@ SHADER_COMPONENT_MAPPING :: enum u32 { FORCE_VALUE_1 = 5, } ENCODE_SHADER_4_COMPONENT_MAPPING :: #force_inline proc "contextless" (Src0, Src1, Src2, Src3: u32) -> u32 { - return (Src0 & SHADER_COMPONENT_MAPPING_MASK) | - ((Src1 & SHADER_COMPONENT_MAPPING_MASK) << SHADER_COMPONENT_MAPPING_SHIFT) | - ((Src2 & SHADER_COMPONENT_MAPPING_MASK) << (SHADER_COMPONENT_MAPPING_SHIFT * 2)) | - ((Src3 & SHADER_COMPONENT_MAPPING_MASK) << (SHADER_COMPONENT_MAPPING_SHIFT * 3)) | - SHADER_COMPONENT_MAPPING_ALWAYS_SET_BIT_AVOIDING_ZEROMEM_MISTAKES + return (Src0 & SHADER_COMPONENT_MAPPING_MASK) | + ((Src1 & SHADER_COMPONENT_MAPPING_MASK) << SHADER_COMPONENT_MAPPING_SHIFT) | + ((Src2 & SHADER_COMPONENT_MAPPING_MASK) << (SHADER_COMPONENT_MAPPING_SHIFT * 2)) | + ((Src3 & SHADER_COMPONENT_MAPPING_MASK) << (SHADER_COMPONENT_MAPPING_SHIFT * 3)) | + SHADER_COMPONENT_MAPPING_ALWAYS_SET_BIT_AVOIDING_ZEROMEM_MISTAKES } DECODE_SHADER_4_COMPONENT_MAPPING :: #force_inline proc "contextless" (ComponentToExtract, Mapping: u32) -> u32 { - return Mapping >> (SHADER_COMPONENT_MAPPING_SHIFT * ComponentToExtract) & SHADER_COMPONENT_MAPPING_MASK + return Mapping >> (SHADER_COMPONENT_MAPPING_SHIFT * ComponentToExtract) & SHADER_COMPONENT_MAPPING_MASK } BUFFER_SRV_FLAGS :: distinct bit_set[BUFFER_SRV_FLAG; u32] From 794e812932fb291e5dc8157e06e8a132c8231e17 Mon Sep 17 00:00:00 2001 From: Harold Brenes Date: Wed, 15 Jan 2025 02:04:49 -0500 Subject: [PATCH 11/64] Fixes crash when unused defines are used in conjunction with `-ignore-warnings`. --- src/main.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index 41c7170f6..1de5d987b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1801,7 +1801,10 @@ gb_internal void check_defines(BuildContext *bc, Checker *c) { if (!found) { ERROR_BLOCK(); warning(nullptr, "given -define:%.*s is unused in the project", LIT(name)); - error_line("\tSuggestion: use the -show-defineables flag for an overview of the possible defines\n"); + + if (!global_ignore_warnings()) { + error_line("\tSuggestion: use the -show-defineables flag for an overview of the possible defines\n"); + } } } } From a0c20023fc87e0cbd2a2316a2eb993edf67815cf Mon Sep 17 00:00:00 2001 From: alektron Date: Wed, 15 Jan 2025 17:59:30 +0100 Subject: [PATCH 12/64] Fix: Issue with non-zeroed memory after arena_temp_and; Fix: total_used field of growing Arena was not decremented correctly in arena_temp_end; --- base/runtime/default_temp_allocator_arena.odin | 3 ++- core/mem/virtual/arena.odin | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/base/runtime/default_temp_allocator_arena.odin b/base/runtime/default_temp_allocator_arena.odin index db157b267..878a2d070 100644 --- a/base/runtime/default_temp_allocator_arena.odin +++ b/base/runtime/default_temp_allocator_arena.odin @@ -282,9 +282,10 @@ arena_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) { if block := arena.curr_block; block != nil { assert(block.used >= temp.used, "out of order use of arena_temp_end", loc) - amount_to_zero := min(block.used-temp.used, block.capacity-block.used) + amount_to_zero := block.used-temp.used intrinsics.mem_zero(block.base[temp.used:], amount_to_zero) block.used = temp.used + arena.total_used -= amount_to_zero } } diff --git a/core/mem/virtual/arena.odin b/core/mem/virtual/arena.odin index 4a0fff241..675558ec8 100644 --- a/core/mem/virtual/arena.odin +++ b/core/mem/virtual/arena.odin @@ -402,9 +402,10 @@ arena_temp_end :: proc(temp: Arena_Temp, loc := #caller_location) { if block := arena.curr_block; block != nil { assert(block.used >= temp.used, "out of order use of arena_temp_end", loc) - amount_to_zero := min(block.used-temp.used, block.reserved-block.used) + amount_to_zero := block.used-temp.used mem.zero_slice(block.base[temp.used:][:amount_to_zero]) block.used = temp.used + arena.total_used -= amount_to_zero } } From a7971f9f6fbe782363b41686aea72073933f117c Mon Sep 17 00:00:00 2001 From: James Duran Date: Wed, 15 Jan 2025 11:02:46 -0800 Subject: [PATCH 13/64] Allow captures in gfind and gmatch to be used in-loop --- core/text/match/strlib.odin | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/core/text/match/strlib.odin b/core/text/match/strlib.odin index bfa696dcd..2068fffdd 100644 --- a/core/text/match/strlib.odin +++ b/core/text/match/strlib.odin @@ -682,11 +682,14 @@ find_aux :: proc( // iterative matching which returns the 0th/1st match // rest has to be used from captures +// assumes captures is zeroed on first iteration +// resets captures to zero on last iteration gmatch :: proc( haystack: ^string, pattern: string, captures: ^[MAX_CAPTURES]Match, ) -> (res: string, ok: bool) { + haystack^ = haystack[captures[0].byte_end:] if len(haystack) > 0 { length, err := find_aux(haystack^, pattern, 0, false, captures) @@ -695,7 +698,8 @@ gmatch :: proc( first := length > 1 ? 1 : 0 cap := captures[first] res = haystack[cap.byte_start:cap.byte_end] - haystack^ = haystack[cap.byte_end:] + } else { + captures^ = {} } } @@ -794,11 +798,14 @@ gsub_with :: proc( gsub :: proc { gsub_builder, gsub_allocator } // iterative find with zeroth capture only +// assumes captures is zeroed on first iteration +// resets captures to zero on last iteration gfind :: proc( haystack: ^string, pattern: string, captures: ^[MAX_CAPTURES]Match, ) -> (res: string, ok: bool) { + haystack^ = haystack[captures[0].byte_end:] if len(haystack) > 0 { length, err := find_aux(haystack^, pattern, 0, true, captures) @@ -806,7 +813,8 @@ gfind :: proc( ok = true cap := captures[0] res = haystack[cap.byte_start:cap.byte_end] - haystack^ = haystack[cap.byte_end:] + } else { + captures^ = {} } } From 4895065afb5630f0f8d55cb863b8ddad41189ea9 Mon Sep 17 00:00:00 2001 From: teapo <75266237+4teapo@users.noreply.github.com> Date: Wed, 15 Jan 2025 20:16:57 +0100 Subject: [PATCH 14/64] Add SoA make/delete to core:mem --- core/mem/alloc.odin | 97 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 94 insertions(+), 3 deletions(-) diff --git a/core/mem/alloc.odin b/core/mem/alloc.odin index fac58daaf..1094e7381 100644 --- a/core/mem/alloc.odin +++ b/core/mem/alloc.odin @@ -785,6 +785,27 @@ delete_map :: proc( return runtime.delete_map(m, loc) } +/* +Free an SoA slice. +*/ +delete_soa_slice :: proc( + array: $T/#soa[]$E, + allocator := context.allocator, + loc := #caller_location, +) -> Allocator_Error { + return runtime.delete_soa_slice(array, allocator, loc) +} + +/* +Free an SoA dynamic array. +*/ +delete_soa_dynamic_array :: proc( + array: $T/#soa[dynamic]$E, + loc := #caller_location, +) -> Allocator_Error { + return runtime.delete_soa_dynamic_array(array, loc) +} + /* Free. */ @@ -794,6 +815,8 @@ delete :: proc{ delete_dynamic_array, delete_slice, delete_map, + delete_soa_slice, + delete_soa_dynamic_array, } /* @@ -900,8 +923,7 @@ make_dynamic_array :: proc( Allocate a dynamic array with initial length. This procedure creates a dynamic array of type `T`, with `allocator` as its -backing allocator, and initial capacity of `0`, and initial length specified by -`len`. +backing allocator, and initial capacity and length specified by `len`. */ @(require_results) make_dynamic_array_len :: proc( @@ -910,7 +932,7 @@ make_dynamic_array_len :: proc( allocator := context.allocator, loc := #caller_location, ) -> (T, Allocator_Error) { - return runtime.make_dynamic_array_len_cap(T, len, len, allocator, loc) + return runtime.make_dynamic_array_len(T, len, allocator, loc) } /* @@ -964,6 +986,71 @@ make_multi_pointer :: proc( return runtime.make_multi_pointer(T, len, allocator, loc) } +/* +Allocate an SoA slice. + +This procedure allocates an SoA slice of type `T` with length `len`, from an +allocator specified by `allocator`, and returns the allocated SoA slice. +*/ +@(require_results) +make_soa_slice :: proc( + $T: typeid/#soa[]$E, + #any_int len: int, + allocator := context.allocator, + loc := #caller_location +) -> (array: T, err: Allocator_Error) { + return runtime.make_soa_slice(T, len, allocator, loc) +} + +/* +Allocate an SoA dynamic array. + +This procedure creates an SoA dynamic array of type `T`, with `allocator` as +its backing allocator, and initial length and capacity of `0`. +*/ +@(require_results) +make_soa_dynamic_array :: proc( + $T: typeid/#soa[dynamic]$E, + allocator := context.allocator, + loc := #caller_location +) -> (array: T, err: Allocator_Error) { + return runtime.make_soa_dynamic_array(T, allocator, loc) +} + +/* +Allocate an SoA dynamic array with initial length. + +This procedure creates an SoA dynamic array of type `T`, with `allocator` as its +backing allocator, and initial capacity and length specified by `len`. +*/ +@(require_results) +make_soa_dynamic_array_len :: proc( + $T: typeid/#soa[dynamic]$E, + #any_int len: int, + allocator := context.allocator, + loc := #caller_location +) -> (array: T, err: Allocator_Error) { + return runtime.make_soa_dynamic_array_len(T, len, allocator, loc) +} + +/* +Allocate an SoA dynamic array with initial length and capacity. + +This procedure creates an SoA dynamic array of type `T`, with `allocator` as its +backing allocator, and initial capacity specified by `cap`, and initial length +specified by `len`. +*/ +@(require_results) +make_soa_dynamic_array_len_cap :: proc( + $T: typeid/#soa[dynamic]$E, + #any_int len: int, + #any_int cap: int, + allocator := context.allocator, + loc := #caller_location +) -> (array: T, err: Allocator_Error) { + return runtime.make_soa_dynamic_array_len_cap(T, len, cap, allocator, loc) +} + /* Allocate. */ @@ -974,6 +1061,10 @@ make :: proc{ make_dynamic_array_len_cap, make_map, make_multi_pointer, + make_soa_slice, + make_soa_dynamic_array, + make_soa_dynamic_array_len, + make_soa_dynamic_array_len_cap, } /* From aa3f0b86c143802d9e81122698e38361751c7a68 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Wed, 15 Jan 2025 20:14:23 +0100 Subject: [PATCH 15/64] compiler: fix align error check --- src/check_type.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/check_type.cpp b/src/check_type.cpp index 44108ccbe..4d9101c6c 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -685,7 +685,8 @@ gb_internal void check_struct_type(CheckerContext *ctx, Type *struct_type, Ast * ST_ALIGN(min_field_align); ST_ALIGN(max_field_align); ST_ALIGN(align); - if (struct_type->Struct.custom_align < struct_type->Struct.custom_min_field_align) { + if (struct_type->Struct.custom_align != 0 && + struct_type->Struct.custom_align < struct_type->Struct.custom_min_field_align) { error(st->align, "#align(%lld) is defined to be less than #min_field_align(%lld)", cast(long long)struct_type->Struct.custom_align, cast(long long)struct_type->Struct.custom_min_field_align); From 13640620ce64aa09736cd75cb43a10934f916886 Mon Sep 17 00:00:00 2001 From: James Duran Date: Wed, 15 Jan 2025 15:56:40 -0800 Subject: [PATCH 16/64] Fix captures not begin zeroed when haystack length is 0 --- core/text/match/strlib.odin | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/core/text/match/strlib.odin b/core/text/match/strlib.odin index 2068fffdd..819f464c5 100644 --- a/core/text/match/strlib.odin +++ b/core/text/match/strlib.odin @@ -698,11 +698,11 @@ gmatch :: proc( first := length > 1 ? 1 : 0 cap := captures[first] res = haystack[cap.byte_start:cap.byte_end] - } else { - captures^ = {} } } - + if !ok { + captures^ = {} + } return } @@ -813,11 +813,11 @@ gfind :: proc( ok = true cap := captures[0] res = haystack[cap.byte_start:cap.byte_end] - } else { - captures^ = {} } } - + if !ok { + captures^ = {} + } return } From 9da144157eca6519dcc5fc254bf2471908a9a770 Mon Sep 17 00:00:00 2001 From: flysand7 Date: Thu, 16 Jan 2025 19:33:09 +0300 Subject: [PATCH 17/64] [sync]: Fix typos in comments and remove my note. --- core/sync/futex_linux.odin | 3 --- core/sync/futex_windows.odin | 4 ++-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/core/sync/futex_linux.odin b/core/sync/futex_linux.odin index 52143880b..2f4b5af72 100644 --- a/core/sync/futex_linux.odin +++ b/core/sync/futex_linux.odin @@ -49,9 +49,6 @@ _futex_signal :: proc "contextless" (futex: ^Futex) { } _futex_broadcast :: proc "contextless" (futex: ^Futex) { - // NOTE(flysand): This code was kinda funny and I don't want to remove it, but here I will - // record history of what has been in here before - // FUTEX_WAKE_PRIVATE | FUTEX_WAKE _, errno := linux.futex(cast(^linux.Futex) futex, linux.FUTEX_WAKE, {.PRIVATE}, max(i32)) #partial switch errno { case .NONE: diff --git a/core/sync/futex_windows.odin b/core/sync/futex_windows.odin index bb9686a1a..927e6781e 100644 --- a/core/sync/futex_windows.odin +++ b/core/sync/futex_windows.odin @@ -26,7 +26,7 @@ foreign Ntdll { BUT requires taking the return value of it and if it is non-zero converting that status to a DOS error and then SetLastError If this is not done, then things don't work as expected when - and error occurs + an error occurs GODDAMN MICROSOFT! */ @@ -46,7 +46,7 @@ _futex_wait :: proc "contextless" (f: ^Futex, expect: u32) -> bool { _futex_wait_with_timeout :: proc "contextless" (f: ^Futex, expect: u32, duration: time.Duration) -> bool { expect := expect - // NOTE(bill): for some bizarre reason, this has be a negative number + // NOTE(bill): for some bizarre reason, this has to be a negative number timeout := -i64(duration / 100) return CustomWaitOnAddress(f, &expect, size_of(expect), &timeout) } From 87b590c99bb30066f47683f2481b44b8d8226a37 Mon Sep 17 00:00:00 2001 From: flysand7 Date: Thu, 16 Jan 2025 20:07:56 +0300 Subject: [PATCH 18/64] Do not warn about stack overflow in range loops 'by reference' --- src/checker.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/checker.cpp b/src/checker.cpp index 5d3263789..85077a5c5 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -749,9 +749,15 @@ gb_internal void check_scope_usage_internal(Checker *c, Scope *scope, u64 vet_fl // TODO(bill): When is a good size warn? // Is >256 KiB good enough? if (sz > 1ll<<18) { - gbString type_str = type_to_string(e->type); - warning(e->token, "Declaration of '%.*s' may cause a stack overflow due to its type '%s' having a size of %lld bytes", LIT(e->token.string), type_str, cast(long long)sz); - gb_string_free(type_str); + bool is_ref = false; + if((e->flags & EntityFlag_ForValue) != 0) { + is_ref = type_deref(e->Variable.for_loop_parent_type) != NULL; + } + if(!is_ref) { + gbString type_str = type_to_string(e->type); + warning(e->token, "Declaration of '%.*s' may cause a stack overflow due to its type '%s' having a size of %lld bytes", LIT(e->token.string), type_str, cast(long long)sz); + gb_string_free(type_str); + } } } } From 4f0206ce08593628bf9458b623f61c2989558f69 Mon Sep 17 00:00:00 2001 From: flysand7 Date: Fri, 17 Jan 2025 01:12:23 +0300 Subject: [PATCH 19/64] Added compile-time checks for thread locals with -no-crt Now using any thread-local variables with -no-crt enabled will cause a compiler error, unless -no-thread-local is given. Also fixed a minor typo in a comment. --- src/build_settings.cpp | 16 +++++++++++++++- src/check_expr.cpp | 15 ++++++++++++++- src/main.cpp | 4 ++++ 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 93168cf77..b3321637f 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -2133,7 +2133,21 @@ gb_internal bool init_build_paths(String init_filename) { case TargetOs_openbsd: case TargetOs_netbsd: case TargetOs_haiku: - gb_printf_err("-no-crt on unix systems requires either -default-to-nil-allocator or -default-to-panic-allocator to also be present because the default allocator requires crt\n"); + gb_printf_err("-no-crt on unix systems requires either -default-to-nil-allocator or -default-to-panic-allocator to also be present, because the default allocator requires crt\n"); + return false; + } + } + + if (build_context.no_crt && !build_context.no_thread_local && !build_context.ODIN_DEFAULT_TO_NIL_ALLOCATOR) { + switch (build_context.metrics.os) { + case TargetOs_linux: + case TargetOs_darwin: + case TargetOs_essence: + case TargetOs_freebsd: + case TargetOs_openbsd: + case TargetOs_netbsd: + case TargetOs_haiku: + gb_printf_err("-no-crt on unix systems requires either -default-to-nil-allocator or -no-thread-local to also be present, because the temporary allocator is a thread local, which are inaccessible without CRT initializing TLS\n"); return false; } } diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 231ece2f4..7574c20a7 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -1044,7 +1044,7 @@ gb_internal AstPackage *get_package_of_type(Type *type) { } -// NOTE(bill): 'content_name' is for debugging and error messages +// NOTE(bill): 'context_name' is for debugging and error messages gb_internal void check_assignment(CheckerContext *c, Operand *operand, Type *type, String context_name) { check_not_tuple(c, operand); if (operand->mode == Addressing_Invalid) { @@ -1822,6 +1822,19 @@ gb_internal Entity *check_ident(CheckerContext *c, Operand *o, Ast *n, Type *nam break; case Entity_Variable: + if (e->kind == Entity_Variable && build_context.no_crt && !build_context.no_thread_local && e->Variable.thread_local_model != "") { + switch (build_context.metrics.os) { + case TargetOs_linux: + case TargetOs_darwin: + case TargetOs_essence: + case TargetOs_freebsd: + case TargetOs_openbsd: + case TargetOs_netbsd: + case TargetOs_haiku: + Token token = ast_token(n); + error(token, "Illegal usage of thread locals: '%.*s'", LIT(e->token.string)); + } + } e->flags |= EntityFlag_Used; if (type == t_invalid) { o->type = t_invalid; diff --git a/src/main.cpp b/src/main.cpp index 1de5d987b..24e33850e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2334,6 +2334,10 @@ gb_internal void print_show_help(String const arg0, String command, String optio print_usage_line(2, "Sets the default allocator to be the nil_allocator, an allocator which does nothing."); } + if (print_flag("-default-to-panic-allocator")) { + print_usage_line(2, "Sets the default allocator to be the panic_allocator, an allocator which calls panic() on any allocation attempt."); + } + if (print_flag("-define:=")) { print_usage_line(2, "Defines a scalar boolean, integer or string as global constant."); print_usage_line(2, "Example: -define:SPAM=123"); From 3f20b6324353cfb9e3ad27fe9ee5f8d07148911b Mon Sep 17 00:00:00 2001 From: flysand7 Date: Fri, 17 Jan 2025 02:15:30 +0300 Subject: [PATCH 20/64] Error if -no-thread-local is used in presence of -no-crt on Unix --- src/build_settings.cpp | 15 ++++++++++----- src/check_expr.cpp | 13 ------------- 2 files changed, 10 insertions(+), 18 deletions(-) diff --git a/src/build_settings.cpp b/src/build_settings.cpp index b3321637f..a8d06d56d 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -2124,6 +2124,7 @@ gb_internal bool init_build_paths(String init_filename) { } } + bool no_crt_checks_failed = false; if (build_context.no_crt && !build_context.ODIN_DEFAULT_TO_NIL_ALLOCATOR && !build_context.ODIN_DEFAULT_TO_PANIC_ALLOCATOR) { switch (build_context.metrics.os) { case TargetOs_linux: @@ -2133,12 +2134,12 @@ gb_internal bool init_build_paths(String init_filename) { case TargetOs_openbsd: case TargetOs_netbsd: case TargetOs_haiku: - gb_printf_err("-no-crt on unix systems requires either -default-to-nil-allocator or -default-to-panic-allocator to also be present, because the default allocator requires crt\n"); - return false; + gb_printf_err("-no-crt on Unix systems requires either -default-to-nil-allocator or -default-to-panic-allocator to also be present, because the default allocator requires CRT\n"); + no_crt_checks_failed = true; } } - if (build_context.no_crt && !build_context.no_thread_local && !build_context.ODIN_DEFAULT_TO_NIL_ALLOCATOR) { + if (build_context.no_crt && !build_context.no_thread_local) { switch (build_context.metrics.os) { case TargetOs_linux: case TargetOs_darwin: @@ -2147,11 +2148,15 @@ gb_internal bool init_build_paths(String init_filename) { case TargetOs_openbsd: case TargetOs_netbsd: case TargetOs_haiku: - gb_printf_err("-no-crt on unix systems requires either -default-to-nil-allocator or -no-thread-local to also be present, because the temporary allocator is a thread local, which are inaccessible without CRT initializing TLS\n"); - return false; + gb_printf_err("-no-crt on Unix systems requires the -no-thread-local flag to also be present, because the TLS is inaccessible without CRT\n"); + no_crt_checks_failed = true; } } + if (no_crt_checks_failed) { + return false; + } + return true; } diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 7574c20a7..349c5dbae 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -1822,19 +1822,6 @@ gb_internal Entity *check_ident(CheckerContext *c, Operand *o, Ast *n, Type *nam break; case Entity_Variable: - if (e->kind == Entity_Variable && build_context.no_crt && !build_context.no_thread_local && e->Variable.thread_local_model != "") { - switch (build_context.metrics.os) { - case TargetOs_linux: - case TargetOs_darwin: - case TargetOs_essence: - case TargetOs_freebsd: - case TargetOs_openbsd: - case TargetOs_netbsd: - case TargetOs_haiku: - Token token = ast_token(n); - error(token, "Illegal usage of thread locals: '%.*s'", LIT(e->token.string)); - } - } e->flags |= EntityFlag_Used; if (type == t_invalid) { o->type = t_invalid; From 19d6c01f0f9ba0a6fc4f70fd5a3efbde9d2e0f28 Mon Sep 17 00:00:00 2001 From: Samuel Elgozi Date: Fri, 17 Jan 2025 10:42:32 +0200 Subject: [PATCH 21/64] Added Foundation bindings --- core/sys/darwin/Foundation/NSBlock.odin | 4 +++ core/sys/darwin/Foundation/NSData.odin | 17 ++++++++++ core/sys/darwin/Foundation/NSDate.odin | 5 +++ core/sys/darwin/Foundation/NSMenu.odin | 16 ++++++++++ core/sys/darwin/Foundation/NSToolbar.odin | 14 ++++++++ core/sys/darwin/Foundation/NSURL.odin | 5 +++ core/sys/darwin/Foundation/NSURLRequest.odin | 24 ++++++++++++++ core/sys/darwin/Foundation/NSURLResponse.odin | 19 +++++++++++ core/sys/darwin/Foundation/NSWindow.odin | 32 +++++++++++++++++++ 9 files changed, 136 insertions(+) create mode 100644 core/sys/darwin/Foundation/NSToolbar.odin create mode 100644 core/sys/darwin/Foundation/NSURLRequest.odin create mode 100644 core/sys/darwin/Foundation/NSURLResponse.odin diff --git a/core/sys/darwin/Foundation/NSBlock.odin b/core/sys/darwin/Foundation/NSBlock.odin index b9d94bfee..b7aa52bbf 100644 --- a/core/sys/darwin/Foundation/NSBlock.odin +++ b/core/sys/darwin/Foundation/NSBlock.odin @@ -25,6 +25,10 @@ Block_createLocalWithParam :: proc (user_data: rawptr, user_proc: proc "c" (user b, _ := Block_createInternalWithParam(false, user_data, user_proc, {}) return b } +@(objc_type = Block, objc_name = "invoke") +Block_invoke :: proc "c" (self: ^Block, args: ..any) -> ^Object { + return msgSend(^Object, self, "invoke:", ..args) +} @(private) Internal_Block_Literal_Base :: struct { diff --git a/core/sys/darwin/Foundation/NSData.odin b/core/sys/darwin/Foundation/NSData.odin index 04c1ce25d..38297ea9e 100644 --- a/core/sys/darwin/Foundation/NSData.odin +++ b/core/sys/darwin/Foundation/NSData.odin @@ -13,6 +13,23 @@ Data_init :: proc "c" (self: ^Data) -> ^Data { return msgSend(^Data, self, "init") } +@(objc_type = Data, objc_name = "initWithBytes") +Data_initWithBytes :: proc "c" (self: ^Data, bytes: []byte) -> ^Data { + return msgSend(^Data, self, "initWithBytes:length:", raw_data(bytes), len(bytes)) +} + +@(objc_type = Data, objc_name = "initWithBytesNoCopy") +Data_initWithBytesNoCopy :: proc "c" (self: ^Data, bytes: []byte, freeWhenDone: ns.BOOL) -> ^Data { + return msgSend( + ^Data, + self, + "initWithBytesNoCopy:length:freeWhenDone:", + raw_data(bytes), + len(bytes), + freeWhenDone, + ) +} + @(objc_type=Data, objc_name="mutableBytes") Data_mutableBytes :: proc "c" (self: ^Data) -> rawptr { return msgSend(rawptr, self, "mutableBytes") diff --git a/core/sys/darwin/Foundation/NSDate.odin b/core/sys/darwin/Foundation/NSDate.odin index 41efb0cf5..4ba539aa4 100644 --- a/core/sys/darwin/Foundation/NSDate.odin +++ b/core/sys/darwin/Foundation/NSDate.odin @@ -18,6 +18,11 @@ Date_dateWithTimeIntervalSinceNow :: proc "c" (secs: TimeInterval) -> ^Date { return msgSend(^Date, Date, "dateWithTimeIntervalSinceNow:", secs) } +@(objc_type=Date, objc_name="timeIntervalSince1970") +Date_timeIntervalSince1970 :: proc "c" (self: ^Date) -> f64 { + return msgSend(f64, self, "timeIntervalSince1970") +} + @(objc_type=Date, objc_name="distantFuture", objc_is_class_method=true) Date_distantFuture :: proc "c" () -> ^Date { return msgSend(^Date, Date, "distantFuture") diff --git a/core/sys/darwin/Foundation/NSMenu.odin b/core/sys/darwin/Foundation/NSMenu.odin index e49162a7f..9a74151b0 100644 --- a/core/sys/darwin/Foundation/NSMenu.odin +++ b/core/sys/darwin/Foundation/NSMenu.odin @@ -30,6 +30,7 @@ MenuItem :: struct {using _: Object} MenuItem_alloc :: proc "c" () -> ^MenuItem { return msgSend(^MenuItem, MenuItem, "alloc") } + @(objc_type=MenuItem, objc_name="registerActionCallback", objc_is_class_method=true) MenuItem_registerActionCallback :: proc "c" (name: cstring, callback: MenuItemCallback) -> SEL { s := string(name) @@ -50,11 +51,21 @@ MenuItem_registerActionCallback :: proc "c" (name: cstring, callback: MenuItemCa return sel } +@(objc_type=MenuItem, objc_name="separatorItem", objc_is_class_method=true) +MenuItem_separatorItem :: proc "c" () -> ^MenuItem { + return msgSend(^MenuItem, MenuItem, "separatorItem") +} + @(objc_type=MenuItem, objc_name="init") MenuItem_init :: proc "c" (self: ^MenuItem) -> ^MenuItem { return msgSend(^MenuItem, self, "init") } +@(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) @@ -75,6 +86,11 @@ 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") diff --git a/core/sys/darwin/Foundation/NSToolbar.odin b/core/sys/darwin/Foundation/NSToolbar.odin new file mode 100644 index 000000000..be6613df4 --- /dev/null +++ b/core/sys/darwin/Foundation/NSToolbar.odin @@ -0,0 +1,14 @@ +package objc_Foundation +@(objc_class = "NSToolbar") + +Toolbar :: struct { using _: Object } + +@(objc_type = Toolbar, objc_name = "alloc", objc_is_class_method = true) +Toolbar_alloc :: proc "c" () -> ^Toolbar { + return msgSend(^Toolbar, Toolbar, "alloc") +} + +@(objc_type = Toolbar, objc_name = "init") +Toolbar_init :: proc "c" (self: ^Toolbar) -> ^Toolbar { + return msgSend(^Toolbar, self, "init") +} diff --git a/core/sys/darwin/Foundation/NSURL.odin b/core/sys/darwin/Foundation/NSURL.odin index 9e9081219..fb9ebca9e 100644 --- a/core/sys/darwin/Foundation/NSURL.odin +++ b/core/sys/darwin/Foundation/NSURL.odin @@ -28,3 +28,8 @@ URL_initFileURLWithPath :: proc "c" (self: ^URL, path: ^String) -> ^URL { URL_fileSystemRepresentation :: proc "c" (self: ^URL) -> cstring { return msgSend(cstring, self, "fileSystemRepresentation") } + +@(objc_type=URL, objc_name="relativePath") +URL_relativePath :: proc "c" (self: ^URL) -> ^String { + return msgSend(^String, self, "relativePath") +} diff --git a/core/sys/darwin/Foundation/NSURLRequest.odin b/core/sys/darwin/Foundation/NSURLRequest.odin new file mode 100644 index 000000000..6b2819c67 --- /dev/null +++ b/core/sys/darwin/Foundation/NSURLRequest.odin @@ -0,0 +1,24 @@ +package objc_Foundation + +@(objc_class = "URLRequest") +URLRequest :: struct { using _: Object } + +@(objc_type = URLRequest, objc_name = "alloc", objc_is_class_method = true) +URLRequest_alloc :: proc "c" () -> ^URLRequest { + return msgSend(^URLRequest, URLRequest, "alloc") +} + +@(objc_type = URLRequest, objc_name = "requestWithURL", objc_is_class_method = true) +URLRequest_requestWithURL :: proc "c" (url: ^URL) -> ^URLRequest { + return msgSend(^URLRequest, URLRequest, "requestWithURL:", url) +} + +@(objc_type = URLRequest, objc_name = "init") +URLRequest_init :: proc "c" (self: ^URLRequest) -> ^URLRequest { + return msgSend(^URLRequest, URLRequest, "init") +} + +@(objc_type = URLRequest, objc_name = "url") +URLRequest_url :: proc "c" (self: ^URLRequest) -> ^URL { + return msgSend(^URL, self, "URL") +} \ No newline at end of file diff --git a/core/sys/darwin/Foundation/NSURLResponse.odin b/core/sys/darwin/Foundation/NSURLResponse.odin new file mode 100644 index 000000000..6295817e8 --- /dev/null +++ b/core/sys/darwin/Foundation/NSURLResponse.odin @@ -0,0 +1,19 @@ +package objc_Foundation + +@(objc_class = "NSURLResponse") +URLResponse :: struct { using _: Object } + +@(objc_type = URLResponse, objc_name = "alloc", objc_is_class_method = true) +URLResponse_alloc :: proc "c" () -> ^URLResponse { + return msgSend(^URLResponse, URLResponse, "alloc") +} + +@(objc_type = URLResponse, objc_name = "init") +URLResponse_init :: proc "c" (self: ^URLResponse) -> ^URLResponse { + return msgSend(^URLResponse, URLResponse, "init") +} + +@(objc_type = URLResponse, objc_name = "initWithURL") +URLResponse_initWithURL :: proc "c" (self: ^URLResponse, url: ^URL, mime_type: ^String, length: int, encoding: ^String ) -> ^URLResponse { + return msgSend(^URLResponse, self, "initWithURL:MIMEType:expectedContentLength:textEncodingName:", url, mime_type, Integer(length), encoding) +} \ No newline at end of file diff --git a/core/sys/darwin/Foundation/NSWindow.odin b/core/sys/darwin/Foundation/NSWindow.odin index 0fe334207..57ac2b6f6 100644 --- a/core/sys/darwin/Foundation/NSWindow.odin +++ b/core/sys/darwin/Foundation/NSWindow.odin @@ -129,6 +129,10 @@ WindowDelegateTemplate :: struct { windowDidExitVersionBrowser: proc(notification: ^Notification), } +Window_Title_Visibility :: enum UInteger { + Visible, + Hidden, +} WindowDelegate :: struct { using _: Object } // This is not the same as NSWindowDelegate _WindowDelegateInternal :: struct { @@ -616,6 +620,10 @@ View_setWantsLayer :: proc "c" (self: ^View, wantsLayer: BOOL) { View_convertPointFromView :: proc "c" (self: ^View, point: Point, view: ^View) -> Point { return msgSend(Point, self, "convertPoint:fromView:", point, view) } +@(objc_type=View, objc_name="addSubview") +View_addSubview :: proc "c" (self: ^View, view: ^View) { + msgSend(nil, self, "addSubview:", view) +} @(objc_class="NSWindow") Window :: struct {using _: Responder} @@ -748,4 +756,28 @@ Window_hasTitleBar :: proc "c" (self: ^Window) -> BOOL { @(objc_type=Window, objc_name="orderedIndex") Window_orderedIndex :: proc "c" (self: ^Window) -> Integer { return msgSend(Integer, self, "orderedIndex") +} +@(objc_type=Window, objc_name="setMinSize") +Window_setMinSize :: proc "c" (self: ^Window, size: Size) { + msgSend(nil, self, "setMinSize:", size) +} +@(objc_type=Window, objc_name="setTitleVisibility") +Window_setTitleVisibility :: proc "c" (self: ^Window, visibility: Window_Title_Visibility) { + msgSend(nil, self, "setTitleVisibility:", visibility) +} +@(objc_type=Window, objc_name="performZoom") +Window_performZoom :: proc "c" (self: ^Window) { + msgSend(nil, self, "performZoom:", self) +} +@(objc_type=Window, objc_name="setFrameAutosaveName") +NSWindow_setFrameAutosaveName :: proc "c" (self: ^Window, name: ^String) { + msgSend(nil, self, "setFrameAutosaveName:", name) +} +@(objc_type=Window, objc_name="performWindowDragWithEvent") +Window_performWindowDragWithEvent :: proc "c" (self: ^Window, event: ^Event) { + msgSend(nil, self, "performWindowDragWithEvent:", event) +} +@(objc_type=Window, objc_name="setToolbar") +Window_setToolbar :: proc "c" (self: ^Window, toolbar: ^Toolbar) { + msgSend(nil, self, "setToolbar:", toolbar) } \ No newline at end of file From 3fb766f98d25c2051b27e80790be2065c3bf175c Mon Sep 17 00:00:00 2001 From: Samuel Elgozi Date: Fri, 17 Jan 2025 13:43:34 +0200 Subject: [PATCH 22/64] updated to meet formatting style --- core/sys/darwin/Foundation/NSBlock.odin | 2 +- core/sys/darwin/Foundation/NSData.odin | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/sys/darwin/Foundation/NSBlock.odin b/core/sys/darwin/Foundation/NSBlock.odin index b7aa52bbf..1ef5e8a9b 100644 --- a/core/sys/darwin/Foundation/NSBlock.odin +++ b/core/sys/darwin/Foundation/NSBlock.odin @@ -25,7 +25,7 @@ Block_createLocalWithParam :: proc (user_data: rawptr, user_proc: proc "c" (user b, _ := Block_createInternalWithParam(false, user_data, user_proc, {}) return b } -@(objc_type = Block, objc_name = "invoke") +@(objc_type=Block, objc_name="invoke") Block_invoke :: proc "c" (self: ^Block, args: ..any) -> ^Object { return msgSend(^Object, self, "invoke:", ..args) } diff --git a/core/sys/darwin/Foundation/NSData.odin b/core/sys/darwin/Foundation/NSData.odin index 38297ea9e..f17d008a3 100644 --- a/core/sys/darwin/Foundation/NSData.odin +++ b/core/sys/darwin/Foundation/NSData.odin @@ -13,12 +13,12 @@ Data_init :: proc "c" (self: ^Data) -> ^Data { return msgSend(^Data, self, "init") } -@(objc_type = Data, objc_name = "initWithBytes") +@(objc_type=Data, objc_name="initWithBytes") Data_initWithBytes :: proc "c" (self: ^Data, bytes: []byte) -> ^Data { return msgSend(^Data, self, "initWithBytes:length:", raw_data(bytes), len(bytes)) } -@(objc_type = Data, objc_name = "initWithBytesNoCopy") +@(objc_type=Data, objc_name="initWithBytesNoCopy") Data_initWithBytesNoCopy :: proc "c" (self: ^Data, bytes: []byte, freeWhenDone: ns.BOOL) -> ^Data { return msgSend( ^Data, From 8e9726866a0e4ad043e3d4e7a9753bff5747d56e Mon Sep 17 00:00:00 2001 From: Laytan Date: Fri, 17 Jan 2025 18:31:39 +0100 Subject: [PATCH 23/64] remove other redundant constant --- vendor/miniaudio/utilities.odin | 1 - 1 file changed, 1 deletion(-) diff --git a/vendor/miniaudio/utilities.odin b/vendor/miniaudio/utilities.odin index d9d23ad83..9285874b6 100644 --- a/vendor/miniaudio/utilities.odin +++ b/vendor/miniaudio/utilities.odin @@ -125,7 +125,6 @@ data_source_flag :: enum c.int { data_source_flags :: bit_set[data_source_flag; u32] -DATA_SOURCE_SELF_MANAGED_RANGE_AND_LOOP_POINT :: data_source_flags{.SELF_MANAGED_RANGE_AND_LOOP_POINT} data_source_vtable :: struct { onRead: proc "c" (pDataSource: ^data_source, pFramesOut: rawptr, frameCount: u64, pFramesRead: ^u64) -> result, From 5951c25f71b7703d0bd70614a818c37ad82e6c37 Mon Sep 17 00:00:00 2001 From: David Rubin Date: Sat, 18 Jan 2025 02:04:35 -0800 Subject: [PATCH 24/64] fix inverted error messages --- src/check_expr.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 349c5dbae..8e4d60d8c 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -1973,10 +1973,10 @@ gb_internal bool check_binary_op(CheckerContext *c, Operand *o, Token op) { case Token_Quo: case Token_QuoEq: if (is_type_matrix(main_type)) { - error(op, "Operator '%.*s' is only allowed with matrix types", LIT(op.string)); + error(op, "Operator '%.*s' is not allowed with matrix types", LIT(op.string)); return false; } else if (is_type_simd_vector(main_type) && is_type_integer(type)) { - error(op, "Operator '%.*s' is only allowed with #simd types with integer elements", LIT(op.string)); + error(op, "Operator '%.*s' is not allowed with #simd types with integer elements", LIT(op.string)); return false; } /*fallthrough*/ @@ -2023,14 +2023,14 @@ gb_internal bool check_binary_op(CheckerContext *c, Operand *o, Token op) { case Token_ModEq: case Token_ModModEq: if (is_type_matrix(main_type)) { - error(op, "Operator '%.*s' is only allowed with matrix types", LIT(op.string)); + error(op, "Operator '%.*s' is not allowed with matrix types", LIT(op.string)); return false; } if (!is_type_integer(type)) { error(op, "Operator '%.*s' is only allowed with integers", LIT(op.string)); return false; } else if (is_type_simd_vector(main_type)) { - error(op, "Operator '%.*s' is only allowed with #simd types with integer elements", LIT(op.string)); + error(op, "Operator '%.*s' is not allowed with #simd types with integer elements", LIT(op.string)); return false; } break; From 4de5911a15243529a8db444bdc47e2ad493c5df8 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Sat, 18 Jan 2025 14:02:27 +0100 Subject: [PATCH 25/64] fix map_entry sometimes giving wrong key pointer `map_desired_position` does not return the actual position, probing must be done afterwards to figure out the real position. `map_entry` did not do this for the returned key pointer so it could point to the wrong key if probing was done. --- base/runtime/dynamic_map_internal.odin | 37 +++++++++++++++++++------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/base/runtime/dynamic_map_internal.odin b/base/runtime/dynamic_map_internal.odin index b95e3cd14..96ae9c73c 100644 --- a/base/runtime/dynamic_map_internal.odin +++ b/base/runtime/dynamic_map_internal.odin @@ -400,7 +400,7 @@ map_alloc_dynamic :: proc "odin" (info: ^Map_Info, log2_capacity: uintptr, alloc // This procedure returns the address of the just inserted value, and will // return 'nil' if there was no room to insert the entry @(require_results) -map_insert_hash_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, h: Map_Hash, ik: uintptr, iv: uintptr) -> (result: uintptr) { +map_insert_hash_dynamic_with_key :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, h: Map_Hash, ik: uintptr, iv: uintptr) -> (key: uintptr, result: uintptr) { h := h pos := map_desired_position(m^, h) distance := uintptr(0) @@ -436,7 +436,11 @@ map_insert_hash_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^ intrinsics.mem_copy_non_overlapping(rawptr(v_dst), rawptr(v), size_of_v) hs[pos] = h - return result if result != 0 else v_dst + if result == 0 { + key = k_dst + result = v_dst + } + return } if map_hash_is_deleted(element_hash) { @@ -444,13 +448,14 @@ map_insert_hash_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^ } if probe_distance := map_probe_distance(m^, element_hash, pos); distance > probe_distance { - if result == 0 { - result = map_cell_index_dynamic(vs, info.vs, pos) - } - kp := map_cell_index_dynamic(ks, info.ks, pos) vp := map_cell_index_dynamic(vs, info.vs, pos) + if result == 0 { + key = kp + result = vp + } + intrinsics.mem_copy_non_overlapping(rawptr(tk), rawptr(k), size_of_k) intrinsics.mem_copy_non_overlapping(rawptr(k), rawptr(kp), size_of_k) intrinsics.mem_copy_non_overlapping(rawptr(kp), rawptr(tk), size_of_k) @@ -491,7 +496,11 @@ map_insert_hash_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^ intrinsics.mem_copy_non_overlapping(rawptr(v_dst), rawptr(v), size_of_v) hs[pos] = h - return result if result != 0 else v_dst + if result == 0 { + key = k_dst + result = v_dst + } + return } k_src := map_cell_index_dynamic(ks, info.ks, la_pos) @@ -501,6 +510,7 @@ map_insert_hash_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^ if probe_distance < look_ahead { // probed can be made ideal while placing saved (ending condition) if result == 0 { + key = k_dst result = v_dst } intrinsics.mem_copy_non_overlapping(rawptr(k_dst), rawptr(k), size_of_k) @@ -550,6 +560,7 @@ map_insert_hash_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^ } else { // place saved, save probed if result == 0 { + key = k_dst result = v_dst } intrinsics.mem_copy_non_overlapping(rawptr(k_dst), rawptr(k), size_of_k) @@ -568,6 +579,12 @@ map_insert_hash_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^ } } +@(require_results) +map_insert_hash_dynamic :: #force_inline proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, h: Map_Hash, ik: uintptr, iv: uintptr) -> (result: uintptr) { + _, result = map_insert_hash_dynamic_with_key(m, info, h, ik, iv) + return +} + @(require_results) map_grow_dynamic :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_Info, loc := #caller_location) -> Allocator_Error { log2_capacity := map_log2_cap(m^) @@ -955,9 +972,9 @@ __dynamic_map_entry :: proc "odin" (#no_alias m: ^Raw_Map, #no_alias info: ^Map_ hash = info.key_hasher(key, map_seed(m^)) } - value_ptr = rawptr(map_insert_hash_dynamic(m, info, hash, uintptr(key), uintptr(zero))) - assert(value_ptr != nil) - key_ptr = rawptr(map_cell_index_dynamic(map_data(m^), info.ks, map_desired_position(m^, hash))) + kp, vp := map_insert_hash_dynamic_with_key(m, info, hash, uintptr(key), uintptr(zero)) + key_ptr = rawptr(kp) + value_ptr = rawptr(vp) m.len += 1 just_inserted = true From 0f1261864261064ea092be488d39f180426e1bce Mon Sep 17 00:00:00 2001 From: Samuel Elgozi Date: Sat, 18 Jan 2025 21:08:54 +0200 Subject: [PATCH 26/64] fix incorrect use of Bool --- core/sys/darwin/Foundation/NSData.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/sys/darwin/Foundation/NSData.odin b/core/sys/darwin/Foundation/NSData.odin index f17d008a3..8baaf3486 100644 --- a/core/sys/darwin/Foundation/NSData.odin +++ b/core/sys/darwin/Foundation/NSData.odin @@ -19,7 +19,7 @@ Data_initWithBytes :: proc "c" (self: ^Data, bytes: []byte) -> ^Data { } @(objc_type=Data, objc_name="initWithBytesNoCopy") -Data_initWithBytesNoCopy :: proc "c" (self: ^Data, bytes: []byte, freeWhenDone: ns.BOOL) -> ^Data { +Data_initWithBytesNoCopy :: proc "c" (self: ^Data, bytes: []byte, freeWhenDone: BOOL) -> ^Data { return msgSend( ^Data, self, From 21e48889347eabde202d2ebd51e9e8effd523d7b Mon Sep 17 00:00:00 2001 From: LineuVale Date: Sat, 18 Jan 2025 18:22:16 -0300 Subject: [PATCH 27/64] Fix raylib DrawRectangleRoundedLines --- vendor/raylib/raylib.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/raylib/raylib.odin b/vendor/raylib/raylib.odin index bb51f105f..755f3bedd 100644 --- a/vendor/raylib/raylib.odin +++ b/vendor/raylib/raylib.odin @@ -1256,7 +1256,7 @@ foreign lib { DrawRectangleLines :: proc(posX, posY: c.int, width, height: c.int, color: Color) --- // Draw rectangle outline DrawRectangleLinesEx :: proc(rec: Rectangle, lineThick: f32, color: Color) --- // Draw rectangle outline with extended parameters DrawRectangleRounded :: proc(rec: Rectangle, roundness: f32, segments: c.int, color: Color) --- // Draw rectangle with rounded edges - DrawRectangleRoundedLines :: proc(rec: Rectangle, roundness: f32, segments: c.int, lineThick: f32, color: Color) --- // Draw rectangle lines with rounded edges + DrawRectangleRoundedLines :: proc(rec: Rectangle, roundness: f32, segments: c.int, color: Color) --- // Draw rectangle lines with rounded edges DrawRectangleRoundedLinesEx :: proc(rec: Rectangle, roundness: f32, segments: c.int, lineThick: f32, color: Color) --- // Draw rectangle with rounded edges outline DrawTriangle :: proc(v1, v2, v3: Vector2, color: Color) --- // Draw a color-filled triangle (vertex in counter-clockwise order!) DrawTriangleLines :: proc(v1, v2, v3: Vector2, color: Color) --- // Draw triangle outline (vertex in counter-clockwise order!) From e4892f1bb232bbad0795a94f809f3124620653a9 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Sat, 18 Jan 2025 22:07:19 +0100 Subject: [PATCH 28/64] os/os2: wasi target support --- core/crypto/rand_generic.odin | 1 + core/crypto/rand_wasi.odin | 13 + core/os/os2/dir_posix.odin | 9 +- core/os/os2/dir_wasi.odin | 110 ++++++ core/os/os2/env_wasi.odin | 186 +++++++++++ core/os/os2/errors_wasi.odin | 47 +++ core/os/os2/file_wasi.odin | 534 ++++++++++++++++++++++++++++++ core/os/os2/heap_wasi.odin | 6 + core/os/os2/path_posix.odin | 2 +- core/os/os2/path_wasi.odin | 84 +++++ core/os/os2/pipe_wasi.odin | 13 + core/os/os2/process_wasi.odin | 89 +++++ core/os/os2/stat_wasi.odin | 101 ++++++ core/os/os2/temp_file_wasi.odin | 9 + core/path/filepath/match.odin | 1 + core/path/filepath/path_wasi.odin | 36 ++ core/path/filepath/walk.odin | 1 + 17 files changed, 1238 insertions(+), 4 deletions(-) create mode 100644 core/crypto/rand_wasi.odin create mode 100644 core/os/os2/dir_wasi.odin create mode 100644 core/os/os2/env_wasi.odin create mode 100644 core/os/os2/errors_wasi.odin create mode 100644 core/os/os2/file_wasi.odin create mode 100644 core/os/os2/heap_wasi.odin create mode 100644 core/os/os2/path_wasi.odin create mode 100644 core/os/os2/pipe_wasi.odin create mode 100644 core/os/os2/process_wasi.odin create mode 100644 core/os/os2/stat_wasi.odin create mode 100644 core/os/os2/temp_file_wasi.odin create mode 100644 core/path/filepath/path_wasi.odin diff --git a/core/crypto/rand_generic.odin b/core/crypto/rand_generic.odin index ef578f5c0..8266f8ffc 100644 --- a/core/crypto/rand_generic.odin +++ b/core/crypto/rand_generic.odin @@ -5,6 +5,7 @@ #+build !netbsd #+build !darwin #+build !js +#+build !wasi package crypto HAS_RAND_BYTES :: false diff --git a/core/crypto/rand_wasi.odin b/core/crypto/rand_wasi.odin new file mode 100644 index 000000000..9653fb985 --- /dev/null +++ b/core/crypto/rand_wasi.odin @@ -0,0 +1,13 @@ +package crypto + +import "core:fmt" +import "core:sys/wasm/wasi" + +HAS_RAND_BYTES :: true + +@(private) +_rand_bytes :: proc(dst: []byte) { + if err := wasi.random_get(dst); err != nil { + fmt.panicf("crypto: wasi.random_get failed: %v", err) + } +} diff --git a/core/os/os2/dir_posix.odin b/core/os/os2/dir_posix.odin index 14fddde50..36cac2597 100644 --- a/core/os/os2/dir_posix.odin +++ b/core/os/os2/dir_posix.odin @@ -39,8 +39,11 @@ _read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info } n := len(fimpl.name)+1 - non_zero_resize(&it.impl.fullpath, n+len(sname)) - n += copy(it.impl.fullpath[n:], sname) + if err := non_zero_resize(&it.impl.fullpath, n+len(sname)); err != nil { + // Can't really tell caller we had an error, sad. + return + } + copy(it.impl.fullpath[n:], sname) fi = internal_stat(stat, string(it.impl.fullpath[:])) ok = true @@ -60,7 +63,7 @@ _read_directory_iterator_create :: proc(f: ^File) -> (iter: Read_Directory_Itera iter.f = f iter.impl.idx = 0 - iter.impl.fullpath.allocator = file_allocator() + iter.impl.fullpath = make([dynamic]byte, 0, len(impl.name)+128, file_allocator()) or_return append(&iter.impl.fullpath, impl.name) append(&iter.impl.fullpath, "/") defer if err != nil { delete(iter.impl.fullpath) } diff --git a/core/os/os2/dir_wasi.odin b/core/os/os2/dir_wasi.odin new file mode 100644 index 000000000..e4349069a --- /dev/null +++ b/core/os/os2/dir_wasi.odin @@ -0,0 +1,110 @@ +#+private +package os2 + +import "base:intrinsics" +import "core:sys/wasm/wasi" + +Read_Directory_Iterator_Impl :: struct { + fullpath: [dynamic]byte, + buf: []byte, + off: int, + idx: int, +} + +@(require_results) +_read_directory_iterator :: proc(it: ^Read_Directory_Iterator) -> (fi: File_Info, index: int, ok: bool) { + fimpl := (^File_Impl)(it.f.impl) + + buf := it.impl.buf[it.impl.off:] + + index = it.impl.idx + it.impl.idx += 1 + + for { + if len(buf) < size_of(wasi.dirent_t) { + return + } + + entry := intrinsics.unaligned_load((^wasi.dirent_t)(raw_data(buf))) + buf = buf[size_of(wasi.dirent_t):] + + if len(buf) < int(entry.d_namlen) { + // shouldn't be possible. + return + } + + name := string(buf[:entry.d_namlen]) + buf = buf[entry.d_namlen:] + it.impl.off += size_of(wasi.dirent_t) + int(entry.d_namlen) + + if name == "." || name == ".." { + continue + } + + n := len(fimpl.name)+1 + if alloc_err := non_zero_resize(&it.impl.fullpath, n+len(name)); alloc_err != nil { + // Can't really tell caller we had an error, sad. + return + } + copy(it.impl.fullpath[n:], name) + + stat, err := wasi.path_filestat_get(__fd(it.f), {}, name) + if err != nil { + // Can't stat, fill what we have from dirent. + stat = { + ino = entry.d_ino, + filetype = entry.d_type, + } + } + + fi = internal_stat(stat, string(it.impl.fullpath[:])) + ok = true + return + } +} + +@(require_results) +_read_directory_iterator_create :: proc(f: ^File) -> (iter: Read_Directory_Iterator, err: Error) { + if f == nil || f.impl == nil { + err = .Invalid_File + return + } + + impl := (^File_Impl)(f.impl) + iter.f = f + + buf: [dynamic]byte + buf.allocator = file_allocator() + defer if err != nil { delete(buf) } + + // NOTE: this is very grug. + for { + non_zero_resize(&buf, 512 if len(buf) == 0 else len(buf)*2) or_return + + n, _err := wasi.fd_readdir(__fd(f), buf[:], 0) + if _err != nil { + err = _get_platform_error(_err) + return + } + + if n < len(buf) { + non_zero_resize(&buf, n) + break + } + + assert(n == len(buf)) + } + iter.impl.buf = buf[:] + + iter.impl.fullpath = make([dynamic]byte, 0, len(impl.name)+128, file_allocator()) or_return + append(&iter.impl.fullpath, impl.name) + append(&iter.impl.fullpath, "/") + + return +} + +_read_directory_iterator_destroy :: proc(it: ^Read_Directory_Iterator) { + delete(it.impl.buf, file_allocator()) + delete(it.impl.fullpath) + it^ = {} +} diff --git a/core/os/os2/env_wasi.odin b/core/os/os2/env_wasi.odin new file mode 100644 index 000000000..8bf4eff38 --- /dev/null +++ b/core/os/os2/env_wasi.odin @@ -0,0 +1,186 @@ +#+private +package os2 + +import "base:runtime" + +import "core:strings" +import "core:sync" +import "core:sys/wasm/wasi" + +g_env: map[string]string +g_env_buf: []byte +g_env_mutex: sync.RW_Mutex +g_env_error: Error +g_env_built: bool + +build_env :: proc() -> (err: Error) { + if g_env_built || g_env_error != nil { + return g_env_error + } + + sync.guard(&g_env_mutex) + + if g_env_built || g_env_error != nil { + return g_env_error + } + + defer if err != nil { + g_env_error = err + } + + num_envs, size_of_envs, _err := wasi.environ_sizes_get() + if _err != nil { + return _get_platform_error(_err) + } + + g_env = make(map[string]string, num_envs, file_allocator()) or_return + defer if err != nil { delete(g_env) } + + g_env_buf = make([]byte, size_of_envs, file_allocator()) or_return + defer if err != nil { delete(g_env_buf, file_allocator()) } + + TEMP_ALLOCATOR_GUARD() + + envs := make([]cstring, num_envs, temp_allocator()) or_return + + _err = wasi.environ_get(raw_data(envs), raw_data(g_env_buf)) + if _err != nil { + return _get_platform_error(_err) + } + + for env in envs { + key, _, value := strings.partition(string(env), "=") + g_env[key] = value + } + + g_env_built = true + return +} + +delete_string_if_not_original :: proc(str: string) { + start := uintptr(raw_data(g_env_buf)) + end := start + uintptr(len(g_env_buf)) + ptr := uintptr(raw_data(str)) + if ptr < start || ptr > end { + delete(str, file_allocator()) + } +} + +@(require_results) +_lookup_env :: proc(key: string, allocator: runtime.Allocator) -> (value: string, found: bool) { + if err := build_env(); err != nil { + return + } + + sync.shared_guard(&g_env_mutex) + + value = g_env[key] or_return + value, _ = clone_string(value, allocator) + return +} + +@(require_results) +_set_env :: proc(key, value: string) -> bool { + if err := build_env(); err != nil { + return false + } + + sync.guard(&g_env_mutex) + + key_ptr, value_ptr, just_inserted, err := map_entry(&g_env, key) + if err != nil { + return false + } + + alloc_err: runtime.Allocator_Error + + if just_inserted { + key_ptr^, alloc_err = clone_string(key, file_allocator()) + if alloc_err != nil { + delete_key(&g_env, key) + return false + } + + value_ptr^, alloc_err = clone_string(value, file_allocator()) + if alloc_err != nil { + delete_key(&g_env, key) + delete(key_ptr^, file_allocator()) + return false + } + + return true + } + + delete_string_if_not_original(value_ptr^) + + value_ptr^, alloc_err = clone_string(value, file_allocator()) + if alloc_err != nil { + delete_key(&g_env, key) + return false + } + + return true +} + +@(require_results) +_unset_env :: proc(key: string) -> bool { + if err := build_env(); err != nil { + return false + } + + sync.guard(&g_env_mutex) + + dkey, dval := delete_key(&g_env, key) + delete_string_if_not_original(dkey) + delete_string_if_not_original(dval) + return true +} + +_clear_env :: proc() { + sync.guard(&g_env_mutex) + + for k, v in g_env { + delete_string_if_not_original(k) + delete_string_if_not_original(v) + } + + delete(g_env_buf, file_allocator()) + g_env_buf = {} + + clear(&g_env) + + g_env_built = true +} + +@(require_results) +_environ :: proc(allocator: runtime.Allocator) -> []string { + if err := build_env(); err != nil { + return nil + } + + sync.shared_guard(&g_env_mutex) + + envs, alloc_err := make([]string, len(g_env), allocator) + if alloc_err != nil { + return nil + } + + defer if alloc_err != nil { + for env in envs { + delete(env, allocator) + } + delete(envs, allocator) + } + + i: int + for k, v in g_env { + defer i += 1 + + envs[i], alloc_err = concatenate({k, "=", v}, allocator) + if alloc_err != nil { + return nil + } + } + + return envs +} diff --git a/core/os/os2/errors_wasi.odin b/core/os/os2/errors_wasi.odin new file mode 100644 index 000000000..b88e5b81e --- /dev/null +++ b/core/os/os2/errors_wasi.odin @@ -0,0 +1,47 @@ +#+private +package os2 + +import "base:runtime" + +import "core:slice" +import "core:sys/wasm/wasi" + +_Platform_Error :: wasi.errno_t + +_error_string :: proc(errno: i32) -> string { + e := wasi.errno_t(errno) + if e == .NONE { + return "" + } + + err := runtime.Type_Info_Enum_Value(e) + + ti := &runtime.type_info_base(type_info_of(wasi.errno_t)).variant.(runtime.Type_Info_Enum) + if idx, ok := slice.binary_search(ti.values, err); ok { + return ti.names[idx] + } + return "" +} + +_get_platform_error :: proc(errno: wasi.errno_t) -> Error { + #partial switch errno { + case .PERM: + return .Permission_Denied + case .EXIST: + return .Exist + case .NOENT: + return .Not_Exist + case .TIMEDOUT: + return .Timeout + case .PIPE: + return .Broken_Pipe + case .BADF: + return .Invalid_File + case .NOMEM: + return .Out_Of_Memory + case .NOSYS: + return .Unsupported + case: + return Platform_Error(errno) + } +} diff --git a/core/os/os2/file_wasi.odin b/core/os/os2/file_wasi.odin new file mode 100644 index 000000000..2b722e5dd --- /dev/null +++ b/core/os/os2/file_wasi.odin @@ -0,0 +1,534 @@ +#+private +package os2 + +import "base:runtime" + +import "core:io" +import "core:sys/wasm/wasi" +import "core:time" + +// NOTE: Don't know if there is a max in wasi. +MAX_RW :: 1 << 30 + +File_Impl :: struct { + file: File, + name: string, + fd: wasi.fd_t, + allocator: runtime.Allocator, +} + +// WASI works with "preopened" directories, the environment retrieves directories +// (for example with `wasmtime --dir=. module.wasm`) and those given directories +// are the only ones accessible by the application. +// +// So in order to facilitate the `os` API (absolute paths etc.) we keep a list +// of the given directories and match them when needed (notably `os.open`). +Preopen :: struct { + fd: wasi.fd_t, + prefix: string, +} +preopens: []Preopen + +@(init) +init_std_files :: proc() { + new_std :: proc(impl: ^File_Impl, fd: wasi.fd_t, name: string) -> ^File { + impl.file.impl = impl + impl.allocator = runtime.nil_allocator() + impl.fd = fd + impl.name = string(name) + impl.file.stream = { + data = impl, + procedure = _file_stream_proc, + } + impl.file.fstat = _fstat + return &impl.file + } + + @(static) files: [3]File_Impl + stdin = new_std(&files[0], 0, "/dev/stdin") + stdout = new_std(&files[1], 1, "/dev/stdout") + stderr = new_std(&files[2], 2, "/dev/stderr") +} + +@(init) +init_preopens :: proc() { + strip_prefixes :: proc(path: string) -> string { + path := path + loop: for len(path) > 0 { + switch { + case path[0] == '/': + path = path[1:] + case len(path) > 2 && path[0] == '.' && path[1] == '/': + path = path[2:] + case len(path) == 1 && path[0] == '.': + path = path[1:] + case: + break loop + } + } + return path + } + + n: int + n_loop: for fd := wasi.fd_t(3); ; fd += 1 { + _, err := wasi.fd_prestat_get(fd) + #partial switch err { + case .BADF: break n_loop + case .SUCCESS: n += 1 + case: + print_error(stderr, _get_platform_error(err), "unexpected error from wasi_prestat_get") + break n_loop + } + } + + alloc_err: runtime.Allocator_Error + preopens, alloc_err = make([]Preopen, n, file_allocator()) + if alloc_err != nil { + print_error(stderr, alloc_err, "could not allocate memory for wasi preopens") + return + } + + loop: for &preopen, i in preopens { + fd := wasi.fd_t(3 + i) + + desc, err := wasi.fd_prestat_get(fd) + assert(err == .SUCCESS) + + switch desc.tag { + case .DIR: + buf: []byte + buf, alloc_err = make([]byte, desc.dir.pr_name_len, file_allocator()) + if alloc_err != nil { + print_error(stderr, alloc_err, "could not allocate memory for wasi preopen dir name") + continue loop + } + + if err = wasi.fd_prestat_dir_name(fd, buf); err != .SUCCESS { + print_error(stderr, _get_platform_error(err), "could not get filesystem preopen dir name") + continue loop + } + + preopen.fd = fd + preopen.prefix = strip_prefixes(string(buf)) + } + } +} + +@(require_results) +match_preopen :: proc(path: string) -> (wasi.fd_t, string, bool) { + @(require_results) + prefix_matches :: proc(prefix, path: string) -> bool { + // Empty is valid for any relative path. + if len(prefix) == 0 && len(path) > 0 && path[0] != '/' { + return true + } + + if len(path) < len(prefix) { + return false + } + + if path[:len(prefix)] != prefix { + return false + } + + // Only match on full components. + i := len(prefix) + for i > 0 && prefix[i-1] == '/' { + i -= 1 + } + return path[i] == '/' + } + + path := path + if path == "" { + return 0, "", false + } + + for len(path) > 0 && path[0] == '/' { + path = path[1:] + } + + match: Preopen + #reverse for preopen in preopens { + if (match.fd == 0 || len(preopen.prefix) > len(match.prefix)) && prefix_matches(preopen.prefix, path) { + match = preopen + } + } + + if match.fd == 0 { + return 0, "", false + } + + relative := path[len(match.prefix):] + for len(relative) > 0 && relative[0] == '/' { + relative = relative[1:] + } + + if len(relative) == 0 { + relative = "." + } + + return match.fd, relative, true +} + +_open :: proc(name: string, flags: File_Flags, perm: int) -> (f: ^File, err: Error) { + dir_fd, relative, ok := match_preopen(name) + if !ok { + return nil, .Invalid_Path + } + + oflags: wasi.oflags_t + if .Create in flags { oflags += {.CREATE} } + if .Excl in flags { oflags += {.EXCL} } + if .Trunc in flags { oflags += {.TRUNC} } + + fdflags: wasi.fdflags_t + if .Append in flags { fdflags += {.APPEND} } + if .Sync in flags { fdflags += {.SYNC} } + + // NOTE: rights are adjusted to what this package's functions might want to call. + rights: wasi.rights_t + if .Read in flags { rights += {.FD_READ, .FD_FILESTAT_GET, .PATH_FILESTAT_GET} } + if .Write in flags { rights += {.FD_WRITE, .FD_SYNC, .FD_FILESTAT_SET_SIZE, .FD_FILESTAT_SET_TIMES, .FD_SEEK} } + + fd, fderr := wasi.path_open(dir_fd, {.SYMLINK_FOLLOW}, relative, oflags, rights, {}, fdflags) + if fderr != nil { + err = _get_platform_error(fderr) + return + } + + return _new_file(uintptr(fd), name, file_allocator()) +} + +_new_file :: proc(handle: uintptr, name: string, allocator: runtime.Allocator) -> (f: ^File, err: Error) { + if name == "" { + err = .Invalid_Path + return + } + + impl := new(File_Impl, allocator) or_return + defer if err != nil { free(impl, allocator) } + + impl.allocator = allocator + // NOTE: wasi doesn't really do full paths afact. + impl.name = clone_string(name, allocator) or_return + impl.fd = wasi.fd_t(handle) + impl.file.impl = impl + impl.file.stream = { + data = impl, + procedure = _file_stream_proc, + } + impl.file.fstat = _fstat + + return &impl.file, nil +} + +_close :: proc(f: ^File_Impl) -> (err: Error) { + if errno := wasi.fd_close(f.fd); errno != nil { + err = _get_platform_error(errno) + } + + delete(f.name, f.allocator) + free(f, f.allocator) + return +} + +_fd :: proc(f: ^File) -> uintptr { + return uintptr(__fd(f)) +} + +__fd :: proc(f: ^File) -> wasi.fd_t { + if f != nil && f.impl != nil { + return (^File_Impl)(f.impl).fd + } + return -1 +} + +_name :: proc(f: ^File) -> string { + if f != nil && f.impl != nil { + return (^File_Impl)(f.impl).name + } + return "" +} + +_sync :: proc(f: ^File) -> Error { + return _get_platform_error(wasi.fd_sync(__fd(f))) +} + +_truncate :: proc(f: ^File, size: i64) -> Error { + return _get_platform_error(wasi.fd_filestat_set_size(__fd(f), wasi.filesize_t(size))) +} + +_remove :: proc(name: string) -> Error { + dir_fd, relative, ok := match_preopen(name) + if !ok { + return .Invalid_Path + } + + err := wasi.path_remove_directory(dir_fd, relative) + if err == .NOTDIR { + err = wasi.path_unlink_file(dir_fd, relative) + } + + return _get_platform_error(err) +} + +_rename :: proc(old_path, new_path: string) -> Error { + src_dir_fd, src_relative, src_ok := match_preopen(old_path) + if !src_ok { + return .Invalid_Path + } + + new_dir_fd, new_relative, new_ok := match_preopen(new_path) + if !new_ok { + return .Invalid_Path + } + + return _get_platform_error(wasi.path_rename(src_dir_fd, src_relative, new_dir_fd, new_relative)) +} + +_link :: proc(old_name, new_name: string) -> Error { + src_dir_fd, src_relative, src_ok := match_preopen(old_name) + if !src_ok { + return .Invalid_Path + } + + new_dir_fd, new_relative, new_ok := match_preopen(new_name) + if !new_ok { + return .Invalid_Path + } + + return _get_platform_error(wasi.path_link(src_dir_fd, {.SYMLINK_FOLLOW}, src_relative, new_dir_fd, new_relative)) +} + +_symlink :: proc(old_name, new_name: string) -> Error { + src_dir_fd, src_relative, src_ok := match_preopen(old_name) + if !src_ok { + return .Invalid_Path + } + + new_dir_fd, new_relative, new_ok := match_preopen(new_name) + if !new_ok { + return .Invalid_Path + } + + if src_dir_fd != new_dir_fd { + return .Invalid_Path + } + + return _get_platform_error(wasi.path_symlink(src_relative, src_dir_fd, new_relative)) +} + +_read_link :: proc(name: string, allocator: runtime.Allocator) -> (s: string, err: Error) { + dir_fd, relative, ok := match_preopen(name) + if !ok { + return "", .Invalid_Path + } + + n, _err := wasi.path_readlink(dir_fd, relative, nil) + if _err != nil { + err = _get_platform_error(_err) + return + } + + buf := make([]byte, n, allocator) or_return + + _, _err = wasi.path_readlink(dir_fd, relative, buf) + s = string(buf) + err = _get_platform_error(_err) + return +} + +_chdir :: proc(name: string) -> Error { + return .Unsupported +} + +_fchdir :: proc(f: ^File) -> Error { + return .Unsupported +} + +_fchmod :: proc(f: ^File, mode: int) -> Error { + return .Unsupported +} + +_chmod :: proc(name: string, mode: int) -> Error { + return .Unsupported +} + +_fchown :: proc(f: ^File, uid, gid: int) -> Error { + return .Unsupported +} + +_chown :: proc(name: string, uid, gid: int) -> Error { + return .Unsupported +} + +_lchown :: proc(name: string, uid, gid: int) -> Error { + return .Unsupported +} + +_chtimes :: proc(name: string, atime, mtime: time.Time) -> Error { + dir_fd, relative, ok := match_preopen(name) + if !ok { + return .Invalid_Path + } + + _atime := wasi.timestamp_t(atime._nsec) + _mtime := wasi.timestamp_t(mtime._nsec) + + return _get_platform_error(wasi.path_filestat_set_times(dir_fd, {.SYMLINK_FOLLOW}, relative, _atime, _mtime, {.MTIM, .ATIM})) +} + +_fchtimes :: proc(f: ^File, atime, mtime: time.Time) -> Error { + _atime := wasi.timestamp_t(atime._nsec) + _mtime := wasi.timestamp_t(mtime._nsec) + + return _get_platform_error(wasi.fd_filestat_set_times(__fd(f), _atime, _mtime, {.ATIM, .MTIM})) +} + +_exists :: proc(path: string) -> bool { + dir_fd, relative, ok := match_preopen(path) + if !ok { + return false + } + + _, err := wasi.path_filestat_get(dir_fd, {.SYMLINK_FOLLOW}, relative) + if err != nil { + return false + } + + return true +} + +_file_stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From) -> (n: i64, err: io.Error) { + f := (^File_Impl)(stream_data) + fd := f.fd + + switch mode { + case .Read: + if len(p) <= 0 { + return + } + + to_read := min(len(p), MAX_RW) + _n, _err := wasi.fd_read(fd, {p[:to_read]}) + n = i64(_n) + + if _err != nil { + err = .Unknown + } else if n == 0 { + err = .EOF + } + + return + + case .Read_At: + if len(p) <= 0 { + return + } + + if offset < 0 { + err = .Invalid_Offset + return + } + + to_read := min(len(p), MAX_RW) + _n, _err := wasi.fd_pread(fd, {p[:to_read]}, wasi.filesize_t(offset)) + n = i64(_n) + + if _err != nil { + err = .Unknown + } else if n == 0 { + err = .EOF + } + + return + + case .Write: + p := p + for len(p) > 0 { + to_write := min(len(p), MAX_RW) + _n, _err := wasi.fd_write(fd, {p[:to_write]}) + if _err != nil { + err = .Unknown + return + } + p = p[_n:] + n += i64(_n) + } + return + + case .Write_At: + p := p + offset := offset + + if offset < 0 { + err = .Invalid_Offset + return + } + + for len(p) > 0 { + to_write := min(len(p), MAX_RW) + _n, _err := wasi.fd_pwrite(fd, {p[:to_write]}, wasi.filesize_t(offset)) + if _err != nil { + err = .Unknown + return + } + + p = p[_n:] + n += i64(_n) + offset += i64(_n) + } + return + + case .Seek: + #assert(int(wasi.whence_t.SET) == int(io.Seek_From.Start)) + #assert(int(wasi.whence_t.CUR) == int(io.Seek_From.Current)) + #assert(int(wasi.whence_t.END) == int(io.Seek_From.End)) + + switch whence { + case .Start, .Current, .End: + break + case: + err = .Invalid_Whence + return + } + + _n, _err := wasi.fd_seek(fd, wasi.filedelta_t(offset), wasi.whence_t(whence)) + #partial switch _err { + case .INVAL: + err = .Invalid_Offset + case: + err = .Unknown + case .SUCCESS: + n = i64(_n) + } + return + + case .Size: + stat, _err := wasi.fd_filestat_get(fd) + if _err != nil { + err = .Unknown + return + } + + n = i64(stat.size) + return + + case .Flush: + ferr := _sync(&f.file) + err = error_to_io_error(ferr) + return + + case .Close, .Destroy: + ferr := _close(f) + err = error_to_io_error(ferr) + return + + case .Query: + return io.query_utility({.Read, .Read_At, .Write, .Write_At, .Seek, .Size, .Flush, .Close, .Destroy, .Query}) + + case: + return 0, .Empty + } +} diff --git a/core/os/os2/heap_wasi.odin b/core/os/os2/heap_wasi.odin new file mode 100644 index 000000000..7da3c4845 --- /dev/null +++ b/core/os/os2/heap_wasi.odin @@ -0,0 +1,6 @@ +#+private +package os2 + +import "base:runtime" + +_heap_allocator_proc :: runtime.wasm_allocator_proc diff --git a/core/os/os2/path_posix.odin b/core/os/os2/path_posix.odin index 5ffdac28e..e6b95c0d4 100644 --- a/core/os/os2/path_posix.odin +++ b/core/os/os2/path_posix.odin @@ -81,7 +81,7 @@ _remove_all :: proc(path: string) -> Error { fullpath, _ := concatenate({path, "/", string(cname), "\x00"}, temp_allocator()) if entry.d_type == .DIR { - _remove_all(fullpath[:len(fullpath)-1]) + _remove_all(fullpath[:len(fullpath)-1]) or_return } else { if posix.unlink(cstring(raw_data(fullpath))) != .OK { return _get_platform_error() diff --git a/core/os/os2/path_wasi.odin b/core/os/os2/path_wasi.odin new file mode 100644 index 000000000..17efbff62 --- /dev/null +++ b/core/os/os2/path_wasi.odin @@ -0,0 +1,84 @@ +#+private +package os2 + +import "base:runtime" + +import "core:path/filepath" +import "core:sys/wasm/wasi" + +_Path_Separator :: '/' +_Path_Separator_String :: "/" +_Path_List_Separator :: ':' + +_is_path_separator :: proc(c: byte) -> bool { + return c == _Path_Separator +} + +_mkdir :: proc(name: string, perm: int) -> Error { + dir_fd, relative, ok := match_preopen(name) + if !ok { + return .Invalid_Path + } + + return _get_platform_error(wasi.path_create_directory(dir_fd, relative)) +} + +_mkdir_all :: proc(path: string, perm: int) -> Error { + if path == "" { + return .Invalid_Path + } + + TEMP_ALLOCATOR_GUARD() + + if exists(path) { + return .Exist + } + + clean_path := filepath.clean(path, temp_allocator()) + return internal_mkdir_all(clean_path) + + internal_mkdir_all :: proc(path: string) -> Error { + dir, file := filepath.split(path) + if file != path && dir != "/" { + if len(dir) > 1 && dir[len(dir) - 1] == '/' { + dir = dir[:len(dir) - 1] + } + internal_mkdir_all(dir) or_return + } + + err := _mkdir(path, 0) + if err == .Exist { err = nil } + return err + } +} + +_remove_all :: proc(path: string) -> (err: Error) { + // PERF: this works, but wastes a bunch of memory using the read_directory_iterator API + // and using open instead of wasi fds directly. + { + dir := open(path) or_return + defer close(dir) + + iter := read_directory_iterator_create(dir) or_return + defer read_directory_iterator_destroy(&iter) + + for fi in read_directory_iterator(&iter) { + if fi.type == .Directory { + _remove_all(fi.fullpath) or_return + } else { + remove(fi.fullpath) or_return + } + } + } + + return remove(path) +} + +_get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { + return ".", .Unsupported +} + +_set_working_directory :: proc(dir: string) -> (err: Error) { + err = .Unsupported + return +} diff --git a/core/os/os2/pipe_wasi.odin b/core/os/os2/pipe_wasi.odin new file mode 100644 index 000000000..19c11b51d --- /dev/null +++ b/core/os/os2/pipe_wasi.odin @@ -0,0 +1,13 @@ +#+private +package os2 + +_pipe :: proc() -> (r, w: ^File, err: Error) { + err = .Unsupported + return +} + +@(require_results) +_pipe_has_data :: proc(r: ^File) -> (ok: bool, err: Error) { + err = .Unsupported + return +} diff --git a/core/os/os2/process_wasi.odin b/core/os/os2/process_wasi.odin new file mode 100644 index 000000000..6ebfe3788 --- /dev/null +++ b/core/os/os2/process_wasi.odin @@ -0,0 +1,89 @@ +#+private +package os2 + +import "base:runtime" + +import "core:time" +import "core:sys/wasm/wasi" + +_exit :: proc "contextless" (code: int) -> ! { + wasi.proc_exit(wasi.exitcode_t(code)) +} + +_get_uid :: proc() -> int { + return 0 +} + +_get_euid :: proc() -> int { + return 0 +} + +_get_gid :: proc() -> int { + return 0 +} + +_get_egid :: proc() -> int { + return 0 +} + +_get_pid :: proc() -> int { + return 0 +} + +_get_ppid :: proc() -> int { + return 0 +} + +_process_info_by_handle :: proc(process: Process, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { + err = .Unsupported + return +} + +_current_process_info :: proc(selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { + err = .Unsupported + return +} + +_Sys_Process_Attributes :: struct {} + +_process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { + err = .Unsupported + return +} + +_process_wait :: proc(process: Process, timeout: time.Duration) -> (process_state: Process_State, err: Error) { + err = .Unsupported + return +} + +_process_close :: proc(process: Process) -> Error { + return .Unsupported +} + +_process_kill :: proc(process: Process) -> (err: Error) { + return .Unsupported +} + +_process_info_by_pid :: proc(pid: int, selection: Process_Info_Fields, allocator: runtime.Allocator) -> (info: Process_Info, err: Error) { + err = .Unsupported + return +} + +_process_list :: proc(allocator: runtime.Allocator) -> (list: []int, err: Error) { + err = .Unsupported + return +} + +_process_open :: proc(pid: int, flags: Process_Open_Flags) -> (process: Process, err: Error) { + process.pid = pid + err = .Unsupported + return +} + +_process_handle_still_valid :: proc(p: Process) -> Error { + return nil +} + +_process_state_update_times :: proc(p: Process, state: ^Process_State) { + return +} diff --git a/core/os/os2/stat_wasi.odin b/core/os/os2/stat_wasi.odin new file mode 100644 index 000000000..2992c6267 --- /dev/null +++ b/core/os/os2/stat_wasi.odin @@ -0,0 +1,101 @@ +#+private +package os2 + +import "base:runtime" + +import "core:path/filepath" +import "core:sys/wasm/wasi" +import "core:time" + +internal_stat :: proc(stat: wasi.filestat_t, fullpath: string) -> (fi: File_Info) { + fi.fullpath = fullpath + fi.name = filepath.base(fi.fullpath) + + fi.inode = u128(stat.ino) + fi.size = i64(stat.size) + + switch stat.filetype { + case .BLOCK_DEVICE: fi.type = .Block_Device + case .CHARACTER_DEVICE: fi.type = .Character_Device + case .DIRECTORY: fi.type = .Directory + case .REGULAR_FILE: fi.type = .Regular + case .SOCKET_DGRAM, .SOCKET_STREAM: fi.type = .Socket + case .SYMBOLIC_LINK: fi.type = .Symlink + case .UNKNOWN: fi.type = .Undetermined + case: fi.type = .Undetermined + } + + fi.creation_time = time.Time{_nsec=i64(stat.ctim)} + fi.modification_time = time.Time{_nsec=i64(stat.mtim)} + fi.access_time = time.Time{_nsec=i64(stat.atim)} + + return +} + +_fstat :: proc(f: ^File, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { + if f == nil || f.impl == nil { + err = .Invalid_File + return + } + + impl := (^File_Impl)(f.impl) + + stat, _err := wasi.fd_filestat_get(__fd(f)) + if _err != nil { + err = _get_platform_error(_err) + return + } + + fullpath := clone_string(impl.name, allocator) or_return + return internal_stat(stat, fullpath), nil +} + +_stat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { + if name == "" { + err = .Invalid_Path + return + } + + dir_fd, relative, ok := match_preopen(name) + if !ok { + err = .Invalid_Path + return + } + + stat, _err := wasi.path_filestat_get(dir_fd, {.SYMLINK_FOLLOW}, relative) + if _err != nil { + err = _get_platform_error(_err) + return + } + + // NOTE: wasi doesn't really do full paths afact. + fullpath := clone_string(name, allocator) or_return + return internal_stat(stat, fullpath), nil +} + +_lstat :: proc(name: string, allocator: runtime.Allocator) -> (fi: File_Info, err: Error) { + if name == "" { + err = .Invalid_Path + return + } + + dir_fd, relative, ok := match_preopen(name) + if !ok { + err = .Invalid_Path + return + } + + stat, _err := wasi.path_filestat_get(dir_fd, {}, relative) + if _err != nil { + err = _get_platform_error(_err) + return + } + + // NOTE: wasi doesn't really do full paths afact. + fullpath := clone_string(name, allocator) or_return + return internal_stat(stat, fullpath), nil +} + +_same_file :: proc(fi1, fi2: File_Info) -> bool { + return fi1.fullpath == fi2.fullpath +} diff --git a/core/os/os2/temp_file_wasi.odin b/core/os/os2/temp_file_wasi.odin new file mode 100644 index 000000000..d5628d300 --- /dev/null +++ b/core/os/os2/temp_file_wasi.odin @@ -0,0 +1,9 @@ +#+private +package os2 + +import "base:runtime" + +_temp_dir :: proc(allocator: runtime.Allocator) -> (string, runtime.Allocator_Error) { + // NOTE: requires user to add /tmp to their preopen dirs, no standard way exists. + return clone_string("/tmp", allocator) +} diff --git a/core/path/filepath/match.odin b/core/path/filepath/match.odin index 003f8046d..1f0ac9287 100644 --- a/core/path/filepath/match.odin +++ b/core/path/filepath/match.odin @@ -1,3 +1,4 @@ +#+build !wasi package filepath import "core:os" diff --git a/core/path/filepath/path_wasi.odin b/core/path/filepath/path_wasi.odin new file mode 100644 index 000000000..74cc6ca1e --- /dev/null +++ b/core/path/filepath/path_wasi.odin @@ -0,0 +1,36 @@ +package filepath + +import "base:runtime" + +import "core:strings" + +SEPARATOR :: '/' +SEPARATOR_STRING :: `/` +LIST_SEPARATOR :: ':' + +is_reserved_name :: proc(path: string) -> bool { + return false +} + +is_abs :: proc(path: string) -> bool { + return strings.has_prefix(path, "/") +} + +abs :: proc(path: string, allocator := context.allocator) -> (string, bool) { + if is_abs(path) { + return strings.clone(string(path), allocator), true + } + + return path, false +} + +join :: proc(elems: []string, allocator := context.allocator) -> (joined: string, err: runtime.Allocator_Error) #optional_allocator_error { + for e, i in elems { + if e != "" { + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD(ignore = context.temp_allocator == allocator) + p := strings.join(elems[i:], SEPARATOR_STRING, context.temp_allocator) or_return + return clean(p, allocator) + } + } + return "", nil +} diff --git a/core/path/filepath/walk.odin b/core/path/filepath/walk.odin index 51dfa71d2..53b10eed7 100644 --- a/core/path/filepath/walk.odin +++ b/core/path/filepath/walk.odin @@ -1,3 +1,4 @@ +#+build !wasi package filepath import "core:os" From 5622fb583cb34fa9767f8d389b97708180b61f35 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Sat, 18 Jan 2025 22:52:13 +0100 Subject: [PATCH 29/64] math/rand: add @(require_results) to create --- core/math/rand/rand.odin | 1 + 1 file changed, 1 insertion(+) diff --git a/core/math/rand/rand.odin b/core/math/rand/rand.odin index 72d9400d7..bbd59a419 100644 --- a/core/math/rand/rand.odin +++ b/core/math/rand/rand.odin @@ -16,6 +16,7 @@ Generator_Query_Info :: runtime.Random_Generator_Query_Info Default_Random_State :: runtime.Default_Random_State default_random_generator :: runtime.default_random_generator +@(require_results) create :: proc(seed: u64) -> (state: Default_Random_State) { seed := seed runtime.default_random_generator(&state) From 27998e0d2147e9535c16280588e529b7679ec321 Mon Sep 17 00:00:00 2001 From: jason Date: Sun, 19 Jan 2025 06:05:55 -0500 Subject: [PATCH 30/64] os2/heap_linux point to runtime._heap_allocator_proc --- core/os/os2/env_linux.odin | 10 +- core/os/os2/heap_linux.odin | 724 +----------------------------------- 2 files changed, 7 insertions(+), 727 deletions(-) diff --git a/core/os/os2/env_linux.odin b/core/os/os2/env_linux.odin index c248a323c..e0ef06010 100644 --- a/core/os/os2/env_linux.odin +++ b/core/os/os2/env_linux.odin @@ -76,7 +76,7 @@ _set_env :: proc(key, v_new: string) -> bool { // wasn't in the environment in the first place. k_addr, v_addr := _kv_addr_from_val(v_curr, key) if len(v_new) > len(v_curr) { - k_addr = ([^]u8)(heap_resize(k_addr, kv_size)) + k_addr = ([^]u8)(runtime.heap_resize(k_addr, kv_size)) if k_addr == nil { return false } @@ -90,7 +90,7 @@ _set_env :: proc(key, v_new: string) -> bool { } } - k_addr := ([^]u8)(heap_alloc(kv_size)) + k_addr := ([^]u8)(runtime.heap_alloc(kv_size)) if k_addr == nil { return false } @@ -129,7 +129,7 @@ _unset_env :: proc(key: string) -> bool { // if we got this far, the envrionment variable // existed AND was allocated by us. k_addr, _ := _kv_addr_from_val(v, key) - heap_free(k_addr) + runtime.heap_free(k_addr) return true } @@ -139,7 +139,7 @@ _clear_env :: proc() { for kv in _env { if !_is_in_org_env(kv) { - heap_free(raw_data(kv)) + runtime.heap_free(raw_data(kv)) } } clear(&_env) @@ -193,7 +193,7 @@ _build_env :: proc() { return } - _env = make(type_of(_env), heap_allocator()) + _env = make(type_of(_env), runtime.heap_allocator()) cstring_env := _get_original_env() _org_env_begin = uintptr(rawptr(cstring_env[0])) for i := 0; cstring_env[i] != nil; i += 1 { diff --git a/core/os/os2/heap_linux.odin b/core/os/os2/heap_linux.odin index 8819dfac7..1d1f12726 100644 --- a/core/os/os2/heap_linux.odin +++ b/core/os/os2/heap_linux.odin @@ -1,726 +1,6 @@ #+private package os2 -import "core:sys/linux" -import "core:sync" -import "core:mem" - -// NOTEs -// -// All allocations below DIRECT_MMAP_THRESHOLD exist inside of memory "Regions." A region -// consists of a Region_Header and the memory that will be divided into allocations to -// send to the user. The memory is an array of "Allocation_Headers" which are 8 bytes. -// Allocation_Headers are used to navigate the memory in the region. The "next" member of -// the Allocation_Header points to the next header, and the space between the headers -// can be used to send to the user. This space between is referred to as "blocks" in the -// code. The indexes in the header refer to these blocks instead of bytes. This allows us -// to index all the memory in the region with a u16. -// -// When an allocation request is made, it will use the first free block that can contain -// the entire block. If there is an excess number of blocks (as specified by the constant -// BLOCK_SEGMENT_THRESHOLD), this extra space will be segmented and left in the free_list. -// -// To keep the implementation simple, there can never exist 2 free blocks adjacent to each -// other. Any freeing will result in attempting to merge the blocks before and after the -// newly free'd blocks. -// -// Any request for size above the DIRECT_MMAP_THRESHOLD will result in the allocation -// getting its own individual mmap. Individual mmaps will still get an Allocation_Header -// that contains the size with the last bit set to 1 to indicate it is indeed a direct -// mmap allocation. - -// Why not brk? -// glibc's malloc utilizes a mix of the brk and mmap system calls. This implementation -// does *not* utilize the brk system call to avoid possible conflicts with foreign C -// code. Just because we aren't directly using libc, there is nothing stopping the user -// from doing it. - -// What's with all the #no_bounds_check? -// When memory is returned from mmap, it technically doesn't get written ... well ... anywhere -// until that region is written to by *you*. So, when a new region is created, we call mmap -// to get a pointer to some memory, and we claim that memory is a ^Region. Therefor, the -// region itself is never formally initialized by the compiler as this would result in writing -// zeros to memory that we can already assume are 0. This would also have the effect of -// actually commiting this data to memory whether it gets used or not. - - -// -// Some variables to play with -// - -// Minimum blocks used for any one allocation -MINIMUM_BLOCK_COUNT :: 2 - -// Number of extra blocks beyond the requested amount where we would segment. -// E.g. (blocks) |H0123456| 7 available -// |H01H0123| Ask for 2, now 4 available -BLOCK_SEGMENT_THRESHOLD :: 4 - -// Anything above this threshold will get its own memory map. Since regions -// are indexed by 16 bit integers, this value should not surpass max(u16) * 6 -DIRECT_MMAP_THRESHOLD_USER :: int(max(u16)) - -// The point at which we convert direct mmap to region. This should be a decent -// amount less than DIRECT_MMAP_THRESHOLD to avoid jumping in and out of regions. -MMAP_TO_REGION_SHRINK_THRESHOLD :: DIRECT_MMAP_THRESHOLD - PAGE_SIZE * 4 - -// free_list is dynamic and is initialized in the begining of the region memory -// when the region is initialized. Once resized, it can be moved anywhere. -FREE_LIST_DEFAULT_CAP :: 32 - - -// -// Other constants that should not be touched -// - -// This universally seems to be 4096 outside of uncommon archs. -PAGE_SIZE :: 4096 - -// just rounding up to nearest PAGE_SIZE -DIRECT_MMAP_THRESHOLD :: (DIRECT_MMAP_THRESHOLD_USER-1) + PAGE_SIZE - (DIRECT_MMAP_THRESHOLD_USER-1) % PAGE_SIZE - -// Regions must be big enough to hold DIRECT_MMAP_THRESHOLD - 1 as well -// as end right on a page boundary as to not waste space. -SIZE_OF_REGION :: DIRECT_MMAP_THRESHOLD + 4 * int(PAGE_SIZE) - -// size of user memory blocks -BLOCK_SIZE :: size_of(Allocation_Header) - -// number of allocation sections (call them blocks) of the region used for allocations -BLOCKS_PER_REGION :: u16((SIZE_OF_REGION - size_of(Region_Header)) / BLOCK_SIZE) - -// minimum amount of space that can used by any individual allocation (includes header) -MINIMUM_ALLOCATION :: (MINIMUM_BLOCK_COUNT * BLOCK_SIZE) + BLOCK_SIZE - -// This is used as a boolean value for Region_Header.local_addr. -CURRENTLY_ACTIVE :: (^^Region)(~uintptr(0)) - -FREE_LIST_ENTRIES_PER_BLOCK :: BLOCK_SIZE / size_of(u16) - -MMAP_FLAGS : linux.Map_Flags : {.ANONYMOUS, .PRIVATE} -MMAP_PROT : linux.Mem_Protection : {.READ, .WRITE} - -@thread_local _local_region: ^Region -global_regions: ^Region - - -// There is no way of correctly setting the last bit of free_idx or -// the last bit of requested, so we can safely use it as a flag to -// determine if we are interacting with a direct mmap. -REQUESTED_MASK :: 0x7FFFFFFFFFFFFFFF -IS_DIRECT_MMAP :: 0x8000000000000000 - -// Special free_idx value that does not index the free_list. -NOT_FREE :: 0x7FFF -Allocation_Header :: struct #raw_union { - using _: struct { - // Block indicies - idx: u16, - prev: u16, - next: u16, - free_idx: u16, - }, - requested: u64, -} - -Region_Header :: struct #align(16) { - next_region: ^Region, // points to next region in global_heap (linked list) - local_addr: ^^Region, // tracks region ownership via address of _local_region - reset_addr: ^^Region, // tracks old local addr for reset - free_list: []u16, - free_list_len: u16, - free_blocks: u16, // number of free blocks in region (includes headers) - last_used: u16, // farthest back block that has been used (need zeroing?) - _reserved: u16, -} - -Region :: struct { - hdr: Region_Header, - memory: [BLOCKS_PER_REGION]Allocation_Header, -} - -_heap_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode, - size, alignment: int, - old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, mem.Allocator_Error) { - // - // NOTE(tetra, 2020-01-14): The heap doesn't respect alignment. - // Instead, we overallocate by `alignment + size_of(rawptr) - 1`, and insert - // padding. We also store the original pointer returned by heap_alloc right before - // the pointer we return to the user. - // - - aligned_alloc :: proc(size, alignment: int, old_ptr: rawptr = nil) -> ([]byte, mem.Allocator_Error) { - a := max(alignment, align_of(rawptr)) - space := size + a - 1 - - allocated_mem: rawptr - if old_ptr != nil { - original_old_ptr := mem.ptr_offset((^rawptr)(old_ptr), -1)^ - allocated_mem = heap_resize(original_old_ptr, space+size_of(rawptr)) - } else { - allocated_mem = heap_alloc(space+size_of(rawptr)) - } - aligned_mem := rawptr(mem.ptr_offset((^u8)(allocated_mem), size_of(rawptr))) - - ptr := uintptr(aligned_mem) - aligned_ptr := (ptr - 1 + uintptr(a)) & -uintptr(a) - diff := int(aligned_ptr - ptr) - if (size + diff) > space || allocated_mem == nil { - return nil, .Out_Of_Memory - } - - aligned_mem = rawptr(aligned_ptr) - mem.ptr_offset((^rawptr)(aligned_mem), -1)^ = allocated_mem - - return mem.byte_slice(aligned_mem, size), nil - } - - aligned_free :: proc(p: rawptr) { - if p != nil { - heap_free(mem.ptr_offset((^rawptr)(p), -1)^) - } - } - - aligned_resize :: proc(p: rawptr, old_size: int, new_size: int, new_alignment: int) -> (new_memory: []byte, err: mem.Allocator_Error) { - if p == nil { - return nil, nil - } - - return aligned_alloc(new_size, new_alignment, p) - } - - switch mode { - case .Alloc, .Alloc_Non_Zeroed: - return aligned_alloc(size, alignment) - - case .Free: - aligned_free(old_memory) - - case .Free_All: - return nil, .Mode_Not_Implemented - - case .Resize, .Resize_Non_Zeroed: - if old_memory == nil { - return aligned_alloc(size, alignment) - } - return aligned_resize(old_memory, old_size, size, alignment) - - case .Query_Features: - set := (^mem.Allocator_Mode_Set)(old_memory) - if set != nil { - set^ = {.Alloc, .Free, .Resize, .Query_Features} - } - return nil, nil - - case .Query_Info: - return nil, .Mode_Not_Implemented - } - - return nil, nil -} - -heap_alloc :: proc(size: int) -> rawptr { - if size >= DIRECT_MMAP_THRESHOLD { - return _direct_mmap_alloc(size) - } - - // atomically check if the local region has been stolen - if _local_region != nil { - res := sync.atomic_compare_exchange_strong_explicit( - &_local_region.hdr.local_addr, - &_local_region, - CURRENTLY_ACTIVE, - .Acquire, - .Relaxed, - ) - if res != &_local_region { - // At this point, the region has been stolen and res contains the unexpected value - expected := res - if res != CURRENTLY_ACTIVE { - expected = res - res = sync.atomic_compare_exchange_strong_explicit( - &_local_region.hdr.local_addr, - expected, - CURRENTLY_ACTIVE, - .Acquire, - .Relaxed, - ) - } - if res != expected { - _local_region = nil - } - } - } - - size := size - size = _round_up_to_nearest(size, BLOCK_SIZE) - blocks_needed := u16(max(MINIMUM_BLOCK_COUNT, size / BLOCK_SIZE)) - - // retrieve a region if new thread or stolen - if _local_region == nil { - _local_region, _ = _region_retrieve_with_space(blocks_needed) - if _local_region == nil { - return nil - } - } - defer sync.atomic_store_explicit(&_local_region.hdr.local_addr, &_local_region, .Release) - - // At this point we have a usable region. Let's find the user some memory - idx: u16 - local_region_idx := _region_get_local_idx() - back_idx := -1 - infinite: for { - for i := 0; i < int(_local_region.hdr.free_list_len); i += 1 { - idx = _local_region.hdr.free_list[i] - #no_bounds_check if _get_block_count(_local_region.memory[idx]) >= blocks_needed { - break infinite - } - } - sync.atomic_store_explicit(&_local_region.hdr.local_addr, &_local_region, .Release) - _local_region, back_idx = _region_retrieve_with_space(blocks_needed, local_region_idx, back_idx) - } - user_ptr, used := _region_get_block(_local_region, idx, blocks_needed) - - sync.atomic_sub_explicit(&_local_region.hdr.free_blocks, used + 1, .Release) - - // If this memory was ever used before, it now needs to be zero'd. - if idx < _local_region.hdr.last_used { - mem.zero(user_ptr, int(used) * BLOCK_SIZE) - } else { - _local_region.hdr.last_used = idx + used - } - - return user_ptr -} - -heap_resize :: proc(old_memory: rawptr, new_size: int) -> rawptr #no_bounds_check { - alloc := _get_allocation_header(old_memory) - if alloc.requested & IS_DIRECT_MMAP > 0 { - return _direct_mmap_resize(alloc, new_size) - } - - if new_size > DIRECT_MMAP_THRESHOLD { - return _direct_mmap_from_region(alloc, new_size) - } - - return _region_resize(alloc, new_size) -} - -heap_free :: proc(memory: rawptr) { - alloc := _get_allocation_header(memory) - if sync.atomic_load(&alloc.requested) & IS_DIRECT_MMAP == IS_DIRECT_MMAP { - _direct_mmap_free(alloc) - return - } - - assert(alloc.free_idx == NOT_FREE) - - _region_find_and_assign_local(alloc) - _region_local_free(alloc) - sync.atomic_store_explicit(&_local_region.hdr.local_addr, &_local_region, .Release) -} - -// -// Regions -// -_new_region :: proc() -> ^Region #no_bounds_check { - ptr, errno := linux.mmap(0, uint(SIZE_OF_REGION), MMAP_PROT, MMAP_FLAGS, -1, 0) - if errno != .NONE { - return nil - } - new_region := (^Region)(ptr) - - new_region.hdr.local_addr = CURRENTLY_ACTIVE - new_region.hdr.reset_addr = &_local_region - - free_list_blocks := _round_up_to_nearest(FREE_LIST_DEFAULT_CAP, FREE_LIST_ENTRIES_PER_BLOCK) - _region_assign_free_list(new_region, &new_region.memory[1], u16(free_list_blocks) * FREE_LIST_ENTRIES_PER_BLOCK) - - // + 2 to account for free_list's allocation header - first_user_block := len(new_region.hdr.free_list) / FREE_LIST_ENTRIES_PER_BLOCK + 2 - - // first allocation header (this is a free list) - new_region.memory[0].next = u16(first_user_block) - new_region.memory[0].free_idx = NOT_FREE - new_region.memory[first_user_block].idx = u16(first_user_block) - new_region.memory[first_user_block].next = BLOCKS_PER_REGION - 1 - - // add the first user block to the free list - new_region.hdr.free_list[0] = u16(first_user_block) - new_region.hdr.free_list_len = 1 - new_region.hdr.free_blocks = _get_block_count(new_region.memory[first_user_block]) + 1 - - for r := sync.atomic_compare_exchange_strong(&global_regions, nil, new_region); - r != nil; - r = sync.atomic_compare_exchange_strong(&r.hdr.next_region, nil, new_region) {} - - return new_region -} - -_region_resize :: proc(alloc: ^Allocation_Header, new_size: int, alloc_is_free_list: bool = false) -> rawptr #no_bounds_check { - assert(alloc.free_idx == NOT_FREE) - - old_memory := mem.ptr_offset(alloc, 1) - - old_block_count := _get_block_count(alloc^) - new_block_count := u16( - max(MINIMUM_BLOCK_COUNT, _round_up_to_nearest(new_size, BLOCK_SIZE) / BLOCK_SIZE), - ) - if new_block_count < old_block_count { - if new_block_count - old_block_count >= MINIMUM_BLOCK_COUNT { - _region_find_and_assign_local(alloc) - _region_segment(_local_region, alloc, new_block_count, alloc.free_idx) - new_block_count = _get_block_count(alloc^) - sync.atomic_store_explicit(&_local_region.hdr.local_addr, &_local_region, .Release) - } - // need to zero anything within the new block that that lies beyond new_size - extra_bytes := int(new_block_count * BLOCK_SIZE) - new_size - extra_bytes_ptr := mem.ptr_offset((^u8)(alloc), new_size + BLOCK_SIZE) - mem.zero(extra_bytes_ptr, extra_bytes) - return old_memory - } - - if !alloc_is_free_list { - _region_find_and_assign_local(alloc) - } - defer if !alloc_is_free_list { - sync.atomic_store_explicit(&_local_region.hdr.local_addr, &_local_region, .Release) - } - - // First, let's see if we can grow in place. - if alloc.next != BLOCKS_PER_REGION - 1 && _local_region.memory[alloc.next].free_idx != NOT_FREE { - next_alloc := _local_region.memory[alloc.next] - total_available := old_block_count + _get_block_count(next_alloc) + 1 - if total_available >= new_block_count { - alloc.next = next_alloc.next - _local_region.memory[alloc.next].prev = alloc.idx - if total_available - new_block_count > BLOCK_SEGMENT_THRESHOLD { - _region_segment(_local_region, alloc, new_block_count, next_alloc.free_idx) - } else { - _region_free_list_remove(_local_region, next_alloc.free_idx) - } - mem.zero(&_local_region.memory[next_alloc.idx], int(alloc.next - next_alloc.idx) * BLOCK_SIZE) - _local_region.hdr.last_used = max(alloc.next, _local_region.hdr.last_used) - _local_region.hdr.free_blocks -= (_get_block_count(alloc^) - old_block_count) - if alloc_is_free_list { - _region_assign_free_list(_local_region, old_memory, _get_block_count(alloc^)) - } - return old_memory - } - } - - // If we made it this far, we need to resize, copy, zero and free. - region_iter := _local_region - local_region_idx := _region_get_local_idx() - back_idx := -1 - idx: u16 - infinite: for { - for i := 0; i < int(region_iter.hdr.free_list_len); i += 1 { - idx = region_iter.hdr.free_list[i] - if _get_block_count(region_iter.memory[idx]) >= new_block_count { - break infinite - } - } - if region_iter != _local_region { - sync.atomic_store_explicit( - ®ion_iter.hdr.local_addr, - region_iter.hdr.reset_addr, - .Release, - ) - } - region_iter, back_idx = _region_retrieve_with_space(new_block_count, local_region_idx, back_idx) - } - if region_iter != _local_region { - sync.atomic_store_explicit( - ®ion_iter.hdr.local_addr, - region_iter.hdr.reset_addr, - .Release, - ) - } - - // copy from old memory - new_memory, used_blocks := _region_get_block(region_iter, idx, new_block_count) - mem.copy(new_memory, old_memory, int(old_block_count * BLOCK_SIZE)) - - // zero any new memory - addon_section := mem.ptr_offset((^Allocation_Header)(new_memory), old_block_count) - new_blocks := used_blocks - old_block_count - mem.zero(addon_section, int(new_blocks) * BLOCK_SIZE) - - region_iter.hdr.free_blocks -= (used_blocks + 1) - - // Set free_list before freeing. - if alloc_is_free_list { - _region_assign_free_list(_local_region, new_memory, used_blocks) - } - - // free old memory - _region_local_free(alloc) - return new_memory -} - -_region_local_free :: proc(alloc: ^Allocation_Header) #no_bounds_check { - alloc := alloc - add_to_free_list := true - - idx := sync.atomic_load(&alloc.idx) - prev := sync.atomic_load(&alloc.prev) - next := sync.atomic_load(&alloc.next) - block_count := next - idx - 1 - free_blocks := sync.atomic_load(&_local_region.hdr.free_blocks) + block_count + 1 - sync.atomic_store_explicit(&_local_region.hdr.free_blocks, free_blocks, .Release) - - // try to merge with prev - if idx > 0 && sync.atomic_load(&_local_region.memory[prev].free_idx) != NOT_FREE { - sync.atomic_store_explicit(&_local_region.memory[prev].next, next, .Release) - _local_region.memory[next].prev = prev - alloc = &_local_region.memory[prev] - add_to_free_list = false - } - - // try to merge with next - if next < BLOCKS_PER_REGION - 1 && sync.atomic_load(&_local_region.memory[next].free_idx) != NOT_FREE { - old_next := next - sync.atomic_store_explicit(&alloc.next, sync.atomic_load(&_local_region.memory[old_next].next), .Release) - - sync.atomic_store_explicit(&_local_region.memory[next].prev, idx, .Release) - - if add_to_free_list { - sync.atomic_store_explicit(&_local_region.hdr.free_list[_local_region.memory[old_next].free_idx], idx, .Release) - sync.atomic_store_explicit(&alloc.free_idx, _local_region.memory[old_next].free_idx, .Release) - } else { - // NOTE: We have aleady merged with prev, and now merged with next. - // Now, we are actually going to remove from the free_list. - _region_free_list_remove(_local_region, _local_region.memory[old_next].free_idx) - } - add_to_free_list = false - } - - // This is the only place where anything is appended to the free list. - if add_to_free_list { - fl := _local_region.hdr.free_list - fl_len := sync.atomic_load(&_local_region.hdr.free_list_len) - sync.atomic_store_explicit(&alloc.free_idx, fl_len, .Release) - fl[alloc.free_idx] = idx - sync.atomic_store_explicit(&_local_region.hdr.free_list_len, fl_len + 1, .Release) - if int(fl_len + 1) == len(fl) { - free_alloc := _get_allocation_header(mem.raw_data(_local_region.hdr.free_list)) - _region_resize(free_alloc, len(fl) * 2 * size_of(fl[0]), true) - } - } -} - -_region_assign_free_list :: proc(region: ^Region, memory: rawptr, blocks: u16) { - raw_free_list := transmute(mem.Raw_Slice)region.hdr.free_list - raw_free_list.len = int(blocks) * FREE_LIST_ENTRIES_PER_BLOCK - raw_free_list.data = memory - region.hdr.free_list = transmute([]u16)(raw_free_list) -} - -_region_retrieve_with_space :: proc(blocks: u16, local_idx: int = -1, back_idx: int = -1) -> (^Region, int) { - r: ^Region - idx: int - for r = sync.atomic_load(&global_regions); r != nil; r = r.hdr.next_region { - if idx == local_idx || idx < back_idx || sync.atomic_load(&r.hdr.free_blocks) < blocks { - idx += 1 - continue - } - idx += 1 - local_addr: ^^Region = sync.atomic_load(&r.hdr.local_addr) - if local_addr != CURRENTLY_ACTIVE { - res := sync.atomic_compare_exchange_strong_explicit( - &r.hdr.local_addr, - local_addr, - CURRENTLY_ACTIVE, - .Acquire, - .Relaxed, - ) - if res == local_addr { - r.hdr.reset_addr = local_addr - return r, idx - } - } - } - - return _new_region(), idx -} - -_region_retrieve_from_addr :: proc(addr: rawptr) -> ^Region { - r: ^Region - for r = global_regions; r != nil; r = r.hdr.next_region { - if _region_contains_mem(r, addr) { - return r - } - } - unreachable() -} - -_region_get_block :: proc(region: ^Region, idx, blocks_needed: u16) -> (rawptr, u16) #no_bounds_check { - alloc := ®ion.memory[idx] - - assert(alloc.free_idx != NOT_FREE) - assert(alloc.next > 0) - - block_count := _get_block_count(alloc^) - if block_count - blocks_needed > BLOCK_SEGMENT_THRESHOLD { - _region_segment(region, alloc, blocks_needed, alloc.free_idx) - } else { - _region_free_list_remove(region, alloc.free_idx) - } - - alloc.free_idx = NOT_FREE - return mem.ptr_offset(alloc, 1), _get_block_count(alloc^) -} - -_region_segment :: proc(region: ^Region, alloc: ^Allocation_Header, blocks, new_free_idx: u16) #no_bounds_check { - old_next := alloc.next - alloc.next = alloc.idx + blocks + 1 - region.memory[old_next].prev = alloc.next - - // Initialize alloc.next allocation header here. - region.memory[alloc.next].prev = alloc.idx - region.memory[alloc.next].next = old_next - region.memory[alloc.next].idx = alloc.next - region.memory[alloc.next].free_idx = new_free_idx - - // Replace our original spot in the free_list with new segment. - region.hdr.free_list[new_free_idx] = alloc.next -} - -_region_get_local_idx :: proc() -> int { - idx: int - for r := sync.atomic_load(&global_regions); r != nil; r = r.hdr.next_region { - if r == _local_region { - return idx - } - idx += 1 - } - - return -1 -} - -_region_find_and_assign_local :: proc(alloc: ^Allocation_Header) { - // Find the region that contains this memory - if !_region_contains_mem(_local_region, alloc) { - _local_region = _region_retrieve_from_addr(alloc) - } - - // At this point, _local_region is set correctly. Spin until acquire - res := CURRENTLY_ACTIVE - - for res == CURRENTLY_ACTIVE { - res = sync.atomic_compare_exchange_strong_explicit( - &_local_region.hdr.local_addr, - &_local_region, - CURRENTLY_ACTIVE, - .Acquire, - .Relaxed, - ) - } -} - -_region_contains_mem :: proc(r: ^Region, memory: rawptr) -> bool #no_bounds_check { - if r == nil { - return false - } - mem_int := uintptr(memory) - return mem_int >= uintptr(&r.memory[0]) && mem_int <= uintptr(&r.memory[BLOCKS_PER_REGION - 1]) -} - -_region_free_list_remove :: proc(region: ^Region, free_idx: u16) #no_bounds_check { - // pop, swap and update allocation hdr - if n := region.hdr.free_list_len - 1; free_idx != n { - region.hdr.free_list[free_idx] = sync.atomic_load(®ion.hdr.free_list[n]) - alloc_idx := region.hdr.free_list[free_idx] - sync.atomic_store_explicit(®ion.memory[alloc_idx].free_idx, free_idx, .Release) - } - region.hdr.free_list_len -= 1 -} - -// -// Direct mmap -// -_direct_mmap_alloc :: proc(size: int) -> rawptr { - mmap_size := _round_up_to_nearest(size + BLOCK_SIZE, PAGE_SIZE) - new_allocation, errno := linux.mmap(0, uint(mmap_size), MMAP_PROT, MMAP_FLAGS, -1, 0) - if errno != .NONE { - return nil - } - - alloc := (^Allocation_Header)(uintptr(new_allocation)) - alloc.requested = u64(size) // NOTE: requested = requested size - alloc.requested += IS_DIRECT_MMAP - return rawptr(mem.ptr_offset(alloc, 1)) -} - -_direct_mmap_resize :: proc(alloc: ^Allocation_Header, new_size: int) -> rawptr { - old_requested := int(alloc.requested & REQUESTED_MASK) - old_mmap_size := _round_up_to_nearest(old_requested + BLOCK_SIZE, PAGE_SIZE) - new_mmap_size := _round_up_to_nearest(new_size + BLOCK_SIZE, PAGE_SIZE) - if int(new_mmap_size) < MMAP_TO_REGION_SHRINK_THRESHOLD { - return _direct_mmap_to_region(alloc, new_size) - } else if old_requested == new_size { - return mem.ptr_offset(alloc, 1) - } - - new_allocation, errno := linux.mremap(alloc, uint(old_mmap_size), uint(new_mmap_size), {.MAYMOVE}) - if errno != .NONE { - return nil - } - - new_header := (^Allocation_Header)(uintptr(new_allocation)) - new_header.requested = u64(new_size) - new_header.requested += IS_DIRECT_MMAP - - if new_mmap_size > old_mmap_size { - // new section may not be pointer aligned, so cast to ^u8 - new_section := mem.ptr_offset((^u8)(new_header), old_requested + BLOCK_SIZE) - mem.zero(new_section, new_mmap_size - old_mmap_size) - } - return mem.ptr_offset(new_header, 1) - -} - -_direct_mmap_from_region :: proc(alloc: ^Allocation_Header, new_size: int) -> rawptr { - new_memory := _direct_mmap_alloc(new_size) - if new_memory != nil { - old_memory := mem.ptr_offset(alloc, 1) - mem.copy(new_memory, old_memory, int(_get_block_count(alloc^)) * BLOCK_SIZE) - } - _region_find_and_assign_local(alloc) - _region_local_free(alloc) - sync.atomic_store_explicit(&_local_region.hdr.local_addr, &_local_region, .Release) - return new_memory -} - -_direct_mmap_to_region :: proc(alloc: ^Allocation_Header, new_size: int) -> rawptr { - new_memory := heap_alloc(new_size) - if new_memory != nil { - mem.copy(new_memory, mem.ptr_offset(alloc, -1), new_size) - _direct_mmap_free(alloc) - } - return new_memory -} - -_direct_mmap_free :: proc(alloc: ^Allocation_Header) { - requested := int(alloc.requested & REQUESTED_MASK) - mmap_size := _round_up_to_nearest(requested + BLOCK_SIZE, PAGE_SIZE) - linux.munmap(alloc, uint(mmap_size)) -} - -// -// Util -// - -_get_block_count :: #force_inline proc(alloc: Allocation_Header) -> u16 { - return alloc.next - alloc.idx - 1 -} - -_get_allocation_header :: #force_inline proc(raw_mem: rawptr) -> ^Allocation_Header { - return mem.ptr_offset((^Allocation_Header)(raw_mem), -1) -} - -_round_up_to_nearest :: #force_inline proc(size, round: int) -> int { - return (size-1) + round - (size-1) % round -} +import "base:runtime" +_heap_allocator_proc :: runtime.heap_allocator_proc From dfd826ed86ffc8a8f8ce9ec52f99ba7bd7079382 Mon Sep 17 00:00:00 2001 From: dozn <16659513+dozn@users.noreply.github.com> Date: Mon, 20 Jan 2025 03:57:36 -0800 Subject: [PATCH 31/64] Fix Times Which Support DST on Windows --- core/time/timezone/tzdate.odin | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/time/timezone/tzdate.odin b/core/time/timezone/tzdate.odin index 96df44299..e62400889 100644 --- a/core/time/timezone/tzdate.odin +++ b/core/time/timezone/tzdate.odin @@ -168,7 +168,7 @@ process_rrule :: proc(rrule: datetime.TZ_RRule, tm: time.Time) -> (out: datetime }, } record_sort_proc :: proc(i, j: datetime.TZ_Record) -> bool { - return i.time > j.time + return i.time < j.time } slice.sort_by(records, record_sort_proc) @@ -179,7 +179,7 @@ process_rrule :: proc(rrule: datetime.TZ_RRule, tm: time.Time) -> (out: datetime } } - return records[len(records)-1], true + return records[0], true } datetime_to_utc :: proc(dt: datetime.DateTime) -> (out: datetime.DateTime, success: bool) #optional_ok { From 4950a65b097d1b900750f413e19ed77e884e5424 Mon Sep 17 00:00:00 2001 From: Karl Zylinski Date: Mon, 20 Jan 2025 14:46:27 +0100 Subject: [PATCH 32/64] Add examples link to README --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index b4bb6bac7..cc40fb1ad 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,10 @@ Answers to common questions about Odin. Documentation for all the official packages part of the [core](https://pkg.odin-lang.org/core/) and [vendor](https://pkg.odin-lang.org/vendor/) library collections. +#### [Examples](https://github.com/odin-lang/examples) + +Examples on how to write idiomatic Odin code. Shows how to accomplish specific tasks in Odin, as well as how to use packages from `core` and `vendor`. + #### [Odin Documentation](https://odin-lang.org/docs/) Documentation for the Odin language itself. From a5f3c1b84960ef3f0175d319acdb958769862397 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Mon, 20 Jan 2025 19:51:46 +0100 Subject: [PATCH 33/64] container/queue: fix init_with_contents Fixes #4729 --- core/container/queue/queue.odin | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/container/queue/queue.odin b/core/container/queue/queue.odin index f83a5f2b7..d1040a7c9 100644 --- a/core/container/queue/queue.odin +++ b/core/container/queue/queue.odin @@ -46,8 +46,7 @@ init_with_contents :: proc(q: ^$Q/Queue($T), backing: []T) -> bool { cap = builtin.len(backing), allocator = {procedure=runtime.nil_allocator_proc, data=nil}, } - q.len = len(backing) - q.offset = len(backing) + q.len = builtin.len(backing) return true } From bf581074028dae7f5d15b5d1e6d58619fdb6f77f Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Mon, 20 Jan 2025 20:15:03 +0100 Subject: [PATCH 34/64] os/os2: bring Linux to other impls standards by looping writes and maxing one shot RW sizes --- core/os/os2/file_linux.odin | 61 +++++++++++++++++++++++++------------ 1 file changed, 41 insertions(+), 20 deletions(-) diff --git a/core/os/os2/file_linux.odin b/core/os/os2/file_linux.odin index f8e4026da..9f6625091 100644 --- a/core/os/os2/file_linux.odin +++ b/core/os/os2/file_linux.odin @@ -7,6 +7,13 @@ import "core:time" import "core:sync" import "core:sys/linux" +// Most implementations will EINVAL at some point when doing big writes. +// In practice a read/write call would probably never read/write these big buffers all at once, +// which is why the number of bytes is returned and why there are procs that will call this in a +// loop for you. +// We set a max of 1GB to keep alignment and to be safe. +MAX_RW :: 1 << 30 + File_Impl :: struct { file: File, name: string, @@ -179,10 +186,11 @@ _seek :: proc(f: ^File_Impl, offset: i64, whence: io.Seek_From) -> (ret: i64, er } _read :: proc(f: ^File_Impl, p: []byte) -> (i64, Error) { - if len(p) == 0 { + if len(p) <= 0 { return 0, nil } - n, errno := linux.read(f.fd, p[:]) + + n, errno := linux.read(f.fd, p[:min(len(p), MAX_RW)]) if errno != .NONE { return -1, _get_platform_error(errno) } @@ -190,13 +198,13 @@ _read :: proc(f: ^File_Impl, p: []byte) -> (i64, Error) { } _read_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (i64, Error) { - if len(p) == 0 { + if len(p) <= 0 { return 0, nil } if offset < 0 { return 0, .Invalid_Offset } - n, errno := linux.pread(f.fd, p[:], offset) + n, errno := linux.pread(f.fd, p[:min(len(p), MAX_RW)], offset) if errno != .NONE { return -1, _get_platform_error(errno) } @@ -206,29 +214,42 @@ _read_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (i64, Error) { return i64(n), nil } -_write :: proc(f: ^File_Impl, p: []byte) -> (i64, Error) { - if len(p) == 0 { - return 0, nil +_write :: proc(f: ^File_Impl, p: []byte) -> (nt: i64, err: Error) { + p := p + for len(p) > 0 { + n, errno := linux.write(f.fd, p[:min(len(p), MAX_RW)]) + if errno != .NONE { + err = _get_platform_error(errno) + return + } + + p = p[n:] + nt += i64(n) } - n, errno := linux.write(f.fd, p[:]) - if errno != .NONE { - return -1, _get_platform_error(errno) - } - return i64(n), nil + + return } -_write_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (i64, Error) { - if len(p) == 0 { - return 0, nil - } +_write_at :: proc(f: ^File_Impl, p: []byte, offset: i64) -> (nt: i64, err: Error) { if offset < 0 { return 0, .Invalid_Offset } - n, errno := linux.pwrite(f.fd, p[:], offset) - if errno != .NONE { - return -1, _get_platform_error(errno) + + p := p + offset := offset + for len(p) > 0 { + n, errno := linux.pwrite(f.fd, p[:min(len(p), MAX_RW)], offset) + if errno != .NONE { + err = _get_platform_error(errno) + return + } + + p = p[n:] + nt += i64(n) + offset += i64(n) } - return i64(n), nil + + return } _file_size :: proc(f: ^File_Impl) -> (n: i64, err: Error) { From ac30d362067ae19c442296150d7db940ecae7914 Mon Sep 17 00:00:00 2001 From: Roland Kovacs Date: Mon, 20 Jan 2025 22:34:49 +0100 Subject: [PATCH 35/64] os/os2: Properly update CWD on Linux when using _process_start() The `dir_fd` argument to `execveat()` is not for setting the current working directory. It is used to resolve relative executable paths, hence explicit `chdir/fchdir` call is required to set CWD. --- core/os/os2/process_linux.odin | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/os/os2/process_linux.odin b/core/os/os2/process_linux.odin index 936fbfc40..c2979b282 100644 --- a/core/os/os2/process_linux.odin +++ b/core/os/os2/process_linux.odin @@ -547,6 +547,10 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { if _, errno = linux.dup2(stderr_fd, STDERR); errno != .NONE { write_errno_to_parent_and_abort(child_pipe_fds[WRITE], errno) } + if dir_fd != linux.AT_FDCWD { + errno = linux.fchdir(dir_fd) + assert(errno == nil) + } errno = linux.execveat(dir_fd, exe_path, &cargs[0], env) assert(errno != nil) From f6ead2e777d0acd9c786880d2e5f1f635efbaeca Mon Sep 17 00:00:00 2001 From: Roland Kovacs Date: Mon, 20 Jan 2025 23:31:13 +0100 Subject: [PATCH 36/64] os/os2: Linux _process_start() write back error on fchdir failure --- core/os/os2/process_linux.odin | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/core/os/os2/process_linux.odin b/core/os/os2/process_linux.odin index c2979b282..09fd8c255 100644 --- a/core/os/os2/process_linux.odin +++ b/core/os/os2/process_linux.odin @@ -548,8 +548,9 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) { write_errno_to_parent_and_abort(child_pipe_fds[WRITE], errno) } if dir_fd != linux.AT_FDCWD { - errno = linux.fchdir(dir_fd) - assert(errno == nil) + if errno = linux.fchdir(dir_fd); errno != .NONE { + write_errno_to_parent_and_abort(child_pipe_fds[WRITE], errno) + } } errno = linux.execveat(dir_fd, exe_path, &cargs[0], env) From 55abf6183687644418a167a9bc2ebbefef09df98 Mon Sep 17 00:00:00 2001 From: NicknEma Date: Tue, 21 Jan 2025 10:55:39 +0100 Subject: [PATCH 37/64] Add ACTCTX definition and procs --- core/sys/windows/kernel32.odin | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/core/sys/windows/kernel32.odin b/core/sys/windows/kernel32.odin index f1d7202da..fb5afba8a 100644 --- a/core/sys/windows/kernel32.odin +++ b/core/sys/windows/kernel32.odin @@ -1240,3 +1240,31 @@ GHND :: (GMEM_MOVEABLE | GMEM_ZEROINIT) GPTR :: (GMEM_FIXED | GMEM_ZEROINIT) LPTOP_LEVEL_EXCEPTION_FILTER :: PVECTORED_EXCEPTION_HANDLER + +ACTCTXW :: struct { + Size: ULONG, + Flags: DWORD, + Source: LPCWSTR, + ProcessorArchitecture: USHORT, + LangId: LANGID, + AssemblyDirectory: LPCWSTR, + ResourceName: LPCWSTR, + ApplicationName: LPCWSTR, + Module: HMODULE, +} +PACTCTXW :: ^ACTCTXW +PCACTCTXW :: ^ACTCTXW + +ACTCTX_FLAG_PROCESSOR_ARCHITECTURE_VALID :: 0x001 +ACTCTX_FLAG_LANGID_VALID :: 0x002 +ACTCTX_FLAG_ASSEMBLY_DIRECTORY_VALID :: 0x004 +ACTCTX_FLAG_RESOURCE_NAME_VALID :: 0x008 +ACTCTX_FLAG_SET_PROCESS_DEFAULT :: 0x010 +ACTCTX_FLAG_APPLICATION_NAME_VALID :: 0x020 +ACTCTX_FLAG_HMODULE_VALID :: 0x080 + +@(default_calling_convention="system") +foreign kernel32 { + CreateActCtxW :: proc(pActCtx: ^ACTCTXW) -> HANDLE --- + ActivateActCtx :: proc(hActCtx: HANDLE, lpCookie: ^ULONG_PTR) -> BOOL --- +} From b6736424125f0ad7dd633369a875050788c7df65 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Mon, 20 Jan 2025 22:35:35 +0100 Subject: [PATCH 38/64] os/os2: add get_executable_path and get_executable_directory --- core/os/os2/errors_posix.odin | 13 ++++++-- core/os/os2/path.odin | 12 ++++++++ core/os/os2/path_darwin.odin | 31 +++++++++++++++++++ core/os/os2/path_freebsd.odin | 29 ++++++++++++++++++ core/os/os2/path_linux.odin | 22 +++++++++++++- core/os/os2/path_netbsd.odin | 24 +++++++++++++++ core/os/os2/path_openbsd.odin | 57 +++++++++++++++++++++++++++++++++++ core/os/os2/path_windows.odin | 20 ++++++++++++ core/sys/darwin/dyld.odin | 7 +++++ tests/core/os/os2/path.odin | 22 ++++++++++++++ 10 files changed, 234 insertions(+), 3 deletions(-) create mode 100644 core/os/os2/path_darwin.odin create mode 100644 core/os/os2/path_freebsd.odin create mode 100644 core/os/os2/path_netbsd.odin create mode 100644 core/os/os2/path_openbsd.odin create mode 100644 core/sys/darwin/dyld.odin create mode 100644 tests/core/os/os2/path.odin diff --git a/core/os/os2/errors_posix.odin b/core/os/os2/errors_posix.odin index 0b5876c0b..8a9ca07df 100644 --- a/core/os/os2/errors_posix.odin +++ b/core/os/os2/errors_posix.odin @@ -10,8 +10,12 @@ _error_string :: proc(errno: i32) -> string { return string(posix.strerror(posix.Errno(errno))) } -_get_platform_error :: proc() -> Error { - #partial switch errno := posix.errno(); errno { +_get_platform_error_from_errno :: proc() -> Error { + return _get_platform_error_existing(posix.errno()) +} + +_get_platform_error_existing :: proc(errno: posix.Errno) -> Error { + #partial switch errno { case .EPERM: return .Permission_Denied case .EEXIST: @@ -32,3 +36,8 @@ _get_platform_error :: proc() -> Error { return Platform_Error(errno) } } + +_get_platform_error :: proc{ + _get_platform_error_existing, + _get_platform_error_from_errno, +} diff --git a/core/os/os2/path.odin b/core/os/os2/path.odin index 254950d68..9231307f5 100644 --- a/core/os/os2/path.odin +++ b/core/os/os2/path.odin @@ -2,6 +2,8 @@ package os2 import "base:runtime" +import "core:path/filepath" + Path_Separator :: _Path_Separator // OS-Specific Path_Separator_String :: _Path_Separator_String // OS-Specific Path_List_Separator :: _Path_List_Separator // OS-Specific @@ -39,3 +41,13 @@ setwd :: set_working_directory set_working_directory :: proc(dir: string) -> (err: Error) { return _set_working_directory(dir) } + +get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) { + return _get_executable_path(allocator) +} + +get_executable_directory :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) { + path = _get_executable_path(allocator) or_return + path, _ = filepath.split(path) + return +} diff --git a/core/os/os2/path_darwin.odin b/core/os/os2/path_darwin.odin new file mode 100644 index 000000000..2e7bbc7b9 --- /dev/null +++ b/core/os/os2/path_darwin.odin @@ -0,0 +1,31 @@ +package os2 + +import "base:runtime" + +import "core:sys/darwin" +import "core:sys/posix" + +_get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) { + size: u32 + + ret := darwin._NSGetExecutablePath(nil, &size) + assert(ret == -1) + assert(size > 0) + + TEMP_ALLOCATOR_GUARD() + + buf := make([]byte, size, temp_allocator()) or_return + assert(u32(len(buf)) == size) + + ret = darwin._NSGetExecutablePath(raw_data(buf), &size) + assert(ret == 0) + + real := posix.realpath(cstring(raw_data(buf))) + if real == nil { + err = _get_platform_error() + return + } + defer posix.free(real) + + return clone_string(string(real), allocator) +} diff --git a/core/os/os2/path_freebsd.odin b/core/os/os2/path_freebsd.odin new file mode 100644 index 000000000..577108eca --- /dev/null +++ b/core/os/os2/path_freebsd.odin @@ -0,0 +1,29 @@ +package os2 + +import "base:runtime" + +import "core:sys/freebsd" +import "core:sys/posix" + +_get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) { + req := []freebsd.MIB_Identifier{.CTL_KERN, .KERN_PROC, .KERN_PROC_PATHNAME, freebsd.MIB_Identifier(-1)} + + size: uint + if ret := freebsd.sysctl(req, nil, &size, nil, 0); ret != .NONE { + err = _get_platform_error(posix.Errno(ret)) + return + } + assert(size > 0) + + buf := make([]byte, size, allocator) or_return + defer if err != nil { delete(buf, allocator) } + + assert(uint(len(buf)) == size) + + if ret := freebsd.sysctl(req, raw_data(buf), &size, nil, 0); ret != .NONE { + err = _get_platform_error(posix.Errno(ret)) + return + } + + return string(buf[:size]), nil +} diff --git a/core/os/os2/path_linux.odin b/core/os/os2/path_linux.odin index bfdb645ef..e3e7f8a7c 100644 --- a/core/os/os2/path_linux.odin +++ b/core/os/os2/path_linux.odin @@ -1,9 +1,10 @@ #+private package os2 +import "base:runtime" + import "core:strings" import "core:strconv" -import "base:runtime" import "core:sys/linux" _Path_Separator :: '/' @@ -171,6 +172,25 @@ _set_working_directory :: proc(dir: string) -> Error { return _get_platform_error(linux.chdir(dir_cstr)) } +_get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) { + TEMP_ALLOCATOR_GUARD() + + buf := make([dynamic]byte, 1024, temp_allocator()) or_return + for { + n, errno := linux.readlink("/proc/self/exe", buf[:]) + if errno != .NONE { + err = _get_platform_error(errno) + return + } + + if n < len(buf) { + return clone_string(string(buf[:n]), allocator) + } + + resize(&buf, len(buf)*2) or_return + } +} + _get_full_path :: proc(fd: linux.Fd, allocator: runtime.Allocator) -> (fullpath: string, err: Error) { PROC_FD_PATH :: "/proc/self/fd/" diff --git a/core/os/os2/path_netbsd.odin b/core/os/os2/path_netbsd.odin new file mode 100644 index 000000000..f56a91fd6 --- /dev/null +++ b/core/os/os2/path_netbsd.odin @@ -0,0 +1,24 @@ +package os2 + +import "base:runtime" + +import "core:sys/posix" + +_get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) { + TEMP_ALLOCATOR_GUARD() + + buf := make([dynamic]byte, 1024, temp_allocator()) or_return + for { + n := posix.readlink("/proc/curproc/exe", raw_data(buf), len(buf)) + if n < 0 { + err = _get_platform_error() + return + } + + if n < len(buf) { + return clone_string(string(buf[:n]), allocator) + } + + resize(&buf, len(buf)*2) or_return + } +} diff --git a/core/os/os2/path_openbsd.odin b/core/os/os2/path_openbsd.odin new file mode 100644 index 000000000..f56c1a61b --- /dev/null +++ b/core/os/os2/path_openbsd.odin @@ -0,0 +1,57 @@ +package os2 + +import "base:runtime" + +import "core:strings" +import "core:sys/posix" + +_get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) { + // OpenBSD does not have an API for this, we do our best below. + + if len(runtime.args__) <= 0 { + err = .Invalid_Path + return + } + + real :: proc(path: cstring, allocator: runtime.Allocator) -> (out: string, err: Error) { + real := posix.realpath(path) + if real == nil { + err = _get_platform_error() + return + } + defer posix.free(real) + return clone_string(string(real), allocator) + } + + arg := runtime.args__[0] + sarg := string(arg) + + if len(sarg) == 0 { + err = .Invalid_Path + return + } + + if sarg[0] == '.' || sarg[0] == '/' { + return real(arg, allocator) + } + + TEMP_ALLOCATOR_GUARD() + + buf := strings.builder_make(temp_allocator()) + + paths := get_env("PATH", temp_allocator()) + for dir in strings.split_iterator(&paths, ":") { + strings.builder_reset(&buf) + strings.write_string(&buf, dir) + strings.write_string(&buf, "/") + strings.write_string(&buf, sarg) + + cpath := strings.to_cstring(&buf) + if posix.access(cpath, {.X_OK}) == .OK { + return real(cpath, allocator) + } + } + + err = .Invalid_Path + return +} diff --git a/core/os/os2/path_windows.odin b/core/os/os2/path_windows.odin index 3e92cb6f3..041a4d1e3 100644 --- a/core/os/os2/path_windows.odin +++ b/core/os/os2/path_windows.odin @@ -136,6 +136,26 @@ _set_working_directory :: proc(dir: string) -> (err: Error) { return } +_get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) { + TEMP_ALLOCATOR_GUARD() + + buf := make([dynamic]u16, 512, temp_allocator()) or_return + for { + ret := win32.GetModuleFileNameW(nil, raw_data(buf), win32.DWORD(len(buf))) + if ret == 0 { + err = _get_platform_error() + return + } + + if ret == win32.DWORD(len(buf)) && win32.GetLastError() == win32.ERROR_INSUFFICIENT_BUFFER { + resize(&buf, len(buf)*2) or_return + continue + } + + return win32_utf16_to_utf8(buf[:ret], allocator) + } +} + can_use_long_paths: bool @(init) diff --git a/core/sys/darwin/dyld.odin b/core/sys/darwin/dyld.odin new file mode 100644 index 000000000..0a6a2cfa6 --- /dev/null +++ b/core/sys/darwin/dyld.odin @@ -0,0 +1,7 @@ +package darwin + +foreign import system "system:System.framework" + +foreign system { + _NSGetExecutablePath :: proc(buf: [^]byte, bufsize: ^u32) -> i32 --- +} diff --git a/tests/core/os/os2/path.odin b/tests/core/os/os2/path.odin new file mode 100644 index 000000000..b91f43368 --- /dev/null +++ b/tests/core/os/os2/path.odin @@ -0,0 +1,22 @@ +package tests_core_os_os2 + +import os "core:os/os2" +import "core:log" +import "core:path/filepath" +import "core:testing" +import "core:strings" + +@(test) +test_executable :: proc(t: ^testing.T) { + path, err := os.get_executable_path(context.allocator) + defer delete(path) + + log.infof("executable path: %q", path) + + // NOTE: some sanity checks that should always be the case, at least in the CI. + + testing.expect_value(t, err, nil) + testing.expect(t, len(path) > 0) + testing.expect(t, filepath.is_abs(path)) + testing.expectf(t, strings.contains(path, filepath.base(os.args[0])), "expected the executable path to contain the base of os.args[0] which is %q", filepath.base(os.args[0])) +} From f1b0b197109a2ffea9435a2ff8eb7bd037298292 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Tue, 21 Jan 2025 19:14:15 +0100 Subject: [PATCH 39/64] os/os2: get_executable_path and working directory on wasi --- core/os/os2/path_wasi.odin | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/core/os/os2/path_wasi.odin b/core/os/os2/path_wasi.odin index 17efbff62..2f8a3c8c6 100644 --- a/core/os/os2/path_wasi.odin +++ b/core/os/os2/path_wasi.odin @@ -4,6 +4,7 @@ package os2 import "base:runtime" import "core:path/filepath" +import "core:sync" import "core:sys/wasm/wasi" _Path_Separator :: '/' @@ -74,11 +75,39 @@ _remove_all :: proc(path: string) -> (err: Error) { return remove(path) } +g_wd: string +g_wd_mutex: sync.Mutex + _get_working_directory :: proc(allocator: runtime.Allocator) -> (dir: string, err: Error) { - return ".", .Unsupported + sync.guard(&g_wd_mutex) + + return clone_string(g_wd if g_wd != "" else "/", allocator) } _set_working_directory :: proc(dir: string) -> (err: Error) { - err = .Unsupported + sync.guard(&g_wd_mutex) + + if dir == g_wd { + return + } + + if g_wd != "" { + delete(g_wd, file_allocator()) + } + + g_wd = clone_string(dir, file_allocator()) or_return return } + +_get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) { + if len(args) <= 0 { + return clone_string("/", allocator) + } + + arg := args[0] + if len(arg) > 0 && (arg[0] == '.' || arg[0] == '/') { + return clone_string(arg, allocator) + } + + return concatenate({"/", arg}, allocator) +} From b25ca0bb11f64a2af887801d451bd8638dba9e76 Mon Sep 17 00:00:00 2001 From: jason Date: Tue, 21 Jan 2025 20:37:02 -0500 Subject: [PATCH 40/64] fixes compiler crash on syntax error (issue 4738) --- src/types.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/types.cpp b/src/types.cpp index 233f903a3..0b6e6d334 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -4773,7 +4773,9 @@ gb_internal gbString write_type_to_string(gbString str, Type *type, bool shortha case Type_BitSet: str = gb_string_appendc(str, "bit_set["); - if (is_type_enum(type->BitSet.elem)) { + if (type->BitSet.elem == nullptr) { + str = gb_string_appendc(str, ""); + } else if (is_type_enum(type->BitSet.elem)) { str = write_type_to_string(str, type->BitSet.elem); } else { str = gb_string_append_fmt(str, "%lld", type->BitSet.lower); From e85667c95cdd9c61dccd1e1b747aa07889edcd6a Mon Sep 17 00:00:00 2001 From: jason Date: Tue, 21 Jan 2025 20:48:11 -0500 Subject: [PATCH 41/64] fix grammar in error message --- src/check_decl.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/check_decl.cpp b/src/check_decl.cpp index 1d792dad8..bf6e39bd2 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -60,7 +60,7 @@ gb_internal Type *check_init_variable(CheckerContext *ctx, Entity *e, Operand *o error(operand->expr, "Cannot assign a type '%s' to variable '%.*s'", t, LIT(e->token.string)); } if (e->type == nullptr) { - error_line("\tThe type of the variable '%.*s' cannot be inferred as a type does not have a default type\n", LIT(e->token.string)); + error_line("\tThe type of the variable '%.*s' cannot be inferred as a type and does not have a default type\n", LIT(e->token.string)); } e->type = operand->type; return nullptr; From 57b8da79f4a06ec157bf96855b06eac4b64c65a9 Mon Sep 17 00:00:00 2001 From: wrathdoesthat <152635455+wrathdoesthat@users.noreply.github.com> Date: Wed, 22 Jan 2025 04:33:33 -0500 Subject: [PATCH 42/64] Add GetTempFileNameW --- core/sys/windows/kernel32.odin | 1 + 1 file changed, 1 insertion(+) diff --git a/core/sys/windows/kernel32.odin b/core/sys/windows/kernel32.odin index fb5afba8a..266dcdbf4 100644 --- a/core/sys/windows/kernel32.odin +++ b/core/sys/windows/kernel32.odin @@ -123,6 +123,7 @@ foreign kernel32 { WaitCommEvent :: proc(handle: HANDLE, lpEvtMask: LPDWORD, lpOverlapped: LPOVERLAPPED) -> BOOL --- GetCommandLineW :: proc() -> LPCWSTR --- GetTempPathW :: proc(nBufferLength: DWORD, lpBuffer: LPCWSTR) -> DWORD --- + GetTempFileNameW :: proc(lpPathName: LPCWSTR, lpPrefixString: LPCWSTR, uUnique: c_int, lpTempFileName: LPWSTR) -> c_uint --- GetCurrentProcess :: proc() -> HANDLE --- GetCurrentProcessId :: proc() -> DWORD --- GetCurrentThread :: proc() -> HANDLE --- From d6633639dc820e564b41f8dd422b424b0a9dcbec Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 22 Jan 2025 13:01:06 +0000 Subject: [PATCH 43/64] Remove duplicates of .framework/.dynlib/.so in linker --- src/linker.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/linker.cpp b/src/linker.cpp index 261d6e7a4..59e6d8dc1 100644 --- a/src/linker.cpp +++ b/src/linker.cpp @@ -536,7 +536,16 @@ gb_internal i32 linker_stage(LinkerData *gen) { } array_add(&gen->output_object_paths, obj_file); } else { - if (string_set_update(&min_libs_set, lib) && build_context.min_link_libs) { + bool short_circuit = false; + if (string_ends_with(lib, str_lit(".framework"))) { + short_circuit = true; + } else if (string_ends_with(lib, str_lit(".dylib"))) { + short_circuit = true; + } else if (string_ends_with(lib, str_lit(".so"))) { + short_circuit = true; + } + + if (string_set_update(&min_libs_set, lib) && (build_context.min_link_libs || short_circuit)) { continue; } From dde3a03022d6cb3ede0d38cfe3976ff190aa6ab5 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 22 Jan 2025 13:03:51 +0000 Subject: [PATCH 44/64] Add `priority_index` to `Foundation.framework` import in `base:runtime` --- base/runtime/procs_darwin.odin | 1 + 1 file changed, 1 insertion(+) diff --git a/base/runtime/procs_darwin.odin b/base/runtime/procs_darwin.odin index 4f4903d47..c3fc46af1 100644 --- a/base/runtime/procs_darwin.odin +++ b/base/runtime/procs_darwin.odin @@ -1,6 +1,7 @@ #+private package runtime +@(priority_index=-1e6) foreign import "system:Foundation.framework" import "base:intrinsics" From d4e15074ea0591af908bf31bb5abf33c918c1bdf Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 22 Jan 2025 13:13:00 +0000 Subject: [PATCH 45/64] Enable `-use-separate-modules` as default for all platforms --- src/build_settings.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/build_settings.cpp b/src/build_settings.cpp index a8d06d56d..ed314f9f6 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -1719,13 +1719,11 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta bc->optimization_level = gb_clamp(bc->optimization_level, -1, 3); -#if defined(GB_SYSTEM_WINDOWS) if (bc->optimization_level <= 0) { if (!is_arch_wasm()) { bc->use_separate_modules = true; } } -#endif // TODO: Static map calls are bugged on `amd64sysv` abi. From 867af80bff2956178ac72d9afbc9327b67cd4ae8 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 22 Jan 2025 13:26:35 +0000 Subject: [PATCH 46/64] Add `-use-single-module` --- src/build_settings.cpp | 5 +++++ src/main.cpp | 21 +++++++++++++++++++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/build_settings.cpp b/src/build_settings.cpp index ed314f9f6..08df34c57 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -472,6 +472,7 @@ struct BuildContext { bool ignore_microsoft_magic; bool linker_map_file; + bool use_single_module; bool use_separate_modules; bool module_per_file; bool cached; @@ -1725,6 +1726,10 @@ gb_internal void init_build_context(TargetMetrics *cross_target, Subtarget subta } } + if (build_context.use_single_module) { + bc->use_separate_modules = false; + } + // TODO: Static map calls are bugged on `amd64sysv` abi. if (bc->metrics.os != TargetOs_windows && bc->metrics.arch == TargetArch_amd64) { diff --git a/src/main.cpp b/src/main.cpp index 24e33850e..e8336b292 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -331,6 +331,7 @@ enum BuildFlagKind { BuildFlag_UseRADLink, BuildFlag_Linker, BuildFlag_UseSeparateModules, + BuildFlag_UseSingleModule, BuildFlag_NoThreadedChecker, BuildFlag_ShowDebugMessages, @@ -545,6 +546,7 @@ gb_internal bool parse_build_flags(Array args) { add_flag(&build_flags, BuildFlag_UseRADLink, str_lit("radlink"), BuildFlagParam_None, Command__does_build); add_flag(&build_flags, BuildFlag_Linker, str_lit("linker"), BuildFlagParam_String, Command__does_build); add_flag(&build_flags, BuildFlag_UseSeparateModules, str_lit("use-separate-modules"), BuildFlagParam_None, Command__does_build); + add_flag(&build_flags, BuildFlag_UseSingleModule, str_lit("use-single-module"), BuildFlagParam_None, Command__does_build); add_flag(&build_flags, BuildFlag_NoThreadedChecker, str_lit("no-threaded-checker"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_ShowDebugMessages, str_lit("show-debug-messages"), BuildFlagParam_None, Command_all); @@ -1240,8 +1242,19 @@ gb_internal bool parse_build_flags(Array args) { case BuildFlag_UseSeparateModules: + if (build_context.use_single_module) { + gb_printf_err("-use-separate-modules cannot be used with -use-single-module\n"); + bad_flags = true; + } build_context.use_separate_modules = true; break; + case BuildFlag_UseSingleModule: + if (build_context.use_separate_modules) { + gb_printf_err("-use-single-module cannot be used with -use-separate-modules\n"); + bad_flags = true; + } + build_context.use_single_module = true; + break; case BuildFlag_NoThreadedChecker: build_context.no_threaded_checker = true; break; @@ -2717,8 +2730,12 @@ gb_internal void print_show_help(String const arg0, String command, String optio if (run_or_build) { if (print_flag("-use-separate-modules")) { print_usage_line(2, "The backend generates multiple build units which are then linked together."); - print_usage_line(2, "Normally, a single build unit is generated for a standard project."); - print_usage_line(2, "This is the default behaviour on Windows for '-o:none' and '-o:minimal' builds."); + print_usage_line(2, "This is the default behaviour for '-o:none' and '-o:minimal' builds."); + print_usage_line(2, "Normally, a single build unit is generated for a standard project for '-o:speed' or '-o:size'."); + } + if (print_flag("-use-single-module")) { + print_usage_line(2, "The backend generates only a single build unit."); + print_usage_line(2, "This is the default behaviour for '-o:speed' or '-o:size'."); } } From d54de6704a0acb95e8123480121f0ddba62f1516 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Wed, 22 Jan 2025 18:39:33 +0100 Subject: [PATCH 47/64] os/os2: use proc_pidpath for executable path on darwin --- core/os/os2/path_darwin.odin | 26 ++++++-------------------- core/sys/darwin/dyld.odin | 7 ------- 2 files changed, 6 insertions(+), 27 deletions(-) delete mode 100644 core/sys/darwin/dyld.odin diff --git a/core/os/os2/path_darwin.odin b/core/os/os2/path_darwin.odin index 2e7bbc7b9..65aaf1e95 100644 --- a/core/os/os2/path_darwin.odin +++ b/core/os/os2/path_darwin.odin @@ -6,26 +6,12 @@ import "core:sys/darwin" import "core:sys/posix" _get_executable_path :: proc(allocator: runtime.Allocator) -> (path: string, err: Error) { - size: u32 - - ret := darwin._NSGetExecutablePath(nil, &size) - assert(ret == -1) - assert(size > 0) - - TEMP_ALLOCATOR_GUARD() - - buf := make([]byte, size, temp_allocator()) or_return - assert(u32(len(buf)) == size) - - ret = darwin._NSGetExecutablePath(raw_data(buf), &size) - assert(ret == 0) - - real := posix.realpath(cstring(raw_data(buf))) - if real == nil { - err = _get_platform_error() - return + buffer: [darwin.PIDPATHINFO_MAXSIZE]byte = --- + ret := darwin.proc_pidpath(posix.getpid(), raw_data(buffer[:]), len(buffer)) + if ret > 0 { + return clone_string(string(buffer[:ret]), allocator) } - defer posix.free(real) - return clone_string(string(real), allocator) + err = _get_platform_error() + return } diff --git a/core/sys/darwin/dyld.odin b/core/sys/darwin/dyld.odin deleted file mode 100644 index 0a6a2cfa6..000000000 --- a/core/sys/darwin/dyld.odin +++ /dev/null @@ -1,7 +0,0 @@ -package darwin - -foreign import system "system:System.framework" - -foreign system { - _NSGetExecutablePath :: proc(buf: [^]byte, bufsize: ^u32) -> i32 --- -} From 5a29e80bc35e040a9193dccca6a4d495bd1df37b Mon Sep 17 00:00:00 2001 From: prescientmoon Date: Thu, 23 Jan 2025 05:56:27 +0100 Subject: [PATCH 48/64] Fix 2x2 matrix inverses in specific.odin --- core/math/linalg/specific.odin | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/core/math/linalg/specific.odin b/core/math/linalg/specific.odin index b841f0610..c23feddce 100644 --- a/core/math/linalg/specific.odin +++ b/core/math/linalg/specific.odin @@ -1207,8 +1207,8 @@ matrix2_inverse_f16 :: proc "contextless" (m: Matrix2f16) -> (c: Matrix2f16) #no d := m[0, 0]*m[1, 1] - m[0, 1]*m[1, 0] id := 1.0/d c[0, 0] = +m[1, 1] * id - c[0, 1] = -m[1, 0] * id - c[1, 0] = -m[0, 1] * id + c[0, 1] = -m[0, 1] * id + c[1, 0] = -m[1, 0] * id c[1, 1] = +m[0, 0] * id return c } @@ -1217,8 +1217,8 @@ matrix2_inverse_f32 :: proc "contextless" (m: Matrix2f32) -> (c: Matrix2f32) #no d := m[0, 0]*m[1, 1] - m[0, 1]*m[1, 0] id := 1.0/d c[0, 0] = +m[1, 1] * id - c[0, 1] = -m[1, 0] * id - c[1, 0] = -m[0, 1] * id + c[0, 1] = -m[0, 1] * id + c[1, 0] = -m[1, 0] * id c[1, 1] = +m[0, 0] * id return c } @@ -1227,8 +1227,8 @@ matrix2_inverse_f64 :: proc "contextless" (m: Matrix2f64) -> (c: Matrix2f64) #no d := m[0, 0]*m[1, 1] - m[0, 1]*m[1, 0] id := 1.0/d c[0, 0] = +m[1, 1] * id - c[0, 1] = -m[1, 0] * id - c[1, 0] = -m[0, 1] * id + c[0, 1] = -m[0, 1] * id + c[1, 0] = -m[1, 0] * id c[1, 1] = +m[0, 0] * id return c } From 7127992625d8d1df4db6140f564526004aa3eaa3 Mon Sep 17 00:00:00 2001 From: flysand7 Date: Fri, 24 Jan 2025 08:36:01 +1100 Subject: [PATCH 49/64] Fix the '+' sign placement in the presence of '0'-padding --- core/fmt/fmt.odin | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/fmt/fmt.odin b/core/fmt/fmt.odin index da3b419d5..51e70f6b7 100644 --- a/core/fmt/fmt.odin +++ b/core/fmt/fmt.odin @@ -1379,9 +1379,9 @@ _pad :: proc(fi: ^Info, s: string) { if fi.minus { // right pad io.write_string(fi.writer, s, &fi.n) fmt_write_padding(fi, width) - } else if !fi.space && s != "" && s[0] == '-' { + } else if !fi.space && s != "" && (s[0] == '-' || s[0] == '+') { // left pad accounting for zero pad of negative number - io.write_byte(fi.writer, '-', &fi.n) + io.write_byte(fi.writer, s[0], &fi.n) fmt_write_padding(fi, width) io.write_string(fi.writer, s[1:], &fi.n) } else { // left pad From f57048f8627b5928df2bdbdffd85e3ec98e1481a Mon Sep 17 00:00:00 2001 From: Dan Korostelev Date: Fri, 24 Jan 2025 01:12:49 +0100 Subject: [PATCH 50/64] fix raylib.CameraMoveRight signature --- vendor/raylib/raylib.odin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/raylib/raylib.odin b/vendor/raylib/raylib.odin index 755f3bedd..02bb6deea 100644 --- a/vendor/raylib/raylib.odin +++ b/vendor/raylib/raylib.odin @@ -1205,7 +1205,7 @@ foreign lib { CameraMoveForward :: proc(camera: ^Camera, distance: f32, moveInWorldPlane: bool) --- // move the camera in its forward direction CameraMoveUp :: proc(camera: ^Camera, distance: f32) --- // move camera in its up direction - CameraMoveRight :: proc(camera: ^Camera, distance: f32, delta: f32) --- // move camera in it's current right direction + CameraMoveRight :: proc(camera: ^Camera, distance: f32, moveInWorldPlane: bool) --- // move camera in it's current right direction CameraMoveToTarget :: proc(camera: ^Camera, delta: f32) --- // moves the camera position closer/farther to/from the camera target CameraYaw :: proc(camera: ^Camera, angle: f32, rotateAroundTarget: bool) --- // rotates the camera around its up vector (left and right) CameraPitch :: proc(camera: ^Camera, angle: f32, lockView: bool, rotateAroundTarget: bool, rotateUp: bool) --- // rotates the camera around its right vector (up and down) From 98b3a9eacd9b95a5db75fb001da0bfb0c7a18645 Mon Sep 17 00:00:00 2001 From: Barinzaya Date: Fri, 24 Jan 2025 09:42:10 -0500 Subject: [PATCH 51/64] Added support for growing in place to some arenas. This affects `runtime.Arena` and `virtual.Arena`, but not currently `mem.Arena`. These changes allow the last allocation that has been made to be resized to a larger size by just extending their allocation in-place, when there's sufficient room in the memory block to do so. Shrinking in place and re-using the rest of the allocation can be supported using almost the same logic, but would require the memory to be zeroed. Since this would add a additional cost that isn't currently present, shrinking has not been changed. --- .../runtime/default_temp_allocator_arena.odin | 22 +++++++++++++++---- core/mem/virtual/arena.odin | 22 +++++++++++++++---- 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/base/runtime/default_temp_allocator_arena.odin b/base/runtime/default_temp_allocator_arena.odin index 878a2d070..6e2900411 100644 --- a/base/runtime/default_temp_allocator_arena.odin +++ b/base/runtime/default_temp_allocator_arena.odin @@ -210,10 +210,24 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: Allocator_Mode, case size == 0: err = .Mode_Not_Implemented return - case (uintptr(old_data) & uintptr(alignment-1) == 0) && size < old_size: - // shrink data in-place - data = old_data[:size] - return + case uintptr(old_data) & uintptr(alignment-1) == 0: + if size < old_size { + // shrink data in-place + data = old_data[:size] + return + } + + if block := arena.curr_block; block != nil { + start := uint(uintptr(old_memory)) - uint(uintptr(block.base)) + old_end := start + old_size + new_end := start + size + if start < old_end && old_end == block.used && new_end <= block.capacity { + // grow data in-place, adjusting next allocation + block.used = uint(new_end) + data = block.base[start:new_end] + return + } + } } new_memory := arena_alloc(arena, size, alignment, location) or_return diff --git a/core/mem/virtual/arena.odin b/core/mem/virtual/arena.odin index 675558ec8..5191505cf 100644 --- a/core/mem/virtual/arena.odin +++ b/core/mem/virtual/arena.odin @@ -328,10 +328,24 @@ arena_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode, case size == 0: err = .Mode_Not_Implemented return - case (uintptr(old_data) & uintptr(alignment-1) == 0) && size < old_size: - // shrink data in-place - data = old_data[:size] - return + case uintptr(old_data) & uintptr(alignment-1) == 0: + if size < old_size { + // shrink data in-place + data = old_data[:size] + return + } + + if block := arena.curr_block; block != nil { + start := uint(uintptr(old_memory)) - uint(uintptr(block.base)) + old_end := start + old_size + new_end := start + size + if start < old_end && old_end == block.used && new_end <= block.reserved { + // grow data in-place, adjusting next allocation + _ = alloc_from_memory_block(block, new_end - old_end, 1, default_commit_size=arena.default_commit_size) or_return + data = block.base[start:new_end] + return + } + } } new_memory := arena_alloc(arena, size, alignment, location) or_return From b2aaf90f88aa85e8893325f78260b3723dc4fe99 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Fri, 24 Jan 2025 19:23:49 +0100 Subject: [PATCH 52/64] fix separate modules with objc code --- src/llvm_backend.cpp | 12 +++- src/llvm_backend.hpp | 4 +- src/llvm_backend_utility.cpp | 104 +++++++++++++++++++---------------- 3 files changed, 69 insertions(+), 51 deletions(-) diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index 696ced0df..277d0433e 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -1147,14 +1147,22 @@ gb_internal void lb_finalize_objc_names(lbProcedure *p) { String name = entry.key; args[0] = lb_const_value(m, t_cstring, exact_value_string(name)); lbValue ptr = lb_emit_runtime_call(p, "objc_lookUpClass", args); - lb_addr_store(p, entry.value, ptr); + + lbValue ptr_ = lb_find_value_from_entity(m, entry.value); + lbAddr local_addr = lb_addr(ptr_); + + lb_addr_store(p, local_addr, ptr); } for (auto const &entry : m->objc_selectors) { String name = entry.key; args[0] = lb_const_value(m, t_cstring, exact_value_string(name)); lbValue ptr = lb_emit_runtime_call(p, "sel_registerName", args); - lb_addr_store(p, entry.value, ptr); + + lbValue ptr_ = lb_find_value_from_entity(m, entry.value); + lbAddr local_addr = lb_addr(ptr_); + + lb_addr_store(p, local_addr, ptr); } lb_end_procedure_body(p); diff --git a/src/llvm_backend.hpp b/src/llvm_backend.hpp index 42d283a1e..dd56d56a3 100644 --- a/src/llvm_backend.hpp +++ b/src/llvm_backend.hpp @@ -196,8 +196,8 @@ struct lbModule { RecursiveMutex debug_values_mutex; PtrMap debug_values; - StringMap objc_classes; - StringMap objc_selectors; + StringMap objc_classes; + StringMap objc_selectors; PtrMap map_cell_info_map; // address of runtime.Map_Info PtrMap map_info_map; // address of runtime.Map_Cell_Info diff --git a/src/llvm_backend_utility.cpp b/src/llvm_backend_utility.cpp index 7b7c9d6e9..61dafa1c0 100644 --- a/src/llvm_backend_utility.cpp +++ b/src/llvm_backend_utility.cpp @@ -2093,23 +2093,36 @@ gb_internal void lb_set_wasm_export_attributes(LLVMValueRef value, String export gb_internal lbAddr lb_handle_objc_find_or_register_selector(lbProcedure *p, String const &name) { - lbAddr *found = string_map_get(&p->module->objc_selectors, name); + lbModule *default_module = &p->module->gen->default_module; + Entity *entity = {}; + + Entity **found = string_map_get(&p->module->objc_selectors, name); if (found) { - return *found; - } else { - lbModule *default_module = &p->module->gen->default_module; - Entity *e = nullptr; - lbAddr default_addr = lb_add_global_generated(default_module, t_objc_SEL, {}, &e); - - lbValue ptr = lb_find_value_from_entity(p->module, e); - lbAddr local_addr = lb_addr(ptr); - - string_map_set(&default_module->objc_selectors, name, default_addr); - if (default_module != p->module) { - string_map_set(&p->module->objc_selectors, name, local_addr); - } - return local_addr; + entity = *found; } + + if (!entity) { + if (default_module != p->module) { + found = string_map_get(&default_module->objc_selectors, name); + if (found) { + entity = *found; + } + } + + if (!entity) { + lbAddr default_addr = lb_add_global_generated(default_module, t_objc_SEL, {}, &entity); + string_map_set(&default_module->objc_selectors, name, entity); + } + } + + lbValue ptr = lb_find_value_from_entity(p->module, entity); + lbAddr local_addr = lb_addr(ptr); + + if (default_module != p->module) { + string_map_set(&p->module->objc_selectors, name, entity); + } + + return local_addr; } gb_internal lbValue lb_handle_objc_find_selector(lbProcedure *p, Ast *expr) { @@ -2139,23 +2152,36 @@ gb_internal lbValue lb_handle_objc_register_selector(lbProcedure *p, Ast *expr) } gb_internal lbAddr lb_handle_objc_find_or_register_class(lbProcedure *p, String const &name) { - lbAddr *found = string_map_get(&p->module->objc_classes, name); + lbModule *default_module = &p->module->gen->default_module; + Entity *entity = {}; + + Entity **found = string_map_get(&p->module->objc_classes, name); if (found) { - return *found; - } else { - lbModule *default_module = &p->module->gen->default_module; - Entity *e = nullptr; - lbAddr default_addr = lb_add_global_generated(default_module, t_objc_SEL, {}, &e); - - lbValue ptr = lb_find_value_from_entity(p->module, e); - lbAddr local_addr = lb_addr(ptr); - - string_map_set(&default_module->objc_classes, name, default_addr); - if (default_module != p->module) { - string_map_set(&p->module->objc_classes, name, local_addr); - } - return local_addr; + entity = *found; } + + if (!entity) { + if (default_module != p->module) { + found = string_map_get(&default_module->objc_classes, name); + if (found) { + entity = *found; + } + } + + if (!entity) { + lbAddr default_addr = lb_add_global_generated(default_module, t_objc_Class, {}, &entity); + string_map_set(&default_module->objc_classes, name, entity); + } + } + + lbValue ptr = lb_find_value_from_entity(p->module, entity); + lbAddr local_addr = lb_addr(ptr); + + if (default_module != p->module) { + string_map_set(&p->module->objc_classes, name, entity); + } + + return local_addr; } gb_internal lbValue lb_handle_objc_find_class(lbProcedure *p, Ast *expr) { @@ -2196,23 +2222,7 @@ gb_internal lbValue lb_handle_objc_id(lbProcedure *p, Ast *expr) { GB_ASSERT(e->kind == Entity_TypeName); String name = e->TypeName.objc_class_name; - lbAddr *found = string_map_get(&p->module->objc_classes, name); - if (found) { - return lb_addr_load(p, *found); - } else { - lbModule *default_module = &p->module->gen->default_module; - Entity *e = nullptr; - lbAddr default_addr = lb_add_global_generated(default_module, t_objc_Class, {}, &e); - - lbValue ptr = lb_find_value_from_entity(p->module, e); - lbAddr local_addr = lb_addr(ptr); - - string_map_set(&default_module->objc_classes, name, default_addr); - if (default_module != p->module) { - string_map_set(&p->module->objc_classes, name, local_addr); - } - return lb_addr_load(p, local_addr); - } + return lb_addr_load(p, lb_handle_objc_find_or_register_class(p, name)); } return lb_build_expr(p, expr); From 9dc17f4c47471829b8360c9114ac382582e2b9b6 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Fri, 24 Jan 2025 19:33:57 +0100 Subject: [PATCH 53/64] optimize fix --- src/llvm_backend.cpp | 12 ++------ src/llvm_backend.hpp | 9 ++++-- src/llvm_backend_utility.cpp | 56 +++++++++++++++++------------------- 3 files changed, 35 insertions(+), 42 deletions(-) diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index 277d0433e..29fa67f3f 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -1147,22 +1147,14 @@ gb_internal void lb_finalize_objc_names(lbProcedure *p) { String name = entry.key; args[0] = lb_const_value(m, t_cstring, exact_value_string(name)); lbValue ptr = lb_emit_runtime_call(p, "objc_lookUpClass", args); - - lbValue ptr_ = lb_find_value_from_entity(m, entry.value); - lbAddr local_addr = lb_addr(ptr_); - - lb_addr_store(p, local_addr, ptr); + lb_addr_store(p, entry.value.local_module_addr, ptr); } for (auto const &entry : m->objc_selectors) { String name = entry.key; args[0] = lb_const_value(m, t_cstring, exact_value_string(name)); lbValue ptr = lb_emit_runtime_call(p, "sel_registerName", args); - - lbValue ptr_ = lb_find_value_from_entity(m, entry.value); - lbAddr local_addr = lb_addr(ptr_); - - lb_addr_store(p, local_addr, ptr); + lb_addr_store(p, entry.value.local_module_addr, ptr); } lb_end_procedure_body(p); diff --git a/src/llvm_backend.hpp b/src/llvm_backend.hpp index dd56d56a3..a0775ac3b 100644 --- a/src/llvm_backend.hpp +++ b/src/llvm_backend.hpp @@ -143,6 +143,11 @@ struct lbPadType { LLVMTypeRef type; }; +struct lbObjcRef { + Entity * entity; + lbAddr local_module_addr; +}; + struct lbModule { LLVMModuleRef mod; LLVMContextRef ctx; @@ -196,8 +201,8 @@ struct lbModule { RecursiveMutex debug_values_mutex; PtrMap debug_values; - StringMap objc_classes; - StringMap objc_selectors; + StringMap objc_classes; + StringMap objc_selectors; PtrMap map_cell_info_map; // address of runtime.Map_Info PtrMap map_info_map; // address of runtime.Map_Cell_Info diff --git a/src/llvm_backend_utility.cpp b/src/llvm_backend_utility.cpp index 61dafa1c0..8910bd67a 100644 --- a/src/llvm_backend_utility.cpp +++ b/src/llvm_backend_utility.cpp @@ -2093,33 +2093,31 @@ gb_internal void lb_set_wasm_export_attributes(LLVMValueRef value, String export gb_internal lbAddr lb_handle_objc_find_or_register_selector(lbProcedure *p, String const &name) { + lbObjcRef *found = string_map_get(&p->module->objc_selectors, name); + if (found) { + return found->local_module_addr; + } + lbModule *default_module = &p->module->gen->default_module; Entity *entity = {}; - Entity **found = string_map_get(&p->module->objc_selectors, name); - if (found) { - entity = *found; + if (default_module != p->module) { + found = string_map_get(&default_module->objc_selectors, name); + if (found) { + entity = found->entity; + } } if (!entity) { - if (default_module != p->module) { - found = string_map_get(&default_module->objc_selectors, name); - if (found) { - entity = *found; - } - } - - if (!entity) { - lbAddr default_addr = lb_add_global_generated(default_module, t_objc_SEL, {}, &entity); - string_map_set(&default_module->objc_selectors, name, entity); - } + lbAddr default_addr = lb_add_global_generated(default_module, t_objc_SEL, {}, &entity); + string_map_set(&default_module->objc_selectors, name, lbObjcRef{entity, default_addr}); } lbValue ptr = lb_find_value_from_entity(p->module, entity); lbAddr local_addr = lb_addr(ptr); if (default_module != p->module) { - string_map_set(&p->module->objc_selectors, name, entity); + string_map_set(&p->module->objc_selectors, name, lbObjcRef{entity, local_addr}); } return local_addr; @@ -2152,33 +2150,31 @@ gb_internal lbValue lb_handle_objc_register_selector(lbProcedure *p, Ast *expr) } gb_internal lbAddr lb_handle_objc_find_or_register_class(lbProcedure *p, String const &name) { + lbObjcRef *found = string_map_get(&p->module->objc_classes, name); + if (found) { + return found->local_module_addr; + } + lbModule *default_module = &p->module->gen->default_module; Entity *entity = {}; - Entity **found = string_map_get(&p->module->objc_classes, name); - if (found) { - entity = *found; + if (default_module != p->module) { + found = string_map_get(&default_module->objc_classes, name); + if (found) { + entity = found->entity; + } } if (!entity) { - if (default_module != p->module) { - found = string_map_get(&default_module->objc_classes, name); - if (found) { - entity = *found; - } - } - - if (!entity) { - lbAddr default_addr = lb_add_global_generated(default_module, t_objc_Class, {}, &entity); - string_map_set(&default_module->objc_classes, name, entity); - } + lbAddr default_addr = lb_add_global_generated(default_module, t_objc_Class, {}, &entity); + string_map_set(&default_module->objc_classes, name, lbObjcRef{entity, default_addr}); } lbValue ptr = lb_find_value_from_entity(p->module, entity); lbAddr local_addr = lb_addr(ptr); if (default_module != p->module) { - string_map_set(&p->module->objc_classes, name, entity); + string_map_set(&p->module->objc_classes, name, lbObjcRef{entity, local_addr}); } return local_addr; From f957542cd3a35fa544052632b1e9e7e3e00c4253 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Sat, 25 Jan 2025 00:50:57 +0100 Subject: [PATCH 54/64] fix duplicate linker warning on macos Fixes #4747 --- build_odin.sh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/build_odin.sh b/build_odin.sh index f4452e291..c7d5c9288 100755 --- a/build_odin.sh +++ b/build_odin.sh @@ -9,7 +9,7 @@ set -eu CPPFLAGS="$CPPFLAGS -DODIN_VERSION_RAW=\"dev-$(date +"%Y-%m")\"" CXXFLAGS="$CXXFLAGS -std=c++14" DISABLED_WARNINGS="-Wno-switch -Wno-macro-redefined -Wno-unused-value" -LDFLAGS="$LDFLAGS -pthread -lm -lstdc++" +LDFLAGS="$LDFLAGS -pthread -lm" OS_ARCH="$(uname -m)" OS_NAME="$(uname -s)" @@ -95,15 +95,15 @@ Darwin) ;; FreeBSD) CXXFLAGS="$CXXFLAGS $($LLVM_CONFIG --cxxflags --ldflags)" - LDFLAGS="$LDFLAGS $($LLVM_CONFIG --libs core native --system-libs)" + LDFLAGS="$LDFLAGS -lstdc++ $($LLVM_CONFIG --libs core native --system-libs)" ;; NetBSD) CXXFLAGS="$CXXFLAGS $($LLVM_CONFIG --cxxflags --ldflags)" - LDFLAGS="$LDFLAGS $($LLVM_CONFIG --libs core native --system-libs)" + LDFLAGS="$LDFLAGS -lstdc++ $($LLVM_CONFIG --libs core native --system-libs)" ;; Linux) CXXFLAGS="$CXXFLAGS $($LLVM_CONFIG --cxxflags --ldflags)" - LDFLAGS="$LDFLAGS -ldl $($LLVM_CONFIG --libs core native --system-libs --libfiles)" + LDFLAGS="$LDFLAGS -lstdc++ -ldl $($LLVM_CONFIG --libs core native --system-libs --libfiles)" # Copy libLLVM*.so into current directory for linking # NOTE: This is needed by the Linux release pipeline! # cp $(readlink -f $($LLVM_CONFIG --libfiles)) ./ @@ -111,12 +111,12 @@ Linux) ;; OpenBSD) CXXFLAGS="$CXXFLAGS -I/usr/local/include $($LLVM_CONFIG --cxxflags --ldflags)" - LDFLAGS="$LDFLAGS -L/usr/local/lib -liconv" + LDFLAGS="$LDFLAGS -lstdc++ -L/usr/local/lib -liconv" LDFLAGS="$LDFLAGS $($LLVM_CONFIG --libs core native --system-libs)" ;; Haiku) CXXFLAGS="$CXXFLAGS -D_GNU_SOURCE $($LLVM_CONFIG --cxxflags --ldflags) -I/system/develop/headers/private/shared -I/system/develop/headers/private/kernel" - LDFLAGS="$LDFLAGS -liconv" + LDFLAGS="$LDFLAGS -lstdc++ -liconv" LDFLAGS="$LDFLAGS $($LLVM_CONFIG --libs core native --system-libs)" ;; *) From 61f02d9f49e4bd6b8aac7a689e0c2c1fe612fdd6 Mon Sep 17 00:00:00 2001 From: Samuel Elgozi Date: Sun, 26 Jan 2025 14:03:45 +0200 Subject: [PATCH 55/64] pass flags down from `os.send` in darwin and linux --- core/os/os_darwin.odin | 2 +- core/os/os_linux.odin | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/os/os_darwin.odin b/core/os/os_darwin.odin index d4435ec63..bbffc46d7 100644 --- a/core/os/os_darwin.odin +++ b/core/os/os_darwin.odin @@ -1287,7 +1287,7 @@ sendto :: proc(sd: Socket, data: []u8, flags: int, addr: ^SOCKADDR, addrlen: soc } send :: proc(sd: Socket, data: []byte, flags: int) -> (u32, Error) { - result := _unix_send(c.int(sd), raw_data(data), len(data), 0) + result := _unix_send(c.int(sd), raw_data(data), len(data), i32(flags)) if result < 0 { return 0, get_last_error() } diff --git a/core/os/os_linux.odin b/core/os/os_linux.odin index e023ce7cb..2281e6a82 100644 --- a/core/os/os_linux.odin +++ b/core/os/os_linux.odin @@ -1155,7 +1155,7 @@ sendto :: proc(sd: Socket, data: []u8, flags: int, addr: ^SOCKADDR, addrlen: soc } send :: proc(sd: Socket, data: []byte, flags: int) -> (u32, Error) { - result := unix.sys_sendto(int(sd), raw_data(data), len(data), 0, nil, 0) + result := unix.sys_sendto(int(sd), raw_data(data), len(data), flags, nil, 0) if result < 0 { return 0, _get_errno(int(result)) } From 72bbbc94a989bf5a8ec21398cdb71cbdd606b662 Mon Sep 17 00:00:00 2001 From: p1xelHer0 Date: Sun, 26 Jan 2025 23:36:35 +0100 Subject: [PATCH 56/64] Vendor - miniaudio - engine: fix sound_config_init2 The new way to init `sound_group` and `sound_group_config` is currently using a binding that doesn't match the miniaudio API. The functions in miniaudio have an underscore between the `init` and `2`. This fixes this. --- vendor/miniaudio/engine.odin | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/vendor/miniaudio/engine.odin b/vendor/miniaudio/engine.odin index 467bde583..a06e6c62c 100644 --- a/vendor/miniaudio/engine.odin +++ b/vendor/miniaudio/engine.odin @@ -150,9 +150,9 @@ sound_inlined :: struct { @(default_calling_convention="c", link_prefix="ma_") foreign lib { - @(deprecated="Will be removed in 0.12. Use sound_config_init2() instead.") + @(deprecated="Will be removed in 0.12. Use sound_config_init_2() instead.") sound_config_init :: proc() -> sound_config --- - sound_config_init2 :: proc(pEngine: ^engine) -> sound_config --- /* Will be renamed to sound_config_init() in version 0.12. */ + sound_config_init_2 :: proc(pEngine: ^engine) -> sound_config --- /* Will be renamed to sound_config_init() in version 0.12. */ sound_init_from_file :: proc(pEngine: ^engine, pFilePath: cstring, flags: sound_flags, pGroup: ^sound_group, pDoneFence: ^fence, pSound: ^sound) -> result --- sound_init_from_file_w :: proc(pEngine: ^engine, pFilePath: [^]c.wchar_t, flags: sound_flags, pGroup: ^sound_group, pDoneFence: ^fence, pSound: ^sound) -> result --- @@ -241,9 +241,9 @@ sound_group :: distinct sound @(default_calling_convention="c", link_prefix="ma_") foreign lib { - @(deprecated="Will be removed in 0.12. Use sound_config_init2() instead.") + @(deprecated="Will be removed in 0.12. Use sound_config_init_2() instead.") sound_group_config_init :: proc() -> sound_group_config --- - sound_group_config_init2 :: proc(pEngine: ^engine) -> sound_group_config --- + sound_group_config_init_2 :: proc(pEngine: ^engine) -> sound_group_config --- sound_group_init :: proc(pEngine: ^engine, flags: sound_flags, pParentGroup, pGroup: ^sound_group) -> result --- sound_group_init_ex :: proc(pEngine: ^engine, pConfig: ^sound_group_config, pGroup: ^sound_group) -> result --- From 34aa326d9995b1372429bbc712f0a6b5ad8170a6 Mon Sep 17 00:00:00 2001 From: Laytan Laats Date: Mon, 27 Jan 2025 18:59:41 +0100 Subject: [PATCH 57/64] put FILE in core:c and use that in bindings to fix wasm --- core/c/c.odin | 2 ++ core/c/libc/stdio.odin | 3 ++- vendor/commonmark/cmark.odin | 3 +-- vendor/stb/image/stb_image.odin | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/core/c/c.odin b/core/c/c.odin index 3dfc19ffc..73727d8d5 100644 --- a/core/c/c.odin +++ b/core/c/c.odin @@ -114,3 +114,5 @@ CHAR_BIT :: 8 va_list :: struct #align(16) { _: [4096]u8, } + +FILE :: struct {} diff --git a/core/c/libc/stdio.odin b/core/c/libc/stdio.odin index a94a53696..56e4e8f66 100644 --- a/core/c/libc/stdio.odin +++ b/core/c/libc/stdio.odin @@ -1,5 +1,6 @@ package libc +import "core:c" import "core:io" when ODIN_OS == .Windows { @@ -15,7 +16,7 @@ when ODIN_OS == .Windows { // 7.21 Input/output -FILE :: struct {} +FILE :: c.FILE Whence :: enum int { SET = SEEK_SET, diff --git a/vendor/commonmark/cmark.odin b/vendor/commonmark/cmark.odin index 2fdf1387c..6b07f157f 100644 --- a/vendor/commonmark/cmark.odin +++ b/vendor/commonmark/cmark.odin @@ -7,7 +7,6 @@ package vendor_commonmark import "core:c" -import "core:c/libc" import "base:runtime" COMMONMARK_SHARED :: #config(COMMONMARK_SHARED, false) @@ -450,7 +449,7 @@ foreign lib { // Called `parse_from_libc_file` so as not to confuse with Odin's file handling. @(link_name = "parse_from_file") - parse_from_libc_file :: proc(file: ^libc.FILE, options: Options) -> (root: ^Node) --- + parse_from_libc_file :: proc(file: ^c.FILE, options: Options) -> (root: ^Node) --- } parser_feed_from_string :: proc "c" (parser: ^Parser, s: string) { diff --git a/vendor/stb/image/stb_image.odin b/vendor/stb/image/stb_image.odin index e74c825b8..1ba63dc47 100644 --- a/vendor/stb/image/stb_image.odin +++ b/vendor/stb/image/stb_image.odin @@ -1,6 +1,6 @@ package stb_image -import c "core:c/libc" +import "core:c" @(private) LIB :: ( From d85c2c1ca7d73c5f0513c731a13c80116124b9e4 Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Mon, 27 Jan 2025 22:16:24 +0100 Subject: [PATCH 58/64] Add mDNS/Bonjour/Avahi (.local) support for Windows --- core/net/common.odin | 3 +++ core/net/dns_windows.odin | 7 ++++++- core/net/socket_linux.odin | 4 +++- core/sys/windows/dnsapi.odin | 2 +- core/sys/windows/types.odin | 25 +++++++++++++++++++++++++ 5 files changed, 38 insertions(+), 3 deletions(-) diff --git a/core/net/common.odin b/core/net/common.odin index 263fc770f..64155313d 100644 --- a/core/net/common.odin +++ b/core/net/common.odin @@ -95,6 +95,7 @@ Resolve_Error :: enum u32 { } DNS_Error :: enum u32 { + None = 0, Invalid_Hostname_Error = 1, Invalid_Hosts_Config_Error, Invalid_Resolv_Config_Error, @@ -147,6 +148,8 @@ IP6_Loopback :: IP6_Address{0, 0, 0, 0, 0, 0, 0, 1} IP4_Any := IP4_Address{} IP6_Any := IP6_Address{} +IP4_mDNS_Broadcast := Endpoint{address=IP4_Address{224, 0, 0, 251}, port=5353} + Endpoint :: struct { address: Address, port: int, diff --git a/core/net/dns_windows.odin b/core/net/dns_windows.odin index 2f3831767..7736851b8 100644 --- a/core/net/dns_windows.odin +++ b/core/net/dns_windows.odin @@ -29,9 +29,14 @@ import win "core:sys/windows" _get_dns_records_os :: proc(hostname: string, type: DNS_Record_Type, allocator := context.allocator) -> (records: []DNS_Record, err: DNS_Error) { context.allocator = allocator + options := win.DNS_QUERY_OPTIONS{} + if strings.has_suffix(hostname, ".local") { + options = {.MULTICAST_ONLY, .MULTICAST_WAIT} // 0x00020500 + } + host_cstr := strings.clone_to_cstring(hostname, context.temp_allocator) rec: ^win.DNS_RECORD - res := win.DnsQuery_UTF8(host_cstr, u16(type), 0, nil, &rec, nil) + res := win.DnsQuery_UTF8(host_cstr, u16(type), options, nil, &rec, nil) switch u32(res) { case 0: diff --git a/core/net/socket_linux.odin b/core/net/socket_linux.odin index b7816b0b6..cafec747d 100644 --- a/core/net/socket_linux.odin +++ b/core/net/socket_linux.odin @@ -35,6 +35,7 @@ Socket_Option :: enum c.int { Send_Buffer_Size = c.int(linux.Socket_Option.SNDBUF), Receive_Timeout = c.int(linux.Socket_Option.RCVTIMEO), Send_Timeout = c.int(linux.Socket_Option.SNDTIMEO), + Broadcast = c.int(linux.Socket_Option.BROADCAST), } // Wrappers and unwrappers for system-native types @@ -337,7 +338,8 @@ _set_option :: proc(sock: Any_Socket, option: Socket_Option, value: any, loc := .Reuse_Address, .Keep_Alive, .Out_Of_Bounds_Data_Inline, - .TCP_Nodelay: + .TCP_Nodelay, + .Broadcast: // TODO: verify whether these are options or not on Linux // .Broadcast, <-- yes // .Conditional_Accept, diff --git a/core/sys/windows/dnsapi.odin b/core/sys/windows/dnsapi.odin index 4fd9f7a19..728813696 100644 --- a/core/sys/windows/dnsapi.odin +++ b/core/sys/windows/dnsapi.odin @@ -5,6 +5,6 @@ foreign import "system:Dnsapi.lib" @(default_calling_convention="system") foreign Dnsapi { - DnsQuery_UTF8 :: proc(name: cstring, type: u16, options: DWORD, extra: PVOID, results: ^^DNS_RECORD, reserved: PVOID) -> DNS_STATUS --- + DnsQuery_UTF8 :: proc(name: cstring, type: u16, options: DNS_QUERY_OPTIONS, extra: PVOID, results: ^^DNS_RECORD, reserved: PVOID) -> DNS_STATUS --- DnsRecordListFree :: proc(list: ^DNS_RECORD, options: DWORD) --- } diff --git a/core/sys/windows/types.odin b/core/sys/windows/types.odin index ab79c682a..8069659c9 100644 --- a/core/sys/windows/types.odin +++ b/core/sys/windows/types.odin @@ -4576,6 +4576,31 @@ DNS_SRV_DATAA :: struct { _: WORD, // padding } +// See https://learn.microsoft.com/en-us/windows/win32/dns/dns-constants +DNS_QUERY_OPTION :: enum DWORD { + ACCEPT_TRUNCATED_RESPONSE = 0, + DNS_QUERY_USE_TCP_ONLY = 1, + NO_RECURSION = 2, + BYPASS_CACHE = 3, + NO_WIRE_QUERY = 4, + NO_LOCAL_NAME = 5, + NO_HOSTS_FILE = 6, + NO_NETBT = 7, + WIRE_ONLY = 8, + RETURN_MESSAGE = 9, + MULTICAST_ONLY = 10, + NO_MULTICAST = 11, + TREAT_AS_FQDN = 12, + ADDRCONFIG = 13, + DUAL_ADDR = 14, + MULTICAST_WAIT = 17, + MULTICAST_VERIFY = 18, + DONT_RESET_TTL_VALUES = 20, + DISABLE_IDN_ENCODING = 21, + APPEND_MULTILABEL = 23, +} +DNS_QUERY_OPTIONS :: bit_set[DNS_QUERY_OPTION; DWORD] + SOCKADDR :: struct { sa_family: ADDRESS_FAMILY, sa_data: [14]CHAR, From 8998d74a926f52ef02a8f77936922dae3da6085f Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Mon, 27 Jan 2025 22:55:48 +0100 Subject: [PATCH 59/64] Add mDNS for *nix. --- core/net/common.odin | 1 + core/net/dns.odin | 89 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 88 insertions(+), 2 deletions(-) diff --git a/core/net/common.odin b/core/net/common.odin index 64155313d..12add8225 100644 --- a/core/net/common.odin +++ b/core/net/common.odin @@ -149,6 +149,7 @@ IP4_Any := IP4_Address{} IP6_Any := IP6_Address{} IP4_mDNS_Broadcast := Endpoint{address=IP4_Address{224, 0, 0, 251}, port=5353} +IP6_mDNS_Broadcast := Endpoint{address=IP6_Address{65282, 0, 0, 0, 0, 0, 0, 251}, port = 5353} Endpoint :: struct { address: Address, diff --git a/core/net/dns.odin b/core/net/dns.odin index ffb97fc5b..b3649c686 100644 --- a/core/net/dns.odin +++ b/core/net/dns.odin @@ -132,7 +132,14 @@ resolve_ip4 :: proc(hostname_and_maybe_port: string) -> (ep4: Endpoint, err: Net return } case Host: - recs, _ := get_dns_records_from_os(t.hostname, .IP4, context.temp_allocator) + recs: []DNS_Record + + if ODIN_OS != .Windows && strings.has_suffix(t.hostname, ".local") { + recs, _ = get_dns_records_from_mdns(t.hostname, .IP4, context.temp_allocator) + } else { + recs, _ = get_dns_records_from_os(t.hostname, .IP4, context.temp_allocator) + } + if len(recs) == 0 { err = .Unable_To_Resolve return @@ -159,7 +166,14 @@ resolve_ip6 :: proc(hostname_and_maybe_port: string) -> (ep6: Endpoint, err: Net return t, nil } case Host: - recs, _ := get_dns_records_from_os(t.hostname, .IP6, context.temp_allocator) + recs: []DNS_Record + + if ODIN_OS != .Windows && strings.has_suffix(t.hostname, ".local") { + recs, _ = get_dns_records_from_mdns(t.hostname, .IP6, context.temp_allocator) + } else { + recs, _ = get_dns_records_from_os(t.hostname, .IP6, context.temp_allocator) + } + if len(recs) == 0 { err = .Unable_To_Resolve return @@ -283,6 +297,77 @@ get_dns_records_from_nameservers :: proc(hostname: string, type: DNS_Record_Type return } +get_dns_records_from_mdns :: proc(hostname: string, type: DNS_Record_Type, allocator := context.allocator) -> (records: []DNS_Record, err: DNS_Error) { + assert(type == .IP4 || type == .IP6) + + context.allocator = allocator + + if !validate_hostname(hostname) { + return nil, .Invalid_Hostname_Error + } + + hdr := DNS_Header{ + id = 0, + is_response = false, + opcode = 0, + is_authoritative = false, + is_truncated = false, + is_recursion_desired = true, + is_recursion_available = false, + response_code = DNS_Response_Code.No_Error, + } + + id, bits := pack_dns_header(hdr) + dns_hdr := [6]u16be{} + dns_hdr[0] = id + dns_hdr[1] = bits + dns_hdr[2] = 1 + + dns_query := [2]u16be{ u16be(type), 1 } + + output := [(size_of(u16be) * 6) + NAME_MAX + (size_of(u16be) * 2)]u8{} + b := strings.builder_from_slice(output[:]) + + strings.write_bytes(&b, mem.slice_data_cast([]u8, dns_hdr[:])) + ok := encode_hostname(&b, hostname) + if !ok { + return nil, .Invalid_Hostname_Error + } + strings.write_bytes(&b, mem.slice_data_cast([]u8, dns_query[:])) + + dns_packet := output[:strings.builder_len(b)] + + dns_response_buf := [4096]u8{} + dns_response: []u8 + + name_server := IP4_mDNS_Broadcast if type == .IP4 else IP6_mDNS_Broadcast + + conn, sock_err := make_unbound_udp_socket(family_from_endpoint(name_server)) + if sock_err != nil { + return nil, .Connection_Error + } + defer close(conn) + + send(conn, dns_packet[:], name_server) + + if set_option(conn, .Receive_Timeout, time.Second * 1) != nil { + return nil, .Connection_Error + } + + recv_sz, _, _ := recv_udp(conn, dns_response_buf[:]) + if recv_sz == 0 { + return nil, .Server_Error + } + + dns_response = dns_response_buf[:recv_sz] + + rsp, _ok := parse_response(dns_response, type) + if !_ok { + return nil, .Server_Error + } + return rsp[:], nil +} + // `records` slice is also destroyed. destroy_dns_records :: proc(records: []DNS_Record, allocator := context.allocator) { context.allocator = allocator From cc29bdaefc8cc34f3a18e7304224252f446a421d Mon Sep 17 00:00:00 2001 From: Jeroen van Rijn Date: Mon, 27 Jan 2025 23:04:15 +0100 Subject: [PATCH 60/64] Simplify *nix mDNS --- core/net/dns.odin | 81 ++--------------------------------------------- 1 file changed, 2 insertions(+), 79 deletions(-) diff --git a/core/net/dns.odin b/core/net/dns.odin index b3649c686..6d5dfea23 100644 --- a/core/net/dns.odin +++ b/core/net/dns.odin @@ -135,7 +135,7 @@ resolve_ip4 :: proc(hostname_and_maybe_port: string) -> (ep4: Endpoint, err: Net recs: []DNS_Record if ODIN_OS != .Windows && strings.has_suffix(t.hostname, ".local") { - recs, _ = get_dns_records_from_mdns(t.hostname, .IP4, context.temp_allocator) + recs, _ = get_dns_records_from_nameservers(t.hostname, .IP4, {IP4_mDNS_Broadcast}, nil, context.temp_allocator) } else { recs, _ = get_dns_records_from_os(t.hostname, .IP4, context.temp_allocator) } @@ -169,7 +169,7 @@ resolve_ip6 :: proc(hostname_and_maybe_port: string) -> (ep6: Endpoint, err: Net recs: []DNS_Record if ODIN_OS != .Windows && strings.has_suffix(t.hostname, ".local") { - recs, _ = get_dns_records_from_mdns(t.hostname, .IP6, context.temp_allocator) + recs, _ = get_dns_records_from_nameservers(t.hostname, .IP6, {IP6_mDNS_Broadcast}, nil, context.temp_allocator) } else { recs, _ = get_dns_records_from_os(t.hostname, .IP6, context.temp_allocator) } @@ -269,12 +269,6 @@ get_dns_records_from_nameservers :: proc(hostname: string, type: DNS_Record_Type return nil, .Connection_Error } - // recv_sz, _, recv_err := recv_udp(conn, dns_response_buf[:]) - // if recv_err == UDP_Recv_Error.Timeout { - // continue - // } else if recv_err != nil { - // continue - // } recv_sz, _ := recv_udp(conn, dns_response_buf[:]) or_continue if recv_sz == 0 { continue @@ -297,77 +291,6 @@ get_dns_records_from_nameservers :: proc(hostname: string, type: DNS_Record_Type return } -get_dns_records_from_mdns :: proc(hostname: string, type: DNS_Record_Type, allocator := context.allocator) -> (records: []DNS_Record, err: DNS_Error) { - assert(type == .IP4 || type == .IP6) - - context.allocator = allocator - - if !validate_hostname(hostname) { - return nil, .Invalid_Hostname_Error - } - - hdr := DNS_Header{ - id = 0, - is_response = false, - opcode = 0, - is_authoritative = false, - is_truncated = false, - is_recursion_desired = true, - is_recursion_available = false, - response_code = DNS_Response_Code.No_Error, - } - - id, bits := pack_dns_header(hdr) - dns_hdr := [6]u16be{} - dns_hdr[0] = id - dns_hdr[1] = bits - dns_hdr[2] = 1 - - dns_query := [2]u16be{ u16be(type), 1 } - - output := [(size_of(u16be) * 6) + NAME_MAX + (size_of(u16be) * 2)]u8{} - b := strings.builder_from_slice(output[:]) - - strings.write_bytes(&b, mem.slice_data_cast([]u8, dns_hdr[:])) - ok := encode_hostname(&b, hostname) - if !ok { - return nil, .Invalid_Hostname_Error - } - strings.write_bytes(&b, mem.slice_data_cast([]u8, dns_query[:])) - - dns_packet := output[:strings.builder_len(b)] - - dns_response_buf := [4096]u8{} - dns_response: []u8 - - name_server := IP4_mDNS_Broadcast if type == .IP4 else IP6_mDNS_Broadcast - - conn, sock_err := make_unbound_udp_socket(family_from_endpoint(name_server)) - if sock_err != nil { - return nil, .Connection_Error - } - defer close(conn) - - send(conn, dns_packet[:], name_server) - - if set_option(conn, .Receive_Timeout, time.Second * 1) != nil { - return nil, .Connection_Error - } - - recv_sz, _, _ := recv_udp(conn, dns_response_buf[:]) - if recv_sz == 0 { - return nil, .Server_Error - } - - dns_response = dns_response_buf[:recv_sz] - - rsp, _ok := parse_response(dns_response, type) - if !_ok { - return nil, .Server_Error - } - return rsp[:], nil -} - // `records` slice is also destroyed. destroy_dns_records :: proc(records: []DNS_Record, allocator := context.allocator) { context.allocator = allocator From 868ab277209908a3857c874014bced9e0fae6949 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 28 Jan 2025 10:31:46 +0000 Subject: [PATCH 61/64] Add `@(ignore_duplicates)` for `foreign import` declarations --- src/checker.cpp | 9 +++++++++ src/checker.hpp | 1 + src/entity.cpp | 1 + vendor/sdl2/sdl2.odin | 2 ++ vendor/sdl2/sdl_audio.odin | 2 ++ vendor/sdl2/sdl_blendmode.odin | 2 ++ vendor/sdl2/sdl_cpuinfo.odin | 2 ++ vendor/sdl2/sdl_events.odin | 2 ++ vendor/sdl2/sdl_gamecontroller.odin | 2 ++ vendor/sdl2/sdl_gesture_haptic.odin | 2 ++ vendor/sdl2/sdl_hints.odin | 2 ++ vendor/sdl2/sdl_joystick.odin | 2 ++ vendor/sdl2/sdl_keyboard.odin | 2 ++ vendor/sdl2/sdl_log.odin | 2 ++ vendor/sdl2/sdl_messagebox.odin | 2 ++ vendor/sdl2/sdl_metal.odin | 2 ++ vendor/sdl2/sdl_mouse.odin | 2 ++ vendor/sdl2/sdl_mutex.odin | 2 ++ vendor/sdl2/sdl_pixels.odin | 2 ++ vendor/sdl2/sdl_rect.odin | 2 ++ vendor/sdl2/sdl_render.odin | 2 ++ vendor/sdl2/sdl_rwops.odin | 2 ++ vendor/sdl2/sdl_stdinc.odin | 2 ++ vendor/sdl2/sdl_surface.odin | 2 ++ vendor/sdl2/sdl_system.odin | 2 ++ vendor/sdl2/sdl_syswm.odin | 2 ++ vendor/sdl2/sdl_thread.odin | 2 ++ vendor/sdl2/sdl_timer.odin | 2 ++ vendor/sdl2/sdl_touch.odin | 2 ++ vendor/sdl2/sdl_video.odin | 2 ++ vendor/sdl2/sdl_vulkan.odin | 2 ++ 31 files changed, 67 insertions(+) diff --git a/src/checker.cpp b/src/checker.cpp index 85077a5c5..baa1e0d2b 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -5040,6 +5040,12 @@ gb_internal DECL_ATTRIBUTE_PROC(foreign_import_decl_attribute) { ac->extra_linker_flags = ev.value_string; } return true; + } else if (name == "ignore_duplicates") { + if (value != nullptr) { + error(elem, "Expected no parameter for '%.*s'", LIT(name)); + } + ac->ignore_duplicates = true; + return true; } return false; } @@ -5190,6 +5196,9 @@ gb_internal void check_add_foreign_import_decl(CheckerContext *ctx, Ast *decl) { if (ac.foreign_import_priority_index != 0) { e->LibraryName.priority_index = ac.foreign_import_priority_index; } + if (ac.ignore_duplicates) { + e->LibraryName.ignore_duplicates = true; + } String extra_linker_flags = string_trim_whitespace(ac.extra_linker_flags); if (extra_linker_flags.len != 0) { e->LibraryName.extra_linker_flags = extra_linker_flags; diff --git a/src/checker.hpp b/src/checker.hpp index 3951fcefe..4634047c0 100644 --- a/src/checker.hpp +++ b/src/checker.hpp @@ -140,6 +140,7 @@ struct AttributeContext { bool instrumentation_enter : 1; bool instrumentation_exit : 1; bool rodata : 1; + bool ignore_duplicates : 1; u32 optimization_mode; // ProcedureOptimizationMode i64 foreign_import_priority_index; String extra_linker_flags; diff --git a/src/entity.cpp b/src/entity.cpp index 802b381f9..d137a8674 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -274,6 +274,7 @@ struct Entity { Slice paths; String name; i64 priority_index; + bool ignore_duplicates; String extra_linker_flags; } LibraryName; i32 Nil; diff --git a/vendor/sdl2/sdl2.odin b/vendor/sdl2/sdl2.odin index b23389a64..5bc52b70e 100644 --- a/vendor/sdl2/sdl2.odin +++ b/vendor/sdl2/sdl2.odin @@ -26,8 +26,10 @@ import "core:c" import "base:intrinsics" when ODIN_OS == .Windows { + @(ignore_duplicates) foreign import lib "SDL2.lib" } else { + @(ignore_duplicates) foreign import lib "system:SDL2" } diff --git a/vendor/sdl2/sdl_audio.odin b/vendor/sdl2/sdl_audio.odin index 28a59d947..6ff9e93f4 100644 --- a/vendor/sdl2/sdl_audio.odin +++ b/vendor/sdl2/sdl_audio.odin @@ -3,8 +3,10 @@ package sdl2 import "core:c" when ODIN_OS == .Windows { + @(ignore_duplicates) foreign import lib "SDL2.lib" } else { + @(ignore_duplicates) foreign import lib "system:SDL2" } diff --git a/vendor/sdl2/sdl_blendmode.odin b/vendor/sdl2/sdl_blendmode.odin index 4fde5111b..3105ad72b 100644 --- a/vendor/sdl2/sdl_blendmode.odin +++ b/vendor/sdl2/sdl_blendmode.odin @@ -3,8 +3,10 @@ package sdl2 import "core:c" when ODIN_OS == .Windows { + @(ignore_duplicates) foreign import lib "SDL2.lib" } else { + @(ignore_duplicates) foreign import lib "system:SDL2" } diff --git a/vendor/sdl2/sdl_cpuinfo.odin b/vendor/sdl2/sdl_cpuinfo.odin index c5175e4d5..a98b6f8d3 100644 --- a/vendor/sdl2/sdl_cpuinfo.odin +++ b/vendor/sdl2/sdl_cpuinfo.odin @@ -3,8 +3,10 @@ package sdl2 import "core:c" when ODIN_OS == .Windows { + @(ignore_duplicates) foreign import lib "SDL2.lib" } else { + @(ignore_duplicates) foreign import lib "system:SDL2" } diff --git a/vendor/sdl2/sdl_events.odin b/vendor/sdl2/sdl_events.odin index b4c92683c..061eb964d 100644 --- a/vendor/sdl2/sdl_events.odin +++ b/vendor/sdl2/sdl_events.odin @@ -3,8 +3,10 @@ package sdl2 import "core:c" when ODIN_OS == .Windows { + @(ignore_duplicates) foreign import lib "SDL2.lib" } else { + @(ignore_duplicates) foreign import lib "system:SDL2" } diff --git a/vendor/sdl2/sdl_gamecontroller.odin b/vendor/sdl2/sdl_gamecontroller.odin index beb7d5ce7..be45d6520 100644 --- a/vendor/sdl2/sdl_gamecontroller.odin +++ b/vendor/sdl2/sdl_gamecontroller.odin @@ -3,8 +3,10 @@ package sdl2 import "core:c" when ODIN_OS == .Windows { + @(ignore_duplicates) foreign import lib "SDL2.lib" } else { + @(ignore_duplicates) foreign import lib "system:SDL2" } diff --git a/vendor/sdl2/sdl_gesture_haptic.odin b/vendor/sdl2/sdl_gesture_haptic.odin index a21e0df06..01d7a6da3 100644 --- a/vendor/sdl2/sdl_gesture_haptic.odin +++ b/vendor/sdl2/sdl_gesture_haptic.odin @@ -3,8 +3,10 @@ package sdl2 import "core:c" when ODIN_OS == .Windows { + @(ignore_duplicates) foreign import lib "SDL2.lib" } else { + @(ignore_duplicates) foreign import lib "system:SDL2" } diff --git a/vendor/sdl2/sdl_hints.odin b/vendor/sdl2/sdl_hints.odin index 913d4ea12..080dc6036 100644 --- a/vendor/sdl2/sdl_hints.odin +++ b/vendor/sdl2/sdl_hints.odin @@ -3,8 +3,10 @@ package sdl2 import "core:c" when ODIN_OS == .Windows { + @(ignore_duplicates) foreign import lib "SDL2.lib" } else { + @(ignore_duplicates) foreign import lib "system:SDL2" } diff --git a/vendor/sdl2/sdl_joystick.odin b/vendor/sdl2/sdl_joystick.odin index 35ca5cdcc..0725a3554 100644 --- a/vendor/sdl2/sdl_joystick.odin +++ b/vendor/sdl2/sdl_joystick.odin @@ -3,8 +3,10 @@ package sdl2 import "core:c" when ODIN_OS == .Windows { + @(ignore_duplicates) foreign import lib "SDL2.lib" } else { + @(ignore_duplicates) foreign import lib "system:SDL2" } diff --git a/vendor/sdl2/sdl_keyboard.odin b/vendor/sdl2/sdl_keyboard.odin index f880286aa..0d0557de9 100644 --- a/vendor/sdl2/sdl_keyboard.odin +++ b/vendor/sdl2/sdl_keyboard.odin @@ -3,8 +3,10 @@ package sdl2 import "core:c" when ODIN_OS == .Windows { + @(ignore_duplicates) foreign import lib "SDL2.lib" } else { + @(ignore_duplicates) foreign import lib "system:SDL2" } diff --git a/vendor/sdl2/sdl_log.odin b/vendor/sdl2/sdl_log.odin index 09b7eaef0..b7668ee1d 100644 --- a/vendor/sdl2/sdl_log.odin +++ b/vendor/sdl2/sdl_log.odin @@ -3,8 +3,10 @@ package sdl2 import "core:c" when ODIN_OS == .Windows { + @(ignore_duplicates) foreign import lib "SDL2.lib" } else { + @(ignore_duplicates) foreign import lib "system:SDL2" } diff --git a/vendor/sdl2/sdl_messagebox.odin b/vendor/sdl2/sdl_messagebox.odin index 6228704ac..edd8422e0 100644 --- a/vendor/sdl2/sdl_messagebox.odin +++ b/vendor/sdl2/sdl_messagebox.odin @@ -3,8 +3,10 @@ package sdl2 import "core:c" when ODIN_OS == .Windows { + @(ignore_duplicates) foreign import lib "SDL2.lib" } else { + @(ignore_duplicates) foreign import lib "system:SDL2" } diff --git a/vendor/sdl2/sdl_metal.odin b/vendor/sdl2/sdl_metal.odin index 1eccf7f5a..e8e650212 100644 --- a/vendor/sdl2/sdl_metal.odin +++ b/vendor/sdl2/sdl_metal.odin @@ -3,8 +3,10 @@ package sdl2 import "core:c" when ODIN_OS == .Windows { + @(ignore_duplicates) foreign import lib "SDL2.lib" } else { + @(ignore_duplicates) foreign import lib "system:SDL2" } diff --git a/vendor/sdl2/sdl_mouse.odin b/vendor/sdl2/sdl_mouse.odin index 0243b6623..8e782a5e3 100644 --- a/vendor/sdl2/sdl_mouse.odin +++ b/vendor/sdl2/sdl_mouse.odin @@ -3,8 +3,10 @@ package sdl2 import "core:c" when ODIN_OS == .Windows { + @(ignore_duplicates) foreign import lib "SDL2.lib" } else { + @(ignore_duplicates) foreign import lib "system:SDL2" } diff --git a/vendor/sdl2/sdl_mutex.odin b/vendor/sdl2/sdl_mutex.odin index 6ff7e5d2b..6eb096c81 100644 --- a/vendor/sdl2/sdl_mutex.odin +++ b/vendor/sdl2/sdl_mutex.odin @@ -3,8 +3,10 @@ package sdl2 import "core:c" when ODIN_OS == .Windows { + @(ignore_duplicates) foreign import lib "SDL2.lib" } else { + @(ignore_duplicates) foreign import lib "system:SDL2" } diff --git a/vendor/sdl2/sdl_pixels.odin b/vendor/sdl2/sdl_pixels.odin index 195f2920f..6a3d89f4e 100644 --- a/vendor/sdl2/sdl_pixels.odin +++ b/vendor/sdl2/sdl_pixels.odin @@ -3,8 +3,10 @@ package sdl2 import "core:c" when ODIN_OS == .Windows { + @(ignore_duplicates) foreign import lib "SDL2.lib" } else { + @(ignore_duplicates) foreign import lib "system:SDL2" } diff --git a/vendor/sdl2/sdl_rect.odin b/vendor/sdl2/sdl_rect.odin index 852309cd2..96cf7180e 100644 --- a/vendor/sdl2/sdl_rect.odin +++ b/vendor/sdl2/sdl_rect.odin @@ -3,8 +3,10 @@ package sdl2 import "core:c" when ODIN_OS == .Windows { + @(ignore_duplicates) foreign import lib "SDL2.lib" } else { + @(ignore_duplicates) foreign import lib "system:SDL2" } diff --git a/vendor/sdl2/sdl_render.odin b/vendor/sdl2/sdl_render.odin index cceebf3ac..5e913e5a3 100644 --- a/vendor/sdl2/sdl_render.odin +++ b/vendor/sdl2/sdl_render.odin @@ -3,8 +3,10 @@ package sdl2 import "core:c" when ODIN_OS == .Windows { + @(ignore_duplicates) foreign import lib "SDL2.lib" } else { + @(ignore_duplicates) foreign import lib "system:SDL2" } diff --git a/vendor/sdl2/sdl_rwops.odin b/vendor/sdl2/sdl_rwops.odin index 28d09511b..ca7fa0bea 100644 --- a/vendor/sdl2/sdl_rwops.odin +++ b/vendor/sdl2/sdl_rwops.odin @@ -3,8 +3,10 @@ package sdl2 import "core:c" when ODIN_OS == .Windows { + @(ignore_duplicates) foreign import lib "SDL2.lib" } else { + @(ignore_duplicates) foreign import lib "system:SDL2" } diff --git a/vendor/sdl2/sdl_stdinc.odin b/vendor/sdl2/sdl_stdinc.odin index 9136ae026..bf04a3f1f 100644 --- a/vendor/sdl2/sdl_stdinc.odin +++ b/vendor/sdl2/sdl_stdinc.odin @@ -3,8 +3,10 @@ package sdl2 import "core:c" when ODIN_OS == .Windows { + @(ignore_duplicates) foreign import lib "SDL2.lib" } else { + @(ignore_duplicates) foreign import lib "system:SDL2" } diff --git a/vendor/sdl2/sdl_surface.odin b/vendor/sdl2/sdl_surface.odin index f50de35f7..1502efbc7 100644 --- a/vendor/sdl2/sdl_surface.odin +++ b/vendor/sdl2/sdl_surface.odin @@ -3,8 +3,10 @@ package sdl2 import "core:c" when ODIN_OS == .Windows { + @(ignore_duplicates) foreign import lib "SDL2.lib" } else { + @(ignore_duplicates) foreign import lib "system:SDL2" } diff --git a/vendor/sdl2/sdl_system.odin b/vendor/sdl2/sdl_system.odin index d9b6b98df..1c34e557e 100644 --- a/vendor/sdl2/sdl_system.odin +++ b/vendor/sdl2/sdl_system.odin @@ -3,8 +3,10 @@ package sdl2 import "core:c" when ODIN_OS == .Windows { + @(ignore_duplicates) foreign import lib "SDL2.lib" } else { + @(ignore_duplicates) foreign import lib "system:SDL2" } diff --git a/vendor/sdl2/sdl_syswm.odin b/vendor/sdl2/sdl_syswm.odin index 62ca9d628..15501c222 100644 --- a/vendor/sdl2/sdl_syswm.odin +++ b/vendor/sdl2/sdl_syswm.odin @@ -3,8 +3,10 @@ package sdl2 import "core:c" when ODIN_OS == .Windows { + @(ignore_duplicates) foreign import lib "SDL2.lib" } else { + @(ignore_duplicates) foreign import lib "system:SDL2" } diff --git a/vendor/sdl2/sdl_thread.odin b/vendor/sdl2/sdl_thread.odin index 5d1c0bd37..84516e26b 100644 --- a/vendor/sdl2/sdl_thread.odin +++ b/vendor/sdl2/sdl_thread.odin @@ -3,8 +3,10 @@ package sdl2 import "core:c" when ODIN_OS == .Windows { + @(ignore_duplicates) foreign import lib "SDL2.lib" } else { + @(ignore_duplicates) foreign import lib "system:SDL2" } diff --git a/vendor/sdl2/sdl_timer.odin b/vendor/sdl2/sdl_timer.odin index d71ed2da5..50b5eb981 100644 --- a/vendor/sdl2/sdl_timer.odin +++ b/vendor/sdl2/sdl_timer.odin @@ -3,8 +3,10 @@ package sdl2 import "core:c" when ODIN_OS == .Windows { + @(ignore_duplicates) foreign import lib "SDL2.lib" } else { + @(ignore_duplicates) foreign import lib "system:SDL2" } diff --git a/vendor/sdl2/sdl_touch.odin b/vendor/sdl2/sdl_touch.odin index f0ca69333..44633aeb6 100644 --- a/vendor/sdl2/sdl_touch.odin +++ b/vendor/sdl2/sdl_touch.odin @@ -3,8 +3,10 @@ package sdl2 import "core:c" when ODIN_OS == .Windows { + @(ignore_duplicates) foreign import lib "SDL2.lib" } else { + @(ignore_duplicates) foreign import lib "system:SDL2" } diff --git a/vendor/sdl2/sdl_video.odin b/vendor/sdl2/sdl_video.odin index 86b564541..809735414 100644 --- a/vendor/sdl2/sdl_video.odin +++ b/vendor/sdl2/sdl_video.odin @@ -3,8 +3,10 @@ package sdl2 import "core:c" when ODIN_OS == .Windows { + @(ignore_duplicates) foreign import lib "SDL2.lib" } else { + @(ignore_duplicates) foreign import lib "system:SDL2" } diff --git a/vendor/sdl2/sdl_vulkan.odin b/vendor/sdl2/sdl_vulkan.odin index 33bb8e51c..4e0db0ffe 100644 --- a/vendor/sdl2/sdl_vulkan.odin +++ b/vendor/sdl2/sdl_vulkan.odin @@ -4,8 +4,10 @@ import "core:c" import vk "vendor:vulkan" when ODIN_OS == .Windows { + @(ignore_duplicates) foreign import lib "SDL2.lib" } else { + @(ignore_duplicates) foreign import lib "system:SDL2" } From 15ece42e74acd7d62fc65bbc611e6766ec34187a Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 28 Jan 2025 10:34:41 +0000 Subject: [PATCH 62/64] Print frameworks first on Darwin targets --- src/linker.cpp | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/linker.cpp b/src/linker.cpp index 59e6d8dc1..cf2ef638d 100644 --- a/src/linker.cpp +++ b/src/linker.cpp @@ -449,6 +449,26 @@ gb_internal i32 linker_stage(LinkerData *gen) { if (extra_linker_flags.len != 0) { lib_str = gb_string_append_fmt(lib_str, " %.*s", LIT(extra_linker_flags)); } + + if (build_context.metrics.os == TargetOs_darwin) { + // Print frameworks first + for (String lib : e->LibraryName.paths) { + lib = string_trim_whitespace(lib); + if (lib.len == 0) { + continue; + } + if (string_ends_with(lib, str_lit(".framework"))) { + if (string_set_update(&min_libs_set, lib)) { + continue; + } + + String lib_name = lib; + lib_name = remove_extension_from_path(lib_name); + lib_str = gb_string_append_fmt(lib_str, " -framework %.*s ", LIT(lib_name)); + } + } + } + for (String lib : e->LibraryName.paths) { lib = string_trim_whitespace(lib); if (lib.len == 0) { @@ -541,7 +561,9 @@ gb_internal i32 linker_stage(LinkerData *gen) { short_circuit = true; } else if (string_ends_with(lib, str_lit(".dylib"))) { short_circuit = true; - } else if (string_ends_with(lib, str_lit(".so"))) { + } else if (string_ends_with(lib, str_lit(".so"))) { + short_circuit = true; + } else if (e->LibraryName.ignore_duplicates) { short_circuit = true; } From 0e27acd7551fe49f48fbdea55c2645097af3b0b4 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 28 Jan 2025 11:38:06 +0000 Subject: [PATCH 63/64] Update `NSSavelPanel` --- core/sys/darwin/Foundation/NSSavePanel.odin | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/core/sys/darwin/Foundation/NSSavePanel.odin b/core/sys/darwin/Foundation/NSSavePanel.odin index 8e4d7a07b..2f89696ee 100644 --- a/core/sys/darwin/Foundation/NSSavePanel.odin +++ b/core/sys/darwin/Foundation/NSSavePanel.odin @@ -7,3 +7,13 @@ SavePanel :: struct{ using _: Panel } SavePanel_runModal :: proc "c" (self: ^SavePanel) -> ModalResponse { return msgSend(ModalResponse, self, "runModal") } + +@(objc_type=SavePanel, objc_name="savePanel", objc_is_class_method=true) +SavePanel_savePanel :: proc "c" () -> ^SavePanel { + return msgSend(^SavePanel, SavePanel, "savePanel") +} + +@(objc_type=SavePanel, objc_name="URL") +SavePanel_URL :: proc "c" (self: ^SavePanel) -> ^Array { + return msgSend(^Array, self, "URL") +} \ No newline at end of file From 2656ecd4e17d448f1d972270bde87f75bc096d0d Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 29 Jan 2025 15:53:34 +0000 Subject: [PATCH 64/64] Fix #4773 - Change order of evaluation for slicing indices --- src/llvm_backend_expr.cpp | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/llvm_backend_expr.cpp b/src/llvm_backend_expr.cpp index df9dca801..871536927 100644 --- a/src/llvm_backend_expr.cpp +++ b/src/llvm_backend_expr.cpp @@ -4294,6 +4294,17 @@ gb_internal lbAddr lb_build_addr_index_expr(lbProcedure *p, Ast *expr) { gb_internal lbAddr lb_build_addr_slice_expr(lbProcedure *p, Ast *expr) { ast_node(se, SliceExpr, expr); + lbAddr addr = lb_build_addr(p, se->expr); + lbValue base = lb_addr_load(p, addr); + Type *type = base_type(base.type); + + if (is_type_pointer(type)) { + type = base_type(type_deref(type)); + addr = lb_addr(base); + base = lb_addr_load(p, addr); + } + + lbValue low = lb_const_int(p->module, t_int, 0); lbValue high = {}; @@ -4306,16 +4317,6 @@ gb_internal lbAddr lb_build_addr_slice_expr(lbProcedure *p, Ast *expr) { bool no_indices = se->low == nullptr && se->high == nullptr; - lbAddr addr = lb_build_addr(p, se->expr); - lbValue base = lb_addr_load(p, addr); - Type *type = base_type(base.type); - - if (is_type_pointer(type)) { - type = base_type(type_deref(type)); - addr = lb_addr(base); - base = lb_addr_load(p, addr); - } - switch (type->kind) { case Type_Slice: { Type *slice_type = type;