Quxlang Q2 2026: Hello World

Quxlang is now complete enough to print out hello world!


::linux_write_syscall INCLUDE_IF(OS_LINUX && ARCH_X64) ASM_PROCEDURE X64
  CALLABLE CALLCONV CCALL(I32, CONST=>> BYTE, SZ; RETURN SZ)
{
  MOV RAX, 1
  SYSCALL
  RET
}

::linux_write_syscall INCLUDE_IF(OS_LINUX && ARCH_X86) ASM_PROCEDURE X64
  CALLABLE CALLCONV CCALL(I32, CONST=>> BYTE, SZ; RETURN SZ)
{
  MOV EBX, [ESP+4]
  MOV ECX, [ESP+8]
  MOV EDX, [ESP+12]
  MOV EAX, 4
  INT 0x80
  RET
}

::linux_write_syscall INCLUDE_IF(OS_LINUX && ARCH_ARM64) ASM_PROCEDURE ARM
  CALLABLE CALLCONV CCALL(I32, CONST=>> BYTE, SZ; RETURN SZ)
{
  MOV X8, 64
  SVC 0
  RET
}

::hello_world_message INCLUDE_IF(OS_LINUX) STATIC STRING_CONSTANT := "hello world!";

::write_hello_world_linux INCLUDE_IF(OS_LINUX) FUNCTION()
{
  VAR begin CONST=>> BYTE := hello_world_message.BEGIN();
  VAR count SZ := (hello_world_message.END() - begin) AS ASSUME SZ;
  linux_write_syscall(1, begin, count);
}

::main FUNCTION(): I32
{
  write_hello_world_linux();
  RETURN 0;
}

Ok, this isn’t exactly something you would use in practice… Also I forgot to mention you also need to implement the program start yourself.


::PROGRAM_START INCLUDE_IF(OS_LINUX && ARCH_X64) ASM_PROCEDURE X64
{
  MOVABS RAX, OFFSET OBJECT_REF(MAIN_FUNCTION)
  MOV RAX, [RAX]
  CALL RAX
  MOV RDI, RAX
  MOV RAX, 60
  SYSCALL
}

::PROGRAM_START INCLUDE_IF(OS_LINUX && ARCH_X86) ASM_PROCEDURE X64
{
  MOV EAX, OFFSET OBJECT_REF(MAIN_FUNCTION)
  MOV EAX, [EAX]
  CALL EAX
  MOV EBX, EAX
  MOV EAX, 1
  INT 0x80
}

::PROGRAM_START INCLUDE_IF(OS_LINUX && ARCH_ARM64) ASM_PROCEDURE ARM
{
  ADRP X16, :got:OBJECT_REF(MAIN_FUNCTION)
  LDR X16, [X16, :got_lo12:OBJECT_REF(MAIN_FUNCTION)]
  LDR X16, [X16]
  BLR X16
  MOV X8, 93
  SVC 0
}

And thread locals don’t really exist or work, or really threads in general. It’s not really ready in any meaningful way. But the generated binaries can print out hello world.

rnicholl@Mac local % ./qxc-run-example.sh&& ./qxc-run-docker-artifacts.sh
Running qxc...
qxc: /Users/rnicholl/CLionProjects/quxlang/dev-workspace/buildspaces/system-clang/quxlang/Release/qxc
input: /Users/rnicholl/CLionProjects/quxlang/quxlang/tests/testdata/example
output: /tmp/qxc
targets: linux-x64,linux-arm64,linux-x86
debug compile output: 0
[quxlang:/Users/rnicholl/CLionProjects/quxlang/quxlang/sources/queries/run_static_test.cpp:323] SLOW 'run_static_test MODULE(main)::nontrivial_output_iterator_serialize_test' 2.25929ms
[quxlang:/Users/rnicholl/CLionProjects/quxlang/quxlang/sources/queries/run_static_test.cpp:323] SLOW 'run_static_test MODULE(main)::self_recursive_auto_return_test' 1.8935ms
[quxlang:/Users/rnicholl/CLionProjects/quxlang/quxlang/sources/queries/run_static_test.cpp:323] SLOW 'run_static_test MODULE(main)::string_tests' 2.84742ms
Wrote output executable: linux-arm64/app -> /tmp/qxc/output/linux-arm64/app
Wrote output executable: linux-arm64/app.dbg -> /tmp/qxc/output/linux-arm64/app.dbg
[quxlang:/Users/rnicholl/CLionProjects/quxlang/quxlang/sources/queries/run_static_test.cpp:323] SLOW 'run_static_test MODULE(main)::self_recursive_auto_return_test' 1.74917ms
[quxlang:/Users/rnicholl/CLionProjects/quxlang/quxlang/sources/queries/run_static_test.cpp:323] SLOW 'run_static_test MODULE(main)::string_tests' 2.73067ms
Wrote output executable: linux-x64/default -> /tmp/qxc/output/linux-x64/default
[quxlang:/Users/rnicholl/CLionProjects/quxlang/quxlang/sources/queries/run_static_test.cpp:323] SLOW 'run_static_test MODULE(main)::nontrivial_output_iterator_serialize_test' 2.09825ms
[quxlang:/Users/rnicholl/CLionProjects/quxlang/quxlang/sources/queries/run_static_test.cpp:323] SLOW 'run_static_test MODULE(main)::self_recursive_auto_return_test' 3.10992ms
[quxlang:/Users/rnicholl/CLionProjects/quxlang/quxlang/sources/queries/run_static_test.cpp:323] SLOW 'run_static_test MODULE(main)::string_tests' 2.89175ms
Wrote output executable: linux-x86/app -> /tmp/qxc/output/linux-x86/app
Wrote output executable: linux-x86/app-dbg -> /tmp/qxc/output/linux-x86/app-dbg
Generated executable artifacts:
/tmp/qxc/output/linux-arm64/app
/tmp/qxc/output/linux-arm64/app.dbg
/tmp/qxc/output/linux-x64/default
/tmp/qxc/output/linux-x86/app
/tmp/qxc/output/linux-x86/app-dbg
== ubuntu 24.04 x64 ==
hello world!
exit=0
== ubuntu 24.04 arm64 ==
hello world!
exit=0
== debian x86 ==
hello world!
exit=0
rnicholl@Mac local %

Yes, this was a full cross compile, with the host being MacOS. That is of course, the nature of Quxlang. All targets go in the quxbuild.yaml file, and the compiler outputs all binaries. The content of the output binaries does not depend on the host system, so you can build for many different targets simultaneously.

After I get thread locals/threading working, MacOS will probably be my next target. Unlike Linux, where all system calls can be done with pure assembly, interfacing with the Darwin kernel requires a bit of linker support. Windows will come last.

After that, we can begin work on the standard library. Although it’s worth noting, nothing will be “included” in the Quxlang compilation process, you still need to copy the standard library into your source bundle to build using it. Quxlang has a rule, no secret compiler magic. If it’s not in the source bundle, it doesn’t exist. This should make it fun for learning if you want to know what *really* happens before entering the main function (including normal some stuff I didn’t code in my hello world example, like how the command line arguments and environment variables are extracted from the auxiliary vector).

Leave a comment