#!/bin/sh # fat cosmopolitan c/c++ compiler # https://github.com/jart/cosmopolitan # https://cosmo.zip/ BIN=${0%/*} PROG=${0##*/} ORIGINAL="$0 $*" GCC_VERSION=14.1.0 TMPDIR=${TMPDIR:-/tmp} if [ "$1" = "--version" ]; then cat <&2 } fatal_error() { echo "$PROG: fatal error: $1" >&2 echo "compilation terminated." >&2 Exit 1 } log_original() { if [ -n "$BUILDLOG" ]; then printf '# %s\n' "$ORIGINAL" >>"$BUILDLOG" fi } log_command() { if [ -n "$BUILDLOG" ]; then printf '(cd %s; %s)\n' "$PWD" "$*" >>"$BUILDLOG" fi } if [ x"$TMPDIR" != x"${TMPDIR#* }" ]; then fatal_error '$TMPDIR containing spaces not supported' elif [ ! -d "$TMPDIR" ]; then if ! mkdir -p "$TMPDIR" 2>/dev/null; then fatal_error "$TMPDIR: not a directory" fi fi OPT= ARGS= FLAGS= OUTPUT= MDFLAG=0 MCOSMO=0 INTENT=ld NEED_JOIN= NEED_EQUAL= NEED_OUTPUT= APELINKFLAGS= FLAGS_X86_64= FLAGS_AARCH64= INPUT_FILE_COUNT=0 DEPENDENCY_OUTPUT= NEED_DEPENDENCY_OUTPUT= for x; do if [ x"$x" != x"${x#* }" ]; then fatal_error "arguments containing spaces unsupported: $x" fi if [ -n "$NEED_OUTPUT" ]; then NEED_OUTPUT= OUTPUT=$x continue elif [ -n "$NEED_DEPENDENCY_OUTPUT" ]; then NEED_DEPENDENCY_OUTPUT= DEPENDENCY_OUTPUT=$x continue elif [ -n "$NEED_JOIN" ]; then x="${NEED_JOIN}${x}" NEED_JOIN= elif [ -n "$NEED_EQUAL" ]; then x="${NEED_EQUAL}=${x}" NEED_EQUAL= elif [ x"$x" = x"-" ] || # is alias for stdin [ x"$x" = x"${x#-*}" ]; then # !startswith(x, "-") if [ x"$x" != x"${x%.s}" ] || [ x"$x" != x"${x%.S}" ]; then fatal_error "$x: assembler input files not supported" elif [ x"$x" != x"${x%.so}" ] || [ x"$x" != x"${x%.dll}" ] || [ x"$x" != x"${x%.dylib}" ]; then fatal_error "$x: dynamic shared object input files not supported" elif [ x"$x" != x"-" ] && [ ! -f "$x" ]; then fatal_error "$x: no such file" fi INPUT_FILE_COUNT=$((INPUT_FILE_COUNT + 1)) ARGS="$ARGS $x" # don't add to $FLAGS array continue elif [ x"$x" = x"-o" ]; then NEED_OUTPUT=1 continue elif [ x"$x" != x"${x#-o}" ]; then # startswith(x, "-o") OUTPUT=${x#-o} continue elif [ x"$x" = x"-MF" ]; then NEED_DEPENDENCY_OUTPUT=1 continue elif [ x"$x" != x"${x#-MF}" ]; then # startswith(x, "-MF") DEPENDENCY_OUTPUT=${x#-MF} continue elif [ x"$x" != x"${x#-O}" ]; then # startswith(x, "-O") OPT=$x elif [ x"$x" = x"-c" ]; then INTENT=cc elif [ x"$x" = x"-E" ] || [ x"$x" = x"-M" ] || [ x"$x" = x"-MM" ]; then INTENT=cpp elif [ x"$x" = x"-s" ]; then APELINKFLAGS="$APELINKFLAGS -s" continue elif [ x"$x" = x"-v" ]; then exec 3<&2 # dup2(2, 3) b/c stderr will be redirected later BUILDLOG=/dev/fd/3 continue elif [ x"$x" = x"-save-temps" ]; then SAVE_TEMPS=1 elif [ x"$x" = x"-mcosmo" ]; then MCOSMO=1 continue elif [ x"$x" = x"-mdbg" ]; then MODE=dbg continue elif [ x"$x" = x"-mtiny" ]; then MODE=tiny continue elif [ x"$x" = x"-moptlinux" ]; then MODE=optlinux continue elif [ x"$x" = x"-fomit-frame-pointer" ]; then # Quoth Apple: "The frame pointer register must always address a # valid frame record. Some functions — such as leaf functions or # tail calls — may opt not to create an entry in this list. As a # result, stack traces are always meaningful, even without debug # information." x="-momit-leaf-frame-pointer -foptimize-sibling-calls -fschedule-insns2" elif [ x"$x" = x"-r" ] || [ x"$x" = x"-S" ] || [ x"$x" = x"-pie" ] || [ x"$x" = x"-shared" ] || [ x"$x" = x"-nostdlib" ] || [ x"$x" = x"-mred-zone" ] || [ x"$x" = x"-fsanitize=thread" ]; then fatal_error "$x flag not supported" elif [ x"$x" = x"-mno-red-zone" ]; then # "Any memory below the stack beyond the red zone is considered # volatile and may be modified by the operating system at any time." # https://devblogs.microsoft.com/oldnewthing/20190111-00/?p=100685 continue elif [ x"$x" = x"-fpic" ] || [ x"$x" = x"-fPIC" ]; then # no support for building dynamic shared objects yet. reports # indicate that ignoring these flags, helps let autoconf know continue elif [ x"$x" = x"-fpie" ] || [ x"$x" = x"-pie" ]; then # no support for position independent executables # https://github.com/jart/cosmopolitan/issues/1126 continue elif [ x"$x" = x"-static-libgcc" ] || \ [ x"$x" = x"-shared-libgcc" ]; then # cosmopolitan.a always has llvm compiler runtime static code continue elif [ x"$x" = x"-march=native" ]; then fatal_error "-march=native can't be used when building fat binaries" elif [ x"$x" != x"${x#-Xx86_64}" ]; then x=${x#-Xx86_64} # e.g. cosmocc "-Xx86_64 -msse3 -mavx -mavx2 -mf16c -mfma" FLAGS_X86_64="$FLAGS_X86_64 ${x#-Xx86_64}" continue elif [ x"$x" != x"${x#-Xaarch64}" ]; then x=${x#-Xaarch64} FLAGS_AARCH64="$FLAGS_AARCH64 $x" continue elif [ x"$x" = x"-dumpversion" ]; then echo $GCC_VERSION Exit 0 elif [ x"$x" = x"-x" ] || [ x"$x" = x"-e" ] || [ x"$x" = x"-z" ] || [ x"$x" = x"-T" ] || [ x"$x" = x"-L" ] || [ x"$x" = x"-I" ] || [ x"$x" = x"-D" ] || [ x"$x" = x"-U" ] || [ x"$x" = x"-MF" ] || [ x"$x" = x"-MT" ] || [ x"$x" = x"-iquote" ] || [ x"$x" = x"-isystem" ] || [ x"$x" = x"-include" ]; then NEED_JOIN=$x continue elif [ x"$x" = x"--param" ]; then NEED_EQUAL=$x continue elif [ x"$x" = x"-MD" ] || [ x"$x" = x"-MMD" ]; then MDFLAG=1 fi FLAGS="$FLAGS $x" ARGS="$ARGS $x" done if [ $INPUT_FILE_COUNT -eq 0 ]; then fatal_error "no input files" elif [ -z "$INPUT" ] && [ $INTENT != ld ] && [ $INPUT_FILE_COUNT -gt 1 ]; then fatal_error "cannot specify '-o' with '-c', or '-E' with multiple files" fi PLATFORM="-D__COSMOPOLITAN__ -D__COSMOCC__ -D__FATCOSMOCC__" PREDEF="-include libc/integral/normalize.inc" CPPFLAGS="-fno-pie -nostdinc -isystem $BIN/../include" CFLAGS="-fportcosmo -fno-dwarf2-cfi-asm -fno-unwind-tables -fno-asynchronous-unwind-tables -fno-semantic-interposition" LDFLAGS="-static -nostdlib -no-pie -fuse-ld=bfd -Wl,-z,noexecstack -Wl,-z,norelro -Wl,--gc-sections" PRECIOUS="-fno-omit-frame-pointer" # these features screw with backtraces so avoid them if [ x"$OPT" != x"-Os" ] && [ x"$MODE" != x"tiny" ] && [ x"$MODE" != x"optlinux" ]; then CFLAGS="$CFLAGS -fno-optimize-sibling-calls -mno-omit-leaf-frame-pointer" fi if [ x"$OPT" != x"-O3" ] && [ x"$MODE" != x"optlinux" ]; then CFLAGS="$CFLAGS -fno-schedule-insns2" fi CC_X86_64="$BIN/x86_64-linux-cosmo-gcc" CC_AARCH64="$BIN/aarch64-linux-cosmo-gcc" if [ x"$PROG" != x"${PROG%++}" ]; then CC_X86_64="$BIN/x86_64-linux-cosmo-g++" CC_AARCH64="$BIN/aarch64-linux-cosmo-g++" CFLAGS="$CFLAGS -fno-rtti -fno-exceptions -fuse-cxa-atexit" CPPFLAGS="-isystem $BIN/../include/third_party/libcxx $CPPFLAGS" else CFLAGS="$CFLAGS -Wno-implicit-int" fi if [ x"$MODE" = x"dbg" ]; then LIB_X86_64="$BIN/../x86_64-linux-cosmo/lib/dbg" LIB_AARCH64="$BIN/../aarch64-linux-cosmo/lib/dbg" elif [ x"$MODE" = x"tiny" ]; then LIB_X86_64="$BIN/../x86_64-linux-cosmo/lib/tiny" LIB_AARCH64="$BIN/../aarch64-linux-cosmo/lib/tiny" elif [ x"$MODE" = x"optlinux" ]; then LIB_X86_64="$BIN/../x86_64-linux-cosmo/lib/optlinux" LIB_AARCH64="$BIN/../aarch64-linux-cosmo/lib/optlinux" else LIB_X86_64="$BIN/../x86_64-linux-cosmo/lib" LIB_AARCH64="$BIN/../aarch64-linux-cosmo/lib" fi CRT_X86_64="$LIB_X86_64/ape.o $LIB_X86_64/crt.o" CPPFLAGS_X86_64="$CPPFLAGS" CFLAGS_X86_64="$CFLAGS -mno-tls-direct-seg-refs" LDFLAGS_X86_64="$LDFLAGS -L$LIB_X86_64 -L$BIN/../x86_64-linux-cosmo/lib -Wl,-T,$LIB_X86_64/ape.lds -Wl,-z,common-page-size=4096 -Wl,-z,max-page-size=16384" LDLIBS_X86_64="-lcosmo" CRT_AARCH64="$LIB_AARCH64/crt.o" CPPFLAGS_AARCH64="$CPPFLAGS -fsigned-char" CFLAGS_AARCH64="$CFLAGS -ffixed-x18 -ffixed-x28 -mno-outline-atomics" LDFLAGS_AARCH64="$LDFLAGS -L$LIB_AARCH64 -L$BIN/../aarch64-linux-cosmo/lib -Wl,-T,$LIB_AARCH64/aarch64.lds -Wl,-z,common-page-size=16384 -Wl,-z,max-page-size=16384" LDLIBS_AARCH64="-lcosmo" SUPPORT_VECTOR=-1 if [ x"$MODE" = x"optlinux" ]; then CPPFLAGS_X86_64="$CPPFLAGS_X86_64 -mno-red-zone" SUPPORT_VECTOR=linux fi if [ x"$OPT" != x"-Os" ] && [ x"$MODE" != x"tiny" ] && [ x"$MODE" != x"optlinux" ]; then CFLAGS_X86_64="${CFLAGS_X86_64} -fpatchable-function-entry=18,16 -fno-inline-functions-called-once -DFTRACE -DSYSDEBUG" CFLAGS_AARCH64="${CFLAGS_AARCH64} -fpatchable-function-entry=7,6 -fno-inline-functions-called-once -DFTRACE -DSYSDEBUG" fi if [ x"$PROG" != x"${PROG%++}" ]; then LDLIBS_X86_64="-lcxx ${LDLIBS_X86_64}" LDLIBS_AARCH64="-lcxx ${LDLIBS_AARCH64}" fi if [ $MCOSMO -eq 1 ]; then CPPFLAGS_X86_64="${CPPFLAGS_X86_64} -D_COSMO_SOURCE" CPPFLAGS_AARCH64="${CPPFLAGS_AARCH64} -D_COSMO_SOURCE" fi log_original if [ $INTENT = cpp ]; then if [ -n "$OUTPUT" ]; then ARGS="$ARGS -o$OUTPUT" fi set -- \ "$CC_X86_64" \ -U__k8 \ -U__k8__ \ -U__amd64 \ -U__amd64__ \ -U__x86_64 \ -U__x86_64__ \ -U__SSE__ \ -U__SSE2__ \ -U__SSE2_MATH__ \ -mno-red-zone \ $PLATFORM \ $CPPFLAGS \ $ARGS log_command "$@" exec "$@" fi mangle_object_path() { path=$1 arch=$2 outdir=${path%/*} outbas=${path##*/} if [ x"$outdir" = x"$path" ]; then outdir= elif [ -n "$outdir" ]; then outdir="$outdir/" fi if [ ! -d "$outdir.$arch" ]; then mkdir -p "$outdir.$arch" || Exit fi mangled_path="${outdir}.$arch/$outbas" } get_dependency_outputs() { if [ $MDFLAG -eq 1 ]; then if [ -n "$DEPENDENCY_OUTPUT" ]; then DEPENDENCY_FLAGS_x86_64="-MF $DEPENDENCY_OUTPUT" mangle_object_path "$DEPENDENCY_OUTPUT" aarch64 DEPENDENCY_FLAGS_AARCH64="-MF $mangled_path" else base="$1.d" DEPENDENCY_FLAGS_x86_64="-MF $base" mangle_object_path "$base" aarch64 DEPENDENCY_FLAGS_AARCH64="-MF $mangled_path" fi fi } mktemper() { "$BIN/mktemper" \ "$TMPDIR/fatcosmocc.XXXXXXXXXXXXX$1" } build_object() { out2=$(mktemper .txt) || Exit TEMP_FILES="${TEMP_FILES} $out2" ( set -- \ "$CC_X86_64" \ -o"$OUTPUT_X86_64" \ $PLATFORM \ $PREDEF \ $CFLAGS_X86_64 \ $CPPFLAGS_X86_64 \ "$@" \ $DEPENDENCY_FLAGS_x86_64 \ $FLAGS_X86_64 \ $PRECIOUS log_command "$@" "$@" || exit set -- \ "$BIN/fixupobj" \ "$OUTPUT_X86_64" log_command "$@" exec "$@" ) & pid1=$! ( set -- \ "$CC_AARCH64" \ -o"$OUTPUT_AARCH64" \ $PLATFORM \ $PREDEF \ $CFLAGS_AARCH64 \ $CPPFLAGS_AARCH64 \ "$@" \ $DEPENDENCY_FLAGS_AARCH64 \ $FLAGS_AARCH64 \ $PRECIOUS log_command "$@" "$@" || exit set -- \ "$BIN/fixupobj" \ "$OUTPUT_AARCH64" log_command "$@" exec "$@" ) 2>"$out2" & pid2=$! if ! wait $pid1; then kill $pid2 2>/dev/null wait Exit 1 fi if ! wait $pid2; then echo "$PROG: x86_64 succeeded but aarch64 failed to build object" >&2 cat "$out2" >&2 Exit 1 fi } # turn source files into objects LDARGS_X86_64= LDARGS_AARCH64= for x in $ARGS; do if [ x"$x" != x"-" ] && # is alias for stdin [ x"$x" != x"${x#-*}" ]; then # startswith(x, "-") # this argument is a flag LDARGS_X86_64="${LDARGS_X86_64} $x" if [ x"$x" != x"${x#-L}" ]; then # startswith(x, "-L") x="$x/.aarch64" fi LDARGS_AARCH64="${LDARGS_AARCH64} $x" else # this argument is an input file if [ x"$x" != x"${x%.o}" ] || [ x"$x" != x"${x%.a}" ]; then if [ $INTENT = cc ]; then show_warning "$x: linker input file unused because linking not done" else mangle_object_path "$x" aarch64 if [ ! -f "$mangled_path" ]; then fatal_error "$x: linker input missing concomitant $mangled_path file" fi LDARGS_X86_64="${LDARGS_X86_64} $x" LDARGS_AARCH64="${LDARGS_AARCH64} $mangled_path" fi elif [ $INTENT = cc ]; then if [ -n "$OUTPUT" ]; then # e.g. `cc -c -o bar.o foo.c` is specified by user OUTPUT_X86_64=$OUTPUT mangle_object_path "$OUTPUT" aarch64 OUTPUT_AARCH64="$mangled_path" get_dependency_outputs "$OUTPUT" build_object $FLAGS -c "$x" else # e.g. `cc -c dir/foo.c` builds foo.o o=${x##*/} OUTPUT_X86_64="${o%.*}.o" mangle_object_path "${o%.*}.o" aarch64 OUTPUT_AARCH64="$mangled_path" get_dependency_outputs "${o%.*}" build_object $FLAGS -c "$x" fi else # e.g. `cc foo.c` should build a.out if [ -z "$OUTPUT" ]; then OUTPUT=a.out fi if [ -z "$DEPENDENCY_OUTPUT" ]; then o=${x##*/} DEPENDENCY_OUTPUT="a-${o%.*}.d" fi # e.g. `cc -o foo foo.c` should *not* build foo.o OUTPUT_X86_64=$(mktemper .o) || Exit OUTPUT_AARCH64=$(mktemper .o) || Exit TEMP_FILES="${TEMP_FILES} ${OUTPUT_X86_64} ${OUTPUT_AARCH64}" get_dependency_outputs build_object $FLAGS -c "$x" LDARGS_X86_64="${LDARGS_X86_64} ${OUTPUT_X86_64}" LDARGS_AARCH64="${LDARGS_AARCH64} ${OUTPUT_AARCH64}" fi fi done if [ $INTENT != ld ]; then Exit fi OUTPUT_X86_64=$(mktemper ".com.dbg") || Exit OUTPUT_AARCH64=$(mktemper ".aarch64.elf") || Exit out2=$(mktemper .txt) || Exit TEMP_FILES="${TEMP_FILES} $out2" ( set -- \ "$CC_X86_64" \ -o"$OUTPUT_X86_64"\ $CRT_X86_64 \ $LDFLAGS_X86_64 \ $LDARGS_X86_64 \ $LDLIBS_X86_64 log_command "$@" "$@" || exit set -- \ "$BIN/fixupobj" \ "$OUTPUT_X86_64" log_command "$@" exec "$@" ) & pid1=$! ( set -- \ "$CC_AARCH64" \ -o"$OUTPUT_AARCH64"\ $CRT_AARCH64 \ $LDFLAGS_AARCH64 \ $LDARGS_AARCH64 \ $LDLIBS_AARCH64 log_command "$@" "$@" || exit set -- \ "$BIN/fixupobj" \ "$OUTPUT_AARCH64" log_command "$@" exec "$@" ) 2>"$out2" & pid2=$! if ! wait $pid1; then kill $pid2 2>/dev/null wait Exit 1 fi if ! wait $pid2; then echo "$PROG: x86_64 succeeded but aarch64 failed to link executable" >&2 cat "$out2" >&2 Exit 1 fi set -- \ "$BIN/apelink" \ -V "$SUPPORT_VECTOR" \ -l "$BIN/ape-x86_64.elf" \ -l "$BIN/ape-aarch64.elf" \ -M "$BIN/ape-m1.c" \ -o "$OUTPUT" \ $APELINKFLAGS \ "$OUTPUT_X86_64" \ "$OUTPUT_AARCH64" log_command "$@" "$@" || Exit if [ x"$MODE" != "optlinux" ]; then set -- \ "$BIN/pecheck" "$OUTPUT" log_command "$@" "$@" || Exit fi if [ $INTENT = ld ] && [ $SAVE_TEMPS -eq 0 ]; then mv -f "$OUTPUT_X86_64" "${OUTPUT%.com}.com.dbg" || Exit mv -f "$OUTPUT_AARCH64" "${OUTPUT%.com}.aarch64.elf" || Exit fi Exit