Re: [RFC PATCH 0/4] x86/percpu: Use segment qualifiers

From: Uros Bizjak
Date: Sun Oct 01 2023 - 15:53:22 EST


On Sun, Oct 1, 2023 at 7:07 PM Linus Torvalds
<torvalds@xxxxxxxxxxxxxxxxxxxx> wrote:
>
> On Sun, 1 Oct 2023 at 06:16, Uros Bizjak <ubizjak@xxxxxxxxx> wrote:
> >
> > This patchset resurrect the work of Richard Henderson [1] and Nadav Amit [2]
> > to introduce named address spaces compiler extension [3,4] into the linux kernel.
>
> So apparently the extension has been around for a while (since gcc-6),
> but I don't actually find any single major _user_ of it.
>
> Debian code search finds exactly one case of it outside of the
> compilers themselves:
>
> # define THREAD_SELF \
> (*(struct pthread *__seg_fs *) offsetof (struct pthread, header.self))
>
> in glibc/sysdeps/x86_64/nptl/tls.h, and even that isn't very widely
> used (it seems to be a pthread_mutex implementation helper).
>
> So the coverage testing of this thing seems very weak. Do we have any
> other reason to believe that this is likely to actually be reliable
> enough to use?

The clang manual nicely summarises named address space extension with:

"Note that this is a very very low-level feature that should only be
used if you know what you’re doing (for example in an OS kernel)."

But, at least in GCC, the middle-end code handles many targets,
grepping for MEM_ADDR_SPACE in config directories returns avr, ft32,
gcn, h8300, i386, m32c, m68k, mn10300, msp430, pru, riscv. rl78,
rs6000, sh and vax target. This extension is quite popular with
embedded targets.

Regarding x86 target specific code, the same functionality used for
explicit address space is used internally to handle __thread
qualifier. The testcase:

__thread int m;
int foo (void) { return m; }

compiles the memory read to:

#(insn:TI 10 2 11 2 (set (reg/i:SI 0 ax)
# (mem/c:SI (const:DI (unspec:DI [
# (symbol_ref:DI ("m") [flags 0x2a] <var_decl
0x7f4a5a811bd0 m>)
# ] UNSPEC_NTPOFF)) [1 m+0 S4 A32 AS1]))
"thread.c":3:28 81 {*movsi_internal}
# (nil))
movl %fs:m@tpoff, %eax # 10 [c=5 l=8] *movsi_internal/0

where AS1 in memory flags represents address space 1 (%fs: prefix).

Also, stack protector internally uses the same target specific code:

#(insn:TI 6 9 10 2 (parallel [
# (set (mem/v/f/c:DI (plus:DI (reg/f:DI 7 sp)
# (const_int 8 [0x8])) [4 D.2009+0 S8 A64])
# (unspec:DI [
# (mem/v/f:DI (const_int 40 [0x28]) [5
MEM[(<address-space-1> long unsigned int *)40B]+0 S8 A64 AS1])
# ] UNSPEC_SP_SET))
# (set (reg:DI 0 ax [92])
# (const_int 0 [0]))
# (clobber (reg:CC 17 flags))
# ]) "pr111023.c":12:1 1265 {stack_protect_set_1_di}
# (expr_list:REG_UNUSED (reg:CC 17 flags)
# (expr_list:REG_UNUSED (reg:DI 0 ax [92])
# (nil))))
movq %fs:40, %rax # 6 [c=0 l=16] stack_protect_set_1_di

Again, AS1 in memory flags defines address space 1 (%fs prefix).

Compare this to the following testcase that explicitly defines __seg_gs:

__seg_gs int m;
int foo (void) { return m; }

The testcase compiles the read from memory to:

#(insn:TI 10 2 11 2 (set (reg/i:SI 0 ax)
# (mem/c:SI (symbol_ref:DI ("m") [flags 0x2] <var_decl
0x7f89fe611bd0 m>) [1 m+0 S4 A32 AS2])) "thread.c":3:28 81
{*movsi_internal}
# (nil))
movl %gs:m(%rip), %eax # 10 [c=5 l=7] *movsi_internal/0

Please note AS2 in memory flags, this represents address space 2 (%gs:
prefix). Otherwise, the memory reference is handled by the compiler as
all other memory references.

As demonstrated above, the compiler handles memory reference with
explicit address space through the same middle-end and
target-dependant code as __thread and stack protector memory
references. __thread and stack protector are heavily used features of
the compiler, so I'm quite confident that explicit address space
should work as advertised. Even *if* there are some issues with
aliasing, the kernel is immune to them due to

KBUILD_CFLAGS += -fno-strict-aliasing

Uros.