Help with ReleaseFast on Windows x86_64 when passing bool to external c uint32_t

Background

I use zig 0.14.0-dev.2851+b074fb7dd.
I’ve built snake using zig and wgpu-native and when running on x86_64-windows-gnu with ReleaseFast I get that my vertexbuffer is still mapped.

Wgpu takes the following descriptor,

pub const Descriptor = extern struct {
    nextInChain: ?*const ChainedStruct = null,
    label: wgpu.StringView = .{ .data = "", .length = 0 },
    usage: wgpu.Flag = @intFromEnum(Usage.None), // TODO: take a [2]Usage
    size: u64 = 0,
    mappedAtCreation: bool = false,
};

mappedAtCreation is a bool which in zig is a u1 I believe. Wgpu has their bools as uint32_t. Now on MacOS with ReleaseFast there is no problem passing the descriptor with the field defined as a bool but on windows ReleaseFast I think this sets mappedAtCreation to true since I tried changing the field to a u32 and it then ran with no problem. Running on debug and ReleaseSafe works on windows.

I have tested it by compiling directly on windows and crosscompiling from MacOS. The bug still apear.

Question

Is this a bug with zig 0.14.0 or is it intended behaviour?

My assumption is that it should be fine passing a u1 to uint32_t since it’s a smaller size. Any help would be appreciated.

extern makes the struct C memory layout compatible, so a bool would be one byte. But let me check what webgpu.h’s idea of a boolean is one sec…

yep, webgpu.h defines a bool as uint32_t (webgpu-headers/webgpu.h at 0c472a042b0d410aa2a1ee297cf8fd3097bf424b · webgpu-native/webgpu-headers · GitHub)… silly thing IMHO since bool size is standardized these days… but maybe they want to avoid struct members < 4 bytes for some reason.

…btw, why not directly @cImport the wgpu.h header from the wgpu-native project? That way you should be safe when it comes to struct memory layout and ABI:

(I can only guess that on macOS it only works by accident with that mappedAtCreation: bool = false, in manual bindings, since that bool should definitely be an u32)

Yes I know, I am writing my own zig bindings to wgpu-native. What I don’t understand is why is it just the ReleaseFast version on windows (haven’t tested Linux yet) that isn’t working. Debug and release safe are fine with the bool instead of uint32_t.

Yeah I am not especially fond of that choice as well lol but maybe there is a good reason for it.

Maybe different data on the stack (around the Descriptor struct) so that one appears to be working by accident but not the other.

From my experience with auto-generating bindings for the sokol headers (e.g. sokol-zig), minor bugs in the memory struct layout can lead to all sorts of weird behaviour, but hardly any actual crashes.

Thanks for your time, yeah I will probably go the route of having one “zig” struct with nicer fields like slices and bools and convert them to the external structs. Just curious if the different behaviour of it working on some modes. Maybe fixing my dev env on windows and using a debugger will tell the answer.

Yeah that library is great, used your way of using emsdk to compile my code for the web. Great resource.

just a few thoughts re ‘idiomatic Zig bindings’ (as opposed to cImport’ed headers):

  • in the sokol-bindings generation I’m using the clang ast-dump JSON output to generate the target language bindings, and then run that on each commit of the C headers - however I’m dictating myself a couple of restrictions in the public C APIs to make this easier (e.g. no nested struct declarations, no unions, no constants defined as #defines but instead as enums)

  • alternatively I also considered using Zig’s comptime reflection on cImported headers (e.g. you can iterate over all structs/enums/functions in the imported header and their struct fields all in Zig code and then use that reflection information to generate Zig source files with some transformations (like Zig’s standard naming convention). When I tried that first (many moons ago) I was running into some internal Zig comptime loop limit, but I think this can be bumped now.

…e.g. I would try anything to avoid manually maintained bindings :wink:

PS: the struggle with embedded C arrays is real though (I would rather see this realized though instead of having to use slices): QoL: Partial array initialization in structs · Issue #6068 · ziglang/zig · GitHub - since slices always come with ownership trouble…

…but thinking about it, probably not a problem for the WebGPU C API, since they use pointer-chaining instead of nested structs and arrays instead (also not great tbh)

Created a minimal example with a c lib taking a similar sized struct as above and printing from c works fine and prints the correct value. So perhaps the bug lies in wgpu-native.

main.zig

const StringView = extern struct {
    data: [*:0]const u8 = "",
    length: usize = 0
};
const Descriptor = extern struct {
    nextInChain: ?*const anyopaque = null,
    label: StringView = .{},
    usage: u64 = 0,
    size: u64 = 0,
    mappedAtCreation: bool = false
};

extern "c" fn take_bool(input: *const Descriptor) void;

pub fn main() !void {
    take_bool(&Descriptor{});
}

lib.c

#include <stdint.h>
#include <stdio.h>
#include "lib.h"

void take_bool(BufferDescriptor *const descriptor) {
    printf("c got: %d\n", descriptor->mappedAtCreation);
}

lib.h

#include <stdint.h>
typedef struct StringView {
    char* text;
    size_t length;
} StringView;

typedef struct BufferDescriptor {
    /* data */
    void* ChainedStruct;
    StringView message;
    uint64_t usage;
    uint64_t size;
    uint32_t mappedAtCreation; //bool
} BufferDescriptor;

void take_bool(BufferDescriptor *const input);

Will definetly try doing that someday, for now I`m just trying to learn Webgpu and it’s also a bit fun to write more idomatic wrapper bindings in the meantime than just using zigs translate-c. But if I wore to mainain it, I would definetly go with the rout of auto generating it. Just between wgpu-native versions has been somewhat tedious and it doesn’t help that emscripten headers are way behind.

I saw sometime before that Sokol can use wgpu as one of the backends as well so will take a closer look on how you do it for inspiration.

The comprtime reflection route seems enticing. Haha less of an headache than having an ast for me.

Why represent 1 bit with 8 bits, when you could represent 1 bit with 32 bits!

5 Likes

zig limits the number of backward branches (loops) to avoid infinite loops at comptime, good news is you can configure it with @setEvalBranchQuota
if possible its best to calculate the limit instead of hard-coding one, idk if it existed when you last tried or not but now ya know :3

Yeah I was running into the eval-branch-quota limit in my massively big (code-generated) switch-decoder in my Z80 emulator (chipz/src/chips/z80.zig at efb0176955d284cbbeac80025b56817dd566ed36 · floooh/chipz · GitHub), I just don’t remember if it was the same limitation I was running into in my comptime-reflection code (and I think back then there wasn’t a simple builtin to bump the limit, may remember wrong though).