-
-
Notifications
You must be signed in to change notification settings - Fork 849
Description
Context
I use my own allocators for both the regular allocator and the temp allocator, accordingly I use -default-to-panic-allocator to make sure I don't accidentally use the default allocators anywhere. However, doing so results in a crash when the runtime starts up.
Output of odin report
Odin: dev-2025-06-nightly:aa0cffb
OS: Windows 11 Unknown Edition (000000a1) (version: 23H2), build 22631.5472
CPU: AMD Ryzen 9 3950X 16-Core Processor
RAM: 65456 MiB
Backend: LLVM 20.1.0
Expected Behavior
_startup_runtime does not panic and my entry point is entered successfully
Current Behavior
_startup_runtime panics
Failure Information
This bug can be triggered if core:log is imported anywhere as this compiles and runs the init_terminal proc which is marked @(init), which then panics due to the panic allocator being set. This is despite the fact that I never use the console logger anywhere and don't need this init to run.
It looks like init_terminal is calling lookup_env which needs to allocate with the temp allocator, but the temp allocator uses the default allocator internally, which is the panic allocator if -default-to-panic-allocator is used. Accordingly, we get a panic.
Steps to Reproduce
- Create an odin source file with the following contents:
package repro
import "core:log"
main :: proc() {return}
- Build with the following command:
odin build .\repro.odin -file -default-to-panic-allocator -debug - Run in a debugger to observe the panic
Failure Logs
N/A
Potential solutions
We were discussing this issue on Discord and I thought of a couple of potential solutions, I write them here in case they're of any interest:
Lazy init
This particular @(init) could be replaced by a lazy initialization such as:
if !intrinsics.expect(terminal_initialized, true) do init_terminal()
and then marking init_terminal as @(cold).
Upsides
- This would allow the user to set up an allocator in time
- If one is not set up, the resulting call stack would paint a very clear picture of what's gone wrong
- No additional complexity/features need to be introduced to the language
Downsides
- This would have to be done in every proc that depends on that init function, with a significant potential for bugs (although the upside of this is it'd be very clear which procs actually do depend on that init)
- Another downside is multi-threading concerns, there would either need to be locking, atomic load/store or paying the init cost per thread
- There's a theoretical performance cost as well but I assume it's negligible, especially in the context of printing to the terminal
Static elimination of @(init) procs
Procedures that require (a) certain @(init) function(s) to run could be marked as such with a new attribute e.g. @(needs-init="init_terminal"). In very much the same way that foreign libs are not linked if nothing from them is referenced, if no proc that depends on a specific @(init) proc is ever referenced, then the @(init) proc is also eliminated.
Procs could not be marked @(init) and @(needs-init) at the same time in order to avoid any need for topological sorting or changing the behavior of the order of @(init) procs in any way.
Upsides
- This would follow the principle of least astonishment - simply importing
core:initwouldn't magically inject extra behavior into the runtime that allocates memory and reads environment variables in order to set things up for printing to the terminal if it's never used, but it does inject this behavior if it is used and therefore needed - A number of completely unnecessary
@(init)procs could be eliminated entirely, although the exact savings would need to be measured
Downsides
- This would introduce extra complexity in the language/compiler itself
- It would break a lot of existing code or introduce a second type of init that can be eliminated this way in order to keep compatibility