How to set RISC-V `-march`

When cross-compiling for RISC-V with Zig I believe you do something like this:

pub fn build(b: *std.Build) void {
    const target = b.resolveTargetQuery(.{
        .cpu_arch = .riscv32,
        .abi = .ilp32,
        .os_tag = .freestanding,
    });
    ...

But how do you enable other extensions? There’s a cpu_features_add field but it seems to be some kind of bitfield and I can’t figure out what to do with it.

For example how would you set this -march ISA string?

RV64IMAFDC_Svinval_Zicsr_Zifencei_Zba_Zbb_Zbs_Zicbom_Zicbop

Bonus question: how do you set the ABI to ilp32f or ilp32d? Or even lp64 - that seems to be missing from Abi, though it does exist in CallingConvention?

The various features are listed in lib/std/Target/riscv.zig. Maybe you can find what you need there.

Here’s how I’m selecting features:

Ah that’s really helpful, thanks!

What about the ABI though? You use

.abi = Target.Abi.none,

which doesn’t seem quite right?

I’m building for an embedded bare-metal target

There’s still an ABI though. If you link two objects together they need to know how to call each other. With GCC that’s handled by -mabi.

Does Zig use CallingConvention as its -mabi? In which case what does the .abi actually do?

Ah I think I figured it out. The .abi values are copied from LLVM’s EnvironmentType (source), presumably with Target.Abi.none corresponding to UnknownEnvironment.

This comes from the 4th component of the extremely awful “target triple”, so I think actually Zig is just wrong in calling this .abi. It should be .environment.

Note that the “triple” actually has five components: <arch>-<vendor>-<os>-<environment>-<object format>, but -<environment> is optional, which is why you sometimes see e.g. sparc-unknown-none-elf - here the <environment> is omitted and elf is the format. What a shit show.

I haven’t confirmed this but I think the environment determines the default ABI (CallingConvention for Zig), so setting it to Target.Abi.none is fine.

The calling convention (-mabi) is determined based on the .cpu_arch:

pub fn cCallingConvention(target: Target) ?std.builtin.CallingConvention {
    return switch (target.cpu.arch) {
...
        .riscv64 => .{ .riscv64_lp64 = .{} },
        .riscv32 => .{ .riscv32_ilp32 = .{} },

It doesn’t seem like you can control it, and I haven’t figured out where it decides the float ABI (e.g. if you want ilp32d).