Is it ok to use vectors in c structs?

if the c-api is like this,

typedef struct hb_glyph_position_t {
  hb_position_t  x_advance;
  hb_position_t  y_advance;
  hb_position_t  x_offset;
  hb_position_t  y_offset;

  /*< private >*/
  hb_var_int_t   var;
} hb_glyph_position_t;

is it ok to use binding liek this ?

pub const HbGlyphPos = extern struct {
    advance: @Vector(2, i32),
    offset: @Vector(2, i32),
    private_var: i32,
};

I really don’t know. I was expecting that this does not work, but there are three facts that suggest an equivalence between C ABI and @Vector

  1. @Vector compiles successfully inside extern.
  2. In the @Vector zig reference it is mentioned:

TODO talk about C ABI interop

  1. @Vector acts as an array in zig.

… it supports … array indexing syntax to convert from vectors to scalars

Vectors also support assignment to and from fixed-length arrays with comptime-known length.

1 Like

It’s possible it might work correctly, but it relies on c and zig having the same vector representation.

zigs vector representation is left entirely up to the backend, so zig with LLVM and clang/zig compiled c might work. But AFAIK this isn’t guaranteed

But other backends, like zigs custom debug backends, may not work.

1 Like

My interpretation of:

TODO talk about C ABI interop

In the documentation, is that Vectors are intended to ‘just work’ with C, doing what one would expect… when possible.

There’s a lot of handwaving involved in saying that, though. I find the following comparison informative.

This is legal, and does what you’d expect:

export fn vSum(v: @Vector(4, u8)) u32 {
    return @reduce(.Add, v);
}

test "export vecFn" {
    const v: @Vector(4, u8) = .{ 1, 2, 3, 4 };
    try expectEqual(10, vSum(v));
}

This, however, is a compile error:

export fn vSum(v: @Vector(4, u9)) u32 {
    return @reduce(.Add, v);
}

error: parameter of type ‘@Vector(4, u9)’ not allowed in function with calling convention ‘etc’

My takeaway: if it’s a ‘natural’ machine-typed vector, it has a well-defined C ABI representation, and it’s fine. Interestingly this works with e.g. @Vector(1024, u16), so it isn’t limited to natural SIMD types. Presumably you can pass that a * uint16_t, and pinky-swear it has 1024 elements? Pure guesswork on my part.

Another data point: a @Vector(8, 256) passes std.meta.hasUniqueRepresentation, but is a compile error in export context: probably extern struct also, although I didn’t try that.

So the way to find out is: put the type where you want it and see what the compiler says. ¯\_(ツ)_/¯

It does seem worth documenting what’s going on here. The less lore, the better.

4 Likes

okay so i got some garbage !

i decided to print the structs as a simple array of ints before returning :

pub fn getGlyphPos(self: *hb_buffer_t) []GlyphPos {        
    var len: u32 = undefined;                              
    const ptr = hb_buffer_get_glyph_positions(self, &len); 
    const ptr_is: [*]i32 = @ptrCast(ptr);                  
    std.log.debug("P = {any}", .{ptr_is[0 .. len * 5]});   
    return ptr[0..len];                                    
}                                                          

and the ints were fine !

alignment ?

std.log.debug("i32 @ {}, i32x2 @ {}", .{ @alignOf(i32), @alignOf(@Vector(2, i32)) });

yep. alignment.

debug: i32 @ 4, i32x2 @ 16
1 Like

align(4) on fields somehow doesnt make the struct smaller :face_with_monocle:

i decided to just have this

pub const GlyphPos = extern struct {                         
    // zig fmt: off                                              
    x_advance: i32, y_advance: i32,                              
    x_offset: i32, y_offset: i32,                                
    private_var: i32,                                            
    // zig fmt: on                                               
    pub fn getAdvance(self: *const GlyphPos) @Vector(2, i32) {   
        return .{ self.x_advance, self.y_advance };              
    }                                                            
    pub fn getOffset(self: *const GlyphPos) @Vector(2, i32) {    
        return .{ self.x_offset, self.y_offset };                
    }                                                            
};                                                               

I also raised a related issue a while ago Vector sizes are different in x86_64 backend · Issue #24179 · ziglang/zig · GitHub and got the following answer:

Because the optimal vector representation is highly target-dependent, it’s intended that vectors have ill-defined layout, meaning nothing about their in-memory representation, including their @sizeOf/@alignOf, is guaranteed.

3 Likes

thank you

i asked to make it a compiling error : https://codeberg.org/ziglang/zig/issues/31423