diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a8e2ef9d..40d69e9f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -9,14 +9,7 @@ jobs: matrix: compiler: [gcc, clang] architecture: [arm, riscv] - link_mode: [static] - include: - - compiler: gcc - architecture: arm - link_mode: dynamic - - compiler: clang - architecture: arm - link_mode: dynamic + link_mode: [static, dynamic] steps: - name: Checkout code uses: actions/checkout@v4 @@ -27,6 +20,9 @@ jobs: sudo apt-get install -q -y qemu-user sudo apt-get install -q -y build-essential sudo apt-get install -q -y gcc-arm-linux-gnueabihf + sudo wget -q https://github.com/riscv-collab/riscv-gnu-toolchain/releases/download/2026.04.05/riscv32-glibc-ubuntu-24.04-gcc.tar.xz + sudo tar Jxf riscv32-glibc-ubuntu-24.04-gcc.tar.xz -C /opt + echo "/opt/riscv/bin" >> "$GITHUB_PATH" - name: Determine static or dynamic linking mode id: determine-mode run: | diff --git a/Makefile b/Makefile index f79d6650..8859d632 100644 --- a/Makefile +++ b/Makefile @@ -49,10 +49,6 @@ STAGE0_FLAGS ?= --dump-ir STAGE1_FLAGS ?= DYNLINK ?= 0 ifeq ($(DYNLINK),1) - ifeq ($(ARCH),riscv) - # TODO: implement dynamic linking for RISC-V. - $(error "Dynamic linking mode is not implemented for RISC-V") - endif STAGE0_FLAGS += --dynlink STAGE1_FLAGS += --dynlink endif @@ -108,8 +104,10 @@ check-sanitizer: $(OUT)/$(STAGE0)-sanitizer tests/driver.sh $(Q)rm $(OUT)/shecc check-snapshots: $(OUT)/$(STAGE0) $(SNAPSHOTS) tests/check-snapshots.sh + # static linking $(Q)$(foreach SNAPSHOT_ARCH, $(ARCHS), $(MAKE) distclean config check-snapshot ARCH=$(SNAPSHOT_ARCH) DYNLINK=0 --silent;) - $(Q)$(MAKE) distclean config check-snapshot ARCH=arm DYNLINK=1 --silent + # dynamic linking + $(Q)$(foreach SNAPSHOT_ARCH, $(ARCHS), $(MAKE) distclean config check-snapshot ARCH=$(SNAPSHOT_ARCH) DYNLINK=1 --silent;) $(VECHO) "Switching backend back to %s (DYNLINK=0)\n" arm $(Q)$(MAKE) distclean config ARCH=arm DYNLINK=0 --silent @@ -118,24 +116,17 @@ check-snapshot: $(OUT)/$(STAGE0) tests/check-snapshots.sh tests/check-snapshots.sh $(ARCH) $(DYNLINK) $(VECHO) " OK\n" -# TODO: Add an ABI conformance test suite for the RISC-V architecture check-abi-stage0: $(OUT)/$(STAGE0) - $(Q)if [ "$(ARCH)" = "arm" ]; then \ - tests/$(ARCH)-abi.sh 0 $(DYNLINK); \ - else \ - echo "Skip ABI compliance validation"; \ - fi + tests/$(ARCH)-abi.sh 0 $(DYNLINK); check-abi-stage2: $(OUT)/$(STAGE2) - $(Q)if [ "$(ARCH)" = "arm" ]; then \ - tests/$(ARCH)-abi.sh 2 $(DYNLINK); \ - else \ - echo "Skip ABI compliance validation"; \ - fi + tests/$(ARCH)-abi.sh 2 $(DYNLINK); update-snapshots: tests/update-snapshots.sh + # static linking $(Q)$(foreach SNAPSHOT_ARCH, $(ARCHS), $(MAKE) distclean config update-snapshot ARCH=$(SNAPSHOT_ARCH) DYNLINK=0 --silent;) - $(Q)$(MAKE) distclean config update-snapshot ARCH=arm DYNLINK=1 --silent + # dynamic linking + $(Q)$(foreach SNAPSHOT_ARCH, $(ARCHS), $(MAKE) distclean config update-snapshot ARCH=$(SNAPSHOT_ARCH) DYNLINK=1 --silent;) $(VECHO) "Switching backend back to %s (DYNLINK=0)\n" arm $(Q)$(MAKE) distclean config ARCH=arm DYNLINK=0 --silent diff --git a/mk/arm.mk b/mk/arm.mk index 53fe0899..7ef7d7e4 100644 --- a/mk/arm.mk +++ b/mk/arm.mk @@ -16,6 +16,7 @@ ARCH_DEFS = \ \#define LIBC_SO \"libc.so.6\"\n$\ \#define PLT_FIXUP_SIZE 20\n$\ \#define PLT_ENT_SIZE 12\n$\ + \#define RESERVED_GOT_NUM 3\n$\ \#define R_ARCH_JUMP_SLOT 0x16\n$\ \#define MAX_ARGS_IN_REG 4\n$\ " @@ -53,45 +54,4 @@ ifneq ($(shell which fastfetch),) endif endif -# Find the sysroot of the ARM GNU toolchain if using dynamic linking. -# -# Since developers may install the toolchain manually instead of -# using a package manager such as apt, we cannot assume that the -# path of ld-linux is always "/usr/arm-linux-gnueabihf". -# -# Therefore, the following process first locates find the correct -# sysroot of the toolchain, and then generate the ELF interpreter -# prefix for later use. -ifeq ($(USE_QEMU),1) - ifeq ($(DYNLINK),1) - CROSS_COMPILE = arm-none-linux-gnueabihf- - ARM_CC = $(CROSS_COMPILE)gcc - ARM_CC := $(shell which $(ARM_CC)) - ifndef ARM_CC - CROSS_COMPILE = arm-linux-gnueabihf- - ARM_CC = $(CROSS_COMPILE)gcc - ARM_CC := $(shell which $(ARM_CC)) - ifndef ARM_CC - $(error "Unable to find ARM GNU toolchain.") - endif - endif - - LD_LINUX_PATH := $(shell cd $(shell $(ARM_CC) --print-sysroot) 2>/dev/null && pwd) - ifeq ("$(LD_LINUX_PATH)","/") - LD_LINUX_PATH := $(shell dirname "$(shell which $(ARM_CC))")/.. - LD_LINUX_PATH := $(shell cd $(LD_LINUX_PATH) 2>/dev/null && pwd) - LD_LINUX_PATH := $(LD_LINUX_PATH)/$(shell echo $(CROSS_COMPILE) | sed s'/.$$//')/libc - LD_LINUX_PATH := $(shell cd $(LD_LINUX_PATH) 2>/dev/null && pwd) - ifndef LD_LINUX_PATH - LD_LINUX_PATH = /usr/$(shell echo $(CROSS_COMPILE) | sed s'/.$$//') - LD_LINUX_PATH := $(shell cd $(LD_LINUX_PATH) 2>/dev/null && pwd) - endif - endif - - ifndef LD_LINUX_PATH - $(error "Dynamic linking mode requires ld-linux.so") - endif - - RUNNER_LD_PREFIX = -L $(LD_LINUX_PATH) - endif -endif +TOOLCHAIN_CANDIDATES := arm-none-linux-gnueabihf- arm-linux-gnueabihf- diff --git a/mk/common.mk b/mk/common.mk index ef47ee0f..40b8f2f9 100644 --- a/mk/common.mk +++ b/mk/common.mk @@ -22,6 +22,47 @@ NO_COLOR = \e[0m pass = $(PRINTF) "$(PASS_COLOR)$1 Passed$(NO_COLOR)\n" +# Find the sysroot of the ARM/RISC-V GNU toolchain if using dynamic linking. +# +# Since developers may install the toolchain manually instead of +# using a package manager such as apt, we cannot assume that the +# path of ld-linux is always "/usr/arm-linux-gnueabihf" or other +# similar paths. +# +# Therefore, the following process first locates find the correct +# sysroot of the toolchain, and then generate the ELF interpreter +# prefix for later use. +ifeq ($(USE_QEMU),1) + ifeq ($(DYNLINK),1) + AVAILABLE_TOOLCHAINS := $(foreach tc, $(TOOLCHAIN_CANDIDATES), $(if $(shell which $(tc)gcc), $(tc))) + CROSS_COMPILE := $(firstword $(AVAILABLE_TOOLCHAINS)) + + ifndef CROSS_COMPILE + $(error "Unable to find a proper GNU toolchain.") + endif + + ARCH_CC = $(CROSS_COMPILE)gcc + + LD_LINUX_PATH := $(shell cd $(shell $(ARCH_CC) --print-sysroot) 2>/dev/null && pwd) + ifeq ("$(LD_LINUX_PATH)","/") + LD_LINUX_PATH := $(shell dirname "$(shell which $(ARCH_CC))")/.. + LD_LINUX_PATH := $(shell cd $(LD_LINUX_PATH) 2>/dev/null && pwd) + LD_LINUX_PATH := $(LD_LINUX_PATH)/$(shell echo $(CROSS_COMPILE) | sed s'/.$$//')/libc + LD_LINUX_PATH := $(shell cd $(LD_LINUX_PATH) 2>/dev/null && pwd) + ifndef LD_LINUX_PATH + LD_LINUX_PATH = /usr/$(shell echo $(CROSS_COMPILE) | sed s'/.$$//') + LD_LINUX_PATH := $(shell cd $(LD_LINUX_PATH) 2>/dev/null && pwd) + endif + endif + + ifndef LD_LINUX_PATH + $(error "Dynamic linking mode requires ld-linux.so") + endif + + RUNNER_LD_PREFIX = -L $(LD_LINUX_PATH) + endif +endif + # Check the prerequisites PREREQ_LIST := dot jq TARGET_EXEC ?= diff --git a/mk/riscv.mk b/mk/riscv.mk index 536d2c0e..02b1f5bb 100644 --- a/mk/riscv.mk +++ b/mk/riscv.mk @@ -7,13 +7,13 @@ ARCH_DEFS = \ \#define ARCH_PREDEFINED \"__riscv\" /* Older versions of the GCC toolchain defined __riscv__ */\n$\ \#define ELF_MACHINE 0xf3\n$\ \#define ELF_FLAGS 0\n$\ - \#define DYN_LINKER \"/lib/ld-linux.so.3\"\n$\ + \#define DYN_LINKER \"/lib/ld-linux-riscv32-ilp32d.so.1\"\n$\ \#define LIBC_SO \"libc.so.6\"\n$\ - \#define PLT_FIXUP_SIZE 20\n$\ - \#define PLT_ENT_SIZE 12\n$\ + \#define PLT_FIXUP_SIZE 32\n$\ + \#define PLT_ENT_SIZE 16\n$\ + \#define RESERVED_GOT_NUM 2\n$\ \#define R_ARCH_JUMP_SLOT 0x5\n$\ \#define MAX_ARGS_IN_REG 8\n$\ " -# TODO: Set this variable for RISC-V architecture -RUNNER_LD_PREFIX= +TOOLCHAIN_CANDIDATES = riscv32-unknown-linux-gnu- diff --git a/src/arch-lower.c b/src/arch-lower.c index 1c70e7d2..96f7ea85 100644 --- a/src/arch-lower.c +++ b/src/arch-lower.c @@ -59,9 +59,9 @@ void riscv_lower(void) /* Entry point: dispatch to the active architecture. */ void arch_lower(void) { -#if ELF_MACHINE == 0x28 /* ARM */ +#if ELF_MACHINE == ELF_MACHINE_ARM32 arm_lower(); -#elif ELF_MACHINE == 0xf3 /* RISC-V */ +#elif ELF_MACHINE == ELF_MACHINE_RV32 riscv_lower(); #else /* Unknown architecture: keep behavior as-is. */ diff --git a/src/defs.h b/src/defs.h index b0b79340..d43d39c9 100644 --- a/src/defs.h +++ b/src/defs.h @@ -43,6 +43,7 @@ #define MAX_DYNSYM 1024 #define MAX_DYNSTR 1024 #define MAX_RELPLT 1024 +#define MAX_RELAPLT 1024 #define MAX_PLT 1024 #define MAX_GOTPLT 1024 #define MAX_CONSTANTS 1024 @@ -101,6 +102,9 @@ #define ALIGN_UP(val, align) (((val) + (align) - 1) & ~((align) - 1)) #endif +#define ELF_MACHINE_ARM32 0x28 +#define ELF_MACHINE_RV32 0xf3 + /* Common data structures */ typedef struct arena_block { char *memory; @@ -682,15 +686,28 @@ typedef struct { strbuf_t *elf_dynsym; strbuf_t *elf_dynstr; strbuf_t *elf_relplt; + strbuf_t *elf_relaplt; strbuf_t *elf_plt; strbuf_t *elf_got; int elf_interp_start; int elf_relplt_start; + int elf_relaplt_start; int elf_plt_start; int elf_got_start; int relplt_size; + int relaplt_size; int plt_size; int got_size; + + /* Currently, we don't consider the scenarios involving + * a mixture of REL and RELA relocation entries. + * + * Therefore, use a flag to determine the type of + * relocation entries to be processed: + * - true: use RELA relocation entries + * - false: use REL relocation entries + */ + bool use_relaplt; } dynamic_sections_t; /* For .dynsym section. */ @@ -709,6 +726,12 @@ typedef struct { int r_info; } elf32_rel_t; +typedef struct { + int r_offset; + int r_info; + int r_addend; +} elf32_rela_t; + /* For .dynamic section */ typedef struct { int d_tag; diff --git a/src/elf.c b/src/elf.c index 17e2244a..ac2ff253 100644 --- a/src/elf.c +++ b/src/elf.c @@ -75,17 +75,20 @@ void elf_generate_header(void) * - number of section headers = 15 * - section header index of .shstrtab = 14 */ + int elf_relplt_size = dynamic_sections.use_relaplt + ? dynamic_sections.elf_relaplt->size + : dynamic_sections.elf_relplt->size; phnum = 4; shnum = 15; shstrndx = 14; - shoff = - elf_header_len + elf_code->size + elf_data->size + - elf_rodata->size + elf_symtab->size + elf_strtab->size + - elf_shstrtab->size + dynamic_sections.elf_interp->size + - dynamic_sections.elf_relplt->size + dynamic_sections.elf_plt->size + - dynamic_sections.elf_got->size + dynamic_sections.elf_dynstr->size + - dynamic_sections.elf_dynsym->size + - dynamic_sections.elf_dynamic->size; + shoff = elf_header_len + elf_code->size + elf_data->size + + elf_rodata->size + elf_symtab->size + elf_strtab->size + + elf_shstrtab->size + dynamic_sections.elf_interp->size + + elf_relplt_size + dynamic_sections.elf_plt->size + + dynamic_sections.elf_got->size + + dynamic_sections.elf_dynstr->size + + dynamic_sections.elf_dynsym->size + + dynamic_sections.elf_dynamic->size; } else { /* In static linking mode: * - number of program headers = 2 @@ -187,9 +190,12 @@ void elf_generate_header(void) void elf_generate_program_headers(void) { + strbuf_t *elf_relplt = dynamic_sections.use_relaplt + ? dynamic_sections.elf_relaplt + : dynamic_sections.elf_relplt; if (!elf_program_header || !elf_code || !elf_data || !elf_rodata || (dynlink && - (!dynamic_sections.elf_interp || !dynamic_sections.elf_relplt || + (!dynamic_sections.elf_interp || !elf_relplt || !dynamic_sections.elf_plt || !dynamic_sections.elf_got || !dynamic_sections.elf_dynstr || !dynamic_sections.elf_dynsym || !dynamic_sections.elf_dynamic))) { @@ -232,10 +238,8 @@ void elf_generate_program_headers(void) phdr.p_flags = 5; /* flags */ phdr.p_align = PAGESIZE; /* alignment */ if (dynlink) { - phdr.p_filesz += - dynamic_sections.elf_relplt->size + dynamic_sections.elf_plt->size; - phdr.p_memsz += - dynamic_sections.elf_relplt->size + dynamic_sections.elf_plt->size; + phdr.p_filesz += elf_relplt->size + dynamic_sections.elf_plt->size; + phdr.p_memsz += elf_relplt->size + dynamic_sections.elf_plt->size; } elf_write_blk(elf_program_header, &phdr, sizeof(elf32_phdr_t)); @@ -250,8 +254,7 @@ void elf_generate_program_headers(void) phdr.p_flags = 6; /* flags */ phdr.p_align = PAGESIZE; /* alignment */ if (dynlink) { - phdr.p_offset += - dynamic_sections.elf_relplt->size + dynamic_sections.elf_plt->size; + phdr.p_offset += elf_relplt->size + dynamic_sections.elf_plt->size; phdr.p_vaddr = dynamic_sections.elf_interp_start; phdr.p_paddr = dynamic_sections.elf_interp_start; phdr.p_filesz += dynamic_sections.elf_interp->size + @@ -272,7 +275,7 @@ void elf_generate_program_headers(void) /* program header - program interpreter (.interp section) */ phdr.p_type = 3; /* PT_INTERP */ phdr.p_offset = elf_header_len + elf_code->size + elf_rodata->size + - dynamic_sections.elf_relplt->size + + elf_relplt->size + dynamic_sections.elf_plt->size; /* offset of segment */ phdr.p_vaddr = dynamic_sections.elf_interp_start; /* virtual address */ phdr.p_paddr = dynamic_sections.elf_interp_start; /* physical address */ @@ -286,7 +289,7 @@ void elf_generate_program_headers(void) phdr.p_type = 2; /* PT_DYNAMIC */ phdr.p_offset = elf_header_len + elf_code->size + elf_rodata->size + - dynamic_sections.elf_relplt->size + dynamic_sections.elf_plt->size + + elf_relplt->size + dynamic_sections.elf_plt->size + dynamic_sections.elf_interp->size + dynamic_sections.elf_got->size + dynamic_sections.elf_dynstr->size + dynamic_sections.elf_dynsym->size; /* offset of segment */ @@ -308,11 +311,14 @@ void elf_generate_program_headers(void) void elf_generate_section_headers(void) { + strbuf_t *elf_relplt = dynamic_sections.use_relaplt + ? dynamic_sections.elf_relaplt + : dynamic_sections.elf_relplt; /* Check for null pointers to prevent crashes */ if (!elf_section_header || !elf_code || !elf_data || !elf_rodata || !elf_symtab || !elf_strtab || !elf_shstrtab || (dynlink && - (!dynamic_sections.elf_interp || !dynamic_sections.elf_relplt || + (!dynamic_sections.elf_interp || !elf_relplt || !dynamic_sections.elf_plt || !dynamic_sections.elf_got || !dynamic_sections.elf_dynstr || !dynamic_sections.elf_dynsym || !dynamic_sections.elf_dynamic))) { @@ -396,20 +402,37 @@ void elf_generate_section_headers(void) sh_name += strlen(".rodata") + 1; if (dynlink) { - /* .rel.plt */ + /* .rel.plt or .rela.plt */ + int sh_type, sh_addr, sh_size, sh_entsize, __ofs, __sh_name; + + if (dynamic_sections.use_relaplt) { + sh_type = 4; /* SHT_RELA */ + sh_addr = dynamic_sections.elf_relaplt_start; + sh_size = dynamic_sections.elf_relaplt->size; + sh_entsize = sizeof(elf32_rela_t); + __ofs = dynamic_sections.elf_relaplt->size; + __sh_name = strlen(".rela.plt") + 1; + } else { + sh_type = 9; /* SHT_REL */ + sh_addr = dynamic_sections.elf_relplt_start; + sh_size = dynamic_sections.elf_relplt->size; + sh_entsize = sizeof(elf32_rel_t); + __ofs = dynamic_sections.elf_relplt->size; + __sh_name = strlen(".rel.plt") + 1; + } shdr.sh_name = sh_name; - shdr.sh_type = 9; /* SHT_REL */ + shdr.sh_type = sh_type; shdr.sh_flags = 0x42; /* 0x40 | SHF_ALLOC */ - shdr.sh_addr = dynamic_sections.elf_relplt_start; + shdr.sh_addr = sh_addr; shdr.sh_offset = ofs; - shdr.sh_size = dynamic_sections.elf_relplt->size; + shdr.sh_size = sh_size; shdr.sh_link = 8; /* The section header index of .dynsym. */ shdr.sh_info = 6; /* The section header index of .got. */ shdr.sh_addralign = 4; - shdr.sh_entsize = sizeof(elf32_rel_t); + shdr.sh_entsize = sh_entsize; elf_write_blk(elf_section_header, &shdr, sizeof(elf32_shdr_t)); - ofs += dynamic_sections.elf_relplt->size; - sh_name += strlen(".rel.plt") + 1; + ofs += __ofs; + sh_name += __sh_name; /* .plt */ shdr.sh_name = sh_name; @@ -597,9 +620,12 @@ void elf_align(strbuf_t *elf_array) void elf_generate_sections(void) { + strbuf_t *elf_relplt = dynamic_sections.use_relaplt + ? dynamic_sections.elf_relaplt + : dynamic_sections.elf_relplt; if (!elf_shstrtab || (dynlink && - (!dynamic_sections.elf_interp || !dynamic_sections.elf_relplt || + (!dynamic_sections.elf_interp || !elf_relplt || !dynamic_sections.elf_plt || !dynamic_sections.elf_got || !dynamic_sections.elf_dynstr || !dynamic_sections.elf_dynsym || !dynamic_sections.elf_dynamic))) { @@ -609,21 +635,20 @@ void elf_generate_sections(void) if (dynlink) { /* In dynamic linking mode, elf_generate_sections() also generates - * .interp, .dynsym, .dynstr, .relplt, .got and dynamic sections. + * .interp, .dynsym, .dynstr, .rel.plt (.rela.plt), .got and dynamic + * sections. * * .plt section is generated at the code generation phase. - * - * TODO: - * Define a new structure named 'elf32_rela_t' and use it to generate - * relocation entries for RISC-V architecture. */ elf32_sym_t sym; elf32_dyn_t dyn; elf32_rel_t rel; + elf32_rela_t rela; int dymsym_idx = 1, func_plt_ofs, func_got_ofs, st_name = 0; memset(&sym, 0, sizeof(elf32_sym_t)); memset(&dyn, 0, sizeof(elf32_dyn_t)); memset(&rel, 0, sizeof(elf32_rel_t)); + memset(&rela, 0, sizeof(elf32_rela_t)); /* .interp section */ elf_write_str(dynamic_sections.elf_interp, DYN_LINKER); @@ -651,9 +676,20 @@ void elf_generate_sections(void) * Since __libc_start_main is not added to the function list, * it must be handled additionally first. */ - rel.r_offset = dynamic_sections.elf_got_start + PTR_SIZE * 3; - rel.r_info = (dymsym_idx << 8) | R_ARCH_JUMP_SLOT; - elf_write_blk(dynamic_sections.elf_relplt, &rel, sizeof(elf32_rel_t)); + if (dynamic_sections.use_relaplt) { + rela.r_offset = + dynamic_sections.elf_got_start + PTR_SIZE * RESERVED_GOT_NUM; + rela.r_info = (dymsym_idx << 8) | R_ARCH_JUMP_SLOT; + rela.r_addend = 0; + elf_write_blk(dynamic_sections.elf_relaplt, &rela, + sizeof(elf32_rela_t)); + } else { + rel.r_offset = + dynamic_sections.elf_got_start + PTR_SIZE * RESERVED_GOT_NUM; + rel.r_info = (dymsym_idx << 8) | R_ARCH_JUMP_SLOT; + elf_write_blk(dynamic_sections.elf_relplt, &rel, + sizeof(elf32_rel_t)); + } sym.st_name = st_name; sym.st_info = ELF32_ST_INFO(1, 2); /* STB_GLOBAL = 1, STT_FUNC = 2 */ @@ -664,58 +700,80 @@ void elf_generate_sections(void) elf_write_byte(dynamic_sections.elf_dynstr, 0); st_name += strlen("__libc_start_main") + 1; - /* Because PLT[1] and GOT[3] are reserved for __libc_start_main, - * its plt_offset and got_offset must be PLT_FIXUP_SIZE and - * PTR_SIZE * 3, respectively. Therefore, no offset assignment is - * required for this function. + /* Because PLT[1] and GOT[RESERVED_GOT_NUM] are reserved for + * __libc_start_main, its plt_offset and got_offset must be + * PLT_FIXUP_SIZE and PTR_SIZE * RESERVED_GOT_NUM, respectively. + * Therefore, no offset assignment is required for this function. */ func_plt_ofs = PLT_FIXUP_SIZE + PLT_ENT_SIZE; func_got_ofs = PTR_SIZE << 2; for (func_t *func = FUNC_LIST.head; func; func = func->next) { - if (func->is_used && !func->bbs) { + if (!func->is_used || func->bbs) + continue; + /* If the function is used and has no basic block, + * consider it to be an external function. + */ + + if (dynamic_sections.use_relaplt) { + rela.r_offset += PTR_SIZE; + rela.r_info = (dymsym_idx << 8) | R_ARCH_JUMP_SLOT; + rela.r_addend = 0; + elf_write_blk(dynamic_sections.elf_relaplt, &rela, + sizeof(elf32_rela_t)); + } else { rel.r_offset += PTR_SIZE; rel.r_info = (dymsym_idx << 8) | R_ARCH_JUMP_SLOT; elf_write_blk(dynamic_sections.elf_relplt, &rel, sizeof(elf32_rel_t)); + } - sym.st_name = st_name; - sym.st_info = - ELF32_ST_INFO(1, 2); /* STB_GLOBAL = 1, STT_FUNC = 2 */ - elf_write_blk(dynamic_sections.elf_dynsym, &sym, - sizeof(elf32_sym_t)); - dymsym_idx += 1; + sym.st_name = st_name; + sym.st_info = + ELF32_ST_INFO(1, 2); /* STB_GLOBAL = 1, STT_FUNC = 2 */ + elf_write_blk(dynamic_sections.elf_dynsym, &sym, + sizeof(elf32_sym_t)); + dymsym_idx += 1; - elf_write_str(dynamic_sections.elf_dynstr, - func->return_def.var_name); - elf_write_byte(dynamic_sections.elf_dynstr, 0); - st_name += strlen(func->return_def.var_name) + 1; + elf_write_str(dynamic_sections.elf_dynstr, + func->return_def.var_name); + elf_write_byte(dynamic_sections.elf_dynstr, 0); + st_name += strlen(func->return_def.var_name) + 1; - func->plt_offset = func_plt_ofs; - func->got_offset = func_got_ofs; + func->plt_offset = func_plt_ofs; + func->got_offset = func_got_ofs; - func_plt_ofs += PLT_ENT_SIZE; - func_got_ofs += PTR_SIZE; - } + func_plt_ofs += PLT_ENT_SIZE; + func_got_ofs += PTR_SIZE; } /* Ensure proper alignment for .dynstr section. */ elf_align(dynamic_sections.elf_dynstr); /* .got section * - * - GOT[0] holds the virtual address of .dynamic section. - * - GOT[1] and GOT[2] are reserved for link_map and resolver, - * and are initialized to 0. + * - Arm architecture: + * - GOT[0] holds the virtual address of .dynamic section. + * - GOT[1] and GOT[2] are reserved for link_map and resolver, + * and are initialized to 0. + * - RISC-V architecture: + * - GOT[0] and GOT[1] are reserved for link_map and resolver, + * and are initialized to 0. * - The remaining entries are initialized to &PLT[0]. */ - elf_write_int(dynamic_sections.elf_got, - dynamic_sections.elf_got_start + - dynamic_sections.got_size + - dynamic_sections.elf_dynstr->size + - dynamic_sections.elf_dynsym->size); - elf_write_int(dynamic_sections.elf_got, 0); - elf_write_int(dynamic_sections.elf_got, 0); - for (int i = PTR_SIZE * 3; i < dynamic_sections.got_size; i += PTR_SIZE) + switch (ELF_MACHINE) { + case ELF_MACHINE_ARM32: + elf_write_int(dynamic_sections.elf_got, + dynamic_sections.elf_got_start + + dynamic_sections.got_size + + dynamic_sections.elf_dynstr->size + + dynamic_sections.elf_dynsym->size); + case ELF_MACHINE_RV32: + elf_write_int(dynamic_sections.elf_got, 0); + elf_write_int(dynamic_sections.elf_got, 0); + break; + } + for (int i = PTR_SIZE * RESERVED_GOT_NUM; i < dynamic_sections.got_size; + i += PTR_SIZE) elf_write_int(dynamic_sections.elf_got, dynamic_sections.elf_plt_start); @@ -740,37 +798,79 @@ void elf_generate_sections(void) dyn.d_un = sizeof(elf32_sym_t); /* Size of an entry. */ elf_write_blk(dynamic_sections.elf_dynamic, &dyn, sizeof(elf32_dyn_t)); - dyn.d_tag = 0x11; /* REL */ - dyn.d_un = dynamic_sections - .elf_relplt_start; /* The virtual address of .rel.plt. */ - elf_write_blk(dynamic_sections.elf_dynamic, &dyn, sizeof(elf32_dyn_t)); - - dyn.d_tag = 0x12; /* RELSZ */ - dyn.d_un = dynamic_sections.relplt_size; - elf_write_blk(dynamic_sections.elf_dynamic, &dyn, sizeof(elf32_dyn_t)); - - dyn.d_tag = 0x13; /* RELENT */ - dyn.d_un = sizeof(elf32_rel_t); - elf_write_blk(dynamic_sections.elf_dynamic, &dyn, sizeof(elf32_dyn_t)); + if (dynamic_sections.use_relaplt) { + dyn.d_tag = 0x7; /* RELA */ + dyn.d_un = + dynamic_sections + .elf_relaplt_start; /* The virtual address of .rela.plt. */ + elf_write_blk(dynamic_sections.elf_dynamic, &dyn, + sizeof(elf32_dyn_t)); + + dyn.d_tag = 0x8; /* RELASZ */ + dyn.d_un = dynamic_sections.relaplt_size; + elf_write_blk(dynamic_sections.elf_dynamic, &dyn, + sizeof(elf32_dyn_t)); + + dyn.d_tag = 0x9; /* RELAENT */ + dyn.d_un = sizeof(elf32_rela_t); + elf_write_blk(dynamic_sections.elf_dynamic, &dyn, + sizeof(elf32_dyn_t)); + + dyn.d_tag = 0x2; /* PLTRELSZ */ + dyn.d_un = dynamic_sections.relaplt_size; + elf_write_blk(dynamic_sections.elf_dynamic, &dyn, + sizeof(elf32_dyn_t)); + + dyn.d_tag = 0x14; /* PLTREL */ + dyn.d_un = 0x7; + elf_write_blk(dynamic_sections.elf_dynamic, &dyn, + sizeof(elf32_dyn_t)); + + /* The virtual address of .rela.plt. */ + dyn.d_tag = 0x17; /* JMPREL */ + dyn.d_un = dynamic_sections.elf_relaplt_start; + elf_write_blk(dynamic_sections.elf_dynamic, &dyn, + sizeof(elf32_dyn_t)); + } else { + dyn.d_tag = 0x11; /* REL */ + dyn.d_un = + dynamic_sections + .elf_relplt_start; /* The virtual address of .rel.plt. */ + elf_write_blk(dynamic_sections.elf_dynamic, &dyn, + sizeof(elf32_dyn_t)); + + dyn.d_tag = 0x12; /* RELSZ */ + dyn.d_un = dynamic_sections.relplt_size; + elf_write_blk(dynamic_sections.elf_dynamic, &dyn, + sizeof(elf32_dyn_t)); + + dyn.d_tag = 0x13; /* RELENT */ + dyn.d_un = sizeof(elf32_rel_t); + elf_write_blk(dynamic_sections.elf_dynamic, &dyn, + sizeof(elf32_dyn_t)); + + dyn.d_tag = 0x2; /* PLTRELSZ */ + dyn.d_un = dynamic_sections.relplt_size; + elf_write_blk(dynamic_sections.elf_dynamic, &dyn, + sizeof(elf32_dyn_t)); + + dyn.d_tag = 0x14; /* PLTREL */ + dyn.d_un = 0x11; + elf_write_blk(dynamic_sections.elf_dynamic, &dyn, + sizeof(elf32_dyn_t)); + + /* The virtual address of .rel.plt. */ + dyn.d_tag = 0x17; /* JMPREL */ + dyn.d_un = dynamic_sections.elf_relplt_start; + elf_write_blk(dynamic_sections.elf_dynamic, &dyn, + sizeof(elf32_dyn_t)); + } dyn.d_tag = 0x3; /* PLTGOT */ dyn.d_un = dynamic_sections.elf_got_start; /* The virtual address of .got.*/ elf_write_blk(dynamic_sections.elf_dynamic, &dyn, sizeof(elf32_dyn_t)); - dyn.d_tag = 0x2; /* PLTRELSZ */ - dyn.d_un = dynamic_sections.relplt_size; - elf_write_blk(dynamic_sections.elf_dynamic, &dyn, sizeof(elf32_dyn_t)); - - dyn.d_tag = 0x14; /* PLTREL */ - dyn.d_un = 0x11; - elf_write_blk(dynamic_sections.elf_dynamic, &dyn, sizeof(elf32_dyn_t)); - - dyn.d_tag = 0x17; /* JMPREL */ - dyn.d_un = dynamic_sections - .elf_relplt_start; /* The virtual address of .rel.plt. */ - elf_write_blk(dynamic_sections.elf_dynamic, &dyn, sizeof(elf32_dyn_t)); - dyn.d_tag = 0x1; /* NEEDED */ dyn.d_un = 0x1; /* The index of "libc.so.6" in .dynstr. */ elf_write_blk(dynamic_sections.elf_dynamic, &dyn, sizeof(elf32_dyn_t)); @@ -789,7 +889,10 @@ void elf_generate_sections(void) elf_write_str(elf_shstrtab, ".rodata"); elf_write_byte(elf_shstrtab, 0); if (dynlink) { - elf_write_str(elf_shstrtab, ".rel.plt"); + if (dynamic_sections.use_relaplt) + elf_write_str(elf_shstrtab, ".rela.plt"); + else + elf_write_str(elf_shstrtab, ".rel.plt"); elf_write_byte(elf_shstrtab, 0); elf_write_str(elf_shstrtab, ".plt"); elf_write_byte(elf_shstrtab, 0); @@ -844,47 +947,67 @@ void elf_preprocess(void) elf_code_start = ELF_START + elf_header_len; elf_rodata_start = elf_code_start + elf_offset; if (dynlink) { - /* Precalculate the sizes of .rel.plt, .plt and .got sections. + /* Precalculate the sizes of .rel.plt (.rela.plt), .plt and .got + * sections. * * Suppose the compiled program has n external functions: - * - .rel.plt contains n entries. + * - .rel.plt (.rela.plt) contains n entries. * - .plt has n entries plus one fixup entry. - * - .got includes n + 3 entries - * - GOT[0] holds the virtual address of .dynamic section. - * - GOT[1] and GOT[2] are reserved for link_map and resolver - * (both set to 0). - * - The remaining entries correspond to all external functions. + * - .got includes n + RESERVED_GOT_NUM entries + * - Arm architecture: + * - GOT[0] holds the virtual address of .dynamic section. + * - GOT[1] and GOT[2] are reserved for link_map and resolver + * (both set to 0). + * - RISC-V architecture: + * - GOT[0] and GOT[1] are reserved for resolver and link_map. + * - Common: + * - The remaining entries correspond to all external functions. * * Next, consider the case of __libc_start_main before initializing * the sizes: - * - .rel.plt has the one entry for __libc_start_main. + * - .rel.plt (.rela.plt) has the one entry for __libc_start_main. * - .plt includes one fixup entry plus one entry for __libc_start_main. - * - .got has 3 + 1 entries. - * - 3 entries for GOT[0] - GOT[2]. - * - 1 entry (GOT[3]) reserved for __libc_start_main. + * - .got has RESERVED_GOT_NUM + 1 entries. + * - RESERVED_GOT_NUM entries for GOT[0] - GOT[RESERVED_GOT_NUM - 1]. + * - 1 entry (GOT[RESERVED_GOT_NUM]) reserved for __libc_start_main. * * Therefore, the following code initialize the section sizes based on * the layout described above, and then traverse the function list in a * for loop to increment the sizes for each newly found external * function. */ - dynamic_sections.relplt_size = sizeof(elf32_rel_t); + if (dynamic_sections.use_relaplt) + dynamic_sections.relaplt_size = sizeof(elf32_rela_t); + else + dynamic_sections.relplt_size = sizeof(elf32_rel_t); dynamic_sections.plt_size = PLT_FIXUP_SIZE + PLT_ENT_SIZE; - dynamic_sections.got_size = PTR_SIZE * 3 + PTR_SIZE; + dynamic_sections.got_size = PTR_SIZE * RESERVED_GOT_NUM + PTR_SIZE; for (func_t *func = FUNC_LIST.head; func; func = func->next) { - if (func->is_used && !func->bbs) { + if (!func->is_used || func->bbs) + continue; + if (dynamic_sections.use_relaplt) + dynamic_sections.relaplt_size += sizeof(elf32_rela_t); + else dynamic_sections.relplt_size += sizeof(elf32_rel_t); - dynamic_sections.plt_size += PLT_ENT_SIZE; - dynamic_sections.got_size += PTR_SIZE; - } + dynamic_sections.plt_size += PLT_ENT_SIZE; + dynamic_sections.got_size += PTR_SIZE; } /* Set the starting addresses of the three sections. */ int elf_interp_size = strlen(DYN_LINKER) + 1; elf_interp_size = ALIGN_UP(elf_interp_size, 4); - dynamic_sections.elf_relplt_start = elf_rodata_start + elf_rodata->size; - dynamic_sections.elf_plt_start = - dynamic_sections.elf_relplt_start + dynamic_sections.relplt_size; + if (dynamic_sections.use_relaplt) { + dynamic_sections.elf_relaplt_start = + elf_rodata_start + elf_rodata->size; + dynamic_sections.elf_plt_start = + dynamic_sections.elf_relaplt_start + + dynamic_sections.relaplt_size; + } else { + dynamic_sections.elf_relplt_start = + elf_rodata_start + elf_rodata->size; + dynamic_sections.elf_plt_start = dynamic_sections.elf_relplt_start + + dynamic_sections.relplt_size; + } /* Since the first section of the second load segment is .interp * when using dynamic linking mode, adding PAGESIZE to elf_interp_start * is to ensure that two load segments don't share a common page. @@ -944,8 +1067,13 @@ void elf_generate(const char *outfile) if (dynlink) { /* Read-only sections */ - for (int i = 0; i < dynamic_sections.elf_relplt->size; i++) - fputc(dynamic_sections.elf_relplt->elements[i], fp); + if (dynamic_sections.use_relaplt) + for (int i = 0; i < dynamic_sections.elf_relaplt->size; i++) + fputc(dynamic_sections.elf_relaplt->elements[i], fp); + else { + for (int i = 0; i < dynamic_sections.elf_relplt->size; i++) + fputc(dynamic_sections.elf_relplt->elements[i], fp); + } for (int i = 0; i < dynamic_sections.elf_plt->size; i++) fputc(dynamic_sections.elf_plt->elements[i], fp); /* Readable and writable sections */ diff --git a/src/globals.c b/src/globals.c index f284d24c..98cfeb36 100644 --- a/src/globals.c +++ b/src/globals.c @@ -1308,11 +1308,23 @@ void global_init(void) elf_bss_size = 0; elf_shstrtab = strbuf_create(MAX_SHSTR); elf_section_header = strbuf_create(MAX_SECTION_HEADER); + + switch (ELF_MACHINE) { + case ELF_MACHINE_ARM32: + dynamic_sections.use_relaplt = false; + break; + case ELF_MACHINE_RV32: + dynamic_sections.use_relaplt = true; + break; + } dynamic_sections.elf_interp = strbuf_create(MAX_INTERP); dynamic_sections.elf_dynamic = strbuf_create(MAX_DYNAMIC); dynamic_sections.elf_dynsym = strbuf_create(MAX_DYNSYM); dynamic_sections.elf_dynstr = strbuf_create(MAX_DYNSTR); - dynamic_sections.elf_relplt = strbuf_create(MAX_RELPLT); + if (dynamic_sections.use_relaplt) + dynamic_sections.elf_relaplt = strbuf_create(MAX_RELAPLT); + else + dynamic_sections.elf_relplt = strbuf_create(MAX_RELPLT); dynamic_sections.elf_plt = strbuf_create(MAX_PLT); dynamic_sections.elf_got = strbuf_create(MAX_GOTPLT); } @@ -1449,7 +1461,10 @@ void global_release(void) strbuf_free(dynamic_sections.elf_dynamic); strbuf_free(dynamic_sections.elf_dynsym); strbuf_free(dynamic_sections.elf_dynstr); - strbuf_free(dynamic_sections.elf_relplt); + if (dynamic_sections.use_relaplt) + strbuf_free(dynamic_sections.elf_relaplt); + else + strbuf_free(dynamic_sections.elf_relplt); strbuf_free(dynamic_sections.elf_plt); strbuf_free(dynamic_sections.elf_got); } diff --git a/src/riscv-codegen.c b/src/riscv-codegen.c index ee641b60..436b002f 100644 --- a/src/riscv-codegen.c +++ b/src/riscv-codegen.c @@ -10,6 +10,64 @@ #include "globals.c" #include "riscv.c" +#define RV32_ALIGNMENT 16 + +/* Explanation: registers preservation/restoration + * + * The following table illustrates which registers are caller-saved + * or callee-saved: + * +-----------+--------+ + * | Register | Saver | + * | ABI Name | | + * +-----------+--------+ + * | zero, | | + * | gp, | (None) | + * | tp | | + * +-----------+--------+ + * | ra | Caller | + * +-----------+--------+ + * | sp | Callee | + * +-----------+--------+ + * | a0 - a7 | Caller | + * +-----------+--------+ + * | s0 - s11 | Callee | + * +-----------+--------+ + * | t0 - t6 | Caller | + * +-----------+--------+ + * + * - ra and sp: are properly handled by the code generator. + * + * - a0 - a7: are implicitly handled in register allocation phase. + * + * Register allocation use 8 virtual registers (vreg0 - vreg7) to + * allocate registers from IR. When encountering a function call, + * all virtual registers are spilled properly before the call, and + * their contents will also be restored if they are used after the + * call. + * + * Since vreg0 - vreg7 map to a0 - a7 directly, these physical + * registers are also preserved/restored naturally whan a function + * is invoked or returned. + * + * - s0 - s11: are not necessary to be preserved or restored. + * + * The current code generator does not use s1 - s11 to generate + * instructions, so these registers are not needed to be processed. + * + * For s0 register, it is used at the program entry point under + * static linking. The program entry point will use s0 to store + * the value of sp register and obtain 'argc' and 'argv' via s0 + * before calling the main function. Then, the content of s0 is + * no longer used after calling the main function, so its + * preservation and restoration can also be ignored. + * + * - t0 - t6: are not necessary to be handled. + * + * These registers are used to store certain temporary values; + * however, the code generator ensures that these values do not + * persist across function calls. + */ + void update_elf_offset(ph2_ir_t *ph2_ir) { switch (ph2_ir->op) { @@ -118,12 +176,30 @@ void update_elf_offset(ph2_ir_t *ph2_ir) void cfg_flatten(void) { - func_t *func = find_func("__syscall"); - /* Prologue ~ 6 instructions (24 bytes). Place __syscall right after. */ - func->bbs->elf_offset = 24; + func_t *func; + + if (dynlink) { + /* When using dynamic linking, 17 instructions are generated at + * the program entry point to perform the following operations: + * - prepare arguments and call __libc_start_main() + * - preserve a0 ('argc'), a1 ('argv') and sp. + * - allocate a global stack and jump to global init function. + */ + elf_offset = 68; + } else { + /* Under static linking, "__syscall" must be generated to allow + * the program to invoke system calls. + * + * "__syscall" consists of 9 instructions, preceded by 6 initial + * instructions. Consequently, the elf offset for "__syscall" is + * is 24 bytes, and the offset for the subsequent function + * (GLOBAL_FUNC) is 60 bytes. + */ + func = find_func("__syscall"); + func->bbs->elf_offset = 24; + elf_offset = 60; + } - /* Reserve space for prologue (24) + syscall trampoline (36) = 60 bytes. */ - elf_offset = 60; GLOBAL_FUNC->bbs->elf_offset = elf_offset; for (ph2_ir_t *ph2_ir = GLOBAL_FUNC->bbs->ph2_ir_list.head; ph2_ir; @@ -132,7 +208,10 @@ void cfg_flatten(void) } /* prepare 'argc' and 'argv', then proceed to 'main' function */ - elf_offset += 24; + if (dynlink) + elf_offset += 32; + else + elf_offset += 24; for (func = FUNC_LIST.head; func; func = func->next) { /* Skip function declarations without bodies */ @@ -144,6 +223,14 @@ void cfg_flatten(void) flatten_ir->src0 = func->stack_size; strncpy(flatten_ir->func_name, func->return_def.var_name, MAX_VAR_LEN); + /* Except for local variables, it must allocate additional space + * to preserve the content of ra at each function entry point. + * + * 'stack_size' doesn't include the additional space, so an extra + * number '4' is added to 'stack_size'. + */ + int stack_top_ofs = ALIGN_UP(func->stack_size + 4, RV32_ALIGNMENT); + for (basic_block_t *bb = func->bbs; bb; bb = bb->rpo_next) { bb->elf_offset = elf_offset; @@ -154,9 +241,23 @@ void cfg_flatten(void) for (ph2_ir_t *insn = bb->ph2_ir_list.head; insn; insn = insn->next) { - /* TODO: recalculate the offset for instructions with the - * 'ofs_based_on_stack_top' flag set. - */ + if (insn->ofs_based_on_stack_top) { + switch (insn->op) { + case OP_load: + case OP_address_of: + insn->src0 = insn->src0 + stack_top_ofs; + break; + case OP_store: + insn->src1 = insn->src1 + stack_top_ofs; + break; + default: + /* Ignore opcodes with the ofs_based_on_stack_top + * flag set since only the three opcodes above needs + * to access a variable's address. + */ + break; + } + } flatten_ir = add_existed_ph2_ir(insn); if (insn->op == OP_return) { @@ -193,9 +294,10 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) switch (ph2_ir->op) { case OP_define: + ofs = ALIGN_UP(ph2_ir->src0 + 4, RV32_ALIGNMENT); emit(__sw(__ra, __sp, -4)); - emit(__lui(__t0, rv_hi(ph2_ir->src0 + 4))); - emit(__addi(__t0, __t0, rv_lo(ph2_ir->src0 + 4))); + emit(__lui(__t0, rv_hi(ofs))); + emit(__addi(__t0, __t0, rv_lo(ofs))); emit(__sub(__sp, __sp, __t0)); return; case OP_load_constant: @@ -274,7 +376,16 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) return; case OP_call: func = find_func(ph2_ir->func_name); - emit(__jal(__ra, func->bbs->elf_offset - elf_code->size)); + if (func->bbs) + ofs = func->bbs->elf_offset - elf_code->size; + else if (dynlink) { + ofs = (dynamic_sections.elf_plt_start + func->plt_offset) - + (elf_code_start + elf_code->size); + } else { + printf("The '%s' function is not implemented\n", ph2_ir->func_name); + abort(); + } + emit(__jal(__ra, ofs)); return; case OP_load_data_address: emit(__lui(rd, rv_hi(elf_data_start + ph2_ir->src0))); @@ -286,7 +397,14 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) return; case OP_address_of_func: func = find_func(ph2_ir->func_name); - ofs = elf_code_start + func->bbs->elf_offset; + if (func->bbs) + ofs = elf_code_start + func->bbs->elf_offset; + else if (dynlink) + ofs = dynamic_sections.elf_plt_start + func->plt_offset; + else { + printf("The '%s' function is not implemented\n", ph2_ir->func_name); + abort(); + } emit(__lui(__t0, rv_hi(ofs))); emit(__addi(__t0, __t0, rv_lo(ofs))); emit(__sw(__t0, rs1, 0)); @@ -302,8 +420,9 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) emit(__addi(__zero, __zero, 0)); else emit(__addi(__a0, rs1, 0)); - emit(__lui(__t0, rv_hi(ph2_ir->src1 + 4))); - emit(__addi(__t0, __t0, rv_lo(ph2_ir->src1 + 4))); + ofs = ALIGN_UP(ph2_ir->src1 + 4, RV32_ALIGNMENT); + emit(__lui(__t0, rv_hi(ofs))); + emit(__addi(__t0, __t0, rv_lo(ofs))); emit(__add(__sp, __sp, __t0)); emit(__lw(__ra, __sp, -4)); emit(__jalr(__zero, __ra, 0)); @@ -482,26 +601,100 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) } } +void plt_generate(void); void code_generate(void) { - /* start: save original sp in s0; allocate global stack; run init */ - emit(__addi(__s0, __sp, 0)); - emit(__lui(__t0, rv_hi(GLOBAL_FUNC->stack_size))); - emit(__addi(__t0, __t0, rv_lo(GLOBAL_FUNC->stack_size))); + int ofs; + + if (dynlink) { + plt_generate(); + /* - Initial stack layout when the program starts: + * + * +----------------+ (high address) + * | ... | + * +----------------+ + * | argv[argc - 1] | + * +----------------+ + * | ... | + * +----------------+ + * | argv[0] | + * +----------------+ + * | argc | + * +----------------+ <- sp points to this location. + * + * - At the program entry point, it must call __libc_start_main() + * under dynamic linking. The function prototype is as follows: + * + * int __libc_start_main(int (*main) (int, char **, char **), + * int argc, char **argv, + * void (*init) (void), + * void (*fini) (void), + * void (*rtld_fini) (void), + * void (*stack_end)); + * + * Currently, to execute a dynamically linked program with the + * minimal effort required, we perform the following call: + * -> __libc_start_main(main_wrapper, argc, argv, NULL, + * NULL, NULL, stack_end) + */ + emit(__lui(__a0, rv_hi(elf_code_start + 36))); + emit(__addi(__a0, __a0, rv_lo(elf_code_start + 36))); + emit(__lw(__a1, __sp, 0)); + emit(__addi(__a2, __sp, 4)); + emit(__addi(__a3, __zero, 0)); + emit(__addi(__a4, __zero, 0)); + emit(__addi(__a5, __zero, 0)); + emit(__addi(__a6, __sp, 0)); + + /* Call __libc_start_main() via PLT[1] */ + ofs = (dynamic_sections.elf_plt_start + PLT_FIXUP_SIZE) - + (elf_code_start + elf_code->size); + emit(__jal(__ra, ofs)); + + /* The main wrapper is located here under the dynamic linking mode + * + * Use t1 and t2 registers to temporarily store 'argc' and 'argv', + * while preserving ra on the stack. + * + * After the main function completes its execution, it must use + * the content of ra to transfer control back to __libc_start_main(). + */ + emit(__addi(__t1, __a0, 0)); + emit(__addi(__t2, __a1, 0)); + emit(__sw(__ra, __sp, -4)); + ofs = ALIGN_UP(GLOBAL_FUNC->stack_size + 4, RV32_ALIGNMENT); + } else { + /* When using static linking, the starting address + * of the main wrapper is here. + * + * Save original sp in s0 first. + */ + ofs = ALIGN_UP(GLOBAL_FUNC->stack_size, RV32_ALIGNMENT); + emit(__addi(__s0, __sp, 0)); + } + /* Next, the main wrapper performs: + * 1. allocate global stack + * 2. jump to global init function + * 3. call the main function + */ + emit(__lui(__t0, rv_hi(ofs))); + emit(__addi(__t0, __t0, rv_lo(ofs))); emit(__sub(__sp, __sp, __t0)); emit(__addi(__gp, __sp, 0)); /* Set up global pointer */ emit(__jal(__ra, GLOBAL_FUNC->bbs->elf_offset - elf_code->size)); - /* syscall trampoline for __syscall - must be at offset 24 */ - emit(__addi(__a7, __a0, 0)); - emit(__addi(__a0, __a1, 0)); - emit(__addi(__a1, __a2, 0)); - emit(__addi(__a2, __a3, 0)); - emit(__addi(__a3, __a4, 0)); - emit(__addi(__a4, __a5, 0)); - emit(__addi(__a5, __a6, 0)); - emit(__ecall()); - emit(__jalr(__zero, __ra, 0)); + if (!dynlink) { + /* syscall trampoline for __syscall */ + emit(__addi(__a7, __a0, 0)); + emit(__addi(__a0, __a1, 0)); + emit(__addi(__a1, __a2, 0)); + emit(__addi(__a2, __a3, 0)); + emit(__addi(__a3, __a4, 0)); + emit(__addi(__a4, __a5, 0)); + emit(__addi(__a5, __a6, 0)); + emit(__ecall()); + emit(__jalr(__zero, __ra, 0)); + } ph2_ir_t *ph2_ir; for (ph2_ir = GLOBAL_FUNC->bbs->ph2_ir_list.head; ph2_ir; @@ -509,16 +702,31 @@ void code_generate(void) emit_ph2_ir(ph2_ir); /* prepare 'argc' and 'argv', then proceed to 'main' function */ - /* use original sp saved in s0 to get argc/argv */ if (MAIN_BB) { - emit(__addi(__t0, __s0, 0)); - emit(__lw(__a0, __t0, 0)); - emit(__addi(__a1, __t0, 4)); - emit(__jal(__ra, MAIN_BB->elf_offset - elf_code->size)); + if (dynlink) { + emit(__addi(__a0, __t1, 0)); + emit(__addi(__a1, __t2, 0)); + emit(__jal(__ra, MAIN_BB->elf_offset - elf_code->size)); - /* exit with main's return value in a0 */ - emit(__addi(__a7, __zero, 93)); - emit(__ecall()); + /* Restore sp and transfer control back to __libc_start_main() + * using the preserved ra. + */ + emit(__lui(__t0, rv_hi(ofs))); + emit(__addi(__t0, __t0, rv_lo(ofs))); + emit(__add(__sp, __sp, __t0)); + emit(__lw(__ra, __sp, -4)); + emit(__jalr(__zero, __ra, 0)); + } else { + /* use original sp saved in s0 to get argc/argv */ + emit(__addi(__t0, __s0, 0)); + emit(__lw(__a0, __t0, 0)); + emit(__addi(__a1, __t0, 4)); + emit(__jal(__ra, MAIN_BB->elf_offset - elf_code->size)); + + /* exit with main's return value in a0 */ + emit(__addi(__a7, __zero, 93)); + emit(__ecall()); + } } for (int i = 0; i < ph2_ir_idx; i++) { @@ -526,3 +734,139 @@ void code_generate(void) emit_ph2_ir(ph2_ir); } } + +void plt_generate() +{ + int addr_of_plt = dynamic_sections.elf_plt_start; + int addr_of_got = dynamic_sections.elf_got_start; + int end = dynamic_sections.plt_size - PLT_FIXUP_SIZE; + int ofs, pcrel_hi, pcrel_lo; + + ofs = addr_of_got - addr_of_plt; + pcrel_hi = ofs & ~0xFFF; + pcrel_lo = ofs & 0xFFF; + if (pcrel_lo > 2047) { + pcrel_hi += 0x1000; + pcrel_lo -= 0x1000; + } + + /* Accroding the RISC-V ABI specification, the first PLT entry should + * contains the following instructions: + * + * 1: auipc t2, %pcrel_hi(.got) + * sub t1, t1, t3 + * lw t3, %pcrel_lo(1b)(t2) + * addi t1, t1 -(PLT0_SIZE + 12) # PLT0_SIZE is 32 bytes. + * addi t0, t2, %pcrel_lo(1b) + * srli t1, t1, log2(16 / PTRSIZE) # PTRSIZE is 4 bytes. + * lw t0, PTRSIZE(t0) + * jr t3 + * + * +-----------------------------------+-----------------------+ + * | Instruction | Contents of registers | + * +-----------------------------------+-----------------------+ + * | auipc t2, %pcrel_hi(.got) | t0: | + * | | t1: &PLT[N] + 12 | + * | | t2: %pcrel_hi(.got) | + * | | t3: &PLT[0] | + * +-----------------------------------+-----------------------+ + * | sub t1, t1, t3 | t0: | + * | | t1: (N - 1) * 16 | + * | | 32 + 12 | + * | | t2: %pcrel_hi(.got) | + * | | t3: &PLT[0] | + * +-----------------------------------+-----------------------+ + * | lw t3, %pcrel_lo(1b)(t2) | t0: | + * | | t1: (N - 1) * 16 | + * | | 32 + 12 | + * | | t2: %pcrel_hi(.got) | + * | | t3: GOT[0] | + * +-----------------------------------+-----------------------+ + * | addi t1, t1 -(PLT0_SIZE + 12) | t0: | + * | | t1: (N - 1) * 16 | + * | | t2: %pcrel_hi(.got) | + * | | t3: GOT[0] | + * +-----------------------------------+-----------------------+ + * | addi t0, t2, %pcrel_lo(1b) | t0: &GOT[0] | + * | | t1: (N - 1) * 16 | + * | | t2: %pcrel_hi(.got) | + * | | t3: GOT[0] | + * +-----------------------------------+-----------------------+ + * | srli t1, t1, log2(16 / PTRSIZE) | t0: &GOT[0] | + * | | t1: (N - 1) * 4 | + * | | t2: %pcrel_hi(.got) | + * | | t3: GOT[0] | + * +-----------------------------------+-----------------------+ + * | lw t0, PTRSIZE(t0) | t0: GOT[1] | + * | | t1: (N - 1) * 4 | + * | | t2: %pcrel_hi(.got) | + * | | t3: GOT[0] | + * +-----------------------------------+-----------------------+ + * | jr t3 | t0: GOT[1] | + * | | t1: (N - 1) * 4 | + * | | t2: %pcrel_hi(.got) | + * | | t3: GOT[0] | + * +-----------------------------------+-----------------------+ + * + * Note: + * - PLT[N] - PLT[0] = size of PLT[0] + size of several PLT stubs. + * = 32 + (N - 1) * 16 + */ + elf_write_int(dynamic_sections.elf_plt, __auipc(__t2, pcrel_hi)); + elf_write_int(dynamic_sections.elf_plt, __sub(__t1, __t1, __t3)); + elf_write_int(dynamic_sections.elf_plt, __lw(__t3, __t2, pcrel_lo)); + elf_write_int(dynamic_sections.elf_plt, __addi(__t1, __t1, -44)); + elf_write_int(dynamic_sections.elf_plt, __addi(__t0, __t2, pcrel_lo)); + elf_write_int(dynamic_sections.elf_plt, __srli(__t1, __t1, 2)); + elf_write_int(dynamic_sections.elf_plt, __lw(__t0, __t0, 4)); + elf_write_int(dynamic_sections.elf_plt, __jalr(__zero, __t3, 0)); + for (int i = 0; i * PLT_ENT_SIZE < end; i++) { + /* elf_generate() ensures that the .got section is placed + * a higher memory address than the plt section. As a result, + * 'ofs' must always be positive. + * + * addr_of_plt: the starting address of PLT[N]. + * addr_of_got: the starting address of GOT[N + 2]. + */ + addr_of_plt = + dynamic_sections.elf_plt_start + PLT_FIXUP_SIZE + PLT_ENT_SIZE * i; + addr_of_got = dynamic_sections.elf_got_start + PTR_SIZE * (i + 2); + ofs = addr_of_got - addr_of_plt; + + /* In RISC-V ABI, a PLT stub takes up 4 instructions to load GOT[N + 2]: + * + * 1: auipc t3, %pcrel_hi(function@.got) + * lw t3, %pcrel_lo(1b)(t3) + * jalr t1, t3 + * nop + * + * Each PLT stub uses auipc and lw instructions to perform a + * PC-relative addressing to obtain GOT[N + 2], and then perform + * an unconditional jump. + * + * +-------------------------------------+----------------------------+ + * | Instruction | Contents of registers | + * +-------------------------------------+----------------------------+ + * | auipc t3, %pcrel_hi(function@.got) | t1: | + * | | t3: pcrel_hi(function%got) | + * +-------------------------------------+----------------------------+ + * | lw t3, %pcrel_lo(1b)(t3) | t1: | + * | | t3: GOT[N + 2] | + * +-------------------------------------+----------------------------+ + * | jalr t1, t3 | t1: addr of nop | + * | | t3: GOT[N + 2] | + * +-------------------------------------+----------------------------+ + */ + pcrel_hi = ofs & ~0xFFF; + pcrel_lo = ofs & 0xFFF; + if (pcrel_lo > 2047) { + pcrel_hi += 0x1000; + pcrel_lo -= 0x1000; + } + + elf_write_int(dynamic_sections.elf_plt, __auipc(__t3, pcrel_hi)); + elf_write_int(dynamic_sections.elf_plt, __lw(__t3, __t3, pcrel_lo)); + elf_write_int(dynamic_sections.elf_plt, __jalr(__t1, __t3, 0)); + elf_write_int(dynamic_sections.elf_plt, __addi(__zero, __zero, 0)); + } +} diff --git a/tests/riscv-abi.sh b/tests/riscv-abi.sh new file mode 100755 index 00000000..b00b9f5a --- /dev/null +++ b/tests/riscv-abi.sh @@ -0,0 +1,608 @@ +#!/usr/bin/env bash + +# RISC-V Calling Convention Compliance Test Suite + +set -u + +# Test Configuration +readonly VERBOSE_MODE="${VERBOSE:-1}" +readonly SHOW_SUMMARY="${SHOW_SUMMARY:-1}" +readonly SHOW_PROGRESS="${SHOW_PROGRESS:-1}" +readonly COLOR_OUTPUT="${COLOR_OUTPUT:-1}" + +# Test Counters +TOTAL_TESTS=0 +PASSED_TESTS=0 +FAILED_TESTS=0 +SKIPPED_TESTS=0 + +# Category Tracking +declare -A CATEGORY_TESTS +declare -A CATEGORY_PASSED +declare -A CATEGORY_FAILED +CURRENT_CATEGORY="Parameter Passing" + +# Performance Metrics +TEST_START_TIME=$(date +%s) +PROGRESS_COUNT=0 + +# Colors +if [[ "$COLOR_OUTPUT" == "1" && -t 1 ]]; then + RED='\033[0;31m' + GREEN='\033[0;32m' + YELLOW='\033[1;33m' + BLUE='\033[0;34m' + CYAN='\033[0;36m' + BOLD='\033[1m' + NC='\033[0m' +else + RED='' GREEN='' YELLOW='' BLUE='' CYAN='' BOLD='' NC='' +fi + +# Command Line Arguments +if [ "$#" -lt 1 ]; then + echo "Usage: $0 []" + echo " stage: 0 (host compiler), 1 (stage1), or 2 (stage2)" + echo " dynlink: 0 (static linking), 1 (dynamic linking)" + echo "" + echo "Environment Variables:" + echo " VERBOSE=1 Enable verbose output" + echo " SHOW_SUMMARY=1 Show category summaries (default)" + echo " SHOW_PROGRESS=1 Show progress dots (default)" + echo " COLOR_OUTPUT=1 Enable colored output (default)" + exit 1 +fi + +case "$1" in + "0") + readonly SHECC="$PWD/out/shecc" + readonly STAGE="Stage 0 (Host Compiler)" ;; + "1") + readonly SHECC="${TARGET_EXEC:-} $PWD/out/shecc-stage1.elf" + readonly STAGE="Stage 1 (Cross-compiled)" ;; + "2") + readonly SHECC="${TARGET_EXEC:-} $PWD/out/shecc-stage2.elf" + readonly STAGE="Stage 2 (Self-hosted)" ;; + *) + echo "Error: Invalid stage '$1'. Use 0, 1, or 2." + exit 1 ;; +esac + +DYNLINK="${2:-0}" + +# Banner +echo -e "${BLUE}${BOLD}========================================${NC}" +echo -e "${BLUE}${BOLD}RISC-V Calling Convention Compliance Test Suite${NC}" +echo -e "${BLUE}${BOLD}========================================${NC}" +echo -e "Stage: $STAGE" +echo -e "Link Mode: $([ "$DYNLINK" == "1" ] && echo "Dynamic" || echo "Static")" +echo -e "Compiler: $SHECC" +echo "" + +# Helper Functions +update_category_stats() { + local category="$1" + local result="$2" # "pass" or "fail" + + if [[ -z "${CATEGORY_TESTS[$category]:-}" ]]; then + CATEGORY_TESTS[$category]=0 + CATEGORY_PASSED[$category]=0 + CATEGORY_FAILED[$category]=0 + fi + + CATEGORY_TESTS[$category]=$((${CATEGORY_TESTS[$category]} + 1)) + + if [[ "$result" == "pass" ]]; then + CATEGORY_PASSED[$category]=$((${CATEGORY_PASSED[$category]} + 1)) + else + CATEGORY_FAILED[$category]=$((${CATEGORY_FAILED[$category]} + 1)) + fi +} + +show_progress() { + if [[ "$SHOW_PROGRESS" == "1" ]]; then + echo -n "." + PROGRESS_COUNT=$((PROGRESS_COUNT + 1)) + if [[ $((PROGRESS_COUNT % 50)) -eq 0 ]]; then + echo "" + fi + fi +} + +# Test execution function +run_abi_test() { + local test_name="$1" + local category="$2" + local source_code="$3" + local expected_output="$4" + local skip_static="${5:-0}" + + CURRENT_CATEGORY="$category" + TOTAL_TESTS=$((TOTAL_TESTS + 1)) + + # Skip if dynamic linking required but we're in static mode + if [[ "$skip_static" == "1" && "$DYNLINK" == "0" ]]; then + if [[ "$VERBOSE_MODE" == "1" ]]; then + echo -e "${YELLOW}SKIP${NC}: $test_name (requires dynamic linking)" + fi + SKIPPED_TESTS=$((SKIPPED_TESTS + 1)) + show_progress + return + fi + + # Create temporary test file + local test_file="/tmp/shecc_abi_test_$$.c" + echo "$source_code" > "$test_file" + + # Compile + local compile_cmd="$SHECC" + if [[ "$DYNLINK" == "1" ]]; then + compile_cmd="$compile_cmd --dynlink" + fi + compile_cmd="$compile_cmd -o /tmp/shecc_abi_test_$$.elf $test_file" + + local compile_output + if ! compile_output=$(eval "$compile_cmd" 2>&1); then + if [[ "$VERBOSE_MODE" == "1" ]]; then + echo -e "${RED}FAIL${NC}: $test_name (compilation failed)" + echo "$compile_output" | sed 's/^/ /' + fi + FAILED_TESTS=$((FAILED_TESTS + 1)) + update_category_stats "$category" "fail" + rm -f "$test_file" + show_progress + return + fi + + # Run + chmod +x "/tmp/shecc_abi_test_$$.elf" + local run_cmd="${TARGET_EXEC:-}" + run_cmd="$run_cmd /tmp/shecc_abi_test_$$.elf" + + local run_output + local exit_code + run_output=$(eval "$run_cmd" 2>&1) + exit_code=$? + + # Check result + if [[ $exit_code -eq 0 ]]; then + if [[ "$VERBOSE_MODE" == "1" ]]; then + echo -e "${GREEN}PASS${NC}: $test_name" + if [[ -n "$expected_output" && "$run_output" != *"$expected_output"* ]]; then + echo -e "${YELLOW}Warning: Output mismatch${NC}" + echo "Expected: $expected_output" + echo "Got: $run_output" + fi + fi + PASSED_TESTS=$((PASSED_TESTS + 1)) + update_category_stats "$category" "pass" + else + if [[ "$VERBOSE_MODE" == "1" ]]; then + echo -e "${RED}FAIL${NC}: $test_name (exit code $exit_code)" + echo "$run_output" | sed 's/^/ /' + fi + FAILED_TESTS=$((FAILED_TESTS + 1)) + update_category_stats "$category" "fail" + fi + + # Cleanup + rm -f "$test_file" "/tmp/shecc_abi_test_$$.elf" + show_progress +} + +# Parameter Passing Tests + +test_one_arg() { + run_abi_test "One argument (a0)" "Parameter Passing" ' +#include +int add_42(int x) { return x + 42; } +int main() { + int result = add_42(8); + if (result == 50) { + printf("PASS\n"); + return 0; + } + printf("FAIL: expected 50, got %d\n", result); + return 1; +} +' "PASS" +} + +test_two_args() { + run_abi_test "Two arguments (a0, a1)" "Parameter Passing" ' +#include +int add(int a, int b) { return a + b; } +int main() { + int result = add(10, 20); + if (result == 30) { + printf("PASS\n"); + return 0; + } + printf("FAIL: expected 30, got %d\n", result); + return 1; +} +' "PASS" +} + +test_four_args() { + run_abi_test "Four arguments (a0-a3)" "Parameter Passing" ' +#include +int sum4(int a, int b, int c, int d) { return a + b + c + d; } +int main() { + int result = sum4(10, 20, 30, 40); + if (result == 100) { + printf("PASS\n"); + return 0; + } + printf("FAIL: expected 100, got %d\n", result); + return 1; +} +' "PASS" +} + +test_five_args() { + run_abi_test "Five arguments (a0-a4)" "Parameter Passing" ' +#include +int sum5(int a, int b, int c, int d, int e) { return a + b + c + d + e; } +int main() { + int result = sum5(1, 2, 3, 4, 5); + if (result == 15) { + printf("PASS\n"); + return 0; + } + printf("FAIL: expected 15, got %d\n", result); + return 1; +} +' "PASS" +} + +test_eight_args() { + run_abi_test "Eight arguments" "Parameter Passing" ' +#include +int sum8(int a, int b, int c, int d, int e, int f, int g, int h) { + return a + b + c + d + e + f + g + h; +} +int main() { + int result = sum8(1, 2, 3, 4, 5, 6, 7, 8); + if (result == 36) { + printf("PASS\n"); + return 0; + } + printf("FAIL: expected 36, got %d\n", result); + return 1; +} +' "PASS" +} + +# Stack Alignment Tests + +test_stack_alignment_basic() { + run_abi_test "Basic stack alignment" "Stack Alignment" ' +#include +int is_aligned(void *ptr) { + int addr = (int)ptr; + return (addr & 0xf) == 0; +} +int check_alignment(int a, int b) { + int local; + return !is_aligned(&local); +} +int main() { + if (check_alignment(1, 2) == 0) { + printf("PASS\n"); + return 0; + } + printf("FAIL: stack not aligned\n"); + return 1; +} +' "PASS" +} + +test_stack_alignment_extended() { + run_abi_test "Stack alignment with extended args" "Stack Alignment" ' +#include +int is_aligned(void *ptr) { + int addr = (int)ptr; + return (addr & 0xf) == 0; +} +int check_extended(int a, int b, int c, int d, int e, int f) { + int local; + return is_aligned(&local) ? (a+b+c+d+e+f) : -1; +} +int main() { + int result = check_extended(1, 2, 3, 4, 5, 6); + if (result == 21) { + printf("PASS\n"); + return 0; + } + printf("FAIL: result=%d\n", result); + return 1; +} +' "PASS" +} + +# Return Value Tests + +test_return_char() { + run_abi_test "Return char value" "Return Values" ' +#include +char get_char(void) { return '\''A'\''; } +int main() { + if (get_char() == '\''A'\'') { + printf("PASS\n"); + return 0; + } + printf("FAIL\n"); + return 1; +} +' "PASS" +} + +test_return_int() { + run_abi_test "Return int value" "Return Values" ' +#include +int get_value(void) { return 12345; } +int main() { + if (get_value() == 12345) { + printf("PASS\n"); + return 0; + } + printf("FAIL\n"); + return 1; +} +' "PASS" +} + +test_return_pointer() { + run_abi_test "Return pointer value" "Return Values" ' +#include +int *return_ptr(int *p) { return p; } +int main() { + int x = 42; + int *ptr = return_ptr(&x); + if (ptr == &x && *ptr == 42) { + printf("PASS\n"); + return 0; + } + printf("FAIL\n"); + return 1; +} +' "PASS" +} + +# External Function Call Tests (Dynamic Linking Only) + +test_printf_one_arg() { + run_abi_test "printf with 1 argument" "External Calls" ' +#include +int main() { + printf("PASS\n"); + return 0; +} +' "PASS" 1 +} + +test_printf_multi_args() { + run_abi_test "printf with 5 arguments" "External Calls" ' +#include +int main() { + printf("Values: %d %d %d %d\n", 1, 2, 3, 4); + printf("PASS\n"); + return 0; +} +' "PASS" 1 +} + +test_strlen() { + run_abi_test "strlen external call" "External Calls" ' +#include +#include +int main() { + char str[] = "Hello"; + if (strlen(str) == 5) { + printf("PASS\n"); + return 0; + } + printf("FAIL\n"); + return 1; +} +' "PASS" 1 +} + +test_strcpy() { + run_abi_test "strcpy external call" "External Calls" ' +#include +#include +int main() { + char dest[20]; + char src[] = "Test"; + strcpy(dest, src); + if (strcmp(dest, "Test") == 0) { + printf("PASS\n"); + return 0; + } + printf("FAIL\n"); + return 1; +} +' "PASS" 1 +} + +test_memcpy() { + run_abi_test "memcpy external call" "External Calls" ' +#include +#include +int main() { + int src[3] = {1, 2, 3}; + int dst[3]; + memcpy(dst, src, 3 * sizeof(int)); + if (dst[0] == 1 && dst[1] == 2 && dst[2] == 3) { + printf("PASS\n"); + return 0; + } + printf("FAIL\n"); + return 1; +} +' "PASS" 1 +} + +# Register Preservation Tests + +test_local_vars_preserved() { + run_abi_test "Local variables preserved across calls" "Register Preservation" ' +#include +int dummy(int a, int b, int c, int d, int e, int f, int g, int h) { + return a + b + c + d + e + f + g + h; +} +int main() { + int v1 = 100, v2 = 200, v3 = 300, v4 = 400, + v5 = 500, v6 = 600, v7 = 700, v8 = 800; + dummy(1, 2, 3, 4, 5, 6, 7, 8); + if (v1 == 100 && v2 == 200 && v3 == 300 && v4 == 400 && + v5 == 500 && v6 == 600 && v7 == 700 && v8 == 800) { + printf("PASS\n"); + return 0; + } + printf("FAIL: locals corrupted\n"); + return 1; +} +' "PASS" +} + +test_recursive_preservation() { + run_abi_test "Register preservation in recursion" "Register Preservation" ' +#include +int factorial(int n) { + if (n <= 1) return 1; + int local = n; + int result = factorial(n - 1); + return (local == n) ? n * result : -1; +} +int main() { + if (factorial(5) == 120) { + printf("PASS\n"); + return 0; + } + printf("FAIL\n"); + return 1; +} +' "PASS" +} + +# Structure Passing Tests + +test_small_struct() { + run_abi_test "Small struct passing (≤4 bytes)" "Structure Passing" ' +#include +typedef struct { char a; char b; short c; } SmallStruct; +int sum_struct(SmallStruct s) { return s.a + s.b + s.c; } +int main() { + SmallStruct s = {10, 20, 30}; + if (sum_struct(s) == 60) { + printf("PASS\n"); + return 0; + } + printf("FAIL\n"); + return 1; +} +' "PASS" +} + +# Run all tests + +echo -e "${CYAN}Running Parameter Passing Tests...${NC}" +test_one_arg +test_two_args +test_four_args +test_five_args +test_eight_args + +echo "" +echo -e "${CYAN}Running Stack Alignment Tests...${NC}" +test_stack_alignment_basic +test_stack_alignment_extended + +echo "" +echo -e "${CYAN}Running Return Value Tests...${NC}" +test_return_char +test_return_int +test_return_pointer + +echo "" +if [[ "$DYNLINK" == "1" ]]; then + echo -e "${CYAN}Running External Function Call Tests...${NC}" + test_printf_one_arg + test_printf_multi_args + test_strlen + test_strcpy + test_memcpy +else + echo -e "${YELLOW}Skipping External Function Call Tests (requires dynamic linking)${NC}" + SKIPPED_TESTS=$((SKIPPED_TESTS + 5)) +fi + +echo "" +echo -e "${CYAN}Running Register Preservation Tests...${NC}" +test_local_vars_preserved +test_recursive_preservation + +echo "" +echo -e "${CYAN}Running Structure Passing Tests...${NC}" +test_small_struct + +# SUMMARY + +echo "" +echo "" + +if [[ "$SHOW_SUMMARY" == "1" ]]; then + echo -e "${BLUE}${BOLD}========================================${NC}" + echo -e "${BLUE}${BOLD}Category Summary${NC}" + echo -e "${BLUE}${BOLD}========================================${NC}" + + for category in "${!CATEGORY_TESTS[@]}"; do + total="${CATEGORY_TESTS[$category]}" + passed="${CATEGORY_PASSED[$category]}" + failed="${CATEGORY_FAILED[$category]}" + pct=0 + if [[ $total -gt 0 ]]; then + pct=$((passed * 100 / total)) + fi + + printf "%-25s: " "$category" + if [[ $failed -eq 0 ]]; then + echo -e "${GREEN}$passed/$total PASSED${NC} (${pct}%%)" + else + echo -e "${RED}$passed/$total PASSED${NC}, ${RED}$failed FAILED${NC} (${pct}%%)" + fi + done + echo "" +fi + +echo -e "${BLUE}${BOLD}========================================${NC}" +echo -e "${BLUE}${BOLD}Overall Test Results${NC}" +echo -e "${BLUE}${BOLD}========================================${NC}" +echo -e "Total Tests: $TOTAL_TESTS" +echo -e "${GREEN}Passed: $PASSED_TESTS${NC}" + +if [[ $FAILED_TESTS -gt 0 ]]; then + echo -e "${RED}Failed: $FAILED_TESTS${NC}" +else + echo -e "Failed: $FAILED_TESTS" +fi + +if [[ $SKIPPED_TESTS -gt 0 ]]; then + echo -e "${YELLOW}Skipped: $SKIPPED_TESTS${NC}" +fi + +TEST_END_TIME=$(date +%s) +TEST_DURATION=$((TEST_END_TIME - TEST_START_TIME)) +echo -e "Duration: ${TEST_DURATION}s" +echo "" + +if [[ $FAILED_TESTS -gt 0 ]]; then + echo -e "${RED}${BOLD}Some ABI tests FAILED!${NC}" + exit 1 +else + echo -e "${GREEN}${BOLD}All ABI tests PASSED!${NC}" + exit 0 +fi diff --git a/tests/snapshots/fib-riscv-dynamic.json b/tests/snapshots/fib-riscv-dynamic.json new file mode 100644 index 00000000..34eede5f --- /dev/null +++ b/tests/snapshots/fib-riscv-dynamic.json @@ -0,0 +1 @@ +{"_subgraph_cnt":13,"directed":true,"edges":[{"_gvid":0,"head":14,"headport":"n","tail":13,"tailport":"s"},{"_gvid":1,"head":15,"tail":14,"weight":"100"},{"_gvid":2,"head":16,"tail":15,"weight":"100"},{"_gvid":3,"head":17,"headport":"n","tail":16,"tailport":"sw"},{"_gvid":4,"head":22,"headport":"n","tail":16,"tailport":"se"},{"_gvid":5,"head":18,"tail":17,"weight":"100"},{"_gvid":6,"head":19,"headport":"n","tail":18,"tailport":"s"},{"_gvid":7,"head":19,"headport":"n","tail":20,"tailport":"s"},{"_gvid":8,"head":19,"headport":"n","tail":21,"tailport":"s"},{"_gvid":9,"head":23,"headport":"n","tail":22,"tailport":"s"},{"_gvid":10,"head":24,"tail":23,"weight":"100"},{"_gvid":11,"head":25,"tail":24,"weight":"100"},{"_gvid":12,"head":26,"headport":"n","tail":25,"tailport":"sw"},{"_gvid":13,"head":27,"headport":"n","tail":25,"tailport":"se"},{"_gvid":14,"head":20,"tail":26,"weight":"100"},{"_gvid":15,"head":28,"headport":"n","tail":27,"tailport":"s"},{"_gvid":16,"head":29,"tail":28,"weight":"100"},{"_gvid":17,"head":30,"tail":29,"weight":"100"},{"_gvid":18,"head":31,"tail":30,"weight":"100"},{"_gvid":19,"head":32,"tail":31,"weight":"100"},{"_gvid":20,"head":33,"tail":32,"weight":"100"},{"_gvid":21,"head":34,"tail":33,"weight":"100"},{"_gvid":22,"head":35,"tail":34,"weight":"100"},{"_gvid":23,"head":36,"tail":35,"weight":"100"},{"_gvid":24,"head":37,"tail":36,"weight":"100"},{"_gvid":25,"head":38,"tail":37,"weight":"100"},{"_gvid":26,"head":21,"tail":38,"weight":"100"},{"_gvid":27,"head":40,"tail":39,"weight":"100"},{"_gvid":28,"head":41,"tail":40,"weight":"100"},{"_gvid":29,"head":42,"tail":41,"weight":"100"},{"_gvid":30,"head":43,"tail":42,"weight":"100"},{"_gvid":31,"head":44,"tail":43,"weight":"100"},{"_gvid":32,"head":45,"tail":44,"weight":"100"},{"_gvid":33,"head":46,"tail":45,"weight":"100"},{"_gvid":34,"head":47,"tail":46,"weight":"100"},{"_gvid":35,"head":48,"tail":47,"weight":"100"},{"_gvid":36,"head":49,"headport":"n","tail":48,"tailport":"s"}],"label":"","name":"CFG","objects":[{"_gvid":0,"edges":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26],"nodes":[13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38],"subgraphs":[1,2,3,4,5,6,7,8,9]},{"_gvid":1,"edges":[],"nodes":[13],"subgraphs":[]},{"_gvid":2,"edges":[1,2],"nodes":[14,15,16],"subgraphs":[]},{"_gvid":3,"edges":[5],"nodes":[17,18],"subgraphs":[]},{"_gvid":4,"edges":[],"nodes":[19],"subgraphs":[]},{"_gvid":5,"edges":[],"nodes":[22],"subgraphs":[]},{"_gvid":6,"edges":[10,11],"nodes":[23,24,25],"subgraphs":[]},{"_gvid":7,"edges":[14],"nodes":[20,26],"subgraphs":[]},{"_gvid":8,"edges":[],"nodes":[27],"subgraphs":[]},{"_gvid":9,"edges":[16,17,18,19,20,21,22,23,24,25,26],"nodes":[21,28,29,30,31,32,33,34,35,36,37,38],"subgraphs":[]},{"_gvid":10,"edges":[27,28,29,30,31,32,33,34,35,36],"nodes":[39,40,41,42,43,44,45,46,47,48,49],"subgraphs":[11,12]},{"_gvid":11,"edges":[27,28,29,30,31,32,33,34,35],"nodes":[39,40,41,42,43,44,45,46,47,48],"subgraphs":[]},{"_gvid":12,"edges":[],"nodes":[49],"subgraphs":[]},{"_gvid":13,"edges":[],"label":"pseudo","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":14,"edges":[],"label":".t00 := CONST 0","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":15,"edges":[],"label":".t10 := n0 == .t00","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":16,"edges":[],"label":"BRANCH .t10","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":17,"edges":[],"label":".t20 := CONST 0","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":18,"edges":[],"label":"RETURN .t20","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":19,"edges":[],"label":"pseudo","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":20,"edges":[],"label":"RETURN .t50","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":21,"edges":[],"label":"RETURN .t120","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":22,"edges":[],"label":"pseudo","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":23,"edges":[],"label":".t30 := CONST 1","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":24,"edges":[],"label":".t40 := n0 == .t30","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":25,"edges":[],"label":"BRANCH .t40","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":26,"edges":[],"label":".t50 := CONST 1","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":27,"edges":[],"label":"pseudo","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":28,"edges":[],"label":".t60 := CONST 1","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":29,"edges":[],"label":".t70 := n0 - .t60","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":30,"edges":[],"label":"PUSH .t70","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":31,"edges":[],"label":"CALL @fib","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":32,"edges":[],"label":".t80 := RETURN VALUE","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":33,"edges":[],"label":".t90 := CONST 2","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":34,"edges":[],"label":".t100 := n0 - .t90","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":35,"edges":[],"label":"PUSH .t100","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":36,"edges":[],"label":"CALL @fib","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":37,"edges":[],"label":".t110 := RETURN VALUE","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":38,"edges":[],"label":".t120 := .t80 + .t110","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":39,"edges":[],"label":".t130 := [.rodata] + 0","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":40,"edges":[],"label":".t140 := CONST 10","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":41,"edges":[],"label":"PUSH .t140","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":42,"edges":[],"label":"CALL @fib","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":43,"edges":[],"label":".t150 := RETURN VALUE","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":44,"edges":[],"label":"PUSH .t130","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":45,"edges":[],"label":"PUSH .t150","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":46,"edges":[],"label":"CALL @printf","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":47,"edges":[],"label":".t160 := CONST 0","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":48,"edges":[],"label":"RETURN .t160","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":49,"edges":[],"label":"pseudo","nodes":[],"shape":"box","subgraphs":[]}],"strict":true} diff --git a/tests/snapshots/hello-riscv-dynamic.json b/tests/snapshots/hello-riscv-dynamic.json new file mode 100644 index 00000000..f005855f --- /dev/null +++ b/tests/snapshots/hello-riscv-dynamic.json @@ -0,0 +1 @@ +{"_subgraph_cnt":3,"directed":true,"edges":[{"_gvid":0,"head":4,"tail":3,"weight":"100"},{"_gvid":1,"head":5,"tail":4,"weight":"100"},{"_gvid":2,"head":6,"tail":5,"weight":"100"},{"_gvid":3,"head":7,"tail":6,"weight":"100"},{"_gvid":4,"head":8,"tail":7,"weight":"100"},{"_gvid":5,"head":9,"tail":8,"weight":"100"},{"_gvid":6,"head":10,"tail":9,"weight":"100"},{"_gvid":7,"head":11,"tail":10,"weight":"100"},{"_gvid":8,"head":12,"headport":"n","tail":11,"tailport":"s"}],"label":"","name":"CFG","objects":[{"_gvid":0,"edges":[0,1,2,3,4,5,6,7,8],"nodes":[3,4,5,6,7,8,9,10,11,12],"subgraphs":[1,2]},{"_gvid":1,"edges":[0,1,2,3,4,5,6,7],"nodes":[3,4,5,6,7,8,9,10,11],"subgraphs":[]},{"_gvid":2,"edges":[],"nodes":[12],"subgraphs":[]},{"_gvid":3,"edges":[],"label":".t00 := [.rodata] + 0","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":4,"edges":[],"label":"PUSH .t00","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":5,"edges":[],"label":"PUSH argc0","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":6,"edges":[],"label":"CALL @printf","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":7,"edges":[],"label":".t10 := [.rodata] + 4","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":8,"edges":[],"label":"PUSH .t10","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":9,"edges":[],"label":"CALL @printf","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":10,"edges":[],"label":".t20 := CONST 0","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":11,"edges":[],"label":"RETURN .t20","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":12,"edges":[],"label":"pseudo","nodes":[],"shape":"box","subgraphs":[]}],"strict":true}