i’m building the same source code for two very different target architectures, and i’m currently using the following pattern to isolate certain functions that contain (say) target-specific asm statements…
const arch = ... ; // current target architecture
fn archIndependent() void {
// compilable for any architecture
}
fn archSpecific() void {
if (arch != some_specific_arch) return;
...
asm volatile ("...");
...
}
without this comptime guard, i’ll receive errors about illegal asm instructions… but apparently this pattern is sufficient to keep the compiler from generating code for the rest of this function…
i was reading up on std.debug.assert, which apparently can provide some level of “semantic hints” to the optimizer… to that end, i tried the following:
but now, the compiler did attempt to generate code for archSpecific and failed… FWIW, some_specific_arch is a resource-constrained MCU where the program is built in ReleaseSmall mode… just like the eariler if(...) return statement, the assert(...) doesn’t show up in the generated machine code (as i’d expect)…
what’s a little disappointing is that i wasn’t able to convey my intent using the assert… in the spirit of “design-by-contract”, i have lots of other “semantic information” which i’d like to share with the compiler (eg., numeric ranges)…
could someone please explain just how much assertCOULD do in theory, SHOULD do in practice, and actually DOES do at present…
in the meanwhile, can i safely assume that my if(comptime_test) idiom will “always work” – giving me the zig equivalent of the C #if directive???
it clearly happens “earlier” when i do a comptime assert… when i compile my source for the “other” architecture, the compiler fails because of unreachable code!!!
and putting comptime in front of my original if(...) statement fails the compiler, since a “runtime” callable function can’t return a value at comptime!!!
so right now, my original solution – which relies on behavior a little further downstream in the compiler pipeline – is my best option???
I don’t think that would work. The problem here is that assert() returns void. Unless the compiler magically changes the function such that in that instance it returns noreturn, compilation would continue.
The difference is that in your original the function is effectively a no-op on other targets, whereas the comptime assert strategy makes calling the function at all on those other targets a compile error.
If you hit the assert it means the compiler cannot prove that the function isn’t called on those other targets.
I think in many cases I would instead create multiple modules that have the same functions, but with different implementations and then use build logic to switch which of the modules gets actually added as an import.
That way your code just gets to call the specific functions without having to use guard logic everywhere and you have one place where you can switch on the architecture to pick the right implementation.
The architecture should be comptime-known. We would have to see the code, but there’s probably a missing comptime somewhere. The compiler will treat things as comptime when it can, but sometimes it fails to notice that something is indeed comptime, and you need the keyword to enforce it. The assertion is doing its job here. If you remove the assertion and the code compiles, there’s a good chance that function will end up in the binary. Not only is this wasted space, but there could be a code path that leads to this function when it shouldn’t.