From b66e716ad80fad70625f8c7f4bc1a4949fb933d7 Mon Sep 17 00:00:00 2001 From: Yu-En Hsiao Date: Wed, 9 Sep 2026 21:08:31 +0800 Subject: [PATCH 1/3] Implement immediate binding for dynamic linking Since the original codebase already implemented the immediate binding flag for the x86-64 and AArch64 architectures, this commit introduces a "-z" option so that the Arm32 and RV32 targets can also use immediate binding. When the command-line arguments contain "-z now", the compiler sets its internal flag (imm_binding) to true and generates the Elf32_Dyn/Elf64_Dyn objects to enable exectuables to perform immediate binding at runtime. For 64-bit targets, immediate binding is always used under the dynamic linking mode as is. --- mk/arm64.mk | 1 - mk/x64.mk | 1 - src/defs.h | 7 ------- src/elf.c | 11 +++++++---- src/globals.c | 1 + src/main.c | 26 ++++++++++++++++++++++++-- 6 files changed, 32 insertions(+), 15 deletions(-) diff --git a/mk/arm64.mk b/mk/arm64.mk index c6b402b2..cb60023d 100644 --- a/mk/arm64.mk +++ b/mk/arm64.mk @@ -25,7 +25,6 @@ ARCH_DEFS = \ \#define PLT_ENT_SIZE 16\n$\ \#define RESERVED_GOT_NUM 3\n$\ \#define R_ARCH_JUMP_SLOT 1026 /* R_AARCH64_JUMP_SLOT */\n$\ - \#define DYN_BIND_NOW 1\n$\ " # An Arm64 Linux host runs this target's output itself, so nothing has to stand diff --git a/mk/x64.mk b/mk/x64.mk index 85ed95a8..cff726ac 100644 --- a/mk/x64.mk +++ b/mk/x64.mk @@ -16,7 +16,6 @@ ARCH_DEFS = \ \#define RESERVED_GOT_NUM 3\n$\ \#define R_ARCH_JUMP_SLOT 7 /* R_X86_64_JUMP_SLOT */\n$\ \#define REG_CNT 11 /* rdi rsi rdx rcx r8 r9 rax rbx r14 r12 r13 */\n$\ - \#define DYN_BIND_NOW 1 /* this PLT has no lazy-resolution path */\n$\ \#define HAVE_COND_MOVE 1 /* CMOVcc */\n$\ \#define CALLEE_SAVED_REGS 4 /* the file ends rbx r14 r12 r13 */\n$\ " diff --git a/src/defs.h b/src/defs.h index 3534f2fe..3580c8a2 100644 --- a/src/defs.h +++ b/src/defs.h @@ -177,13 +177,6 @@ #define ALIGN_UP(val, align) (((val) + (align) - 1) & ~((align) - 1)) #endif -/* Targets whose PLT has no lazy-resolution path ask the loader to bind every - * PLT entry at load time. - */ -#ifndef DYN_BIND_NOW -#define DYN_BIND_NOW 0 -#endif - #define ELF_MACHINE_ARM32 0x28 #define ELF_MACHINE_RV32 0xf3 #define ELF_MACHINE_X86_64 0x3e diff --git a/src/elf.c b/src/elf.c index 66ec0e34..316d8e69 100644 --- a/src/elf.c +++ b/src/elf.c @@ -1086,14 +1086,17 @@ void elf_generate_dynamic_sections(void) elf_write_dyn(dynamic_sections.elf_dynamic, 0x3, dynamic_sections.elf_got_start); elf_write_dyn(dynamic_sections.elf_dynamic, 0x1, 0x1); -#if DYN_BIND_NOW == 1 + /* Resolve every PLT entry at load time. This target's PLT[0] does not * arrange the GOT[1]/GOT[2] hand-off the lazy resolver needs, so the loader * writes the final addresses straight into the GOT instead. */ - elf_write_dyn(dynamic_sections.elf_dynamic, 0x18, 0x0); /* DT_BIND_NOW */ - elf_write_dyn(dynamic_sections.elf_dynamic, 0x1e, 0x8); /* DF_BIND_NOW */ -#endif + if (imm_binding) { + elf_write_dyn(dynamic_sections.elf_dynamic, 0x18, + 0x0); /* DT_BIND_NOW */ + elf_write_dyn(dynamic_sections.elf_dynamic, 0x1e, + 0x8); /* DF_BIND_NOW */ + } elf_write_dyn(dynamic_sections.elf_dynamic, 0x0, 0x0); } diff --git a/src/globals.c b/src/globals.c index dd89767b..147aa133 100644 --- a/src/globals.c +++ b/src/globals.c @@ -101,6 +101,7 @@ dynamic_sections_t dynamic_sections; /* Command line compilation flags */ bool dynlink = false; +bool imm_binding = false; bool libc = true; bool expand_only = false; bool dump_ir = false; diff --git a/src/main.c b/src/main.c index 1ccd5134..84afdcf7 100644 --- a/src/main.c +++ b/src/main.c @@ -117,7 +117,18 @@ int main(int argc, char *argv[]) libc = false; else if (!strcmp(argv[i], "--dynlink")) dynlink = true; - else if (!strcmp(argv[i], "-E")) + else if (!strcmp(argv[i], "-z")) { + if (i + 1 >= argc) + usage_error("-z requires \"lazy\" or \"now\""); + + if (!strcmp(argv[i + 1], "lazy")) + imm_binding = false; + else if (!strcmp(argv[i + 1], "now")) + imm_binding = true; + else + usage_error("-z requires \"lazy\" or \"now\""); + i++; + } else if (!strcmp(argv[i], "-E")) expand_only = true; else if (!strcmp(argv[i], "-o")) { if (i + 1 < argc) { @@ -131,10 +142,21 @@ int main(int argc, char *argv[]) in = argv[i]; } + if (dynlink) { + switch (ELF_MACHINE) { + /* The following 64-bit targets have no lazy-resolution path, so + * immediate binding must be used. + */ + case ELF_MACHINE_X86_64: + case ELF_MACHINE_AARCH64: + imm_binding = true; + } + } + if (!in) { printf( "Usage: shecc [-o output] [+m] [--dot] [--dump-ir] [--no-libc] " - "[--dynlink] [-E] \n"); + "[--dynlink] [-z ] [-E] \n"); usage_error("Missing source file"); } From 6349b32b40c65134f0e6d06d2486538f66d6d0b9 Mon Sep 17 00:00:00 2001 From: Yu-En Hsiao Date: Wed, 9 Sep 2026 21:26:17 +0800 Subject: [PATCH 2/3] Introduce a Makefile variable to determine the binding strategy Since the compiler supports the "-z" option for lazy or immediate binding, a new "BINDING" variable has benn added to the Makefile so that users can choose which binding mode to use. For example, users can build dynamically linked compilers with immediate binding as follows: $ make DYNLINK=1 BINDING=now --- Makefile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 6661e623..73dc8026 100644 --- a/Makefile +++ b/Makefile @@ -53,12 +53,13 @@ BUILTIN_LIBC_HEADER := c.h STAGE0_FLAGS ?= --dump-ir STAGE1_FLAGS ?= DYNLINK ?= 0 +BINDING ?= lazy COMMENTFLOW ?= commentflow SHFMT ?= shfmt ifeq ($(DYNLINK),1) - STAGE0_FLAGS += --dynlink - STAGE1_FLAGS += --dynlink + STAGE0_FLAGS += --dynlink -z $(BINDING) + STAGE1_FLAGS += --dynlink -z $(BINDING) endif SRCS := $(wildcard $(patsubst %,%/main.c, $(SRCDIR))) From 3c3998dbcb8a46c38987958314ae9ec19ccefd27 Mon Sep 17 00:00:00 2001 From: Yu-En Hsiao Date: Tue, 15 Sep 2026 21:53:46 +0800 Subject: [PATCH 3/3] Improve test suites to validate immediate binding mode Since the compiler has implemented dynamic linking with immediate binding, the test suites have been enhance to validate immediate binding mode. The approach is to add an additional command-line argument to determine which mode, lazy or immediate, is enabled during the tests. --- Makefile | 8 ++++---- tests/arm-abi.sh | 6 ++++-- tests/arm64-abi.sh | 4 ++-- tests/driver.sh | 2 +- tests/riscv-abi.sh | 6 ++++-- tests/x64-abi.sh | 6 ++++-- 6 files changed, 19 insertions(+), 13 deletions(-) diff --git a/Makefile b/Makefile index 73dc8026..615a5dab 100644 --- a/Makefile +++ b/Makefile @@ -184,11 +184,11 @@ uninstall-hooks: check-stage0: $(OUT)/$(STAGE0) tests/driver.sh $(VECHO) " TEST STAGE 0\n" - tests/driver.sh 0 $(DYNLINK) + tests/driver.sh 0 $(DYNLINK) $(BINDING) check-stage2: $(OUT)/$(STAGE2) tests/driver.sh $(VECHO) " TEST STAGE 2\n" - tests/driver.sh 2 $(DYNLINK) + tests/driver.sh 2 $(DYNLINK) $(BINDING) check-sanitizer: $(OUT)/$(STAGE0)-sanitizer tests/driver.sh $(VECHO) " TEST STAGE 0 (with sanitizers)\n" @@ -197,10 +197,10 @@ check-sanitizer: $(OUT)/$(STAGE0)-sanitizer tests/driver.sh $(Q)rm $(OUT)/shecc check-abi-stage0: $(OUT)/$(STAGE0) - tests/$(ARCH)-abi.sh 0 $(DYNLINK); + tests/$(ARCH)-abi.sh 0 $(DYNLINK) $(BINDING); check-abi-stage2: $(OUT)/$(STAGE2) - tests/$(ARCH)-abi.sh 2 $(DYNLINK); + tests/$(ARCH)-abi.sh 2 $(DYNLINK) $(BINDING); # Both prerequisites are order-only, and both exist because "make -j" would # otherwise let a compile start beside the thing it reads. Selecting a target diff --git a/tests/arm-abi.sh b/tests/arm-abi.sh index fd1b2771..cf48ad70 100755 --- a/tests/arm-abi.sh +++ b/tests/arm-abi.sh @@ -41,9 +41,10 @@ fi # Command Line Arguments if [ "$#" -lt 1 ]; then - echo "Usage: $0 []" + echo "Usage: $0 [ []]" echo " stage: 0 (host compiler), 1 (stage1), or 2 (stage2)" echo " dynlink: 0 (static linking), 1 (dynamic linking)" + echo " binding: lazy, now" echo "" echo "Environment Variables:" echo " VERBOSE=1 Enable verbose output" @@ -73,6 +74,7 @@ case "$1" in esac DYNLINK="${2:-0}" +BINDING="${3:-lazy}" # Banner echo -e "${BLUE}${BOLD}========================================${NC}" @@ -144,7 +146,7 @@ run_abi_test() # Compile local compile_cmd="$SHECC" if [[ "$DYNLINK" == "1" ]]; then - compile_cmd="$compile_cmd --dynlink" + compile_cmd="$compile_cmd --dynlink -z $BINDING" fi compile_cmd="$compile_cmd -o /tmp/shecc_abi_test_$$.elf $test_file" diff --git a/tests/arm64-abi.sh b/tests/arm64-abi.sh index 02a552fb..38989e9a 100755 --- a/tests/arm64-abi.sh +++ b/tests/arm64-abi.sh @@ -4,7 +4,7 @@ set -eu if [ "$#" -lt 1 ]; then - echo "Usage: $0 []" >&2 + echo "Usage: $0 [ []]" >&2 exit 2 fi @@ -23,7 +23,7 @@ case "$1" in esac if [ "${2:-0}" = 1 ]; then - shecc+=(--dynlink) + shecc+=(--dynlink -z "${3:-lazy}") link_mode=dynamic else link_mode=static diff --git a/tests/driver.sh b/tests/driver.sh index 0f3afd66..7a0d42d8 100755 --- a/tests/driver.sh +++ b/tests/driver.sh @@ -106,7 +106,7 @@ case "$1" in esac if [ $# -ge 2 ] && [ "$2" = "1" ]; then - readonly SHECC_CFLAGS="--dynlink" + readonly SHECC_CFLAGS="--dynlink ${3:-lazy}" readonly LINK_MODE="dynamic" else readonly SHECC_CFLAGS="" diff --git a/tests/riscv-abi.sh b/tests/riscv-abi.sh index 2ec4b338..98a271f8 100755 --- a/tests/riscv-abi.sh +++ b/tests/riscv-abi.sh @@ -41,9 +41,10 @@ fi # Command Line Arguments if [ "$#" -lt 1 ]; then - echo "Usage: $0 []" + echo "Usage: $0 [ []]" echo " stage: 0 (host compiler), 1 (stage1), or 2 (stage2)" echo " dynlink: 0 (static linking), 1 (dynamic linking)" + echo " binding: lazy, now" echo "" echo "Environment Variables:" echo " VERBOSE=1 Enable verbose output" @@ -73,6 +74,7 @@ case "$1" in esac DYNLINK="${2:-0}" +BINDING="${3:-lazy}" # Banner echo -e "${BLUE}${BOLD}========================================${NC}" @@ -144,7 +146,7 @@ run_abi_test() # Compile local compile_cmd="$SHECC" if [[ "$DYNLINK" == "1" ]]; then - compile_cmd="$compile_cmd --dynlink" + compile_cmd="$compile_cmd --dynlink -z $BINDING" fi compile_cmd="$compile_cmd -o /tmp/shecc_abi_test_$$.elf $test_file" diff --git a/tests/x64-abi.sh b/tests/x64-abi.sh index 9bcbdbbd..da91fbca 100755 --- a/tests/x64-abi.sh +++ b/tests/x64-abi.sh @@ -42,9 +42,10 @@ fi # Command Line Arguments if [ "$#" -lt 1 ]; then - echo "Usage: $0 []" + echo "Usage: $0 [ []]" echo " stage: 0 (host compiler), 1 (stage1), or 2 (stage2)" echo " dynlink: 0 (static linking), 1 (dynamic linking)" + echo " binding: lazy, now" echo "" echo "Environment Variables:" echo " VERBOSE=1 Enable verbose output" @@ -74,6 +75,7 @@ case "$1" in esac DYNLINK="${2:-0}" +BINDING="${3:-lazy}" # Banner echo -e "${BLUE}${BOLD}========================================${NC}" @@ -149,7 +151,7 @@ run_abi_test() # Compile local compile_cmd="$SHECC" if [[ "$DYNLINK" == "1" ]]; then - compile_cmd="$compile_cmd --dynlink" + compile_cmd="$compile_cmd --dynlink -z $BINDING" fi compile_cmd="$compile_cmd -o /tmp/shecc_abi_test_$$.elf $test_file"