Skip to content

Support immediate binding option - #335

Open
DrXiao wants to merge 4 commits into
sysprog21:masterfrom
DrXiao:support-immediate-binding
Open

DrXiao wants to merge 4 commits into
sysprog21:masterfrom
DrXiao:support-immediate-binding

Conversation

@DrXiao

@DrXiao DrXiao commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator

Since the immediate binding has already been implemented for the x86-64 and AArch64 architectures, the proposed changes extend its use by introducing a new "-z" command-line option, allowing the Arm32 and RV32 targets to also generate dynamically linked executables with immediate binding.

For the two 64-bit targets, immediate binding is always used under the dynamic linking mode as is.

The update usage:

# Support the "-z" option
$ qemu-arm -L /usr/arm-linux-gnueabihf/ out/shecc-stage2.elf
Usage: shecc [-o output] [+m] [--dot] [--dump-ir] [--no-libc] [--dynlink] [-z <lazy | now>] [-E] <input.c>
[Error]: Missing source file

# The build system can use the new 'BINDING' variable to determine whether to use immediate binding.
$ make ARCH=arm DYNLINK=1 BINDING=now

TODO:

  • Update the GitHub Actions workflow to validate builds when using immediate binding.

Summary by cubic

Adds a -z <lazy|now> option (default lazy) so Arm32 and RISC-V dynamic executables can use immediate binding, matching x86-64 and AArch64.

  • x86-64 and AArch64 still force now in dynamic mode, so -z lazy is ignored there.
  • The Makefile exposes the option as BINDING and passes it to builds, driver tests, and ABI tests.
  • ABI test scripts pass -z; tests/driver.sh doesn't, so the binding value reaches shecc as an extra source file and driver-based dynamic runs fail.
  • CI adds immediate-binding matrix entries for Arm32 and RISC-V; the host-arm Arm32 entry misspells immediate as immedaite, so that job still runs lazy.

Written for commit c4fbf69. Summary will update on new commits.

Review in cubic

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 7 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread Makefile
Comment thread src/main.c
@DrXiao
DrXiao force-pushed the support-immediate-binding branch from 0d1e98b to 3c3998d Compare September 15, 2026 14:02

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 7 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="tests/riscv-abi.sh">

<violation number="1" location="tests/riscv-abi.sh:149">
P2: An unvalidated `binding` argument is inserted into a command string executed by `eval`, so invoking this test script with a crafted third argument executes arbitrary shell commands. Validate the value against `lazy|now` or shell-escape it before constructing the command.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread tests/riscv-abi.sh
Comment thread tests/arm-abi.sh
jserv

This comment was marked as resolved.

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.
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
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.
@DrXiao
DrXiao force-pushed the support-immediate-binding branch 4 times, most recently from c0c2484 to e75d599 Compare September 20, 2026 14:03
# the stage 0 and stage 2 compilers.
host-x86:
name: ${{ matrix.architecture }}/${{ matrix.link_mode }} (${{ matrix.compiler }})
name: ${{ matrix.architecture }}/${{ matrix.link_mode }}/${{ matrix.link_mode == 'dynamic' && (matrix.binding_mode == 'immediate' && 'immediate' || 'default') || 'none' }} (${{ matrix.compiler }})

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Naming:

  • static linking mode: <arch>/static/none.
  • dynamic linking mode: <arch>/dynamic/<default or immediate>

The default binding mode for each architecture is as follows:

  • Arm32: lazy binding
  • RISC-V: lazy binding
  • x64: immediate binding
  • Arm64: immediate binding

@DrXiao

DrXiao commented Sep 20, 2026

Copy link
Copy Markdown
Collaborator Author
  • Static linking mode does not involve lazy or immediate binding.
  • Only the Arm32 and RISC-V architectures can test both lazy binding and immediate binding modes.
  • x64 and Arm64 always use immediate binding.

These conditions make the workflow modifications somewhat complex.

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
Arm32 and RISC-V architectures.

Primary changes and considerations:
- Add an additional 'binding_mode' parameter so that the GitHub Actions
  workflows cab verify immediate binding for Arm32 and RISC-V.
- When CI tests the static linking mode, the 'binding_mode' parameter is
  ignored because static linking involves neither lazy nor immediate
  binding.
- The x86-64 and Arm64 architectures use immediate binding by default,
  so neither of them tests lazy binding.
@DrXiao
DrXiao force-pushed the support-immediate-binding branch from e75d599 to c4fbf69 Compare September 20, 2026 14:37
compiler: [gcc, clang]
architecture: [arm, arm64, riscv, x64]
link_mode: [static, dynamic]
include:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An include entry whose keys all match an existing combination is merged into that combination instead of creating a new job, so these four entries convert the existing arm/dynamic and riscv/dynamic jobs into immediate-binding runs rather than adding jobs. The default lazy path then has no dynamic coverage on Arm32 or RV32 at all, and the include blocks in sanitizer, host-arm and host-arm-sanitizer behave the same way. Adding binding_mode: [lazy] to the matrix makes the immediate entries conflict, so they expand into separate jobs and the lazy jobs survive.

include:
- architecture: arm
link_mode: dynamic
binding_mode: immedaite

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

immedaite never matches the matrix.binding_mode == 'immediate' test that computes BINDING, so this entry merges into the existing arm/dynamic job and leaves it on lazy binding. The native Arm64 runner is the only place a real loader resolves these relocations, so immediate binding ends up untested where it matters most.

Suggested change
binding_mode: immedaite
binding_mode: immediate

Comment thread Makefile
check-stage2: $(OUT)/$(STAGE2) tests/driver.sh
$(VECHO) " TEST STAGE 2\n"
tests/driver.sh 2 $(DYNLINK)
tests/driver.sh 2 $(DYNLINK) $(BINDING)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

check-stage0 and check-stage2 forward $(BINDING), but check-sanitizer below still calls tests/driver.sh 0 $(DYNLINK), so the sanitizer jobs that set BINDING=now run the suite with lazy binding. Pass $(BINDING) there as well.

Comment thread src/elf.c
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) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment above this branch still states the old compile-time rule. On Arm32 and RV32 the PLT does arrange the GOT[1]/GOT[2] hand-off, and these entries are emitted because the user asked for -z now, not because lazy resolution is impossible. Reword it to say the binding mode is selected by imm_binding and forced on the targets whose PLT has no lazy path.

Comment thread src/main.c
*/
case ELF_MACHINE_X86_64:
case ELF_MACHINE_AARCH64:
imm_binding = true;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An explicit -z lazy is parsed, accepted, and then silently overridden here, so the user gets immediate binding with no diagnostic, unlike the other incompatible option pairs that usage_error rejects. -z without --dynlink is likewise accepted and does nothing. Recording whether -z was given, then rejecting -z lazy on these targets and -z without --dynlink, keeps the option contract honest.

Comment thread src/main.c
"Usage: shecc [-I directory] [-o output] [+m] [--dot] [--dump-ir] "
"[--warn-string-literals] [--std=c99] [--no-libc] "
"[--dynlink] [-E] <input.c>\n");
"[--dynlink] [-z <lazy | now>] [-E] <input.c>\n");

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The usage string gains -z, but the usage line and option list in README.md still document the old set, and docs/dynamic-linking.md still lists "DT_BIND_NOW (force immediate binding) is not set" under Limitations, which is wrong for every target once this lands. Both belong in this change.

Comment thread tests/driver.sh

if [ $# -ge 2 ] && [ "$2" = "1" ]; then
readonly SHECC_CFLAGS="--dynlink"
readonly SHECC_CFLAGS="--dynlink ${3:-lazy}"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SHECC_CFLAGS gets the binding mode without the -z that introduces it, so shecc sees a bare lazy or now operand. That operand is parsed as an input file and then overwritten by the real source that follows it, so make check BINDING=now runs the whole driver suite with lazy binding and reports success.

Suggested change
readonly SHECC_CFLAGS="--dynlink ${3:-lazy}"
readonly SHECC_CFLAGS="--dynlink -z ${3:-lazy}"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants