What are the semantic differences between @export and export + linksection?

Hello :wave:

I have this snippet:

const SomeType = extern struct { value: u64 };

fn MakeSymbols(comptime name1: []const u8, comptime name2: []const u8) void {
    const A = SomeType{ .value = 10 };
    @export(A, .{
        .name = name1,
    });

    const B = &A;
    @export(B, .{
        .name = name2,
    });
}

pub fn main() void {
    comptime MakeSymbols("A", "B");
}

On an ARM Macbook, this generates the following two symbols:

	.section	__TEXT,__const
	.globl	_A
	.p2align	3, 0x0
_A:
	.quad	10

	.section	__DATA,__const
	.globl	_B
	.p2align	3, 0x0
_B:
	.quad	_A

Notice that _B references _A, which is good.

Then I tweak the example to specify the section for A:

const SomeType = extern struct { value: u64 };

fn MakeSymbols(comptime name1: []const u8, comptime name2: []const u8) void {
    const A = SomeType{ .value = 10 };
    @export(A, .{
        .name = name1,
        .section = "__TEXT,__myconst",  // this is new
    });

    const B = &A;
    @export(B, .{
        .name = name2,
    });
}

pub fn main() void {
    comptime MakeSymbols("A", "B");
}

This modified snippet gives me the following symbols:

	.section	__TEXT,__const
	.p2align	3, 0x0
_symbols.MakeSymbols__anon_2728:
	.quad	10

	.section	__TEXT,__myconst
	.globl	_A
	.p2align	3, 0x0
_A:
	.quad	10

	.section	__DATA,__const
	.globl	_B
	.p2align	3, 0x0
_B:
	.quad	_symbols.MakeSymbols__anon_2728

Notice that _B no longer references _A but instead points to _symbols.MakeSymbols__anon_2728, which is a non-global duplicate of symbol _A. This behavior occurs regardless of which section I specify for A (even if I specify its default section __TEXT,__const!)

Of course, I can rewrite this example to use the (more concise) export and linksection keywords:

const SomeType = extern struct { value: u64 };
export const A linksection("__TEXT,__myconst") = SomeType{ .value = 10 };
export const B = &A;

pub fn main() void {}

And this does work! In particular, it puts _A in the specified section and _B still references _A:

	.section	__TEXT,__myconst
	.globl	_A
	.p2align	3, 0x0
_A:
	.quad	10

	.section	__DATA,__const
	.globl	_B
	.p2align	3, 0x0
_B:
	.quad	_A

However, I cannot use the export keyword inside of a comptime function – which is the crux of what I am going for. My use case is generating symbols for the Objective-C runtime at comptime (akin to what clang/LLVM/ld do for actual Objective-C code). In particular, I’d like to do so using a comptime function so that I can encapsulate the logic required generate the different ObjC symbols that the runtime consumes.

Not sure if pretending to be Objective-C is a valid Zig use case :sweat_smile: – though it is tantalizing to have potentially zero-overhead interop with Apple’s frameworks from Zig. Even so, I would like to better understand the semantic difference between @export and export + linksection that lead to this difference in the generated assembly.

Zig version: 0.11.0-dev.3892+0a6cd257b

I think it’s in scope. I’ve shared this post with Jakub.

1 Like