As other comments have noted, the asm statement needs to have its input/output registers specified to ensure the compiler doesn't erase the "unused" values.
Working example: https://john-millikin.com/unix-syscalls#linux-x86-64-gnu-c
Adapted to use main():
static const int STDOUT = 1;
static const int SYSCALL_WRITE = 1;
static const char message[] = "Hello, world!\n";
static const int message_len = sizeof(message);
int main() {
register int rax __asm__ ("rax") = SYSCALL_WRITE;
register int rdi __asm__ ("rdi") = STDOUT;
register const char *rsi __asm__ ("rsi") = message;
register int rdx __asm__ ("rdx") = message_len;
__asm__ __volatile__ ("syscall"
: "+r" (rax)
: "r" (rax), "r" (rdi), "r" (rsi), "r" (rdx)
: "rcx", "r11");
return 0;
}
Test with: $ gcc -o hello hello.c
$ ./hello
Hello, world!