From 86d81786835bffe48bcee97e960b6d5aeeb05924 Mon Sep 17 00:00:00 2001 From: Yu-En Hsiao Date: Sun, 11 Oct 2026 11:12:57 +0800 Subject: [PATCH 1/8] Remove the DYN_BIND_NOW flag from the codebase These changes remove the use of the DYN_BIND_NOW macro from the codebase. Later commits will introduce a binding option that fully supports both lazy binding and immediate binding for all targets. --- mk/arm64.mk | 1 - mk/x64.mk | 1 - src/defs.h | 7 ------- src/elf.c | 8 -------- 4 files changed, 17 deletions(-) diff --git a/mk/arm64.mk b/mk/arm64.mk index a3a4151a..c2d96bc2 100644 --- a/mk/arm64.mk +++ b/mk/arm64.mk @@ -26,7 +26,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 98c73210..877c1ed8 100644 --- a/mk/x64.mk +++ b/mk/x64.mk @@ -17,7 +17,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 647b6799..a3be7731 100644 --- a/src/defs.h +++ b/src/defs.h @@ -192,13 +192,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 c2ff605f..34bf7b87 100644 --- a/src/elf.c +++ b/src/elf.c @@ -1112,14 +1112,6 @@ void elf_generate_dynamic_sections(void) elf_write_dyn(dynamic_sections.elf_dynamic, 0x1, 0x1); if (libdl_name) elf_write_dyn(dynamic_sections.elf_dynamic, 0x1, libdl_name); -#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 elf_write_dyn(dynamic_sections.elf_dynamic, 0x0, 0x0); } From 0577baeeac84c22bfcf99ea56aa7ccc175eb3da9 Mon Sep 17 00:00:00 2001 From: Yu-En Hsiao Date: Sun, 11 Oct 2026 16:14:04 +0800 Subject: [PATCH 2/8] Implement a PLT entry calling DL resolver for the AArch64 backend The original AArch64 backend did not generate the PLT stub that invokes _dl_runtime_resolve() so that the dynamic linking with lazy binding was not fully functional. This commit introduces the necessary PLT entry to support dynamic linking and lazy binding for AArch64. --- mk/arm64.mk | 2 +- src/arm64-codegen.c | 39 ++++++++++++++++++++++++++++++++++++--- src/arm64.c | 5 +++++ 3 files changed, 42 insertions(+), 4 deletions(-) diff --git a/mk/arm64.mk b/mk/arm64.mk index c2d96bc2..9eab2908 100644 --- a/mk/arm64.mk +++ b/mk/arm64.mk @@ -22,7 +22,7 @@ ARCH_DEFS = \ \#define DYN_LINKER \"/lib/ld-linux-aarch64.so.1\"\n$\ \#define LIBC_SO \"libc.so.6\"\n$\ \#define LIBDL_SO \"libdl.so.2\"\n$\ - \#define PLT_FIXUP_SIZE 0\n$\ + \#define PLT_FIXUP_SIZE 32\n$\ \#define PLT_ENT_SIZE 16\n$\ \#define RESERVED_GOT_NUM 3\n$\ \#define R_ARCH_JUMP_SLOT 1026 /* R_AARCH64_JUMP_SLOT */\n$\ diff --git a/src/arm64-codegen.c b/src/arm64-codegen.c index a66246d7..dcbbe435 100644 --- a/src/arm64-codegen.c +++ b/src/arm64-codegen.c @@ -650,10 +650,42 @@ void emit_ph2_ir(ph2_ir_t *p) */ void plt_generate(void) { - int entries = dynamic_sections.plt_size / PLT_ENT_SIZE; + int entries = (dynamic_sections.plt_size - PLT_FIXUP_SIZE) / PLT_ENT_SIZE; + int ent = dynamic_sections.elf_plt_start; + int got = + dynamic_sections.elf_got_start + PTR_SIZE * (RESERVED_GOT_NUM - 1); + + /* _dl_runtime_resolve() requires: + * + * - ip0: Address of got[2] (&got[2]) + * - ip1: got[2] + * - [sp]: Address of got[N + 3] (Address of the N-th function's GOT stub.) + * - [sp + 8]: Value stored in register lr + * + * PLT[0] uses the STP, ADRP, LDR, ADD and BR instructions to prepare the + * above arguments and jump to the resolver. + * + * The remaining 3 nop instructions are used to pad PLT[0] to 32 bytes, + * allowing the later sections to stay aligned. + */ + elf_write_int(dynamic_sections.elf_plt, + a64_stp_pre_insn(A64_IP0, A64_LR, A64_SP, -16)); + elf_write_int(dynamic_sections.elf_plt, + a64_adrp_insn(A64_IP0, ent + 4, got)); + elf_write_int(dynamic_sections.elf_plt, + a64_add_imm_insn(true, A64_IP0, A64_IP0, got & 0xfff)); + elf_write_int(dynamic_sections.elf_plt, + a64_mem_scaled_insn(A64_LOAD, 8, A64_IP1, A64_IP0, 0)); + elf_write_int(dynamic_sections.elf_plt, a64_br_insn(A64_IP1)); + elf_write_int(dynamic_sections.elf_plt, a64_nop_insn()); + elf_write_int(dynamic_sections.elf_plt, a64_nop_insn()); + elf_write_int(dynamic_sections.elf_plt, a64_nop_insn()); + ent += PLT_FIXUP_SIZE; for (int i = 0; i < entries; i++) { - int ent = dynamic_sections.elf_plt_start + i * PLT_ENT_SIZE; - int got = + /* Each PLT stub uses the ADRP, ADD, LDR and BR instructions to pass + * &got[N + 3] and got[N + 3] to ip0 and ip1, respectively. + */ + got = dynamic_sections.elf_got_start + PTR_SIZE * (RESERVED_GOT_NUM + i); elf_write_int(dynamic_sections.elf_plt, a64_adrp_insn(A64_IP0, ent, got)); @@ -662,6 +694,7 @@ void plt_generate(void) elf_write_int(dynamic_sections.elf_plt, a64_mem_scaled_insn(A64_LOAD, 8, A64_IP1, A64_IP0, 0)); elf_write_int(dynamic_sections.elf_plt, a64_br_insn(A64_IP1)); + ent += PLT_ENT_SIZE; } } diff --git a/src/arm64.c b/src/arm64.c index 1fca24b7..da61e83b 100644 --- a/src/arm64.c +++ b/src/arm64.c @@ -337,3 +337,8 @@ int a64_adrp_pages_insn(int rd, int pages) return 0x90000000 | ((pages & 3) << 29) | (((pages >> 2) & 0x7ffff) << 5) | rd; } + +int a64_nop_insn() +{ + return 0xd503201f; +} From 8a5c7188afa99a68ff1f0dcb383019e96460a4f9 Mon Sep 17 00:00:00 2001 From: Yu-En Hsiao Date: Sun, 11 Oct 2026 16:38:11 +0800 Subject: [PATCH 3/8] Implement immediate binding and the corresponding compiler option In the previous codebase, the compiler had implemented immediate binding for 64-bit backends using DT_BIND_NOW and DF_BIND_NOW, which are entries in the dynamic section. This commit reintroduces them and extends this binding feature to all backends. When the command-line arguments contain "-z now", the compiler sets its internal flag (imm_binding) to true and generates the necessary Elf32_Dyn/Elf64_Dyn objects so that executables can perform immediate binding at runtime. If "-z lazy" is given, it is still accepted but ignored. However, the compiler will reject compilation if the "-z" option is given in static linking mode. --- src/elf.c | 7 +++++++ src/globals.c | 1 + src/main.c | 23 ++++++++++++++++++++--- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/elf.c b/src/elf.c index 34bf7b87..b6bb6855 100644 --- a/src/elf.c +++ b/src/elf.c @@ -1112,6 +1112,13 @@ void elf_generate_dynamic_sections(void) elf_write_dyn(dynamic_sections.elf_dynamic, 0x1, 0x1); if (libdl_name) elf_write_dyn(dynamic_sections.elf_dynamic, 0x1, libdl_name); + + 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 2dad38be..3ba7e0c9 100644 --- a/src/globals.c +++ b/src/globals.c @@ -116,6 +116,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 4b3aa5d6..24fc2fe0 100644 --- a/src/main.c +++ b/src/main.c @@ -105,6 +105,7 @@ int main(int argc, char *argv[]) char *in = NULL; token_stream_t *libc_token_stream = NULL, *token_stream; token_t *tk; + bool has_binding_option = false; for (int i = 1; i < argc; i++) { if (!strcmp(argv[i], "--dump-ir")) @@ -121,7 +122,19 @@ 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\""); + + has_binding_option = true; + 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], "-I")) { if (i + 1 >= argc) @@ -147,12 +160,16 @@ int main(int argc, char *argv[]) if (!in) { printf( - "Usage: shecc [-I directory] [-o output] [+m] [--dot] [--dump-ir] " + "Usage: shecc [-I directory] [-o output] [+m] [--dot] " + "[--dump-ir] " "[--warn-string-literals] [--std=c99] [--no-libc] " - "[--dynlink] [-E] \n"); + "[--dynlink] [-z ] [-E] \n"); usage_error("Missing source file"); } + if (!dynlink && has_binding_option) + usage_error("-z cannot be used under static linking."); + /* --dot stops the pipeline at a different phase than these do. */ if (dump_dot && expand_only) usage_error("--dot cannot be combined with -E"); From ef8287dc423c1adf320ec54b3a845c203b2f3ff5 Mon Sep 17 00:00:00 2001 From: Yu-En Hsiao Date: Wed, 9 Sep 2026 21:26:17 +0800 Subject: [PATCH 4/8] 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 434ca363..cfb8e4cd 100644 --- a/Makefile +++ b/Makefile @@ -84,12 +84,13 @@ TRANSLATION_DEFS = "\#define SHECC_TRANSLATION_DATE \"$(TRANSLATION_DATE)\"\n\#d 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 7e58283516a8b93b808a5d4bf4b696d3738acc8d Mon Sep 17 00:00:00 2001 From: Yu-En Hsiao Date: Tue, 15 Sep 2026 21:53:46 +0800 Subject: [PATCH 5/8] 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 | 10 +++++----- tests/arm-abi.sh | 6 ++++-- tests/arm64-abi.sh | 4 ++-- tests/driver.sh | 5 +++-- tests/riscv-abi.sh | 6 ++++-- tests/x64-abi.sh | 6 ++++-- 6 files changed, 22 insertions(+), 15 deletions(-) diff --git a/Makefile b/Makefile index cfb8e4cd..9fbc6972 100644 --- a/Makefile +++ b/Makefile @@ -237,23 +237,23 @@ 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" $(Q)cp $(OUT)/$(STAGE0)-sanitizer $(OUT)/shecc - tests/driver.sh 0 $(DYNLINK) + tests/driver.sh 0 $(DYNLINK) $(BINDING) $(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 263ccc19..911245f7 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 9a986cb4..5ad6c546 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 db80f40f..c291c4bf 100755 --- a/tests/driver.sh +++ b/tests/driver.sh @@ -72,9 +72,10 @@ PROGRESS_COUNT=0 # 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" @@ -106,7 +107,7 @@ case "$1" in esac if [ $# -ge 2 ] && [ "$2" = "1" ]; then - readonly SHECC_CFLAGS="--dynlink" + readonly SHECC_CFLAGS="--dynlink -z ${3:-lazy}" readonly LINK_MODE="dynamic" else readonly SHECC_CFLAGS="" diff --git a/tests/riscv-abi.sh b/tests/riscv-abi.sh index 34c3aadb..1ea3ca51 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 cf91f280..12e75fc2 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" From 72808a4507d238e6a480391ee893ab9b8bd8cbe1 Mon Sep 17 00:00:00 2001 From: Yu-En Hsiao Date: Sun, 20 Sep 2026 21:47:57 +0800 Subject: [PATCH 6/8] Validate immediate binding mode in CI Since the test scripts can validate either lazy binding or immediate binding via an additional argument, the CI workflows are improved so that dynamic linking with immediate binding can be validated on the all architectures. Primary changes and considerations: - Add an additional 'binding_mode' parameter so that the GitHub Actions workflows can verify immediate binding. - When CI tests the static linking mode, the 'binding_mode' parameter is 'none' because static linking involves neither lazy nor immediate binding. --- .github/workflows/main.yml | 108 ++++++++++++++++++++++++++++++------- 1 file changed, 88 insertions(+), 20 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 81a7c242..e5b22a57 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -21,7 +21,7 @@ jobs: # Bootstrap each target and run the consolidated behavioral suite against # the stage 0 and stage 2 compilers. host-x86: - name: ${{ matrix.architecture }}/${{ matrix.link_mode }} (${{ matrix.compiler }}) + name: ${{ matrix.architecture }}/${{ matrix.link_mode }}/${{ matrix.binding_mode }} (${{ matrix.compiler }}) runs-on: ubuntu-24.04 timeout-minutes: 30 strategy: @@ -31,11 +31,47 @@ jobs: matrix: compiler: [gcc, clang] architecture: [arm, arm64, riscv, x64] - link_mode: [static, dynamic] + link_mode: [dynamic] + binding_mode: [lazy, immediate] + include: + - compiler: gcc + architecture: arm + link_mode: static + binding_mode: none + - compiler: clang + architecture: arm + link_mode: static + binding_mode: none + - compiler: gcc + architecture: riscv + link_mode: static + binding_mode: none + - compiler: clang + architecture: riscv + link_mode: static + binding_mode: none + - compiler: gcc + architecture: arm64 + link_mode: static + binding_mode: none + - compiler: clang + architecture: arm64 + link_mode: static + binding_mode: none + - compiler: gcc + architecture: x64 + link_mode: static + binding_mode: none + - compiler: clang + architecture: x64 + link_mode: static + binding_mode: none env: CC: ${{ matrix.compiler }} ARCH: ${{ matrix.architecture }} DYNLINK: ${{ matrix.link_mode == 'dynamic' && '1' || '0' }} + # This variable is ignored when verifying static linking mode + BINDING: ${{ matrix.binding_mode == 'immediate' && 'now' || 'lazy' }} steps: - name: Checkout code uses: actions/checkout@v7 @@ -45,18 +81,18 @@ jobs: architecture: ${{ matrix.architecture }} link-mode: ${{ matrix.link_mode }} github-token: ${{ github.token }} - # ARCH and DYNLINK are named on the command line rather than left to the - # environment: the build session the tree records assigns ARCH outright, + # ARCH, DYNLINK and BINDING are named on the command line rather than left + # to the environment: the build session the tree records assigns ARCH outright, # and a makefile assignment wins over the environment. - name: Build artifacts - run: make ARCH="$ARCH" DYNLINK="$DYNLINK" + run: make ARCH="$ARCH" DYNLINK="$DYNLINK" BINDING="$BINDING" - name: Unit tests - run: make check ARCH="$ARCH" DYNLINK="$DYNLINK" + run: make check ARCH="$ARCH" DYNLINK="$DYNLINK" BINDING="$BINDING" - name: Upload the test logs if: failure() uses: actions/upload-artifact@v7 with: - name: logs-${{ matrix.compiler }}-${{ matrix.architecture }}-${{ matrix.link_mode }} + name: logs-${{ matrix.compiler }}-${{ matrix.architecture }}-${{ matrix.link_mode }}-${{ matrix.binding_mode }} path: | out/*.log if-no-files-found: ignore @@ -68,7 +104,7 @@ jobs: # sanitizer step of host-x86 used to do. Building "sanitizer" first, in a # job that builds nothing else, is what puts the instrumentation in. sanitizer: - name: Sanitizers ${{ matrix.architecture }}/${{ matrix.link_mode }} + name: Sanitizers ${{ matrix.architecture }}/${{ matrix.link_mode }}/${{ matrix.binding_mode }} runs-on: ubuntu-24.04 timeout-minutes: 30 strategy: @@ -81,10 +117,26 @@ jobs: # roughly fifteen hours. Restoring the dimension means finding out # why first. architecture: [arm, arm64, riscv, x64] - link_mode: [static, dynamic] + link_mode: [dynamic] + binding_mode: [lazy, immediate] + include: + - architecture: arm + link_mode: static + binding_mode: none + - architecture: riscv + link_mode: static + binding_mode: none + - architecture: arm64 + link_mode: static + binding_mode: none + - architecture: x64 + link_mode: static + binding_mode: none env: ARCH: ${{ matrix.architecture }} DYNLINK: ${{ matrix.link_mode == 'dynamic' && '1' || '0' }} + # This variable is ignored when verifying static linking mode + BINDING: ${{ matrix.binding_mode == 'immediate' && 'now' || 'lazy' }} steps: - name: Checkout code uses: actions/checkout@v7 @@ -95,9 +147,9 @@ jobs: link-mode: ${{ matrix.link_mode }} github-token: ${{ github.token }} - name: Build the stage 0 compiler with sanitizers - run: make sanitizer ARCH="$ARCH" DYNLINK="$DYNLINK" + run: make sanitizer ARCH="$ARCH" DYNLINK="$DYNLINK" BINDING="$BINDING" - name: Sanitizer-enabled stage 0 tests - run: make check-sanitizer ARCH="$ARCH" DYNLINK="$DYNLINK" + run: make check-sanitizer ARCH="$ARCH" DYNLINK="$DYNLINK" BINDING="$BINDING" # Preprocess shecc with itself, then compile the result: the stage 1 source # is the largest input the preprocessor gets, and the only one that exercises @@ -135,17 +187,27 @@ jobs: # AArch64 that is the only place the real loader is exercised: QEMU-user maps # the image itself, and on an x86-64 host it always presents 4 KiB pages. host-arm: - name: ${{ matrix.architecture }}/${{ matrix.link_mode }} on Arm64 + name: ${{ matrix.architecture }}/${{ matrix.link_mode }}/${{ matrix.binding_mode }} on Arm64 runs-on: ubuntu-24.04-arm timeout-minutes: 30 strategy: fail-fast: false matrix: architecture: [arm, arm64] - link_mode: [static, dynamic] + link_mode: [dynamic] + binding_mode: [lazy, immediate] + include: + - architecture: arm + link_mode: static + binding_mode: none + - architecture: arm64 + link_mode: static + binding_mode: none env: ARCH: ${{ matrix.architecture }} DYNLINK: ${{ matrix.link_mode == 'dynamic' && '1' || '0' }} + # This variable is ignored when verifying static linking mode + BINDING: ${{ matrix.binding_mode == 'immediate' && 'now' || 'lazy' }} steps: - name: Checkout code uses: actions/checkout@v7 @@ -156,14 +218,14 @@ jobs: link-mode: ${{ matrix.link_mode }} github-token: ${{ github.token }} - name: Build artifacts - run: make ARCH="$ARCH" DYNLINK="$DYNLINK" + run: make ARCH="$ARCH" DYNLINK="$DYNLINK" BINDING="$BINDING" - name: Unit tests - run: make check ARCH="$ARCH" DYNLINK="$DYNLINK" + run: make check ARCH="$ARCH" DYNLINK="$DYNLINK" BINDING="$BINDING" - name: Upload the test logs if: failure() uses: actions/upload-artifact@v7 with: - name: logs-arm64-host-${{ matrix.architecture }}-${{ matrix.link_mode }} + name: logs-arm64-host-${{ matrix.architecture }}-${{ matrix.link_mode }}-${{ matrix.binding_mode }} path: | out/*.log out/tests/*.log @@ -174,15 +236,21 @@ jobs: # build that produces the object files, and that job has already made them # without instrumentation. host-arm-sanitizer: - name: Sanitizers arm/${{ matrix.link_mode }} on Arm64 + name: Sanitizers arm/${{ matrix.link_mode }}/${{ matrix.binding_mode }} on Arm64 runs-on: ubuntu-24.04-arm timeout-minutes: 30 strategy: fail-fast: false matrix: - link_mode: [static, dynamic] + link_mode: [dynamic] + binding_mode: [lazy, immediate] + include: + - link_mode: static + binding_mode: none env: DYNLINK: ${{ matrix.link_mode == 'dynamic' && '1' || '0' }} + # This variable is ignored when verifying static linking mode + BINDING: ${{ matrix.binding_mode == 'immediate' && 'now' || 'lazy' }} steps: - name: Checkout code uses: actions/checkout@v7 @@ -193,9 +261,9 @@ jobs: link-mode: ${{ matrix.link_mode }} github-token: ${{ github.token }} - name: Build the stage 0 compiler with sanitizers - run: make sanitizer ARCH=arm DYNLINK="$DYNLINK" + run: make sanitizer ARCH=arm DYNLINK="$DYNLINK" BINDING="$BINDING" - name: Sanitizer-enabled stage 0 tests - run: make check-sanitizer ARCH=arm DYNLINK="$DYNLINK" + run: make check-sanitizer ARCH=arm DYNLINK="$DYNLINK" BINDING="$BINDING" coding-style: name: Coding style From d5be4bcfb132d1c1aee7641570f78adb4ad14969 Mon Sep 17 00:00:00 2001 From: Yu-En Hsiao Date: Sun, 11 Oct 2026 19:55:33 +0800 Subject: [PATCH 7/8] Document the usage of the binding option Since the '-z' option has been implemented to enable lazy binding or immediate binding, the README has been revised to document the usage of this new compiler option and the new 'BINDING' variable in the build system. --- README.md | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index bb7147a4..8f7bc617 100644 --- a/README.md +++ b/README.md @@ -164,6 +164,20 @@ out/shecc-stage2.elf: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dy out/shecc-stage2.elf: ELF 64-bit LSB executable, ARM aarch64, dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, no section header ``` +When using `DYNLINK=1`, adding `BINDING=now` can also enable the use of **immediate binding**. +```shell +# Dynamic linking with lazy binding +$ make DYNLINK=1 +$ make DYNLINK=1 BINDING=lazy + +# Dynamic linking with immediate binding +$ make DYNLINK=1 BINDING=now + +# BINDING is ignored by the build system if the static linking mode is enabled. +$ make BINDING=now +$ make DYNLINK=0 BINDING=lazy +``` + For development builds with memory safety checks: ```shell $ make sanitizer @@ -172,7 +186,7 @@ $ make check-sanitizer File `out/shecc` is the first stage compiler. Its usage: ```shell -$ shecc [-o output] [+m] [--dot] [--no-libc] [--dump-ir] [--dynlink] [-E] +$ shecc [-o output] [+m] [--dot] [--no-libc] [--dump-ir] [--dynlink] [-z ] [-E] ``` Compiler options: @@ -182,6 +196,9 @@ Compiler options: - `--no-libc` : Exclude embedded C library (default: embedded) - `--dump-ir` : Dump intermediate representation (IR) - `--dynlink` : Use dynamic linking (default: disabled) +- `-z`: Use lazy binding or immediate binding (default: lazy) + - This option is valid only when `--dynlink` is also enabled. Otherwise, the compiler + reports an error message if this option is used with static linking. - `-E` : Preprocess only; write the expanded token stream and stop Example 1: static linking mode From 9fbbe1be4e8ea69c7dbecf94f5eef19a0780e7a9 Mon Sep 17 00:00:00 2001 From: Yu-En Hsiao Date: Sun, 11 Oct 2026 20:09:16 +0800 Subject: [PATCH 8/8] Improve the dynamic linking documentation The '-z' option has been added to enable lazy binding or immediate binding. The dynamic linking document is updated to revise the following descriptions: - Document the usage of the '-z' compiler option and the 'BINDING' variable in the build system. - The original document states that 'DT_BIND_NOW' has not yet been implemented. Remove this statement because this dynamic tag has been introduced. --- docs/dynamic-linking.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/dynamic-linking.md b/docs/dynamic-linking.md index a9f1c283..f1acd0a0 100644 --- a/docs/dynamic-linking.md +++ b/docs/dynamic-linking.md @@ -5,8 +5,13 @@ Build the dynamically linked version of shecc: ```shell +# The 'DYNLINK' variable can control the use of dynamic linking mode $ make ARCH=arm DYNLINK=1 $ make ARCH=riscv DYNLINK=1 + +# Enable immediate binding using the 'BINDING' variable (default: lazy) +$ make ARCH=arm DYNLINK=1 BINDING=now +$ make ARCH=riscv DYNLINK=1 BINDING=now ``` Next, you can use shecc to build dynamically linked programs by adding the `--dynlink` flag: @@ -17,6 +22,9 @@ $ out/shecc --dynlink -o # Use the stage 1 or stage 2 compiler $ qemu-arm -L out/shecc-stage2.elf --dynlink -o $ qemu-riscv32 -L out/shecc-stage2.elf --dynlink -o +# Enable immediate binding by the '-z now' option +$ qemu-arm -L out/shecc-stage2.elf --dynlink -z now -o +$ qemu-riscv32 -L out/shecc-stage2.elf --dynlink -z now -o # Execute the compiled program $ qemu-arm -L @@ -331,7 +339,6 @@ For the current implementation of dynamic linking, note the following: * GOT is located in a writable segment (`.data` segment). * The `PT_GNU_RELRO` program header has not yet been implemented. -* `DT_BIND_NOW` (force immediate binding) is not set. This implies that: