From 661f2a3de0ef0f9e021d500b21ec0e20a2581dc9 Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Tue, 8 Sep 2026 02:05:33 +0800 Subject: [PATCH 1/6] Reflow comments and reformat the shell scripts Mechanical result of running "make indent" over the tree, so that the checks added in the next commit start from a clean base. No C code changes. Verified rather than asserted: with comments stripped and whitespace normalized, all 21 touched files under src, lib, tools and tests are byte-identical to their previous revisions. Reflow merges a comment block into the one beneath it, which is right when they are one thought and wrong when the first is a section heading. Five headings are handled here rather than left to read as run-on sentences. Three are reworded into the sentence that follows, so that a later reflow leaves them alone. One keeps its own block and gains a blank line, because it labels a region rather than the constant under it. Two are deleted: they were already orphaned before this branch, naming functions that are not there. The shell reformatting is shfmt reading .editorconfig: function braces move to their own line, case arms get their own ";;", and redirections gain a space. Three test scripts also had a "Check result" heading absorbed into the sentence beneath it; those are split apart again. --- .github/scripts/release-asset.sh | 10 +- lib/c.c | 72 ++++--- lib/c.h | 14 +- src/arch-lower.c | 8 +- src/arm-codegen.c | 125 +++++------ src/arm.c | 23 +- src/defs.h | 209 ++++++++++-------- src/elf.c | 120 +++++------ src/globals.c | 115 +++++----- src/lexer.c | 51 +++-- src/main.c | 28 +-- src/opt-sccp.c | 12 +- src/parser.c | 357 ++++++++++++++++--------------- src/peephole.c | 36 ++-- src/preprocessor.c | 128 +++++------ src/reg-alloc.c | 158 +++++++------- src/riscv-codegen.c | 91 ++++---- src/riscv.c | 4 +- src/ssa.c | 294 +++++++++++++------------ tests/arm-abi.sh | 84 +++++--- tests/driver.sh | 332 ++++++++++++++-------------- tests/riscv-abi.sh | 84 +++++--- tests/strength-reduce.c | 9 +- tests/x64-abi.sh | 84 +++++--- tools/inliner.c | 14 +- tools/norm-lf.c | 4 +- 26 files changed, 1308 insertions(+), 1158 deletions(-) diff --git a/.github/scripts/release-asset.sh b/.github/scripts/release-asset.sh index eb1160db..80869bb7 100755 --- a/.github/scripts/release-asset.sh +++ b/.github/scripts/release-asset.sh @@ -1,9 +1,9 @@ #!/usr/bin/env bash -# Print " " for one asset of the latest release of -# a GitHub repository. Both toolchain downloads in the workflows resolve what -# they fetch this way, so that neither pins a version that goes stale nor -# trusts an archive it has not checksummed. +# Print " " for one asset of the latest release of a +# GitHub repository. Both toolchain downloads in the workflows resolve what they +# fetch this way, so that neither pins a version that goes stale nor trusts an +# archive it has not checksummed. set -euo pipefail @@ -36,4 +36,4 @@ jq -er --arg name "$ASSET" ' error("\($name) has no sha256 digest in \($release.tag_name)") else "\($release.tag_name) \($asset.browser_download_url) \($sha256)" - end' <<<"$release" + end' <<< "$release" diff --git a/lib/c.c b/lib/c.c index b1a0f950..37081966 100644 --- a/lib/c.c +++ b/lib/c.c @@ -1,8 +1,8 @@ /* * shecc - Self-Hosting and Educational C Compiler. * - * shecc is freely redistributable under the BSD 2 clause license. See the - * file "LICENSE" for information on usage and redistribution of this file. + * shecc is freely redistributable under the BSD 2 clause license. See the file + * "LICENSE" for information on usage and redistribution of this file. */ /* minimal libc implementation */ @@ -12,8 +12,8 @@ /* Staging buffer for the printf family that writes straight to a descriptor. * * The longest single call in the tree is ssa.c's "insn_%p [label=%s]": a - * DUMP_INSN_LEN staging buffer plus 26 bytes around it, so 537. Every byte - * here is stack in every program shecc emits, so it stays close to that. + * DUMP_INSN_LEN staging buffer plus 26 bytes around it, so 537. Every byte here + * is stack in every program shecc emits, so it stays close to that. */ #define FMT_BUF_LEN 576 @@ -123,15 +123,16 @@ char *strncat(char *dest, char *src, int len) char *strchr(char *str, int ch) { int i = 0; + /* Compare both sides as bytes. * * A byte above 0x7F is the whole difficulty: comparing str[i] against the * int the caller passed fails wherever char is signed, since one side is * negative and the other is not. Converting the search value to a char is - * not enough either -- the arm backend widens a char loaded from memory - * and a char held in a variable differently, so the two disagree even - * though each promotes to -61 on its own. Masking both to 0..255 leaves - * nothing to disagree about, on any target. + * not enough either -- the arm backend widens a char loaded from memory and + * a char held in a variable differently, so the two disagree even though + * each promotes to -61 on its own. Masking both to 0..255 leaves nothing to + * disagree about, on any target. * * The terminator counts as part of the string, and a masked zero still * finds it. @@ -218,8 +219,8 @@ void *memset(void *s, int c, int n) /* set 10 digits (32bit) without div * - * This function converts a given integer value to its string representation - * in base-10 without using division operations. The method involves calculating + * This function converts a given integer value to its string representation in + * base-10 without using division operations. The method involves calculating * the approximate quotient and remainder using bitwise operations, which are * then used to derive each digit of the result. * @@ -227,11 +228,10 @@ void *memset(void *s, int c, int n) * detailed in the reference link: * http://web.archive.org/web/20180517023231/http://www.hackersdelight.org/divcMore.pdf. * This approach avoids expensive division instructions by using a series of - * bitwise shifts and additions to calculate the quotient and remainder. - */ -/* Pointer width of the target, held in a variable rather than tested with - * the preprocessor: shecc must be able to compile this file for either - * target, and a constant condition would leave statically dead code behind. + * bitwise shifts and additions to calculate the quotient and remainder. Pointer + * width of the target, held in a variable rather than tested with the + * preprocessor: shecc must be able to compile this file for either target, and + * a constant condition would leave statically dead code behind. */ int __ptr_width = __SIZEOF_POINTER__; @@ -243,9 +243,9 @@ void __str_base10(char *pb, int val) /* On a 32-bit target, negating INT_MIN overflows and the digit loop below * cannot make progress, so the value is spelled out directly. On LP64 the - * negation happens in a 64-bit register and the normal path is exact. - * This is an ordinary constant expression rather than a preprocessor - * conditional so that shecc can compile this file for either target. + * negation happens in a 64-bit register and the normal path is exact. This + * is an ordinary constant expression rather than a preprocessor conditional + * so that shecc can compile this file for either target. */ if (__ptr_width == 4 && val == -2147483648) { strncpy(pb + INT_BUF_LEN - 11, "-2147483648", 11); @@ -322,10 +322,10 @@ void __str_base16(char *pb, int val) * - On success, the return value should be the length of the entire converted * string even if n is insufficient to store it. * - * Thus, a structure fmtbuf_t is defined for formatted output conversion for - * the functions in the printf() family. + * Thus, a structure fmtbuf_t is defined for formatted output conversion for the + * functions in the printf() family. * @buf: the current position of the buffer. - * @n : the remaining space of the buffer. + * @n : the remaining space of the buffer. * @len: the number of characters that would have been written (excluding the * null terminator) had n been sufficiently large. * @@ -344,8 +344,8 @@ void __fmtbuf_write_char(fmtbuf_t *fmtbuf, int val) { fmtbuf->len += 1; - /* Write the given character when n is greater than 1. - * This means preserving one position for the null character. + /* Write the given character when n is greater than 1. This means preserving + * one position for the null character. */ if (fmtbuf->n <= 1) return; @@ -360,8 +360,8 @@ void __fmtbuf_write_str(fmtbuf_t *fmtbuf, char *str, int l) { fmtbuf->len += l; - /* Write the given string when n is greater than 1. - * This means preserving one position for the null character. + /* Write the given string when n is greater than 1. This means preserving + * one position for the null character. */ if (fmtbuf->n <= 1) return; @@ -457,8 +457,9 @@ void __format(fmtbuf_t *fmtbuf, void __format_to_buf(fmtbuf_t *fmtbuf, char *format, int *var_args) { int si = 0, pi = 0; - /* A pointer-width view of the same argument area, for %s. Reading a - * pointer argument through an int would truncate it on LP64. + + /* A pointer-width view of the same argument area, for %s. Reading a pointer + * argument through an int would truncate it on LP64. */ char **var_args_p = (char **) var_args; @@ -512,11 +513,11 @@ void __format_to_buf(fmtbuf_t *fmtbuf, char *format, int *var_args) case 'p': { /* Append param as a pointer. * - * A pointer occupies VA_INT_STEP int-sized slots, so on an - * LP64 target the second one carries the high word. Printing - * only @v would drop it, and the graph writer in ssa.c names - * its nodes after these values, so two objects sharing a low - * word would collapse into one node. + * A pointer occupies VA_INT_STEP int-sized slots, so on an LP64 + * target the second one carries the high word. Printing only @v + * would drop it, and the graph writer in ssa.c names its nodes + * after these values, so two objects sharing a low word would + * collapse into one node. * * A pointer has one spelling here, "0x" and its significant * digits, so any width or zero-pad in the format is ignored. @@ -532,6 +533,7 @@ void __format_to_buf(fmtbuf_t *fmtbuf, char *format, int *var_args) __fmtbuf_write_char(fmtbuf, 'x'); if (hi) { __format(fmtbuf, hi, 0, 0, 16, 0); + /* The low word keeps its leading zeros, or the two halves * would run together into a different number. */ @@ -649,9 +651,9 @@ FILE *fopen(char *filename, char *mode) if (!strcmp(mode, "w") || !strcmp(mode, "wb")) { /* Flags below are O_WRONLY | O_CREAT | O_TRUNC. Without O_TRUNC, - * writing a shorter file over a longer one leaves the old tail in - * place -- which turns a rebuilt executable into the new image - * followed by a fragment of the previous one. + * writing a shorter file over a longer one leaves the old tail in place + * -- which turns a rebuilt executable into the new image followed by a + * fragment of the previous one. * * "wb" writes an executable and opens 0775; "w" writes text, which has * no business being executable, and opens 0666 before the umask. diff --git a/lib/c.h b/lib/c.h index 0ed9d6a1..d9be4b15 100644 --- a/lib/c.h +++ b/lib/c.h @@ -1,8 +1,8 @@ /* * shecc - Self-Hosting and Educational C Compiler. * - * shecc is freely redistributable under the BSD 2 clause license. See the - * file "LICENSE" for information on usage and redistribution of this file. + * shecc is freely redistributable under the BSD 2 clause license. See the file + * "LICENSE" for information on usage and redistribution of this file. */ #pragma once @@ -55,6 +55,7 @@ #define __syscall_lseek 8 #define __syscall_mmap 9 #define __syscall_munmap 11 + /* x86-64 provides no mmap2. Every call site passes offset 0, so mmap2's * page-granular offset is indistinguishable from mmap's byte offset here. */ @@ -75,8 +76,8 @@ typedef int *va_list; /* Every variadic argument occupies one pointer-sized stack slot, so an - * int-based va_list must advance this many elements per argument: one on - * the 32-bit targets, two on LP64. + * int-based va_list must advance this many elements per argument: one on the + * 32-bit targets, two on LP64. */ #define VA_INT_STEP (__SIZEOF_POINTER__ / 4) @@ -100,9 +101,10 @@ int fclose(FILE *stream); int fgetc(FILE *stream); char *fgets(char *str, int n, FILE *stream); int fputc(int c, FILE *stream); + /* Only under dynamic linking, where the host libc supplies them and buffers - * behind them. A statically linked program has neither, and moves whole - * blocks through '__syscall' instead. + * behind them. A statically linked program has neither, and moves whole blocks + * through '__syscall' instead. */ int fread(char *ptr, int size, int nmemb, FILE *stream); int fwrite(char *ptr, int size, int nmemb, FILE *stream); diff --git a/src/arch-lower.c b/src/arch-lower.c index e9cc8545..d8ee19be 100644 --- a/src/arch-lower.c +++ b/src/arch-lower.c @@ -10,11 +10,11 @@ #include "../config" #include "defs.h" -/* Mark detached conditional branches so codegen can decide between - * short/long forms without re-deriving CFG shape. +/* Mark detached conditional branches so codegen can decide between short/long + * forms without re-deriving CFG shape. * - * Only the ARM backend reads 'is_branch_detached'; RISC-V and x86-64 ignore - * it, so the pass runs for ARM alone. + * Only the ARM backend reads 'is_branch_detached'; RISC-V and x86-64 ignore it, + * so the pass runs for ARM alone. */ void arch_lower(void) { diff --git a/src/arm-codegen.c b/src/arm-codegen.c index ccecd96e..e6b584e0 100644 --- a/src/arm-codegen.c +++ b/src/arm-codegen.c @@ -1,8 +1,8 @@ /* * shecc - Self-Hosting and Educational C Compiler. * - * shecc is freely redistributable under the BSD 2 clause license. See the - * file "LICENSE" for information on usage and redistribution of this file. + * shecc is freely redistributable under the BSD 2 clause license. See the file + * "LICENSE" for information on usage and redistribution of this file. */ /* Translate IR to target machine code */ @@ -90,8 +90,8 @@ void update_elf_offset(ph2_ir_t *ph2_ir) if (func->bbs) elf_offset += 4; else if (dynlink) { - /* When calling external functions in dynamic linking mode, - * the following instructions are required: + /* When calling external functions in dynamic linking mode, the + * following instructions are required: * - movw + movt: set r8 to 'elf_data_start' * - ldr: load a word from the address 'elf_data_start' into r12. * (restore the global stack pointer.) @@ -196,9 +196,8 @@ void cfg_flatten(void) * (to ensure 8-byte alignment after pushing the 9 registers) * - ALIGN_UP(func->stack_size, 8) * - * Note that func->stack_size does not include the 36 + 4 bytes, - * so an additional 40 bytes should be added to - * ALIGN_UP(func->stack_size, 8). + * Note that func->stack_size does not include the 36 + 4 bytes, so an + * additional 40 bytes should be added to ALIGN_UP(func->stack_size, 8). */ int stack_top_ofs = ALIGN_UP(func->stack_size, MIN_ALIGNMENT) + 40; @@ -225,9 +224,9 @@ void cfg_flatten(void) 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. + /* 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; } @@ -393,17 +392,17 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) abort(); } - /* When calling external functions in dynamic linking mode, - * the following instructions are required: + /* When calling external functions in dynamic linking mode, the + * following instructions are required: * - movw + movt: set r8 to 'elf_data_start' * - ldr: load a word from the address 'elf_data_start' to r12. * (restore the global stack pointer.) * * Since shecc uses r12 to store a global stack pointer and external - * functions can freely modify r12, causing internal functions to - * access global variables incorrectly, additional instructions are - * needed to restore r12 from the global object after the external - * function returns. + * functions can freely modify r12, causing internal functions to access + * global variables incorrectly, additional instructions are needed to + * restore r12 from the global object after the external function + * returns. * * Otherwise, only a 'bl' instruction is generated to call internal * functions because shecc guarantees they do not modify r12. @@ -487,8 +486,7 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) return; } interm = __r8; - /* div/mod emulation */ - /* Preserve the values of the dividend and divisor */ + /* div/mod emulation: preserve the dividend and the divisor */ emit(__stmdb(__AL, 1, __sp, (1 << rn) | (1 << rm))); /* Obtain absolute values of the dividend and divisor */ emit(__srl_amt(__AL, 0, arith_rs, __r8, rn, 31)); @@ -524,6 +522,7 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) emit(__srl_amt(__AL, 1, logic_rs, __r9, __r9, 1)); emit(__srl_amt(__CC, 0, logic_rs, rm, rm, 1)); emit(__b(__CC, -20)); + /* After completing the emulation, the quotient and remainder will be * stored in __r8 and __r9, respectively. * @@ -630,32 +629,35 @@ void code_generate(void) emit(__mov_i(__AL, __r3, 0)); emit(__bl(__AL, (dynamic_sections.elf_plt_start + PLT_FIXUP_SIZE) - (elf_code_start + elf_code->size))); + /* Call '_exit' (syscall) to terminate the program if __libc_start_main - * returns. */ + * returns. + */ emit(__mov_i(__AL, __r0, 127)); emit(__mov_i(__AL, __r7, 1)); emit(__svc()); - /* If the compiled program is dynamic linking, the starting - * point of 'main_wrapper' is located here. + /* If the compiled program is dynamic linking, the starting point of + * 'main_wrapper' is located here. * - * Push the contents of r4-r11 and lr onto stack. - * Preserve 'argc' and 'argv' for the 'main' function. + * Push the contents of r4-r11 and lr onto stack. Preserve 'argc' and + * 'argv' for the 'main' function. */ emit(__stmdb(__AL, 1, __sp, 0x4FF0)); emit(__mov_r(__AL, __r9, __r0)); emit(__mov_r(__AL, __r10, __r1)); } - /* For both static and dynamic linking, we need to set up the stack - * and call the main function. + + /* For both static and dynamic linking, we need to set up the stack and call + * the main function. * - * To ensure that the stack remains 8-byte aligned after adjustment, - * 'ofs' is to align(GLOBAL_FUNC->stack_size, 8) to allocate space - * for the global stack. + * To ensure that the stack remains 8-byte aligned after adjustment, 'ofs' + * is to align(GLOBAL_FUNC->stack_size, 8) to allocate space for the global + * stack. * - * In dynamic linking mode, since the preceding __stmdb instruction - * pushes 9 registers onto stack, 'ofs' must be increased by 4 to - * prevent the stack from becoming misaligned. + * In dynamic linking mode, since the preceding __stmdb instruction pushes 9 + * registers onto stack, 'ofs' must be increased by 4 to prevent the stack + * from becoming misaligned. */ ofs = ALIGN_UP(GLOBAL_FUNC->stack_size, MIN_ALIGNMENT); if (dynlink) @@ -664,21 +666,22 @@ void code_generate(void) emit(__movt(__AL, __r8, ofs)); emit(__sub_r(__AL, __sp, __sp, __r8)); emit(__mov_r(__AL, __r12, __sp)); - /* The first object in the .data section is used to store the global - * stack pointer. Therefore, store r12 at the address 'elf_data_start' - * after the global stack has been prepared. + + /* The first object in the .data section is used to store the global stack + * pointer. Therefore, store r12 at the address 'elf_data_start' after the + * global stack has been prepared. */ emit(__movw(__AL, __r8, elf_data_start)); emit(__movt(__AL, __r8, elf_data_start)); emit(__sw(__AL, __r12, __r8, 0)); if (!dynlink) { - /* Jump directly to the main preparation and then execute the - * main function. + /* Jump directly to the main preparation and then execute the main + * function. * * In static linking mode, when the main function completes its - * execution, it will invoke the '_exit' syscall to terminate - * the program. + * execution, it will invoke the '_exit' syscall to terminate the + * program. * * That is, the execution flow is: * @@ -707,12 +710,12 @@ void code_generate(void) /* __syscall - only for static linking * * If the number of arguments is greater than 4, the additional - * arguments need to be retrieved from the stack. However, this - * process must modify the contents of registers r4-r7. + * arguments need to be retrieved from the stack. However, this process + * must modify the contents of registers r4-r7. * * Therefore, __syscall needs to preserve the contents of these - * registers before invoking a syscall, and restore them after - * the syscall has completed. + * registers before invoking a syscall, and restore them after the + * syscall has completed. */ emit(__stmdb(__AL, 1, __sp, 0x00F0)); emit(__lw(__AL, __r4, __sp, 16)); @@ -741,12 +744,12 @@ void code_generate(void) if (dynlink) { emit(__mov_r(__AL, __r0, __r9)); emit(__mov_r(__AL, __r1, __r10)); + /* Call the main function. * - * After the main function returns, the following - * instructions restore the registers r4-r11 and - * return control to __libc_start_main via the - * preserved lr. + * After the main function returns, the following instructions + * restore the registers r4-r11 and return control to + * __libc_start_main via the preserved lr. */ emit(__bl(__AL, MAIN_BB->elf_offset - elf_code->size)); emit(__movw(__AL, __r8, ofs)); @@ -760,12 +763,13 @@ void code_generate(void) emit(__lw(__AL, __r0, __r8, 0)); emit(__add_i(__AL, __r1, __r8, 4)); - /* Call main function, and call '_exit' syscall to - * terminate the program. */ + /* Call main function, and call '_exit' syscall to terminate the + * program. + */ emit(__bl(__AL, MAIN_BB->elf_offset - elf_code->size)); - /* exit with main's return value - r0 already has the - * return value */ + /* exit with main's return value - r0 already has the return value + */ emit(__mov_i(__AL, __r7, 1)); emit(__svc()); } @@ -781,29 +785,28 @@ void plt_generate(void) { /* - PLT code generation explanation - * - * As described in ARM's Platform Standard, PLT code should make register - * ip address the corresponding GOT entry on SVr4-like (Linux-like) - * platforms. + * As described in ARM's Platform Standard, PLT code should make register ip + * address the corresponding GOT entry on SVr4-like (Linux-like) platforms. * - * Therefore, PLT[1] ~ PLT[N] use r12 (ip) to load the address of the - * GOT entry and jump to the function entry via the GOT value. + * Therefore, PLT[1] ~ PLT[N] use r12 (ip) to load the address of the GOT + * entry and jump to the function entry via the GOT value. * * PLT[0] is used to call the resolver, which requires: * - [sp] contains the return address from the original function call. * - ip contains the address of the GOT entry. * - lr points to the address of GOT[2]. * - * The second requirement is alreadly handled by PLT[1] - PLT[N], so - * PLT[0] must take care of the other two. The first one can be achieved - * by a 'push' instruction; for the third, we use r10 to store the address - * of GOT[2] and then move the value to lr. + * The second requirement is alreadly handled by PLT[1] - PLT[N], so PLT[0] + * must take care of the other two. The first one can be achieved by a + * 'push' instruction; for the third, we use r10 to store the address of + * GOT[2] and then move the value to lr. * * - Reason for using r10 in PLT[0] - * * The register allocation assumes 8 available registers, so the ARM code * generator primarily uses r0-r7 for code generation. These registers - * cannot be modified arbitrarily; otherwise, the program may fail if any - * of them are changed by PLT[0]. + * cannot be modified arbitrarily; otherwise, the program may fail if any of + * them are changed by PLT[0]. * * However, r8-r11 can be freely used as temporary registers during code * generation, so PLT[0] arbitrarily chooses r10 to perform the required diff --git a/src/arm.c b/src/arm.c index 8a07ca92..6bc83555 100644 --- a/src/arm.c +++ b/src/arm.c @@ -1,8 +1,8 @@ /* * shecc - Self-Hosting and Educational C Compiler. * - * shecc is freely redistributable under the BSD 2 clause license. See the - * file "LICENSE" for information on usage and redistribution of this file. + * shecc is freely redistributable under the BSD 2 clause license. See the file + * "LICENSE" for information on usage and redistribution of this file. */ /* ARMv7-A instruction encoding */ @@ -22,8 +22,8 @@ * | +------- always * +------------ branch * - * Machine-level "b" instructions have restricted ranges from the address of - * the current instruction. + * Machine-level "b" instructions have restricted ranges from the address of the + * current instruction. */ #include "defs.h" @@ -44,8 +44,7 @@ typedef enum { arm_stmdb = 16 } arm_op_t; -/* Condition code - * Reference: +/* Condition code Reference: * https://community.arm.com/developer/ip-products/processors/b/processors-ip-blog/posts/condition-codes-1-condition-flags-and-codes */ typedef enum { @@ -230,8 +229,7 @@ int __sll_amt(arm_cond_t cond, int __sra(arm_cond_t cond, arm_reg rd, arm_reg rm, arm_reg rs) { - /* Arithmetic right shift with register - * Bit 4 = 1 (register-specified shift) + /* Arithmetic right shift with register Bit 4 = 1 (register-specified shift) * Bits 5-6 = arith_rs (2) for arithmetic right shift */ return arm_encode(cond, 0 + (arm_mov << 1) + (0 << 5), 0, rd, @@ -260,11 +258,10 @@ int __zero(int rd) return __mov_i(__AL, rd, 0); } -/* ARM halfword transfer (immediate offset) using special encoding - * For halfword: bits[11:8] = imm4H, bits[7:4] = encoding, bits[3:0] = imm4L - * imm4H: upper 4 bits of offset - * imm4L: lower 4 bits of offset - * encoding: 0b1011 for unsigned halfword, 0b1111 for signed halfword +/* ARM halfword transfer (immediate offset) using special encoding For halfword: + * bits[11:8] = imm4H, bits[7:4] = encoding, bits[3:0] = imm4L imm4H: upper 4 + * bits of offset imm4L: lower 4 bits of offset encoding: 0b1011 for unsigned + * halfword, 0b1111 for signed halfword */ int arm_halfword_transfer(arm_cond_t cond, int l, diff --git a/src/defs.h b/src/defs.h index 02ba4e4f..3f206584 100644 --- a/src/defs.h +++ b/src/defs.h @@ -1,8 +1,8 @@ /* * shecc - Self-Hosting and Educational C Compiler. * - * shecc is freely redistributable under the BSD 2 clause license. See the - * file "LICENSE" for information on usage and redistribution of this file. + * shecc is freely redistributable under the BSD 2 clause license. See the file + * "LICENSE" for information on usage and redistribution of this file. */ #pragma once @@ -18,10 +18,10 @@ * * A host compiler's runtime does, and so does the one a dynamically linked * shecc reaches through the PLT. The embedded lib/c.c does not: it has no - * buffer behind fgets() or fputc(), so every byte would become its own - * read(2) or write(2). Those builds call the kernel directly instead, which - * is available on exactly the same condition, since '__syscall' is - * synthesized only for static linking. + * buffer behind fgets() or fputc(), so every byte would become its own read(2) + * or write(2). Those builds call the kernel directly instead, which is + * available on exactly the same condition, since '__syscall' is synthesized + * only for static linking. */ #ifdef __SHECC__ #ifdef __SHECC_DYNLINK__ @@ -38,6 +38,7 @@ #define MAX_VAR_LEN 128 /* ".label." plus an int, for basic_block_t's dump name. */ #define MAX_LABEL_LEN 24 + /* Staging buffer for one instruction's Graphviz label in bb_dump(). The widest * case is a binary operator: three MAX_VAR_LEN names, three subscripts, an * operator and 41 bytes of markup. @@ -75,12 +76,14 @@ #define MAX_GOTPLT 1024 #define MAX_CONSTANTS 1024 #define MAX_NESTING 128 + /* How many instructions an if may speculate when flattened into a select, and * how many blocks one of its arms may span. Beyond a handful, running the arm * that would have been skipped costs more than the misprediction it avoids. */ #define MAX_SPECULATED_INSNS 8 #define MAX_IF_ARM_BLOCKS 4 + /* Recursion limits for nesting in the input. The parser descends recursively * for each of these, so a deeply nested program would otherwise exhaust the * machine stack before any diagnostic could be produced. @@ -90,8 +93,9 @@ #define MAX_OPERAND_STACK_SIZE 32 #define MAX_ANALYSIS_STACK_SIZE 1600 -/* Default capacities for common data structures */ -/* Arena sizes optimized based on typical usage patterns */ +/* Default capacities for common data structures, with the arena sizes taken + * from typical usage patterns. + */ #define DEFAULT_ARENA_SIZE 262144 /* 256 KiB - standard default */ #define SMALL_ARENA_SIZE 65536 /* 64 KiB - for small allocations */ #define LARGE_ARENA_SIZE 524288 /* 512 KiB - for instruction arena */ @@ -124,9 +128,10 @@ do { \ ; \ } while (0) -/* shecc runs on the target it compiles for, so the host pointer width is - * the target's. This must not be hardcoded to 4: on an LP64 target the - * var_list allocations and memcpy sizes below would be half what they need. + +/* shecc runs on the target it compiles for, so the host pointer width is the + * target's. This must not be hardcoded to 4: on an LP64 target the var_list + * allocations and memcpy sizes below would be half what they need. */ #define HOST_PTR_SIZE PTR_SIZE #else @@ -181,8 +186,8 @@ #define MIN_IV_CHAIN 3 #define MAX_IV_PER_LOOP 2 -/* How many blocks one natural loop's walk keeps in hand at once. Past this - * the walk stops widening, which can only understate a depth. +/* How many blocks one natural loop's walk keeps in hand at once. Past this the + * walk stops widening, which can only understate a depth. */ #define MAX_LOOP_WALK 512 @@ -195,18 +200,17 @@ #define MAX_INLINE_VARS 32 #define MAX_INLINE_ROUNDS 3 -/* What a naming inside one loop is worth against one in straight-line code, - * and how many nesting levels still multiply it. +/* What a naming inside one loop is worth against one in straight-line code, and + * how many nesting levels still multiply it. */ #define LOOP_USE_WEIGHT 8 #define MAX_WEIGHTED_LOOP_DEPTH 3 -/* How many registers at the top of the allocator's file a call preserves. - * Only such a register can hold a value across a call, so only these may be - * given to a variable for the whole of a function that calls anything. A - * target states its own count in mk/.mk, beside the REG_CNT that fixes - * the file it counts from; a target that has not had its file checked this way - * keeps none. +/* How many registers at the top of the allocator's file a call preserves. Only + * such a register can hold a value across a call, so only these may be given to + * a variable for the whole of a function that calls anything. A target states + * its own count in mk/.mk, beside the REG_CNT that fixes the file it + * counts from; a target that has not had its file checked this way keeps none. * * HAVE_COND_MOVE comes from the same place and says whether the target can * select between two values without branching, which is what makes flattening @@ -328,15 +332,17 @@ typedef enum { T_cppd_ifdef, T_cppd_ifndef, T_cppd_pragma, - /* C pre-processor specific, these kinds - * will be removed after pre-processing is done. + + /* C pre-processor specific, these kinds will be removed after + * pre-processing is done. */ T_newline, T_backslash, T_whitespace, T_tab, - /* '#' and '##' inside a macro replacement list. Resolved while the macro - * is expanded, so neither ever reaches the parser. + + /* '#' and '##' inside a macro replacement list. Resolved while the macro is + * expanded, so neither ever reaches the parser. */ T_hash, T_hashhash @@ -462,6 +468,7 @@ typedef enum { } opcode_t; /* variable definition */ + /* Depth of the SSA renaming stack: how many definitions of one variable can be * live along a single dominator path. */ @@ -469,6 +476,7 @@ typedef enum { typedef struct { int counter; + /* Grown on demand: only a base variable is ever renamed, so the SSA * versions copied from it -- the large majority of all variables -- would * otherwise each carry an unused MAX_RENAME_STACK array. @@ -504,12 +512,13 @@ typedef struct var_list { struct var { type_t *type; + /* Interned, not copied. A MAX_VAR_LEN array was 128 of this struct's 312 * bytes on every one of the ~86k variables a self-compile creates, and - * var_t is embedded by value MAX_FIELDS times in each type_t and - * MAX_PARAMS times in each func_t, so the array cost another 8 KiB per - * type. Generated temporary names come from gen_name(); source-level names - * come from intern_string(). Never NULL -- an unnamed variable holds "". + * var_t is embedded by value MAX_FIELDS times in each type_t and MAX_PARAMS + * times in each func_t, so the array cost another 8 KiB per type. Generated + * temporary names come from gen_name(); source-level names come from + * intern_string(). Never NULL -- an unnamed variable holds "". */ char *var_name; int ptr_level; @@ -519,29 +528,31 @@ struct var { bool address_taken; /* true if variable address was taken (&var) */ /* Working state for strength_reduce(): how many instructions in the * function write the variable, whether it is written inside the loop being - * examined, and how much its value moves per iteration when it does. - * All three are recomputed per loop; nothing outside that pass reads them. + * examined, and how much its value moves per iteration when it does. All + * three are recomputed per loop; nothing outside that pass reads them. */ int def_cnt; int loop_stamp; int iv_gen; int iv_step; + /* pin_registers()'s tally for the variable: what its namings are worth * weighted by loop depth, the reverse-post-order number of the last block - * that named it, whether it was - * ever named in two, and whether a loop named it. Stamped per function so - * that no array has to hold the candidates -- the file has a handful of - * registers and a function names hundreds of variables, and the one worth - * a register is not reliably among the first few met. + * that named it, whether it was ever named in two, and whether a loop named + * it. Stamped per function so that no array has to hold the candidates -- + * the file has a handful of registers and a function names hundreds of + * variables, and the one worth a register is not reliably among the first + * few met. */ int pin_gen; int pin_weight; int pin_blk; bool pin_cross; bool pin_hot; + /* Defined inside an arm that if_convert() flattened into a select. The - * register its variable is pinned to still holds the value flowing into - * the select, which the arms read and the select overwrites, so a value + * register its variable is pinned to still holds the value flowing into the + * select, which the arms read and the select overwrites, so a value * computed on the way there must go somewhere else. */ bool in_select_arm; @@ -558,6 +569,7 @@ struct var { int merge_gen; struct var *base; int subscript; + /* Every SSA version of this variable, grown on demand. A fixed 128-entry * array made every var_t 1 KiB heavier -- and var_t is embedded by value in * type_t's field table and in every function's parameter list -- while the @@ -567,6 +579,7 @@ struct var { struct var **subscripts; int subscripts_idx; int subscripts_cap; + /* SSA renaming state, allocated on first use by var_rename(). Only a base * variable is ever renamed, so the versions copied from it -- the large * majority of all variables -- each carried an unused 24-byte rename_t @@ -588,13 +601,13 @@ struct var { bool space_is_allocated; /* whether space is allocated for this variable */ bool has_backing_storage; - /* This flag is used to indicate to the compiler that the offset of - * the variable is based on the top of the local stack. + /* This flag is used to indicate to the compiler that the offset of the + * variable is based on the top of the local stack. */ bool ofs_based_on_stack_top; - /* True when this variable was synthesized to hold a compound literal - * (e.g., array or struct literal temporaries). + /* True when this variable was synthesized to hold a compound literal (e.g., + * array or struct literal temporaries). */ bool is_compound_literal; }; @@ -613,7 +626,7 @@ typedef struct block block_t; typedef struct basic_block basic_block_t; /* Definition of a growable buffer for a mutable null-terminated string - * @size: Current number of elements in the array + * @size: Current number of elements in the array * @capacity: Number of elements that can be stored without resizing * @elements: Pointer to the array of characters */ @@ -626,13 +639,11 @@ typedef struct { /* phase-2 IR definition */ struct ph2_ir { /* Grouped by width so the struct carries no interior padding: mixed in - * declaration order it was 72 bytes for 63 bytes of fields, on all ~101k - * of them a self-compile emits. - */ - /* Callee / definition name, interned in GENERAL_ARENA rather than copied. - * A MAX_VAR_LEN array here was 128 of this struct's 192 bytes while only - * OP_define, OP_call and OP_address_of_func ever name anything. NULL when - * unused. + * declaration order it was 72 bytes for 63 bytes of fields, on all ~101k of + * them a self-compile emits. Callee / definition name, interned in + * GENERAL_ARENA rather than copied. A MAX_VAR_LEN array here was 128 of + * this struct's 192 bytes while only OP_define, OP_call and + * OP_address_of_func ever name anything. NULL when unused. */ char *func_name; basic_block_t *next_bb; @@ -650,13 +661,14 @@ struct ph2_ir { int size_bytes; /* Size in bytes for load/store/read/write operations */ bool is_branch_detached; - /* When an instruction uses a variable that its offset is based on - * the top of the stack, this instruction's flag is also set to - * indicate the compiler to recalculate the offset after the function's - * stack size has been determined. + + /* When an instruction uses a variable that its offset is based on the top + * of the stack, this instruction's flag is also set to indicate the + * compiler to recalculate the offset after the function's stack size has + * been determined. * - * Currently, only OP_load, OP_store and OP_address_of need this flag - * to recompute the offset. + * Currently, only OP_load, OP_store and OP_address_of need this flag to + * recompute the offset. */ bool ofs_based_on_stack_top; bool is_pointer; /* True if this operation involves a pointer type */ @@ -670,8 +682,9 @@ struct type { base_type_t base_type; struct type *base_struct; int size; - /* Member table, allocated when the type is created rather than inlined. - * A MAX_FIELDS array of var_t by value made type_t 12 KiB, and TYPES is a + + /* Member table, allocated when the type is created rather than inlined. A + * MAX_FIELDS array of var_t by value made type_t 12 KiB, and TYPES is a * flat MAX_TYPES array that global_init() zeroes up front -- 3 MiB of * resident memory for the 90 types a self-compile actually declares. */ @@ -710,6 +723,7 @@ struct insn { var_t *rd; var_t *rs1; var_t *rs2; + /* The value OP_cmov keeps when its condition does not hold. A select needs * three inputs and the two source fields are taken by the chosen value and * the condition; every other opcode leaves this NULL. @@ -719,6 +733,7 @@ struct insn { bool useful; /* Used in DCE process. Set true if instruction is useful. */ basic_block_t *belong_to; phi_operand_t *phi_ops; + /* Callee or goto-label name, interned rather than copied. add_insn() * already interned the text before copying it in, so the array was 64 of * this struct's 136 bytes for a string the pool owns anyway, on all ~73k @@ -773,23 +788,25 @@ struct basic_block { * while almost every block has one or two predecessors. */ bb_connection_t *prev; + /* Register file on entry to this block, captured by reg_alloc() at the end * of the predecessor it falls out of. Non-NULL only when a file was handed * over, and bb_export_regs() does that only for an edge that is all three * of: the predecessor's single successor, that predecessor's rpo_next, and * this block's single predecessor. A sole predecessor alone is NOT enough - * -- a branch target is emitted wherever the backend's linear walk puts - * it, so the registers reaching it are not the ones the branch left. + * -- a branch target is emitted wherever the backend's linear walk puts it, + * so the registers reaching it are not the ones the branch left. * * Allocated only for a block that actually inherits a file: fewer than one - * block in thirteen does, so a REG_CNT array here cost 64 bytes on all - * ~51k blocks to serve 7% of them. + * block in thirteen does, so a REG_CNT array here cost 64 bytes on all ~51k + * blocks to serve 7% of them. */ struct var **entry_regs; + /* Used in instruction dumping when ir_dump is enabled, and allocated only * then: a fixed array here is 128 bytes on every one of the tens of - * thousands of blocks a self-compile creates, all of it zeroed for - * nothing in the default path. + * thousands of blocks a self-compile creates, all of it zeroed for nothing + * in the default path. */ char *bb_label_name; struct basic_block *next; /* normal BB */ @@ -799,6 +816,7 @@ struct basic_block { struct basic_block *r_idom; struct basic_block *rpo_next; struct basic_block *rpo_r_next; + /* Dominance and reverse-dominance frontiers. These were fixed worst-case * arrays sized MAX_BB_DOM_SUCC / MAX_BB_RDOM_SUCC, which cost 2560 bytes in * every basic block while a typical block uses a handful of entries. Worse, @@ -808,6 +826,7 @@ struct basic_block { */ struct basic_block **DF; struct basic_block **RDF; + /* Dominator-tree children, grown on demand for the same reason as prev[] * and the frontiers: a fixed array cost half a kilobyte in every block. */ @@ -818,6 +837,7 @@ struct basic_block { block_t *scope; int prev_cap; + /* One past the highest slot bb_connect() has ever filled. Scans of prev[] * stop here instead of walking all MAX_BB_PRED slots; a block typically has * one or two predecessors, so the difference is two orders of magnitude. @@ -825,6 +845,7 @@ struct basic_block { * bound and the NULL checks in each loop still skip the holes. */ int prev_idx; + /* Index of this block's first instruction in PH2_IR_FLATTEN, or -1 when it * emitted none. Recorded while that mapping is built so the backend need * not search for it. @@ -832,20 +853,23 @@ struct basic_block { int ph2_base; int rpo; int rpo_r; - /* How many loops enclose the block. - * pin_registers() weights a variable's uses by it: a name inside a loop - * stands for as many reads as the loop has iterations, and ranking by the - * plain count gave a register to a variable named four times in - * straight-line code over one named once in the innermost loop. + + /* How many loops enclose the block. pin_registers() weights a variable's + * uses by it: a name inside a loop stands for as many reads as the loop has + * iterations, and ranking by the plain count gave a register to a variable + * named four times in straight-line code over one named once in the + * innermost loop. */ int loop_depth; + /* What one naming of a variable in this block is worth to pin_registers(), * derived from loop_depth once rather than on every operand of every * instruction the tally walks. */ int loop_weight; - /* Stamp marking the block as already counted for the loop being walked, - * so that one loop raises its depth once however many ways in there are. + + /* Stamp marking the block as already counted for the loop being walked, so + * that one loop raises its depth once however many ways in there are. */ int loop_mark; int df_idx; @@ -888,6 +912,7 @@ struct func { var_t param_defs[MAX_PARAMS]; int num_params; int va_args; + /* inline_calls()'s verdict on this body and the return that ends it, * stamped with the round that reached them: a body is examined once per * round rather than once per call site that names it. @@ -910,10 +935,10 @@ struct func { int saved_regs; /* Registers holding a variable for the whole function, one bit each. The - * backend needs these: its notion of what is live out of a block comes - * from the successor's entry registers, which say nothing about a variable - * that is resident everywhere, and it would otherwise drop the code that - * puts a value into one as dead. + * backend needs these: its notion of what is live out of a block comes from + * the successor's entry registers, which say nothing about a variable that + * is resident everywhere, and it would otherwise drop the code that puts a + * value into one as dead. */ int pinned_regs; @@ -940,8 +965,8 @@ typedef struct { int polluted; } regfile_t; -/* In the ELF specification, the following data types are defined - * for data representation: +/* In the ELF specification, the following data types are defined for data + * representation: * * +-------------+------+-----------+--------------------------+ * | Name | Size | Alignment | Purpose | @@ -960,18 +985,17 @@ typedef struct { * | char | | | | * +-------------+------+-----------+--------------------------+ * - * However, since the current implementation doesn't support unsigned - * data type definitions, such as 'unsigned int', 'unsigned short' and - * so on, the ELF structures now are implemented using the 'signed' data - * type primarily. - * - Elf32_Addr -> int - * - Elf32_Half -> short - * - Elf32_Off -> int - * - Elf32_Word -> int + * However, since the current implementation doesn't support unsigned data type + * definitions, such as 'unsigned int', 'unsigned short' and so on, the ELF + * structures now are implemented using the 'signed' data type primarily. + * - Elf32_Addr -> int + * - Elf32_Half -> short + * - Elf32_Off -> int + * - Elf32_Word -> int * - unsigned char -> char * - * TODO: Use correct unsigned types for these ELF structures after - * the 'unsigned' specifier is supported. + * TODO: Use correct unsigned types for these ELF structures after the + * 'unsigned' specifier is supported. */ /* ELF header */ @@ -1018,8 +1042,7 @@ typedef struct { int sh_entsize; /* Elf32_Word */ } elf32_shdr_t; -/* Structures for dynamic linked program */ -/* ELF buffers for dynamic sections */ +/* Structures for dynamic linked program ELF buffers for dynamic sections */ typedef struct { strbuf_t *elf_interp; strbuf_t *elf_dynamic; @@ -1039,11 +1062,11 @@ typedef struct { int plt_size; int got_size; - /* Currently, we don't consider the scenarios involving - * a mixture of REL and RELA relocation entries. + /* 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: + * Therefore, use a flag to determine the type of relocation entries to be + * processed: * - true: use RELA relocation entries * - false: use REL relocation entries */ diff --git a/src/elf.c b/src/elf.c index c38efdba..78274813 100644 --- a/src/elf.c +++ b/src/elf.c @@ -1,8 +1,8 @@ /* * shecc - Self-Hosting and Educational C Compiler. * - * shecc is freely redistributable under the BSD 2 clause license. See the - * file "LICENSE" for information on usage and redistribution of this file. + * shecc is freely redistributable under the BSD 2 clause license. See the file + * "LICENSE" for information on usage and redistribution of this file. */ /* ELF file manipulation */ @@ -21,8 +21,8 @@ void elf_write_str(strbuf_t *elf_array, const char *vals) { /* Note that strbuf_puts() does not push the null character. * - * If necessary, use elf_write_byte() to append the null character - * after calling elf_write_str(). + * If necessary, use elf_write_byte() to append the null character after + * calling elf_write_str(). */ if (!elf_array || !vals) return; @@ -77,10 +77,10 @@ void elf_write_blk(strbuf_t *elf_array, void *blk, int sz) strbuf_putc(elf_array, ptr[i]); } -/* The dynamic-linking tables differ only in width between the two ELF - * classes, so the generator below writes them through these helpers rather - * than memcpy-ing a struct: shecc has no 64-bit integer type, so an ELF64 - * entry cannot be expressed as a C struct here at all. +/* The dynamic-linking tables differ only in width between the two ELF classes, + * so the generator below writes them through these helpers rather than + * memcpy-ing a struct: shecc has no 64-bit integer type, so an ELF64 entry + * cannot be expressed as a C struct here at all. */ int elf_sym_size(void) @@ -140,6 +140,7 @@ void elf_write_jmprel(strbuf_t *buf, int offset, int sym_idx) { #if ELF_IS_64 == 1 elf_write_quad(buf, offset); + /* r_info is (symbol << 32) | type, so the two halves are written in * little-endian order as type followed by symbol index. */ @@ -165,8 +166,8 @@ void elf_write_got_slot(strbuf_t *buf, int val) } /* Place the dynamic sections. Every start is derived from the end of .rodata, - * so a backend whose final code size is only known after emission can call - * this again once it is. + * so a backend whose final code size is only known after emission can call this + * again once it is. */ void elf_layout_dynamic(void) { @@ -184,8 +185,8 @@ void elf_layout_dynamic(void) dynamic_sections.elf_plt_start = ro_end + relplt_bytes; /* .interp opens the second load segment, so start it a page clear of the - * first: the two must not share a page, and the offset must stay - * congruent to the address modulo the page size. + * first: the two must not share a page, and the offset must stay congruent + * to the address modulo the page size. */ dynamic_sections.elf_interp_start = dynamic_sections.elf_plt_start + dynamic_sections.plt_size + PAGESIZE; @@ -285,8 +286,9 @@ void elf_generate_header(void) elf_rodata->size + elf_symtab->size + elf_strtab->size + elf_shstrtab->size; } - /* The following table explains the meaning of each field in the - * ELF32 file header. + + /* The following table explains the meaning of each field in the ELF32 file + * header. * * Notice that the following values are hexadecimal. * @@ -379,8 +381,7 @@ void elf_generate_program_headers(void) #if ELF_IS_64 == 1 /* Two ELF64 PT_LOAD segments, 56 bytes each. Field order differs from - * ELF32: p_flags sits immediately after p_type rather than before - * p_align. + * ELF32: p_flags sits immediately after p_type rather than before p_align. */ int ro_size = elf_header_len + elf_code->size + elf_rodata->size; if (dynlink) @@ -399,9 +400,9 @@ void elf_generate_program_headers(void) /* read-write segment. Statically linked it holds .data (plus .bss, which * occupies no file space) and starts at the next page boundary so that * p_vaddr === p_offset (mod p_align), which the kernel enforces. - * Dynamically linked it begins at .interp and covers everything the - * loader needs, which elf_preprocess() has already placed a page clear of - * the read-only segment. + * Dynamically linked it begins at .interp and covers everything the loader + * needs, which elf_preprocess() has already placed a page clear of the + * read-only segment. */ int data_file_ofs = ALIGN_UP(ro_size, PAGESIZE); int rw_vaddr = elf_data_start; @@ -566,11 +567,11 @@ void elf_generate_program_headers(void) void elf_generate_section_headers(void) { #if ELF_IS_64 == 0 - /* x86-64 output carries no section headers; the program headers alone - * are sufficient to load and run the image. The body below is therefore - * compiled out entirely for that target -- leaving it after an early - * return would make it unreachable code, which shecc's own parser - * rejects when it compiles this file. + /* x86-64 output carries no section headers; the program headers alone are + * sufficient to load and run the image. The body below is therefore + * compiled out entirely for that target -- leaving it after an early return + * would make it unreachable code, which shecc's own parser rejects when it + * compiles this file. */ strbuf_t *elf_relplt = elf_relplt_buf(); @@ -590,9 +591,8 @@ void elf_generate_section_headers(void) elf32_shdr_t shdr; int ofs = elf_header_len, sh_name = 0; - /* - * The following table uses the text section header as an example - * to explain the ELF32 section header. + /* The following table uses the text section header as an example to explain + * the ELF32 section header. * * | Section | | * & | Header bytes | Explanation | @@ -882,8 +882,8 @@ void elf_align_to(strbuf_t *elf_array, int boundary) elf_write_byte(elf_array, 0); } -/* Pad to a four-byte boundary, which is what the sections holding words want. - * A section whose contents are read as pointers wants elf_align_to(PTR_SIZE) +/* Pad to a four-byte boundary, which is what the sections holding words want. A + * section whose contents are read as pointers wants elf_align_to(PTR_SIZE) * instead: on a 64-bit target four bytes is not enough. */ void elf_align(strbuf_t *elf_array) @@ -901,9 +901,8 @@ void elf_generate_dynamic_sections(void) { strbuf_t *elf_relplt = elf_relplt_buf(); - /* In dynamic linking mode, elf_generate_sections() also generates - * .interp, .dynsym, .dynstr, .rel.plt (.rela.plt), .got and dynamic - * sections. + /* In dynamic linking mode, elf_generate_sections() also generates .interp, + * .dynsym, .dynstr, .rel.plt (.rela.plt), .got and dynamic sections. * * .plt section is generated at the code generation phase. */ @@ -913,8 +912,9 @@ void elf_generate_dynamic_sections(void) /* .interp section */ elf_write_str(dynamic_sections.elf_interp, DYN_LINKER); elf_write_byte(dynamic_sections.elf_interp, 0); - /* .got follows .interp and the loader writes pointers into it, so pad - * to a pointer boundary rather than the usual four bytes. + + /* .got follows .interp and the loader writes pointers into it, so pad to a + * pointer boundary rather than the usual four bytes. */ elf_align_to(dynamic_sections.elf_interp, PTR_SIZE); @@ -936,8 +936,8 @@ void elf_generate_dynamic_sections(void) * - Append the external function name to .dynstr section. * - Set plt_offset for the external function. * - * Since __libc_start_main is not added to the function list, - * it must be handled additionally first. + * Since __libc_start_main is not added to the function list, it must be + * handled additionally first. */ rel_offset = dynamic_sections.elf_got_start + PTR_SIZE * RESERVED_GOT_NUM; elf_write_jmprel(elf_relplt, rel_offset, dymsym_idx); @@ -951,17 +951,18 @@ void elf_generate_dynamic_sections(void) elf_write_byte(dynamic_sections.elf_dynstr, 0); st_name += strlen("__libc_start_main") + 1; - /* Because PLT[1] is reserved for __libc_start_main, its plt_offset - * must be PLT_FIXUP_SIZE. Therefore, no offset assignment is - * required for this function. + /* Because PLT[1] is reserved for __libc_start_main, its plt_offset must be + * PLT_FIXUP_SIZE. Therefore, no offset assignment is required for this + * function. */ func_plt_ofs = PLT_FIXUP_SIZE + PLT_ENT_SIZE; for (func_t *func = FUNC_LIST.head; func; func = func->next) { 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 the function is used and has no basic block, consider it to be an + * external function. */ rel_offset += PTR_SIZE; elf_write_jmprel(elf_relplt, rel_offset, dymsym_idx); @@ -978,6 +979,7 @@ void elf_generate_dynamic_sections(void) func_plt_ofs += PLT_ENT_SIZE; } + /* .dynsym begins where .dynstr ends, and its entries are read as aligned * words: 24 bytes each under ELF64, which wants 8. Four-byte alignment * would leave a string table ending 4 bytes off an 8-byte boundary, and @@ -1002,9 +1004,8 @@ void elf_generate_dynamic_sections(void) switch (ELF_MACHINE) { case ELF_MACHINE_ARM32: case ELF_MACHINE_X86_64: - /* GOT[0] holds the address of .dynamic. The GOT is still being - * built, so its final size comes from got_size rather than the - * buffer. + /* GOT[0] holds the address of .dynamic. The GOT is still being built, + * so its final size comes from got_size rather than the buffer. */ elf_write_got_slot(dynamic_sections.elf_got, dynamic_sections.elf_got_start + @@ -1024,9 +1025,10 @@ void elf_generate_dynamic_sections(void) i += PTR_SIZE) { int slot = dynamic_sections.elf_plt_start; if (ELF_MACHINE == ELF_MACHINE_X86_64) + /* x86-64 reaches the resolver through the push in its own PLT - * entry, which supplies the relocation index, rather than - * jumping straight to PLT[0]. + * entry, which supplies the relocation index, rather than jumping + * straight to PLT[0]. */ slot = dynamic_sections.elf_plt_start + PLT_FIXUP_SIZE + got_idx * PLT_ENT_SIZE + 6; @@ -1073,8 +1075,8 @@ void elf_generate_dynamic_sections(void) elf_write_dyn(dynamic_sections.elf_dynamic, 0x1, 0x1); #if DYN_BIND_NOW == 1 /* Resolve every PLT entry at load time. This target's PLT[0] does not - * arrange the GOT[1]/GOT[2] hand-off the lazy resolver needs, so the - * loader writes the final addresses straight into the GOT instead. + * 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 */ @@ -1111,9 +1113,7 @@ void elf_generate_sections(void) if (dynlink) elf_generate_dynamic_sections(); - /* shstr section; len = 53 - * If using dynamic linking, len = 105. - */ + /* shstr section; len = 53 If using dynamic linking, len = 105. */ elf_write_byte(elf_shstrtab, 0); elf_write_str(elf_shstrtab, ".text"); elf_write_byte(elf_shstrtab, 0); @@ -1203,8 +1203,8 @@ void elf_preprocess(void) * - Common: * - The remaining entries correspond to all external functions. * - * Next, consider the case of __libc_start_main before initializing - * the sizes: + * Next, consider the case of __libc_start_main before initializing the + * sizes: * - .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 RESERVED_GOT_NUM + 1 entries. @@ -1240,9 +1240,9 @@ void elf_preprocess(void) elf_data_start = elf_dynamic_start() + dynamic_sections.elf_dynamic->size; } else { - /* To prevent two load segments from sharing a common page, add - * PAGESIZE to elf_data_start, since the first section of the second - * load segment is .data in static linking mode. + /* To prevent two load segments from sharing a common page, add PAGESIZE + * to elf_data_start, since the first section of the second load segment + * is .data in static linking mode. */ elf_data_start = elf_rodata_start + elf_rodata->size + PAGESIZE; } @@ -1262,8 +1262,8 @@ void elf_postprocess(void) * * The image was written a byte at a time through fputc(), which costs a call * into the C library for every one of the several hundred thousand bytes of a - * self-compile -- and, once shecc is compiled by itself, a write(2) for each - * of them, because its own libc has no buffer behind fputc(). + * self-compile -- and, once shecc is compiled by itself, a write(2) for each of + * them, because its own libc has no buffer behind fputc(). * * That reasoning holds only where lib/c.c is the libc in the output. A host * compiler's runtime already buffers fwrite(), and so does the one a @@ -1354,8 +1354,8 @@ void elf_generate(const char *outfile) /* Other sections and section headers. * - * ELF64 output emits no section headers, so the symbol and string - * tables have nothing to reference and are left out of the image. + * ELF64 output emits no section headers, so the symbol and string tables + * have nothing to reference and are left out of the image. */ #if ELF_IS_64 == 0 elf_write_all(fp, elf_symtab->elements, elf_symtab->size); diff --git a/src/globals.c b/src/globals.c index 33d04064..34cf5e28 100644 --- a/src/globals.c +++ b/src/globals.c @@ -1,8 +1,8 @@ /* * shecc - Self-Hosting and Educational C Compiler. * - * shecc is freely redistributable under the BSD 2 clause license. See the - * file "LICENSE" for information on usage and redistribution of this file. + * shecc is freely redistributable under the BSD 2 clause license. See the file + * "LICENSE" for information on usage and redistribution of this file. */ #pragma once @@ -54,7 +54,8 @@ arena_t *BLOCK_ARENA; arena_t *BB_ARENA; /* TOKEN_ARENA is responsible for token_t (including literal) / - * source_location_t allocation */ + * source_location_t allocation + */ arena_t *TOKEN_ARENA; /* GENERAL_ARENA is responsible for functions, symbols, constants, aliases, @@ -161,8 +162,8 @@ arena_t *arena_init(int initial_capacity) return arena; } -/* Allocate memory from the given arena with given size. - * The arena may create a new arena block if no space is available. +/* Allocate memory from the given arena with given size. The arena may create a + * new arena block if no space is available. * @arena: The arena to allocate memory from. Must not be NULL. * @size: The size of memory to allocate. Must be positive. * @@ -182,7 +183,8 @@ void *arena_alloc(arena_t *arena, int size) if (!arena->head || arena->head->offset + size > arena->head->capacity) { /* Need a new block: choose capacity = max(DEFAULT_ARENA_SIZE, - * arena->block_size, size) */ + * arena->block_size, size) + */ const int base = (arena->block_size > DEFAULT_ARENA_SIZE ? arena->block_size : DEFAULT_ARENA_SIZE); @@ -200,8 +202,8 @@ void *arena_alloc(arena_t *arena, int size) /* arena_alloc() plus explicit zero‑initialization. * @arena: The arena to allocate memory from. Must not be NULL. - * @n: Number of elements. - * @size: Size of each element in bytes. + * @n: Number of elements. + * @size: Size of each element in bytes. * * Internally calls arena_alloc(n * size) and then fills the entire region with * zero bytes. @@ -337,10 +339,9 @@ void arena_free(arena_t *arena) free(arena); } -/* Hash a string with FNV-1a hash function - * and converts into usable hashmap index. The range of returned - * hashmap index is ranged from "(0 ~ 2,147,483,647) mod size" due to - * lack of unsigned integer implementation. +/* Hash a string with FNV-1a hash function and converts into usable hashmap + * index. The range of returned hashmap index is ranged from "(0 ~ + * 2,147,483,647) mod size" due to lack of unsigned integer implementation. * @size: The size of map. Must not be negative or 0. * @key: The key string. May be NULL. * @@ -374,10 +375,9 @@ int round_up_pow2(int v) return v; } -/* Create a hashmap on heap. Notice that provided size will always be rounded - * up to nearest power of 2. - * @size: The initial bucket size of hashmap. Must not be 0 or - * negative. +/* Create a hashmap on heap. Notice that provided size will always be rounded up + * to nearest power of 2. + * @size: The initial bucket size of hashmap. Must not be 0 or negative. * * Return: The pointer of created hashmap. */ @@ -449,9 +449,8 @@ void hashmap_rehash(hashmap_t *map) free(old_table); } -/* Put a key-value pair into given hashmap. - * If key already contains a value, then replace it with new value, the old - * value will be freed. +/* Put a key-value pair into given hashmap. If key already contains a value, + * then replace it with new value, the old value will be freed. * @map: The hashmap to be put into. Must not be NULL. * @key: The key string. May be NULL. * @val: The value pointer. May be NULL. This value's lifetime is held by @@ -609,6 +608,7 @@ ph2_ir_t *add_ph2_ir(opcode_t op) ph2_ir->is_branch_detached = 0; ph2_ir->src0 = 0; ph2_ir->src1 = 0; + /* Only a select names a third source, but the allocation is not zeroed and * every field is set here by hand. */ @@ -619,6 +619,7 @@ ph2_ir_t *add_ph2_ir(opcode_t op) ph2_ir->then_bb = NULL; ph2_ir->else_bb = NULL; ph2_ir->ofs_based_on_stack_top = false; + /* Default to the full slot. Slots are PTR_SIZE wide, so a wide access is * always valid; only an address-taken narrow scalar may be written behind * the allocator's back, and reg-alloc narrows those explicitly. @@ -853,15 +854,15 @@ int parse_numeric_constant(char *buffer) /* Give @type its field table, on the first field it is asked for. * - * The table is MAX_FIELDS var_t by value, and it has to stay put: a struct - * body hands out a var_t * per declarator and reads it again after the next + * The table is MAX_FIELDS var_t by value, and it has to stay put: a struct body + * hands out a var_t * per declarator and reads it again after the next * declarator has been added, so a table that grew by reallocating would leave * those pointers behind. Allocating it once at full size keeps them valid. * * What it need not do is allocate for a type that never has a field. Most of * what add_type() creates -- every enum, every typedef of a scalar, every - * builtin -- has none, and was paying for the whole table and for the loop - * that walked it. + * builtin -- has none, and was paying for the whole table and for the loop that + * walked it. */ void type_ensure_fields(type_t *type) { @@ -869,6 +870,7 @@ void type_ensure_fields(type_t *type) return; type->fields = arena_calloc(GENERAL_ARENA, MAX_FIELDS, sizeof(var_t)); + /* The field variables come out of a zeroed allocation, so give their * interned name pointers the empty string a reader can dereference. */ @@ -1015,8 +1017,8 @@ int size_var(var_t *var) { int size; if (var->ptr_level > 0 || var->is_func) { - /* Pointers and function pointers occupy a target pointer, which is - * 8 bytes on LP64 targets and 4 on the 32-bit ones. + /* Pointers and function pointers occupy a target pointer, which is 8 + * bytes on LP64 targets and 4 on the 32-bit ones. */ size = PTR_SIZE; } else { @@ -1057,11 +1059,11 @@ func_t *add_func(char *func_name, bool synthesize) func->param_defs[i].var_name = ""; /* Use interned string for function name */ func->return_def.var_name = intern_string(func_name); + /* Prepare space for function arguments. * - * For Arm architecture, the first four arguments (arg1 ~ arg4) are - * passed to r0 ~ r3, and any additional arguments (arg5+) are passed - * to the stack. + * For Arm architecture, the first four arguments (arg1 ~ arg4) are passed + * to r0 ~ r3, and any additional arguments (arg5+) are passed to the stack. * * +-------------+ * | local vars | @@ -1077,8 +1079,8 @@ func_t *add_func(char *func_name, bool synthesize) * | arg 5 | * +-------------+ <-- sp * - * If the target architecture is RISC-V, arg1 ~ arg8 are passed to - * registers and arg9+ are passed to the stack. + * If the target architecture is RISC-V, arg1 ~ arg8 are passed to registers + * and arg9+ are passed to the stack. * * We reserve one slot per stack-passed argument at the bottom of every * frame so that each function can use the space to pass extra arguments. @@ -1124,16 +1126,17 @@ basic_block_t *bb_create(block_t *parent) /* Initialize non-zero fields */ bb->scope = parent; bb->belong_to = parent->func; - /* -1 marks "no machine code emitted for this block yet". Backends assign - * a real offset as they emit; 0 is a legitimate offset, so it cannot - * double as the sentinel. + + /* -1 marks "no machine code emitted for this block yet". Backends assign a + * real offset as they emit; 0 is a legitimate offset, so it cannot double + * as the sentinel. */ bb->elf_offset = -1; if (dump_ir || dump_dot) { - /* MAX_VAR_LEN spent 128 bytes on a string that is always ".label." - * plus an int. A self-compile calls bb_create() 52k times, so that - * was 6.4 MiB of arena where 1.2 MiB does. + /* MAX_VAR_LEN spent 128 bytes on a string that is always ".label." plus + * an int. A self-compile calls bb_create() 52k times, so that was 6.4 + * MiB of arena where 1.2 MiB does. */ bb->bb_label_name = arena_alloc(GENERAL_ARENA, MAX_LABEL_LEN); snprintf(bb->bb_label_name, MAX_LABEL_LEN, ".label.%d", bb_label_idx++); @@ -1370,6 +1373,7 @@ void add_insn(block_t *block, n->rd = rd; n->rs1 = rs1; n->rs2 = rs2; + /* Only a select names a third source. The allocation is not zeroed and * every field is set here by hand, so this one has to be too. */ @@ -1483,8 +1487,8 @@ void strbuf_free(strbuf_t *src) free(src); } -/* This routine is required because the global variable initializations are - * not supported now. +/* This routine is required because the global variable initializations are not + * supported now. */ void global_init(void) { @@ -1559,18 +1563,17 @@ void global_init(void) /* Forward declaration for lexer cleanup */ void lexer_cleanup(void); -/* Free empty trailing blocks from an arena safely. - * This only frees blocks that come after the last used block, - * ensuring no pointers are invalidated. +/* Free empty trailing blocks from an arena safely. This only frees blocks that + * come after the last used block, ensuring no pointers are invalidated. * * NOTE: measured over a self-compile, this reclaims nothing. arena_alloc() * prepends each new block at the head, so the list runs newest-to-oldest and * every block behind the head is full by construction: last_used is always the * tail and there is never anything after it to free. The only case that ever * fires is an arena whose very first allocation was larger than its initial - * block, leaving that block at offset 0 behind a newer one. Reclaiming a - * bump allocator's memory needs a phase boundary that can drop a whole arena - * -- see release_token_arena() -- not a scan for empty blocks. + * block, leaving that block at offset 0 behind a newer one. Reclaiming a bump + * allocator's memory needs a phase boundary that can drop a whole arena -- see + * release_token_arena() -- not a scan for empty blocks. * * @arena: The arena to compact. * Return: Bytes freed. @@ -1616,9 +1619,9 @@ int arena_free_trailing_blocks(arena_t *arena) * Every token, macro, hide set and conditional-inclusion record lives in * TOKEN_ARENA, and nothing survives parsing: identifiers and string literals * reach the parser through intern_string(), which copies into GENERAL_ARENA, - * and every parser entry point copies a token's text into a local buffer - * before storing it. So once parse() returns, all 17 MiB of it is garbage that - * would otherwise stay resident through the memory peak in reg_alloc(). + * and every parser entry point copies a token's text into a local buffer before + * storing it. So once parse() returns, all 17 MiB of it is garbage that would + * otherwise stay resident through the memory peak in reg_alloc(). * * The source buffers in SRC_FILE_MAP exist only to quote a line in a parse * error, so they go at the same time. @@ -1649,8 +1652,8 @@ void release_token_arena(void) } } -/* Compact all arenas to reduce memory usage after compilation phases. - * This safely frees only trailing empty blocks without invalidating pointers. +/* Compact all arenas to reduce memory usage after compilation phases. This + * safely frees only trailing empty blocks without invalidating pointers. * * Return: Total bytes freed across all arenas. */ @@ -1668,8 +1671,8 @@ int compact_all_arenas(void) return total_saved; } -/* Compact specific arenas based on compilation phase. - * Different phases have different memory usage patterns. +/* Compact specific arenas based on compilation phase. Different phases have + * different memory usage patterns. * * @phase_mask: Bitmask using COMPACT_ARENA_* defines * to indicate which arenas to compact. @@ -1748,8 +1751,9 @@ void global_release(void) void fatal(char *msg) { printf("[Error]: %s\n", msg); - /* abort() does not flush, so a diagnostic written to a pipe -- a build - * log, or any invocation whose output is captured -- is discarded and the + + /* abort() does not flush, so a diagnostic written to a pipe -- a build log, + * or any invocation whose output is captured -- is discarded and the * compiler appears to die silently. */ fflush(stdout); @@ -1767,9 +1771,8 @@ void usage_error(char *msg) exit(1); } -/* Reports error and prints occurred position context, - * if the given location is NULL or source file is missing, - * then fallbacks to fatal(char *). +/* Reports error and prints occurred position context, if the given location is + * NULL or source file is missing, then fallbacks to fatal(char *). */ void error_at(char *msg, source_location_t *loc) { diff --git a/src/lexer.c b/src/lexer.c index c90f2437..f2df170c 100644 --- a/src/lexer.c +++ b/src/lexer.c @@ -1,8 +1,8 @@ /* * shecc - Self-Hosting and Educational C Compiler. * - * shecc is freely redistributable under the BSD 2 clause license. See the - * file "LICENSE" for information on usage and redistribution of this file. + * shecc is freely redistributable under the BSD 2 clause license. See the file + * "LICENSE" for information on usage and redistribution of this file. */ #include #include @@ -138,8 +138,8 @@ void lexer_cleanup() } /* Token storage arrays are allocated from GENERAL_ARENA and will be - * automatically freed when the arena is freed in global_release(). - * No need to explicitly free them here. + * automatically freed when the arena is freed in global_release(). No need + * to explicitly free them here. */ directive_tokens_storage = NULL; keyword_tokens_storage = NULL; @@ -870,12 +870,11 @@ token_t *lex_token(strbuf_t *buf, source_location_t *loc) if (isalnum(ch) || ch == '_') { int sz = 0; do { - /* Bounded by the smallest buffer an identifier is ever copied - * into, not by the token buffer's own size: lex_ident() and - * lex_peek() strcpy into caller arrays of MAX_ID_LEN, so a longer - * name would run off the end of one. Diagnosing it here is what - * keeps a long identifier in the input from corrupting the - * compiler's stack. + /* Bounded by the smallest buffer an identifier is ever copied into, + * not by the token buffer's own size: lex_ident() and lex_peek() + * strcpy into caller arrays of MAX_ID_LEN, so a longer name would + * run off the end of one. Diagnosing it here is what keeps a long + * identifier in the input from corrupting the compiler's stack. */ if (sz >= MAX_ID_LEN - 1) { loc->len = sz; @@ -962,10 +961,10 @@ token_t *lex_token(strbuf_t *buf, source_location_t *loc) break; } - /* Fall back to the hashmap for anything the switch does not name. - * No keyword is shorter than two characters or longer than eight, so a - * name outside that range cannot be one and needs no lookup -- which - * is most of the identifiers in a real program. + /* Fall back to the hashmap for anything the switch does not name. No + * keyword is shorter than two characters or longer than eight, so a + * name outside that range cannot be one and needs no lookup -- which is + * most of the identifiers in a real program. */ if (kind == T_identifier && sz >= 2 && sz <= 8) kind = lookup_keyword(token_buffer); @@ -988,10 +987,10 @@ token_stream_t *gen_file_token_stream(char *filename) token_t head; token_t *cur = &head; token_stream_t *tks; - /* initialie source location with the following configuration: - * pos is at 0, - * len is 1 for reporting convenience, - * and the column and line number are set to 1. + + /* initialie source location with the following configuration: pos is at 0, + * len is 1 for reporting convenience, and the column and line number are + * set to 1. */ source_location_t loc = {0, 1, 1, 1, filename}; strbuf_t *buf; @@ -1051,10 +1050,10 @@ token_stream_t *gen_libc_token_stream() hashmap_put(SRC_FILE_MAP, filename, LIBC_SRC); /* This buffer was built by appending, so its capacity is whatever the - * doubling left and runs past the text into memory that was never - * written -- while the scan below, like the one over a file, stops at - * capacity. Terminate it the way read_file() leaves a file: the text, a - * NUL, and capacity naming one past the text. Without this the lexer reads + * doubling left and runs past the text into memory that was never written + * -- while the scan below, like the one over a file, stops at capacity. + * Terminate it the way read_file() leaves a file: the text, a NUL, and + * capacity naming one past the text. Without this the lexer reads * uninitialised bytes, and what it finds there depends on the allocator, * which is enough to make the compiler emit different code from one build * to the next. @@ -1069,9 +1068,8 @@ token_stream_t *gen_libc_token_stream() while (buf->size < buf->capacity) { tk = lex_token(buf, &loc); - /* Early break to discard eof token, so later - * we can concat libc token stream with actual - * input file's token stream. + /* Early break to discard eof token, so later we can concat libc token + * stream with actual input file's token stream. */ if (tk->kind == T_eof) break; @@ -1204,8 +1202,7 @@ void lex_ident(token_kind_t token, char *value) error_at("Unexpected token", &tk->location); } -/* Strictly match next token with given token type. - */ +/* Strictly match next token with given token type. */ void lex_expect(token_kind_t token) { if (cur_token->next && cur_token->next->kind == token) { diff --git a/src/main.c b/src/main.c index 8fcf52d6..e29cc066 100644 --- a/src/main.c +++ b/src/main.c @@ -1,8 +1,8 @@ /* * shecc - Self-Hosting and Educational C Compiler. * - * shecc is freely redistributable under the BSD 2 clause license. See the - * file "LICENSE" for information on usage and redistribution of this file. + * shecc is freely redistributable under the BSD 2 clause license. See the file + * "LICENSE" for information on usage and redistribution of this file. */ #include @@ -71,9 +71,9 @@ char *dot_output_name(char *input) if (slash) base = slash + 1; - /* A dot only introduces a suffix when something in the same path - * component precedes it. That rules out a directory's dot ("dir.d/file") - * and a dotfile's leading one, which would reduce ".bashrc" to ".dot". + /* A dot only introduces a suffix when something in the same path component + * precedes it. That rules out a directory's dot ("dir.d/file") and a + * dotfile's leading one, which would reduce ".bashrc" to ".dot". */ if (suffix && suffix <= base) suffix = NULL; @@ -85,6 +85,7 @@ char *dot_output_name(char *input) if (suffix) base_len = suffix - input; + /* strlen, not sizeof: shecc types a string literal as a pointer, so * sizeof(".dot") is 1 once the compiler is compiling itself. */ @@ -148,8 +149,8 @@ int main(int argc, char *argv[]) out = dot_output_name(in); /* The graph is written by truncating its output, so naming the input - * destroys the source. That happens both when -o names it outright and - * when an input already ending in .dot derives its own name. + * destroys the source. That happens both when -o names it outright and when + * an input already ending in .dot derives its own name. */ if (dump_dot && !strcmp(out, in)) usage_error("--dot would overwrite the input; name another output"); @@ -207,8 +208,8 @@ int main(int argc, char *argv[]) unwind_phi(); - /* Copy small helpers into their callers before anything else looks at - * them, so the optimizer sees one body rather than a call boundary. + /* Copy small helpers into their callers before anything else looks at them, + * so the optimizer sees one body rather than a call boundary. */ inline_calls(); @@ -227,9 +228,9 @@ int main(int argc, char *argv[]) /* Flatten unpredictable ifs into branchless selects. * * After the optimizer rather than inside ssa_build(): a select reads a - * third operand, and the passes in optimize() walk instructions two - * sources at a time. One of them would rewrite a copy feeding that third - * operand and leave the select reading a value nothing defines. + * third operand, and the passes in optimize() walk instructions two sources + * at a time. One of them would rewrite a copy feeding that third operand + * and leave the select reading a value nothing defines. */ if_convert(); @@ -275,8 +276,7 @@ int main(int argc, char *argv[]) if (dump_ir) dump_ph2_ir(); - /* - * ELF preprocess: + /* ELF preprocess: * 1. generate all sections except for .text section. * 2. calculate the starting addresses of certain sections. */ diff --git a/src/opt-sccp.c b/src/opt-sccp.c index bc1d7541..efb833bf 100644 --- a/src/opt-sccp.c +++ b/src/opt-sccp.c @@ -1,8 +1,8 @@ /* * shecc - Self-Hosting and Educational C Compiler. * - * shecc is freely redistributable under the BSD 2 clause license. See the - * file "LICENSE" for information on usage and redistribution of this file. + * shecc is freely redistributable under the BSD 2 clause license. See the file + * "LICENSE" for information on usage and redistribution of this file. */ /* Constant cast optimization pass. @@ -14,8 +14,8 @@ /* Narrow a constant to 'size' bytes, keeping its sign. Every integer type in * this language is signed -- there is no 'unsigned' keyword -- and widening - * sign-extends, so masking alone would make "char c = -1" compare as 255. - * A size the caller does not narrow is returned unchanged. + * sign-extends, so masking alone would make "char c = -1" compare as 255. A + * size the caller does not narrow is returned unchanged. */ int sign_extend_const(int value, int size) { @@ -48,8 +48,8 @@ bool optimize_constant_casts(func_t *func) insn = insn->next) { insn_t *next_insn = insn->next; - /* Look for pattern: const %.tX, VALUE followed by - * %.tY = trunc %.tX, SIZE + /* Look for pattern: const %.tX, VALUE followed by %.tY = trunc + * %.tX, SIZE */ if (insn->opcode == OP_load_constant && next_insn->opcode == OP_trunc && insn->rd && next_insn->rs1 && diff --git a/src/parser.c b/src/parser.c index 34d12cd9..24db42f7 100644 --- a/src/parser.c +++ b/src/parser.c @@ -1,8 +1,8 @@ /* * shecc - Self-Hosting and Educational C Compiler. * - * shecc is freely redistributable under the BSD 2 clause license. See the - * file "LICENSE" for information on usage and redistribution of this file. + * shecc is freely redistributable under the BSD 2 clause license. See the file + * "LICENSE" for information on usage and redistribution of this file. */ #include #include @@ -68,13 +68,13 @@ void add_label(char *name, basic_block_t *bb) l->bb = bb; } -/* Name for a compiler-generated temporary, interned so that the var_t only - * has to hold a pointer to it. +/* Name for a compiler-generated temporary, interned so that the var_t only has + * to hold a pointer to it. * - * This is the parser's most frequent call by a wide margin -- one per - * temporary value -- and sprintf() spends most of a call parsing a format - * string that never changes. Writing the fixed prefix and the decimal digits - * directly produces the same name for a fraction of the work. + * This is the parser's most frequent call by a wide margin -- one per temporary + * value -- and sprintf() spends most of a call parsing a format string that + * never changes. Writing the fixed prefix and the decimal digits directly + * produces the same name for a fraction of the work. */ char *gen_name(void) { @@ -111,6 +111,7 @@ var_t *require_var(block_t *blk) var_t *var = arena_calloc(BLOCK_ARENA, 1, sizeof(var_t)); var_list->elements[var_list->size++] = var; + /* var_name is a pointer now; every reader dereferences it unconditionally, * so an unnamed variable points at the empty string rather than NULL. */ @@ -138,10 +139,11 @@ var_t *require_typed_var(block_t *blk, type_t *type) return var; } -/* Function-address operands carry a function name, but are not declarations - * in the current scope. Keeping them out of the local lookup list lets - * find_var() distinguish a resolved function-pointer variable from a - * generated function symbol. */ +/* Function-address operands carry a function name, but are not declarations in + * the current scope. Keeping them out of the local lookup list lets find_var() + * distinguish a resolved function-pointer variable from a generated function + * symbol. + */ var_t *require_func_symbol_var(block_t *blk) { var_t *var = require_var(blk); @@ -341,11 +343,10 @@ var_t *promote_unchecked(block_t *block, { var_t *rd = require_typed_ptr_var(block, target_type, target_ptr); rd->var_name = gen_name(); - /* Encode both source and target sizes in src1: - * Lower 16 bits: target size - * Upper 16 bits: source size - * This allows codegen to distinguish between different promotion types - * without changing IR semantics. + + /* Encode both source and target sizes in src1: Lower 16 bits: target size + * Upper 16 bits: source size This allows codegen to distinguish between + * different promotion types without changing IR semantics. */ int encoded_size = ((var->type->size) << 16); if (target_ptr) @@ -406,10 +407,10 @@ var_t *resize_var(block_t *block, basic_block_t **bb, var_t *from, var_t *to) if (from_size < to_size) { /* Widening into a pointer needs no conversion instruction. Values - * already occupy a full register and integer loads sign-extend, so - * the pointer's bits are the value's bits. Emitting the conversion - * here also placed it ahead of the instructions computing its own - * operand, which produced a garbage pointer. + * already occupy a full register and integer loads sign-extend, so the + * pointer's bits are the value's bits. Emitting the conversion here + * also placed it ahead of the instructions computing its own operand, + * which produced a garbage pointer. * * On the 32-bit targets PTR_SIZE equals an int, so this case cannot * arise there and behaviour is unchanged. @@ -451,8 +452,8 @@ void read_parameter_list_decl(func_t *func, bool anon); /* Forward declaration for ternary handling used by initializers */ void read_ternary_operation(block_t *parent, basic_block_t **bb); -/* Parse array initializer to determine size for implicit arrays and - * optionally emit initialization code. +/* Parse array initializer to determine size for implicit arrays and optionally + * emit initialization code. */ var_t *compute_element_address(block_t *parent, basic_block_t **bb, @@ -522,6 +523,7 @@ var_t *parse_global_constant_value(block_t *parent, basic_block_t **bb) add_insn(parent, *bb, OP_load_constant, val, NULL, NULL, 0, NULL); } else if (lex_peek(T_string, NULL)) { lex_accept(T_string); + /* TODO: String fields in structs not yet supported - requires proper * handling of string literals as initializers */ @@ -652,8 +654,8 @@ basic_block_t *handle_return_statement(block_t *parent, basic_block_t *bb) var_t *rs1 = opstack_pop(); - /* Handle array compound literals in return context. - * Convert array compound literals to their first element value. + /* Handle array compound literals in return context. Convert array compound + * literals to their first element value. */ if (rs1 && rs1->array_size > 0 && rs1->var_name[0] == '.') { var_t *val = require_var(parent); @@ -775,14 +777,9 @@ basic_block_t *handle_goto_statement(block_t *parent, basic_block_t *bb) * wrap the goto, and connect the unreachable basic block to the else * branch. Finally, return this else block. * - * after: - * a = b + c; - * goto label; - * c *= d; + * after: a = b + c; goto label; c *= d; * - * before: - * a = b + c; - * if (1) + * before: a = b + c; if (1) * goto label; * c *= d; */ @@ -833,10 +830,10 @@ void parse_array_init(var_t *var, bool is_implicit = (var->array_size == 0); /* Elements of a pointer array are pointer-sized. Using the base type's - * width strided "char *a[2] = {...}" by one byte, so every element but - * the first got a bogus address. An implicit-size array reaches this with - * ptr_level set as a marker rather than as a real pointer type, so only - * an explicitly sized array is treated this way. + * width strided "char *a[2] = {...}" by one byte, so every element but the + * first got a bogus address. An implicit-size array reaches this with + * ptr_level set as a marker rather than as a real pointer type, so only an + * explicitly sized array is treated this way. */ int elem_size = var->type->size; if (!is_implicit && var->ptr_level > 0) @@ -887,8 +884,8 @@ void parse_array_init(var_t *var, } else { /* A global initializer is restricted to simple constants, but * it still has to be stored. Consuming the tokens and dropping - * the value left every global array zero-filled, while the - * same initializer on a local worked. + * the value left every global array zero-filled, while the same + * initializer on a local worked. */ if (parent == GLOBAL_BLOCK && !lex_peek(T_numeric, NULL) && !lex_peek(T_minus, NULL) && !lex_peek(T_string, NULL) && @@ -955,7 +952,7 @@ void parse_array_init(var_t *var, * initialize other elements without explicit assignments to 0. * * Therefore, the first and second cases return 0 and 15, respectively. - * */ + */ for (; count < var->array_size; count++) { var_t *val = require_var(parent); val->var_name = gen_name(); @@ -1033,8 +1030,9 @@ void parse_array_compound_literal(var_t *var, lex_expect(T_close_curly); var->array_size = count; } -/* Identify compiler-emitted temporaries that hold array compound literals. - * They keep array metadata without pointer indirection and are marked via + +/* Identify compiler-emitted temporaries that hold array compound literals. They + * keep array metadata without pointer indirection and are marked via * is_compound_literal when synthesized. */ bool is_array_literal_placeholder(var_t *var) @@ -1070,8 +1068,8 @@ var_t *scalarize_array_literal(block_t *parent, if (literal_size <= 0) literal_size = TY_int->size; - /* A caller-provided hint (e.g., assignment target) dictates the result - * type when available so we reuse wider/narrower scalar destinations. + /* A caller-provided hint (e.g., assignment target) dictates the result type + * when available so we reuse wider/narrower scalar destinations. */ type_t *result_type = hint_type ? hint_type : literal_type; if (!result_type) @@ -1091,8 +1089,8 @@ var_t *scalarize_array_literal(block_t *parent, return scalar; } -/* Centralized guard for lowering array literal placeholders when a scalar - * value is expected, keeping the scattered special cases consistent. +/* Centralized guard for lowering array literal placeholders when a scalar value + * is expected, keeping the scattered special cases consistent. */ var_t *scalarize_array_literal_if_needed(block_t *parent, basic_block_t **bb, @@ -1108,12 +1106,12 @@ var_t *scalarize_array_literal_if_needed(block_t *parent, /* Integer constant-expression parser. * - * Array dimensions, and other places C requires an integer constant - * expression, accept far more than a bare literal. These evaluate such an - * expression at parse time without emitting any IR, folding through the same - * precedence table (get_operator_prio()) and the same operator semantics - * (eval_expression_imm()) the rest of the parser already uses, so there is - * only one statement of what C's operators mean. + * Array dimensions, and other places C requires an integer constant expression, + * accept far more than a bare literal. These evaluate such an expression at + * parse time without emitting any IR, folding through the same precedence table + * (get_operator_prio()) and the same operator semantics (eval_expression_imm()) + * the rest of the parser already uses, so there is only one statement of what + * C's operators mean. */ #define MAX_CONST_EXPR_OPS 16 @@ -1221,18 +1219,19 @@ void read_inner_var_decl(var_t *vd, bool anon, bool is_param) /* Preserve typedef pointer level - don't reset if already inherited */ vd->init_val = 0; if (is_param) { - /* However, if the parsed variable is a function parameter, - * reset its pointer level to zero. + /* However, if the parsed variable is a function parameter, reset its + * pointer level to zero. */ vd->ptr_level = 0; } while (lex_accept(T_asterisk)) { vd->ptr_level++; - /* Check for const after asterisk (e.g., int * const ptr). - * For now, we just consume const qualifiers after pointer. - * Full support would require tracking const-ness of the pointer - * itself vs the pointed-to data separately. + + /* Check for const after asterisk (e.g., int * const ptr). For now, we + * just consume const qualifiers after pointer. Full support would + * require tracking const-ness of the pointer itself vs the pointed-to + * data separately. */ while (lex_peek(T_const, NULL)) lex_accept(T_const); @@ -1507,9 +1506,9 @@ void read_func_parameters(func_t *func, block_t *parent, basic_block_t **bb) /* Writing past 'params' corrupts this frame, and the damage only * surfaces later as a wrong argument value. The check has to come * before the conversions below: those index func->param_defs[], a - * MAX_PARAMS-element array embedded in func_t, so an over-long - * argument list reads past it and dereferences a garbage type - * pointer -- the compiler crashed instead of reporting the limit. + * MAX_PARAMS-element array embedded in func_t, so an over-long argument + * list reads past it and dereferences a garbage type pointer -- the + * compiler crashed instead of reporting the limit. */ if (param_num >= MAX_PARAMS) error_at("Too many arguments in function call", cur_token_loc()); @@ -1520,8 +1519,9 @@ void read_func_parameters(func_t *func, block_t *parent, basic_block_t **bb) param = scalarize_array_literal(parent, bb, param, target->type); } - /* Handle parameter type conversion for direct calls. - * Indirect calls currently don't provide function instance. + + /* Handle parameter type conversion for direct calls. Indirect calls + * currently don't provide function instance. */ if (func && param_num >= func->num_params && func->va_args) { /* Default promotions apply to scalar varargs, but pointer-like @@ -1613,6 +1613,7 @@ void handle_single_dereference(block_t *parent, basic_block_t **bb) lex_expect(T_close_bracket); rs1 = opstack_pop(); + /* For pointer dereference, we need to determine the target type and * size. Since we do not have full type tracking in expressions, use * defaults @@ -1679,14 +1680,14 @@ void handle_single_dereference(block_t *parent, basic_block_t **bb) } } -/* Scan ahead for an assignment operator at the top level of the statement - * that starts at the current token, stopping at its terminating semicolon. +/* Scan ahead for an assignment operator at the top level of the statement that + * starts at the current token, stopping at its terminating semicolon. * - * A statement beginning with '*' is either a store through a pointer or a - * plain expression, and the two need opposite treatment of the leading - * asterisk. Deciding by looking at tokens keeps the choice free of side - * effects: by the time an expression has been parsed, its instructions have - * already been emitted and there is no way back. + * A statement beginning with '*' is either a store through a pointer or a plain + * expression, and the two need opposite treatment of the leading asterisk. + * Deciding by looking at tokens keeps the choice free of side effects: by the + * time an expression has been parsed, its instructions have already been + * emitted and there is no way back. */ bool stmt_starts_assignment(void) { @@ -1733,8 +1734,9 @@ void handle_multiple_dereference(block_t *parent, basic_block_t **bb) var_t *vd, *rs1; int sz; - /* Handle consecutive asterisks for multiple dereference: **pp, ***ppp, - * ***(expr) */ + /* Handle consecutive asterisks for multiple dereference: **pp, ***ppp, and + * the parenthesized ***(expr) form. + */ int deref_count = 1; /* We already consumed one asterisk */ while (lex_accept(T_asterisk)) deref_count++; @@ -1928,7 +1930,8 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) } } else if (lex_accept(T_open_bracket)) { /* Check if this is a cast, compound literal, or parenthesized - * expression */ + * expression + */ char lookahead_token[MAX_ID_LEN]; bool is_compound_literal = false; bool is_cast = false; @@ -1957,6 +1960,7 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) bool is_array = false; if (lex_accept(T_open_square)) { is_array = true; + /* Skip the array size: it is discarded, and a numeric * literal can be longer than any small buffer. */ @@ -1972,6 +1976,7 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) is_compound_literal = true; cast_or_literal_type = type; cast_ptr_level = ptr_level; + /* Store is_array flag in cast_ptr_level if it's an * array */ @@ -1993,8 +1998,7 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) } if (is_cast) { - /* Process cast: (type)expr */ - /* Parse the expression to be cast */ + /* Process cast: (type)expr Parse the expression to be cast */ read_expr_operand(parent, bb); /* Get the expression result */ @@ -2084,8 +2088,8 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) } else if (cast_or_literal_type->base_type == TYPE_struct || cast_or_literal_type->base_type == TYPE_typedef) { /* Struct compound literal support (including typedef structs) + * For typedef structs, the actual struct info is in the type */ - /* For typedef structs, the actual struct info is in the type */ /* Initialize struct compound literal */ compound_var->init_val = 0; @@ -2197,8 +2201,8 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) /* Create result that provides first element access. * This enables array compound literals in scalar - * contexts: int x = (int[]){1,2,3}; // x gets 1 int y - * = 5 + (int[]){10}; // adds 5 + 10 + * contexts: int x = (int[]){1,2,3}; // x gets 1 int y = + * 5 + (int[]){10}; // adds 5 + 10 */ var_t *result_var = require_var(parent); result_var->var_name = gen_name(); @@ -2323,11 +2327,11 @@ bool is_logical(opcode_t op) return op == OP_log_and || op == OP_log_or; } -/* Helper function to calculate element size for pointer operations */ /* Consume a compound-assignment operator ("+=", "-=", ...) and report the - * arithmetic it applies. Returns false and consumes nothing when the next - * token is not one, so it can sit in an else-if chain beside the other - * statement forms. + * arithmetic it applies. + * + * Returns false and consumes nothing when the next token is not one, so it can + * sit in an else-if chain beside the other statement forms. */ bool accept_compound_assign_op(opcode_t *op) { @@ -2364,10 +2368,10 @@ int get_pointer_element_size(var_t *ptr_var) /* Direct pointer with type info. * * Only a single level of indirection points at the base type. For deeper - * pointers (int **, char ***, ...) the element is itself a pointer, so - * the step is PTR_SIZE. Returning the base type size there makes - * "q + 1" advance by 4 instead of 8 on LP64 and drops a level of type - * information from the result. + * pointers (int **, char ***, ...) the element is itself a pointer, so the + * step is PTR_SIZE. Returning the base type size there makes "q + 1" + * advance by 4 instead of 8 on LP64 and drops a level of type information + * from the result. */ if (ptr_var->ptr_level && ptr_var->type) { if (ptr_var->ptr_level > 1) @@ -2436,8 +2440,9 @@ void handle_pointer_arithmetic(block_t *parent, rs2_is_ptr = is_pointer_like_value(rs2); if (rs1_is_ptr && rs2_is_ptr) { - /* Both are pointers - this is pointer difference */ - /* Determine element size */ + /* Both are pointers - this is pointer difference Determine element + * size + */ element_size = PTR_SIZE; /* Default */ /* Get element size from the first pointer */ @@ -2644,9 +2649,8 @@ void read_expr_body(block_t *parent, basic_block_t **bb) has_prev_log_op = true; } else if (prev_log_op == OP_log_and) { /* For example: a && b || c - * previous opcode: prev_log_op == OP_log_and - * current opcode: op == OP_log_or - * current operand: b + * previous opcode: prev_log_op == OP_log_and current opcode: op + * == OP_log_or current operand: b * * Finalize the logical-and operation and test the operand for * the following logical-or operation. @@ -2683,14 +2687,13 @@ void read_expr_body(block_t *parent, basic_block_t **bb) * * Eventually, the current opcode becomes the previous opcode * and pprev opcode is set to 0. - * */ + */ prev_log_op = op; pprev_log_op = 0; } else { /* For example: a || b && c - * previous opcode: prev_log_op == OP_log_or - * current opcode: op == OP_log_and - * current operand: b + * previous opcode: prev_log_op == OP_log_or current opcode: op + * == OP_log_and current operand: b * * Using the logical-and operation to test the current operand * instead of using the logical-or operation. @@ -2891,8 +2894,8 @@ void read_expr(block_t *parent, basic_block_t **bb) * * @allow_ptr_arith says whether a following "+ expr" belongs to this lvalue. * Normally it does, and the addend is scaled by the element size. The - * dereference handlers pass false, because unary '*' binds tighter than '+': - * in "*p + 1" the sum belongs to the enclosing expression, and reading it as + * dereference handlers pass false, because unary '*' binds tighter than '+': in + * "*p + 1" the sum belongs to the enclosing expression, and reading it as * pointer arithmetic gives p[1] instead of one more than p[0]. It applies to * this lvalue alone -- an lvalue parsed further in, as a subscript or a call * argument, gets the normal behaviour from its own call. @@ -2909,8 +2912,8 @@ void read_lvalue(lvalue_t *lvalue, bool is_address_got = false; bool is_member = false; - /* Callers pass a find_var() result, which is NULL for a name that was - * never declared. + /* Callers pass a find_var() result, which is NULL for a name that was never + * declared. */ if (!var) error_at("Undeclared identifier", next_token_loc()); @@ -2934,9 +2937,8 @@ void read_lvalue(lvalue_t *lvalue, lex_peek(T_dot, NULL)) { if (lex_accept(T_open_square)) { /* if subscripted member's is not yet resolved, dereference to - * resolve base address. - * e.g., dereference of "->" in "data->raw[0]" would be performed - * here. + * resolve base address. e.g., dereference of "->" in "data->raw[0]" + * would be performed here. */ if (lvalue->is_reference && lvalue->ptr_level && is_member) { rs1 = opstack_pop(); @@ -2946,16 +2948,18 @@ void read_lvalue(lvalue_t *lvalue, add_insn(parent, *bb, OP_read, vd, rs1, NULL, PTR_SIZE, NULL); } - /* var must be either a pointer or an array of some type */ - /* For typedef pointers, check the type's ptr_level */ + /* var must be either a pointer or an array of some type For typedef + * pointers, check the type's ptr_level + */ bool is_typedef_pointer = (var->type && var->type->ptr_level > 0); if (var->ptr_level == 0 && var->array_size == 0 && !is_typedef_pointer) error_at("Cannot apply square operator to non-pointer", cur_token_loc()); - /* if nested pointer, still pointer */ - /* Also handle typedef pointers which have ptr_level == 0 */ + /* if nested pointer, still pointer Also handle typedef pointers + * which have ptr_level == 0 + */ if ((var->ptr_level <= 1 || is_typedef_pointer) && var->array_size == 0) { /* For typedef pointers, get the size of the base type that the @@ -2988,8 +2992,9 @@ void read_lvalue(lvalue_t *lvalue, read_expr(parent, bb); - /* multiply by element size */ - /* For 2D arrays, check if this is the first or second dimension */ + /* multiply by element size For 2D arrays, check if this is the + * first or second dimension + */ int multiplier = lvalue->size; /* If this is the first index of a 2D array, multiply by dim2 * @@ -3141,14 +3146,15 @@ void read_lvalue(lvalue_t *lvalue, rs2 = opstack_pop(); rs1 = opstack_pop(); vd = require_var(parent); + /* A pointer plus an integer is still a pointer of the same type; * without this the result looks like a plain int and a later * dereference reads the base type's width instead of a pointer. * - * Only genuine pointers are propagated. An array base has - * ptr_level 0, and copying that would label the sum with the - * element type, making get_size() report the element width for - * what is actually an address. + * Only genuine pointers are propagated. An array base has ptr_level + * 0, and copying that would label the sum with the element type, + * making get_size() report the element width for what is actually + * an address. */ if (var->ptr_level) { vd->type = lvalue->type; @@ -3175,6 +3181,7 @@ void read_lvalue(lvalue_t *lvalue, if (prefix_op != OP_generic) { vd = require_var(parent); vd->var_name = gen_name(); + /* For pointer arithmetic, increment by the size of pointed-to type */ if (lvalue->ptr_level) @@ -3196,6 +3203,7 @@ void read_lvalue(lvalue_t *lvalue, if (lvalue->is_reference) { rs1 = vd; vd = opstack_pop(); + /* The column of arguments of the new insn of 'OP_write' is * different from 'ph1_ir' */ @@ -3333,10 +3341,10 @@ void finalize_logical(opcode_t op, if (op == OP_log_and) { /* For example: a && b * - * If handling the expression, the basic blocks will - * connect to each other as the following illustration: + * If handling the expression, the basic blocks will connect to each + * other as the following illustration: * - * bb1 bb2 bb3 + * bb1 bb2 bb3 * +-----------+ +-----------+ +---------+ * | teq a, #0 | True | teq b, #0 | True | ldr 1 | * | bne bb2 | ----> | bne bb3 | ----> | b bb5 | @@ -3351,9 +3359,8 @@ void finalize_logical(opcode_t op, * +---------+ +--------+ * bb4 bb5 * - * In this case, finalize_logical() should add some - * instructions to bb2 ~ bb5 and properly connect them - * to each other. + * In this case, finalize_logical() should add some instructions to bb2 + * ~ bb5 and properly connect them to each other. * * Notice that * - bb1 has been handled by read_logical(). @@ -3362,10 +3369,10 @@ void finalize_logical(opcode_t op, * - bb4 is 'shared_bb'. * - bb5 needs to be created. * - * Thus, here uses 'then', 'then_next', 'else_bb' and - * 'end' to respectively point to bb2 ~ bb5. Subsequently, - * perform the mentioned operations for finalizing. - * */ + * Thus, here uses 'then', 'then_next', 'else_bb' and 'end' to + * respectively point to bb2 ~ bb5. Subsequently, perform the mentioned + * operations for finalizing. + */ then = *bb; then_next = bb_create(parent); else_bb = shared_bb; @@ -3375,12 +3382,11 @@ void finalize_logical(opcode_t op, } else if (op == OP_log_or) { /* For example: a || b * - * Similar to handling logical-and operations, it should - * add some instructions to the basic blocks and connect - * them to each other for logical-or operations as in - * the figure: + * Similar to handling logical-and operations, it should add some + * instructions to the basic blocks and connect them to each other for + * logical-or operations as in the figure: * - * bb1 bb2 bb3 + * bb1 bb2 bb3 * +-----------+ +-----------+ +---------+ * | teq a, #0 | False | teq b, #0 | False | ldr 0 | * | bne bb4 | ----> | bne bb4 | ----> | b bb5 | @@ -3395,10 +3401,9 @@ void finalize_logical(opcode_t op, * +---------+ +--------+ * bb4 bb5 * - * Similarly, here uses 'else_if', 'else_bb', 'then' and - * 'end' to respectively point to bb2 ~ bb5, and then - * finishes the finalization. - * */ + * Similarly, here uses 'else_if', 'else_bb', 'then' and 'end' to + * respectively point to bb2 ~ bb5, and then finishes the finalization. + */ then = shared_bb; else_if = *bb; else_bb = bb_create(parent); @@ -3415,13 +3420,12 @@ void finalize_logical(opcode_t op, add_insn(parent, op == OP_log_and ? then : else_if, OP_branch, NULL, vd, NULL, 0, NULL); - /* - * If handling logical-and operation, here creates a true branch for the + /* If handling logical-and operation, here creates a true branch for the * logical-and operation and assigns a true value. * * Otherwise, create a false branch and assign a false value for logical-or * operation. - * */ + */ vd = require_var(parent); vd->var_name = gen_name(); vd->init_val = op == OP_log_and; @@ -3440,8 +3444,8 @@ void finalize_logical(opcode_t op, /* Create the shared branch and assign the other value for the other * condition of a logical-and/or operation. * - * If handing a logical-and operation, assign a false value. else, assign - * a true value for a logical-or operation. + * If handing a logical-and operation, assign a false value. else, assign a + * true value for a logical-or operation. */ vd = require_var(parent); vd->var_name = gen_name(); @@ -3586,9 +3590,8 @@ bool read_body_assignment(char *token, if (op != OP_generic) { int increment_size = 1; - /* if we have a pointer, shift it by element size */ - /* But not if we are operating on a dereferenced value (array - * indexing) + /* if we have a pointer, shift it by element size But not if we are + * operating on a dereferenced value (array indexing) */ if (lvalue.ptr_level && !lvalue.is_reference) increment_size = lvalue.type->size; @@ -3705,9 +3708,10 @@ bool read_body_assignment(char *token, rs1 = opstack_pop(); /* is_func labels both function symbols and function-pointer - * variables. A variable on the RHS must contribute its - * stored pointer value, rather than its identifier being - * lowered as a function address. */ + * variables. A variable on the RHS must contribute its stored + * pointer value, rather than its identifier being lowered as a + * function address. + */ if (rs2->is_func && find_var(rs2->var_name, parent) == rs2) { t = require_ref_var(parent, rs2->type, rs2->ptr_level); t->var_name = gen_name(); @@ -3719,8 +3723,8 @@ bool read_body_assignment(char *token, rs2 = vd; } - /* Acquire destination address of lvalue if lvalue is a - * local variable. + /* Acquire destination address of lvalue if lvalue is a local + * variable. */ if (!lvalue.is_reference) { var_t *addr = @@ -3792,6 +3796,7 @@ int eval_expression_imm(opcode_t op, int op1, int op2) if (!op2) error_at("Division by zero in constant expression", cur_token_loc()); + /* INT_MIN / -1 has no representable result; on x86 it raises SIGFPE * rather than producing one. */ @@ -3893,11 +3898,10 @@ bool read_global_assignment(char *token) var = find_global_var(token); if (var) { if (lex_peek(T_string, NULL)) { - /* String literal global initialization: - * String literals are now stored in .rodata section. - * TODO: Implement compile-time address resolution for global - * pointer initialization with rodata addresses - * (e.g., char *p = "str";) + /* String literal global initialization: String literals are now + * stored in .rodata section. TODO: Implement compile-time address + * resolution for global pointer initialization with rodata + * addresses (e.g., char *p = "str";) */ read_literal_param(parent, bb); rs1 = opstack_pop(); @@ -4554,7 +4558,8 @@ basic_block_t *read_body_statement(block_t *parent, basic_block_t *bb) field->ptr_level); /* Compute field address: &struct + - * field_offset */ + * field_offset + */ var_t *struct_addr = require_var(parent); struct_addr->var_name = gen_name(); add_insn(parent, bb, OP_address_of, @@ -4627,11 +4632,13 @@ basic_block_t *read_body_statement(block_t *parent, basic_block_t *bb) if (!is_const && !lex_peek(T_identifier, token) && !has_asterisk) error_at("Unexpected token", next_token_loc()); - /* is it a variable declaration? */ - /* Special handling when statement starts with asterisk */ + /* is it a variable declaration? Special handling when statement starts with + * asterisk + */ if (has_asterisk) { - /* For "*identifier", check if identifier is a type. - * If not, it's a dereference, not a declaration. */ + /* For "*identifier", check if identifier is a type. If not, it's a + * dereference, not a declaration. + */ token_t *saved_token = cur_token; /* Skip the asterisk to peek at the identifier */ @@ -4753,9 +4760,8 @@ basic_block_t *read_body_statement(block_t *parent, basic_block_t *bb) first_elem->type = var->type; first_elem->var_name = gen_name(); - /* Read first element from array at offset 0 - * expr_result is the array itself, so we can read - * directly from it + /* Read first element from array at offset 0 expr_result is + * the array itself, so we can read directly from it */ add_insn(parent, bb, OP_read, first_elem, expr_result, NULL, var->type->size, NULL); @@ -4876,12 +4882,12 @@ basic_block_t *read_body_statement(block_t *parent, basic_block_t *bb) if (lex_peek(T_asterisk, NULL)) { if (stmt_starts_assignment()) { /* Consume exactly one asterisk and evaluate what follows as an - * ordinary expression. That expression is the address to store - * to: for "*p" it is p, for "**pp" it is the value of *pp, and - * for "*(p + 1)" it is p + 1. Letting read_expr() consume the - * leading asterisk too would dereference once more than the - * assignment asks for, and the store then went to whatever the - * pointee happened to hold. + * ordinary expression. That expression is the address to store to: + * for "*p" it is p, for "**pp" it is the value of *pp, and for "*(p + * + 1)" it is p + 1. Letting read_expr() consume the leading + * asterisk too would dereference once more than the assignment asks + * for, and the store then went to whatever the pointee happened to + * hold. */ lex_expect(T_asterisk); read_expr(parent, &bb); @@ -5061,7 +5067,6 @@ void print_func_decl(func_t *func, const char *prefix, bool newline) printf("\n"); } -/* if first token is type */ /* Emit the optional initializer of a global declarator. Arrays and pointers * written with a brace list go through the array initializer; everything else * is a scalar constant. @@ -5118,8 +5123,8 @@ void read_global_decl(block_t *block, bool is_const) read_parameter_list_decl(func, 0); if (check_decl) { - /* Validate whether the previous declaration and the current - * one differ. + /* Validate whether the previous declaration and the current one + * differ. */ if ((func->return_def.type != func_tmp.return_def.type) || (func->return_def.ptr_level != func_tmp.return_def.ptr_level) || @@ -5281,10 +5286,9 @@ void read_global_statement(void) var->array_size == 0 && var->ptr_level == 0 && (decl_type->base_type == TYPE_struct || decl_type->base_type == TYPE_typedef)) { - /* Global struct compound literal support - * Currently we just consume the syntax - actual - * initialization would require runtime code which globals - * don't support + /* Global struct compound literal support Currently we just + * consume the syntax - actual initialization would require + * runtime code which globals don't support */ consume_global_compound_literal(); } else { @@ -5317,8 +5321,7 @@ void read_global_statement(void) return; } - /* struct definition */ - /* has forward declaration? */ + /* struct definition has forward declaration? */ type_t *type = find_type(token, 2); if (!type) type = add_type(); @@ -5600,20 +5603,20 @@ void parse_internal(void) if (dynlink) { /* In dynamic mode, __syscall won't be implemented. * - * Simply declare a 'syscall' function as follows if the program - * needs to use 'syscall': + * Simply declare a 'syscall' function as follows if the program needs + * to use 'syscall': * * int syscall(int number, ...); * - * shecc will treat it as an external function, and the compiled - * program will eventually use the implementation provided by - * the external C library. + * shecc will treat it as an external function, and the compiled program + * will eventually use the implementation provided by the external C + * library. * * If shecc supports the 'long' data type in the future, it would be * better to declare syscall using its original prototype: * * long syscall(long number, ...); - * */ + */ } else { /* Linux syscall */ func_t *func = add_func("__syscall", true); diff --git a/src/peephole.c b/src/peephole.c index 9b9dbaf1..8bd620b3 100644 --- a/src/peephole.c +++ b/src/peephole.c @@ -42,10 +42,9 @@ bool is_fusible_insn(ph2_ir_t *ph2_ir) /* Main peephole optimization function that applies pattern matching and * transformation rules to consecutive IR instructions. - * Returns true if any optimization was applied, false otherwise. - */ -/* Drop the instructions after @ir through @last, keeping ph2_ir_list.tail on a - * node still in the list. + * Returns true if any optimization was applied, false otherwise. Drop the + * instructions after @ir through @last, keeping ph2_ir_list.tail on a node + * still in the list. * * Every removal in this file goes through here. A bare "ir->next = last->next" * leaves tail pointing at a removed node, which is why x64-codegen.c used to @@ -369,8 +368,8 @@ bool eliminate_load_store_pairs(basic_block_t *bb, ph2_ir_t *ph2_ir) * links the caller is holding valid. * * Only at equal width. A wide store followed by a narrow one to the - * same slot leaves the bytes the second does not cover holding what - * the first put there, so dropping the first loses them. + * same slot leaves the bytes the second does not cover holding what the + * first put there, so dropping the first loses them. */ if (ph2_ir->src1 == next->src1 && ph2_ir->src1 >= 0 && ph2_ir->size_bytes == next->size_bytes && @@ -553,9 +552,10 @@ bool strength_reduction(ph2_ir_t *ph2_ir) return false; } -/* Simplify bitwise patterns the SSA optimizer cannot see, because they - * only become visible once registers are assigned. Returns true when it - * rewrote something. +/* Simplify bitwise patterns the SSA optimizer cannot see, because they only + * become visible once registers are assigned. + * + * Returns true when it rewrote something. */ bool bitwise_optimization(basic_block_t *bb, ph2_ir_t *ph2_ir) { @@ -724,8 +724,8 @@ bool triple_pattern_optimization(basic_block_t *bb, ph2_ir_t *ph2_ir) * * This runs on ph2_ir_t, after register allocation, and so sees only what * assigning registers makes visible. Constant folding, common subexpression - * elimination and dead code elimination have already run over insn_t in the - * SSA optimizer and are not repeated here. + * elimination and dead code elimination have already run over insn_t in the SSA + * optimizer and are not repeated here. * * What is left to do at this level: * - self-assignment elimination, for assignments allocation itself created @@ -757,13 +757,13 @@ void peephole(void) continue; } - /* Every rewrite below moves this instruction's result to - * the destination of the one after it, dropping the write to - * the register it named. That is fine for a temporary, whose - * value nothing wants again, and wrong for a pinned register: - * a variable lives there for the whole function and nothing - * else ever reloads it, so "li rbx, 0; add rax, rsi, rbx" must - * not become "mov rax, rsi" and leave rbx unwritten. + /* Every rewrite below moves this instruction's result to the + * destination of the one after it, dropping the write to the + * register it named. That is fine for a temporary, whose value + * nothing wants again, and wrong for a pinned register: a + * variable lives there for the whole function and nothing else + * ever reloads it, so "li rbx, 0; add rax, rsi, rbx" must not + * become "mov rax, rsi" and leave rbx unwritten. */ if (ir->dest >= 0 && ir->dest < REG_CNT && ((func->pinned_regs >> ir->dest) & 1)) diff --git a/src/preprocessor.c b/src/preprocessor.c index 984e4b4c..53438ccc 100644 --- a/src/preprocessor.c +++ b/src/preprocessor.c @@ -1,8 +1,8 @@ /* * shecc - Self-Hosting and Educational C Compiler. * - * shecc is freely redistributable under the BSD 2 clause license. See the - * file "LICENSE" for information on usage and redistribution of this file. + * shecc is freely redistributable under the BSD 2 clause license. See the file + * "LICENSE" for information on usage and redistribution of this file. */ #include "../config" #include "defs.h" @@ -67,9 +67,8 @@ token_t *pp_lex_expect_token(token_t *tk, token_kind_t kind, bool skip_space) /* Copies and isolate the given copied token */ token_t *copy_token(token_t *tk) { - /* The copy overwrites every byte, so zeroing the allocation first would - * be wasted work -- and this runs once per token of every macro - * expansion. + /* The copy overwrites every byte, so zeroing the allocation first would be + * wasted work -- and this runs once per token of every macro expansion. */ token_t *new_tk = arena_alloc(TOKEN_ARENA, sizeof(token_t)); memcpy(new_tk, tk, sizeof(token_t)); @@ -165,8 +164,8 @@ bool hide_set_contains(hide_set_t *hs, char *name) typedef enum { CK_if_then, CK_elif_then, CK_else_then } cond_kind_t; /* cond_incl_t is used as a stack-like context to track conditional macro - * directives' expansion, and gives information to the expansion context - * to process the token stream with correct behavior. + * directives' expansion, and gives information to the expansion context to + * process the token stream with correct behavior. */ typedef struct cond_incl { struct cond_incl *prev; @@ -186,13 +185,12 @@ cond_incl_t *push_cond(cond_incl_t *ci, token_t *tk, bool included) } /* preprocess_ctx_t is used to track various inforamtion when expanding token - * stream, the context state may vary due to the current expanding object, - * but in general case, it will tries to inherit parent context state if - * possible. + * stream, the context state may vary due to the current expanding object, but + * in general case, it will tries to inherit parent context state if possible. * - * Due to the standard that token stream are always ends with EOF token, - * the default behavior is not to trim EOF token, but if the result requires - * EOF token to be present, set trim_eof to true would suffice. + * Due to the standard that token stream are always ends with EOF token, the + * default behavior is not to trim EOF token, but if the result requires EOF + * token to be present, set trim_eof to true would suffice. */ typedef struct preprocess_ctx { hide_set_t *hide_set; @@ -551,8 +549,8 @@ token_t *pp_skip_cond_incl(token_t *tk) /* Spell an argument's tokens as a string literal, for '#'. * - * T_string literals are stored with their escapes intact and unescaped later - * by the parser, so a quote or a backslash coming from the argument has to be + * T_string literals are stored with their escapes intact and unescaped later by + * the parser, so a quote or a backslash coming from the argument has to be * escaped again here. Tokens are separated by a single space, with none at * either end. */ @@ -647,18 +645,19 @@ token_t *pp_paste_tokens(token_t *lhs, token_t *rhs, source_location_t *loc) * * Both operate on an argument as it was written rather than on its expansion, * so they cannot wait for the expansion loop: by the time that loop reaches a - * parameter it has already expanded it. @args is NULL for an object-like - * macro, which has no parameters to stringify but may still paste. + * parameter it has already expanded it. @args is NULL for an object-like macro, + * which has no parameters to stringify but may still paste. * * An argument with no tokens in it is not "no argument": '#' spells it as the - * empty string, and pasting against it leaves the other operand standing on - * its own. Membership in @args, rather than a non-empty value, is what makes a - * name a parameter. + * empty string, and pasting against it leaves the other operand standing on its + * own. Membership in @args, rather than a non-empty value, is what makes a name + * a parameter. */ token_t *pp_subst_hash(token_t *rep, hashmap_t *args) { token_t head; token_t *tail = &head, *tail_prev = NULL; + /* Whether anything at all precedes a '##' here, and whether that something * was an argument that turned out to be empty. */ @@ -719,9 +718,9 @@ token_t *pp_subst_hash(token_t *rep, hashmap_t *args) tail = tail_prev->next; } - /* Only the first token of a multi-token argument is joined; - * the rest follow it. An argument is a list of its own, so it - * ends where the argument does. A literal operand is not: its + /* Only the first token of a multi-token argument is joined; the + * rest follow it. An argument is a list of its own, so it ends + * where the argument does. A literal operand is not: its * successor is the next token of the replacement list, which * the loop below still has to walk, so copying from here would * emit the remainder of the macro body twice. @@ -795,7 +794,8 @@ token_t *pp_preprocess_internal(token_t *tk, preprocess_ctx_t *ctx) preprocess_ctx_t expansion_ctx; /* Initialize expansion context: inherit parent context and enable - * EOF trimming for macro body expansion */ + * EOF trimming for macro body expansion + */ expansion_ctx.expanded_from = ctx->expanded_from ? ctx->expanded_from : tk; expansion_ctx.macro_args = ctx->macro_args; @@ -803,14 +803,14 @@ token_t *pp_preprocess_internal(token_t *tk, preprocess_ctx_t *ctx) token_t *macro_arg_replcaement = NULL; - /* Check if this identifier is a macro parameter (argument) - * If we're currently expanding a macro body, parameters should be - * replaced with their supplied arguments. + /* Check if this identifier is a macro parameter (argument) If we're + * currently expanding a macro body, parameters should be replaced + * with their supplied arguments. * * Membership decides this, not a non-empty value: an argument with - * no tokens in it still names a parameter, and substituting - * nothing for it is what "M(a,)" means. Testing the value would - * leave the parameter's own name standing in the output. + * no tokens in it still names a parameter, and substituting nothing + * for it is what "M(a,)" means. Testing the value would leave the + * parameter's own name standing in the output. * * '#' and '##' were already resolved by pp_subst_hash(), which had * to run before this expansion could reach their operands. @@ -830,7 +830,8 @@ token_t *pp_preprocess_internal(token_t *tk, preprocess_ctx_t *ctx) expansion_ctx.hide_set = ctx->hide_set; expansion_ctx.macro_args = NULL; /* Don't take account of macro arguments, this - might run into infinite loop */ + might run into infinite loop + */ macro_arg_replcaement = pp_preprocess_internal( macro_arg_replcaement, &expansion_ctx); cur->next = macro_arg_replcaement; @@ -851,8 +852,9 @@ token_t *pp_preprocess_internal(token_t *tk, preprocess_ctx_t *ctx) if (!macro || macro->is_disabled) break; - /* Handle built-in function-like macros (__FILE__, __LINE__) - * These have special handlers that generate tokens directly */ + /* Handle built-in function-like macros (__FILE__, __LINE__) These + * have special handlers that generate tokens directly + */ if (macro->handler) { cur->next = macro->handler(expansion_ctx.expanded_from); cur = cur->next; @@ -874,7 +876,8 @@ token_t *pp_preprocess_internal(token_t *tk, preprocess_ctx_t *ctx) arg_head.next = NULL; /* Add macro name to hide set to prevent re-expansion of itself - * during its own body expansion */ + * during its own body expansion + */ expansion_ctx.hide_set = hide_set_union(ctx->hide_set, new_hide_set(tk->literal)); /* Create parameter mapping table for this macro invocation */ @@ -885,7 +888,8 @@ token_t *pp_preprocess_internal(token_t *tk, preprocess_ctx_t *ctx) /* Parse macro arguments until closing parenthesis * * Handles nested parentheses and comma-separated argument list - * by tracking the nested depth */ + * by tracking the nested depth + */ while (true) { if (pp_lex_peek_token(tk, T_open_bracket, false)) bracket_depth++; @@ -953,7 +957,8 @@ token_t *pp_preprocess_internal(token_t *tk, preprocess_ctx_t *ctx) if (hashmap_contains(expansion_ctx.macro_args, param_tk->literal)) { /* Append to existing variadic args with comma - * separator to preserve argument boundaries */ + * separator to preserve argument boundaries + */ token_t *prev = hashmap_get(expansion_ctx.macro_args, param_tk->literal); @@ -997,8 +1002,9 @@ token_t *pp_preprocess_internal(token_t *tk, preprocess_ctx_t *ctx) error_at("Too few arguments supplied to macro invocation", ¯o_tk->location); - /* Expand macro body with collected arguments - * Replace parameter references with supplied argument tokens */ + /* Expand macro body with collected arguments Replace parameter + * references with supplied argument tokens + */ cur->next = pp_preprocess_internal( pp_subst_hash(macro->replacement, expansion_ctx.macro_args), &expansion_ctx); @@ -1006,9 +1012,10 @@ token_t *pp_preprocess_internal(token_t *tk, preprocess_ctx_t *ctx) hashmap_free(expansion_ctx.macro_args); } else { - /* Handle object-like macro expansion (no parameters) - * Simply expand the replacement with current hide set plus - * this macro name added to prevent re-expansion */ + /* Handle object-like macro expansion (no parameters) Simply + * expand the replacement with current hide set plus this macro + * name added to prevent re-expansion + */ expansion_ctx.hide_set = hide_set_union(ctx->hide_set, new_hide_set(tk->literal)); cur->next = pp_preprocess_internal( @@ -1021,9 +1028,9 @@ token_t *pp_preprocess_internal(token_t *tk, preprocess_ctx_t *ctx) } case T_hash: case T_hashhash: - /* Every '#' a macro body owns is resolved by pp_subst_hash() - * before that body is rescanned, so one arriving here is loose in - * ordinary code. + /* Every '#' a macro body owns is resolved by pp_subst_hash() before + * that body is rescanned, so one arriving here is loose in ordinary + * code. */ error_at("'#' is only meaningful inside a macro definition", &tk->location); @@ -1068,8 +1075,8 @@ token_t *pp_preprocess_internal(token_t *tk, preprocess_ctx_t *ctx) tk = pp_lex_expect_token(tk, T_lt, true); /* The path is ignored (see the FIXME below), so just consume - * it. Stopping at a newline too keeps an unterminated - * "#include at this moment, since - * all libc functions are included done by inlining. + + /* FIXME: We ignore #include <...> at this moment, since all + * libc functions are included done by inlining. */ tk = pp_lex_expect_token(tk, T_newline, true); tk = pp_lex_next_token(tk, false); @@ -1303,16 +1311,16 @@ token_t *pp_preprocess_internal(token_t *tk, preprocess_ctx_t *ctx) return head.next; } -/* Drop the whitespace, tab and newline tokens from a fully preprocessed - * stream, on the way into the parser. +/* Drop the whitespace, tab and newline tokens from a fully preprocessed stream, + * on the way into the parser. * - * They carry no meaning to the parser, which never names those kinds, and - * every token is created before this runs, so once they are gone the parser - * never meets one again. That is what let the skip-over-layout walk in front - * of each token access -- more than a million iterations over a self-compile - * -- be removed outright. Preprocessed output (-E) still needs them to - * separate one token from the next, so the stripping belongs here and not in - * preprocess() itself. + * They carry no meaning to the parser, which never names those kinds, and every + * token is created before this runs, so once they are gone the parser never + * meets one again. That is what let the skip-over-layout walk in front of each + * token access -- more than a million iterations over a self-compile -- be + * removed outright. Preprocessed output (-E) still needs them to separate one + * token from the next, so the stripping belongs here and not in preprocess() + * itself. */ token_t *pp_strip_layout(token_t *tk) { @@ -1373,9 +1381,9 @@ token_t *preprocess(token_t *tk) macro->replacement->literal = "1"; hashmap_put(MACROS, "__SHECC__", macro); - /* Tells the source being compiled that the embedded libc is not part of - * the output, so the functions lib/c.c would have supplied -- '__syscall' - * above all -- are unavailable and libc resolves through the PLT instead. + /* Tells the source being compiled that the embedded libc is not part of the + * output, so the functions lib/c.c would have supplied -- '__syscall' above + * all -- are unavailable and libc resolves through the PLT instead. */ if (dynlink) { macro = calloc(1, sizeof(macro_t)); diff --git a/src/reg-alloc.c b/src/reg-alloc.c index 4d6bf472..0d889907 100644 --- a/src/reg-alloc.c +++ b/src/reg-alloc.c @@ -173,6 +173,7 @@ ph2_ir_t *bb_add_ph2_ir(basic_block_t *bb, opcode_t op) n->is_branch_detached = 0; /* arch-lowering will set for branches */ n->src0 = 0; n->src1 = 0; + /* Only a select names a third source, but the allocation is not zeroed and * every field is set here by hand. */ @@ -261,8 +262,8 @@ var_t *pinned_base[REG_CNT]; * Preparing one operand can spill another's register to make room, leaving the * number recorded for it naming a register that no longer holds the value. An * instruction reading more operands than the two "avoid" arguments can express - * -- a select reads three -- locks each as it is placed, and the spill - * searches leave those alone. + * -- a select reads three -- locks each as it is placed, and the spill searches + * leave those alone. */ int reg_locked; @@ -279,6 +280,7 @@ int pinned_reg_of(var_t *var) { if (!var || !var->base) return -1; + /* A value an if-converted arm computes shares its variable's base with the * select's result, but the pinned register still holds what the arms read, * so it takes an ordinary register instead. @@ -396,19 +398,18 @@ int slot_lookup(int offset) */ void slot_scan(func_t *func) { - /* alloc_var_slot() appends in increasing offset order, but - * phi_slot_merge() then rewrites the offsets of variables already in the - * table to put a chain of phis on one slot, which can leave it out of - * order. slot_lookup() binary-searches, so an unsorted table makes it miss - * a slot that is present: both scans below and dead_store_elim() miss the - * same one, so nothing is misidentified, but the two cleanups skip work - * they could do. + /* alloc_var_slot() appends in increasing offset order, but phi_slot_merge() + * then rewrites the offsets of variables already in the table to put a + * chain of phis on one slot, which can leave it out of order. slot_lookup() + * binary-searches, so an unsorted table makes it miss a slot that is + * present: both scans below and dead_store_elim() miss the same one, so + * nothing is misidentified, but the two cleanups skip work they could do. * * Insertion sort, because the table is nearly sorted already and every - * entry a merge moved sits close to where it belongs. It costs about - * 0.085% of a self-compile and recovers optimizations worth rather less - * than that; it is here to keep the invariant alloc_var_slot() documents - * true, not to pay for itself. + * entry a merge moved sits close to where it belongs. It costs about 0.085% + * of a self-compile and recovers optimizations worth rather less than that; + * it is here to keep the invariant alloc_var_slot() documents true, not to + * pay for itself. */ for (int i = 1; i < slot_var_count; i++) { var_t *var = slot_vars[i]; @@ -701,13 +702,14 @@ int find_in_regs(var_t *var) return -1; } -/* Whether @var can live in a register for a whole function: nothing else may - * be able to reach it, and it must fit in one register. +/* Whether @var can live in a register for a whole function: nothing else may be + * able to reach it, and it must fit in one register. */ bool var_is_pinnable(var_t *var) { if (!var || var->is_const || !var->base) return false; + /* slot_is_private() rules out everything reachable other than by name: * globals, address-taken variables, arrays and backing storage. */ @@ -720,8 +722,8 @@ bool var_is_pinnable(var_t *var) * * Candidates are ranked by how often they are named, which stands in well * enough for how hot they are: a variable a loop carries is named on every - * iteration. At most half the file is given away so expression evaluation - * still has registers to work with. + * iteration. At most half the file is given away so expression evaluation still + * has registers to work with. */ int pin_scan_gen; @@ -748,8 +750,8 @@ void pin_registers(func_t *func) } /* Across a call only the registers the callee preserves will still hold - * their value. Those sit at the top of the file, which is the end the - * loop below hands out from, so capping the count is all that is needed. + * their value. Those sit at the top of the file, which is the end the loop + * below hands out from, so capping the count is all that is needed. */ if (calls) { if (!CALLEE_SAVED_REGS) @@ -760,8 +762,8 @@ void pin_registers(func_t *func) /* Tally what every variable's namings are worth. The count lives on the * variable rather than in an array here: the file has a handful of - * registers and a function names hundreds of variables, and the one worth - * a register is not reliably among the first few met -- a pointer + * registers and a function names hundreds of variables, and the one worth a + * register is not reliably among the first few met -- a pointer * strength_reduce() introduced is named last of all. */ pin_scan_gen++; @@ -779,11 +781,12 @@ void pin_registers(func_t *func) if (!var_is_pinnable(var)) continue; + /* A parameter passed on the stack lives at an offset into the * caller's frame that the callee cannot pin; one passed in a - * register is moved into its pinned register on entry, so it - * is a candidate like any other -- and a good one, since a - * pointer a loop walks is usually a parameter. + * register is moved into its pinned register on entry, so it is + * a candidate like any other -- and a good one, since a pointer + * a loop walks is usually a parameter. */ bool on_stack = false; bool in_reg = false; @@ -804,8 +807,9 @@ void pin_registers(func_t *func) if (base->pin_gen != pin_scan_gen) { base->pin_gen = pin_scan_gen; base->pin_weight = 0; - /* A parameter is written in the entry block before the - * body names it, so its range already spans blocks. + + /* A parameter is written in the entry block before the body + * names it, so its range already spans blocks. */ base->pin_cross = in_reg; base->pin_hot = false; @@ -823,9 +827,8 @@ void pin_registers(func_t *func) /* Take the most-named bases, highest register first: the low registers are * where arguments and return values land. The winner is found by walking - * the tally again rather than sorting it, which costs one pass per - * register handed out -- a handful, against a function's instruction - * count. + * the tally again rather than sorting it, which costs one pass per register + * handed out -- a handful, against a function's instruction count. */ for (int taken = 0; taken < limit; taken++) { var_t *best = NULL; @@ -847,14 +850,14 @@ void pin_registers(func_t *func) if (base->pin_gen != pin_scan_gen) continue; - /* Registers are handed out from the top of the file, - * which is where the preserved ones are, so a pinned - * function saves and restores one in its prologue. A - * variable named once in straight-line code does not earn - * that back -- least of all in a small leaf called from a - * loop, which pays it on every call -- so a candidate has - * to be read inside a loop and to outlive the block that - * names it. + + /* Registers are handed out from the top of the file, which + * is where the preserved ones are, so a pinned function + * saves and restores one in its prologue. A variable named + * once in straight-line code does not earn that back -- + * least of all in a small leaf called from a loop, which + * pays it on every call -- so a candidate has to be read + * inside a loop and to outlive the block that names it. */ if (!base->pin_cross || !base->pin_hot) continue; @@ -876,8 +879,8 @@ void pin_registers(func_t *func) return; /* Incoming arguments arrive in the low registers and are placed there - * before the body runs, which would overwrite anything pinned to one - * of them; the variable would then read a parameter's value instead. + * before the body runs, which would overwrite anything pinned to one of + * them; the variable would then read a parameter's value instead. */ int reg = REG_CNT - 1 - taken; int args_in_reg = func->num_params < MAX_ARGS_IN_REG ? func->num_params @@ -925,10 +928,10 @@ void load_var(basic_block_t *bb, var_t *var, int idx) int prepare_operand(basic_block_t *bb, var_t *var, int operand_0) { - /* A pinned variable is already where it always is -- unless this version - * of it is a constant, which has no home to be in until it is written - * there. Every other version reaches the register by being defined into it - * or by a phi move, so nothing else needs materialising. + /* A pinned variable is already where it always is -- unless this version of + * it is a constant, which has no home to be in until it is written there. + * Every other version reaches the register by being defined into it or by a + * phi move, so nothing else needs materialising. */ int pinned = pinned_reg_of(var); if (pinned >= 0) { @@ -1065,8 +1068,8 @@ int coalesce_candidate(basic_block_t *bb, insn_t *insn, int reg) return -1; /* A pinned register is not a scratch one however dead the version it - * currently holds looks: the variable living there is read again further - * on and nothing ever reloads it. Handing it to a temporary destroyed the + * currently holds looks: the variable living there is read again further on + * and nothing ever reloads it. Handing it to a temporary destroyed the * value a select was about to choose between. */ if (pinned_base[reg]) @@ -1146,10 +1149,10 @@ int prepare_dest(basic_block_t *bb, } } - /* Callers normally have at least one register which is not an operand, - * but an instruction with more register inputs than the allocator's two - * avoid arguments can leave every register protected. Let that caller - * make an instruction-specific choice instead of indexing REGS[-1]. + /* Callers normally have at least one register which is not an operand, but + * an instruction with more register inputs than the allocator's two avoid + * arguments can leave every register protected. Let that caller make an + * instruction-specific choice instead of indexing REGS[-1]. */ if (spilled < 0) return -1; @@ -1170,8 +1173,8 @@ void spill_alive(basic_block_t *bb, insn_t *insn) /* Spill all locals on pointer writes (conservative aliasing handling) */ if (insn && insn->opcode == OP_write) { for (int i = 0; i < REG_CNT; i++) { - /* A pinned variable has no address, so no write through a - * pointer can reach it. + /* A pinned variable has no address, so no write through a pointer + * can reach it. */ if (REGS[i].var && !REGS[i].var->is_global && !pinned_base[i]) spill_var(bb, REGS[i].var, i); @@ -1243,30 +1246,30 @@ void spill_live_out_keep(basic_block_t *bb) * slots just written. * * Requiring the successor to be next in reverse post-order is what makes - * "emitted immediately after" true: cfg_flatten() walks the same rpo_next - * chain the allocator does, so the two orders are the same traversal. + * "emitted immediately after" true: cfg_flatten() walks the same rpo_next chain + * the allocator does, so the two orders are the same traversal. * * Only a successor emitted immediately after this block qualifies. A branch - * target is emitted wherever the backend's linear walk puts it, and a block - * the walk misses is re-emitted later -- that second copy is reached with - * unrelated registers, so a file handed to it would not hold on every path - * that runs its code. Requiring the successor to be the next block in reverse - * post-order is what rules that out, since that is the order the walk emits. + * target is emitted wherever the backend's linear walk puts it, and a block the + * walk misses is re-emitted later -- that second copy is reached with unrelated + * registers, so a file handed to it would not hold on every path that runs its + * code. Requiring the successor to be the next block in reverse post-order is + * what rules that out, since that is the order the walk emits. * * A conditional branch falls through as well: the backend jumps to one * successor and lets control run on into the other, which is emitted * contiguously exactly as a plain successor is. That edge therefore qualifies - * on the same terms, and it is the one that matters -- it is where an - * if/else chain would otherwise reload on every arm what the test just had in - * a register. + * on the same terms, and it is the one that matters -- it is where an if/else + * chain would otherwise reload on every arm what the test just had in a + * register. */ void bb_export_regs(basic_block_t *bb) { basic_block_t *succ = bb->next; if (!succ) { - /* Take whichever arm the walk placed next; the checks below confirm - * it really is contiguous and has no other way in. + /* Take whichever arm the walk placed next; the checks below confirm it + * really is contiguous and has no other way in. */ if (bb->then_ == bb->rpo_next) succ = bb->then_; @@ -1346,9 +1349,9 @@ bool abi_lower_call_args(basic_block_t *bb, insn_t *insn) insn = insn->prev; stack_args = num_of_args - MAX_ARGS_IN_REG; while (stack_args) { - /* A pinned variable has no slot to load from: its value only ever - * lives in its register, so reading the frame here handed the callee - * whatever the slot happened to hold. + /* A pinned variable has no slot to load from: its value only ever lives + * in its register, so reading the frame here handed the callee whatever + * the slot happened to hold. */ int held = pinned_reg_of(insn->rs1); @@ -1601,6 +1604,7 @@ void phi_live_index_build(func_t *func) c = phi_cand_index(insn->rs2); if (c >= 0) phi_cand_last[c] = n; + /* A select reads a third operand. Leaving it out of the walk ends * the value's range before the instruction that reads it, and two * variables live at once then look free to share a slot. @@ -2504,19 +2508,19 @@ void reg_alloc(void) dest = prepare_dest(bb, insn, insn->rd, taken, other); if (dest < 0) { - /* A select needs a fourth register only while all - * three inputs remain live. Save one unpinned input - * first, then use its physical register as the - * result. The CMOV emitter deliberately supports the - * destination aliasing either arm; it tests the - * condition before overwriting anything, so the - * condition is safe too if it is the only choice. + /* A select needs a fourth register only while all three + * inputs remain live. Save one unpinned input first, + * then use its physical register as the result. The + * CMOV emitter deliberately supports the destination + * aliasing either arm; it tests the condition before + * overwriting anything, so the condition is safe too if + * it is the only choice. * * spill_var() leaves the machine register unchanged, - * which is exactly what the select still needs. It - * only removes the allocator's association, making - * the value available for the result and forcing a - * later use of the saved input to reload its slot. + * which is exactly what the select still needs. It only + * removes the allocator's association, making the value + * available for the result and forcing a later use of + * the saved input to reload its slot. */ int reuse = -1; int sources[] = {taken, other, cond}; diff --git a/src/riscv-codegen.c b/src/riscv-codegen.c index f87c1f93..4da05e22 100644 --- a/src/riscv-codegen.c +++ b/src/riscv-codegen.c @@ -1,8 +1,8 @@ /* * shecc - Self-Hosting and Educational C Compiler. * - * shecc is freely redistributable under the BSD 2 clause license. See the - * file "LICENSE" for information on usage and redistribution of this file. + * shecc is freely redistributable under the BSD 2 clause license. See the file + * "LICENSE" for information on usage and redistribution of this file. */ /* Translate IR to target machine code */ @@ -14,8 +14,8 @@ /* Explanation: registers preservation/restoration * - * The following table illustrates which registers are caller-saved - * or callee-saved: + * The following table illustrates which registers are caller-saved or + * callee-saved: * +-----------+--------+ * | Register | Saver | * | ABI Name | | @@ -192,21 +192,21 @@ void cfg_flatten(void) func_t *func; if (dynlink) { - /* When using dynamic linking, 20 instructions are generated at - * the program entry point to perform the following operations: + /* When using dynamic linking, 20 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 = 80; } else { - /* Under static linking, "__syscall" must be generated to allow - * the program to invoke system calls. + /* 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. + * 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; @@ -236,11 +236,11 @@ void cfg_flatten(void) flatten_ir->src0 = func->stack_size; flatten_ir->func_name = intern_string(func->return_def.var_name); - /* Except for local variables, it must allocate additional space - * to preserve the content of ra at each function entry point. + /* 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'. + * '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); @@ -264,9 +264,9 @@ void cfg_flatten(void) 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. + /* 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; } @@ -297,8 +297,8 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) int rs2 = ph2_ir->src1 + 10; int ofs; - /* Prepare the variables to reuse the same code for - * the instruction sequence of + /* Prepare the variables to reuse the same code for the instruction sequence + * of * 1. division and modulo. * 2. load and store operations. * 3. address-of operations. @@ -579,9 +579,8 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) } return; case OP_sign_ext: { - /* Decode size information: - * Lower 16 bits: target size - * Upper 16 bits: source size + /* Decode size information: Lower 16 bits: target size Upper 16 bits: + * source size */ int target_size = ph2_ir->src1 & 0xFFFF; int source_size = (ph2_ir->src1 >> 16) & 0xFFFF; @@ -590,9 +589,9 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) int shift_amount = (target_size - source_size) * 8; if (source_size == 2) { - /* Sign extend from short to word (16-bit shift) - * For 16-bit sign extension, use only shift operations - * since 0xFFFF is too large for RISC-V immediate field + /* Sign extend from short to word (16-bit shift) For 16-bit sign + * extension, use only shift operations since 0xFFFF is too large + * for RISC-V immediate field */ emit(__slli(rd, rs1, shift_amount)); emit(__srai(rd, rd, shift_amount)); @@ -620,6 +619,7 @@ void code_generate(void) if (dynlink) { plt_generate(); + /* - Initial stack layout when the program starts: * * +----------------+ (high address) @@ -644,9 +644,9 @@ void code_generate(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, + * 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))); @@ -665,11 +665,11 @@ void code_generate(void) /* The main wrapper is located here under the dynamic linking mode * - * Use s0 and s1 registers to temporarily store 'argc' and 'argv', - * while preserving ra on the stack. + * Use s0 and s1 registers to temporarily store 'argc' and 'argv', while + * preserving ra on the stack. * - * After the main function completes its execution, it must use - * the original content of ra to transfer control back to + * After the main function completes its execution, it must use the + * original content of ra to transfer control back to * __libc_start_main(). */ emit(__addi(__sp, __sp, -12)); @@ -680,14 +680,15 @@ void code_generate(void) emit(__addi(__s1, __a1, 0)); /* argv */ ofs = ALIGN_UP(GLOBAL_FUNC->stack_size, RV32_ALIGNMENT) + 4; } else { - /* When using static linking, the starting address - * of the main wrapper is here. + /* 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 @@ -773,7 +774,7 @@ void plt_generate() /* Accroding the RISC-V ABI specification, the first PLT entry should * contains the following instructions: * - * 1: auipc t2, %pcrel_hi(.got) + * 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. @@ -842,12 +843,12 @@ void plt_generate() 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. + /* 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]. (N >= 1) - * addr_of_got: the starting address of GOT[N + 1]. + * addr_of_plt: the starting address of PLT[N]. (N >= 1) addr_of_got: + * the starting address of GOT[N + 1]. */ addr_of_plt = dynamic_sections.elf_plt_start + PLT_FIXUP_SIZE + PLT_ENT_SIZE * i; @@ -856,14 +857,14 @@ void plt_generate() /* In RISC-V ABI, a PLT stub takes up 4 instructions to load GOT[N + 2]: * - * 1: auipc t3, %pcrel_hi(function@.got) + * 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 + 1], and then perform - * an unconditional jump. + * Each PLT stub uses auipc and lw instructions to perform a PC-relative + * addressing to obtain GOT[N + 1], and then perform an unconditional + * jump. * * +-------------------------------------+----------------------------+ * | Instruction | Contents of registers | diff --git a/src/riscv.c b/src/riscv.c index 8ebde663..0baf159f 100644 --- a/src/riscv.c +++ b/src/riscv.c @@ -1,8 +1,8 @@ /* * shecc - Self-Hosting and Educational C Compiler. * - * shecc is freely redistributable under the BSD 2 clause license. See the - * file "LICENSE" for information on usage and redistribution of this file. + * shecc is freely redistributable under the BSD 2 clause license. See the file + * "LICENSE" for information on usage and redistribution of this file. */ /* RISC-V instruction encoding */ diff --git a/src/ssa.c b/src/ssa.c index 4281a51b..a1028eee 100644 --- a/src/ssa.c +++ b/src/ssa.c @@ -1,8 +1,8 @@ /* * shecc - Self-Hosting and Educational C Compiler. * - * shecc is freely redistributable under the BSD 2 clause license. See the - * file "LICENSE" for information on usage and redistribution of this file. + * shecc is freely redistributable under the BSD 2 clause license. See the file + * "LICENSE" for information on usage and redistribution of this file. */ #include #include @@ -10,8 +10,8 @@ #include "defs.h" #include "globals.c" -/* Constant cast optimization. Despite the file name this is not SCCP: - * there is no lattice and no CFG-edge worklist anywhere in the tree. +/* Constant cast optimization. Despite the file name this is not SCCP: there is + * no lattice and no CFG-edge worklist anywhere in the tree. */ #include "opt-sccp.c" @@ -77,12 +77,12 @@ void var_list_assign_array(var_list_t *list, var_t **data, int count) list->size = count; } -/* cfront does not accept structure as an argument, pass pointer */ -/* The only thing a step of either traversal changes in the argument block is - * the block it names, so a step sets that field and puts it back on the way - * out. Copying the whole structure per edge instead -- which is what these did - * -- costs a copy on every block of every traversal, and the traversals are - * how nearly every analysis in the middle end walks a function. +/* cfront does not accept structure as an argument, pass pointer The only thing + * a step of either traversal changes in the argument block is the block it + * names, so a step sets that field and puts it back on the way out. Copying the + * whole structure per edge instead -- which is what these did -- costs a copy + * on every block of every traversal, and the traversals are how nearly every + * analysis in the middle end walks a function. */ void bb_forward_traversal(bb_traversal_args_t *args) { @@ -251,11 +251,12 @@ void build_idom(void) pred = bb->prev[i].bb; break; } - /* Reverse postorder puts a predecessor of every reachable - * block ahead of it, so one is normally settled by now. A - * block where none is cannot be given an immediate dominator - * yet; leaving it for a later round is what keeps the walk off - * an uninitialised pointer. + + /* Reverse postorder puts a predecessor of every reachable block + * ahead of it, so one is normally settled by now. A block where + * none is cannot be given an immediate dominator yet; leaving + * it for a later round is what keeps the walk off an + * uninitialised pointer. */ if (!pred) continue; @@ -333,8 +334,8 @@ void build_dom(void) * the target no longer does. strength_reduce() and mark_loop_depth() both ask * is_dominate() which edges close a loop, and against a stale tree a forward * edge reads as a back edge -- mark_natural_loop() then walks predecessors out - * of the region it was meant to stay inside, since it has no stop at the - * header and relies on the header dominating the latch. + * of the region it was meant to stay inside, since it has no stop at the header + * and relies on the header dominating the latch. * * The order has to be rebuilt along with the tree. Both passes keep the * rpo_next chain consistent -- a block they drop comes out of it -- but @@ -387,12 +388,12 @@ void bb_build_df(func_t *func, basic_block_t *bb) for (int i = 0; i < bb->prev_idx; i++) { if (bb->prev[i].bb) { - /* Walk up from the predecessor to this block's immediate - * dominator. The walk normally stops there, since a block's - * immediate dominator dominates all of its predecessors -- but an - * edge the dominator tree does not account for runs off the top - * instead, so stop at the root as well. Ending early only widens - * the frontier, which costs a phi that turns out to be trivial. + /* Walk up from the predecessor to this block's immediate dominator. + * The walk normally stops there, since a block's immediate + * dominator dominates all of its predecessors -- but an edge the + * dominator tree does not account for runs off the top instead, so + * stop at the root as well. Ending early only widens the frontier, + * which costs a phi that turns out to be trivial. */ for (basic_block_t *curr = bb->prev[i].bb; curr && curr != bb->idom; curr = curr->idom) @@ -830,8 +831,8 @@ var_t *require_var(block_t *blk); * * It has to be new. rename_var() gives every use reached by one definition the * same var_t, and mark_const() stamps init_val onto that shared object, so - * rewriting an existing constant's value changes what every other use sees. - * The caller emits the OP_load_constant that defines it. + * rewriting an existing constant's value changes what every other use sees. The + * caller emits the OP_load_constant that defines it. */ var_t *new_const_var(block_t *scope, int val) { @@ -914,6 +915,7 @@ void pop_name(var_t *var) { if (var->is_global) return; + /* Pop unconditionally, creating the state if the variable has none: the * inline rename_t this replaced was always present, so a pop with nothing * pushed drove stack_idx to -1, and the next push then landed one slot @@ -1068,9 +1070,9 @@ void bb_unwind_phi(func_t *func, basic_block_t *bb) int loop_scan_gen; -/* Raise the depth of every block in the natural loop that the edge from - * @latch back to @header closes: the header, and everything that can reach the - * latch without leaving the loop. +/* Raise the depth of every block in the natural loop that the edge from @latch + * back to @header closes: the header, and everything that can reach the latch + * without leaving the loop. */ bool mark_natural_loop(basic_block_t *header, basic_block_t *latch) { @@ -1095,11 +1097,12 @@ bool mark_natural_loop(basic_block_t *header, basic_block_t *latch) if (!p || p->loop_mark == loop_scan_gen) continue; + /* Out of room to widen. Marking this block and then not walking * through it would leave the rest of the loop unmarked while * looking marked, and a reader of loop_mark would take blocks - * inside the loop for blocks outside it. Report the whole answer - * as unusable instead. + * inside the loop for blocks outside it. Report the whole answer as + * unusable instead. */ if (sp >= MAX_LOOP_WALK) return false; @@ -1138,6 +1141,7 @@ void mark_loop_depth(func_t *func) continue; if (succ[k] != p && !is_dominate(succ[k], p)) continue; + /* A walk that ran out of room stopped partway, leaving the depths * it had already raised standing over a region it never finished * measuring. Those feed the weights pin_registers() compares, so a @@ -1245,9 +1249,9 @@ bool insn_is_speculatable(insn_t *insn) * * An arm is not one block: the copy carrying its value to the join is appended * to whichever block immediately precedes the join, which is separate from the - * one holding the arm's computation. The walk follows single-entry, - * single-exit blocks and stops at the first block something else can also - * reach, which is the join. + * one holding the arm's computation. The walk follows single-entry, single-exit + * blocks and stops at the first block something else can also reach, which is + * the join. */ basic_block_t *if_arm_chain(basic_block_t *arm, basic_block_t **chain, @@ -1272,6 +1276,7 @@ basic_block_t *if_arm_chain(basic_block_t *arm, continue; if (!insn_is_speculatable(insn)) return NULL; + /* Writing a global or an address-taken variable is a store, and a * store is a side effect however plain the arithmetic producing it * looks. @@ -1360,6 +1365,7 @@ bool if_convert_bb(func_t *func, basic_block_t *bb) if (!join || join != e_join) return false; + /* Flattening removes both arms, so the join must be reached from them and * nothing else. */ @@ -1413,6 +1419,7 @@ bool if_convert_bb(func_t *func, basic_block_t *bb) next = insn->next; if (insn == skip) continue; + /* Whatever the arm computes is a version of the variable the * select writes as often as not, and that variable's pinned * register still has to carry the value the arms read. @@ -1472,8 +1479,8 @@ int sr_gen; * @skip. * * With @loop_only set, only the blocks the stamped loop covers are searched: a - * reader outside it does not keep a literal alive inside, because the - * allocator materialises one wherever the value is wanted. + * reader outside it does not keep a literal alive inside, because the allocator + * materialises one wherever the value is wanted. */ bool var_read_by(func_t *func, var_t *var, @@ -1530,8 +1537,9 @@ bool thread_const_branch(func_t *func, basic_block_t *join) if (!join->then_ || !join->else_) return false; - /* Only a block that does nothing else: anything before the branch would - * be skipped, and a phi copy left in it belongs to a successor. + + /* Only a block that does nothing else: anything before the branch would be + * skipped, and a phi copy left in it belongs to a successor. */ if (!br || br->next || br->opcode != OP_branch || !br->rs1) return false; @@ -1549,8 +1557,9 @@ bool thread_const_branch(func_t *func, basic_block_t *join) if (!pred || pred == join) continue; - /* A predecessor that also goes somewhere else keeps a branch of - * its own, and rewiring one of its edges would need that branch + + /* A predecessor that also goes somewhere else keeps a branch of its + * own, and rewiring one of its edges would need that branch * rewritten. */ if (pred->then_ || pred->else_ || pred->next != join) @@ -1630,8 +1639,8 @@ void thread_const_branches(void) * into the argument registers, everything live crosses a call boundary and so * goes to the frame, and the callee builds and tears down a frame of its own. * The benchmark suite's "calls" case spends three instructions on that for - * every one it spends computing. Copying the body in removes all of it and - * lets the optimizer see the caller and the callee together. + * every one it spends computing. Copying the body in removes all of it and lets + * the optimizer see the caller and the callee together. * * Only a body with no control flow of its own is copied, which keeps the * transformation to splicing one instruction list into another. @@ -1640,9 +1649,9 @@ var_t *inline_from[MAX_INLINE_VARS]; var_t *inline_to[MAX_INLINE_VARS]; int inline_map_n; -/* The caller's stand-in for the callee's @var: the argument for a parameter, - * a fresh variable for anything the body computes, and the variable itself - * for anything shared. +/* The caller's stand-in for the callee's @var: the argument for a parameter, a + * fresh variable for anything the body computes, and the variable itself for + * anything shared. */ var_t *inline_lookup(var_t *var, block_t *scope) { @@ -1653,8 +1662,9 @@ var_t *inline_lookup(var_t *var, block_t *scope) for (int i = 0; i < inline_map_n; i++) { if (inline_from[i] == var) return inline_to[i]; - /* Parameters are mapped by the variable they are versions of, so that - * a body naming a later version still finds the argument. + + /* Parameters are mapped by the variable they are versions of, so that a + * body naming a later version still finds the argument. */ if (inline_from[i] && inline_from[i] == var->base) return inline_to[i]; @@ -1678,10 +1688,10 @@ var_t *inline_lookup(var_t *var, block_t *scope) /* Whether @func is small and straight-line enough to copy into its callers. * - * A function is a chain of blocks even when its source has no control flow: - * the entry block holds the declarations and falls into the body. Any block - * that branches, or that something else can reach, ends the chain and makes - * the function too complicated to splice into an instruction list. + * A function is a chain of blocks even when its source has no control flow: the + * entry block holds the declarations and falls into the body. Any block that + * branches, or that something else can reach, ends the chain and makes the + * function too complicated to splice into an instruction list. */ int inline_round; @@ -1695,8 +1705,8 @@ int inline_round; * anything is touched is what keeps that from needing to be undone. * * The count mirrors inline_lookup()'s matching exactly: a global is shared - * rather than copied, a parameter is found through the variable it is a - * version of, and every other version gets an entry of its own. + * rather than copied, a parameter is found through the variable it is a version + * of, and every other version gets an entry of its own. */ bool inline_map_fits(func_t *func) { @@ -1763,6 +1773,7 @@ bool func_is_inlinable(func_t *func) return false; if (bb != func->bbs && bb_pred_count(bb) != 1) return false; + /* The single-predecessor test above is skipped for the entry block, so * a "next" chain that came back round to it would walk for ever. A * chain longer than the instruction budget cannot be inlinable in any @@ -1778,15 +1789,16 @@ bool func_is_inlinable(func_t *func) last = insn; if (insn->opcode == OP_return) continue; + /* A call of its own would have to be copied as a call, which is * what the copying is meant to remove; the pass runs again once - * that callee has been copied in, and by then this body - * qualifies. + * that callee has been copied in, and by then this body qualifies. */ if (!insn_is_speculatable(insn)) return false; if (insn->rd && (insn->rd->is_global || insn->rd->address_taken)) return false; + /* Writing a parameter would mean the copy assigns to the caller's * own variable, since a parameter maps onto the argument. */ @@ -1824,12 +1836,13 @@ insn_t *inline_clone(basic_block_t *bb, return copy; } -/* Replace the push/call/retval sequence ending at @call with the callee's - * body. Returns the instruction to carry on scanning from, which is NULL when - * the copy lands at the end of the block; *@done says whether the call was - * replaced at all. The two have to be reported separately -- a NULL return - * read as "left alone" stopped the round from being counted as progress and - * left the rest of the block unscanned. +/* Replace the push/call/retval sequence ending at @call with the callee's body. + * + * Returns the instruction to carry on scanning from, which is NULL when the + * copy lands at the end of the block; *@done says whether the call was replaced + * at all. The two have to be reported separately -- a NULL return read as "left + * alone" stopped the round from being counted as progress and left the rest of + * the block unscanned. * * @done is an int and not a bool on purpose. A _Bool is one byte, and writing * one byte through a pointer into the caller's slot -- which is pointer-sized @@ -1860,11 +1873,11 @@ insn_t *inline_call_at(basic_block_t *bb, insn_t *call, int *done) if (argc != callee->num_params || argc > MAX_PARAMS) return NULL; - /* A call nested in another call's argument list -- "f(g(x), y)" -- has - * the outer call's pushes already standing before it. The register - * allocator hands those the argument registers as it meets them, so - * anything spliced in between would overwrite arguments the outer call is - * still waiting to make. + /* A call nested in another call's argument list -- "f(g(x), y)" -- has the + * outer call's pushes already standing before it. The register allocator + * hands those the argument registers as it meets them, so anything spliced + * in between would overwrite arguments the outer call is still waiting to + * make. */ for (insn_t *p = first->prev; p; p = p->prev) { if (p->opcode == OP_call || p->opcode == OP_indirect) @@ -1961,9 +1974,10 @@ void inline_calls(void) if (done) { next = resume; changed = true; - /* The body just grew, so the verdict cached for it - * this round no longer describes it -- and losing a - * call may be exactly what makes it copyable. + + /* The body just grew, so the verdict cached for it this + * round no longer describes it -- and losing a call may + * be exactly what makes it copyable. */ func->inline_gen = 0; } @@ -1983,12 +1997,11 @@ void inline_calls(void) * at the bottom leaves the body with the access alone -- matmul's innermost * loop spent six of its sixteen instructions on the two subscripts. * - * Unlike hoisting a loop-invariant value, this pays even when the result has - * to live on the frame: the loop trades a four-instruction recomputation for - * one addition, where hoisting trades a recomputation for a reload. - */ -/* The variable the loop counts with, where the chain walk stops: its value - * before the loop is what the first address is computed from. + * Unlike hoisting a loop-invariant value, this pays even when the result has to + * live on the frame: the loop trades a four-instruction recomputation for one + * addition, where hoisting trades a recomputation for a reload. The variable + * the loop counts with, where the chain walk stops: its value before the loop + * is what the first address is computed from. */ var_t *sr_iv; @@ -2000,8 +2013,8 @@ var_t *sr_base(var_t *var) } /* Whether @var is written inside the loop being examined. A constant never is, - * wherever its materialisation sits: the allocator emits one wherever the - * value is wanted. + * wherever its materialisation sits: the allocator emits one wherever the value + * is wanted. */ bool sr_varies(var_t *var) { @@ -2013,8 +2026,8 @@ bool sr_varies(var_t *var) return base && base->loop_stamp == sr_gen; } -/* How much @var moves per iteration, into *step. Reports false when that is - * not known -- which for a variable the loop writes means "not yet derived". +/* How much @var moves per iteration, into *step. Reports false when that is not + * known -- which for a variable the loop writes means "not yet derived". */ bool sr_step_of(var_t *var, int *step) { @@ -2050,11 +2063,11 @@ void sr_set_step(var_t *var, int step) base->iv_step = step; } -/* Whether @insn is one the pass is willing to lift out of a loop: it computes - * a value from its operands, touches no memory and faults on nothing. Taking - * an array's address qualifies -- that address is the same on every - * iteration -- and a literal does not, because the allocator materialises one - * wherever it is wanted and moving it would only lengthen a live range. +/* Whether @insn is one the pass is willing to lift out of a loop: it computes a + * value from its operands, touches no memory and faults on nothing. Taking an + * array's address qualifies -- that address is the same on every iteration -- + * and a literal does not, because the allocator materialises one wherever it is + * wanted and moving it would only lengthen a live range. */ bool sr_movable(insn_t *insn) { @@ -2137,12 +2150,13 @@ var_t *sr_basic_iv(func_t *func, basic_block_t *latch, int *step) } if (!addend || !addend->init_val) continue; + /* The literal has to be added to the variable itself, not to * something computed from it: a temporary made from a variable - * carries that variable as its base, so "g = g * 2 + 1" matches - * the shape as readily as "i = i + 1" and would be taken for a - * counter stepping by one. The value flowing in is the one the - * loop either carries round in a phi or never writes. + * carries that variable as its base, so "g = g * 2 + 1" matches the + * shape as readily as "i = i + 1" and would be taken for a counter + * stepping by one. The value flowing in is the one the loop either + * carries round in a phi or never writes. */ insn_t *src = sr_def_of(func, carried); @@ -2162,9 +2176,9 @@ var_t *sr_basic_iv(func_t *func, basic_block_t *latch, int *step) if (!found) return NULL; - /* One variable the loop writes twice is not a counter: "i++" in one arm - * and "i--" in another moves it by neither step, and an address derived - * from it would advance by a fixed amount that matches neither. + /* One variable the loop writes twice is not a counter: "i++" in one arm and + * "i--" in another moves it by neither step, and an address derived from it + * would advance by a fixed amount that matches neither. */ int writes = 0; basic_block_t *wblk = NULL; @@ -2175,19 +2189,19 @@ var_t *sr_basic_iv(func_t *func, basic_block_t *latch, int *step) for (insn_t *insn = bb->insn_list.head; insn; insn = insn->next) { /* Only a copy back into the variable counts. The arithmetic that * produces the new value writes a temporary, and a temporary made - * from a variable carries that variable as its base. - */ - /* Only a write of the variable itself counts. The arithmetic - * that produces the new value writes a temporary, and a temporary - * made from a variable carries that variable as its base -- but a - * select names the variable, and one writing the counter means the - * counter does not advance on every trip round. + * from a variable carries that variable as its base. Only a write + * of the variable itself counts. The arithmetic that produces the + * new value writes a temporary, and a temporary made from a + * variable carries that variable as its base -- but a select names + * the variable, and one writing the counter means the counter does + * not advance on every trip round. */ if (insn->opcode != OP_assign && insn->opcode != OP_unwound_phi && insn->opcode != OP_cmov) continue; if (!insn->rd || sr_base(insn->rd) != found) continue; + /* A phi copying the variable to itself carries it around the loop * rather than giving it a new value. */ @@ -2201,9 +2215,9 @@ var_t *sr_basic_iv(func_t *func, basic_block_t *latch, int *step) return NULL; /* The write has to happen on every trip round, because the pointer derived - * from the counter is advanced on every trip round. A "continue" that - * jumps over the sole increment leaves the two disagreeing, and the loop - * reads one element too far from then on. + * from the counter is advanced on every trip round. A "continue" that jumps + * over the sole increment leaves the two disagreeing, and the loop reads + * one element too far from then on. */ if (wblk != latch && !is_dominate(wblk, latch)) return NULL; @@ -2331,9 +2345,10 @@ void sr_emit_advance(basic_block_t *latch, var_t *var, int step) insn_t *add; insn_t *assign; - /* A loop latch ends in the jump back to its header. Appending the - * advance after that jump leaves it unreachable, so place the whole - * sequence before the terminator instead. */ + /* A loop latch ends in the jump back to its header. Appending the advance + * after that jump leaves it unreachable, so place the whole sequence before + * the terminator instead. + */ if (after && (after->opcode == OP_branch || after->opcode == OP_jump || after->opcode == OP_return || after->opcode == OP_func_ret)) after = after->prev; @@ -2398,10 +2413,10 @@ bool sr_reduce_access(func_t *func, * The walk collected every definition before its operands, so reversing it * is nearly right -- but only while the chain is a straight line. A value * two of them share is collected under the first, and reversing then puts - * it after the second, which would read a variable nothing had defined - * yet. Choosing repeatedly instead is correct either way: take an entry - * once every chain value it reads has been taken. Nothing is moved until - * the whole order is settled, so a chain that cannot be ordered at all is + * it after the second, which would read a variable nothing had defined yet. + * Choosing repeatedly instead is correct either way: take an entry once + * every chain value it reads has been taken. Nothing is moved until the + * whole order is settled, so a chain that cannot be ordered at all is * declined rather than half-moved. */ int order[MAX_IV_CHAIN + 1]; @@ -2521,8 +2536,8 @@ void sr_loop(func_t *func, basic_block_t *header, basic_block_t *latch) int step = 0, made = 0; /* One way back, and one way in that runs everything before the loop: the - * first address is computed there and advanced there. The test on the - * latch costs nothing and comes before the walk that marks the loop. + * first address is computed there and advanced there. The test on the latch + * costs nothing and comes before the walk that marks the loop. */ if (latch->then_ || latch->else_ || latch->next != header) return; @@ -2567,6 +2582,7 @@ void sr_loop(func_t *func, basic_block_t *header, basic_block_t *latch) next = insn->next; if (insn->opcode != OP_read && insn->opcode != OP_write) continue; + /* A reduction takes instructions out of this block, so the walk * starts again rather than following a pointer into the block they * moved to. @@ -2663,8 +2679,8 @@ void unwind_phi(void) * The walk goes up from @succ rather than down through everything @pred * dominates: the answer lies on the one path to the root, where the downward * search visited @pred's whole subtree and did not stop when it found it. - * dom_prev is the inverse of the dom_next the search followed, so the two - * agree on every pair. + * dom_prev is the inverse of the dom_next the search followed, so the two agree + * on every pair. */ bool is_dominate(basic_block_t *pred, basic_block_t *succ) { @@ -2675,9 +2691,8 @@ bool is_dominate(basic_block_t *pred, basic_block_t *succ) return false; } -/* - * For any variable, the basic block that defines it must dominate all the - * basic blocks where it is used; otherwise, it is an invalid cross-block +/* For any variable, the basic block that defines it must dominate all the basic + * blocks where it is used; otherwise, it is an invalid cross-block * initialization. */ void bb_check_var_cross_init(func_t *func, basic_block_t *bb) @@ -2702,14 +2717,12 @@ void bb_check_var_cross_init(func_t *func, basic_block_t *bb) } /** - * A variable's initialization lives in a basic block that does not dominate - * all of its uses, so control flow can reach a use without first passing - * through its initialization (i.e., a possibly-uninitialized use). + * A variable's initialization lives in a basic block that does not dominate all + * of its uses, so control flow can reach a use without first passing through + * its initialization (i.e., a possibly-uninitialized use). * - * For Example: - * // Jumps directly to 'label', skipping the declaration below - * goto label; - * if (1) { + * For Example: // Jumps directly to 'label', skipping the declaration below + * goto label; if (1) { * // This line is never executed when 'goto' is taken * int x; * label: @@ -3237,8 +3250,9 @@ bool is_cse_candidate(insn_t *insn) } } -/* Common Subexpression Elimination (CSE) */ -/* Enhanced to support general binary operations */ +/* Common Subexpression Elimination (CSE) Enhanced to support general binary + * operations + */ bool cse(insn_t *insn, basic_block_t *bb) { /* Handle array access pattern: add + read */ @@ -3369,8 +3383,9 @@ bool mark_const(insn_t *insn) */ if (insn->rd->is_global) return false; - /* Copying from such a variable is no better: the value read is whatever - * the pointer last wrote, not the constant the source was assigned. + + /* Copying from such a variable is no better: the value read is whatever the + * pointer last wrote, not the constant the source was assigned. */ if (insn->rs1->address_taken) return false; @@ -3571,6 +3586,7 @@ void dce_init_push(insn_t *work_list[], int dce_init_mark(insn_t *insn, insn_t *work_list[], int work_list_idx) { int mark_num = 0; + /* mark instruction "useful" if it sets a return value, affects the value in * a storage location, or it is a function call. */ @@ -3742,6 +3758,7 @@ void dce_sweep(void) insn_t *next = insn->next; if (!insn->useful) { total_eliminated++; + /* If a branch instruction is useless, redirect to the * reverse immediate dominator of this basic block and * remove the branch instruction. Later, register allocation @@ -3871,8 +3888,9 @@ void optimize(void) } } - /* Enhanced algebraic simplifications */ - /* Self-operation optimizations */ + /* Enhanced algebraic simplifications Self-operation + * optimizations + */ if (insn->rs1 && insn->rs2 && insn->rs1 == insn->rs2) { /* x - x = 0 */ if (insn->opcode == OP_sub && insn->rd) { @@ -4002,8 +4020,9 @@ void optimize(void) } } - /* Multi-instruction analysis and optimization */ - /* Store-to-load forwarding */ + /* Multi-instruction analysis and optimization Store-to-load + * forwarding + */ if (insn->opcode == OP_load && insn->rs1 && insn->rd) { insn_t *search = insn->prev; int search_limit = 10; /* Look back up to 10 instructions */ @@ -4226,10 +4245,10 @@ void bb_reset_and_solve_locals(func_t *func, basic_block_t *bb) bb->live_kill.size = 0; /* Both sets are asked about once per operand and once per destination, and - * answering from the lists themselves means a scan of one of them for - * every one of a block's instructions -- quadratic in the size of the - * block, which is what made this the most expensive part of the analysis - * on shecc's own longer functions. Stamping a variable as it enters a set + * answering from the lists themselves means a scan of one of them for every + * one of a block's instructions -- quadratic in the size of the block, + * which is what made this the most expensive part of the analysis on + * shecc's own longer functions. Stamping a variable as it enters a set * turns each of those questions into one comparison. live_kill was just * emptied, so nothing carries a stale stamp; live_gen is not, so what it * already holds is stamped first. @@ -4244,8 +4263,8 @@ void bb_reset_and_solve_locals(func_t *func, basic_block_t *bb) for (insn_t *insn = bb->insn_list.head; insn; insn = insn->next) { insn->idx = i++; - /* The three source operands are treated alike; the third is the value - * a select keeps when its condition does not hold. + /* The three source operands are treated alike; the third is the value a + * select keeps when its condition does not hold. */ var_t *srcs[3]; srcs[0] = insn->rs1; @@ -4352,8 +4371,9 @@ bool recompute_live_out(basic_block_t *bb) return true; } - /* Size is same, need to check if contents are identical */ - /* Optimize by checking if first few elements match (common case) */ + /* Size is same, need to check if contents are identical Optimize by + * checking if first few elements match (common case) + */ if (live_out_idx > 0) { /* Quick check first element */ bool first_found = false; diff --git a/tests/arm-abi.sh b/tests/arm-abi.sh index b4aff266..fd1b2771 100755 --- a/tests/arm-abi.sh +++ b/tests/arm-abi.sh @@ -56,16 +56,20 @@ fi case "$1" in "0") readonly SHECC="$PWD/out/shecc" - readonly STAGE="Stage 0 (Host Compiler)" ;; + readonly STAGE="Stage 0 (Host Compiler)" + ;; "1") readonly SHECC="${TARGET_EXEC:-} $PWD/out/shecc-stage1.elf" - readonly STAGE="Stage 1 (Cross-compiled)" ;; + readonly STAGE="Stage 1 (Cross-compiled)" + ;; "2") readonly SHECC="${TARGET_EXEC:-} $PWD/out/shecc-stage2.elf" - readonly STAGE="Stage 2 (Self-hosted)" ;; + readonly STAGE="Stage 2 (Self-hosted)" + ;; *) echo "Error: Invalid stage '$1'. Use 0, 1, or 2." - exit 1 ;; + exit 1 + ;; esac DYNLINK="${2:-0}" @@ -80,9 +84,10 @@ echo -e "Compiler: $SHECC" echo "" # Helper Functions -update_category_stats() { +update_category_stats() +{ local category="$1" - local result="$2" # "pass" or "fail" + local result="$2" # "pass" or "fail" if [[ -z "${CATEGORY_TESTS[$category]:-}" ]]; then CATEGORY_TESTS[$category]=0 @@ -99,7 +104,8 @@ update_category_stats() { fi } -show_progress() { +show_progress() +{ if [[ "$SHOW_PROGRESS" == "1" ]]; then echo -n "." PROGRESS_COUNT=$((PROGRESS_COUNT + 1)) @@ -110,7 +116,8 @@ show_progress() { } # Test execution function -run_abi_test() { +run_abi_test() +{ local test_name="$1" local category="$2" local source_code="$3" @@ -164,11 +171,10 @@ run_abi_test() { run_output=$(eval "$run_cmd" 2>&1) exit_code=$? - # Check result - # If the exit code is not zero or the output is not expected, - # set 'run_status' to "FAILED". + # If the exit code is not zero or the output is not expected, set + # 'run_status' to "FAILED". run_status="SUCCESS" - if [[ $exit_code -ne 0 || ( -n "$expected_output" && "$run_output" != *"$expected_output"* ) ]]; then + if [[ $exit_code -ne 0 || (-n "$expected_output" && "$run_output" != *"$expected_output"*) ]]; then run_status="FAILED" fi @@ -200,7 +206,8 @@ run_abi_test() { # Parameter Passing Tests -test_one_arg() { +test_one_arg() +{ run_abi_test "One argument (r0)" "Parameter Passing" ' #include int add_42(int x) { return x + 42; } @@ -216,7 +223,8 @@ int main() { ' "PASS" } -test_two_args() { +test_two_args() +{ run_abi_test "Two arguments (r0, r1)" "Parameter Passing" ' #include int add(int a, int b) { return a + b; } @@ -232,7 +240,8 @@ int main() { ' "PASS" } -test_four_args() { +test_four_args() +{ run_abi_test "Four arguments (r0-r3)" "Parameter Passing" ' #include int sum4(int a, int b, int c, int d) { return a + b + c + d; } @@ -248,7 +257,8 @@ int main() { ' "PASS" } -test_five_args() { +test_five_args() +{ run_abi_test "Five arguments (r0-r3 + stack)" "Parameter Passing" ' #include int sum5(int a, int b, int c, int d, int e) { return a + b + c + d + e; } @@ -264,7 +274,8 @@ int main() { ' "PASS" } -test_eight_args() { +test_eight_args() +{ run_abi_test "Eight arguments (stack-heavy)" "Parameter Passing" ' #include int sum8(int a, int b, int c, int d, int e, int f, int g, int h) { @@ -284,7 +295,8 @@ int main() { # Stack Alignment Tests -test_stack_alignment_basic() { +test_stack_alignment_basic() +{ run_abi_test "Basic stack alignment" "Stack Alignment" ' #include int is_aligned(void *ptr) { @@ -306,7 +318,8 @@ int main() { ' "PASS" } -test_stack_alignment_extended() { +test_stack_alignment_extended() +{ run_abi_test "Stack alignment with extended args" "Stack Alignment" ' #include int is_aligned(void *ptr) { @@ -331,7 +344,8 @@ int main() { # Return Value Tests -test_return_char() { +test_return_char() +{ run_abi_test "Return char value" "Return Values" ' #include char get_char(void) { return '\''A'\''; } @@ -346,7 +360,8 @@ int main() { ' "PASS" } -test_return_int() { +test_return_int() +{ run_abi_test "Return int value" "Return Values" ' #include int get_value(void) { return 12345; } @@ -361,7 +376,8 @@ int main() { ' "PASS" } -test_return_pointer() { +test_return_pointer() +{ run_abi_test "Return pointer value" "Return Values" ' #include int *return_ptr(int *p) { return p; } @@ -380,7 +396,8 @@ int main() { # External Function Call Tests (Dynamic Linking Only) -test_printf_one_arg() { +test_printf_one_arg() +{ run_abi_test "printf with 1 argument" "External Calls" ' #include int main() { @@ -390,7 +407,8 @@ int main() { ' "PASS" 1 } -test_printf_multi_args() { +test_printf_multi_args() +{ run_abi_test "printf with 5 arguments" "External Calls" ' #include int main() { @@ -400,7 +418,8 @@ int main() { ' "Values: 1 2 3 4" 1 } -test_strlen() { +test_strlen() +{ run_abi_test "strlen external call" "External Calls" ' #include #include @@ -416,7 +435,8 @@ int main() { ' "PASS" 1 } -test_strcpy() { +test_strcpy() +{ run_abi_test "strcpy external call" "External Calls" ' #include #include @@ -434,7 +454,8 @@ int main() { ' "PASS" 1 } -test_memcpy() { +test_memcpy() +{ run_abi_test "memcpy external call" "External Calls" ' #include #include @@ -454,7 +475,8 @@ int main() { # Register Preservation Tests -test_local_vars_preserved() { +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) { @@ -473,7 +495,8 @@ int main() { ' "PASS" } -test_recursive_preservation() { +test_recursive_preservation() +{ run_abi_test "Register preservation in recursion" "Register Preservation" ' #include int factorial(int n) { @@ -495,7 +518,8 @@ int main() { # Structure Passing Tests -test_small_struct() { +test_small_struct() +{ run_abi_test "Small struct passing (≤4 bytes)" "Structure Passing" ' #include typedef struct { char a; char b; short c; } SmallStruct; diff --git a/tests/driver.sh b/tests/driver.sh index ec08547e..1cdfb0a2 100755 --- a/tests/driver.sh +++ b/tests/driver.sh @@ -11,16 +11,18 @@ readonly SHOW_PROGRESS="${SHOW_PROGRESS:-1}" readonly COLOR_OUTPUT="${COLOR_OUTPUT:-1}" # Substring match against the category name; empty runs everything. readonly TEST_FILTER="${TEST_FILTER:-}" + # 1 stops at the first failure. The default reports every failure and still # exits non-zero at the end, so one bad case no longer hides the other 600. readonly FAIL_FAST="${FAIL_FAST:-0}" -# Everything the run creates goes here, so it can be removed in one step -- -# the suite used to leave ~2400 files in /tmp per invocation. Kept on failure, +# Everything the run creates goes here, so it can be removed in one step -- the +# suite used to leave ~2400 files in /tmp per invocation. Kept on failure, # because report_test_failure names the files it wants you to look at. readonly TEST_TMPDIR="$(mktemp -d)" export TMPDIR="$TEST_TMPDIR" -function cleanup() { +function cleanup() +{ if [ "$FAILED_TESTS" -eq 0 ]; then rm -rf "$TEST_TMPDIR" else @@ -32,7 +34,8 @@ trap cleanup EXIT # Set by begin_category; tests outside the selected categories return early. CATEGORY_SELECTED=1 -function test_selected() { +function test_selected() +{ [ "$CATEGORY_SELECTED" = "1" ] } @@ -43,7 +46,7 @@ readonly TESTS_DIR="$(cd "$(dirname "$0")" && pwd)" # Pointer width of the configured target. The sizeof tests below assert on it, # and it differs between the 32-bit targets and x86-64. PTR_SZ=$(sed -n 's/^#define PTR_SIZE \([0-9]*\).*/\1/p' \ - "$TESTS_DIR/../config" 2>/dev/null | head -1) + "$TESTS_DIR/../config" 2> /dev/null | head -1) [ -n "${PTR_SZ}" ] || PTR_SZ=4 # Variadic arguments occupy one pointer-sized slot each, so an int-based walk @@ -86,16 +89,20 @@ fi case "$1" in "0") readonly SHECC="$PWD/out/shecc" - readonly STAGE="Stage 0 (Host Compiler)" ;; + readonly STAGE="Stage 0 (Host Compiler)" + ;; "1") readonly SHECC="${TARGET_EXEC:-} $PWD/out/shecc-stage1.elf" - readonly STAGE="Stage 1 (Cross-compiled)" ;; + readonly STAGE="Stage 1 (Cross-compiled)" + ;; "2") readonly SHECC="${TARGET_EXEC:-} $PWD/out/shecc-stage2.elf" - readonly STAGE="Stage 2 (Self-hosted)" ;; + readonly STAGE="Stage 2 (Self-hosted)" + ;; *) echo "$1 is not a valid stage" - exit 1 ;; + exit 1 + ;; esac if [ $# -ge 2 ] && [ "$2" = "1" ]; then @@ -109,15 +116,16 @@ fi # Utility Functions # Color output functions -function print_color() { +function print_color() +{ if [ "$COLOR_OUTPUT" = "1" ]; then case "$1" in - green) echo -ne "\033[32m$2\033[0m" ;; - red) echo -ne "\033[31m$2\033[0m" ;; + green) echo -ne "\033[32m$2\033[0m" ;; + red) echo -ne "\033[31m$2\033[0m" ;; yellow) echo -ne "\033[33m$2\033[0m" ;; - blue) echo -ne "\033[34m$2\033[0m" ;; - bold) echo -ne "\033[1m$2\033[0m" ;; - *) echo -n "$2" ;; + blue) echo -ne "\033[34m$2\033[0m" ;; + bold) echo -ne "\033[1m$2\033[0m" ;; + *) echo -n "$2" ;; esac else echo -n "$2" @@ -125,7 +133,8 @@ function print_color() { } # Begin a new test category -function begin_category() { +function begin_category() +{ local category="$1" local description="${2:-}" @@ -146,8 +155,8 @@ function begin_category() { CATEGORY_SELECTED=1 else case "$category" in - *"$TEST_FILTER"*) CATEGORY_SELECTED=1 ;; - *) CATEGORY_SELECTED=0 ;; + *"$TEST_FILTER"*) CATEGORY_SELECTED=1 ;; + *) CATEGORY_SELECTED=0 ;; esac fi CATEGORY_TESTS["$category"]=0 @@ -167,7 +176,8 @@ function begin_category() { } # Show progress indicator -function show_progress() { +function show_progress() +{ if [ "$SHOW_PROGRESS" = "1" ]; then ((PROGRESS_COUNT++)) if [ $((PROGRESS_COUNT % 10)) -eq 0 ]; then @@ -180,7 +190,8 @@ function show_progress() { } # Core test failure reporting function (consolidated) -function report_test_failure() { +function report_test_failure() +{ local test_type="$1" local tmp_in="$2" local tmp_exe="$3" @@ -223,7 +234,8 @@ function report_test_failure() { } # Main test execution function -function try() { +function try() +{ local expected="$1" local expected_output="" local input="" @@ -234,6 +246,7 @@ function try() { elif [ $# -eq 3 ]; then expected_output="$2" input="$3" + # An expectation was supplied, so compare against it -- including when # it is empty, which asserts that the program prints nothing. check_output=1 @@ -245,9 +258,10 @@ function try() { local tmp_exe="$(mktemp)" local tmp_err="$(mktemp)" echo "$input" > "$tmp_in" + # Keep the compiler's diagnostic rather than discarding it: without it a # failure reports only an exit-code mismatch and never says why. - $SHECC $SHECC_CFLAGS -o "$tmp_exe" "$tmp_in" 2>"$tmp_err" + $SHECC $SHECC_CFLAGS -o "$tmp_exe" "$tmp_in" 2> "$tmp_err" chmod +x $tmp_exe local output='' @@ -273,13 +287,15 @@ function try() { fi } -function try_() { +function try_() +{ local expected="$1" local input="$(cat)" try "$expected" "$input" } -function try_output() { +function try_output() +{ local expected="$1" local expected_output="$2" local input="$(cat)" @@ -288,32 +304,32 @@ function try_output() { # Compile and run a checked-in program through the same path as inline cases. # This keeps the small end-to-end programs in both stage-0 and stage-2 runs. -function try_file() { +function try_file() +{ try "$1" "$2" "$(< "$3")" } -# try_compile_error - test shecc with invalid C program -# Usage: -# - try_compile_error invalid_input_code -# compile "invalid_input_code" with shecc so that shecc generates a -# compilation error message. +# try_compile_error - test shecc with invalid C program Usage: +# - try_compile_error invalid_input_code compile "invalid_input_code" with shecc +# so that shecc generates a compilation error message. # -# This function uses shecc to compile invalid code and obtains the exit -# code returned by shecc. The exit code must be a non-zero value to -# indicate that shecc has the ability to parse the invalid code and -# output an error message. -function try_compile_error() { +# This function uses shecc to compile invalid code and obtains the exit code +# returned by shecc. The exit code must be a non-zero value to indicate that +# shecc has the ability to parse the invalid code and output an error message. +function try_compile_error() +{ local input=$(cat) test_selected || return 0 local tmp_in="$(mktemp --suffix .c)" local tmp_exe="$(mktemp)" echo "$input" > "$tmp_in" - # Suppress compiler error output and "Aborted" messages completely - # Run in a subshell with job control disabled + + # Suppress compiler error output and "Aborted" messages completely Run in a + # subshell with job control disabled ( - set +m 2>/dev/null # Disable job control messages + set +m 2> /dev/null # Disable job control messages $SHECC $SHECC_CFLAGS -o "$tmp_exe" "$tmp_in" 2>&1 - ) >/dev/null 2>&1 + ) > /dev/null 2>&1 local exit_code=$? ((TOTAL_TESTS++)) @@ -331,20 +347,23 @@ function try_compile_error() { fi } -function items() { +function items() +{ local expected="$1" local input="$2" try "$expected" "int main(int argc, int argv) { $input }" } -function expr() { +function expr() +{ local expected="$1" local input="$2" items "$expected" "exit($input);" } # Batch test runners for common patterns -function run_expr_tests() { +function run_expr_tests() +{ local -n tests_ref=$1 for test in "${tests_ref[@]}"; do IFS=' ' read -r expected code <<< "$test" @@ -352,7 +371,8 @@ function run_expr_tests() { done } -function run_try_tests() { +function run_try_tests() +{ local -n tests_ref=$1 for test in "${tests_ref[@]}"; do local expected=$(echo "$test" | head -n1) @@ -361,7 +381,8 @@ function run_try_tests() { done } -function run_items_tests() { +function run_items_tests() +{ local -n tests_ref=$1 for test in "${tests_ref[@]}"; do IFS=' ' read -r expected code <<< "$test" @@ -369,12 +390,12 @@ function run_items_tests() { done } -# try_large - test shecc with large return values (> 255) -# Usage: -# - try_large expected_value input_code -# compile "input_code" with shecc and verify the return value by printing it -# instead of using exit code (which is limited to 0-255). -function try_large() { +# try_large - test shecc with large return values (> 255) Usage: +# - try_large expected_value input_code compile "input_code" with shecc and +# verify the return value by printing it instead of using exit code (which is +# limited to 0-255). +function try_large() +{ local expected="$1" local input="$(cat)" @@ -394,7 +415,7 @@ int main() { EOF # Suppress compiler warnings by redirecting stderr - $SHECC $SHECC_CFLAGS -o "$tmp_exe" "$tmp_in" 2>/dev/null + $SHECC $SHECC_CFLAGS -o "$tmp_exe" "$tmp_in" 2> /dev/null chmod +x $tmp_exe local output=$(${TARGET_EXEC:-} "$tmp_exe") @@ -590,7 +611,7 @@ declare -a bitwise_tests=( ) run_expr_tests bitwise_tests -try_output 0 "128 59926 -6 -4 -500283" << EOF +try_output 0 "128 59926 -6 -4 -500283" << EOF int main() { printf("%d %d %d %d %d", 32768 >> 8, 245458999 >> 12, -11 >> 1, -16 >> 2, -1000565 >> 1); return 0; @@ -630,8 +651,8 @@ run_items_tests variable_tests # Category: Compound Literals begin_category "Compound Literals" "Testing C99 compound literal features" -# Compound literal support - C90/C99 compliant implementation -# Basic struct compound literals (verified working) +# Compound literal support - C90/C99 compliant implementation Basic struct +# compound literals (verified working) try_ 42 << EOF typedef struct { int x; int y; } point_t; int main() { @@ -764,7 +785,8 @@ EOF # Enhanced compound literal tests - C99 features with non-standard extensions # These tests validate both standard C99 compound literals and the non-standard -# behavior required by the test suite (array compound literals in scalar contexts) +# behavior required by the test suite (array compound literals in scalar +# contexts) # Test: Array compound literal assigned to scalar int (non-standard) try_ 100 << EOF @@ -968,7 +990,7 @@ items 8 "if (1) return 010; else return 11;" items 10 "int a; a = 012 - 10; int b; b = 0100 - 64; if (a) b = 10; else if (0) return a; else if (a) return b; else return 10;" # The values on both sides of the select, its condition, and unrelated values -# are all used after the join. This keeps the register file full when the +# are all used after the join. This keeps the register file full when the # allocator has to choose the select result's register. try_ 30 << EOF int pick(int a, int b, int c, int d, int e, int f, int g) { @@ -1023,8 +1045,7 @@ items 0 "int i = 0; for (;; i++) { break; } return i;" # Category: Comments begin_category "Comments" "Testing C-style and C++-style comment parsing" -# C-style comments / C++-style comments -# Start +# C-style comments / C++-style comments Start try_ 0 << EOF /* This is a test C-style comments */ int main() { return 0; } @@ -1172,8 +1193,8 @@ try_compile_error << EOF int main(void, int i) {} EOF -# Unreachable declaration should not cause prog segmentation fault -# (prog should leave normally with exit code 0) +# Unreachable declaration should not cause prog segmentation fault (prog should +# leave normally with exit code 0) try_ 0 << EOF int main() { @@ -1503,8 +1524,8 @@ int main() { } EOF -# Pointer difference calculations -# Test basic pointer subtraction returning element count +# Pointer difference calculations Test basic pointer subtraction returning +# element count try_ 5 << EOF int main() { char arr[10]; @@ -1784,8 +1805,8 @@ int main() { } EOF -# A local function pointer shadows a global function. Copying it must load -# the local variable's stored target, rather than materializing the global +# A local function pointer shadows a global function. Copying it must load the +# local variable's stored target, rather than materializing the global # function's address. try_ 9 << EOF int target(int x) { return x + 3; } @@ -1821,9 +1842,8 @@ int main() { } EOF - -# Addressing a pointer to a function-pointer aggregate must return the -# pointer variable's address, not backing storage for its pointee. +# Addressing a pointer to a function-pointer aggregate must return the pointer +# variable's address, not backing storage for its pointee. try_ 5 << EOF typedef struct { int (*fn)(int); @@ -1957,8 +1977,7 @@ int main() { } EOF -# 2D Array Tests -# with proper row-major indexing for multi-dimensional arrays +# 2D Array Tests with proper row-major indexing for multi-dimensional arrays try_ 78 << EOF int main() { int matrix[3][4]; @@ -2151,8 +2170,8 @@ int main() { } EOF -# Mixed subscript and arrow / dot operators, -# excerpted and modified from issue #165 +# Mixed subscript and arrow / dot operators, excerpted and modified from issue +# #165 try_output 0 "DDDDDDMMMEEE1" << EOF #include #include @@ -2426,23 +2445,23 @@ items 24 "short s; s = 6; s *= 4; return s;" begin_category "Sizeof Operator" "Testing sizeof operator on various types" # sizeof -expr 0 "sizeof(void)"; -expr 1 "sizeof(_Bool)"; -expr 1 "sizeof(char)"; -expr 2 "sizeof(short)"; -expr 4 "sizeof(int)"; +expr 0 "sizeof(void)" +expr 1 "sizeof(_Bool)" +expr 1 "sizeof(char)" +expr 2 "sizeof(short)" +expr 4 "sizeof(int)" # sizeof pointers -expr $PTR_SZ "sizeof(void*)"; -expr $PTR_SZ "sizeof(_Bool*)"; -expr $PTR_SZ "sizeof(char*)"; -expr $PTR_SZ "sizeof(short*)"; -expr $PTR_SZ "sizeof(int*)"; +expr $PTR_SZ "sizeof(void*)" +expr $PTR_SZ "sizeof(_Bool*)" +expr $PTR_SZ "sizeof(char*)" +expr $PTR_SZ "sizeof(short*)" +expr $PTR_SZ "sizeof(int*)" # sizeof multi-level pointer -expr $PTR_SZ "sizeof(void**)"; -expr $PTR_SZ "sizeof(_Bool**)"; -expr $PTR_SZ "sizeof(char**)"; -expr $PTR_SZ "sizeof(short**)"; -expr $PTR_SZ "sizeof(int**)"; +expr $PTR_SZ "sizeof(void**)" +expr $PTR_SZ "sizeof(_Bool**)" +expr $PTR_SZ "sizeof(char**)" +expr $PTR_SZ "sizeof(short**)" +expr $PTR_SZ "sizeof(int**)" # sizeof struct try_ $PTR_SZ << EOF typedef struct { @@ -2541,8 +2560,8 @@ EOF begin_category "Memory Management" "Testing malloc, free, and dynamic memory allocation" if [ "$LINK_MODE" = "static" ]; then -# malloc and free -try_ 1 << EOF + # malloc and free + try_ 1 << EOF int main() { /* change test bench if different scheme apply */ @@ -2927,10 +2946,10 @@ skip: } EOF -# Forward reference. Statements between a goto and its label are unreachable -# but perfectly legal, and gcc accepts this silently at -Wall -Wextra -# -pedantic. shecc used to abort on the unreachable "return 1;" -- this case -# asserted that abort as a compile error; it now asserts the correct result. +# Forward reference. Statements between a goto and its label are unreachable but +# perfectly legal, and gcc accepts this silently at -Wall -Wextra -pedantic. +# shecc used to abort on the unreachable "return 1;" -- this case asserted that +# abort as a compile error; it now asserts the correct result. try_ 0 << EOF int main() { @@ -3976,16 +3995,16 @@ EOF if [ "$LINK_MODE" = "static" ]; then -# printf family, including truncation and zero size input -try_output 11 "Hello World" << EOF + # printf family, including truncation and zero size input + try_output 11 "Hello World" << EOF int main() { int written = printf("Hello World"); return written; } EOF -# tests printf returns EBADF (errno 9) when stdout is closed -try_output 1 "" << EOF + # tests printf returns EBADF (errno 9) when stdout is closed + try_output 1 "" << EOF int main() { __syscall(__syscall_close, 1); @@ -3994,7 +4013,7 @@ int main() } EOF -try_output 11 "Hello World" << EOF + try_output 11 "Hello World" << EOF int main() { char buffer[50]; int written = sprintf(buffer, "Hello World"); @@ -4003,7 +4022,7 @@ int main() { } EOF -try_output 16 "Hello World 1123" << EOF + try_output 16 "Hello World 1123" << EOF int main() { char buffer[50]; int written = sprintf(buffer, "Hello %s %d", "World", 1123); @@ -4012,12 +4031,11 @@ int main() { } EOF -# The following cases validate the behavior and return value of -# snprintf(). -# -# This case is a normal case and outputs the complete string -# because the given buffer size is large enough. -try_output 16 "Hello World 1123" << EOF + # The following cases validate the behavior and return value of snprintf(). + # + # This case is a normal case and outputs the complete string because the + # given buffer size is large enough. + try_output 16 "Hello World 1123" << EOF int main() { char buffer[50]; int written = snprintf(buffer, 50, "Hello %s %d", "World", 1123); @@ -4026,11 +4044,11 @@ int main() { } EOF -# If n is zero, nothing is written. -# -# Thus, the output should be the string containing 19 characters -# for this test case. -try_output 11 "0000000000000000000" << EOF + # If n is zero, nothing is written. + # + # Thus, the output should be the string containing 19 characters for this + # test case. + try_output 11 "0000000000000000000" << EOF int main() { char buffer[20]; for (int i = 0; i < 19; i++) @@ -4042,10 +4060,10 @@ int main() { } EOF -# In this case, snprintf() only writes at most 10 bytes (including '\0'), -# but the return value is 11, which corresponds to the length of -# "Number: -37". -try_output 11 "Number: -" << EOF + # In this case, snprintf() only writes at most 10 bytes (including '\0'), + # but the return value is 11, which corresponds to the length of "Number: + # -37". + try_output 11 "Number: -" << EOF int main() { char buffer[10]; for (int i = 0; i < 9; i++) @@ -4057,7 +4075,7 @@ int main() { } EOF -try_output 14 " 4e 75 6d 62 65 72 3a 20 2d 0 30 30 30 30 30 30 30 30 30 0" << EOF + try_output 14 " 4e 75 6d 62 65 72 3a 20 2d 0 30 30 30 30 30 30 30 30 30 0" << EOF int main() { char buffer[20]; @@ -4073,8 +4091,8 @@ int main() } EOF -# A complex test case for snprintf(). -ans="written = 24 + # A complex test case for snprintf(). + ans="written = 24 buffer = buf - 00000 written = 13 buffer = aaaa - 0 @@ -4083,7 +4101,7 @@ buffer = aaaa - 000000777777 written = 14 buffer = aaaa - 000000777777 61 61 61 61 20 2d 20 30 30 30 30 30 30 37 37 37 37 37 37 0 30 30 30 30 30 30 30 30 30 0" -try_output 0 "$ans" << EOF + try_output 0 "$ans" << EOF int main() { char buffer[30]; @@ -4107,16 +4125,15 @@ int main() } EOF -# test the return value when calling fputc(). -# -# Since the FILE data type is defined as an int in -# the built-in C library, and most of the functions -# such as fputc(), fgetc(), fclose() and fgets() directly -# treat the "stream" parameter (of type FILE *) as a file -# descriptor for performing input/output operations, the -# following test cases define "stdout" as 1, which is the -# file descriptor for the standard output. -try_output 0 "awritten = a" << EOF + # test the return value when calling fputc(). + # + # Since the FILE data type is defined as an int in the built-in C library, + # and most of the functions such as fputc(), fgetc(), fclose() and fgets() + # directly treat the "stream" parameter (of type FILE *) as a file + # descriptor for performing input/output operations, the following test + # cases define "stdout" as 1, which is the file descriptor for the standard + # output. + try_output 0 "awritten = a" << EOF #define stdout 1 int main() { @@ -4126,7 +4143,7 @@ int main() } EOF -try_output 1 "" << EOF + try_output 1 "" << EOF #define stdout 1 int main() { @@ -4139,8 +4156,7 @@ else echo "Skip test cases because of using dynamic linking mode" fi # "LINK_MODE" = "static" -# tests integer type conversion -# excerpted and modified from issue #166 +# tests integer type conversion excerpted and modified from issue #166 try_output 0 "a = -127, b = -78, c = -93, d = -44" << EOF int main() { @@ -4224,8 +4240,7 @@ int main() } EOF -# Binary literal tests (0b/0B prefix) -# Test basic binary literals +# Binary literal tests (0b/0B prefix) Test basic binary literals expr 0 "0b0" expr 1 "0b1" expr 2 "0b10" @@ -4257,15 +4272,15 @@ items 54 "int a = 0b1111; int b = 0b0011; return (a + b) * 3;" items 160 "int mask = 0b11110000; int value = 0b10101010; return value & mask;" # Test combination of different number bases -expr 45 "0b1111 + 0xF + 017" # 15 + 15 + 15 = 45 -expr 90 "0b110000 + 0x10 + 032" # 48 + 16 + 26 = 90 +expr 45 "0b1111 + 0xF + 017" # 15 + 15 + 15 = 45 +expr 90 "0b110000 + 0x10 + 032" # 48 + 16 + 26 = 90 # Test binary literals in comparisons expr 1 "0b1010 == 10" expr 1 "0b11111111 == 255" expr 0 "0b1000 != 8" expr 1 "0b10000 > 0xF" -expr 1 "0B1111 < 020" # 15 < 16 (octal) +expr 1 "0B1111 < 020" # 15 < 16 (octal) # Test binary literals with large values try_large 1023 << EOF @@ -4305,8 +4320,8 @@ int main() } EOF -# New escape sequence tests (\a, \b, \v, \f) -# Test character literals with new escape sequences +# New escape sequence tests (\a, \b, \v, \f) Test character literals with new +# escape sequences try_ 7 << EOF int main() { char bell = '\a'; /* ASCII 7 - bell/alert */ @@ -4442,8 +4457,8 @@ int main() { } EOF -# Test escape sequences in printf -# Note: The bell character (\a) is non-printable but present in output +# Test escape sequences in printf Note: The bell character (\a) is non-printable +# but present in output try_output 0 "$(printf 'Bell: \a Tab:\t Newline:\n')" << EOF int main() { printf("Bell: %c Tab:%c Newline:%c", '\a', '\t', '\n'); @@ -4476,9 +4491,9 @@ int main() { } EOF -# va_list and variadic function tests -# Note: These tests demonstrate both direct pointer arithmetic and -# va_list typedef forwarding between functions, now fully supported. +# va_list and variadic function tests Note: These tests demonstrate both direct +# pointer arithmetic and va_list typedef forwarding between functions, now fully +# supported. # Test 1: Sum calculation using variadic arguments try_output 0 "Sum: 15" << EOF @@ -4686,8 +4701,8 @@ int main() } EOF -# va_list typedef forwarding tests -# These tests demonstrate va_list typedef forwarding between functions +# va_list typedef forwarding tests These tests demonstrate va_list typedef +# forwarding between functions # Test 11: Basic va_list typedef forwarding try_output 0 "Test: 42" << EOF @@ -4788,8 +4803,8 @@ int main(void) } EOF -# Complex pointer arithmetic tests -# Testing enhanced parser capability to handle expressions like *(ptr + offset) +# Complex pointer arithmetic tests Testing enhanced parser capability to handle +# expressions like *(ptr + offset) # Test 1: Basic pointer arithmetic on RHS try_output 0 "Values: 10 20 30" << EOF @@ -5355,8 +5370,8 @@ int main() } EOF -# Additional struct initialization tests from refine-parser -# Test: Local struct initialization (working with field-by-field assignment) +# Additional struct initialization tests from refine-parser Test: Local struct +# initialization (working with field-by-field assignment) try_ 42 << EOF typedef struct { int x; @@ -5420,8 +5435,7 @@ int main() { } EOF -# Union support tests -# Basic union declaration and field access +# Union support tests Basic union declaration and field access try_ 42 << EOF typedef union { int i; @@ -5638,8 +5652,8 @@ int main() { } EOF -# Sizeof union with mixed types. The largest member is the pointer, so the -# union is one pointer wide: 4 on the 32-bit targets, 8 on LP64. +# Sizeof union with mixed types. The largest member is the pointer, so the union +# is one pointer wide: 4 on the 32-bit targets, 8 on LP64. try_ $PTR_SZ << EOF typedef union { char c; @@ -5907,8 +5921,8 @@ int main() { } EOF -# Local array initializers - verify compilation and correct values -# Test 1: Implicit size array with single element +# Local array initializers - verify compilation and correct values Test 1: +# Implicit size array with single element try_ 1 << 'EOF' int main() { int a[] = {1}; @@ -6139,8 +6153,8 @@ int main() { } EOF -# Pointer dereference assignment tests -# Test Case 1: Simple pointer dereference assignment +# Pointer dereference assignment tests Test Case 1: Simple pointer dereference +# assignment try_ 0 << EOF void f(int *ap) { *ap = 0; // Should work now @@ -6355,7 +6369,7 @@ EOF echo "" if [ "$SHOW_PROGRESS" = "1" ]; then - echo "" # New line after progress indicators + echo "" # New line after progress indicators fi TEST_END_TIME=$(date +%s) @@ -6372,14 +6386,14 @@ echo "Overall Statistics:" echo " Total Tests: $TOTAL_TESTS" print_color green " Passed: $PASSED_TESTS" if [ "$PASSED_TESTS" -gt 0 ] && [ "$TOTAL_TESTS" -gt 0 ]; then - echo " ($(( PASSED_TESTS * 100 / TOTAL_TESTS ))%)" + echo " ($((PASSED_TESTS * 100 / TOTAL_TESTS))%)" else echo "" fi if [ "$FAILED_TESTS" -gt 0 ]; then print_color red " Failed: $FAILED_TESTS" - echo " ($(( FAILED_TESTS * 100 / TOTAL_TESTS ))%)" + echo " ($((FAILED_TESTS * 100 / TOTAL_TESTS))%)" else echo " Failed: 0" fi diff --git a/tests/riscv-abi.sh b/tests/riscv-abi.sh index c71b5a6a..2ec4b338 100755 --- a/tests/riscv-abi.sh +++ b/tests/riscv-abi.sh @@ -56,16 +56,20 @@ fi case "$1" in "0") readonly SHECC="$PWD/out/shecc" - readonly STAGE="Stage 0 (Host Compiler)" ;; + readonly STAGE="Stage 0 (Host Compiler)" + ;; "1") readonly SHECC="${TARGET_EXEC:-} $PWD/out/shecc-stage1.elf" - readonly STAGE="Stage 1 (Cross-compiled)" ;; + readonly STAGE="Stage 1 (Cross-compiled)" + ;; "2") readonly SHECC="${TARGET_EXEC:-} $PWD/out/shecc-stage2.elf" - readonly STAGE="Stage 2 (Self-hosted)" ;; + readonly STAGE="Stage 2 (Self-hosted)" + ;; *) echo "Error: Invalid stage '$1'. Use 0, 1, or 2." - exit 1 ;; + exit 1 + ;; esac DYNLINK="${2:-0}" @@ -80,9 +84,10 @@ echo -e "Compiler: $SHECC" echo "" # Helper Functions -update_category_stats() { +update_category_stats() +{ local category="$1" - local result="$2" # "pass" or "fail" + local result="$2" # "pass" or "fail" if [[ -z "${CATEGORY_TESTS[$category]:-}" ]]; then CATEGORY_TESTS[$category]=0 @@ -99,7 +104,8 @@ update_category_stats() { fi } -show_progress() { +show_progress() +{ if [[ "$SHOW_PROGRESS" == "1" ]]; then echo -n "." PROGRESS_COUNT=$((PROGRESS_COUNT + 1)) @@ -110,7 +116,8 @@ show_progress() { } # Test execution function -run_abi_test() { +run_abi_test() +{ local test_name="$1" local category="$2" local source_code="$3" @@ -164,11 +171,10 @@ run_abi_test() { run_output=$(eval "$run_cmd" 2>&1) exit_code=$? - # Check result - # If the exit code is not zero or the output is not expected, - # set 'run_status' to "FAILED". + # If the exit code is not zero or the output is not expected, set + # 'run_status' to "FAILED". run_status="SUCCESS" - if [[ $exit_code -ne 0 || ( -n "$expected_output" && "$run_output" != *"$expected_output"* ) ]]; then + if [[ $exit_code -ne 0 || (-n "$expected_output" && "$run_output" != *"$expected_output"*) ]]; then run_status="FAILED" fi @@ -200,7 +206,8 @@ run_abi_test() { # Parameter Passing Tests -test_one_arg() { +test_one_arg() +{ run_abi_test "One argument (a0)" "Parameter Passing" ' #include int add_42(int x) { return x + 42; } @@ -216,7 +223,8 @@ int main() { ' "PASS" } -test_two_args() { +test_two_args() +{ run_abi_test "Two arguments (a0, a1)" "Parameter Passing" ' #include int add(int a, int b) { return a + b; } @@ -232,7 +240,8 @@ int main() { ' "PASS" } -test_four_args() { +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; } @@ -248,7 +257,8 @@ int main() { ' "PASS" } -test_five_args() { +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; } @@ -264,7 +274,8 @@ int main() { ' "PASS" } -test_eight_args() { +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) { @@ -284,7 +295,8 @@ int main() { # Stack Alignment Tests -test_stack_alignment_basic() { +test_stack_alignment_basic() +{ run_abi_test "Basic stack alignment" "Stack Alignment" ' #include int is_aligned(void *ptr) { @@ -306,7 +318,8 @@ int main() { ' "PASS" } -test_stack_alignment_extended() { +test_stack_alignment_extended() +{ run_abi_test "Stack alignment with extended args" "Stack Alignment" ' #include int is_aligned(void *ptr) { @@ -331,7 +344,8 @@ int main() { # Return Value Tests -test_return_char() { +test_return_char() +{ run_abi_test "Return char value" "Return Values" ' #include char get_char(void) { return '\''A'\''; } @@ -346,7 +360,8 @@ int main() { ' "PASS" } -test_return_int() { +test_return_int() +{ run_abi_test "Return int value" "Return Values" ' #include int get_value(void) { return 12345; } @@ -361,7 +376,8 @@ int main() { ' "PASS" } -test_return_pointer() { +test_return_pointer() +{ run_abi_test "Return pointer value" "Return Values" ' #include int *return_ptr(int *p) { return p; } @@ -380,7 +396,8 @@ int main() { # External Function Call Tests (Dynamic Linking Only) -test_printf_one_arg() { +test_printf_one_arg() +{ run_abi_test "printf with 1 argument" "External Calls" ' #include int main() { @@ -390,7 +407,8 @@ int main() { ' "PASS" 1 } -test_printf_multi_args() { +test_printf_multi_args() +{ run_abi_test "printf with 5 arguments" "External Calls" ' #include int main() { @@ -400,7 +418,8 @@ int main() { ' "Values: 1 2 3 4" 1 } -test_strlen() { +test_strlen() +{ run_abi_test "strlen external call" "External Calls" ' #include #include @@ -416,7 +435,8 @@ int main() { ' "PASS" 1 } -test_strcpy() { +test_strcpy() +{ run_abi_test "strcpy external call" "External Calls" ' #include #include @@ -434,7 +454,8 @@ int main() { ' "PASS" 1 } -test_memcpy() { +test_memcpy() +{ run_abi_test "memcpy external call" "External Calls" ' #include #include @@ -454,7 +475,8 @@ int main() { # Register Preservation Tests -test_local_vars_preserved() { +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) { @@ -475,7 +497,8 @@ int main() { ' "PASS" } -test_recursive_preservation() { +test_recursive_preservation() +{ run_abi_test "Register preservation in recursion" "Register Preservation" ' #include int factorial(int n) { @@ -497,7 +520,8 @@ int main() { # Structure Passing Tests -test_small_struct() { +test_small_struct() +{ run_abi_test "Small struct passing (≤4 bytes)" "Structure Passing" ' #include typedef struct { char a; char b; short c; } SmallStruct; diff --git a/tests/strength-reduce.c b/tests/strength-reduce.c index e85bd858..31cc60ef 100644 --- a/tests/strength-reduce.c +++ b/tests/strength-reduce.c @@ -1,7 +1,8 @@ -/* The address of a[i * 4] changes by a fixed amount on each trip through - * these loops. Their explicit goto back edges give the strength-reduction - * pass a latch with an existing SSA jump: it must advance the address before - * that jump, not append the advance after it. +/* + * The address of a[i * 4] changes by a fixed amount on each trip through these + * loops. Their explicit goto back edges give the strength-reduction pass a + * latch with an existing SSA jump: it must advance the address before that + * jump, not append the advance after it. */ int main() { diff --git a/tests/x64-abi.sh b/tests/x64-abi.sh index 36c9f1c7..9bcbdbbd 100755 --- a/tests/x64-abi.sh +++ b/tests/x64-abi.sh @@ -57,16 +57,20 @@ fi case "$1" in "0") readonly SHECC="$PWD/out/shecc" - readonly STAGE="Stage 0 (Host Compiler)" ;; + readonly STAGE="Stage 0 (Host Compiler)" + ;; "1") readonly SHECC="${TARGET_EXEC:-} $PWD/out/shecc-stage1.elf" - readonly STAGE="Stage 1 (Cross-compiled)" ;; + readonly STAGE="Stage 1 (Cross-compiled)" + ;; "2") readonly SHECC="${TARGET_EXEC:-} $PWD/out/shecc-stage2.elf" - readonly STAGE="Stage 2 (Self-hosted)" ;; + readonly STAGE="Stage 2 (Self-hosted)" + ;; *) echo "Error: Invalid stage '$1'. Use 0, 1, or 2." - exit 1 ;; + exit 1 + ;; esac DYNLINK="${2:-0}" @@ -81,9 +85,10 @@ echo -e "Compiler: $SHECC" echo "" # Helper Functions -update_category_stats() { +update_category_stats() +{ local category="$1" - local result="$2" # "pass", "fail" or "skip" + local result="$2" # "pass", "fail" or "skip" if [[ -z "${CATEGORY_TESTS[$category]:-}" ]]; then CATEGORY_TESTS[$category]=0 @@ -103,7 +108,8 @@ update_category_stats() { fi } -show_progress() { +show_progress() +{ if [[ "$SHOW_PROGRESS" == "1" ]]; then echo -n "." PROGRESS_COUNT=$((PROGRESS_COUNT + 1)) @@ -114,7 +120,8 @@ show_progress() { } # Test execution function -run_abi_test() { +run_abi_test() +{ local test_name="$1" local category="$2" local source_code="$3" @@ -169,11 +176,10 @@ run_abi_test() { run_output=$(eval "$run_cmd" 2>&1) exit_code=$? - # Check result - # If the exit code is not zero or the output is not expected, - # set 'run_status' to "FAILED". + # If the exit code is not zero or the output is not expected, set + # 'run_status' to "FAILED". run_status="SUCCESS" - if [[ $exit_code -ne 0 || ( -n "$expected_output" && "$run_output" != *"$expected_output"* ) ]]; then + if [[ $exit_code -ne 0 || (-n "$expected_output" && "$run_output" != *"$expected_output"*) ]]; then run_status="FAILED" fi @@ -205,7 +211,8 @@ run_abi_test() { # Parameter Passing Tests -test_one_arg() { +test_one_arg() +{ run_abi_test "One argument (rdi)" "Parameter Passing" ' #include int add_42(int x) { return x + 42; } @@ -221,7 +228,8 @@ int main() { ' "PASS" } -test_two_args() { +test_two_args() +{ run_abi_test "Two arguments (rdi, rsi)" "Parameter Passing" ' #include int add(int a, int b) { return a + b; } @@ -237,7 +245,8 @@ int main() { ' "PASS" } -test_four_args() { +test_four_args() +{ run_abi_test "Four arguments (rdi-rcx)" "Parameter Passing" ' #include int sum4(int a, int b, int c, int d) { return a + b + c + d; } @@ -253,7 +262,8 @@ int main() { ' "PASS" } -test_five_args() { +test_five_args() +{ run_abi_test "Five arguments (rdi-r8)" "Parameter Passing" ' #include int sum5(int a, int b, int c, int d, int e) { return a + b + c + d + e; } @@ -269,7 +279,8 @@ int main() { ' "PASS" } -test_eight_args() { +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) { @@ -296,7 +307,8 @@ int main() { # "the frame layout moved", not as a conformance verdict: the ABI does not # require an int local to sit on a 16-byte boundary. -test_stack_alignment_basic() { +test_stack_alignment_basic() +{ run_abi_test "Frame slot alignment" "Stack Alignment" ' #include int is_aligned(void *ptr) { @@ -318,7 +330,8 @@ int main() { ' "PASS" } -test_stack_alignment_extended() { +test_stack_alignment_extended() +{ run_abi_test "Frame slot alignment with stack-passed args" "Stack Alignment" ' #include int is_aligned(void *ptr) { @@ -343,7 +356,8 @@ int main() { # Return Value Tests -test_return_char() { +test_return_char() +{ run_abi_test "Return char value" "Return Values" ' #include char get_char(void) { return '\''A'\''; } @@ -358,7 +372,8 @@ int main() { ' "PASS" } -test_return_int() { +test_return_int() +{ run_abi_test "Return int value" "Return Values" ' #include int get_value(void) { return 12345; } @@ -373,7 +388,8 @@ int main() { ' "PASS" } -test_return_pointer() { +test_return_pointer() +{ run_abi_test "Return pointer value" "Return Values" ' #include int *return_ptr(int *p) { return p; } @@ -392,7 +408,8 @@ int main() { # External Function Call Tests (Dynamic Linking Only) -test_printf_one_arg() { +test_printf_one_arg() +{ run_abi_test "printf with 1 argument" "External Calls" ' #include int main() { @@ -402,7 +419,8 @@ int main() { ' "PASS" 1 } -test_printf_multi_args() { +test_printf_multi_args() +{ run_abi_test "printf with 5 arguments" "External Calls" ' #include int main() { @@ -412,7 +430,8 @@ int main() { ' "Values: 1 2 3 4" 1 } -test_strlen() { +test_strlen() +{ run_abi_test "strlen external call" "External Calls" ' #include #include @@ -428,7 +447,8 @@ int main() { ' "PASS" 1 } -test_strcpy() { +test_strcpy() +{ run_abi_test "strcpy external call" "External Calls" ' #include #include @@ -446,7 +466,8 @@ int main() { ' "PASS" 1 } -test_memcpy() { +test_memcpy() +{ run_abi_test "memcpy external call" "External Calls" ' #include #include @@ -466,7 +487,8 @@ int main() { # Register Preservation Tests -test_local_vars_preserved() { +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) { @@ -487,7 +509,8 @@ int main() { ' "PASS" } -test_recursive_preservation() { +test_recursive_preservation() +{ run_abi_test "Register preservation in recursion" "Register Preservation" ' #include int factorial(int n) { @@ -509,7 +532,8 @@ int main() { # Structure Passing Tests -test_small_struct() { +test_small_struct() +{ run_abi_test "Small struct passing (≤4 bytes)" "Structure Passing" ' #include typedef struct { char a; char b; short c; } SmallStruct; diff --git a/tools/inliner.c b/tools/inliner.c index a3e38f6c..77a8b2bb 100644 --- a/tools/inliner.c +++ b/tools/inliner.c @@ -1,18 +1,18 @@ /* * shecc - Self-Hosting and Educational C Compiler. * - * shecc is freely redistributable under the BSD 2 clause license. See the - * file "LICENSE" for information on usage and redistribution of this file. + * shecc is freely redistributable under the BSD 2 clause license. See the file + * "LICENSE" for information on usage and redistribution of this file. */ /* inliner - inline libc source into C file. * - * The inliner is used at build-time, and developers can use the - * "inline C" feature to implement target-specific parts such as - * C runtime and essential libraries. + * The inliner is used at build-time, and developers can use the "inline C" + * feature to implement target-specific parts such as C runtime and essential + * libraries. * - * Note: Input files are preprocessed by norm-lf tool to ensure - * consistent LF (Unix) line endings before processing. + * Note: Input files are preprocessed by norm-lf tool to ensure consistent LF + * (Unix) line endings before processing. */ #include diff --git a/tools/norm-lf.c b/tools/norm-lf.c index 74589ced..2daa6b8a 100644 --- a/tools/norm-lf.c +++ b/tools/norm-lf.c @@ -1,8 +1,8 @@ /* * Convert all line endings to LF (Unix style) * - * This tool ensures consistent line endings before processing with inliner. - * It converts CR-only (old Mac) and CRLF (Windows) to LF (Unix). + * This tool ensures consistent line endings before processing with inliner. It + * converts CR-only (old Mac) and CRLF (Windows) to LF (Unix). */ #include From 32795b3d729c87bb269f619d497e6b6044a32ede Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Tue, 8 Sep 2026 02:06:02 +0800 Subject: [PATCH 2/6] Check style with clang-format 20 and commentflow The coding convention job installed clang-format-18 inline and ran two scripts that between them could not fail usefully: check-format.sh counted XML replacement tags to build an exit code, and check-newline.sh covered only src, tools and tests. Neither had a local equivalent, so the first time a contributor learned their patch was misformatted was after pushing it. Four checks now live behind "make check-style", each a script that CI and a developer run the same way: trailing newlines and LF endings, clang-format 20, commentflow comment reflow, and shfmt plus ShellCheck for the shell. They share one file-discovery helper, so the set "make indent" rewrites is exactly the set the checks cover, and a new untracked file is held to the same rule that formats it. Every check honors one exit-code contract: 0 clean, 1 violation, 2 could not run. That third value is what lets "make install-hooks" put a pre-commit hook in front of the same checks without making the style tools mandatory to commit. The hook judges a snapshot of the index rather than the working tree, so a partially staged file is checked as committed, and a checker whose tool is missing prints a note and stands aside. CI remains the authority. An existing local hook is never replaced, and a hook git would ignore, because its source is missing or not executable, is refused rather than installed. The contract only means something if nothing can report a clean tree without having looked at one, so the ways that could happen are closed here rather than discovered later. Enumeration failures exit 2 instead of yielding an empty list, and the listing is read in the shell that acts on it, because a failure inside a process substitution ends only the subshell. A formatting violation already found outranks a linter that could not run. Tool absence is probed rather than assumed, and only status 2 is advisory, so a checker killed by a signal blocks. File lists are passed after "--", cover regular files only, and skip index entries with no file behind them. The tools come from their own releases with the published sha256 verified, rather than from apt: a formatter the distribution moves out from under the tree turns "make check-style" into a disagreement between two machines. clang-format is the exception, pinned to major version 20 through the LLVM apt repository via a keyring holding only the fingerprint we name. --- .ci/check-commentflow.sh | 44 +++++ .ci/check-format.sh | 63 ++++++- .ci/check-newline.sh | 97 +++++++++-- .ci/check-shell.sh | 66 ++++++++ .ci/common.sh | 74 ++++++++ .editorconfig | 24 +++ .github/actions/setup-build-env/action.yml | 93 ++++++++++ .github/scripts/release-asset.sh | 15 +- .github/workflows/main.yml | 18 +- CONTRIBUTING.md | 20 ++- Makefile | 52 +++++- scripts/git-pre-commit.sh | 81 +++++++++ scripts/install-git-hooks.sh | 68 ++++++++ scripts/test-git-hooks.sh | 187 +++++++++++++++++++++ 14 files changed, 863 insertions(+), 39 deletions(-) create mode 100755 .ci/check-commentflow.sh create mode 100755 .ci/check-shell.sh create mode 100644 .ci/common.sh create mode 100644 .editorconfig create mode 100755 scripts/git-pre-commit.sh create mode 100755 scripts/install-git-hooks.sh create mode 100755 scripts/test-git-hooks.sh diff --git a/.ci/check-commentflow.sh b/.ci/check-commentflow.sh new file mode 100755 index 00000000..a2853e6f --- /dev/null +++ b/.ci/check-commentflow.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash + +# Verify, or with --write impose, commentflow reflow of the comment blocks. + +set -uo pipefail + +source "$(dirname "${BASH_SOURCE[0]}")/common.sh" || exit 2 + +write=0 +case "${1:-}" in + --check) shift ;; + --write) + write=1 + shift + ;; +esac + +COMMENTFLOW=${COMMENTFLOW:-commentflow} + +files=() +if [ "$#" -gt 0 ]; then + files=("$@") +else + require_repo + collect_files '*.c' '*.h' '*.sh' + files=(${FILES[@]+"${FILES[@]}"}) +fi + +[ "${#files[@]}" -gt 0 ] || exit 0 +if ! command -v "$COMMENTFLOW" > /dev/null 2>&1; then + echo "Error: $COMMENTFLOW not found" >&2 + echo "Install it from https://github.com/sysprog21/commentflow" >&2 + exit 2 +fi +if [ "$write" -eq 1 ]; then + exec "$COMMENTFLOW" -- "${files[@]}" +fi + +status=0 +"$COMMENTFLOW" --check -- "${files[@]}" || status=$? +if [ "$status" -eq 1 ]; then + echo "Run 'make indent' to reflow comments." >&2 +fi +exit "$status" diff --git a/.ci/check-format.sh b/.ci/check-format.sh index c6da0c97..b92a2df0 100755 --- a/.ci/check-format.sh +++ b/.ci/check-format.sh @@ -1,12 +1,61 @@ #!/usr/bin/env bash -SOURCES=$(find $(git rev-parse --show-toplevel) | egrep "\.(c|cxx|cpp|h|hpp)\$") +# Verify, or with --write impose, clang-format conformance for the C sources. -set -x +set -uo pipefail -for file in ${SOURCES}; -do - clang-format-18 ${file} > expected-format - diff -u -p --label="${file}" --label="expected coding style" ${file} expected-format +source "$(dirname "${BASH_SOURCE[0]}")/common.sh" || exit 2 + +write=0 +case "${1:-}" in + --check) shift ;; + --write) + write=1 + shift + ;; +esac + +CLANG_FORMAT=$(find_clang_format) || { + echo "Error: clang-format version 20 is required" >&2 + exit 2 +} + +files=() +if [ "$#" -gt 0 ]; then + files=("$@") +else + require_repo + collect_files '*.c' '*.h' + files=(${FILES[@]+"${FILES[@]}"}) +fi + +[ "${#files[@]}" -gt 0 ] || exit 0 +if [ "$write" -eq 1 ]; then + exec "$CLANG_FORMAT" -i "${files[@]}" +fi + +# One batched pass answers "is anything unformatted" in a third of the time the +# per-file loop below takes. The loop only has to run when the answer is yes, +# and then only to produce the diff that says what to change. +list=$(mktemp) || exit 2 +trap 'rm -f "$list"' EXIT +printf '%s\n' "${files[@]}" > "$list" +"$CLANG_FORMAT" --dry-run -Werror --files="$list" > /dev/null 2>&1 && exit 0 + +failed=0 +expected=$(mktemp) || exit 2 +trap 'rm -f "$list" "$expected"' EXIT +for file in "${files[@]}"; do + + # An index entry with no file behind it -- a sparse checkout, or a deletion + # staged but not yet committed -- is nothing to format. + [ -f "$file" ] || continue + if ! "$CLANG_FORMAT" "$file" > "$expected"; then + echo "Error: $CLANG_FORMAT failed on $file" >&2 + exit 1 + fi + diff -u -p --label="$file" --label="expected coding style" \ + "$file" "$expected" || failed=1 done -exit $(clang-format-18 --output-replacements-xml ${SOURCES} | egrep -c "") + +exit "$failed" diff --git a/.ci/check-newline.sh b/.ci/check-newline.sh index 1d7d5470..794e2679 100755 --- a/.ci/check-newline.sh +++ b/.ci/check-newline.sh @@ -1,17 +1,88 @@ #!/usr/bin/env bash -ret=0 -show=0 -# Reference: https://medium.com/@alexey.inkin/how-to-force-newline-at-end-of-files-and-why-you-should-do-it-fdf76d1d090e -while IFS= read -rd '' f; do - if file --mime-encoding "$f" | grep -qv binary; then - tail -c1 < "$f" | read -r _ || show=1 - if [ $show -eq 1 ]; then - echo "Warning: No newline at end of file $f" - ret=1 - show=0 - fi +# Ensure every text file uses LF line endings and ends with a newline, which is +# what the [*] section of .editorconfig asks editors to do. + +set -uo pipefail + +source "$(dirname "${BASH_SOURCE[0]}")/common.sh" || exit 2 + +write=0 +case "${1:-}" in + --check) shift ;; + --write) + write=1 + shift + ;; +esac + +files=() +if [ "$#" -gt 0 ]; then + files=("$@") +else + require_repo + collect_files + files=(${FILES[@]+"${FILES[@]}"}) +fi + +[ "${#files[@]}" -gt 0 ] || exit 0 +if ! command -v file > /dev/null 2>&1; then + echo "Error: file not found" >&2 + exit 2 +fi + +# One file(1) run for the whole set: it spends most of its time loading +# libmagic, so paying that once rather than per file is the difference between +# the newline check dominating "make check-style" and disappearing into it. +encodings=() +while IFS= read -r encoding; do + encodings+=("$encoding") +done < <(file -b --mime-encoding -- "${files[@]}") + +# Answers are matched to inputs by position, so a reply that ran short would +# quietly reclassify the rest of the tree as text and report nonsense about it. +if [ "${#encodings[@]}" -ne "${#files[@]}" ]; then + echo "Error: file described ${#encodings[@]} of ${#files[@]} files" >&2 + exit 2 +fi + +text=() +for i in "${!files[@]}"; do + [ "${encodings[i]}" = binary ] && continue + text+=("${files[i]}") +done +[ "${#text[@]}" -gt 0 ] || exit 0 + +failed=0 +for path in "${text[@]}"; do + last=$(tail -c1 < "$path") || exit 2 + [ -n "$last" ] || continue + if [ "$write" -eq 1 ]; then + printf '\n' >> "$path" + continue fi -done < <(git ls-files -z src tools tests) + echo "No newline at end of file: $path" >&2 + failed=1 +done + +# grep exits 0 having found a carriage return, 1 having found none, and 2 on a +# real error, which says nothing about the tree and must not read as clean. Its +# answer goes through a file because the names it prints are NUL separated and a +# command substitution would drop the separators along with them. +matches=$(mktemp) || exit 2 +trap 'rm -f "$matches"' EXIT +grep -lZ $'\r' -- "${text[@]}" > "$matches" +status=$? +if [ "$status" -gt 1 ]; then + echo "Error: grep failed to scan for carriage returns" >&2 + exit 2 +fi +while IFS= read -r -d '' path; do + + # Reported even under --write: a carriage return sits inside the text, and + # stripping one is an edit to content rather than to layout. + echo "CRLF line ending: $path" >&2 + failed=1 +done < "$matches" -exit $ret +exit "$failed" diff --git a/.ci/check-shell.sh b/.ci/check-shell.sh new file mode 100755 index 00000000..6a58017e --- /dev/null +++ b/.ci/check-shell.sh @@ -0,0 +1,66 @@ +#!/usr/bin/env bash + +# Verify, or with --write impose, shfmt formatting for the shell scripts, and +# lint them with ShellCheck. + +set -uo pipefail + +source "$(dirname "${BASH_SOURCE[0]}")/common.sh" || exit 2 + +write=0 +case "${1:-}" in + --check) shift ;; + --write) + write=1 + shift + ;; +esac + +SHFMT=${SHFMT:-shfmt} +SHELLCHECK=${SHELLCHECK:-shellcheck} + +files=() +if [ "$#" -gt 0 ]; then + files=("$@") +else + require_repo + collect_files '*.sh' + files=(${FILES[@]+"${FILES[@]}"}) +fi + +[ "${#files[@]}" -gt 0 ] || exit 0 +if ! command -v "$SHFMT" > /dev/null 2>&1; then + echo "Error: $SHFMT not found" >&2 + exit 2 +fi +if [ "$write" -eq 1 ]; then + exec "$SHFMT" -w -- "${files[@]}" +fi + +failed=0 +"$SHFMT" -d -- "${files[@]}" || failed=1 + +# The test scripts embed shecc expressions that read as shell and trip +# ShellCheck. Naming what is exempt rather than what is covered keeps a new +# directory linted by default instead of silently skipped. +lint_files=() +for file in "${files[@]}"; do + case "$file" in + tests/*) ;; + *) lint_files+=("$file") ;; + esac +done + +[ "${#lint_files[@]}" -gt 0 ] || exit "$failed" +if ! command -v "$SHELLCHECK" > /dev/null 2>&1; then + echo "Error: $SHELLCHECK not found" >&2 + + # A formatting violation shfmt already found is a verdict on the tree, and + # outranks the linter that could not run: reporting 2 here would file it + # under "unavailable" and let the pre-commit hook wave it through. + [ "$failed" -eq 0 ] || exit "$failed" + exit 2 +fi +"$SHELLCHECK" --severity=warning -- "${lint_files[@]}" || failed=1 + +exit "$failed" diff --git a/.ci/common.sh b/.ci/common.sh new file mode 100644 index 00000000..5d4e03a8 --- /dev/null +++ b/.ci/common.sh @@ -0,0 +1,74 @@ +#!/usr/bin/env bash + +# Shared helpers for the style checks. Sourced, not executed. +# +# Every .ci/check-*.sh honors the same exit-code contract, which +# scripts/git-pre-commit.sh depends on to stay advisory: 0 when clean, 1 when +# the tree violates the rule, and 2 when the check could not run at all, which +# in practice means a missing tool. Only 1 blocks a commit. + +# Abort unless the current directory sits inside a git repository. Without this +# the checks below find no files and report success, which reads exactly like a +# clean tree. +require_repo() +{ + if ! git rev-parse --git-dir > /dev/null 2>&1; then + echo "Error: not a git repository" >&2 + exit 2 + fi +} + +# Fill the FILES array with every file matching the given pathspecs that git +# would either track or add, so that a new file is checked by the same rule that +# formats it. +# +# The listing goes through a temporary file rather than a process substitution +# because git's exit status has to be read in this shell: inside "< <(...)" a +# failure exits only the subshell, the loop sees end of input, and an +# enumeration that never happened arrives as an empty list, which every checker +# reads as a clean tree. +# +# An index entry can also outlive its file, during a staged deletion or in a +# sparse checkout, and there is nothing for any checker to read at that path. +collect_files() +{ + local listing status file + listing=$(mktemp) || exit 2 + trap 'rm -f "$listing"' EXIT + git ls-files -z --cached --others --exclude-standard -- "$@" > "$listing" + status=$? + FILES=() + if [ "$status" -ne 0 ]; then + echo "Error: could not enumerate the worktree" >&2 + exit 2 + fi + while IFS= read -r -d '' file; do + + # -f rather than -e: a submodule gitlink and a symlink to a directory + # are both entries in the index that no file-oriented checker can read. + [ -f "$file" ] || continue + FILES+=("$file") + done < "$listing" + rm -f "$listing" + trap - EXIT +} + +# Print the name of a clang-format at the version the project pins, or nothing. +# CLANG_FORMAT names a specific binary to use instead of searching. +find_clang_format() +{ + local candidate candidates + if [ -n "${CLANG_FORMAT:-}" ]; then + candidates=("$CLANG_FORMAT") + else + candidates=(clang-format-20 clang-format) + fi + for candidate in "${candidates[@]}"; do + if command -v "$candidate" > /dev/null 2>&1 \ + && "$candidate" --version 2> /dev/null | grep -qE 'version 20\.'; then + echo "$candidate" + return 0 + fi + done + return 1 +} diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..d6f439d0 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,24 @@ +# Top-level EditorConfig file +root = true + +# Enforced repository-wide by .ci/check-newline.sh +[*] +end_of_line = lf +insert_final_newline = true + +# Matching .clang-format's "UseTab: Never" and "IndentWidth: 4" +[*.{c,h}] +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true + +# Shell adds the shfmt-specific keys, which no editor reads but which keep +# "shfmt -d" and "shfmt -w" agreeing without duplicating flags in the Makefile. +[*.sh] +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true +function_next_line = true +switch_case_indent = true +space_redirects = true +binary_next_line = true diff --git a/.github/actions/setup-build-env/action.yml b/.github/actions/setup-build-env/action.yml index aa4d6001..a52bcf3f 100644 --- a/.github/actions/setup-build-env/action.yml +++ b/.github/actions/setup-build-env/action.yml @@ -16,6 +16,14 @@ inputs: GitHub. Only the anonymous rate limit applies without one. required: false default: '' + style-tools: + description: > + Install the formatters and linters that "make check-style" runs: + clang-format at the major version the project requires, and shfmt, + ShellCheck and commentflow at their latest release, each checked against + the digest that release publishes. + required: false + default: 'false' runs: using: composite @@ -25,12 +33,16 @@ runs: env: ARCHITECTURE: ${{ inputs.architecture }} LINK_MODE: ${{ inputs.link-mode }} + STYLE_TOOLS: ${{ inputs.style-tools }} DEBIAN_FRONTEND: noninteractive run: | set -euo pipefail # jq is used by the release-asset helper below. Graphviz is not needed # by the build or test suite. packages=(build-essential jq) + if [ "$STYLE_TOOLS" = true ]; then + packages+=(ca-certificates curl file gnupg) + fi if [ "$ARCHITECTURE" = arm ] && [ "$(uname -m)" = aarch64 ]; then # An Arm64 host runs the Arm output natively, through the armhf # loader and libc rather than an emulator. @@ -50,6 +62,87 @@ runs: sudo apt-get update -q -y sudo apt-get install -q -y --no-install-recommends "${packages[@]}" + - name: Install clang-format 20 + if: inputs.style-tools == 'true' + shell: bash + run: | + set -euo pipefail + key="$RUNNER_TEMP/llvm-snapshot.gpg.key" + curl --fail --silent --show-error --location \ + -o "$key" https://apt.llvm.org/llvm-snapshot.gpg.key + expected=6084F3CF814B57C1CF12EFD515CF4D18AF4F7421 + # Import into a throwaway keyring and export only the pinned key. + # Checking the first fingerprint and then installing the whole bundle + # would let a second key riding along in the same file sign packages. + ring="$RUNNER_TEMP/llvm-keyring" + rm -rf "$ring" + mkdir -m 0700 "$ring" + gpg --homedir "$ring" --batch --import "$key" + sudo install -d -m 0755 /etc/apt/keyrings + gpg --homedir "$ring" --export "$expected" | + sudo tee /etc/apt/keyrings/llvm.gpg > /dev/null + test -s /etc/apt/keyrings/llvm.gpg + echo "deb [signed-by=/etc/apt/keyrings/llvm.gpg] https://apt.llvm.org/noble/ llvm-toolchain-noble-20 main" | + sudo tee /etc/apt/sources.list.d/llvm-20.list > /dev/null + sudo apt-get update -q -y + sudo apt-get install -q -y --no-install-recommends clang-format-20 + clang-format-20 --version + + # shfmt and ShellCheck come from their own releases rather than from apt: + # a formatter that the distribution moves out from under the tree rewraps + # scripts a contributor's shfmt just formatted, and turns "make check-style" + # into a disagreement between two machines rather than a statement about the + # code. + - name: Install shfmt + if: inputs.style-tools == 'true' + shell: bash + env: + GITHUB_TOKEN: ${{ inputs.github-token }} + run: | + set -euo pipefail + asset=$("$GITHUB_WORKSPACE"/.github/scripts/release-asset.sh mvdan/sh \ + 'shfmt_{tag}_linux_amd64') + read -r _ url sha256 <<< "$asset" + binary="$RUNNER_TEMP/shfmt" + curl --fail --silent --show-error --location -o "$binary" "$url" + echo "$sha256 $binary" | sha256sum -c - + sudo install -m 0755 "$binary" /usr/local/bin/shfmt + shfmt --version + + - name: Install ShellCheck + if: inputs.style-tools == 'true' + shell: bash + env: + GITHUB_TOKEN: ${{ inputs.github-token }} + run: | + set -euo pipefail + asset=$("$GITHUB_WORKSPACE"/.github/scripts/release-asset.sh \ + koalaman/shellcheck 'shellcheck-{tag}.linux.x86_64.tar.xz') + read -r _ url sha256 <<< "$asset" + archive="$RUNNER_TEMP/shellcheck.tar.xz" + curl --fail --silent --show-error --location -o "$archive" "$url" + echo "$sha256 $archive" | sha256sum -c - + # The archive nests the binary under a directory named for the release. + sudo tar -Jxf "$archive" -C /usr/local/bin --strip-components=1 \ + --wildcards '*/shellcheck' + shellcheck --version + + - name: Install commentflow + if: inputs.style-tools == 'true' + shell: bash + env: + GITHUB_TOKEN: ${{ inputs.github-token }} + run: | + set -euo pipefail + asset=$("$GITHUB_WORKSPACE"/.github/scripts/release-asset.sh \ + sysprog21/commentflow \ + commentflow-x86_64-unknown-linux-gnu.tar.gz) + read -r _ url sha256 <<< "$asset" + archive="$RUNNER_TEMP/commentflow.tar.gz" + curl --fail --silent --show-error --location -o "$archive" "$url" + echo "$sha256 $archive" | sha256sum -c - + sudo tar -xzf "$archive" -C /usr/local/bin commentflow + # mk/arm.mk asks fastfetch what machine this is, and skips the emulator # when the answer is one that runs the Arm output natively. - name: Install fastfetch diff --git a/.github/scripts/release-asset.sh b/.github/scripts/release-asset.sh index 80869bb7..4ae8ac01 100755 --- a/.github/scripts/release-asset.sh +++ b/.github/scripts/release-asset.sh @@ -1,14 +1,16 @@ #!/usr/bin/env bash # Print " " for one asset of the latest release of a -# GitHub repository. Both toolchain downloads in the workflows resolve what they -# fetch this way, so that neither pins a version that goes stale nor trusts an -# archive it has not checksummed. +# GitHub repository. Every download in the workflows resolves what it fetches +# this way, so that none pins a version that goes stale nor trusts an archive it +# has not checksummed. An asset name may carry "{tag}" where the release stamps +# its own tag into the file name, which is only knowable once the release is in +# hand. set -euo pipefail if [ "$#" -ne 2 ]; then - echo "Usage: $0 " >&2 + echo "Usage: $0 " >&2 exit 1 fi @@ -30,10 +32,11 @@ release=$(curl --fail --silent --show-error --location \ # printing nulls and leaving the caller to download from the string "null". jq -er --arg name "$ASSET" ' . as $release - | (.assets[] | select(.name == $name)) as $asset + | ($name | gsub("\\{tag\\}"; $release.tag_name)) as $wanted + | (.assets[] | select(.name == $wanted)) as $asset | ($asset.digest // "" | sub("^sha256:"; "")) as $sha256 | if $sha256 == "" then - error("\($name) has no sha256 digest in \($release.tag_name)") + error("\($wanted) has no sha256 digest in \($release.tag_name)") else "\($release.tag_name) \($asset.browser_download_url) \($sha256)" end' <<< "$release" diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 52e9ed4b..75a29cfb 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -200,12 +200,14 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v7 + - name: Set up style tools + uses: ./.github/actions/setup-build-env + with: + architecture: x64 + link-mode: static + github-token: ${{ github.token }} + style-tools: 'true' - name: Coding convention - shell: bash - env: - DEBIAN_FRONTEND: noninteractive - run: | - sudo apt-get update -q -y - sudo apt-get install -q -y --no-install-recommends clang-format-18 - .ci/check-newline.sh - .ci/check-format.sh + run: make check-style + - name: Git hooks + run: make check-hooks diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0ff73ac0..e88107b9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -51,10 +51,22 @@ However, participation requires adherence to fundamental ground rules: This variant should be considered the standard for all documentation efforts. For instance, opt for "initialize" over "initialise" and "color" rather than "colour". -Software requirement: [clang-format](https://clang.llvm.org/docs/ClangFormat.html) version 18 or later. - -This repository consistently contains an up-to-date `.clang-format` file with rules that match the explained ones. -For maintaining a uniform coding style, execute the command `clang-format -i *.{c,h}`. +Building and testing need none of the style tools. `make check-style` needs all +four, and `make indent` all but ShellCheck, which only ever reports: +[clang-format](https://clang.llvm.org/docs/ClangFormat.html) version 20, +[commentflow](https://github.com/sysprog21/commentflow), +[shfmt](https://github.com/mvdan/sh), and +[ShellCheck](https://www.shellcheck.net/). +The newline check also calls `file(1)` to tell text from binary, which is +already present on most systems and reports itself as missing when it is not. + +The rules live in `.clang-format` for C and in `.editorconfig` for shell, both +kept up to date with the conventions explained here. +Run `make indent` to apply them, and `make check-style` to verify them without +modifying the tree. Note that `indent` also rewraps the text of your comments +through commentflow, so review its output rather than committing it blind. +Run `make install-hooks` to apply the same checks to staged changes; an +existing local hook is preserved. ## Coding Style for Modern C diff --git a/Makefile b/Makefile index 55605256..46ebd04c 100644 --- a/Makefile +++ b/Makefile @@ -25,8 +25,13 @@ check_flag = $(shell $(CC) $(1) -S -o /dev/null -xc /dev/null 2>/dev/null; \ if test $$? -eq 0; then echo "$(1)"; fi) # Iterate through the list of all potential flags, effectively filtering out all -# unsupported flags. +# unsupported flags. Half a second of $(CC) probing that the style and hook +# targets have no use for, so skip it when nothing is being compiled. +STYLE_GOALS := check-style check-newline check-comments check-format check-shell \ + indent install-hooks uninstall-hooks check-hooks +ifneq ($(filter-out $(STYLE_GOALS),$(or $(MAKECMDGOALS),all)),) $(foreach flag, $(CFLAGS_TO_CHECK), $(eval CFLAGS += $(call check_flag, $(flag)))) +endif BUILD_SESSION := .session.mk @@ -53,6 +58,9 @@ BUILTIN_LIBC_HEADER := c.h STAGE0_FLAGS ?= --dump-ir STAGE1_FLAGS ?= DYNLINK ?= 0 + +COMMENTFLOW ?= commentflow +SHFMT ?= shfmt ifeq ($(DYNLINK),1) STAGE0_FLAGS += --dynlink STAGE1_FLAGS += --dynlink @@ -119,8 +127,50 @@ config: $(VECHO) "Target machine code switch to %s\n" $(ARCH) $(Q)$(CONFIG_CHECK_CMD) +.PHONY: $(STYLE_GOALS) + check: check-stage0 check-stage2 check-abi-stage0 check-abi-stage2 +# One checker per target: they share nothing, so "make -j check-style" runs them +# concurrently and finishes in the time the slowest one takes. +check-style: check-newline check-comments check-format check-shell + +check-newline: + $(Q).ci/check-newline.sh + +check-comments: + $(Q)COMMENTFLOW=$(COMMENTFLOW) .ci/check-commentflow.sh + +check-format: + $(Q).ci/check-format.sh + +check-shell: + $(Q)SHFMT=$(SHFMT) .ci/check-shell.sh + +check-hooks: + $(Q)scripts/test-git-hooks.sh + +# Naming both goals would otherwise run each --write pass beside the checker +# reading the same files, so the rewrite goes first and the checkers then report +# on a tree that has stopped moving. Neither goal alone is affected. +ifneq ($(filter indent,$(MAKECMDGOALS)),) +check-newline check-comments check-format check-shell: | indent +endif + +# The checkers own both halves: which files they cover and which tool rewrites +# them. Naming either one here again would only be a second place to update. +indent: + $(Q).ci/check-newline.sh --write + $(Q)SHFMT=$(SHFMT) .ci/check-shell.sh --write + $(Q)COMMENTFLOW=$(COMMENTFLOW) .ci/check-commentflow.sh --write + $(Q).ci/check-format.sh --write + +install-hooks: + $(Q)scripts/install-git-hooks.sh + +uninstall-hooks: + $(Q)scripts/install-git-hooks.sh --uninstall + check-stage0: $(OUT)/$(STAGE0) tests/driver.sh $(VECHO) " TEST STAGE 0\n" tests/driver.sh 0 $(DYNLINK) diff --git a/scripts/git-pre-commit.sh b/scripts/git-pre-commit.sh new file mode 100755 index 00000000..973567fa --- /dev/null +++ b/scripts/git-pre-commit.sh @@ -0,0 +1,81 @@ +#!/usr/bin/env bash + +# Reject a commit whose staged content violates the project's style rules. +# +# Checks run against a snapshot of the index rather than the working tree, so a +# partially staged file is judged on what is actually being committed. A checker +# that cannot run at all, exit 2 and in practice a missing tool, prints a note +# and lets the commit through: the hook is a convenience, CI is the authority. + +set -uo pipefail + +# git runs hooks from the top of the working tree. +ci_dir=$(git rev-parse --show-toplevel)/.ci || exit 1 + +staged=() +while IFS= read -r -d '' file; do + staged+=("$file") +done < <(git diff --cached --name-only -z --diff-filter=ACMRT) + +failed=0 +run_check() +{ # [file...] + local what=$1 status=0 + shift + (cd "$snapshot" && "$@") || status=$? + case "$status" in + 0) ;; + 1) failed=1 ;; + 2) echo "note: $what unavailable; CI will check it" >&2 ;; + + # Anything else is the checker dying rather than declining, and a signal + # is not a verdict on the tree: block instead of waving it on. + *) + echo "error: $what exited with status $status" >&2 + failed=1 + ;; + esac +} + +if [ "${#staged[@]}" -gt 0 ]; then + snapshot=$(mktemp -d) || exit 1 + trap 'rm -rf "$snapshot"' EXIT + + # Restyling rules apply to the whole tree, so a staged config change has to + # drag every file it governs into the snapshot with it. + style_files=("${staged[@]}") + if [ -n "$(git diff --cached --name-only -- .clang-format .editorconfig)" ]; then + style_files=() + while IFS= read -r -d '' file; do + style_files+=("$file") + done < <(git ls-files -z -- '*.c' '*.h' '*.sh') + fi + + # git ls-files already emits the NUL stream checkout-index wants, and asking + # git which configs exist beats naming one the index may not hold. + { + printf '%s\0' "${staged[@]}" "${style_files[@]}" + git ls-files -z -- .clang-format .editorconfig + } | git checkout-index --stdin -z -f --prefix="$snapshot/" || exit 1 + + c_files=() + sh_files=() + for file in "${style_files[@]}"; do + case "$file" in + *.c | *.h) c_files+=("$file") ;; + *.sh) sh_files+=("$file") ;; + esac + done + + run_check "newline check" "$ci_dir/check-newline.sh" "${staged[@]}" + [ "${#c_files[@]}" -eq 0 ] \ + || run_check "clang-format 20" "$ci_dir/check-format.sh" "${c_files[@]}" + [ $((${#c_files[@]} + ${#sh_files[@]})) -eq 0 ] \ + || run_check commentflow "$ci_dir/check-commentflow.sh" \ + ${c_files[@]+"${c_files[@]}"} ${sh_files[@]+"${sh_files[@]}"} + [ "${#sh_files[@]}" -eq 0 ] \ + || run_check "shell tools" "$ci_dir/check-shell.sh" "${sh_files[@]}" +fi + +git diff --cached --check || failed=1 +exit "$failed" diff --git a/scripts/install-git-hooks.sh b/scripts/install-git-hooks.sh new file mode 100755 index 00000000..de28dcd1 --- /dev/null +++ b/scripts/install-git-hooks.sh @@ -0,0 +1,68 @@ +#!/usr/bin/env bash + +# Link this repository's pre-commit hook without replacing a hook already there. + +set -uo pipefail + +uninstall=0 +if [ "${1:-}" = --uninstall ] && [ "$#" -eq 1 ]; then + uninstall=1 +elif [ "$#" -gt 0 ]; then + echo "usage: $0 [--uninstall]" >&2 + exit 2 +fi + +# .git/hooks is shared by every linked worktree, so the link has to name the +# main one; pointing it at a linked worktree leaves it dangling once that is +# removed. +root=$(git worktree list --porcelain | sed -n '1s/^worktree //p') +hooks=$(git rev-parse --path-format=absolute --git-path hooks) || exit 1 +mkdir -p "$hooks" || exit 1 + +source=$root/scripts/git-pre-commit.sh +target=$hooks/pre-commit + +# .git/hooks being shared has a second consequence: the main worktree may be on +# a branch that does not carry this script, and git runs a dangling hook by +# quietly running nothing. Refuse rather than install a link to a file that is +# not there, so that the hook is never silently absent. +if [ "$uninstall" -eq 0 ] && [ ! -x "$source" ]; then + if [ -e "$source" ]; then + echo "Error: $source is not executable" >&2 + echo "git ignores a hook it cannot run, and says nothing about it." >&2 + else + echo "Error: $source does not exist" >&2 + echo "The main worktree is on a branch without it; check that branch" \ + "out there, or commit this one into it, first." >&2 + fi + exit 2 +fi + +# Whether the link is one this script created. Matching the tail rather than a +# current worktree path keeps the answer right after the checkout has been moved +# or renamed, which is precisely when the link dangles and needs attention. +ours() +{ + local link + link=$(readlink "$target") || return 1 + [ "$link" != "${link%/scripts/git-pre-commit.sh}" ] +} + +if [ "$uninstall" -eq 1 ]; then + if [ ! -L "$target" ]; then + echo "No pre-commit symlink to remove" + elif ours; then + rm -f "$target" && echo "Removed pre-commit" + else + echo "Left $target (not ours)" + fi +elif [ -L "$target" ] && [ ! -e "$target" ] && ours; then + + # A moved or renamed checkout leaves our own link dangling. Keeping it would + # disable the hook for good, so repoint it. + ln -sfn "$source" "$target" && echo "Repointed pre-commit" +elif [ -e "$target" ] || [ -L "$target" ]; then + echo "Kept existing $target" +else + ln -s "$source" "$target" && echo "Installed pre-commit" +fi diff --git a/scripts/test-git-hooks.sh b/scripts/test-git-hooks.sh new file mode 100755 index 00000000..618586ee --- /dev/null +++ b/scripts/test-git-hooks.sh @@ -0,0 +1,187 @@ +#!/usr/bin/env bash + +# Smoke-test hook installation and staged-content checking. + +set -euo pipefail + +root=$(git rev-parse --show-toplevel) +source "$root/.ci/common.sh" + +# The hook downgrades a missing tool to a note and passes, so probe for all of +# them up front; otherwise an absent tool surfaces as a bogus assertion failure. +formatter=$(find_clang_format) || { + echo "clang-format version 20 not found" >&2 + exit 1 +} + +# The names the checkers resolve, not the defaults: an override pointing at a +# tool under another name is a valid configuration, and file(1) is a dependency +# of check-newline.sh that nothing else here would notice was missing. +for tool in "${COMMENTFLOW:-commentflow}" "${SHFMT:-shfmt}" \ + "${SHELLCHECK:-shellcheck}" file; do + command -v "$tool" > /dev/null 2>&1 || { + echo "$tool not found" >&2 + exit 1 + } +done + +# The repositories below must not inherit the developer's own configuration: a +# global core.hooksPath sends install-git-hooks.sh outside this sandbox, where +# the trap does not reach and a stray hook is left behind for every later +# commit. +export GIT_CONFIG_GLOBAL=/dev/null +export GIT_CONFIG_SYSTEM=/dev/null + +test_dir=$(mktemp -d) +linked_dir="$test_dir-linked" +trap 'rm -rf "$test_dir" "$linked_dir"' EXIT + +git -C "$test_dir" init -q +git -C "$test_dir" config user.email test@example.com +git -C "$test_dir" config user.name Test +cp -R "$root/.ci" "$root/scripts" "$test_dir/" +cp "$root/.clang-format" "$root/.editorconfig" "$test_dir/" +git -C "$test_dir" add . +(cd "$test_dir" && scripts/install-git-hooks.sh) > /dev/null +test -L "$test_dir/.git/hooks/pre-commit" + +echo 'int add(int first,int second,int third){return first+second+third;} int main(){return 0;}' > "$test_dir/bad.c" +git -C "$test_dir" add bad.c +if (cd "$test_dir" && .git/hooks/pre-commit) > /dev/null 2>&1; then + echo "pre-commit accepted unformatted C" >&2 + exit 1 +fi + +"$formatter" -i "$test_dir/bad.c" +git -C "$test_dir" add bad.c +(cd "$test_dir" && .git/hooks/pre-commit) +git -C "$test_dir" commit -q -m "Add baseline" + +printf 'no newline' > "$test_dir/bad.txt" +git -C "$test_dir" add bad.txt +if (cd "$test_dir" && .git/hooks/pre-commit) > /dev/null 2>&1; then + echo "pre-commit accepted a text file without a final newline" >&2 + exit 1 +fi +printf 'newline restored\n' > "$test_dir/bad.txt" +git -C "$test_dir" add bad.txt +(cd "$test_dir" && .git/hooks/pre-commit) +git -C "$test_dir" commit -q -m "Add text" + +mkdir -p "$test_dir/.ci" +printf '#!/usr/bin/env bash\nreadonly value="$(false)"\necho "$value"\n' \ + > "$test_dir/.ci/warning.sh" +git -C "$test_dir" add .ci/warning.sh +if (cd "$test_dir" && .git/hooks/pre-commit) > /dev/null 2>&1; then + echo "pre-commit accepted a ShellCheck warning" >&2 + exit 1 +fi +printf '#!/usr/bin/env bash\nreadonly value\nvalue="$(false)"\necho "$value"\n' \ + > "$test_dir/.ci/warning.sh" +git -C "$test_dir" add .ci/warning.sh +(cd "$test_dir" && .git/hooks/pre-commit) +git -C "$test_dir" commit -q -m "Add clean shell" + +sed -i.bak 's/indent_size = 4/indent_size = 2/' "$test_dir/.editorconfig" +rm "$test_dir/.editorconfig.bak" +git -C "$test_dir" add .editorconfig +if (cd "$test_dir" && .git/hooks/pre-commit) > /dev/null 2>&1; then + echo "pre-commit ignored a staged EditorConfig change" >&2 + exit 1 +fi + +git -C "$test_dir" checkout -q HEAD -- .editorconfig +printf '\nColumnLimit: 20\n' >> "$test_dir/.clang-format" +git -C "$test_dir" add .clang-format +if (cd "$test_dir" && .git/hooks/pre-commit) > /dev/null 2>&1; then + echo "pre-commit ignored a staged clang-format change" >&2 + exit 1 +fi +git -C "$test_dir" checkout -q HEAD -- .clang-format + +# A checkout without .editorconfig must still be committable: git checkout-index +# fails outright on a name the index does not hold, which used to block every +# commit on any branch predating the file. +git -C "$test_dir" rm -q .editorconfig +git -C "$test_dir" commit -q -m "Drop EditorConfig" +printf 'int answer(void)\n{\n return 42;\n}\n' > "$test_dir/answer.c" +git -C "$test_dir" add answer.c +(cd "$test_dir" && .git/hooks/pre-commit) +git -C "$test_dir" commit -q -m "Add answer" + +# What is staged is what is being committed, so the snapshot has to be judged +# rather than the working tree, which may hold anything at all. +printf 'int partial(void)\n{\n return 1;\n}\n' > "$test_dir/partial.c" +git -C "$test_dir" add partial.c +printf 'int partial (void){return 1;}\n' > "$test_dir/partial.c" +(cd "$test_dir" && .git/hooks/pre-commit) +git -C "$test_dir" commit -q -m "Add partial" +git -C "$test_dir" checkout -q -- partial.c + +# A checker that cannot run at all must read as a note rather than as a verdict +# on the tree, or a machine missing one tool can no longer commit anything. +printf 'int advisory(void)\n{\n return 0;\n}\n' > "$test_dir/advisory.c" +git -C "$test_dir" add advisory.c +note=$(cd "$test_dir" && COMMENTFLOW=$test_dir/absent .git/hooks/pre-commit 2>&1) || { + echo "pre-commit rejected a commit over an unavailable tool" >&2 + exit 1 +} +case "$note" in + *"commentflow unavailable"*) ;; + *) + echo "pre-commit did not report the unavailable tool" >&2 + exit 1 + ;; +esac +git -C "$test_dir" commit -q -m "Add advisory" + +# .git/hooks is shared, so the hook must run the checkers of the worktree being +# committed to, not those of whichever worktree installed it. +git -C "$test_dir" worktree add -q -b linked-test "$linked_dir" +printf '#!/usr/bin/env bash\nexit 1\n' > "$linked_dir/.ci/check-newline.sh" +git -C "$linked_dir" add .ci/check-newline.sh +hook=$(git -C "$linked_dir" rev-parse --path-format=absolute \ + --git-path hooks/pre-commit) +if (cd "$linked_dir" && "$hook") > /dev/null 2>&1; then + echo "pre-commit used another worktree's checks" >&2 + exit 1 +fi +git -C "$test_dir" worktree remove --force "$linked_dir" + +# A moved or renamed checkout leaves our own symlink dangling; installing again +# must repoint it rather than report it as someone else's hook. +ln -sfn /nonexistent/scripts/git-pre-commit.sh "$test_dir/.git/hooks/pre-commit" +(cd "$test_dir" && scripts/install-git-hooks.sh) > /dev/null +test -x "$test_dir/.git/hooks/pre-commit" + +(cd "$test_dir" && scripts/install-git-hooks.sh --uninstall) > /dev/null +test ! -e "$test_dir/.git/hooks/pre-commit" +printf '#!/bin/sh\nexit 0\n' > "$test_dir/.git/hooks/pre-commit" +(cd "$test_dir" && scripts/install-git-hooks.sh) > /dev/null +test ! -L "$test_dir/.git/hooks/pre-commit" + +# git ignores a hook it cannot run, and a hook whose source is missing or not +# executable is exactly that. Installing one has to refuse rather than report +# success, so neither refusal can go quiet again. +rm -f "$test_dir/.git/hooks/pre-commit" +chmod -x "$test_dir/scripts/git-pre-commit.sh" +if (cd "$test_dir" && scripts/install-git-hooks.sh) > /dev/null 2>&1; then + echo "install accepted a source that cannot be executed" >&2 + exit 1 +fi +test ! -e "$test_dir/.git/hooks/pre-commit" +chmod +x "$test_dir/scripts/git-pre-commit.sh" + +mv "$test_dir/scripts/git-pre-commit.sh" "$test_dir/git-pre-commit.sh" +if (cd "$test_dir" && scripts/install-git-hooks.sh) > /dev/null 2>&1; then + echo "install accepted a source that is not there" >&2 + exit 1 +fi +test ! -e "$test_dir/.git/hooks/pre-commit" +mv "$test_dir/git-pre-commit.sh" "$test_dir/scripts/git-pre-commit.sh" + +# An extra argument is a mistake, not a second opinion about uninstalling. +if (cd "$test_dir" && scripts/install-git-hooks.sh --uninstall extra) > /dev/null 2>&1; then + echo "install accepted --uninstall with a stray argument" >&2 + exit 1 +fi From ba650c6722c8082e9de7b45c8b0df1d5fde9fa0e Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Tue, 8 Sep 2026 02:06:17 +0800 Subject: [PATCH 3/6] Give the sanitizer build its own objects "make sanitizer" adds the instrumentation through a target-specific CFLAGS, but both it and the ordinary link depended on the same out/src/main.o, so whichever ran last decided what the other got. One direction is loud: "make" after "make sanitizer" fails at the link on undefined __asan_ and __ubsan_ symbols. The other is silent, and is the one that matters. "make sanitizer" after any ordinary build, which "make check" performs, finds the object newer than its source, relinks it against the sanitizer runtime without rebuilding it, and produces a binary that intercepts nothing. Counted in symbols: 32 __asan references after a clean sanitizer build, 1 after a plain build followed by "make sanitizer". So "make check-sanitizer" ran the whole suite and reported success having checked nothing. A target-specific variable also reaches only its own target and that target's prerequisites, so reaching the sanitizer binary through check-sanitizer built its objects with the ordinary flags: run on its own, that goal instrumented nothing at all, zero __asan references. The sanitizer objects now live under out/sanitize, and both goals carry the flags, so every entry point gives 32 and either build order works. --- Makefile | 35 ++++++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index 46ebd04c..c411a995 100644 --- a/Makefile +++ b/Makefile @@ -68,12 +68,27 @@ endif SRCS := $(wildcard $(patsubst %,%/main.c, $(SRCDIR))) OBJS := $(SRCS:%.c=$(OUT)/%.o) -deps := $(OBJS:%.o=%.o.d) + +# The sanitizer build keeps its objects apart from the normal one. Sharing them +# lets whichever ran last decide what the other links: a plain "make" after it +# fails outright on the missing runtime, and -- quietly, which is worse -- +# "make sanitizer" after a plain build relinks an uninstrumented object, so +# check-sanitizer then passes having checked nothing. +SAN_OUT := $(OUT)/sanitize +SAN_OBJS := $(SRCS:%.c=$(SAN_OUT)/%.o) +deps := $(OBJS:%.o=%.o.d) $(SAN_OBJS:%.o=%.o.d) all: config bootstrap -sanitizer: CFLAGS += -fsanitize=address -fsanitize=undefined -fno-omit-frame-pointer -O0 -sanitizer: LDFLAGS += -fsanitize=address -fsanitize=undefined +# Both goals carry the flags, because a target-specific variable reaches only +# that target and its prerequisites: "make check-sanitizer" on its own would +# otherwise build $(SAN_OBJS) with the ordinary CFLAGS and run the suite against +# a binary that instruments nothing. +SAN_CFLAGS := -fsanitize=address -fsanitize=undefined -fno-omit-frame-pointer -O0 +SAN_LDFLAGS := -fsanitize=address -fsanitize=undefined + +sanitizer check-sanitizer: CFLAGS += $(SAN_CFLAGS) +sanitizer check-sanitizer: LDFLAGS += $(SAN_LDFLAGS) sanitizer: config $(OUT)/$(STAGE0)-sanitizer $(VECHO) " Built stage 0 compiler with sanitizers\n" @@ -201,7 +216,8 @@ $(OUT)/%.o: %.c | config $(OUT)/libc.inc $(VECHO) " CC\t$@\n" $(Q)$(CC) -o $@ $(CFLAGS) -c -MMD -MF $@.d $< -SHELL_HACK := $(shell mkdir -p $(OUT) $(OUT)/$(SRCDIR) $(OUT)/tests) +SHELL_HACK := $(shell mkdir -p $(OUT) $(OUT)/$(SRCDIR) $(OUT)/tests \ + $(SAN_OUT)/$(SRCDIR)) $(OUT)/norm-lf: tools/norm-lf.c $(VECHO) " CC+LD\t$@\n" @@ -222,9 +238,13 @@ $(OUT)/$(STAGE0): $(OUT)/libc.inc $(OBJS) $(VECHO) " LD\t$@\n" $(Q)$(CC) $(OBJS) $(LDFLAGS) -o $@ -$(OUT)/$(STAGE0)-sanitizer: $(OUT)/libc.inc $(OBJS) +$(SAN_OUT)/%.o: %.c | config $(OUT)/libc.inc + $(VECHO) " CC\t$@\n" + $(Q)$(CC) -o $@ $(CFLAGS) -c -MMD -MF $@.d $< + +$(OUT)/$(STAGE0)-sanitizer: $(OUT)/libc.inc $(SAN_OBJS) $(VECHO) " LD\t$@ (with sanitizers)\n" - $(Q)$(CC) $(OBJS) $(LDFLAGS) -o $@ + $(Q)$(CC) $(SAN_OBJS) $(LDFLAGS) -o $@ $(OUT)/$(STAGE1): $(OUT)/$(STAGE0) $(Q)$(STAGE1_CHECK_CMD) @@ -251,7 +271,8 @@ bootstrap: $(OUT)/$(STAGE2) .PHONY: clean clean: -$(RM) $(OUT)/$(STAGE0) $(OUT)/$(STAGE1) $(OUT)/$(STAGE2) - -$(RM) $(OBJS) $(deps) + -$(RM) $(OUT)/$(STAGE0)-sanitizer + -$(RM) $(OBJS) $(SAN_OBJS) $(deps) -$(RM) $(TESTBINS) $(OUT)/tests/*.log $(OUT)/tests/*.lst -$(RM) $(OUT)/shecc*.log -$(RM) $(OUT)/libc.inc From 7ceb5938c6d04cd67405e9858373627a1965eebc Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Tue, 8 Sep 2026 02:06:18 +0800 Subject: [PATCH 4/6] Expand a macro whose replacement list is empty "#define EMPTY" is ordinary C, and shecc hangs or crashes on it. So does a function-like macro with an empty body. The tree has never contained one, which is why it went unnoticed until a header wanted an attribute macro that expands to nothing under __SHECC__. pp_preprocess_internal() assembles its result behind a local list head and hands the caller two things: the first token, and end_of_token for splicing the next one on. An expansion that produces no tokens gets both wrong. The head is never initialized, so the return value is whatever the stack happened to hold, and end_of_token is left pointing at that head, a local of a frame that is about to go away, which all six callers write through. What the caller then builds is a token list with a cycle in it. The crash is wherever the cycle is first walked, so it surfaced as a self-compile spinning forever in pp_strip_layout(), nowhere near the preprocessor. The head is initialized, end_of_token is NULL rather than a dead pointer when nothing was produced, and every caller skips the splice instead of performing it with a cursor that means nothing. Two tests cover both macro shapes; without the fix the first one aborts. --- src/preprocessor.c | 56 ++++++++++++++++++++++++++++++++++------------ tests/driver.sh | 23 +++++++++++++++++++ 2 files changed, 65 insertions(+), 14 deletions(-) diff --git a/src/preprocessor.c b/src/preprocessor.c index 53438ccc..b64cf59d 100644 --- a/src/preprocessor.c +++ b/src/preprocessor.c @@ -363,9 +363,11 @@ token_t *pp_read_constant_expr_operand(token_t *tk, int *val) ctx.macro_args = NULL; ctx.trim_eof = false; expanded_tk = pp_preprocess_internal(macro->replacement, &ctx); - tmp = tk->next; - tk->next = expanded_tk; - ctx.end_of_token->next = tmp; + if (expanded_tk) { + tmp = tk->next; + tk->next = expanded_tk; + ctx.end_of_token->next = tmp; + } return pp_read_constant_expr_operand(tk, val); } @@ -785,6 +787,14 @@ token_t *pp_preprocess_internal(token_t *tk, preprocess_ctx_t *ctx) token_t *cur = &head; cond_incl_t *ci = NULL; + /* A macro whose replacement list is empty -- "#define NDEBUG", or a + * function-like macro that expands to nothing -- produces no tokens at all, + * and both of the values returned below have to say so. Without the + * initializer the result is whatever the stack held, and end_of_token below + * would name this frame, which the caller splices onto after it has died. + */ + head.next = NULL; + while (tk) { macro_t *macro = NULL; @@ -834,8 +844,10 @@ token_t *pp_preprocess_internal(token_t *tk, preprocess_ctx_t *ctx) */ macro_arg_replcaement = pp_preprocess_internal( macro_arg_replcaement, &expansion_ctx); - cur->next = macro_arg_replcaement; - cur = expansion_ctx.end_of_token; + if (macro_arg_replcaement) { + cur->next = macro_arg_replcaement; + cur = expansion_ctx.end_of_token; + } } tk = pp_lex_next_token(tk, false); continue; @@ -914,8 +926,10 @@ token_t *pp_preprocess_internal(token_t *tk, preprocess_ctx_t *ctx) arg_tk = pp_preprocess_internal(arg_tk, &arg_expansion_ctx); tk = pp_lex_next_token(tk, false); - arg_cur->next = arg_tk; - arg_cur = arg_expansion_ctx.end_of_token; + if (arg_tk) { + arg_cur->next = arg_tk; + arg_cur = arg_expansion_ctx.end_of_token; + } continue; } } @@ -1005,10 +1019,13 @@ token_t *pp_preprocess_internal(token_t *tk, preprocess_ctx_t *ctx) /* Expand macro body with collected arguments Replace parameter * references with supplied argument tokens */ - cur->next = pp_preprocess_internal( + token_t *expanded = pp_preprocess_internal( pp_subst_hash(macro->replacement, expansion_ctx.macro_args), &expansion_ctx); - cur = expansion_ctx.end_of_token; + if (expanded) { + cur->next = expanded; + cur = expansion_ctx.end_of_token; + } hashmap_free(expansion_ctx.macro_args); } else { @@ -1018,9 +1035,12 @@ token_t *pp_preprocess_internal(token_t *tk, preprocess_ctx_t *ctx) */ expansion_ctx.hide_set = hide_set_union(ctx->hide_set, new_hide_set(tk->literal)); - cur->next = pp_preprocess_internal( + token_t *expanded = pp_preprocess_internal( pp_subst_hash(macro->replacement, NULL), &expansion_ctx); - cur = expansion_ctx.end_of_token; + if (expanded) { + cur->next = expanded; + cur = expansion_ctx.end_of_token; + } } tk = pp_lex_next_token(tk, false); @@ -1102,8 +1122,12 @@ token_t *pp_preprocess_internal(token_t *tk, preprocess_ctx_t *ctx) continue; file_tks = gen_file_token_stream(intern_string(inclusion_path)); - cur->next = pp_preprocess_internal(file_tks->head, &inclusion_ctx); - cur = inclusion_ctx.end_of_token; + token_t *included = + pp_preprocess_internal(file_tks->head, &inclusion_ctx); + if (included) { + cur->next = included; + cur = inclusion_ctx.end_of_token; + } continue; } case T_cppd_define: { @@ -1307,7 +1331,11 @@ token_t *pp_preprocess_internal(token_t *tk, preprocess_ctx_t *ctx) if (ci) error_at("Unterminated conditional directive", &ci->tk->location); - ctx->end_of_token = cur; + /* NULL rather than '&head' when nothing was produced: the caller must skip + * the splice entirely, and a stale read should fault rather than corrupt + * the token list it is building. + */ + ctx->end_of_token = cur == &head ? NULL : cur; return head.next; } diff --git a/tests/driver.sh b/tests/driver.sh index 1cdfb0a2..08a9dd45 100755 --- a/tests/driver.sh +++ b/tests/driver.sh @@ -2693,6 +2693,29 @@ int main() } EOF +# An empty replacement list expands to nothing, in both macro shapes. Producing +# no tokens used to hand the caller a pointer into the dead frame that expanded +# them, which spliced the token list into a cycle the parser never left. +try_output 42 "" << EOF +#define EMPTY +#define NOTHING(x) +EMPTY int main(void) +{ + NOTHING(1) + EMPTY return 42; +} +EOF + +try_output 0 "ab" << EOF +#define BLANK +#define JOIN(a, b) printf(a); BLANK printf(b); +int main(void) +{ + JOIN("a", "b") + return 0; +} +EOF + # format try_output 0 "2147483647" << EOF int main() { From 18cbcaec59edd7059c12ea9dd18dc79a7bee292c Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Tue, 8 Sep 2026 02:06:40 +0800 Subject: [PATCH 5/6] Stop suppressing warnings, and fix what they hid CFLAGS_TO_CHECK carried ten -Wno- flags. Measured one at a time, four of them hid nothing at all on either compiler, and the rest were hiding work. All ten are gone; gcc and clang are clean on arm, riscv and x64 with nothing switched off. -Wno-uninitialized was the expensive one. It was there because fatal(), usage_error() and error_at() never return but could not say so: shecc parses no attributes, so annotating them would not compile itself. A __noreturn macro that is empty under __SHECC__ solves that, and with it every remaining report in the class was real or explicable. The real one is build_r_idom(), whose pred is read without an initializer when no successor has a postdominator yet. Its forward twin build_idom() has carried the guard, and a comment explaining it, all along; the suppression is why nobody saw the reverse copy lacked it. Reintroducing the defect now fails the build. -Wno-overflow hid 134 x86-64 opcode bytes above 0x7F handed to a char parameter. The bytes were right, but the narrowing was implicit 134 times over, which would have hidden a real one. emit_byte() takes an int and truncates once, on purpose, and no call site changes. The rest were smaller: nine "%p" arguments that were typed pointers, four parameters kept for signature symmetry, and thirteen "()" declarations, which in C means unspecified arguments rather than none. Separately, a diagnostic that reached a pipe did not survive it. Twenty-four sites printed and then abort()ed, which discards the buffer, so the MAX_TYPES guard, reachable from ordinary source, wrote zero bytes into a build log. All of them flush now. The three exits are also split by cause rather than by habit. error_at() reports a mistake in the input and exits: a syntax error should not raise SIGABRT, wake the system crash handler or leave a core behind, which cost 1.1 s per diagnostic here and made 24 of the suite's tests pay it. Losing the text to quote changes what can be shown and not what happened, so both of its fallbacks exit too. fatal() keeps abort(), because a broken invariant is worth a core dump, and usage_error() already exited for the same reason. The rest is what the analysis turned up alongside: 89 const qualifiers on pointers that are only read, an unreachable statement after a now-noreturn call, an unreachable ternary branch, three dead stores, a dead counter, a redundant condition, a duplicated if, the bare 10 that bounded three operator stacks, and two spelling mistakes. --- Makefile | 21 +++----- src/arm-codegen.c | 3 ++ src/defs.h | 18 +++++++ src/elf.c | 12 ++--- src/globals.c | 84 ++++++++++++++++++++++---------- src/lexer.c | 20 ++++---- src/main.c | 8 +-- src/opt-sccp.c | 3 -- src/parser.c | 72 ++++++++++++++------------- src/peephole.c | 2 +- src/preprocessor.c | 35 ++++++------- src/reg-alloc.c | 31 +++++++----- src/riscv-codegen.c | 4 +- src/ssa.c | 116 +++++++++++++++++++++++--------------------- src/x64-codegen.c | 1 + src/x64.c | 11 +++-- tools/inliner.c | 4 +- 17 files changed, 254 insertions(+), 191 deletions(-) diff --git a/Makefile b/Makefile index c411a995..ed56d7bd 100644 --- a/Makefile +++ b/Makefile @@ -1,20 +1,15 @@ +# -fwrapv is required, not probed: hashmap_hash_index() carries the FNV-1a +# accumulator in a signed int, because shecc has no 'unsigned' to compile +# itself with, and the multiply there overflows by design. CFLAGS := -O -g \ - -std=c99 -pedantic + -std=c99 -pedantic -fwrapv +# Every -Wno- that used to sit here has been earned away rather than renewed: +# the tree is clean under gcc and clang with nothing switched off, so a warning +# that appears from now on is about the code and not about the flag list. CFLAGS_TO_CHECK := \ - -fwrapv \ -Wall -Wextra \ - -Wno-unused-but-set-variable \ - -Wno-unused-parameter \ - -Wno-unused-function \ - -Wshadow \ - -Wno-variadic-macros \ - -Wno-uninitialized \ - -Wno-strict-prototypes \ - -Wno-declaration-after-statement \ - -Wno-format \ - -Wno-format-pedantic \ - -Wno-overflow + -Wshadow SUPPORTED_CFLAGS := # Check if a specific compiler flag is supported, attempting a dummy compilation diff --git a/src/arm-codegen.c b/src/arm-codegen.c index e6b584e0..bf7615f0 100644 --- a/src/arm-codegen.c +++ b/src/arm-codegen.c @@ -101,6 +101,7 @@ void update_elf_offset(ph2_ir_t *ph2_ir) elf_offset += 16; } else { printf("The '%s' function is not implemented\n", ph2_ir->func_name); + fflush(stdout); /* see fatal() */ abort(); } return; @@ -389,6 +390,7 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) is_external_call = true; } else { printf("The '%s' function is not implemented\n", ph2_ir->func_name); + fflush(stdout); /* see fatal() */ abort(); } @@ -430,6 +432,7 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) ofs = dynamic_sections.elf_plt_start + func->plt_offset; else { printf("The '%s' function is not implemented\n", ph2_ir->func_name); + fflush(stdout); /* see fatal() */ abort(); } emit(__movw(__AL, __r8, ofs)); diff --git a/src/defs.h b/src/defs.h index 3f206584..c99fcc3e 100644 --- a/src/defs.h +++ b/src/defs.h @@ -91,6 +91,11 @@ #define MAX_EXPR_DEPTH 256 #define MAX_BLOCK_DEPTH 256 #define MAX_OPERAND_STACK_SIZE 32 + +/* Depth of the operator stack that read_expr() and the constant-expression + * evaluator shunt through, and of the value stack the latter keeps beside it. + */ +#define MAX_OPERATOR_STACK_SIZE 10 #define MAX_ANALYSIS_STACK_SIZE 1600 /* Default capacities for common data structures, with the arena sizes taken @@ -134,11 +139,24 @@ * allocations and memcpy sizes below would be half what they need. */ #define HOST_PTR_SIZE PTR_SIZE + +/* shecc parses no attributes, and does not need the hint: it reports what it + * cannot compile rather than warning about it. + */ +#define __noreturn #else /* suppress GCC/Clang warnings */ #define UNUSED(x) (void) (x) /* configure host data model when using 'memcpy'. */ #define HOST_PTR_SIZE __SIZEOF_POINTER__ + +/* Marks the diagnostic paths that never come back. Without it the host compiler + * cannot see that a variable set on every surviving path is initialized, and + * reports each of those as a maybe-uninitialized use -- which is why the + * warning used to be switched off for the whole tree, taking the real cases + * with it. + */ +#define __noreturn __attribute__((noreturn)) #endif #ifndef MIN_ALIGNMENT diff --git a/src/elf.c b/src/elf.c index 78274813..44043dc4 100644 --- a/src/elf.c +++ b/src/elf.c @@ -72,7 +72,7 @@ void elf_write_blk(strbuf_t *elf_array, void *blk, int sz) { if (!elf_array || !blk || sz <= 0) return; - char *ptr = blk; + const char *ptr = blk; for (int i = 0; i < sz; i++) strbuf_putc(elf_array, ptr[i]); } @@ -368,7 +368,7 @@ void elf_generate_header(void) void elf_generate_program_headers(void) { - strbuf_t *elf_relplt = elf_relplt_buf(); + const strbuf_t *elf_relplt = elf_relplt_buf(); if (!elf_program_header || !elf_code || !elf_data || !elf_rodata || (dynlink && (!dynamic_sections.elf_interp || !elf_relplt || @@ -574,7 +574,7 @@ void elf_generate_section_headers(void) * compiles this file. */ - strbuf_t *elf_relplt = elf_relplt_buf(); + const strbuf_t *elf_relplt = elf_relplt_buf(); /* Check for null pointers to prevent crashes */ if (!elf_section_header || !elf_code || !elf_data || !elf_rodata || !elf_symtab || !elf_strtab || !elf_shstrtab || @@ -1099,7 +1099,7 @@ void elf_reset_dynamic_sections(void) void elf_generate_sections(void) { - strbuf_t *elf_relplt = elf_relplt_buf(); + const strbuf_t *elf_relplt = elf_relplt_buf(); if (!elf_shstrtab || (dynlink && (!dynamic_sections.elf_interp || !elf_relplt || @@ -1271,13 +1271,13 @@ void elf_postprocess(void) * '__syscall' to call, since it is synthesized only for static linking. */ #ifdef HOST_BUFFERED_STDIO -void elf_write_all(FILE *fp, char *buf, int len) +void elf_write_all(FILE *fp, const char *buf, int len) { if (len > 0) fwrite(buf, 1, len, fp); } #else -void elf_write_all(FILE *fp, char *buf, int len) +void elf_write_all(FILE *fp, const char *buf, int len) { int off = 0; diff --git a/src/globals.c b/src/globals.c index 34cf5e28..c035b47c 100644 --- a/src/globals.c +++ b/src/globals.c @@ -117,6 +117,7 @@ arena_block_t *arena_block_create(int capacity) if (!block) { printf("Failed to allocate memory for arena block structure\n"); + fflush(stdout); /* see fatal() */ abort(); } @@ -125,6 +126,7 @@ arena_block_t *arena_block_create(int capacity) if (!block->memory) { printf("Failed to allocate memory for arena block buffer\n"); free(block); + fflush(stdout); /* see fatal() */ abort(); } @@ -153,6 +155,7 @@ arena_t *arena_init(int initial_capacity) arena_t *arena = malloc(sizeof(arena_t)); if (!arena) { printf("Failed to allocate memory for arena structure\n"); + fflush(stdout); /* see fatal() */ abort(); } arena->head = arena_block_create(initial_capacity); @@ -174,6 +177,7 @@ void *arena_alloc(arena_t *arena, int size) { if (size <= 0) { printf("arena_alloc: size must be positive\n"); + fflush(stdout); /* see fatal() */ abort(); } @@ -218,6 +222,7 @@ void *arena_calloc(arena_t *arena, int n, int size) */ if (n <= 0 || size <= 0 || n > 0x7fffffff / size) { printf("arena_calloc: invalid allocation size\n"); + fflush(stdout); /* see fatal() */ abort(); } @@ -252,12 +257,14 @@ void *arena_realloc(arena_t *arena, char *oldptr, int oldsz, int newsz) if (!oldptr) { if (oldsz != 0) { printf("arena_realloc: oldptr == NULL requires oldsz == 0\n"); + fflush(stdout); /* see fatal() */ abort(); } return arena_alloc(arena, newsz); } if (oldsz == 0) { printf("arena_realloc: oldptr != NULL requires oldsz > 0\n"); + fflush(stdout); /* see fatal() */ abort(); } @@ -269,7 +276,7 @@ void *arena_realloc(arena_t *arena, char *oldptr, int oldsz, int newsz) /* From here on, oldptr != NULL and newsz > oldsz and oldsz != 0 */ int delta = newsz - oldsz; arena_block_t *blk = arena->head; - char *block_end = blk->memory + blk->offset; + const char *block_end = blk->memory + blk->offset; /* grow in place if oldptr is the last allocation in the current block */ if (oldptr + oldsz == block_end && blk->offset + delta <= blk->capacity) { @@ -290,7 +297,7 @@ void *arena_realloc(arena_t *arena, char *oldptr, int oldsz, int newsz) * * Return: Pointer to the duplicated string stored in the arena. */ -char *arena_strdup(arena_t *arena, char *str) +char *arena_strdup(arena_t *arena, const char *str) { const int n = strlen(str); char *dup = arena_alloc(arena, n + 1); @@ -436,6 +443,7 @@ void hashmap_rehash(hashmap_t *map) index = (index + 1) & (map->cap - 1); if (index == start) { printf("Error: New table is full during rehash\n"); + fflush(stdout); /* see fatal() */ abort(); } } @@ -477,6 +485,7 @@ void hashmap_put(hashmap_t *map, char *key, void *val) index = (index + 1) & (map->cap - 1); if (index == start) { printf("Error: Hashmap is full\n"); + fflush(stdout); /* see fatal() */ abort(); } } @@ -560,7 +569,7 @@ void hashmap_free(hashmap_t *map) * * Return: The pointer to the type, or NULL if not found. */ -type_t *find_type(char *type_name, int flag) +type_t *find_type(const char *type_name, int flag) { char head = type_name[0]; @@ -593,6 +602,7 @@ ph2_ir_t *add_existed_ph2_ir(ph2_ir_t *ph2_ir) { if (ph2_ir_idx >= MAX_IR_INSTR) { printf("Error: too many phase-2 IR instructions\n"); + fflush(stdout); /* see fatal() */ abort(); } PH2_IR_FLATTEN[ph2_ir_idx++] = ph2_ir; @@ -815,7 +825,7 @@ int unescape_string(const char *input, char *output, int output_size) return j; } -int parse_numeric_constant(char *buffer) +int parse_numeric_constant(const char *buffer) { int i = 0; int value = 0; @@ -882,6 +892,7 @@ type_t *add_type(void) { if (types_idx >= MAX_TYPES) { printf("Error: Maximum number of types (%d) exceeded\n", MAX_TYPES); + fflush(stdout); /* see fatal() */ abort(); } type_t *t = &TYPES[types_idx++]; @@ -895,8 +906,8 @@ type_t *add_type(void) * so a longer tag would run past it into the fields that follow. Refuse it * rather than corrupting the type. */ -void fatal(char *msg); -void usage_error(char *msg); +__noreturn void fatal(const char *msg); +__noreturn void usage_error(const char *msg); void set_type_name(type_t *type, char *name) { @@ -932,7 +943,7 @@ constant_t *find_constant(char alias[]) return hashmap_get(CONSTANTS_MAP, alias); } -var_t *find_member(char token[], type_t *type) +var_t *find_member(const char token[], type_t *type) { /* If it is a forwardly declared alias of a structure, switch to the base * structure type. A scalar -- or "void", whose size is also 0 -- has no @@ -962,7 +973,7 @@ var_t *find_member(char token[], type_t *type) * -- before making the call. Names are never empty, so reading the first byte * of either side is always in bounds. */ -var_t *find_local_var(char *token, block_t *block) +var_t *find_local_var(const char *token, block_t *block) { func_t *func = block->func; char head = token[0]; @@ -990,7 +1001,7 @@ var_t *find_local_var(char *token, block_t *block) return NULL; } -var_t *find_global_var(char *token) +var_t *find_global_var(const char *token) { var_list_t *var_list = &GLOBAL_BLOCK->locals; char head = token[0]; @@ -1316,7 +1327,7 @@ void bb_disconnect(basic_block_t *pred, basic_block_t *succ) * prev[], so prev_idx is only a high-water mark and the entries must be counted * rather than trusted. */ -int bb_pred_count(basic_block_t *bb) +int bb_pred_count(const basic_block_t *bb) { int n = 0; @@ -1747,8 +1758,12 @@ void global_release(void) strbuf_free(dynamic_sections.elf_got); } -/* Reports an error without specifying a position */ -void fatal(char *msg) +/* Reports a broken invariant, which has no position in the source to point at + * because nothing in the source is necessarily wrong. This one abort()s: a core + * dump is what makes an internal failure debuggable. A mistake in the input + * belongs in error_at(), and a mistake on the command line in usage_error(). + */ +__noreturn void fatal(const char *msg) { printf("[Error]: %s\n", msg); @@ -1764,31 +1779,46 @@ void fatal(char *msg) * a broken invariant, so this exits rather than abort()ing: no core dump, and * no "Aborted" line, for an ordinary typo. */ -void usage_error(char *msg) +__noreturn void usage_error(const char *msg) { printf("[Error]: %s\n", msg); fflush(stdout); exit(1); } -/* Reports error and prints occurred position context, if the given location is - * NULL or source file is missing, then fallbacks to fatal(char *). +/* Reports a mistake in the input, quoting the line it sits on. A program the + * compiler refuses is not a broken invariant, so this exits the way + * usage_error() does rather than abort()ing: an ordinary syntax error should + * not raise SIGABRT, wake the system crash handler, or leave a core behind. + * + * Falls back to the same message without context when the location is NULL or + * the source file is no longer on hand. */ -void error_at(char *msg, source_location_t *loc) +__noreturn void error_at(char *msg, source_location_t *loc) { int offset, start_idx, i = 0, len, pos; char diagnostic[MAX_LINE_LEN]; - if (!loc) - fatal(msg); + if (!loc) { + printf("[Error]: %s\n", msg); + fflush(stdout); + exit(1); + } len = loc->len; pos = loc->pos; strbuf_t *src = hashmap_get(SRC_FILE_MAP, loc->filename); - if (!src) - fatal(msg); + /* The source text is no longer on hand, which changes what can be shown and + * not what went wrong: still a mistake in the input, so still an exit + * rather than the core dump fatal() would take. + */ + if (!src) { + printf("[Error]: %s\n", msg); + fflush(stdout); + exit(1); + } if (len < 1) len = 1; @@ -1819,7 +1849,7 @@ void error_at(char *msg, source_location_t *loc) printf("%6c | ", ' '); /* Keep room for the note appended after the underline. */ - char *note = " Error occurs here"; + const char *note = " Error occurs here"; int limit = MAX_LINE_LEN - strlen(note) - 1; i = 0; @@ -1832,8 +1862,8 @@ void error_at(char *msg, source_location_t *loc) strcpy(diagnostic + i, note); printf("%s\n", diagnostic); - fflush(stdout); /* see fatal(): abort() discards buffered output */ - abort(); + fflush(stdout); /* exit() flushes, but say so once rather than rely on it */ + exit(1); } void print_indent(int indent) @@ -1842,11 +1872,13 @@ void print_indent(int indent) printf("\t"); } -void dump_bb_insn(func_t *func, basic_block_t *bb, bool *at_func_start) +void dump_bb_insn(const func_t *func, + const basic_block_t *bb, + bool *at_func_start) { if (!bb) return; - var_t *rd, *rs1, *rs2; + const var_t *rd, *rs1, *rs2; if (bb != func->bbs && bb->insn_list.head) { if (!at_func_start[0]) @@ -2111,7 +2143,7 @@ void dump_insn(void) /* Handle implicit return */ for (int i = 0; func->exit && i < func->exit->prev_idx; i++) { - basic_block_t *bb = func->exit->prev[i].bb; + const basic_block_t *bb = func->exit->prev[i].bb; if (!bb) continue; diff --git a/src/lexer.c b/src/lexer.c index f2df170c..93ff2770 100644 --- a/src/lexer.c +++ b/src/lexer.c @@ -28,7 +28,7 @@ hashmap_t *KEYWORD_MAP = NULL; token_kind_t *directive_tokens_storage = NULL; token_kind_t *keyword_tokens_storage = NULL; -void lex_init_directives() +void lex_init_directives(void) { if (DIRECTIVE_MAP) return; @@ -57,7 +57,7 @@ void lex_init_directives() } } -void lex_init_keywords() +void lex_init_keywords(void) { if (KEYWORD_MAP) return; @@ -125,7 +125,7 @@ token_kind_t lookup_keyword(char *token) /* Cleanup function for lexer hashmaps */ -void lexer_cleanup() +void lexer_cleanup(void) { if (DIRECTIVE_MAP) { hashmap_free(DIRECTIVE_MAP); @@ -189,7 +189,7 @@ int file_read_all(FILE *f, char *dst, int len) } #endif -strbuf_t *read_file(char *filename) +strbuf_t *read_file(const char *filename) { FILE *f = fopen(filename, "rb"); strbuf_t *src; @@ -225,7 +225,7 @@ strbuf_t *get_file_buf(char *filename) return buf; } -token_t *new_token(token_kind_t kind, source_location_t *loc, int len) +token_t *new_token(token_kind_t kind, const source_location_t *loc, int len) { /* Every field is written here, so the allocation does not need zeroing * first -- and tokens are the single largest source of allocations in the @@ -1032,10 +1032,10 @@ token_stream_t *gen_file_token_stream(char *filename) return tks; } -token_stream_t *gen_libc_token_stream() +token_stream_t *gen_libc_token_stream(void) { token_t head; - token_t *cur = &head, *tk; + token_t *cur = &head, *tk = NULL; token_stream_t *tks; char *filename = dynlink ? "lib/c.h" : "lib/c.c"; strbuf_t *buf = LIBC_SRC; @@ -1078,7 +1078,7 @@ token_stream_t *gen_libc_token_stream() cur = cur->next; } - if (!head.next) + if (!tk || !head.next) fatal("Unable to include libc"); if (tk->kind != T_eof) @@ -1093,7 +1093,7 @@ token_stream_t *gen_libc_token_stream() } /* Fetches current token's location. */ -source_location_t *cur_token_loc() +source_location_t *cur_token_loc(void) { return &cur_token->location; } @@ -1101,7 +1101,7 @@ source_location_t *cur_token_loc() /* Finds next token's location; if the current token is eof, returns the eof * token's location instead. */ -source_location_t *next_token_loc() +source_location_t *next_token_loc(void) { if (cur_token->kind == T_eof) return &cur_token->location; diff --git a/src/main.c b/src/main.c index e29cc066..1ccd5134 100644 --- a/src/main.c +++ b/src/main.c @@ -64,9 +64,9 @@ char *last_char(char *text, char needle) /* Derive lacc-style DOT output when the caller did not specify -o. */ char *dot_output_name(char *input) { - char *suffix = last_char(input, '.'); + const char *suffix = last_char(input, '.'); char *slash = last_char(input, '/'); - char *base = input; + const char *base = input; if (slash) base = slash + 1; @@ -103,7 +103,7 @@ int main(int argc, char *argv[]) { char *out = NULL; char *in = NULL; - token_stream_t *libc_token_stream, *token_stream; + token_stream_t *libc_token_stream = NULL, *token_stream; token_t *tk; for (int i = 1; i < argc; i++) { @@ -169,7 +169,7 @@ int main(int argc, char *argv[]) token_stream = gen_file_token_stream(in); /* concat libc's and input file's token stream */ - if (libc) { + if (libc_token_stream) { libc_token_stream->tail->next = token_stream->head; token_stream = libc_token_stream; } diff --git a/src/opt-sccp.c b/src/opt-sccp.c index efb833bf..3421139b 100644 --- a/src/opt-sccp.c +++ b/src/opt-sccp.c @@ -41,9 +41,6 @@ bool optimize_constant_casts(func_t *func) /* Simple peephole optimization: const + trunc pattern */ for (basic_block_t *bb = func->bbs; bb; bb = bb->rpo_next) { - if (!bb) - continue; - for (insn_t *insn = bb->insn_list.head; insn && insn->next; insn = insn->next) { insn_t *next_insn = insn->next; diff --git a/src/parser.c b/src/parser.c index 24db42f7..899529f0 100644 --- a/src/parser.c +++ b/src/parser.c @@ -37,8 +37,8 @@ var_t *operand_stack[MAX_OPERAND_STACK_SIZE]; int operand_stack_idx = 0; /* Forward declarations */ -source_location_t *cur_token_loc(); -source_location_t *next_token_loc(); +source_location_t *cur_token_loc(void); +source_location_t *next_token_loc(void); basic_block_t *read_body_statement(block_t *parent, basic_block_t *bb); void perform_side_effect(block_t *parent, basic_block_t *bb); @@ -49,7 +49,7 @@ void parse_array_init(var_t *var, basic_block_t **bb, bool emit_code); -label_t *find_label(char *name) +label_t *find_label(const char *name) { for (int i = 0; i < label_idx; i++) { if (!strcmp(name, labels[i].label_name)) @@ -58,7 +58,7 @@ label_t *find_label(char *name) return NULL; } -void add_label(char *name, basic_block_t *bb) +void add_label(const char *name, basic_block_t *bb) { if (label_idx > MAX_LABELS - 1) error_at("Too many labels in function", cur_token_loc()); @@ -478,7 +478,7 @@ var_t *compute_element_address(block_t *parent, var_t *compute_field_address(block_t *parent, basic_block_t **bb, var_t *struct_addr, - var_t *field) + const var_t *field) { if (field->offset == 0) return struct_addr; @@ -1035,7 +1035,7 @@ void parse_array_compound_literal(var_t *var, * keep array metadata without pointer indirection and are marked via * is_compound_literal when synthesized. */ -bool is_array_literal_placeholder(var_t *var) +bool is_array_literal_placeholder(const var_t *var) { return var && var->array_size > 0 && !var->ptr_level && var->is_compound_literal; @@ -1322,6 +1322,7 @@ void read_full_var_decl(var_t *vd, bool anon, bool is_param) if (!type) { printf("Could not find type %s%s\n", find_type_flag == 2 ? "struct/union " : "", type_name); + fflush(stdout); /* see fatal() */ abort(); } @@ -1333,6 +1334,7 @@ void read_full_var_decl(var_t *vd, bool anon, bool is_param) /* starting next_token, need to check the type */ void read_partial_var_decl(var_t *vd, var_t *template) { + UNUSED(template); read_inner_var_decl(vd, false, false); } @@ -2062,7 +2064,7 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) if (!lex_peek(T_close_curly, NULL)) { read_expr(parent, bb); read_ternary_operation(parent, bb); - var_t *ptr_val = opstack_pop(); + const var_t *ptr_val = opstack_pop(); /* For pointer compound literals, store the address */ compound_var->init_val = ptr_val->init_val; @@ -2099,7 +2101,7 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) if (!lex_peek(T_close_curly, NULL)) { read_expr(parent, bb); read_ternary_operation(parent, bb); - var_t *first_field = opstack_pop(); + const var_t *first_field = opstack_pop(); compound_var->init_val = first_field->init_val; /* Consume additional fields if present */ @@ -2135,10 +2137,8 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) read_expr(parent, bb); read_ternary_operation(parent, bb); - /* Check if there are more elements (comma-separated) or if - * it's an explicit array - */ - if (lex_peek(T_comma, NULL) || is_array_literal) { + /* Check if there are more elements (comma-separated) */ + if (lex_peek(T_comma, NULL)) { /* Array compound literal: (int[]){1, 2, 3} */ var_t *first_element = opstack_pop(); @@ -2245,7 +2245,7 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) lex_peek(T_identifier, token); /* is a constant or variable? */ - constant_t *con = find_constant(token); + const constant_t *con = find_constant(token); var_t *var = find_var(token, parent); func_t *func = find_func(token); @@ -2574,7 +2574,7 @@ bool is_pointer_operation(opcode_t op, var_t *rs1, var_t *rs2) void read_expr_body(block_t *parent, basic_block_t **bb) { var_t *vd, *rs1, *rs2; - opcode_t oper_stack[10]; + opcode_t oper_stack[MAX_OPERATOR_STACK_SIZE]; int oper_stack_idx = 0; /* These variables used for parsing logical-and/or operation. @@ -2602,7 +2602,7 @@ void read_expr_body(block_t *parent, basic_block_t **bb) has_prev_log_op = true; prev_log_op = op; } else { - if (oper_stack_idx >= 10) + if (oper_stack_idx >= MAX_OPERATOR_STACK_SIZE) fatal("Expression too complex: operator stack exhausted"); oper_stack[oper_stack_idx++] = op; } @@ -2732,7 +2732,7 @@ void read_expr_body(block_t *parent, basic_block_t **bb) } read_expr_operand(parent, bb); if (!is_logical(op)) { - if (oper_stack_idx >= 10) + if (oper_stack_idx >= MAX_OPERATOR_STACK_SIZE) fatal("Expression too complex: operator stack exhausted"); oper_stack[oper_stack_idx++] = op; } @@ -3165,7 +3165,10 @@ void read_lvalue(lvalue_t *lvalue, add_insn(parent, *bb, OP_add, vd, rs1, rs2, 0, NULL); } } else { - var_t *t; + /* Set and read only under 'is_reference'; the initializer says so to a + * compiler that cannot correlate the two tests. + */ + var_t *t = NULL; /* If operand is a reference, read the value and push to stack for the * incoming addition/subtraction. Otherwise, use the top element of @@ -3910,9 +3913,9 @@ bool read_global_assignment(char *token) return true; } - opcode_t op_stack[10]; + opcode_t op_stack[MAX_OPERATOR_STACK_SIZE]; opcode_t op, next_op; - int val_stack[10]; + int val_stack[MAX_OPERATOR_STACK_SIZE]; int op_stack_index = 0, val_stack_index = 0; int operand1, operand2; operand1 = read_primary_constant(); @@ -3948,12 +3951,6 @@ bool read_global_assignment(char *token) add_insn(parent, bb, OP_assign, vd, rs1, NULL, 0, NULL); return true; } - if (op == OP_ternary) { - lex_expect(T_question); - int cond = eval_expression_imm(op, operand1, operand2); - eval_ternary_imm(cond, token); - return true; - } /* using stack if operands more than two */ op_stack[op_stack_index++] = op; @@ -3985,7 +3982,8 @@ bool read_global_assignment(char *token) } while (op_stack_index > 0 && same_op == 0); } /* push next operand on stack */ - if (val_stack_index >= 10 || op_stack_index >= 10) + if (val_stack_index >= MAX_OPERATOR_STACK_SIZE || + op_stack_index >= MAX_OPERATOR_STACK_SIZE) fatal("Constant expression too complex"); val_stack[val_stack_index++] = read_primary_constant(); /* push operator on stack */ @@ -4128,7 +4126,7 @@ basic_block_t *read_body_statement(block_t *parent, basic_block_t *bb) case_val = unescaped[0]; lex_expect(T_char); } else if (lex_peek(T_identifier, token)) { - constant_t *cd = find_constant(token); + const constant_t *cd = find_constant(token); if (!cd) error_at("Unknown constant in case label", cur_token_loc()); @@ -4942,7 +4940,7 @@ basic_block_t *read_body_statement(block_t *parent, basic_block_t *bb) lex_accept(T_identifier); token_t *id_tk = cur_token; if (lex_accept(T_colon)) { - label_t *l = find_label(token); + const label_t *l = find_label(token); if (l) error_at("label redefinition", &id_tk->location); @@ -5013,7 +5011,7 @@ void read_func_body(func_t *func) } for (int i = 0; i < label_idx; i++) { - label_t *label = &labels[i]; + const label_t *label = &labels[i]; if (label->used) continue; @@ -5045,7 +5043,7 @@ void print_func_decl(func_t *func, const char *prefix, bool newline) printf("%s(", func->return_def.var_name); for (int i = 0; i < func->num_params; i++) { - var_t *var = &func->param_defs[i]; + const var_t *var = &func->param_defs[i]; if (var->is_const_qualified) printf("const "); @@ -5134,30 +5132,33 @@ void read_global_decl(block_t *block, bool is_const) func->return_def.var_name); print_func_decl(&func_tmp, "before: ", true); print_func_decl(func, "after: ", true); + fflush(stdout); /* see fatal() */ abort(); } if (func->num_params != func_tmp.num_params) { printf( - "Error: confilcting number of arguments for the function " + "Error: conflicting number of arguments for the function " "%s.\n", func->return_def.var_name); print_func_decl(&func_tmp, "before: ", true); print_func_decl(func, "after: ", true); + fflush(stdout); /* see fatal() */ abort(); } for (int i = 0; i < func->num_params; i++) { - var_t *func_var = &func->param_defs[i]; - var_t *func_tmp_var = &func_tmp.param_defs[i]; + const var_t *func_var = &func->param_defs[i]; + const var_t *func_tmp_var = &func_tmp.param_defs[i]; if ((func_var->type != func_tmp_var->type) || (func_var->ptr_level != func_tmp_var->ptr_level) || (func_var->is_const_qualified != func_tmp_var->is_const_qualified)) { - printf("Error: confilcting types for the function %s.\n", + printf("Error: conflicting types for the function %s.\n", func->return_def.var_name); print_func_decl(&func_tmp, "before: ", true); print_func_decl(func, "after: ", true); + fflush(stdout); /* see fatal() */ abort(); } } @@ -5167,6 +5168,7 @@ void read_global_decl(block_t *block, bool is_const) func->return_def.var_name); print_func_decl(&func_tmp, "before: ", true); print_func_decl(func, "after: ", true); + fflush(stdout); /* see fatal() */ abort(); } } @@ -5537,7 +5539,7 @@ void read_global_statement(void) lex_expect(T_semicolon); } else { char base_type[MAX_ID_LEN]; - type_t *base; + const type_t *base; type_t *type = add_type(); lex_ident(T_identifier, base_type); base = find_type(base_type, true); diff --git a/src/peephole.c b/src/peephole.c index 8bd620b3..06fb273d 100644 --- a/src/peephole.c +++ b/src/peephole.c @@ -13,7 +13,7 @@ * instructions are those whose results can be directly written to the final * destination register, eliminating intermediate moves. */ -bool is_fusible_insn(ph2_ir_t *ph2_ir) +bool is_fusible_insn(const ph2_ir_t *ph2_ir) { switch (ph2_ir->op) { case OP_add: /* Arithmetic operations */ diff --git a/src/preprocessor.c b/src/preprocessor.c index b64cf59d..422a9c7e 100644 --- a/src/preprocessor.c +++ b/src/preprocessor.c @@ -21,7 +21,7 @@ token_t *pp_lex_skip_space(token_t *tk) } /* Whether @tk is whitespace, a tab or a newline. */ -bool pp_is_layout(token_t *tk) +bool pp_is_layout(const token_t *tk) { return tk->kind == T_whitespace || tk->kind == T_newline || tk->kind == T_tab; @@ -30,7 +30,7 @@ bool pp_is_layout(token_t *tk) /* The first token after @tk that is not layout, or NULL at the end. */ token_t *pp_next_significant(token_t *tk) { - token_t *before = pp_lex_skip_space(tk); + const token_t *before = pp_lex_skip_space(tk); return before->next; } @@ -65,7 +65,7 @@ token_t *pp_lex_expect_token(token_t *tk, token_kind_t kind, bool skip_space) } /* Copies and isolate the given copied token */ -token_t *copy_token(token_t *tk) +token_t *copy_token(const token_t *tk) { /* The copy overwrites every byte, so zeroing the allocation first would be * wasted work -- and this runs once per token of every macro expansion. @@ -90,7 +90,7 @@ typedef struct macro { bool is_macro_defined(char *name) { - macro_t *macro = hashmap_get(MACROS, name); + const macro_t *macro = hashmap_get(MACROS, name); return macro && !macro->is_disabled; } @@ -153,7 +153,7 @@ hide_set_t *hide_set_union(hide_set_t *hs1, hide_set_t *hs2) return head.next; } -bool hide_set_contains(hide_set_t *hs, char *name) +bool hide_set_contains(hide_set_t *hs, const char *name) { for (; hs; hs = hs->next) if (!strcmp(hs->name, name)) @@ -607,8 +607,8 @@ token_t *pp_stringify(token_t *arg, source_location_t *loc) token_t *pp_paste_tokens(token_t *lhs, token_t *rhs, source_location_t *loc) { char lbuf[MAX_TOKEN_LEN], rbuf[MAX_TOKEN_LEN], joined[MAX_TOKEN_LEN]; - char *l = token_to_string(lhs, lbuf); - char *r = token_to_string(rhs, rbuf); + const char *l = token_to_string(lhs, lbuf); + const char *r = token_to_string(rhs, rbuf); if (!l || !r) error_at("Operand of '##' cannot be pasted", loc); @@ -745,7 +745,7 @@ token_t *pp_subst_hash(token_t *rep, hashmap_t *args) /* A parameter the next '##' will join is substituted here, unexpanded * -- letting the expansion loop reach it would expand it first. */ - token_t *after = pp_next_significant(tk); + const token_t *after = pp_next_significant(tk); if (args && tk->kind == T_identifier && after && after->kind == T_hashhash && hashmap_contains(args, tk->literal)) { @@ -811,7 +811,7 @@ token_t *pp_preprocess_internal(token_t *tk, preprocess_ctx_t *ctx) expansion_ctx.macro_args = ctx->macro_args; expansion_ctx.trim_eof = true; - token_t *macro_arg_replcaement = NULL; + token_t *macro_arg_replacement = NULL; /* Check if this identifier is a macro parameter (argument) If we're * currently expanding a macro body, parameters should be replaced @@ -829,12 +829,10 @@ token_t *pp_preprocess_internal(token_t *tk, preprocess_ctx_t *ctx) ctx->macro_args && hashmap_contains(ctx->macro_args, tk->literal); - if (is_macro_param) - macro_arg_replcaement = - hashmap_get(ctx->macro_args, tk->literal); - if (is_macro_param) { - if (macro_arg_replcaement) { + macro_arg_replacement = + hashmap_get(ctx->macro_args, tk->literal); + if (macro_arg_replacement) { /* Recursively expand the argument to handle nested macros */ expansion_ctx.hide_set = ctx->hide_set; @@ -842,10 +840,10 @@ token_t *pp_preprocess_internal(token_t *tk, preprocess_ctx_t *ctx) NULL; /* Don't take account of macro arguments, this might run into infinite loop */ - macro_arg_replcaement = pp_preprocess_internal( - macro_arg_replcaement, &expansion_ctx); - if (macro_arg_replcaement) { - cur->next = macro_arg_replcaement; + macro_arg_replacement = pp_preprocess_internal( + macro_arg_replacement, &expansion_ctx); + if (macro_arg_replacement) { + cur->next = macro_arg_replacement; cur = expansion_ctx.end_of_token; } } @@ -1614,7 +1612,6 @@ char *token_to_string(token_t *tk, char *dest) break; default: error_at("Unknown token kind", &tk->location); - printf("UNKNOWN_TOKEN"); break; } diff --git a/src/reg-alloc.c b/src/reg-alloc.c index 0d889907..f2d0e8a4 100644 --- a/src/reg-alloc.c +++ b/src/reg-alloc.c @@ -149,7 +149,7 @@ void track_var_use(var_t *var, int insn_idx) var->last_use = insn_idx; } -void refresh(basic_block_t *bb, insn_t *insn) +void refresh(basic_block_t *bb, const insn_t *insn) { for (int i = 0; i < REG_CNT; i++) { if (!REGS[i].var) @@ -276,7 +276,7 @@ bool reg_is_locked(int reg) } /* The register @var's base is pinned to, or -1. */ -int pinned_reg_of(var_t *var) +int pinned_reg_of(const var_t *var) { if (!var || !var->base) return -1; @@ -359,7 +359,7 @@ void slot_var_track(var_t *var) * arithmetic. A store through a pointer is invisible to these scans, so * anything else in the frame is left alone. */ -bool slot_is_private(var_t *var) +bool slot_is_private(const var_t *var) { if (var->address_taken || var->array_size || var->has_backing_storage) return false; @@ -491,7 +491,7 @@ void ph2_list_remove(basic_block_t *bb, ph2_ir_t *prev, ph2_ir_t *ir) } /* Whether @ir leaves @reg holding something other than what it held before. */ -bool ph2_writes_reg(ph2_ir_t *ir, int reg) +bool ph2_writes_reg(const ph2_ir_t *ir, int reg) { switch (ir->op) { case OP_store: @@ -522,6 +522,7 @@ bool ph2_writes_reg(ph2_ir_t *ir, int reg) */ void collapse_slot_roundtrip(func_t *func) { + UNUSED(func); for (int i = 0; i < slot_var_count; i++) { if (!slot_private[i] || slot_stores[i] != 1 || slot_loads[i] != 1) continue; @@ -693,7 +694,7 @@ bool reg_is_free(int i) } /* Return the index of register for given variable. Otherwise, return -1. */ -int find_in_regs(var_t *var) +int find_in_regs(const var_t *var) { for (int i = 0; i < REG_CNT; i++) { if (REGS[i].var == var) @@ -1002,8 +1003,9 @@ bool is_pushing_args; * in its live-out set. Asking the block directly is what makes the two arms of * an if-else able to reuse the same register. */ -bool var_read_later_in_bb(basic_block_t *bb, insn_t *from, var_t *var) +bool var_read_later_in_bb(basic_block_t *bb, insn_t *from, const var_t *var) { + UNUSED(bb); for (insn_t *insn = from; insn; insn = insn->next) { if (insn->rs1 == var || insn->rs2 == var || insn->rs3 == var) return true; @@ -1168,7 +1170,7 @@ int prepare_dest(basic_block_t *bb, return spilled; } -void spill_alive(basic_block_t *bb, insn_t *insn) +void spill_alive(basic_block_t *bb, const insn_t *insn) { /* Spill all locals on pointer writes (conservative aliasing handling) */ if (insn && insn->opcode == OP_write) { @@ -1325,7 +1327,10 @@ void load_entry_regs(basic_block_t *bb) } /* The operand of 'OP_push' should not been killed until function called. */ -void extend_liveness(basic_block_t *bb, insn_t *insn, var_t *var, int offset) +void extend_liveness(basic_block_t *bb, + const insn_t *insn, + var_t *var, + int offset) { if (check_live_out(bb, var)) return; @@ -1439,7 +1444,7 @@ bool phi_live_ready; /* The candidate number of @var under the current function's stamp, or -1 when * @var is not one. */ -int phi_cand_index(var_t *var) +int phi_cand_index(const var_t *var) { if (!var) return -1; @@ -1526,7 +1531,7 @@ int phi_live_note(basic_block_t *bb, var_t *var, int flags) } /* Note that @var is live on exit from @bb, when @bb already records it. */ -void phi_live_mark_out(basic_block_t *bb, var_t *var) +void phi_live_mark_out(const basic_block_t *bb, var_t *var) { int idx = phi_cand_index(var); if (idx < 0) @@ -1709,7 +1714,7 @@ bool live_iter_next(live_iter_t *it) * outer walk run in the block order of func->bbs, which is increasing rpo, so a * cursor only ever moves forward. */ -bool live_rec_seek(int *cursor, basic_block_t *bb) +bool live_rec_seek(int *cursor, const basic_block_t *bb) { int rec = *cursor; @@ -2153,6 +2158,7 @@ void reg_alloc(void) break; default: printf("Unsupported global operation: %d\n", global_insn->opcode); + fflush(stdout); /* see fatal() */ abort(); } } @@ -2523,7 +2529,7 @@ void reg_alloc(void) * the saved input to reload its slot. */ int reuse = -1; - int sources[] = {taken, other, cond}; + const int sources[] = {taken, other, cond}; for (int i = 0; i < 3; i++) { int reg = sources[i]; @@ -2769,6 +2775,7 @@ void reg_alloc(void) break; default: printf("Unknown opcode\n"); + fflush(stdout); /* see fatal() */ abort(); } } diff --git a/src/riscv-codegen.c b/src/riscv-codegen.c index 4da05e22..d394d10e 100644 --- a/src/riscv-codegen.c +++ b/src/riscv-codegen.c @@ -396,6 +396,7 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) (elf_code_start + elf_code->size); } else { printf("The '%s' function is not implemented\n", ph2_ir->func_name); + fflush(stdout); /* see fatal() */ abort(); } emit(__jal(__ra, ofs)); @@ -416,6 +417,7 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) ofs = dynamic_sections.elf_plt_start + func->plt_offset; else { printf("The '%s' function is not implemented\n", ph2_ir->func_name); + fflush(stdout); /* see fatal() */ abort(); } emit(__lui(__t0, rv_hi(ofs))); @@ -756,7 +758,7 @@ void code_generate(void) } } -void plt_generate() +void plt_generate(void) { int addr_of_plt = dynamic_sections.elf_plt_start; int addr_of_got = dynamic_sections.elf_got_start; diff --git a/src/ssa.c b/src/ssa.c index a1028eee..eb6c9860 100644 --- a/src/ssa.c +++ b/src/ssa.c @@ -43,7 +43,7 @@ void var_list_ensure_capacity(var_list_t *list, int min_capacity) } /* Whether @var appears in @list. */ -bool var_list_holds(var_list_t *list, var_t *var) +bool var_list_holds(const var_list_t *list, var_t *var) { for (int i = 0; i < list->size; i++) { if (list->elements[i] == var) @@ -171,7 +171,6 @@ void bb_build_rpo(func_t *func, basic_block_t *bb) } bb->rpo_next = curr; prev->rpo_next = bb; - prev = curr; return; } @@ -447,7 +446,7 @@ void build_r_idom(void) for (basic_block_t *bb = func->exit->rpo_r_next; bb; bb = bb->rpo_r_next) { /* pick one predecessor */ - basic_block_t *pred; + basic_block_t *pred = NULL; if (bb->next && bb->next->r_idom) { pred = bb->next; } else if (bb->else_ && bb->else_->r_idom) { @@ -456,6 +455,16 @@ void build_r_idom(void) pred = bb->then_; } + /* The mirror of the rule in build_idom(): reverse postorder + * from the exit puts a successor of every block that reaches it + * ahead of that block, so one is normally settled by now. A + * block where none is cannot be given an immediate + * postdominator yet; leaving it for a later round is what keeps + * the walk off an uninitialised pointer. + */ + if (!pred) + continue; + if (bb->next && bb->next != pred && bb->next->r_idom) pred = reverse_intersect(bb->next, pred); if (bb->else_ && bb->else_ != pred && bb->else_->r_idom) @@ -597,7 +606,7 @@ void use_chain_build(void) } } -bool var_check_killed(var_t *var, basic_block_t *bb) +bool var_check_killed(const var_t *var, const basic_block_t *bb) { for (int i = 0; i < bb->live_kill.size; i++) { if (bb->live_kill.elements[i] == var) @@ -695,9 +704,9 @@ void solve_globals(void) } } -bool var_check_in_scope(var_t *var, block_t *block) +bool var_check_in_scope(const var_t *var, block_t *block) { - func_t *func = block->func; + const func_t *func = block->func; while (block) { /* Only the first 'size' entries hold a variable; the rest of the @@ -843,7 +852,7 @@ var_t *new_const_var(block_t *scope, int val) var->init_val = val; return var; } -bool is_dominate(basic_block_t *pred, basic_block_t *succ); +bool is_dominate(const basic_block_t *pred, basic_block_t *succ); /* The renaming state of @v, created on first use. */ rename_t *var_rename(var_t *v) @@ -1213,7 +1222,7 @@ insn_t *new_insn(opcode_t op, var_t *rd, var_t *rs1, var_t *rs2) /* Whether @insn computes a value with no side effect and no way to fault, so * running it on a path that would not have reached it changes nothing. */ -bool insn_is_speculatable(insn_t *insn) +bool insn_is_speculatable(const insn_t *insn) { switch (insn->opcode) { case OP_add: @@ -1411,7 +1420,7 @@ bool if_convert_bb(func_t *func, basic_block_t *bb) for (int step = 0; step < 2; step++) { bool take_t = e_needs_t ? step == 0 : step == 1; basic_block_t **chain = take_t ? t_chain : e_chain; - insn_t *skip = take_t ? t_phi : e_phi; + const insn_t *skip = take_t ? t_phi : e_phi; int len = take_t ? t_len : e_len; for (int i = 0; i < len; i++) { @@ -1483,7 +1492,7 @@ int sr_gen; * materialises one wherever the value is wanted. */ bool var_read_by(func_t *func, - var_t *var, + const var_t *var, insn_t **skip, int nskip, bool loop_only) @@ -1624,7 +1633,7 @@ void thread_const_branches(void) if (!func->bbs) continue; for (basic_block_t *bb = func->bbs; bb; bb = bb->rpo_next) { - insn_t *br = bb->insn_list.head; + const insn_t *br = bb->insn_list.head; if (!br || br->next || br->opcode != OP_branch || !br->rs1) continue; @@ -2018,7 +2027,7 @@ var_t *sr_base(var_t *var) */ bool sr_varies(var_t *var) { - var_t *base; + const var_t *base; if (!var || var->is_const) return false; @@ -2031,7 +2040,7 @@ bool sr_varies(var_t *var) */ bool sr_step_of(var_t *var, int *step) { - var_t *base; + const var_t *base; if (!var) return false; @@ -2069,7 +2078,7 @@ void sr_set_step(var_t *var, int step) * and a literal does not, because the allocator materialises one wherever it is * wanted and moving it would only lengthen a live range. */ -bool sr_movable(insn_t *insn) +bool sr_movable(const insn_t *insn) { switch (insn->opcode) { case OP_add: @@ -2092,7 +2101,7 @@ bool sr_movable(insn_t *insn) } /* The instruction inside the loop that defines @var, or NULL. */ -insn_t *sr_def_of(func_t *func, var_t *var) +insn_t *sr_def_of(func_t *func, const var_t *var) { for (basic_block_t *bb = func->bbs; bb; bb = bb->rpo_next) { if (bb->loop_mark != sr_gen) @@ -2134,7 +2143,7 @@ var_t *sr_basic_iv(func_t *func, basic_block_t *latch, int *step) if (!sum->rs1 || !sum->rs2) continue; - var_t *addend = NULL; + const var_t *addend = NULL; var_t *carried = NULL; int sign = 1; @@ -2241,7 +2250,7 @@ void sr_derive_steps(func_t *func) if (!insn->rd) continue; - var_t *base = sr_base(insn->rd); + const var_t *base = sr_base(insn->rd); if (!base || base->iv_gen == sr_gen) continue; @@ -2310,7 +2319,7 @@ bool sr_collect_chain(func_t *func, var_t *var, insn_t **chain, int *len) if (!sr_movable(def)) return false; - var_t *base = sr_base(def->rd); + const var_t *base = sr_base(def->rd); if (!base || base->def_cnt != 1) return false; @@ -2523,7 +2532,7 @@ int sr_latch_count(basic_block_t *header) int n = 0; for (int i = 0; i < header->prev_idx; i++) { - basic_block_t *p = header->prev[i].bb; + const basic_block_t *p = header->prev[i].bb; if (p && p->loop_mark == sr_gen) n++; @@ -2682,7 +2691,7 @@ void unwind_phi(void) * dom_prev is the inverse of the dom_next the search followed, so the two agree * on every pair. */ -bool is_dominate(basic_block_t *pred, basic_block_t *succ) +bool is_dominate(const basic_block_t *pred, basic_block_t *succ) { for (basic_block_t *bb = succ; bb; bb = bb->dom_prev) { if (bb->dom_prev == pred) @@ -2703,7 +2712,7 @@ void bb_check_var_cross_init(func_t *func, basic_block_t *bb) if (insn->opcode != OP_allocat) continue; - var_t *var = insn->rd; + const var_t *var = insn->rd; ref_block_t *ref; for (ref = var->ref_block_list.head; ref; ref = ref->next) { if (ref->bb == bb) @@ -2730,7 +2739,7 @@ void bb_check_var_cross_init(func_t *func, basic_block_t *bb) * x = 5; * } */ -void check_var_cross_init() +void check_var_cross_init(void) { bb_traversal_args_t *args = arena_alloc_traversal_args(); for (func_t *func = FUNC_LIST.head; func; func = func->next) { @@ -2752,7 +2761,7 @@ void bb_dump_connection(FILE *fd, basic_block_t *next, bb_connection_type_t type) { - char *str; + const char *str; switch (type) { case NEXT: @@ -2768,8 +2777,8 @@ void bb_dump_connection(FILE *fd, fatal("Unknown basic block connection type"); } - char *pred; - void *pred_id; + const char *pred; + const void *pred_id; if (curr->insn_list.tail) { pred = "insn"; pred_id = curr->insn_list.tail; @@ -2778,8 +2787,8 @@ void bb_dump_connection(FILE *fd, pred_id = curr; } - char *succ; - void *succ_id; + const char *succ; + const void *succ_id; if (next->insn_list.tail) { succ = "insn"; succ_id = next->insn_list.head; @@ -2792,7 +2801,7 @@ void bb_dump_connection(FILE *fd, } /* escape character for the tag in dot file */ -char *get_insn_op(insn_t *insn) +char *get_insn_op(const insn_t *insn) { switch (insn->opcode) { case OP_add: @@ -2855,18 +2864,19 @@ void bb_dump(FILE *fd, func_t *func, basic_block_t *bb) if (next_ && (then_ || else_)) printf("Warning: normal BB with condition\n"); - fprintf(fd, "subgraph cluster_%p {\n", bb); - fprintf(fd, "label=\"BasicBlock %p (%s)\"\n", bb, bb->bb_label_name); + fprintf(fd, "subgraph cluster_%p {\n", (void *) bb); + fprintf(fd, "label=\"BasicBlock %p (%s)\"\n", (void *) bb, + bb->bb_label_name); insn_t *insn = bb->insn_list.head; if (!insn) - fprintf(fd, "pseudo_%p [label=\"pseudo\"]\n", bb); + fprintf(fd, "pseudo_%p [label=\"pseudo\"]\n", (void *) bb); if (!insn && (then_ || else_)) printf("Warning: pseudo node should only have NEXT\n"); for (; insn; insn = insn->next) { if (insn->opcode == OP_phi) { - fprintf(fd, "insn_%p [label=", insn); + fprintf(fd, "insn_%p [label=", (void *) insn); fprintf(fd, "<%s%d := PHI(%s%d", insn->rd->var_name, insn->rd->subscript, insn->phi_ops->var->var_name, @@ -3018,11 +3028,12 @@ void bb_dump(FILE *fd, func_t *func, basic_block_t *bb) default: fatal("Unknown opcode in instruction dump"); } - fprintf(fd, "insn_%p [label=%s]\n", insn, str); + fprintf(fd, "insn_%p [label=%s]\n", (void *) insn, str); } if (insn->next) - fprintf(fd, "insn_%p->insn_%p [weight=100]\n", insn, insn->next); + fprintf(fd, "insn_%p->insn_%p [weight=100]\n", (void *) insn, + (void *) insn->next); } fprintf(fd, "}\n"); @@ -3044,7 +3055,7 @@ void bb_dump(FILE *fd, func_t *func, basic_block_t *bb) bb_dump_connection(fd, bb->prev[i].bb, bb, bb->prev[i].type); } -void dump_cfg(char name[]) +void dump_cfg(const char name[]) { FILE *fd = fopen(name, "w"); @@ -3059,8 +3070,9 @@ void dump_cfg(char name[]) continue; func->visited++; - fprintf(fd, "subgraph cluster_%p {\n", func); - fprintf(fd, "label=\"%p (%s)\"\n", func, func->return_def.var_name); + fprintf(fd, "subgraph cluster_%p {\n", (void *) func); + fprintf(fd, "label=\"%p (%s)\"\n", (void *) func, + func->return_def.var_name); bb_dump(fd, func, func->bbs); fprintf(fd, "}\n"); } @@ -3223,7 +3235,7 @@ void ssa_build(void) } /* Check if operation can be subject to CSE */ -bool is_cse_candidate(insn_t *insn) +bool is_cse_candidate(const insn_t *insn) { switch (insn->opcode) { case OP_add: @@ -3253,7 +3265,7 @@ bool is_cse_candidate(insn_t *insn) /* Common Subexpression Elimination (CSE) Enhanced to support general binary * operations */ -bool cse(insn_t *insn, basic_block_t *bb) +bool cse(insn_t *insn, const basic_block_t *bb) { /* Handle array access pattern: add + read */ if (insn->opcode == OP_read) { @@ -3533,7 +3545,7 @@ bool const_folding(insn_t *insn) } /* Check if a basic block is unreachable */ -bool is_block_unreachable(basic_block_t *bb) +bool is_block_unreachable(const basic_block_t *bb) { if (!bb) return true; @@ -3559,6 +3571,8 @@ bool is_block_unreachable(basic_block_t *bb) bool var_escapes(var_t *var) { + UNUSED(var); + /* Reports every variable as escaping, which makes dce_init_mark() treat * every OP_write as useful and so disables SSA-level dead-store * elimination. The is_global/is_func branches that used to precede this @@ -3649,7 +3663,7 @@ int dce_init_mark(insn_t *insn, insn_t *work_list[], int work_list_idx) } /* Dead Code Elimination (DCE) */ -void dce_insn(basic_block_t *bb) +void dce_insn(const basic_block_t *bb) { insn_t *work_list[DCE_WORKLIST_SIZE]; int work_list_idx = 0; @@ -3731,8 +3745,6 @@ void dce_insn(basic_block_t *bb) void dce_sweep(void) { - int total_eliminated = 0; /* Track effectiveness */ - for (func_t *func = FUNC_LIST.head; func; func = func->next) { /* Skip function declarations without bodies */ if (!func->bbs) @@ -3741,13 +3753,8 @@ void dce_sweep(void) for (basic_block_t *bb = func->bbs; bb; bb = bb->rpo_next) { /* Skip unreachable blocks entirely */ if (is_block_unreachable(bb)) { - /* Count instructions being eliminated */ - for (insn_t *insn = bb->insn_list.head; insn; - insn = insn->next) { - if (!insn->useful) - total_eliminated++; + for (insn_t *insn = bb->insn_list.head; insn; insn = insn->next) insn->useful = false; - } /* Mark entire block as dead */ bb->useful = false; continue; @@ -3757,8 +3764,6 @@ void dce_sweep(void) while (insn) { insn_t *next = insn->next; if (!insn->useful) { - total_eliminated++; - /* If a branch instruction is useless, redirect to the * reverse immediate dominator of this basic block and * remove the branch instruction. Later, register allocation @@ -3793,7 +3798,7 @@ void dce_sweep(void) } } -void build_reversed_rpo(); +void build_reversed_rpo(void); void optimize(void) { @@ -4201,7 +4206,6 @@ void bb_build_reversed_rpo(func_t *func, basic_block_t *bb) } bb->rpo_r_next = curr; prev->rpo_r_next = bb; - prev = curr; return; } @@ -4234,7 +4238,7 @@ void build_reversed_rpo(void) } } -void update_consumed(insn_t *insn, var_t *var); +void update_consumed(const insn_t *insn, var_t *var); /* Combined function to reset and solve locals in one pass */ void bb_reset_and_solve_locals(func_t *func, basic_block_t *bb) @@ -4289,7 +4293,7 @@ void bb_reset_and_solve_locals(func_t *func, basic_block_t *bb) } } -void update_consumed(insn_t *insn, var_t *var) +void update_consumed(const insn_t *insn, var_t *var) { if (insn->idx > var->consumed) var->consumed = insn->idx; @@ -4432,7 +4436,7 @@ void liveness_analysis(void) if (!func->bbs) continue; - basic_block_t *bb = func->exit; + basic_block_t *bb; bool changed; do { changed = false; diff --git a/src/x64-codegen.c b/src/x64-codegen.c index d0ca0113..5a619c2f 100644 --- a/src/x64-codegen.c +++ b/src/x64-codegen.c @@ -2371,6 +2371,7 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) */ printf("Error: Undefined function called: %s\n", ph2_ir->func_name); + fflush(stdout); /* see fatal() */ abort(); } else { emit_byte(0xE8); /* CALL rel32 */ diff --git a/src/x64.c b/src/x64.c index f5dce37c..e5b5c77a 100644 --- a/src/x64.c +++ b/src/x64.c @@ -61,10 +61,15 @@ int reg_low3(int reg) return reg & 0x07; } -/* Helper to emit bytes to the code buffer */ -void emit_byte(char byte) +/* Helper to emit bytes to the code buffer Takes an int because shecc has no + * 'unsigned': every opcode above 0x7F would otherwise be a value the call site + * cannot write and the conversion silently changes. The narrowing to the byte + * actually emitted happens here, once and on purpose, rather than 134 times + * implicitly. + */ +void emit_byte(int byte) { - strbuf_putc(elf_code, byte); + strbuf_putc(elf_code, (char) (byte & 0xff)); } /* Emit a REX prefix. 'w' selects a 64-bit operand size; the two register diff --git a/tools/inliner.c b/tools/inliner.c index 77a8b2bb..052160e5 100644 --- a/tools/inliner.c +++ b/tools/inliner.c @@ -182,11 +182,11 @@ int main(int argc, char *argv[]) write_str(" strbuf_puts(LIBC_SRC, src);\n"); write_str("}\n"); - write_str("void libc_impl() {\n"); + write_str("void libc_impl(void) {\n"); load_from(argv[1]); write_str("}\n"); - write_str("void libc_decl() {\n"); + write_str("void libc_decl(void) {\n"); load_from(argv[2]); write_str("}\n"); From 86728664f440562bc86066ca9267cb3f9cf8441c Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Tue, 8 Sep 2026 02:06:52 +0800 Subject: [PATCH 6/6] Split the four functions nobody can read at once emit_ph2_ir() was 1543 lines, read_body_statement() 896, reg_alloc() 830, lex_token() 737. Each is a dispatcher with its work inlined, so each splits along a seam that is already there rather than one invented for the occasion. lex_token() dispatched on the first character of a token through fifty if-blocks. It now offers the character to six readers, layout, numbers, literals, punctuation, operators and words, each of which claims what it knows and returns NULL for the rest, and is 28 lines. emit_ph2_ir() is a 218-line prologue of fusion attempts and a switch over 43 opcode groups. The groups become eight emitters named for the instruction family they cover, taking only the prologue values they use, and the function is 398 lines. read_body_statement() already delegated return, if and while to handle_*_statement() helpers; switch, for, do, record declarations and the declaration tail now go the same way, leaving a 63-line dispatcher that reads as the grammar it implements. reg_alloc() is two loops. Their bodies become reg_alloc_global() and reg_alloc_bb(), and the function is 155 lines. Three continues that ended an iteration of the block loop end the call instead. This is code motion and nothing else, checked as such rather than asserted: a frozen copy of the compiler's own source, compiled by the compiler built before and after, is byte-identical on all three targets. That is a stronger statement than the suite makes, since stage1 equal to stage2 only proves the compiler is deterministic. That every extracted handler returns on all paths is part of the same proof: gcc reports no control reaching the end of any of them, so no caller ever fell out of the original if-block into the statement kinds below it. --- src/lexer.c | 285 ++++++---- src/parser.c | 871 +++++++++++++++--------------- src/reg-alloc.c | 1280 ++++++++++++++++++++++----------------------- src/x64-codegen.c | 644 ++++++++++++++--------- 4 files changed, 1682 insertions(+), 1398 deletions(-) diff --git a/src/lexer.c b/src/lexer.c index 93ff2770..562c0a48 100644 --- a/src/lexer.c +++ b/src/lexer.c @@ -240,12 +240,20 @@ token_t *new_token(token_kind_t kind, const source_location_t *loc, int len) return token; } -token_t *lex_token(strbuf_t *buf, source_location_t *loc) +/* Skipping a comment or a run of whitespace resumes the scan, and lex_layout() + * below does that by starting a fresh token here. + */ +token_t *lex_token(strbuf_t *buf, source_location_t *loc); + +/* Preprocessor directives, comments, and the whitespace between tokens. + * + * Returns NULL when 'ch' is none of its business, so that lex_token() can offer + * the character to the next reader in line. + */ +token_t *lex_layout(strbuf_t *buf, source_location_t *loc, char ch) { token_t *token; - char token_buffer[MAX_TOKEN_LEN], ch = peek_char(buf, 0); - - loc->pos = buf->size; + char token_buffer[MAX_TOKEN_LEN]; if (ch == '#') { /* Inside a macro replacement list '#' stringifies the parameter that @@ -392,6 +400,19 @@ token_t *lex_token(strbuf_t *buf, source_location_t *loc) return token; } + return NULL; +} + +/* Integer literals, in every base the language accepts. + * + * Returns NULL when 'ch' is none of its business, so that lex_token() can offer + * the character to the next reader in line. + */ +token_t *lex_number(strbuf_t *buf, source_location_t *loc, char ch) +{ + token_t *token; + char token_buffer[MAX_TOKEN_LEN]; + if (isdigit(ch)) { int sz = 0; token_buffer[sz++] = ch; @@ -479,6 +500,97 @@ token_t *lex_token(strbuf_t *buf, source_location_t *loc) return token; } + return NULL; +} + +/* String and character literals. + * + * Returns NULL when 'ch' is none of its business, so that lex_token() can offer + * the character to the next reader in line. + */ +token_t *lex_literal(strbuf_t *buf, source_location_t *loc, char ch) +{ + token_t *token; + char token_buffer[MAX_TOKEN_LEN]; + + if (ch == '"') { + int sz = 0; + bool special = false; + + ch = read_char(buf); + while (ch != '"' || special) { + if (sz >= MAX_TOKEN_LEN - 1) { + loc->len = sz + 1; + error_at("String literal too long", loc); + } + token_buffer[sz++] = ch; + + if (ch == '\\') + special = true; + else + special = false; + + ch = read_char(buf); + } + token_buffer[sz] = '\0'; + + read_char(buf); + token = new_token(T_string, loc, sz + 2); + token->literal = intern_string(token_buffer); + loc->column += sz + 2; + return token; + } + + if (ch == '\'') { + int sz = 0; + bool escaped = false; + + ch = read_char(buf); + if (ch == '\\') { + token_buffer[sz++] = ch; + ch = read_char(buf); + + do { + if (sz >= MAX_TOKEN_LEN - 1) { + loc->len = sz + 1; + error_at("Character literal too long", loc); + } + token_buffer[sz++] = ch; + ch = read_char(buf); + escaped = true; + } while (ch && ch != '\''); + } else { + token_buffer[sz++] = ch; + } + token_buffer[sz] = '\0'; + + if (!escaped) + ch = read_char(buf); + + if (ch != '\'') { + loc->len = 2; + error_at("Unenclosed character literal", loc); + } + + read_char(buf); + token = new_token(T_char, loc, sz + 2); + token->literal = intern_string(token_buffer); + loc->column += sz + 2; + return token; + } + + return NULL; +} + +/* Punctuation that is never the start of a longer token. + * + * Returns NULL when 'ch' is none of its business, so that lex_token() can offer + * the character to the next reader in line. + */ +token_t *lex_punct(strbuf_t *buf, source_location_t *loc, char ch) +{ + token_t *token; + if (ch == '(') { ch = read_char(buf); token = new_token(T_open_bracket, loc, 1); @@ -528,91 +640,58 @@ token_t *lex_token(strbuf_t *buf, source_location_t *loc) return token; } - if (ch == '^') { + if (ch == '~') { ch = read_char(buf); - - if (ch == '=') { - ch = read_char(buf); - token = new_token(T_xoreq, loc, 2); - loc->column += 2; - return token; - } - - token = new_token(T_bit_xor, loc, 1); + token = new_token(T_bit_not, loc, 1); loc->column++; return token; } - if (ch == '~') { - ch = read_char(buf); - token = new_token(T_bit_not, loc, 1); + if (ch == ';') { + read_char(buf); + token = new_token(T_semicolon, loc, 1); loc->column++; return token; } - if (ch == '"') { - int sz = 0; - bool special = false; - - ch = read_char(buf); - while (ch != '"' || special) { - if (sz >= MAX_TOKEN_LEN - 1) { - loc->len = sz + 1; - error_at("String literal too long", loc); - } - token_buffer[sz++] = ch; - - if (ch == '\\') - special = true; - else - special = false; - - ch = read_char(buf); - } - token_buffer[sz] = '\0'; + if (ch == '?') { + read_char(buf); + token = new_token(T_question, loc, 1); + loc->column++; + return token; + } + if (ch == ':') { read_char(buf); - token = new_token(T_string, loc, sz + 2); - token->literal = intern_string(token_buffer); - loc->column += sz + 2; + token = new_token(T_colon, loc, 1); + loc->column++; return token; } - if (ch == '\'') { - int sz = 0; - bool escaped = false; + return NULL; +} - ch = read_char(buf); - if (ch == '\\') { - token_buffer[sz++] = ch; - ch = read_char(buf); +/* Operators, each of which may or may not continue into a longer one. + * + * Returns NULL when 'ch' is none of its business, so that lex_token() can offer + * the character to the next reader in line. + */ +token_t *lex_operator(strbuf_t *buf, source_location_t *loc, char ch) +{ + token_t *token; - do { - if (sz >= MAX_TOKEN_LEN - 1) { - loc->len = sz + 1; - error_at("Character literal too long", loc); - } - token_buffer[sz++] = ch; - ch = read_char(buf); - escaped = true; - } while (ch && ch != '\''); - } else { - token_buffer[sz++] = ch; - } - token_buffer[sz] = '\0'; + if (ch == '^') { + ch = read_char(buf); - if (!escaped) + if (ch == '=') { ch = read_char(buf); - - if (ch != '\'') { - loc->len = 2; - error_at("Unenclosed character literal", loc); + token = new_token(T_xoreq, loc, 2); + loc->column += 2; + return token; } - read_char(buf); - token = new_token(T_char, loc, sz + 2); - token->literal = intern_string(token_buffer); - loc->column += sz + 2; + token = new_token(T_bit_xor, loc, 1); + loc->column++; return token; } @@ -831,27 +910,6 @@ token_t *lex_token(strbuf_t *buf, source_location_t *loc) return token; } - if (ch == ';') { - read_char(buf); - token = new_token(T_semicolon, loc, 1); - loc->column++; - return token; - } - - if (ch == '?') { - read_char(buf); - token = new_token(T_question, loc, 1); - loc->column++; - return token; - } - - if (ch == ':') { - read_char(buf); - token = new_token(T_colon, loc, 1); - loc->column++; - return token; - } - if (ch == '=') { ch = read_char(buf); @@ -867,6 +925,19 @@ token_t *lex_token(strbuf_t *buf, source_location_t *loc) return token; } + return NULL; +} + +/* Identifiers, and the keywords spelled like them. + * + * Returns NULL when 'ch' is none of its business, so that lex_token() can offer + * the character to the next reader in line. + */ +token_t *lex_word(strbuf_t *buf, source_location_t *loc, char ch) +{ + token_t *token; + char token_buffer[MAX_TOKEN_LEN]; + if (isalnum(ch) || ch == '_') { int sz = 0; do { @@ -975,6 +1046,40 @@ token_t *lex_token(strbuf_t *buf, source_location_t *loc) return token; } + return NULL; +} + +/* Reads one token, dispatching on the character it starts with. Each reader + * above claims the characters it knows and returns NULL for the rest; the order + * of the calls matters only between lex_number() and lex_word(), which would + * otherwise both claim a leading digit. + */ +token_t *lex_token(strbuf_t *buf, source_location_t *loc) +{ + token_t *token; + char ch = peek_char(buf, 0); + + loc->pos = buf->size; + + token = lex_layout(buf, loc, ch); + if (token) + return token; + token = lex_number(buf, loc, ch); + if (token) + return token; + token = lex_literal(buf, loc, ch); + if (token) + return token; + token = lex_punct(buf, loc, ch); + if (token) + return token; + token = lex_operator(buf, loc, ch); + if (token) + return token; + token = lex_word(buf, loc, ch); + if (token) + return token; + error_at("Unexpected token", loc); return NULL; } diff --git a/src/parser.c b/src/parser.c index 899529f0..8a2062ee 100644 --- a/src/parser.c +++ b/src/parser.c @@ -4058,383 +4058,446 @@ basic_block_t *read_code_block(func_t *func, block_t *parent, basic_block_t *bb); -basic_block_t *read_body_statement(block_t *parent, basic_block_t *bb) +/* A switch, its cases, and the block they break out of. */ +basic_block_t *handle_switch_statement(block_t *parent, basic_block_t *bb) { char token[MAX_ID_LEN]; - func_t *func; - type_t *type; - var_t *vd, *rs1, *rs2, *var; - opcode_t prefix_op = OP_generic; - bool is_const = false; + var_t *vd; + var_t *rs1; + var_t *rs2; - if (!bb) - printf("Warning: unreachable code detected\n"); + bool is_default = false; - /* statement can be: - * function call, variable declaration, assignment operation, - * keyword, block - */ + basic_block_t *n = bb_create(parent); + bb_connect(bb, n, NEXT); + bb = n; - if (lex_peek(T_open_curly, NULL)) - return read_code_block(parent->func, parent, bb); + lex_expect(T_open_bracket); + read_expr(parent, &bb); + lex_expect(T_close_bracket); - if (lex_accept(T_return)) { - return handle_return_statement(parent, bb); - } + /* create exit jump for breaks */ + basic_block_t *switch_end = bb_create(parent); + break_bb_push(switch_end); + basic_block_t *true_body_ = bb_create(parent); - if (lex_accept(T_if)) { - return handle_if_statement(parent, bb); - } + lex_expect(T_open_curly); + while (lex_peek(T_default, NULL) || lex_peek(T_case, NULL)) { + if (lex_accept(T_default)) + is_default = true; + else { + int case_val; + + lex_accept(T_case); + char literal[MAX_TOKEN_LEN]; + + if (lex_peek_n(T_numeric, literal, MAX_TOKEN_LEN)) { + case_val = parse_numeric_constant(literal); + lex_expect(T_numeric); + } else if (lex_peek_n(T_char, literal, MAX_TOKEN_LEN)) { + char unescaped[MAX_TOKEN_LEN]; + if (unescape_string(literal, unescaped, MAX_TOKEN_LEN) < 0) + error_at("Invalid escape sequence", next_token_loc()); + case_val = unescaped[0]; + lex_expect(T_char); + } else if (lex_peek(T_identifier, token)) { + const constant_t *cd = find_constant(token); + if (!cd) + error_at("Unknown constant in case label", cur_token_loc()); + case_val = cd->value; + lex_expect(T_identifier); + } else { + fatal("Not a valid case value"); + } - if (lex_accept(T_while)) { - return handle_while_statement(parent, bb); - } + vd = require_var(parent); + vd->var_name = gen_name(); + vd->init_val = case_val; + opstack_push(vd); + add_insn(parent, bb, OP_load_constant, vd, NULL, NULL, 0, NULL); - if (lex_accept(T_switch)) { - bool is_default = false; + vd = require_var(parent); + vd->var_name = gen_name(); + rs1 = opstack_pop(); + rs2 = operand_stack[operand_stack_idx - 1]; + add_insn(parent, bb, OP_eq, vd, rs1, rs2, 0, NULL); - basic_block_t *n = bb_create(parent); - bb_connect(bb, n, NEXT); - bb = n; + add_insn(parent, bb, OP_branch, NULL, vd, NULL, 0, NULL); + } + lex_expect(T_colon); - lex_expect(T_open_bracket); - read_expr(parent, &bb); - lex_expect(T_close_bracket); + if (is_default) + /* there's no condition if it is a default label */ + bb_connect(bb, true_body_, NEXT); + else + bb_connect(bb, true_body_, THEN); - /* create exit jump for breaks */ - basic_block_t *switch_end = bb_create(parent); - break_bb_push(switch_end); - basic_block_t *true_body_ = bb_create(parent); + int control = 0; - lex_expect(T_open_curly); - while (lex_peek(T_default, NULL) || lex_peek(T_case, NULL)) { - if (lex_accept(T_default)) - is_default = true; - else { - int case_val; - - lex_accept(T_case); - char literal[MAX_TOKEN_LEN]; - - if (lex_peek_n(T_numeric, literal, MAX_TOKEN_LEN)) { - case_val = parse_numeric_constant(literal); - lex_expect(T_numeric); - } else if (lex_peek_n(T_char, literal, MAX_TOKEN_LEN)) { - char unescaped[MAX_TOKEN_LEN]; - if (unescape_string(literal, unescaped, MAX_TOKEN_LEN) < 0) - error_at("Invalid escape sequence", next_token_loc()); - case_val = unescaped[0]; - lex_expect(T_char); - } else if (lex_peek(T_identifier, token)) { - const constant_t *cd = find_constant(token); - if (!cd) - error_at("Unknown constant in case label", - cur_token_loc()); - case_val = cd->value; - lex_expect(T_identifier); - } else { - fatal("Not a valid case value"); - } + while (!lex_peek(T_case, NULL) && !lex_peek(T_close_curly, NULL) && + !lex_peek(T_default, NULL)) { + true_body_ = read_body_statement(parent, true_body_); + control = 1; + } - vd = require_var(parent); - vd->var_name = gen_name(); - vd->init_val = case_val; - opstack_push(vd); - add_insn(parent, bb, OP_load_constant, vd, NULL, NULL, 0, NULL); + if (control && true_body_) { + /* Create a new body block for next case, and connect the last body + * block which lacks 'break' to it to make that one ignore the + * upcoming cases. + */ + n = bb_create(parent); + bb_connect(true_body_, n, NEXT); + true_body_ = n; + } - vd = require_var(parent); - vd->var_name = gen_name(); - rs1 = opstack_pop(); - rs2 = operand_stack[operand_stack_idx - 1]; - add_insn(parent, bb, OP_eq, vd, rs1, rs2, 0, NULL); + if (!lex_peek(T_close_curly, NULL)) { + if (is_default) + error_at("Label default should be the last one", + next_token_loc()); - add_insn(parent, bb, OP_branch, NULL, vd, NULL, 0, NULL); - } - lex_expect(T_colon); + /* create a new conditional block for next case */ + n = bb_create(parent); + bb_connect(bb, n, ELSE); + bb = n; - if (is_default) - /* there's no condition if it is a default label */ - bb_connect(bb, true_body_, NEXT); - else - bb_connect(bb, true_body_, THEN); + /* create a new body block for next case if the last body block + * exits 'switch'. + */ + if (!true_body_) + true_body_ = bb_create(parent); + } else if (!is_default) { + /* handle missing default label */ + bb_connect(bb, switch_end, ELSE); + } + } - int control = 0; + /* remove the expression in switch() */ + opstack_pop(); + lex_expect(T_close_curly); - while (!lex_peek(T_case, NULL) && !lex_peek(T_close_curly, NULL) && - !lex_peek(T_default, NULL)) { - true_body_ = read_body_statement(parent, true_body_); - control = 1; - } + if (true_body_) + /* if the last label has no explicit break, connect it to the end */ + bb_connect(true_body_, switch_end, NEXT); - if (control && true_body_) { - /* Create a new body block for next case, and connect the last - * body block which lacks 'break' to it to make that one ignore - * the upcoming cases. - */ - n = bb_create(parent); - bb_connect(true_body_, n, NEXT); - true_body_ = n; - } + break_exit_idx--; - if (!lex_peek(T_close_curly, NULL)) { - if (is_default) - error_at("Label default should be the last one", - next_token_loc()); + int dangling = 1; + for (int i = 0; i < switch_end->prev_idx; i++) + if (switch_end->prev[i].bb) + dangling = 0; - /* create a new conditional block for next case */ - n = bb_create(parent); - bb_connect(bb, n, ELSE); - bb = n; + if (dangling) + return NULL; - /* create a new body block for next case if the last body block - * exits 'switch'. - */ - if (!true_body_) - true_body_ = bb_create(parent); - } else if (!is_default) { - /* handle missing default label */ - bb_connect(bb, switch_end, ELSE); - } - } + return switch_end; +} - /* remove the expression in switch() */ - opstack_pop(); - lex_expect(T_close_curly); +/* A for loop: setup, condition, body and increment. */ +basic_block_t *handle_for_statement(block_t *parent, basic_block_t *bb) +{ + char token[MAX_ID_LEN]; + type_t *type; + var_t *vd; + var_t *rs1; + var_t *var; + opcode_t prefix_op = OP_generic; - if (true_body_) - /* if the last label has no explicit break, connect it to the end */ - bb_connect(true_body_, switch_end, NEXT); + lex_expect(T_open_bracket); - break_exit_idx--; + /* synthesize for loop block */ + block_t *blk = add_block(parent, parent->func); - int dangling = 1; - for (int i = 0; i < switch_end->prev_idx; i++) - if (switch_end->prev[i].bb) - dangling = 0; + /* setup - execute once */ + basic_block_t *setup = bb_create(blk); + bb_connect(bb, setup, NEXT); - if (dangling) - return NULL; + if (!lex_accept(T_semicolon)) { + if (!lex_peek(T_identifier, token)) + error_at("Unexpected token when parsing for loop", + next_token_loc()); - return switch_end; - } + int find_type_flag = lex_accept(T_struct) ? 2 : 1; + if (find_type_flag == 1 && lex_accept(T_union)) { + find_type_flag = 2; + } + type = find_type(token, find_type_flag); + if (type) { + var = require_typed_var(blk, type); + read_full_var_decl(var, false, false); + add_insn(blk, setup, OP_allocat, var, NULL, NULL, 0, NULL); + add_symbol(setup, var); + if (lex_accept(T_assign)) { + read_expr(blk, &setup); + read_ternary_operation(blk, &setup); + + rs1 = resize_var(parent, &bb, opstack_pop(), var); + add_insn(blk, setup, OP_assign, var, rs1, NULL, 0, NULL); + } + while (lex_accept(T_comma)) { + var_t *nv; + + /* add sequence point at T_comma */ + perform_side_effect(blk, setup); + + /* multiple (partial) declarations */ + nv = require_typed_var(blk, type); + read_partial_var_decl(nv, var); /* partial */ + add_insn(blk, setup, OP_allocat, nv, NULL, NULL, 0, NULL); + add_symbol(setup, nv); + if (lex_accept(T_assign)) { + read_expr(blk, &setup); + + rs1 = resize_var(parent, &bb, opstack_pop(), nv); + add_insn(blk, setup, OP_assign, nv, rs1, NULL, 0, NULL); + } + } + } else { + read_body_assignment(token, blk, OP_generic, &setup); + } - if (lex_accept(T_break)) { - if (!break_exit_idx) - error_at("'break' outside of a loop or switch", cur_token_loc()); - bb_connect(bb, break_bb[break_exit_idx - 1], NEXT); lex_expect(T_semicolon); - return NULL; } - if (lex_accept(T_continue)) { - if (!continue_pos_idx) - error_at("'continue' outside of a loop", cur_token_loc()); - bb_connect(bb, continue_bb[continue_pos_idx - 1], NEXT); + basic_block_t *cond_ = bb_create(blk); + basic_block_t *for_end = bb_create(parent); + basic_block_t *cond_start = cond_; + break_bb_push(for_end); + bb_connect(setup, cond_, NEXT); + + /* condition - check before the loop */ + if (!lex_accept(T_semicolon)) { + read_expr(blk, &cond_); lex_expect(T_semicolon); - return NULL; + } else { + /* always true */ + vd = require_var(blk); + vd->init_val = 1; + vd->var_name = gen_name(); + opstack_push(vd); + add_insn(blk, cond_, OP_load_constant, vd, NULL, NULL, 0, NULL); } + bb_connect(cond_, for_end, ELSE); - if (lex_accept(T_for)) { - lex_expect(T_open_bracket); - - /* synthesize for loop block */ - block_t *blk = add_block(parent, parent->func); + vd = opstack_pop(); + add_insn(blk, cond_, OP_branch, NULL, vd, NULL, 0, NULL); - /* setup - execute once */ - basic_block_t *setup = bb_create(blk); - bb_connect(bb, setup, NEXT); + basic_block_t *inc_ = bb_create(blk); + continue_bb_push(inc_); - if (!lex_accept(T_semicolon)) { - if (!lex_peek(T_identifier, token)) - error_at("Unexpected token when parsing for loop", - next_token_loc()); + /* increment after each loop */ + if (!lex_accept(T_close_bracket)) { + if (lex_accept(T_increment)) + prefix_op = OP_add; + else if (lex_accept(T_decrement)) + prefix_op = OP_sub; + lex_peek(T_identifier, token); + read_body_assignment(token, blk, prefix_op, &inc_); + lex_expect(T_close_bracket); + } - int find_type_flag = lex_accept(T_struct) ? 2 : 1; - if (find_type_flag == 1 && lex_accept(T_union)) { - find_type_flag = 2; - } - type = find_type(token, find_type_flag); - if (type) { - var = require_typed_var(blk, type); - read_full_var_decl(var, false, false); - add_insn(blk, setup, OP_allocat, var, NULL, NULL, 0, NULL); - add_symbol(setup, var); - if (lex_accept(T_assign)) { - read_expr(blk, &setup); - read_ternary_operation(blk, &setup); + /* loop body */ + basic_block_t *body_ = bb_create(blk); + bb_connect(cond_, body_, THEN); + body_ = read_body_statement(blk, body_); - rs1 = resize_var(parent, &bb, opstack_pop(), var); - add_insn(blk, setup, OP_assign, var, rs1, NULL, 0, NULL); - } - while (lex_accept(T_comma)) { - var_t *nv; - - /* add sequence point at T_comma */ - perform_side_effect(blk, setup); - - /* multiple (partial) declarations */ - nv = require_typed_var(blk, type); - read_partial_var_decl(nv, var); /* partial */ - add_insn(blk, setup, OP_allocat, nv, NULL, NULL, 0, NULL); - add_symbol(setup, nv); - if (lex_accept(T_assign)) { - read_expr(blk, &setup); - - rs1 = resize_var(parent, &bb, opstack_pop(), nv); - add_insn(blk, setup, OP_assign, nv, rs1, NULL, 0, NULL); - } - } - } else { - read_body_assignment(token, blk, OP_generic, &setup); - } + /* Normal fallthrough from the loop body goes through the increment block. A + * continue statement may already have connected another predecessor to + * inc_. + */ + if (body_) + bb_connect(body_, inc_, NEXT); - lex_expect(T_semicolon); + /* An empty increment block still needs its back-edge when it is reachable + * through normal fallthrough or continue. + * + * Do not connect a completely unreachable increment block, such as: + * + * for (;;) { + * break; + * } + */ + bool has_pred = false; + for (int i = 0; i < inc_->prev_idx; i++) { + if (inc_->prev[i].bb) { + has_pred = true; + break; } + } + if (has_pred) + bb_connect(inc_, cond_start, NEXT); - basic_block_t *cond_ = bb_create(blk); - basic_block_t *for_end = bb_create(parent); - basic_block_t *cond_start = cond_; - break_bb_push(for_end); - bb_connect(setup, cond_, NEXT); + /* jump to increment */ + continue_pos_idx--; + break_exit_idx--; + return for_end; +} - /* condition - check before the loop */ - if (!lex_accept(T_semicolon)) { - read_expr(blk, &cond_); - lex_expect(T_semicolon); - } else { - /* always true */ - vd = require_var(blk); - vd->init_val = 1; - vd->var_name = gen_name(); - opstack_push(vd); - add_insn(blk, cond_, OP_load_constant, vd, NULL, NULL, 0, NULL); - } - bb_connect(cond_, for_end, ELSE); +/* A do-while loop, whose condition is tested after the body. */ +basic_block_t *handle_do_statement(block_t *parent, basic_block_t *bb) +{ + var_t *vd; - vd = opstack_pop(); - add_insn(blk, cond_, OP_branch, NULL, vd, NULL, 0, NULL); + basic_block_t *n = bb_create(parent); + bb_connect(bb, n, NEXT); + bb = n; - basic_block_t *inc_ = bb_create(blk); - continue_bb_push(inc_); + basic_block_t *cond_ = bb_create(parent); + basic_block_t *do_while_end = bb_create(parent); - /* increment after each loop */ - if (!lex_accept(T_close_bracket)) { - if (lex_accept(T_increment)) - prefix_op = OP_add; - else if (lex_accept(T_decrement)) - prefix_op = OP_sub; - lex_peek(T_identifier, token); - read_body_assignment(token, blk, prefix_op, &inc_); - lex_expect(T_close_bracket); - } + continue_bb_push(cond_); + break_bb_push(do_while_end); - /* loop body */ - basic_block_t *body_ = bb_create(blk); - bb_connect(cond_, body_, THEN); - body_ = read_body_statement(blk, body_); + basic_block_t *do_body = read_body_statement(parent, bb); + if (do_body) + bb_connect(do_body, cond_, NEXT); - /* Normal fallthrough from the loop body goes through the increment - * block. A continue statement may already have connected another - * predecessor to inc_. - */ - if (body_) - bb_connect(body_, inc_, NEXT); + lex_expect(T_while); + lex_expect(T_open_bracket); + read_expr(parent, &cond_); + lex_expect(T_close_bracket); - /* An empty increment block still needs its back-edge when it is - * reachable through normal fallthrough or continue. - * - * Do not connect a completely unreachable increment block, such as: - * - * for (;;) { - * break; - * } - */ - bool has_pred = false; - for (int i = 0; i < inc_->prev_idx; i++) { - if (inc_->prev[i].bb) { - has_pred = true; - break; - } + vd = opstack_pop(); + add_insn(parent, cond_, OP_branch, NULL, vd, NULL, 0, NULL); + + lex_expect(T_semicolon); + + for (int i = 0; i < cond_->prev_idx; i++) { + if (cond_->prev[i].bb) { + bb_connect(cond_, bb, THEN); + bb_connect(cond_, do_while_end, ELSE); + break; } - if (has_pred) - bb_connect(inc_, cond_start, NEXT); + /* if breaking out of loop, skip condition block */ + } + + continue_pos_idx--; + break_exit_idx--; + return do_while_end; +} + +/* A local struct or union declaration. */ +basic_block_t *handle_record_statement(block_t *parent, basic_block_t *bb) +{ + char token[MAX_ID_LEN]; + type_t *type; + var_t *rs1; + var_t *var; + bool is_const = false; - /* jump to increment */ - continue_pos_idx--; - break_exit_idx--; - return for_end; + int find_type_flag = lex_accept(T_struct) ? 2 : 1; + if (find_type_flag == 1 && lex_accept(T_union)) { + find_type_flag = 2; } + lex_ident(T_identifier, token); + type = find_type(token, find_type_flag); + if (type) { + var = require_typed_var(parent, type); + var->is_const_qualified = is_const; + read_partial_var_decl(var, NULL); + add_insn(parent, bb, OP_allocat, var, NULL, NULL, 0, NULL); + add_symbol(bb, var); + if (lex_accept(T_assign)) { + if (lex_peek(T_open_curly, NULL) && + (var->array_size > 0 || var->ptr_level > 0)) { + parse_array_init(var, parent, &bb, 1); /* Always emit code */ + } else if (lex_peek(T_open_curly, NULL) && + (var->type->base_type == TYPE_struct || + var->type->base_type == TYPE_typedef)) { + /* C90-compliant struct compound literal support */ + type_t *struct_type = var->type; - if (lex_accept(T_do)) { - basic_block_t *n = bb_create(parent); - bb_connect(bb, n, NEXT); - bb = n; + /* Handle typedef by getting actual struct type */ + if (struct_type->base_type == TYPE_typedef && + struct_type->base_struct) + struct_type = struct_type->base_struct; - basic_block_t *cond_ = bb_create(parent); - basic_block_t *do_while_end = bb_create(parent); + lex_expect(T_open_curly); + int field_idx = 0; - continue_bb_push(cond_); - break_bb_push(do_while_end); + if (!lex_peek(T_close_curly, NULL)) { + for (;;) { + /* Parse field value expression */ + read_expr(parent, &bb); + read_ternary_operation(parent, &bb); + var_t *val = opstack_pop(); - basic_block_t *do_body = read_body_statement(parent, bb); - if (do_body) - bb_connect(do_body, cond_, NEXT); + /* Initialize field if within bounds */ + if (field_idx < struct_type->num_fields) { + var_t *field = &struct_type->fields[field_idx]; - lex_expect(T_while); - lex_expect(T_open_bracket); - read_expr(parent, &cond_); - lex_expect(T_close_bracket); + /* Create target variable for field */ + var_t *field_val = + resize_to(parent, &bb, val, field->type, + field->ptr_level); - vd = opstack_pop(); - add_insn(parent, cond_, OP_branch, NULL, vd, NULL, 0, NULL); + /* Compute field address: &struct + field_offset */ + var_t *struct_addr = require_var(parent); + struct_addr->var_name = gen_name(); + add_insn(parent, bb, OP_address_of, struct_addr, + var, NULL, 0, NULL); - lex_expect(T_semicolon); + var_t *field_addr = struct_addr; + if (field->offset > 0) { + var_t *offset = require_var(parent); + offset->var_name = gen_name(); + offset->init_val = field->offset; + add_insn(parent, bb, OP_load_constant, offset, + NULL, NULL, 0, NULL); - for (int i = 0; i < cond_->prev_idx; i++) { - if (cond_->prev[i].bb) { - bb_connect(cond_, bb, THEN); - bb_connect(cond_, do_while_end, ELSE); - break; - } - /* if breaking out of loop, skip condition block */ - } + var_t *addr = require_var(parent); + addr->var_name = gen_name(); + add_insn(parent, bb, OP_add, addr, struct_addr, + offset, 0, NULL); + field_addr = addr; + } - continue_pos_idx--; - break_exit_idx--; - return do_while_end; - } + /* Write field value */ + int field_size = size_var(field); + add_insn(parent, bb, OP_write, NULL, field_addr, + field_val, field_size, NULL); + } - if (lex_accept(T_goto)) - return handle_goto_statement(parent, bb); + field_idx++; + if (!lex_accept(T_comma)) + break; + if (lex_peek(T_close_curly, NULL)) + break; + } + } + lex_expect(T_close_curly); + } else { + read_expr(parent, &bb); + read_ternary_operation(parent, &bb); - /* empty statement */ - if (lex_accept(T_semicolon)) - return bb; + var_t *rhs = opstack_pop(); + rhs = scalarize_array_literal_if_needed( + parent, &bb, rhs, var->type, + !var->ptr_level && var->array_size == 0); - /* struct/union variable declaration */ - if (lex_peek(T_struct, NULL) || lex_peek(T_union, NULL)) { - int find_type_flag = lex_accept(T_struct) ? 2 : 1; - if (find_type_flag == 1 && lex_accept(T_union)) { - find_type_flag = 2; + rs1 = resize_var(parent, &bb, rhs, var); + add_insn(parent, bb, OP_assign, var, rs1, NULL, 0, NULL); + } } - lex_ident(T_identifier, token); - type = find_type(token, find_type_flag); - if (type) { - var = require_typed_var(parent, type); - var->is_const_qualified = is_const; - read_partial_var_decl(var, NULL); - add_insn(parent, bb, OP_allocat, var, NULL, NULL, 0, NULL); - add_symbol(bb, var); + while (lex_accept(T_comma)) { + var_t *nv; + + /* add sequence point at T_comma */ + perform_side_effect(parent, bb); + + /* multiple (partial) declarations */ + nv = require_typed_var(parent, type); + read_inner_var_decl(nv, false, false); + add_insn(parent, bb, OP_allocat, nv, NULL, NULL, 0, NULL); + add_symbol(bb, nv); if (lex_accept(T_assign)) { if (lex_peek(T_open_curly, NULL) && - (var->array_size > 0 || var->ptr_level > 0)) { - parse_array_init(var, parent, &bb, - 1); /* Always emit code */ + (nv->array_size > 0 || nv->ptr_level > 0)) { + parse_array_init(nv, parent, &bb, true); } else if (lex_peek(T_open_curly, NULL) && - (var->type->base_type == TYPE_struct || - var->type->base_type == TYPE_typedef)) { + (nv->type->base_type == TYPE_struct || + nv->type->base_type == TYPE_typedef)) { /* C90-compliant struct compound literal support */ - type_t *struct_type = var->type; + type_t *struct_type = nv->type; /* Handle typedef by getting actual struct type */ if (struct_type->base_type == TYPE_typedef && @@ -4465,7 +4528,7 @@ basic_block_t *read_body_statement(block_t *parent, basic_block_t *bb) var_t *struct_addr = require_var(parent); struct_addr->var_name = gen_name(); add_insn(parent, bb, OP_address_of, struct_addr, - var, NULL, 0, NULL); + nv, NULL, 0, NULL); var_t *field_addr = struct_addr; if (field->offset > 0) { @@ -4499,120 +4562,35 @@ basic_block_t *read_body_statement(block_t *parent, basic_block_t *bb) } else { read_expr(parent, &bb); read_ternary_operation(parent, &bb); - var_t *rhs = opstack_pop(); rhs = scalarize_array_literal_if_needed( - parent, &bb, rhs, var->type, - !var->ptr_level && var->array_size == 0); + parent, &bb, rhs, nv->type, + !nv->ptr_level && nv->array_size == 0); - rs1 = resize_var(parent, &bb, rhs, var); - add_insn(parent, bb, OP_assign, var, rs1, NULL, 0, NULL); - } - } - while (lex_accept(T_comma)) { - var_t *nv; - - /* add sequence point at T_comma */ - perform_side_effect(parent, bb); - - /* multiple (partial) declarations */ - nv = require_typed_var(parent, type); - read_inner_var_decl(nv, false, false); - add_insn(parent, bb, OP_allocat, nv, NULL, NULL, 0, NULL); - add_symbol(bb, nv); - if (lex_accept(T_assign)) { - if (lex_peek(T_open_curly, NULL) && - (nv->array_size > 0 || nv->ptr_level > 0)) { - parse_array_init(nv, parent, &bb, true); - } else if (lex_peek(T_open_curly, NULL) && - (nv->type->base_type == TYPE_struct || - nv->type->base_type == TYPE_typedef)) { - /* C90-compliant struct compound literal support */ - type_t *struct_type = nv->type; - - /* Handle typedef by getting actual struct type */ - if (struct_type->base_type == TYPE_typedef && - struct_type->base_struct) - struct_type = struct_type->base_struct; - - lex_expect(T_open_curly); - int field_idx = 0; - - if (!lex_peek(T_close_curly, NULL)) { - for (;;) { - /* Parse field value expression */ - read_expr(parent, &bb); - read_ternary_operation(parent, &bb); - var_t *val = opstack_pop(); - - /* Initialize field if within bounds */ - if (field_idx < struct_type->num_fields) { - var_t *field = - &struct_type->fields[field_idx]; - - /* Create target variable for field */ - var_t *field_val = - resize_to(parent, &bb, val, field->type, - field->ptr_level); - - /* Compute field address: &struct + - * field_offset - */ - var_t *struct_addr = require_var(parent); - struct_addr->var_name = gen_name(); - add_insn(parent, bb, OP_address_of, - struct_addr, nv, NULL, 0, NULL); - - var_t *field_addr = struct_addr; - if (field->offset > 0) { - var_t *offset = require_var(parent); - offset->var_name = gen_name(); - offset->init_val = field->offset; - add_insn(parent, bb, OP_load_constant, - offset, NULL, NULL, 0, NULL); - - var_t *addr = require_var(parent); - addr->var_name = gen_name(); - add_insn(parent, bb, OP_add, addr, - struct_addr, offset, 0, NULL); - field_addr = addr; - } - - /* Write field value */ - int field_size = size_var(field); - add_insn(parent, bb, OP_write, NULL, - field_addr, field_val, field_size, - NULL); - } - - field_idx++; - if (!lex_accept(T_comma)) - break; - if (lex_peek(T_close_curly, NULL)) - break; - } - } - lex_expect(T_close_curly); - } else { - read_expr(parent, &bb); - read_ternary_operation(parent, &bb); - var_t *rhs = opstack_pop(); - rhs = scalarize_array_literal_if_needed( - parent, &bb, rhs, nv->type, - !nv->ptr_level && nv->array_size == 0); - - rs1 = resize_var(parent, &bb, rhs, nv); - add_insn(parent, bb, OP_assign, nv, rs1, NULL, 0, NULL); - } + rs1 = resize_var(parent, &bb, rhs, nv); + add_insn(parent, bb, OP_assign, nv, rs1, NULL, 0, NULL); } } - lex_expect(T_semicolon); - return bb; } - error_at("Unknown struct/union type", next_token_loc()); + lex_expect(T_semicolon); + return bb; } + error_at("Unknown struct/union type", next_token_loc()); +} + +/* Everything a statement can still be: a declaration, an assignment, a call, or + * an expression evaluated for its effect. + */ +basic_block_t *handle_declaration(block_t *parent, basic_block_t *bb) +{ + char token[MAX_ID_LEN]; + func_t *func; + type_t *type; + var_t *rs1; + var_t *var; + opcode_t prefix_op = OP_generic; + bool is_const = false; - /* Handle const qualifier for local variable declarations */ if (lex_accept(T_const)) { is_const = true; /* After const, we expect a type */ @@ -4956,6 +4934,71 @@ basic_block_t *read_body_statement(block_t *parent, basic_block_t *bb) return NULL; } +basic_block_t *read_body_statement(block_t *parent, basic_block_t *bb) +{ + if (!bb) + printf("Warning: unreachable code detected\n"); + + /* statement can be: + * function call, variable declaration, assignment operation, + * keyword, block + */ + + if (lex_peek(T_open_curly, NULL)) + return read_code_block(parent->func, parent, bb); + + if (lex_accept(T_return)) { + return handle_return_statement(parent, bb); + } + + if (lex_accept(T_if)) { + return handle_if_statement(parent, bb); + } + + if (lex_accept(T_while)) { + return handle_while_statement(parent, bb); + } + + if (lex_accept(T_switch)) + return handle_switch_statement(parent, bb); + + if (lex_accept(T_break)) { + if (!break_exit_idx) + error_at("'break' outside of a loop or switch", cur_token_loc()); + bb_connect(bb, break_bb[break_exit_idx - 1], NEXT); + lex_expect(T_semicolon); + return NULL; + } + + if (lex_accept(T_continue)) { + if (!continue_pos_idx) + error_at("'continue' outside of a loop", cur_token_loc()); + bb_connect(bb, continue_bb[continue_pos_idx - 1], NEXT); + lex_expect(T_semicolon); + return NULL; + } + + if (lex_accept(T_for)) + return handle_for_statement(parent, bb); + + if (lex_accept(T_do)) + return handle_do_statement(parent, bb); + + if (lex_accept(T_goto)) + return handle_goto_statement(parent, bb); + + /* empty statement */ + if (lex_accept(T_semicolon)) + return bb; + + /* struct/union variable declaration */ + if (lex_peek(T_struct, NULL) || lex_peek(T_union, NULL)) + return handle_record_statement(parent, bb); + + /* Handle const qualifier for local variable declarations */ + return handle_declaration(parent, bb); +} + /* Nesting counter for read_code_block(), which recurses through * read_body_statement() for every nested block. */ diff --git a/src/reg-alloc.c b/src/reg-alloc.c index f2d0e8a4..913646c6 100644 --- a/src/reg-alloc.c +++ b/src/reg-alloc.c @@ -1999,170 +1999,679 @@ void coalesce_phi_slots(func_t *func) } } -void reg_alloc(void) +/* Place one global initializer, which has no basic block of its own. */ +void reg_alloc_global(insn_t *global_insn) { - /* TODO: Add proper .bss and .data section support for uninitialized / - * initialized globals + ph2_ir_t *ir; + int dest, src0; + + /* Global initializers carry no liveness information, so no operand register + * may be reused as a destination here. */ - for (insn_t *global_insn = GLOBAL_FUNC->bbs->insn_list.head; global_insn; - global_insn = global_insn->next) { - ph2_ir_t *ir; - int dest, src0; - /* Global initializers carry no liveness information, so no operand - * register may be reused as a destination here. - */ + switch (global_insn->opcode) { + case OP_allocat: + if (global_insn->rd->array_size) { + /* Original scheme: pointer slot + backing region. Cache the base + * offset of the backing region into init_val so later global + * initializers can address elements without loading the pointer. + */ + global_insn->rd->offset = GLOBAL_FUNC->stack_size; + global_insn->rd->space_is_allocated = true; + GLOBAL_FUNC->stack_size += PTR_SIZE; + src0 = GLOBAL_FUNC->stack_size; /* base of backing region */ + + /* Stash base offset for this array variable */ + global_insn->rd->init_val = src0; + + if (global_insn->rd->ptr_level) + GLOBAL_FUNC->stack_size += + align_size(PTR_SIZE * global_insn->rd->array_size); + else { + GLOBAL_FUNC->stack_size += align_size( + global_insn->rd->array_size * global_insn->rd->type->size); + } - switch (global_insn->opcode) { - case OP_allocat: - if (global_insn->rd->array_size) { - /* Original scheme: pointer slot + backing region. Cache the - * base offset of the backing region into init_val so later - * global initializers can address elements without loading the - * pointer. - */ - global_insn->rd->offset = GLOBAL_FUNC->stack_size; - global_insn->rd->space_is_allocated = true; + dest = + prepare_dest(GLOBAL_FUNC->bbs, NULL, global_insn->rd, -1, -1); + ir = bb_add_ph2_ir(GLOBAL_FUNC->bbs, OP_global_address_of); + ir->src0 = src0; + ir->dest = dest; + ir->is_pointer = true; + ir->size_bytes = PTR_SIZE; + spill_var(GLOBAL_FUNC->bbs, global_insn->rd, dest); + } else { + global_insn->rd->offset = GLOBAL_FUNC->stack_size; + global_insn->rd->space_is_allocated = true; + if (global_insn->rd->ptr_level) + GLOBAL_FUNC->stack_size += PTR_SIZE; + else if (global_insn->rd->type != TY_int && + global_insn->rd->type != TY_short && + global_insn->rd->type != TY_char && + global_insn->rd->type != TY_bool) { + GLOBAL_FUNC->stack_size += + align_size(global_insn->rd->type->size); + } else + /* 'char' is aligned to one byte for the convenience */ GLOBAL_FUNC->stack_size += PTR_SIZE; - src0 = GLOBAL_FUNC->stack_size; /* base of backing region */ - - /* Stash base offset for this array variable */ - global_insn->rd->init_val = src0; - - if (global_insn->rd->ptr_level) - GLOBAL_FUNC->stack_size += - align_size(PTR_SIZE * global_insn->rd->array_size); - else { - GLOBAL_FUNC->stack_size += - align_size(global_insn->rd->array_size * - global_insn->rd->type->size); + } + break; + case OP_load_constant: + case OP_load_data_address: + case OP_load_rodata_address: + dest = prepare_dest(GLOBAL_FUNC->bbs, NULL, global_insn->rd, -1, -1); + ir = bb_add_ph2_ir(GLOBAL_FUNC->bbs, global_insn->opcode); + ir->src0 = global_insn->rd->init_val; + ir->dest = dest; + break; + case OP_assign: + src0 = prepare_operand(GLOBAL_FUNC->bbs, global_insn->rs1, -1); + dest = prepare_dest(GLOBAL_FUNC->bbs, NULL, global_insn->rd, src0, -1); + ir = bb_add_ph2_ir(GLOBAL_FUNC->bbs, OP_assign); + ir->src0 = src0; + ir->dest = dest; + spill_var(GLOBAL_FUNC->bbs, global_insn->rd, dest); + /* release the unused constant number in register manually */ + REGS[src0].polluted = 0; + vreg_clear_phys(REGS[src0].var); + REGS[src0].var = NULL; + break; + case OP_add: { + /* Special-case address computation for globals: if rs1 is a global base + * and rs2 is a constant, propagate absolute offset to rd so OP_write + * can fold into OP_global_store. + */ + if (global_insn->rs1 && global_insn->rs1->is_global && + global_insn->rs2) { + int base_off = global_insn->rs1->offset; + + /* For global arrays, use backing-region base cached in init_val */ + if (global_insn->rs1->array_size > 0) + base_off = global_insn->rs1->init_val; + global_insn->rd->offset = base_off + global_insn->rs2->init_val; + global_insn->rd->space_is_allocated = true; + global_insn->rd->is_global = true; + break; + } + /* Fallback: generate an add */ + int src1; + src0 = prepare_operand(GLOBAL_FUNC->bbs, global_insn->rs1, -1); + src1 = prepare_operand(GLOBAL_FUNC->bbs, global_insn->rs2, src0); + dest = + prepare_dest(GLOBAL_FUNC->bbs, NULL, global_insn->rd, src0, src1); + ir = bb_add_ph2_ir(GLOBAL_FUNC->bbs, OP_add); + ir->src0 = src0; + ir->src1 = src1; + ir->dest = dest; + break; + } + case OP_write: { + /* Fold (addr, val) where addr carries GP-relative offset */ + if (global_insn->rs1 && (global_insn->rs1->is_global)) { + int vreg = prepare_operand(GLOBAL_FUNC->bbs, global_insn->rs2, -1); + ir = bb_add_ph2_ir(GLOBAL_FUNC->bbs, OP_global_store); + ir->src0 = vreg; + + /* For array variables used as base, store to the backing region's + * base offset (cached in init_val). + */ + int base_off = global_insn->rs1->offset; + if (global_insn->rs1->array_size > 0) + base_off = global_insn->rs1->init_val; + ir->src1 = base_off; + break; + } + /* Fallback generic write */ + int src1; + src0 = prepare_operand(GLOBAL_FUNC->bbs, global_insn->rs1, -1); + src1 = prepare_operand(GLOBAL_FUNC->bbs, global_insn->rs2, src0); + ir = bb_add_ph2_ir(GLOBAL_FUNC->bbs, OP_write); + ir->src0 = src0; + ir->src1 = src1; + ir->dest = global_insn->sz; + break; + } + case OP_trunc: + case OP_sign_ext: + case OP_cast: + /* A narrowing initializer such as "char g[] = {65, 66}" reaches the + * global block as a conversion, so it has to be lowered here exactly as + * it is inside a function. + */ + src0 = prepare_operand(GLOBAL_FUNC->bbs, global_insn->rs1, -1); + dest = prepare_dest(GLOBAL_FUNC->bbs, NULL, global_insn->rd, src0, -1); + ir = bb_add_ph2_ir(GLOBAL_FUNC->bbs, global_insn->opcode); + ir->src0 = src0; + ir->src1 = global_insn->sz; + ir->dest = dest; + break; + default: + printf("Unsupported global operation: %d\n", global_insn->opcode); + fflush(stdout); /* see fatal() */ + abort(); + } +} + +/* Assign registers across one basic block, and emit the phase-2 IR that carries + * the assignment. + */ +void reg_alloc_bb(func_t *func, basic_block_t *bb) +{ + bool handle_abi = false, args_on_stack = false; + + is_pushing_args = false; + int args = 0; + + bb->visited++; + + /* The entry block starts with the incoming arguments already in their + * registers; every other block takes what its predecessor handed over, or + * nothing. + */ + if (bb != func->bbs) + load_entry_regs(bb); + + for (insn_t *insn = bb->insn_list.head; insn; insn = insn->next) { + func_t *callee_func; + ph2_ir_t *ir; + int dest, src0, src1; + int sz, clear_reg; + + refresh(bb, insn); + + switch (insn->opcode) { + case OP_unwound_phi: + track_var_use(insn->rs1, insn->idx); + + /* A pinned destination lives in the same register on every path, so + * the copy this phi stands for is a register move rather than a + * write into a slot nothing reads back. + */ + int to = pinned_reg_of(insn->rd); + if (to >= 0) { + src0 = prepare_operand(bb, insn->rs1, -1); + if (src0 != to) { + ir = bb_add_ph2_ir(bb, OP_assign); + ir->src0 = src0; + ir->dest = to; + ir->is_pointer = is_pointer_like(insn->rd); + ir->size_bytes = var_slot_size(insn->rd); } + REGS[to].var = insn->rd; + REGS[to].polluted = 1; + break; + } - dest = prepare_dest(GLOBAL_FUNC->bbs, NULL, global_insn->rd, -1, - -1); - ir = bb_add_ph2_ir(GLOBAL_FUNC->bbs, OP_global_address_of); - ir->src0 = src0; - ir->dest = dest; - ir->is_pointer = true; - ir->size_bytes = PTR_SIZE; - spill_var(GLOBAL_FUNC->bbs, global_insn->rd, dest); - } else { - global_insn->rd->offset = GLOBAL_FUNC->stack_size; - global_insn->rd->space_is_allocated = true; - if (global_insn->rd->ptr_level) - GLOBAL_FUNC->stack_size += PTR_SIZE; - else if (global_insn->rd->type != TY_int && - global_insn->rd->type != TY_short && - global_insn->rd->type != TY_char && - global_insn->rd->type != TY_bool) { - GLOBAL_FUNC->stack_size += - align_size(global_insn->rd->type->size); - } else - /* 'char' is aligned to one byte for the convenience */ - GLOBAL_FUNC->stack_size += PTR_SIZE; + if (!insn->rd->space_is_allocated) + alloc_var_slot(bb->belong_to, insn->rd); + + /* Sharing a slot with the phi turns the copy into a write of the + * operand into the place it already lives. Only a register the + * block has changed still needs storing -- and reading the slot + * back first, as the general path would, is a load whose value goes + * straight home again. + */ + if (insn->rs1->space_is_allocated && + insn->rs1->offset == insn->rd->offset && + insn->rs1->ofs_based_on_stack_top == + insn->rd->ofs_based_on_stack_top) { + int held = find_in_regs(insn->rs1); + + if (held < 0 || !REGS[held].polluted) + break; /* the slot already holds the value */ + store_var(bb, insn->rs1, held); + break; + } + + src0 = prepare_operand(bb, insn->rs1, -1); + ir = bb_add_ph2_ir(bb, OP_store); + ir->src0 = src0; + ir->src1 = insn->rd->offset; + ir->ofs_based_on_stack_top = insn->rd->ofs_based_on_stack_top; + ir->is_pointer = is_pointer_like(insn->rd); + ir->size_bytes = var_slot_size(insn->rd); + break; + case OP_allocat: + if ((insn->rd->type == TY_void || insn->rd->type == TY_int || + insn->rd->type == TY_short || insn->rd->type == TY_char || + insn->rd->type == TY_bool) && + insn->rd->array_size == 0) + break; + + insn->rd->offset = func->stack_size; + insn->rd->space_is_allocated = true; + func->stack_size += PTR_SIZE; + src0 = func->stack_size; + + if (insn->rd->ptr_level) + sz = PTR_SIZE; + else { + sz = insn->rd->type->size; + } + + if (insn->rd->array_size) + func->stack_size += align_size(insn->rd->array_size * sz); + else + func->stack_size += align_size(sz); + + if (!insn->rd->is_global && + aggregate_has_function_pointer(insn->rd->type)) { + insn->rd->has_backing_storage = true; } + + dest = prepare_dest(bb, insn, insn->rd, -1, -1); + ir = bb_add_ph2_ir(bb, OP_address_of); + ir->src0 = src0; + ir->dest = dest; + ir->ofs_based_on_stack_top = insn->rd->ofs_based_on_stack_top; + + /* For arrays, store the base address just like global arrays do */ + if (insn->rd->array_size) + spill_var(bb, insn->rd, dest); break; case OP_load_constant: case OP_load_data_address: case OP_load_rodata_address: - dest = - prepare_dest(GLOBAL_FUNC->bbs, NULL, global_insn->rd, -1, -1); - ir = bb_add_ph2_ir(GLOBAL_FUNC->bbs, global_insn->opcode); - ir->src0 = global_insn->rd->init_val; + dest = prepare_dest(bb, insn, insn->rd, -1, -1); + ir = bb_add_ph2_ir(bb, insn->opcode); + ir->src0 = insn->rd->init_val; ir->dest = dest; + + /* store global variable immediately after assignment */ + if (insn->rd->is_global) { + ir = bb_add_ph2_ir(bb, OP_global_store); + ir->src0 = dest; + ir->src1 = insn->rd->offset; + REGS[dest].polluted = 0; + } + break; - case OP_assign: - src0 = prepare_operand(GLOBAL_FUNC->bbs, global_insn->rs1, -1); - dest = - prepare_dest(GLOBAL_FUNC->bbs, NULL, global_insn->rd, src0, -1); - ir = bb_add_ph2_ir(GLOBAL_FUNC->bbs, OP_assign); - ir->src0 = src0; + case OP_address_of: + case OP_global_address_of: + /* Mark variable as address-taken, disable constant optimization */ + insn->rs1->address_taken = true; + insn->rs1->is_const = false; + + /* OP_allocat puts a local aggregate's spill slot before its backing + * storage. &aggregate must name the backing storage, not the spill + * slot. + * + * FIXME: This does not support aggregate parameter for now. + */ + bool is_pointer = insn->rs1->ptr_level || + (insn->rs1->type && insn->rs1->type->ptr_level); + if (!insn->rs1->is_global && !is_pointer && + aggregate_has_function_pointer(insn->rs1->type)) { + if (!insn->rs1->has_backing_storage) { + insn->rs1->offset = func->stack_size; + insn->rs1->space_is_allocated = true; + insn->rs1->ofs_based_on_stack_top = false; + func->stack_size += PTR_SIZE; + if (insn->rs1->ptr_level) + sz = PTR_SIZE; + else + sz = insn->rs1->type->size; + if (insn->rs1->array_size) + func->stack_size += + align_size(insn->rs1->array_size * sz); + else + func->stack_size += align_size(sz); + insn->rs1->has_backing_storage = true; + } + + dest = prepare_dest(bb, insn, insn->rd, -1, -1); + ir = bb_add_ph2_ir(bb, OP_address_of); + ir->src0 = insn->rs1->offset + PTR_SIZE; + ir->dest = dest; + ir->ofs_based_on_stack_top = insn->rs1->ofs_based_on_stack_top; + break; + } + + /* make sure variable is on stack */ + if (!insn->rs1->space_is_allocated) { + alloc_var_slot(bb->belong_to, insn->rs1); + + for (int i = 0; i < REG_CNT; i++) + if (REGS[i].var == insn->rs1 && !pinned_base[i]) { + ir = bb_add_ph2_ir(bb, OP_store); + ir->src0 = i; + ir->src1 = insn->rs1->offset; + ir->ofs_based_on_stack_top = + insn->rs1->ofs_based_on_stack_top; + /* Clear stale register tracking */ + REGS[i].var = NULL; + } + } + + dest = prepare_dest(bb, insn, insn->rd, -1, -1); + if (insn->rs1->is_global || insn->opcode == OP_global_address_of) + ir = bb_add_ph2_ir(bb, OP_global_address_of); + else + ir = bb_add_ph2_ir(bb, OP_address_of); + ir->src0 = insn->rs1->offset; ir->dest = dest; - spill_var(GLOBAL_FUNC->bbs, global_insn->rd, dest); - /* release the unused constant number in register manually */ - REGS[src0].polluted = 0; - vreg_clear_phys(REGS[src0].var); - REGS[src0].var = NULL; - break; - case OP_add: { - /* Special-case address computation for globals: if rs1 is a global - * base and rs2 is a constant, propagate absolute offset to rd so - * OP_write can fold into OP_global_store. + ir->ofs_based_on_stack_top = insn->rs1->ofs_based_on_stack_top; + break; + case OP_cmov: { + /* A select reads three registers, one more than the allocator's + * avoid arguments can protect, so each is locked as it is placed. + * With all three safe the destination may land anywhere. */ - if (global_insn->rs1 && global_insn->rs1->is_global && - global_insn->rs2) { - int base_off = global_insn->rs1->offset; + int cond, taken, other; + + track_var_use(insn->rs1, insn->idx); + track_var_use(insn->rs2, insn->idx); + track_var_use(insn->rs3, insn->idx); + + reg_locked = 0; + taken = prepare_operand(bb, insn->rs1, -1); + reg_locked = reg_locked | (1 << taken); + other = prepare_operand(bb, insn->rs3, taken); + reg_locked = reg_locked | (1 << other); + cond = prepare_operand(bb, insn->rs2, taken); + reg_locked = reg_locked | (1 << cond); + dest = prepare_dest(bb, insn, insn->rd, taken, other); + + if (dest < 0) { + /* A select needs a fourth register only while all three inputs + * remain live. Save one unpinned input first, then use its + * physical register as the result. The CMOV emitter + * deliberately supports the destination aliasing either arm; it + * tests the condition before overwriting anything, so the + * condition is safe too if it is the only choice. + * + * spill_var() leaves the machine register unchanged, which is + * exactly what the select still needs. It only removes the + * allocator's association, making the value available for the + * result and forcing a later use of the saved input to reload + * its slot. + */ + int reuse = -1; + const int sources[] = {taken, other, cond}; + + for (int i = 0; i < 3; i++) { + int reg = sources[i]; + if (!pinned_base[reg]) { + reuse = reg; + break; + } + } - /* For global arrays, use backing-region base cached in init_val + /* pin_registers() reserves at most half the file, so one of a + * select's inputs is always reclaimable. */ - if (global_insn->rs1->array_size > 0) - base_off = global_insn->rs1->init_val; - global_insn->rd->offset = base_off + global_insn->rs2->init_val; - global_insn->rd->space_is_allocated = true; - global_insn->rd->is_global = true; + if (reuse < 0) + abort(); + spill_var(bb, REGS[reuse].var, reuse); + dest = prepare_dest(bb, insn, insn->rd, taken, other); + if (dest != reuse) + abort(); + } + reg_locked = 0; + ir = bb_add_ph2_ir(bb, OP_cmov); + ir->src0 = cond; + ir->src1 = taken; + ir->src2 = other; + ir->dest = dest; + ir->size_bytes = var_slot_size(insn->rd); + ir->is_pointer = is_pointer_like(insn->rd); + break; + } + case OP_assign: + if (insn->rd->consumed == -1) break; + + track_var_use(insn->rs1, insn->idx); + src0 = find_in_regs(insn->rs1); + + /* If operand is loaded from stack, clear the original slot after + * moving. + */ + if (src0 > -1) + clear_reg = 0; + else { + clear_reg = 1; + src0 = prepare_operand(bb, insn->rs1, -1); } - /* Fallback: generate an add */ - int src1; - src0 = prepare_operand(GLOBAL_FUNC->bbs, global_insn->rs1, -1); - src1 = prepare_operand(GLOBAL_FUNC->bbs, global_insn->rs2, src0); - dest = prepare_dest(GLOBAL_FUNC->bbs, NULL, global_insn->rd, src0, - src1); - ir = bb_add_ph2_ir(GLOBAL_FUNC->bbs, OP_add); + dest = prepare_dest(bb, insn, insn->rd, src0, -1); + ir = bb_add_ph2_ir(bb, OP_assign); ir->src0 = src0; - ir->src1 = src1; ir->dest = dest; + + /* store global variable immediately after assignment */ + if (insn->rd->is_global) { + ir = bb_add_ph2_ir(bb, OP_global_store); + ir->src0 = dest; + ir->src1 = insn->rd->offset; + REGS[dest].polluted = 0; + } + + if (clear_reg) { + vreg_clear_phys(REGS[src0].var); + REGS[src0].var = NULL; + } + break; - } - case OP_write: { - /* Fold (addr, val) where addr carries GP-relative offset */ - if (global_insn->rs1 && (global_insn->rs1->is_global)) { - int vreg = - prepare_operand(GLOBAL_FUNC->bbs, global_insn->rs2, -1); - ir = bb_add_ph2_ir(GLOBAL_FUNC->bbs, OP_global_store); - ir->src0 = vreg; - - /* For array variables used as base, store to the backing - * region's base offset (cached in init_val). + case OP_read: + src0 = prepare_operand(bb, insn->rs1, -1); + dest = prepare_dest(bb, insn, insn->rd, src0, -1); + ir = bb_add_ph2_ir(bb, OP_read); + ir->src0 = src0; + ir->src1 = insn->sz; + ir->dest = dest; + break; + case OP_write: + if (insn->rs2->is_func) { + src0 = prepare_operand(bb, insn->rs1, -1); + ir = bb_add_ph2_ir(bb, OP_address_of_func); + ir->src0 = src0; + ir->func_name = intern_string(insn->rs2->var_name); + if (dynlink) { + func_t *target_fn = find_func(ir->func_name); + if (target_fn) + target_fn->is_used = true; + } + } else { + /* FIXME: Register content becomes stale after store operation. + * Current workaround causes redundant spilling - need better + * register invalidation strategy. */ - int base_off = global_insn->rs1->offset; - if (global_insn->rs1->array_size > 0) - base_off = global_insn->rs1->init_val; - ir->src1 = base_off; - break; + spill_alive(bb, insn); + src0 = prepare_operand(bb, insn->rs1, -1); + src1 = prepare_operand(bb, insn->rs2, src0); + ir = bb_add_ph2_ir(bb, OP_write); + ir->src0 = src0; + ir->src1 = src1; + ir->dest = insn->sz; } - /* Fallback generic write */ - int src1; - src0 = prepare_operand(GLOBAL_FUNC->bbs, global_insn->rs1, -1); - src1 = prepare_operand(GLOBAL_FUNC->bbs, global_insn->rs2, src0); - ir = bb_add_ph2_ir(GLOBAL_FUNC->bbs, OP_write); + break; + case OP_branch: + src0 = prepare_operand(bb, insn->rs1, -1); + + /* REGS[src0].var had been set to NULL, but the actual content is + * still holded in the register. + * + * Write every live-out value back but keep it in its register: the + * arm reached by the jump starts with an empty file and loads from + * the slots just written, while the arm that falls through can + * inherit the registers through bb_export_regs(). + */ + spill_live_out_keep(bb); + + ir = bb_add_ph2_ir(bb, OP_branch); + ir->src0 = src0; + ir->then_bb = bb->then_; + ir->else_bb = bb->else_; + break; + case OP_push: + extend_liveness(bb, insn, insn->rs1, insn->sz); + + if (!is_pushing_args) { + spill_alive(bb, insn); + is_pushing_args = true; + } + if (!handle_abi) { + args_on_stack = abi_lower_call_args(bb, insn); + handle_abi = true; + } + + if (args_on_stack && args >= MAX_ARGS_IN_REG) + break; + + src0 = prepare_operand(bb, insn->rs1, -1); + ir = bb_add_ph2_ir(bb, OP_assign); + ir->src0 = src0; + ir->dest = args++; + REGS[ir->dest].var = insn->rs1; + REGS[ir->dest].polluted = 0; + break; + case OP_call: + callee_func = find_func(insn->str); + if (!callee_func->num_params) + spill_alive(bb, insn); + + if (dynlink) + callee_func->is_used = true; + + ir = bb_add_ph2_ir(bb, OP_call); + /* add_insn() interned this when the call was created. */ + ir->func_name = insn->str; + + is_pushing_args = false; + args = 0; + handle_abi = false; + + clobber_caller_saved(); + + break; + case OP_indirect: + if (!args) + spill_alive(bb, insn); + + src0 = prepare_operand(bb, insn->rs1, -1); + ir = bb_add_ph2_ir(bb, OP_load_func); + ir->src0 = src0; + + bb_add_ph2_ir(bb, OP_indirect); + + is_pushing_args = false; + args = 0; + handle_abi = false; + + clobber_caller_saved(); + break; + case OP_func_ret: + dest = prepare_dest(bb, insn, insn->rd, -1, -1); + ir = bb_add_ph2_ir(bb, OP_assign); + ir->src0 = 0; + ir->dest = dest; + break; + case OP_return: + if (insn->rs1) + src0 = prepare_operand(bb, insn->rs1, -1); + else + src0 = -1; + + ir = bb_add_ph2_ir(bb, OP_return); + ir->src0 = src0; + break; + case OP_add: + case OP_sub: + case OP_mul: + case OP_div: + case OP_mod: + case OP_lshift: + case OP_rshift: + case OP_eq: + case OP_neq: + case OP_gt: + case OP_geq: + case OP_lt: + case OP_leq: + case OP_bit_and: + case OP_bit_or: + case OP_bit_xor: + track_var_use(insn->rs1, insn->idx); + track_var_use(insn->rs2, insn->idx); + src0 = prepare_operand(bb, insn->rs1, -1); + src1 = prepare_operand(bb, insn->rs2, src0); + dest = prepare_dest(bb, insn, insn->rd, src0, src1); + ir = bb_add_ph2_ir(bb, insn->opcode); ir->src0 = src0; ir->src1 = src1; - ir->dest = global_insn->sz; + ir->dest = dest; + + /* Record whether the result is an address. On LP64 an int-typed + * result has to wrap at 32 bits, while a pointer must keep all + * 64. The backend cannot tell the two apart without this. + */ + ir->is_pointer = is_pointer_like(insn->rd) || + is_pointer_like(insn->rs1) || + is_pointer_like(insn->rs2); + break; + case OP_negate: + case OP_bit_not: + case OP_log_not: + src0 = prepare_operand(bb, insn->rs1, -1); + dest = prepare_dest(bb, insn, insn->rd, src0, -1); + ir = bb_add_ph2_ir(bb, insn->opcode); + ir->src0 = src0; + ir->dest = dest; break; - } case OP_trunc: case OP_sign_ext: case OP_cast: - /* A narrowing initializer such as "char g[] = {65, 66}" reaches the - * global block as a conversion, so it has to be lowered here - * exactly as it is inside a function. - */ - src0 = prepare_operand(GLOBAL_FUNC->bbs, global_insn->rs1, -1); - dest = - prepare_dest(GLOBAL_FUNC->bbs, NULL, global_insn->rd, src0, -1); - ir = bb_add_ph2_ir(GLOBAL_FUNC->bbs, global_insn->opcode); + src0 = prepare_operand(bb, insn->rs1, -1); + dest = prepare_dest(bb, insn, insn->rd, src0, -1); + ir = bb_add_ph2_ir(bb, insn->opcode); + ir->src1 = insn->sz; ir->src0 = src0; - ir->src1 = global_insn->sz; ir->dest = dest; break; default: - printf("Unsupported global operation: %d\n", global_insn->opcode); + printf("Unknown opcode\n"); fflush(stdout); /* see fatal() */ abort(); } } + if (bb->next) { + spill_live_out_keep(bb); + bb_export_regs(bb); + } else if (bb->then_ || bb->else_) { + /* A conditional branch has already written its live-out values back at + * OP_branch; only the handover is left. + */ + bb_export_regs(bb); + } + + if (bb == func->exit) + return; + + /* append jump instruction for the normal block only */ + if (!bb->next) + return; + + if (bb->next == func->exit) + return; + + /* jump to the beginning of loop or over the else block */ + if (bb->next->visited == func->visited || bb->next->rpo != bb->rpo + 1) { + ph2_ir_t *ir = bb_add_ph2_ir(bb, OP_jump); + ir->next_bb = bb->next; + } +} + +void reg_alloc(void) +{ + /* TODO: Add proper .bss and .data section support for uninitialized / + * initialized globals + */ + for (insn_t *global_insn = GLOBAL_FUNC->bbs->insn_list.head; global_insn; + global_insn = global_insn->next) { + reg_alloc_global(global_insn); + } + for (func_t *func = FUNC_LIST.head; func; func = func->next) { /* Skip function declarations without bodies */ if (!func->bbs) @@ -2284,528 +2793,7 @@ void reg_alloc(void) } for (basic_block_t *bb = func->bbs; bb; bb = bb->rpo_next) { - bool handle_abi = false, args_on_stack = false; - - is_pushing_args = false; - int args = 0; - - bb->visited++; - - /* The entry block starts with the incoming arguments already in - * their registers; every other block takes what its predecessor - * handed over, or nothing. - */ - if (bb != func->bbs) - load_entry_regs(bb); - - for (insn_t *insn = bb->insn_list.head; insn; insn = insn->next) { - func_t *callee_func; - ph2_ir_t *ir; - int dest, src0, src1; - int sz, clear_reg; - - refresh(bb, insn); - - switch (insn->opcode) { - case OP_unwound_phi: - track_var_use(insn->rs1, insn->idx); - - /* A pinned destination lives in the same register on every - * path, so the copy this phi stands for is a register move - * rather than a write into a slot nothing reads back. - */ - int to = pinned_reg_of(insn->rd); - if (to >= 0) { - src0 = prepare_operand(bb, insn->rs1, -1); - if (src0 != to) { - ir = bb_add_ph2_ir(bb, OP_assign); - ir->src0 = src0; - ir->dest = to; - ir->is_pointer = is_pointer_like(insn->rd); - ir->size_bytes = var_slot_size(insn->rd); - } - REGS[to].var = insn->rd; - REGS[to].polluted = 1; - break; - } - - if (!insn->rd->space_is_allocated) - alloc_var_slot(bb->belong_to, insn->rd); - - /* Sharing a slot with the phi turns the copy into a write - * of the operand into the place it already lives. Only a - * register the block has changed still needs storing -- and - * reading the slot back first, as the general path would, - * is a load whose value goes straight home again. - */ - if (insn->rs1->space_is_allocated && - insn->rs1->offset == insn->rd->offset && - insn->rs1->ofs_based_on_stack_top == - insn->rd->ofs_based_on_stack_top) { - int held = find_in_regs(insn->rs1); - - if (held < 0 || !REGS[held].polluted) - break; /* the slot already holds the value */ - store_var(bb, insn->rs1, held); - break; - } - - src0 = prepare_operand(bb, insn->rs1, -1); - ir = bb_add_ph2_ir(bb, OP_store); - ir->src0 = src0; - ir->src1 = insn->rd->offset; - ir->ofs_based_on_stack_top = - insn->rd->ofs_based_on_stack_top; - ir->is_pointer = is_pointer_like(insn->rd); - ir->size_bytes = var_slot_size(insn->rd); - break; - case OP_allocat: - if ((insn->rd->type == TY_void || - insn->rd->type == TY_int || - insn->rd->type == TY_short || - insn->rd->type == TY_char || - insn->rd->type == TY_bool) && - insn->rd->array_size == 0) - break; - - insn->rd->offset = func->stack_size; - insn->rd->space_is_allocated = true; - func->stack_size += PTR_SIZE; - src0 = func->stack_size; - - if (insn->rd->ptr_level) - sz = PTR_SIZE; - else { - sz = insn->rd->type->size; - } - - if (insn->rd->array_size) - func->stack_size += - align_size(insn->rd->array_size * sz); - else - func->stack_size += align_size(sz); - - if (!insn->rd->is_global && - aggregate_has_function_pointer(insn->rd->type)) { - insn->rd->has_backing_storage = true; - } - - dest = prepare_dest(bb, insn, insn->rd, -1, -1); - ir = bb_add_ph2_ir(bb, OP_address_of); - ir->src0 = src0; - ir->dest = dest; - ir->ofs_based_on_stack_top = - insn->rd->ofs_based_on_stack_top; - - /* For arrays, store the base address just like global - * arrays do - */ - if (insn->rd->array_size) - spill_var(bb, insn->rd, dest); - break; - case OP_load_constant: - case OP_load_data_address: - case OP_load_rodata_address: - dest = prepare_dest(bb, insn, insn->rd, -1, -1); - ir = bb_add_ph2_ir(bb, insn->opcode); - ir->src0 = insn->rd->init_val; - ir->dest = dest; - - /* store global variable immediately after assignment */ - if (insn->rd->is_global) { - ir = bb_add_ph2_ir(bb, OP_global_store); - ir->src0 = dest; - ir->src1 = insn->rd->offset; - REGS[dest].polluted = 0; - } - - break; - case OP_address_of: - case OP_global_address_of: - /* Mark variable as address-taken, disable constant - * optimization - */ - insn->rs1->address_taken = true; - insn->rs1->is_const = false; - - /* OP_allocat puts a local aggregate's spill slot before its - * backing storage. &aggregate must name the backing - * storage, not the spill slot. - * - * FIXME: This does not support aggregate parameter for now. - */ - bool is_pointer = - insn->rs1->ptr_level || - (insn->rs1->type && insn->rs1->type->ptr_level); - if (!insn->rs1->is_global && !is_pointer && - aggregate_has_function_pointer(insn->rs1->type)) { - if (!insn->rs1->has_backing_storage) { - insn->rs1->offset = func->stack_size; - insn->rs1->space_is_allocated = true; - insn->rs1->ofs_based_on_stack_top = false; - func->stack_size += PTR_SIZE; - if (insn->rs1->ptr_level) - sz = PTR_SIZE; - else - sz = insn->rs1->type->size; - if (insn->rs1->array_size) - func->stack_size += - align_size(insn->rs1->array_size * sz); - else - func->stack_size += align_size(sz); - insn->rs1->has_backing_storage = true; - } - - dest = prepare_dest(bb, insn, insn->rd, -1, -1); - ir = bb_add_ph2_ir(bb, OP_address_of); - ir->src0 = insn->rs1->offset + PTR_SIZE; - ir->dest = dest; - ir->ofs_based_on_stack_top = - insn->rs1->ofs_based_on_stack_top; - break; - } - - /* make sure variable is on stack */ - if (!insn->rs1->space_is_allocated) { - alloc_var_slot(bb->belong_to, insn->rs1); - - for (int i = 0; i < REG_CNT; i++) - if (REGS[i].var == insn->rs1 && !pinned_base[i]) { - ir = bb_add_ph2_ir(bb, OP_store); - ir->src0 = i; - ir->src1 = insn->rs1->offset; - ir->ofs_based_on_stack_top = - insn->rs1->ofs_based_on_stack_top; - /* Clear stale register tracking */ - REGS[i].var = NULL; - } - } - - dest = prepare_dest(bb, insn, insn->rd, -1, -1); - if (insn->rs1->is_global || - insn->opcode == OP_global_address_of) - ir = bb_add_ph2_ir(bb, OP_global_address_of); - else - ir = bb_add_ph2_ir(bb, OP_address_of); - ir->src0 = insn->rs1->offset; - ir->dest = dest; - ir->ofs_based_on_stack_top = - insn->rs1->ofs_based_on_stack_top; - break; - case OP_cmov: { - /* A select reads three registers, one more than the - * allocator's avoid arguments can protect, so each is - * locked as it is placed. With all three safe the - * destination may land anywhere. - */ - int cond, taken, other; - - track_var_use(insn->rs1, insn->idx); - track_var_use(insn->rs2, insn->idx); - track_var_use(insn->rs3, insn->idx); - - reg_locked = 0; - taken = prepare_operand(bb, insn->rs1, -1); - reg_locked = reg_locked | (1 << taken); - other = prepare_operand(bb, insn->rs3, taken); - reg_locked = reg_locked | (1 << other); - cond = prepare_operand(bb, insn->rs2, taken); - reg_locked = reg_locked | (1 << cond); - dest = prepare_dest(bb, insn, insn->rd, taken, other); - - if (dest < 0) { - /* A select needs a fourth register only while all three - * inputs remain live. Save one unpinned input first, - * then use its physical register as the result. The - * CMOV emitter deliberately supports the destination - * aliasing either arm; it tests the condition before - * overwriting anything, so the condition is safe too if - * it is the only choice. - * - * spill_var() leaves the machine register unchanged, - * which is exactly what the select still needs. It only - * removes the allocator's association, making the value - * available for the result and forcing a later use of - * the saved input to reload its slot. - */ - int reuse = -1; - const int sources[] = {taken, other, cond}; - - for (int i = 0; i < 3; i++) { - int reg = sources[i]; - if (!pinned_base[reg]) { - reuse = reg; - break; - } - } - - /* pin_registers() reserves at most half the file, so - * one of a select's inputs is always reclaimable. - */ - if (reuse < 0) - abort(); - spill_var(bb, REGS[reuse].var, reuse); - dest = prepare_dest(bb, insn, insn->rd, taken, other); - if (dest != reuse) - abort(); - } - reg_locked = 0; - ir = bb_add_ph2_ir(bb, OP_cmov); - ir->src0 = cond; - ir->src1 = taken; - ir->src2 = other; - ir->dest = dest; - ir->size_bytes = var_slot_size(insn->rd); - ir->is_pointer = is_pointer_like(insn->rd); - break; - } - case OP_assign: - if (insn->rd->consumed == -1) - break; - - track_var_use(insn->rs1, insn->idx); - src0 = find_in_regs(insn->rs1); - - /* If operand is loaded from stack, clear the original slot - * after moving. - */ - if (src0 > -1) - clear_reg = 0; - else { - clear_reg = 1; - src0 = prepare_operand(bb, insn->rs1, -1); - } - dest = prepare_dest(bb, insn, insn->rd, src0, -1); - ir = bb_add_ph2_ir(bb, OP_assign); - ir->src0 = src0; - ir->dest = dest; - - /* store global variable immediately after assignment */ - if (insn->rd->is_global) { - ir = bb_add_ph2_ir(bb, OP_global_store); - ir->src0 = dest; - ir->src1 = insn->rd->offset; - REGS[dest].polluted = 0; - } - - if (clear_reg) { - vreg_clear_phys(REGS[src0].var); - REGS[src0].var = NULL; - } - - break; - case OP_read: - src0 = prepare_operand(bb, insn->rs1, -1); - dest = prepare_dest(bb, insn, insn->rd, src0, -1); - ir = bb_add_ph2_ir(bb, OP_read); - ir->src0 = src0; - ir->src1 = insn->sz; - ir->dest = dest; - break; - case OP_write: - if (insn->rs2->is_func) { - src0 = prepare_operand(bb, insn->rs1, -1); - ir = bb_add_ph2_ir(bb, OP_address_of_func); - ir->src0 = src0; - ir->func_name = intern_string(insn->rs2->var_name); - if (dynlink) { - func_t *target_fn = find_func(ir->func_name); - if (target_fn) - target_fn->is_used = true; - } - } else { - /* FIXME: Register content becomes stale after store - * operation. Current workaround causes redundant - * spilling - need better register invalidation - * strategy. - */ - spill_alive(bb, insn); - src0 = prepare_operand(bb, insn->rs1, -1); - src1 = prepare_operand(bb, insn->rs2, src0); - ir = bb_add_ph2_ir(bb, OP_write); - ir->src0 = src0; - ir->src1 = src1; - ir->dest = insn->sz; - } - break; - case OP_branch: - src0 = prepare_operand(bb, insn->rs1, -1); - - /* REGS[src0].var had been set to NULL, but the actual - * content is still holded in the register. - * - * Write every live-out value back but keep it in its - * register: the arm reached by the jump starts with an - * empty file and loads from the slots just written, while - * the arm that falls through can inherit the registers - * through bb_export_regs(). - */ - spill_live_out_keep(bb); - - ir = bb_add_ph2_ir(bb, OP_branch); - ir->src0 = src0; - ir->then_bb = bb->then_; - ir->else_bb = bb->else_; - break; - case OP_push: - extend_liveness(bb, insn, insn->rs1, insn->sz); - - if (!is_pushing_args) { - spill_alive(bb, insn); - is_pushing_args = true; - } - if (!handle_abi) { - args_on_stack = abi_lower_call_args(bb, insn); - handle_abi = true; - } - - if (args_on_stack && args >= MAX_ARGS_IN_REG) - break; - - src0 = prepare_operand(bb, insn->rs1, -1); - ir = bb_add_ph2_ir(bb, OP_assign); - ir->src0 = src0; - ir->dest = args++; - REGS[ir->dest].var = insn->rs1; - REGS[ir->dest].polluted = 0; - break; - case OP_call: - callee_func = find_func(insn->str); - if (!callee_func->num_params) - spill_alive(bb, insn); - - if (dynlink) - callee_func->is_used = true; - - ir = bb_add_ph2_ir(bb, OP_call); - /* add_insn() interned this when the call was created. */ - ir->func_name = insn->str; - - is_pushing_args = false; - args = 0; - handle_abi = false; - - clobber_caller_saved(); - - break; - case OP_indirect: - if (!args) - spill_alive(bb, insn); - - src0 = prepare_operand(bb, insn->rs1, -1); - ir = bb_add_ph2_ir(bb, OP_load_func); - ir->src0 = src0; - - bb_add_ph2_ir(bb, OP_indirect); - - is_pushing_args = false; - args = 0; - handle_abi = false; - - clobber_caller_saved(); - break; - case OP_func_ret: - dest = prepare_dest(bb, insn, insn->rd, -1, -1); - ir = bb_add_ph2_ir(bb, OP_assign); - ir->src0 = 0; - ir->dest = dest; - break; - case OP_return: - if (insn->rs1) - src0 = prepare_operand(bb, insn->rs1, -1); - else - src0 = -1; - - ir = bb_add_ph2_ir(bb, OP_return); - ir->src0 = src0; - break; - case OP_add: - case OP_sub: - case OP_mul: - case OP_div: - case OP_mod: - case OP_lshift: - case OP_rshift: - case OP_eq: - case OP_neq: - case OP_gt: - case OP_geq: - case OP_lt: - case OP_leq: - case OP_bit_and: - case OP_bit_or: - case OP_bit_xor: - track_var_use(insn->rs1, insn->idx); - track_var_use(insn->rs2, insn->idx); - src0 = prepare_operand(bb, insn->rs1, -1); - src1 = prepare_operand(bb, insn->rs2, src0); - dest = prepare_dest(bb, insn, insn->rd, src0, src1); - ir = bb_add_ph2_ir(bb, insn->opcode); - ir->src0 = src0; - ir->src1 = src1; - ir->dest = dest; - - /* Record whether the result is an address. On LP64 an - * int-typed result has to wrap at 32 bits, while a pointer - * must keep all 64. The backend cannot tell the two apart - * without this. - */ - ir->is_pointer = is_pointer_like(insn->rd) || - is_pointer_like(insn->rs1) || - is_pointer_like(insn->rs2); - break; - case OP_negate: - case OP_bit_not: - case OP_log_not: - src0 = prepare_operand(bb, insn->rs1, -1); - dest = prepare_dest(bb, insn, insn->rd, src0, -1); - ir = bb_add_ph2_ir(bb, insn->opcode); - ir->src0 = src0; - ir->dest = dest; - break; - case OP_trunc: - case OP_sign_ext: - case OP_cast: - src0 = prepare_operand(bb, insn->rs1, -1); - dest = prepare_dest(bb, insn, insn->rd, src0, -1); - ir = bb_add_ph2_ir(bb, insn->opcode); - ir->src1 = insn->sz; - ir->src0 = src0; - ir->dest = dest; - break; - default: - printf("Unknown opcode\n"); - fflush(stdout); /* see fatal() */ - abort(); - } - } - - if (bb->next) { - spill_live_out_keep(bb); - bb_export_regs(bb); - } else if (bb->then_ || bb->else_) { - /* A conditional branch has already written its live-out values - * back at OP_branch; only the handover is left. - */ - bb_export_regs(bb); - } - - if (bb == func->exit) - continue; - - /* append jump instruction for the normal block only */ - if (!bb->next) - continue; - - if (bb->next == func->exit) - continue; - - /* jump to the beginning of loop or over the else block */ - if (bb->next->visited == func->visited || - bb->next->rpo != bb->rpo + 1) { - ph2_ir_t *ir = bb_add_ph2_ir(bb, OP_jump); - ir->next_bb = bb->next; - } + reg_alloc_bb(func, bb); } /* handle implicit return */ diff --git a/src/x64-codegen.c b/src/x64-codegen.c index 5a619c2f..d1560077 100644 --- a/src/x64-codegen.c +++ b/src/x64-codegen.c @@ -1607,252 +1607,20 @@ bool emit_mul_by_const(int rd, int rs1, int c) return false; } -void emit_ph2_ir(ph2_ir_t *ph2_ir) +/* An OP_jump whose target is a single instruction emits that instruction in + * place, so the family emitters below reach back into the dispatcher. + */ +void emit_ph2_ir(ph2_ir_t *ph2_ir); + +/* Integer arithmetic. */ +void emit_arith(ph2_ir_t *ph2_ir, + int rd, + int rs1, + int rs2, + bool src1_const_known, + int src1_const) { - for (int i = 0; i < MAX_SKIP_IR; i++) { - if (skip_ir_index[i] >= 0 && skip_ir_index[i] == emit_ir_index) { - skip_ir_index[i] = -1; - return; - } - } - if (try_fold_mem_dest(ph2_ir)) - return; - if (try_fold_alu_to_slot(ph2_ir)) - return; - - int rd = map_ir_reg(ph2_ir->dest); - int rs1 = map_ir_reg(ph2_ir->src0); - if (src0_override >= 0 && src0_override_at == emit_ir_index) { - rs1 = src0_override; - src0_override = -1; - src0_override_at = -1; - } - int rs2 = map_ir_reg(ph2_ir->src1); - - /* Read the tracked value before invalidating, since dest may alias src1. */ - bool src1_const_known = false; - int src1_const = 0; - if (ph2_ir->src1 >= 0 && ph2_ir->src1 < REG_CNT && - const_reg_valid[ph2_ir->src1]) { - src1_const_known = true; - src1_const = const_reg_val[ph2_ir->src1]; - } - if (op_writes_dest(ph2_ir->op) && ph2_ir->dest >= 0 && - ph2_ir->dest < REG_CNT) { - const_reg_valid[ph2_ir->dest] = false; - shift_cache_kill(ph2_ir->dest); - } - - /* Anything that can touch a register this table does not name -- a call, or - * a division writing RAX and RDX -- invalidates all of it. This is the same - * set that drops frame mirrors below. - */ - if (!op_keeps_frame_mirrors(ph2_ir->op)) { - const_track_reset(); - shift_cache_reset(); - } - if (ph2_ir->op != OP_branch) - fused_cc_pending = false; - if (!branch_cc_for(ph2_ir->op)) { - cmp_mem_slot = -1; - cmp_imm_known = false; - } else if (src1_const_known) { - cmp_imm_known = true; - cmp_imm_val = src1_const; - } - - /* A value loaded only to be compared does not need a register: x86 takes - * the second compare operand from memory. Unlike arithmetic, nothing reuses - * this value afterwards, so folding it costs no later reload. - */ - if (ph2_ir->op == OP_load && emit_ir_index >= 0 && emit_next_ir && - branch_cc_for(emit_next_ir->op) && emit_next_ir->src1 == ph2_ir->dest && - emit_next_ir->src0 != ph2_ir->dest && - reg_dead_after(emit_ir_index + 2, ph2_ir->dest)) { - int w = load_width(ph2_ir); - if (w == 4 || w == 8) { - cmp_mem_slot = ph2_ir->src0; - cmp_mem_width = w; - return; - } - } - - if (!op_keeps_frame_mirrors(ph2_ir->op)) { - frame_mirror_reset(); - } else { - if (ph2_ir->op == OP_load && ph2_ir->dest >= 0 && - ph2_ir->dest < REG_CNT) { - int want = load_width(ph2_ir); - if (reg_mirror_valid[ph2_ir->dest] && - reg_mirror_slot[ph2_ir->dest] == ph2_ir->src0 && - reg_mirror_size[ph2_ir->dest] == want && - !reg_mirror_sext[ph2_ir->dest]) - return; /* the register still holds this slot */ - /* Some other register may already hold it. Copying between - * registers costs the same instruction but avoids waiting on the - * store-to-load forwarding this slot would otherwise require. - */ - for (int i = 0; i < REG_CNT; i++) { - if (!reg_mirror_valid[i] || - reg_mirror_slot[i] != ph2_ir->src0 || - reg_mirror_size[i] != want) - continue; - if (i == ph2_ir->dest && !reg_mirror_sext[i]) - continue; - int held = map_ir_reg(i); - - /* If this value exists only to be stored straight back out, let - * the store read the register that already holds it. Only sound - * when the register is bit-identical to what the load would - * produce, so a narrower mirror is excluded. Only when the - * consumer is the very next instruction: a later one could be - * skipped by another fold, and the copy would already be gone. - * And only when src0 is its one read of the register: the - * override redirects that read alone, so a second read of the - * same register -- src1, or a select's src2 -- would be left - * looking at whatever the load this skips was going to - * overwrite. - */ - if (!reg_mirror_sext[i] && src0_override < 0 && emit_next_ir && - op_src0_is_reg(emit_next_ir->op) && - emit_next_ir->src0 == ph2_ir->dest && - emit_next_ir->src1 != ph2_ir->dest && - !(op_src2_is_reg(emit_next_ir->op) && - emit_next_ir->src2 == ph2_ir->dest) && - reg_dead_after(emit_ir_index + 2, ph2_ir->dest)) { - src0_override = held; - src0_override_at = emit_ir_index + 1; - return; - } - emit_narrow_move(rd, held, want, reg_mirror_sext[i]); - reg_mirror_valid[ph2_ir->dest] = true; - reg_mirror_slot[ph2_ir->dest] = ph2_ir->src0; - reg_mirror_size[ph2_ir->dest] = want; - reg_mirror_sext[ph2_ir->dest] = false; - const_reg_valid[ph2_ir->dest] = false; - return; - } - } - - if (ph2_ir->dest >= 0 && ph2_ir->dest < REG_CNT) - reg_mirror_valid[ph2_ir->dest] = false; - - if (ph2_ir->op == OP_load && ph2_ir->dest >= 0 && - ph2_ir->dest < REG_CNT) { - reg_mirror_valid[ph2_ir->dest] = true; - reg_mirror_slot[ph2_ir->dest] = ph2_ir->src0; - reg_mirror_size[ph2_ir->dest] = load_width(ph2_ir); - reg_mirror_sext[ph2_ir->dest] = false; - } else if (ph2_ir->op == OP_store) { - /* Storing a register into the slot it already mirrors writes the - * bytes that are there. Coalescing a phi with its operand makes - * this common: the value's own write-back and the phi's copy become - * the same store. - */ - if (ph2_ir->src0 >= 0 && ph2_ir->src0 < REG_CNT && - reg_mirror_valid[ph2_ir->src0] && - reg_mirror_slot[ph2_ir->src0] == ph2_ir->src1) { - int keep = ph2_ir->size_bytes; - if (ph2_ir->is_pointer && keep != PTR_SIZE) - keep = PTR_SIZE; - if (reg_mirror_size[ph2_ir->src0] == keep) - return; - } - - /* The slot takes a new value, so any register mirroring it is now - * stale. - */ - for (int i = 0; i < REG_CNT; i++) { - if (reg_mirror_valid[i] && reg_mirror_slot[i] == ph2_ir->src1) - reg_mirror_valid[i] = false; - } - - /* The slot now holds the source register's low bytes, so that - * register mirrors it. A full-width store leaves the two - * bit-identical; a narrower one does not, and a later load of the - * slot sign-extends what was written -- which the sext flag - * records, so the reload becomes a MOVSX rather than a copy. - */ - if (ph2_ir->src0 >= 0 && ph2_ir->src0 < REG_CNT) { - int eff = ph2_ir->size_bytes; - if (ph2_ir->is_pointer && eff != PTR_SIZE) - eff = PTR_SIZE; - if (eff == 1 || eff == 2 || eff == 4 || eff == 8) { - reg_mirror_valid[ph2_ir->src0] = true; - reg_mirror_slot[ph2_ir->src0] = ph2_ir->src1; - reg_mirror_size[ph2_ir->src0] = eff; - reg_mirror_sext[ph2_ir->src0] = eff < 8; - } - } - } - } - - /* When the only consumer of a comparison is the branch right behind it, the - * CMP's flags can drive Jcc directly: SETcc, MOVZX and TEST all go away, - * and the boolean never needs to exist. The branch ends the block, so a - * result that were live elsewhere would have been stored between the two - * instructions -- and then this test would not fire. - */ - int fuse_cc = branch_cc_for(ph2_ir->op); - if (fuse_cc && emit_next_ir && emit_next_ir->op == OP_branch && - emit_next_ir->src0 == ph2_ir->dest) { - emit_cmp(rs1, rs2); - fused_cc = fuse_cc; - fused_cc_pending = true; - return; - } - - /* "if (x & mask)" needs no destination register: TEST leaves exactly the - * flags AND would, and the branch reads nothing else. This drops the AND, - * the MOV that staged its destination, and the TEST the branch would - * otherwise emit -- three instructions down to one. Only when the masked - * value is dead afterwards, since TEST does not write it. - */ - if (ph2_ir->op == OP_bit_and && emit_next_ir && - emit_next_ir->op == OP_branch && emit_next_ir->src0 == ph2_ir->dest && - emit_ir_index >= 0 && reg_dead_after(emit_ir_index + 2, ph2_ir->dest)) { - if (src1_const_known) { - emit_rex(1, 0, rs1); - emit_byte(0xF7); /* TEST rs1, imm32 */ - emit_byte(modrm(MOD_DIRECT, 0, reg_low3(rs1))); - emit_dword(src1_const); - } else { - emit_rex(1, rs2, rs1); - emit_byte(0x85); /* TEST rs1, rs2 */ - emit_byte(modrm(MOD_DIRECT, reg_low3(rs2), reg_low3(rs1))); - } - fused_cc = 0x85; /* JNZ */ - fused_cc_pending = true; - return; - } - switch (ph2_ir->op) { - case OP_load_constant: { - /* MOV r64, imm32 (sign-extended). The immediate is in src0, not a - * register index. - */ - if (ph2_ir->dest >= 0 && ph2_ir->dest < REG_CNT) { - const_reg_valid[ph2_ir->dest] = true; - const_reg_val[ph2_ir->dest] = ph2_ir->src0; - - /* Nothing will read the register itself: every remaining use turns - * into a shift immediate, so the materialisation is dead. - */ - if (const_load_dead(emit_ir_index + 1, ph2_ir->dest, ph2_ir->src0)) - return; - } - emit_rex(1, -1, rd); - emit_byte(0xC7); - emit_byte(modrm(MOD_DIRECT, 0, reg_low3(rd))); - emit_dword(ph2_ir->src0); - return; - } - - case OP_assign: { - emit_mov_reg(rd, rs1); - return; - } - case OP_add: { /* A tracked literal becomes an immediate, which also makes the * instruction that materialised it dead. @@ -2039,6 +1807,20 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) emit_byte(modrm(MOD_DIRECT, 3, reg_low3(rd))); return; } + default: + break; + } +} + +/* Bitwise operations and shifts. */ +void emit_bitwise(ph2_ir_t *ph2_ir, + int rd, + int rs1, + int rs2, + bool src1_const_known, + int src1_const) +{ + switch (ph2_ir->op) { case OP_bit_and: case OP_bit_or: case OP_bit_xor: { @@ -2224,10 +2006,19 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) emit_byte(modrm(MOD_DIRECT, 3, reg_low3(rd))); return; } - case OP_eq: - case OP_neq: - case OP_lt: - case OP_leq: + default: + break; + } +} + +/* Comparisons, and the jumps and branches they feed. */ +void emit_compare_jump(ph2_ir_t *ph2_ir, int rd, int rs1, int rs2) +{ + switch (ph2_ir->op) { + case OP_eq: + case OP_neq: + case OP_lt: + case OP_leq: case OP_gt: case OP_geq: /* CMP rs1, rs2, then turn the flags into 0 or 1 in rd. The six @@ -2348,6 +2139,15 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) } return; } + default: + break; + } +} + +/* Calls, and the returns that unwind them. */ +void emit_call_return(ph2_ir_t *ph2_ir, int rs1) +{ + switch (ph2_ir->op) { case OP_call: /* CALL rel32 - find the target function and calculate relative offset */ @@ -2474,6 +2274,15 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) printf("Warning: OP_func_ret should not reach x64 backend\n"); return; + default: + break; + } +} + +/* Stack slots, addresses, conditional moves, loads and stores. */ +void emit_memory(ph2_ir_t *ph2_ir, int rd, int rs1) +{ + switch (ph2_ir->op) { case OP_allocat: { /* SUB rsp, size. * @@ -2609,6 +2418,15 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) } return; + default: + break; + } +} + +/* Indirect access through a pointer. */ +void emit_read_write(ph2_ir_t *ph2_ir, int rd, int rs1, int rs2) +{ + switch (ph2_ir->op) { case OP_read: /* Load *rs1 into rd. The element width lives in src1 (1, 2 or 4); * pointer-typed reads must move a full pointer instead. @@ -2752,6 +2570,15 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) emit_byte(modrm(MOD_DIRECT, 0, 7)); return; + default: + break; + } +} + +/* Globals, functions, and the data sections they live in. */ +void emit_global(ph2_ir_t *ph2_ir, int rd, int rs1) +{ + switch (ph2_ir->op) { case OP_load_func: case OP_global_load_func: /* Stage the callee address in R11 for the OP_indirect that follows. R11 @@ -2965,6 +2792,15 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) emit_dword(0); return; } + default: + break; + } +} + +/* Logical operators and width conversions. */ +void emit_logic_cast(ph2_ir_t *ph2_ir, int rd, int rs1) +{ + switch (ph2_ir->op) { case OP_log_not: { /* TEST rs1, rs1; SETE r11b; MOVZX rd, r11b. * @@ -3059,6 +2895,318 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) } return; + default: + break; + } +} + +void emit_ph2_ir(ph2_ir_t *ph2_ir) +{ + for (int i = 0; i < MAX_SKIP_IR; i++) { + if (skip_ir_index[i] >= 0 && skip_ir_index[i] == emit_ir_index) { + skip_ir_index[i] = -1; + return; + } + } + if (try_fold_mem_dest(ph2_ir)) + return; + if (try_fold_alu_to_slot(ph2_ir)) + return; + + int rd = map_ir_reg(ph2_ir->dest); + int rs1 = map_ir_reg(ph2_ir->src0); + if (src0_override >= 0 && src0_override_at == emit_ir_index) { + rs1 = src0_override; + src0_override = -1; + src0_override_at = -1; + } + int rs2 = map_ir_reg(ph2_ir->src1); + + /* Read the tracked value before invalidating, since dest may alias src1. */ + bool src1_const_known = false; + int src1_const = 0; + if (ph2_ir->src1 >= 0 && ph2_ir->src1 < REG_CNT && + const_reg_valid[ph2_ir->src1]) { + src1_const_known = true; + src1_const = const_reg_val[ph2_ir->src1]; + } + if (op_writes_dest(ph2_ir->op) && ph2_ir->dest >= 0 && + ph2_ir->dest < REG_CNT) { + const_reg_valid[ph2_ir->dest] = false; + shift_cache_kill(ph2_ir->dest); + } + + /* Anything that can touch a register this table does not name -- a call, or + * a division writing RAX and RDX -- invalidates all of it. This is the same + * set that drops frame mirrors below. + */ + if (!op_keeps_frame_mirrors(ph2_ir->op)) { + const_track_reset(); + shift_cache_reset(); + } + if (ph2_ir->op != OP_branch) + fused_cc_pending = false; + if (!branch_cc_for(ph2_ir->op)) { + cmp_mem_slot = -1; + cmp_imm_known = false; + } else if (src1_const_known) { + cmp_imm_known = true; + cmp_imm_val = src1_const; + } + + /* A value loaded only to be compared does not need a register: x86 takes + * the second compare operand from memory. Unlike arithmetic, nothing reuses + * this value afterwards, so folding it costs no later reload. + */ + if (ph2_ir->op == OP_load && emit_ir_index >= 0 && emit_next_ir && + branch_cc_for(emit_next_ir->op) && emit_next_ir->src1 == ph2_ir->dest && + emit_next_ir->src0 != ph2_ir->dest && + reg_dead_after(emit_ir_index + 2, ph2_ir->dest)) { + int w = load_width(ph2_ir); + if (w == 4 || w == 8) { + cmp_mem_slot = ph2_ir->src0; + cmp_mem_width = w; + return; + } + } + + if (!op_keeps_frame_mirrors(ph2_ir->op)) { + frame_mirror_reset(); + } else { + if (ph2_ir->op == OP_load && ph2_ir->dest >= 0 && + ph2_ir->dest < REG_CNT) { + int want = load_width(ph2_ir); + if (reg_mirror_valid[ph2_ir->dest] && + reg_mirror_slot[ph2_ir->dest] == ph2_ir->src0 && + reg_mirror_size[ph2_ir->dest] == want && + !reg_mirror_sext[ph2_ir->dest]) + return; /* the register still holds this slot */ + /* Some other register may already hold it. Copying between + * registers costs the same instruction but avoids waiting on the + * store-to-load forwarding this slot would otherwise require. + */ + for (int i = 0; i < REG_CNT; i++) { + if (!reg_mirror_valid[i] || + reg_mirror_slot[i] != ph2_ir->src0 || + reg_mirror_size[i] != want) + continue; + if (i == ph2_ir->dest && !reg_mirror_sext[i]) + continue; + int held = map_ir_reg(i); + + /* If this value exists only to be stored straight back out, let + * the store read the register that already holds it. Only sound + * when the register is bit-identical to what the load would + * produce, so a narrower mirror is excluded. Only when the + * consumer is the very next instruction: a later one could be + * skipped by another fold, and the copy would already be gone. + * And only when src0 is its one read of the register: the + * override redirects that read alone, so a second read of the + * same register -- src1, or a select's src2 -- would be left + * looking at whatever the load this skips was going to + * overwrite. + */ + if (!reg_mirror_sext[i] && src0_override < 0 && emit_next_ir && + op_src0_is_reg(emit_next_ir->op) && + emit_next_ir->src0 == ph2_ir->dest && + emit_next_ir->src1 != ph2_ir->dest && + !(op_src2_is_reg(emit_next_ir->op) && + emit_next_ir->src2 == ph2_ir->dest) && + reg_dead_after(emit_ir_index + 2, ph2_ir->dest)) { + src0_override = held; + src0_override_at = emit_ir_index + 1; + return; + } + emit_narrow_move(rd, held, want, reg_mirror_sext[i]); + reg_mirror_valid[ph2_ir->dest] = true; + reg_mirror_slot[ph2_ir->dest] = ph2_ir->src0; + reg_mirror_size[ph2_ir->dest] = want; + reg_mirror_sext[ph2_ir->dest] = false; + const_reg_valid[ph2_ir->dest] = false; + return; + } + } + + if (ph2_ir->dest >= 0 && ph2_ir->dest < REG_CNT) + reg_mirror_valid[ph2_ir->dest] = false; + + if (ph2_ir->op == OP_load && ph2_ir->dest >= 0 && + ph2_ir->dest < REG_CNT) { + reg_mirror_valid[ph2_ir->dest] = true; + reg_mirror_slot[ph2_ir->dest] = ph2_ir->src0; + reg_mirror_size[ph2_ir->dest] = load_width(ph2_ir); + reg_mirror_sext[ph2_ir->dest] = false; + } else if (ph2_ir->op == OP_store) { + /* Storing a register into the slot it already mirrors writes the + * bytes that are there. Coalescing a phi with its operand makes + * this common: the value's own write-back and the phi's copy become + * the same store. + */ + if (ph2_ir->src0 >= 0 && ph2_ir->src0 < REG_CNT && + reg_mirror_valid[ph2_ir->src0] && + reg_mirror_slot[ph2_ir->src0] == ph2_ir->src1) { + int keep = ph2_ir->size_bytes; + if (ph2_ir->is_pointer && keep != PTR_SIZE) + keep = PTR_SIZE; + if (reg_mirror_size[ph2_ir->src0] == keep) + return; + } + + /* The slot takes a new value, so any register mirroring it is now + * stale. + */ + for (int i = 0; i < REG_CNT; i++) { + if (reg_mirror_valid[i] && reg_mirror_slot[i] == ph2_ir->src1) + reg_mirror_valid[i] = false; + } + + /* The slot now holds the source register's low bytes, so that + * register mirrors it. A full-width store leaves the two + * bit-identical; a narrower one does not, and a later load of the + * slot sign-extends what was written -- which the sext flag + * records, so the reload becomes a MOVSX rather than a copy. + */ + if (ph2_ir->src0 >= 0 && ph2_ir->src0 < REG_CNT) { + int eff = ph2_ir->size_bytes; + if (ph2_ir->is_pointer && eff != PTR_SIZE) + eff = PTR_SIZE; + if (eff == 1 || eff == 2 || eff == 4 || eff == 8) { + reg_mirror_valid[ph2_ir->src0] = true; + reg_mirror_slot[ph2_ir->src0] = ph2_ir->src1; + reg_mirror_size[ph2_ir->src0] = eff; + reg_mirror_sext[ph2_ir->src0] = eff < 8; + } + } + } + } + + /* When the only consumer of a comparison is the branch right behind it, the + * CMP's flags can drive Jcc directly: SETcc, MOVZX and TEST all go away, + * and the boolean never needs to exist. The branch ends the block, so a + * result that were live elsewhere would have been stored between the two + * instructions -- and then this test would not fire. + */ + int fuse_cc = branch_cc_for(ph2_ir->op); + if (fuse_cc && emit_next_ir && emit_next_ir->op == OP_branch && + emit_next_ir->src0 == ph2_ir->dest) { + emit_cmp(rs1, rs2); + fused_cc = fuse_cc; + fused_cc_pending = true; + return; + } + + /* "if (x & mask)" needs no destination register: TEST leaves exactly the + * flags AND would, and the branch reads nothing else. This drops the AND, + * the MOV that staged its destination, and the TEST the branch would + * otherwise emit -- three instructions down to one. Only when the masked + * value is dead afterwards, since TEST does not write it. + */ + if (ph2_ir->op == OP_bit_and && emit_next_ir && + emit_next_ir->op == OP_branch && emit_next_ir->src0 == ph2_ir->dest && + emit_ir_index >= 0 && reg_dead_after(emit_ir_index + 2, ph2_ir->dest)) { + if (src1_const_known) { + emit_rex(1, 0, rs1); + emit_byte(0xF7); /* TEST rs1, imm32 */ + emit_byte(modrm(MOD_DIRECT, 0, reg_low3(rs1))); + emit_dword(src1_const); + } else { + emit_rex(1, rs2, rs1); + emit_byte(0x85); /* TEST rs1, rs2 */ + emit_byte(modrm(MOD_DIRECT, reg_low3(rs2), reg_low3(rs1))); + } + fused_cc = 0x85; /* JNZ */ + fused_cc_pending = true; + return; + } + + switch (ph2_ir->op) { + case OP_load_constant: { + /* MOV r64, imm32 (sign-extended). The immediate is in src0, not a + * register index. + */ + if (ph2_ir->dest >= 0 && ph2_ir->dest < REG_CNT) { + const_reg_valid[ph2_ir->dest] = true; + const_reg_val[ph2_ir->dest] = ph2_ir->src0; + + /* Nothing will read the register itself: every remaining use turns + * into a shift immediate, so the materialisation is dead. + */ + if (const_load_dead(emit_ir_index + 1, ph2_ir->dest, ph2_ir->src0)) + return; + } + emit_rex(1, -1, rd); + emit_byte(0xC7); + emit_byte(modrm(MOD_DIRECT, 0, reg_low3(rd))); + emit_dword(ph2_ir->src0); + return; + } + + case OP_assign: { + emit_mov_reg(rd, rs1); + return; + } + + case OP_add: + case OP_sub: + case OP_mul: + case OP_div: + case OP_mod: + emit_arith(ph2_ir, rd, rs1, rs2, src1_const_known, src1_const); + break; + case OP_bit_and: + case OP_bit_or: + case OP_bit_xor: + case OP_bit_not: + case OP_negate: + case OP_lshift: + case OP_rshift: + emit_bitwise(ph2_ir, rd, rs1, rs2, src1_const_known, src1_const); + break; + case OP_eq: + case OP_neq: + case OP_lt: + case OP_leq: + case OP_gt: + case OP_geq: + case OP_jump: + case OP_branch: + emit_compare_jump(ph2_ir, rd, rs1, rs2); + break; + case OP_call: + case OP_return: + case OP_func_ret: + emit_call_return(ph2_ir, rs1); + break; + case OP_allocat: + case OP_address_of: + case OP_cmov: + case OP_load: + case OP_store: + emit_memory(ph2_ir, rd, rs1); + break; + case OP_read: + case OP_write: + case OP_indirect: + emit_read_write(ph2_ir, rd, rs1, rs2); + break; + case OP_load_func: + case OP_global_load_func: + case OP_address_of_func: + case OP_global_address_of: + case OP_global_load: + case OP_global_store: + case OP_load_data_address: + case OP_load_rodata_address: + emit_global(ph2_ir, rd, rs1); + break; + case OP_log_not: + case OP_log_and: + case OP_log_or: + case OP_trunc: + case OP_sign_ext: + case OP_cast: + emit_logic_cast(ph2_ir, rd, rs1); + break; case OP_define: /* Update the function's actual offset to current code position */ {