Luis Acebal
Luis Acebal

Reputation: 21

How to build and link to CGLM from Zig with or without SIMD intrinsics

I would like to link and use cglm C library. I'm working on windows without msvc (so targeting gnu C ABI) with Zig 0.7.1 and Zig 0.8.0 (master) without any luck.

I have been able to build CGLM static library from Zig build.zig but no luck linking against it from Zig program as CGLM SIMD intrinsics optimizations errors are reported.

const Builder = @import("std").build.Builder;

pub fn build(b: *Builder) void {
    // Standard target options allows the person running `zig build` to choose
    // what target to build for. Here we do not override the defaults, which
    // means any target is allowed, and the default is native. Other options
    // for restricting supported target set are available.
    const target = b.standardTargetOptions(.{});

    // Standard release options allow the person running `zig build` to select
    // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall.
    const mode = b.standardReleaseOptions();

    // cglm
    const cglmLibBasePath = "vendor/cglm/";
    const cglmFiles = &[_][]const u8{ 
        cglmLibBasePath ++ "src/euler.c",
        cglmLibBasePath ++ "src/affine.c",
        cglmLibBasePath ++ "src/io.c",
        cglmLibBasePath ++ "src/quat.c",
        cglmLibBasePath ++ "src/cam.c",
        cglmLibBasePath ++ "src/vec2.c",
        cglmLibBasePath ++ "src/vec3.c",
        cglmLibBasePath ++ "src/vec4.c",
        cglmLibBasePath ++ "src/mat2.c",
        cglmLibBasePath ++ "src/mat3.c",
        cglmLibBasePath ++ "src/mat4.c",
        cglmLibBasePath ++ "src/plane.c",
        cglmLibBasePath ++ "src/frustum.c",
        cglmLibBasePath ++ "src/box.c",
        cglmLibBasePath ++ "src/project.c",
        cglmLibBasePath ++ "src/sphere.c",
        cglmLibBasePath ++ "src/ease.c",
        cglmLibBasePath ++ "src/curve.c",
        cglmLibBasePath ++ "src/bezier.c",
        cglmLibBasePath ++ "src/ray.c",
        cglmLibBasePath ++ "src/affine2d.c",
    };
    const cglmLib = b.addStaticLibrary("cglm", null);
    cglmLib.setBuildMode(mode);
    cglmLib.setTarget(target);
    cglmLib.defineCMacro("CGLM_STATIC");
    cglmLib.defineCMacro("WIN32");
    cglmLib.addIncludeDir(cglmLibBasePath ++ "src/");
    for (cglmFiles) |cglmFile| {
        cglmLib.addCSourceFile(cglmFile, &[_][]const u8 { 
            "-std=c11",
            "-Wall",
            "-Werror",
            "-O3",
        });
    }    
    cglmLib.linkLibC();
    cglmLib.install();

    const exe = b.addExecutable("app-zig-cglm", "src/main.zig");
    exe.setBuildMode(mode);
    exe.setTarget(target);  
    // cglm
    exe.linkLibrary(cglmLib);
    exe.addSystemIncludeDir(cglmLibBasePath ++ "include/");
    // C and win32
    exe.linkLibC();
    exe.install();
    

    const run_cmd = exe.run();
    run_cmd.step.dependOn(b.getInstallStep());
    if (b.args) |args| {
        run_cmd.addArgs(args);
    }

    const run_step = b.step("run", "Run the app");
    run_step.dependOn(&run_cmd.step);
}

When I try to build/run the exe with the generated library linked then the following error is reported (similar in both Zig versions 0.7.1 and 0.8.0 master) Ex from 0.7.1 Zig version.

alagt@LAPTOP-HS5L5VEH MINGW64 /c/Dev/Projects/test-bed/app-zig-cglm (master)
$ zig build -Dtarget=x86_64-windows-gnu
.\zig-cache\o\1bc9e6dc93c2ab6590b8006381a9000e\cimport.zig:4255:26: error: unable to translate function
pub const glm_mul_sse2 = @compileError("unable to translate function"); // C:\Dev\Projects\test-bed\app-zig-cglm\vendor\cglm\include\cglm/simd/sse2/affine.h:17:1
                         ^
.\zig-cache\o\1bc9e6dc93c2ab6590b8006381a9000e\cimport.zig:4264:5: note: referenced here
    glm_mul_sse2(m1, m2, dest);
    ^
.\zig-cache\o\1bc9e6dc93c2ab6590b8006381a9000e\cimport.zig:657:20: error: unable to resolve typedef child type
pub const __m128 = @compileError("unable to resolve typedef child type"); // C:\Dev\Tools\zig-0.7.1\lib\include\xmmintrin.h:17:15
                   ^
