ziglang / zig

General-purpose programming language and toolchain for maintaining robust, optimal, and reusable software.
https://ziglang.org
MIT License
34.6k stars 2.53k forks source link

Add mechanism for selectively enabling different forms of runtime safety #13339

Open Vexu opened 1 year ago

Vexu commented 1 year ago

Currently runtime safety can be controlled with two mechanisms:

But sometimes when you're pre 1.0 and have known issues you might want to release a release-fast build for the speed but keep certain less expensive runtime safety checks enabled like reached unreachable code and attempt to use null value.

This could be achieved with a new compiler flag which would allow you to toggle runtime safety checks regardless of those two mechanisms in a similar way to CPU feature flags:

ehaas commented 1 year ago

I like this proposal and have often wanted this exact thing - ("release a release-fast build for the speed but keep certain less expensive runtime safety checks enabled like reached unreachable code") - e.g. that would allow a release-fast build where assert still asserts.

With this proposal would there be a corresponding way to use @setRuntimeSafety (or something like it) to individually enable/disable certain checks per-scope?

Vexu commented 1 year ago

With this proposal would there be a corresponding way to use @setRuntimeSafety (or something like it) to individually enable/disable certain checks per-scope?

I did consider it but it doesn't seem as useful and if absolutely necessary could mostly be achieved by wrapping the operation causing the safety check in a block with @setRuntimeSafety.

ominitay commented 1 year ago

With this proposal would there be a corresponding way to use @setRuntimeSafety (or something like it) to individually enable/disable certain checks per-scope?

I did consider it but it doesn't seem as useful and if absolutely necessary could mostly be achieved by wrapping the operation causing the safety check in a block with @setRuntimeSafety.

I think that's quite a noisy and laborious-to-implement solution though. For example, a project may determine (for the entire project, or for a specific scope) that bounds-checking is a worthwhile safety check to use, but that overflow checks aren't. This wouldn't be very feasible without a specific solution, on a scope-based level. I think there should be a way to set the default safety checks for a build mode (or perhaps a custom build mode?) in build.zig, and then on a scope-level, with something like a more expressive @setRuntimeSafety.

Durobot commented 1 year ago

I like this proposal and have often wanted this exact thing - ("release a release-fast build for the speed but keep certain less expensive runtime safety checks enabled like reached unreachable code") - e.g. that would allow a release-fast build where assert still asserts.

Would it allow a ReleaseSafe build, where certain sections of the code, e.g. the sections that have the most impact on performance, can be optimized by having the checks removed / optimized away by the compiler?

Vexu commented 9 months ago

I wanted to see how enabling safety checks for unreachable would affect the compiler so I made an MVP of this.

zig-out/bin/zig dcaf43674e35372e1d28ab12c4c4ff9af9f3d646 build-fast/bin/zig dcaf43674e35372e1d28ab12c4c4ff9af9f3d646 + https://github.com/Vexu/zig/tree/safety-opt

It seems that by default release fast builds include debug info now:

# with debug info
261M    zig-out/bin/zig
261M    build-fast/bin/zig
# stripped
150M    zig-out/bin/zig
150M    build-fast/bin/zig

Equivalent performance:

Benchmark 1 (3 runs): zig-out/bin/zig test test/behavior.zig --test-no-exec -I test
  measurement          mean ± σ            min … max           outliers         delta
  wall_time          6.98s  ± 30.8ms    6.96s  … 7.02s           0 ( 0%)        0%
  peak_rss            438MB ±  429KB     437MB …  438MB          0 ( 0%)        0%
  cpu_cycles         27.1G  ±  125M     27.0G  … 27.2G           0 ( 0%)        0%
  instructions       35.9G  ± 17.9M     35.9G  … 35.9G           0 ( 0%)        0%
  cache_references    658M  ± 7.81M      649M  …  663M           0 ( 0%)        0%
  cache_misses       25.3M  ±  447K     24.8M  … 25.7M           0 ( 0%)        0%
  branch_misses       180M  ±  754K      180M  …  181M           0 ( 0%)        0%
Benchmark 2 (3 runs): build-fast/bin/zig test test/behavior.zig --test-no-exec -I test
  measurement          mean ± σ            min … max           outliers         delta
  wall_time          7.08s  ± 64.7ms    7.03s  … 7.15s           0 ( 0%)          +  1.4% ±  1.6%
  peak_rss            439MB ± 1.48MB     438MB …  441MB          0 ( 0%)          +  0.3% ±  0.6%
  cpu_cycles         27.6G  ±  254M     27.3G  … 27.8G           0 ( 0%)          +  1.7% ±  1.7%
  instructions       36.3G  ± 21.6M     36.3G  … 36.3G           0 ( 0%)          +  1.1% ±  0.1%
  cache_references    674M  ±  543K      673M  …  674M           0 ( 0%)          +  2.4% ±  1.9%
  cache_misses       26.1M  ± 1.27M     25.2M  … 27.6M           0 ( 0%)          +  3.3% ±  8.5%
  branch_misses       184M  ± 1.49M      182M  …  185M           0 ( 0%)          +  1.8% ±  1.5%

Hitting an assertion failure (#18273):

$ zig-out/bin/zig build-obj a.zig
Segmentation fault (core dumped)
$ build-fast/bin/zig build-obj a.zig
thread 204448 panic: reached unreachable code
Unwind information for `:0xa0d9b61` was not available, trace may be incomplete

Aborted (core dumped)