#!/usr/bin/env bash set -eu # LOCAL ONLY: not wired into CI. # # This covers what is not in CI: the targets with no CI job and no cross libc, # i386 and arm32, for checking manually. # # `abi_main.odin` is the corpus without core:testing, exiting with the number of # failing types, so this needs no threads, no libc and no cross sysroot, only # clang (which targets everything) and qemu-user. # # ./cross.sh linux_arm64 aarch64-linux-gnu qemu-aarch64 # ./cross.sh linux_i386 i386-linux-gnu qemu-i386 -microarch:pentium4 # ./cross.sh linux_arm32 arm-linux-gnueabihf qemu-arm # ./cross.sh linux_riscv64 riscv64-linux-gnu qemu-riscv64 # # i386 needs a microarch: below SSE2 the x86 backend cannot legalise a # sub-16-byte `f16` vector, and merely declaring a `proc "c"` that takes a # `#simd[2]f16` aborts the compiler with "LLVM ERROR: Do not know how to split # the result of this operator!". # # `pentium4` is what clang's own default for `i386-linux-gnu` is, so it is the # baseline to compare against. BOTH SIDES must agree on it: the C side follows # clang's default unless ABI_CFLAGS says otherwise, so `-microarch:haswell` # alone makes the two disagree about where a 32-byte vector lives and reports # phantom vector failures. Match them (`ABI_CFLAGS=-march=haswell`) or use # pentium4 on both. TARGET=${1:?odin target, e.g. linux_arm64} TRIPLE=${2:?clang triple, e.g. aarch64-linux-gnu} QEMU=${3:?qemu binary, e.g. qemu-aarch64} shift 3 # anything else goes to `odin build` : "${ODIN:=../../odin}" : "${CLANG:=clang}" # The C side's optimisation level. An ABI is a link-time contract, so the two # sides are built independently and either may be optimised: `ABI_CFLAGS=-O2`. : "${ABI_CFLAGS:=}" case "$TARGET" in *i386*) START='.text .globl _start _start: call probe_main movl %eax, %ebx movl $1, %eax int $0x80' ;; *arm64*) START='.text .globl _start _start: bl probe_main mov x8, #93 svc #0' ;; *arm32*) START='.text .globl _start _start: bl probe_main mov r7, #1 svc #0 @ The runtime does 64-bit division. On Arm, the compiler emits `__aeabi_uldivmod` @ instead of `__udivdi3`. It returns the quotient in r0:r1 and the remainder in @ r2:r3, which C cannot express. It is written here and forwards to the shim. .globl __aeabi_uldivmod __aeabi_uldivmod: push {lr} sub sp, sp, #12 add r12, sp, #4 str r12, [sp] bl shim_udivmod ldr r2, [sp, #4] ldr r3, [sp, #8] add sp, sp, #12 pop {pc}' ;; *riscv64*) START='.text .globl _start _start: call probe_main mv a0, a0 li a7, 93 ecall' ;; *) echo "no start stub for $TARGET" >&2; exit 2 ;; esac # cleaned BEFORE, not after -- the driver is left in place to inspect rm -rf build-cross mkdir -p build-cross/p $ODIN run gen.odin -file -- build-cross # Ask the C compiler which tiers it has, by preprocessing the generated `build-cross/tiers.c`. # The Odin side must use the same tiers or it references symbols C never emitted. have() { $CLANG --target="$TRIPLE" -E build-cross/tiers.c 2>/dev/null | grep -q "ABI_YES_$1" && echo true || echo false; } TIERS="-define:ABI_TIER_GNU=$(have GNU) -define:ABI_TIER_F16=$(have F16) -define:ABI_TIER_I128=$(have I128)" mv build-cross/abi_main.odin build-cross/p/ printf '%s\n' "$START" > build-cross/start.s # A freestanding shim: the runtime reaches for a few libc symbols even with # -no-crt, and 64-bit division on a 32-bit target is a compiler-rt call. cat > build-cross/shim.c <<'EOF' typedef unsigned long usz; static char heap[1<<20]; static usz hoff; void *malloc(usz n){ usz a=(hoff+15)&~(usz)15; if(a+n>sizeof heap) return 0; hoff=a+n; return heap+a; } void free(void *p){ (void)p; } void *calloc(usz n, usz m){ char*p=malloc(n*m); if(p) for(usz i=0;i0;i--)a[i-1]=b[i-1];} return d; } void *memset(void *d, int c, usz n){ char*a=d; for(usz i=0;i=0;i--){ r=(r<<1)|((a>>i)&1); if(r>=b){ r-=b; q|=(u64)1<&2 echo " Either type index $rc, or the driver died of signal $((rc-128)) (11 = SIGSEGV)." >&2 echo " Re-run with -define:ABI_SKIP=$((rc+1)): if the result moves it was the type," >&2 echo " and if it does not, a wrong-ABI call is corrupting the process." >&2 fi if [ "$rc" -eq 0 ]; then echo "$TARGET: every type agrees with clang" else # the driver returns the INDEX of the first disagreement, and the generator # emits the index -> name map, so the failure names a type rather than a count name=$(grep -m1 "^// $rc " build-cross/p/abi_main.odin | cut -f2) echo "$TARGET: DISAGREES with clang, first at type '${name:-#$rc}'" >&2 echo " re-run with -define:ABI_SKIP=$rc to find the next one" >&2 fi exit $rc