.\zig-cache\o\1bc9e6dc93c2ab6590b8006381a9000e\cimport.zig:1255:48: note: referenced here
pub fn _mm_store_ps(arg___p: [*c]f32, arg___a: __m128) callconv(.C) void {
                                               ^
.\zig-cache\o\1bc9e6dc93c2ab6590b8006381a9000e\cimport.zig:3557:5: note: referenced here
    _mm_store_ps(&dest[@intCast(c_uint, @as(c_int, 0))], _mm_load_ps(&mat[@intCast(c_uint, @as(c_int, 0))]));
    ^
app-zig-cglm...The following command exited with error code 1:
C:\Dev\Tools\zig-0.7.1\zig.exe build-exe C:\Dev\Projects\test-bed\app-zig-cglm\src\main.zig C:\Dev\Projects\test-bed\app-zig-cglm\zig-cache\o\dfacf78e7514990119de31b967d43202\cglm.lib --library c --cache-dir C:\Dev\Projects\test-bed\app-zig-cglm\zig-cache --global-cache-dir C:\Users\alagt\AppData\Local\zig --name app-zig-cglm -target x86_64-windows-gnu -isystem C:\Dev\Projects\test-bed\app-zig-cglm\vendor\cglm\include --enable-cache
error: the following build command failed with exit code 1:
C:\Dev\Projects\test-bed\app-zig-cglm\zig-cache\o\64a99c599e98cac00a19cffa57c71a68\build.exe C:\Dev\Tools\zig-0.7.1\zig.exe C:\Dev\Projects\test-bed\app-zig-cglm C:\Dev\Projects\test-bed\app-zig-cglm\zig-cache C:\Users\alagt\AppData\Local\zig -Dtarget=x86_64-windows-gnu

The unable to resolve line from avxintrin.h is the following

typedef float __m128 __attribute__((__vector_size__(16), __aligned__(16)));

I would like to know if C libraries using intrinsics can work as of now and if not I would like a way to disable the intrinsics features sse/avx/... from the build.zig so the CGLM can be built and linked without SIMD optimizations.

Edit (Disabled SIMD features from build.zig)

I have dug a bit on the lib/zig/std/build.zig to know how to disable SIMD features and finally I was able to disable with following code on my build.zig

var target = b.standardTargetOptions(.{});
    target.cpu_features_sub = x86.featureSet(&[_]x86.Feature{
        x86.Feature.avx,
        x86.Feature.avx2,
        x86.Feature.avx512bf16,
        x86.Feature.avx512bitalg,
        x86.Feature.avx512bw,
        x86.Feature.avx512cd,
        x86.Feature.avx512dq,
        x86.Feature.avx512er,
        x86.Feature.avx512f,
        x86.Feature.avx512ifma,
        x86.Feature.avx512pf,
        x86.Feature.avx512vbmi,
        x86.Feature.avx512vbmi2,
        x86.Feature.avx512vl,
        x86.Feature.avx512vnni,
        x86.Feature.avx512vp2intersect,
        x86.Feature.avx512vpopcntdq,
        x86.Feature.sse,
        x86.Feature.sse_unaligned_mem,
        x86.Feature.sse2,
        x86.Feature.sse3,
        x86.Feature.sse4_1,
        x86.Feature.sse4_2,
        x86.Feature.sse4a,
        x86.Feature.ssse3,
    });    

State:

Now I'm able to build (without SIMD optimizations what is not ideal) and link CGLM lib from zig code but really concerning problem arise as the code calling standard C tanf function is computing really wrong results.

Following the zig code generated for the glm_perspective function.

pub fn glm_perspective(arg_fovy: f32, arg_aspect: f32, arg_nearVal: f32, arg_farVal: f32, arg_dest: [*c]vec4) callconv(.C) void {
    var fovy = arg_fovy;
    var aspect = arg_aspect;
    var nearVal = arg_nearVal;
    var farVal = arg_farVal;
    var dest = arg_dest;
    var f: f32 = undefined;
    var @"fn": f32 = undefined;
    glm_mat4_zero(dest);
    f = (1 / tanf((fovy * 0.5)));
    @"fn" = (1 / (nearVal - farVal));
    dest[@intCast(c_uint, @as(c_int, 0))][@intCast(c_uint, @as(c_int, 0))] = (f / aspect);
    dest[@intCast(c_uint, @as(c_int, 1))][@intCast(c_uint, @as(c_int, 1))] = f;
    dest[@intCast(c_uint, @as(c_int, 2))][@intCast(c_uint, @as(c_int, 2))] = ((nearVal + farVal) * @"fn");
    dest[@intCast(c_uint, @as(c_int, 2))][@intCast(c_uint, @as(c_int, 3))] = -1;
    dest[@intCast(c_uint, @as(c_int, 3))][@intCast(c_uint, @as(c_int, 2))] = (((2 * nearVal) * farVal) * @"fn");
}

The result of executing the f = (1 / tanf((fovy * 0.5))); line with arg_fovy = 0.785398185 (45 deg in radians) is returning 134217728. which is just totally wrong. Correct result should be approx. 2.4142134.

VSCode cppvsdbg do not allow me to step into the tanf to understand what could be wrong with the tanf implementation.

Edit (building and compiling min C code with Zig compiler):

I have compiled a min C case using math tanf and built with Zig builder mechanism.

build.zig

const Builder = @import("std").build.Builder;
const x86 = @import("std").Target.x86;

pub fn build(b: *Builder) void {
    // Standard target options allows the person running `zig build` to choose
    // what target to build for. Here we do not override the defaults, which
    // means any target is allowed, and the default is native. Other options
    // for restricting supported target set are available.
    var target = b.standardTargetOptions(.{});    
    target.cpu_features_sub = x86.featureSet(&[_]x86.Feature{
        x86.Feature.avx,
        x86.Feature.avx2,
        x86.Feature.avx512bf16,
        x86.Feature.avx512bitalg,
        x86.Feature.avx512bw,
        x86.Feature.avx512cd,
        x86.Feature.avx512dq,
        x86.Feature.avx512er,
        x86.Feature.avx512f,
        x86.Feature.avx512ifma,
        x86.Feature.avx512pf,
        x86.Feature.avx512vbmi,
        x86.Feature.avx512vbmi2,
        x86.Feature.avx512vl,
        x86.Feature.avx512vnni,
        x86.Feature.avx512vp2intersect,
        x86.Feature.avx512vpopcntdq,
        x86.Feature.sse,
        x86.Feature.sse_unaligned_mem,
        x86.Feature.sse2,
        x86.Feature.sse3,
        x86.Feature.sse4_1,
        x86.Feature.sse4_2,
        x86.Feature.sse4a,
        x86.Feature.ssse3,
    });
    

    // Standard release options allow the person running `zig build` to select
    // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall.
    const mode = b.standardReleaseOptions();

    const exeC = b.addExecutable("app-c", "src/main.c");
    exeC.setBuildMode(mode);
    exeC.setTarget(target);    
    // C and win32
    exeC.linkLibC();
    exeC.install();
    

    const run_cmd = exeC.run();
    run_cmd.step.dependOn(b.getInstallStep());
    if (b.args) |args| {
        run_cmd.addArgs(args);
    }

    const run_step = b.step("run", "Run the app");
    run_step.dependOn(&run_cmd.step);
}

src/main.c

#include <stdio.h>
#include <math.h>

int main(int argc, char **argv) {
    float fovDeg = (argc == 1 ? 45.0f : 50.0f);
    while (1) {
        float fovRad = fovDeg * (M_PI / 180.0f);
        float tanFov = tanf(fovRad);
        fprintf(stdout, "fovDeg=%f, fovRad=%f, tanFov=%f\n", fovDeg, fovRad, tanFov);
    }
    return 0;
}

If I build without SIMD exclusions or just set target as native the code executes correctly with the expected results.

$ zig build -Dtarget=native-windows-gnu run -- 1

fovDeg=50.000000, fovRad=0.872665, tanFov=1.191754

If I build with the SIMD exclusions and execute it then tanf returns 0

$ zig build -Dtarget=x86_64-windows-gnu run -- 1

fovDeg=50.000000, fovRad=0.872665, tanFov=0.000000

Current status

It seems to me that

If anybody can bring some light in case I'm missing something it would be appreciated.

Thanks in advance.

Upvotes: 2

Views: 1126

Answers (1)

user3475861
user3475861

Reputation: 133

Perhaps, I misunderstand your problem but this worked for me with no errors. Add to your build.zig:

exe.addIncludeDir("path_to_your_cglm/cglm/include");
exe.addLibPath("path_to_your_cglm/cglm/win/Release");

exe.linkSystemLibrary("cglm");

Also, the Discord and reddit Zig communities are very active so consider posting there. Have a look at this page: https://github.com/ziglang/zig/wiki/Community. There is also the Zig Forum.

Upvotes: 0

Related Questions