From 24b0f94aeffdc5fb32fff0132683595903bb7726 Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Thu, 22 Oct 2026 00:07:08 +0800 Subject: [PATCH 1/5] Remove unused opcode and type helpers op_is_integer_binary(), op_is_scalar_unary() and op_is_scalar_cast() had no callers, so their property bits go with them. The same holds for vir_type_is_i32_or_i64_or_pointer(), vir_type_is_pointer_or_i64(), the AArch64 SXTW extend constant, the name of the ARM opcode enum, and a variadic callback the indirect-call fixture declared but never called. --- src/arm.c | 4 +-- src/arm64.c | 3 +-- src/defs.h | 3 --- src/vir-lower.c | 40 ++++++++++++------------------ src/vir.h | 8 ------ tests/vir-frontend-indirect-call.c | 6 ----- 6 files changed, 19 insertions(+), 45 deletions(-) diff --git a/src/arm.c b/src/arm.c index e9957549..54bde1c9 100644 --- a/src/arm.c +++ b/src/arm.c @@ -29,7 +29,7 @@ #include "defs.h" /* opcode */ -typedef enum { +enum { arm_and = 0, arm_eor = 1, arm_sub = 2, @@ -45,7 +45,7 @@ typedef enum { arm_mov = 13, arm_mvn = 15, arm_stmdb = 16 -} arm_op_t; +}; /* Condition code Reference: * https://community.arm.com/developer/ip-products/processors/b/processors-ip-blog/posts/condition-codes-1-condition-flags-and-codes diff --git a/src/arm64.c b/src/arm64.c index 00a862df..035ae78e 100644 --- a/src/arm64.c +++ b/src/arm64.c @@ -122,9 +122,8 @@ int a64_cmp_reg_insn(bool sf, int rn, int rm) } /* The extended-register forms of 64-bit ADD and SUB. UXTX is the plain form - * that may name SP; SXTW widens a W register operand as it is added. + * that may name SP. */ -#define A64_EXT_SXTW 6 #define A64_EXT_UXTX 3 int a64_add_ext_insn(int rd, int rn, int rm, int option) diff --git a/src/defs.h b/src/defs.h index 21d8b94e..4d10bb6f 100644 --- a/src/defs.h +++ b/src/defs.h @@ -1248,10 +1248,7 @@ typedef struct { bool op_is_binary_alu(opcode_t op); bool op_writes_dest(opcode_t op); -bool op_is_integer_binary(opcode_t op); bool op_is_comparison(opcode_t op); -bool op_is_scalar_unary(opcode_t op); -bool op_is_scalar_cast(opcode_t op); const char *opcode_mnemonic(opcode_t op); basic_block_t *bb_sole_pred(const basic_block_t *bb); diff --git a/src/vir-lower.c b/src/vir-lower.c index 85bdb67d..6f84f326 100644 --- a/src/vir-lower.c +++ b/src/vir-lower.c @@ -168,10 +168,7 @@ enum { OP_COPY_FORWARD_SAFE = 16, OP_FUSIBLE = 32, OP_BINARY_ALU = 64, - OP_INTEGER_BINARY = 128, OP_COMPARISON = 256, - OP_SCALAR_UNARY = 512, - OP_SCALAR_CAST = 1024, }; #define OP_PROPERTY(op, flags) [op] = flags, @@ -181,7 +178,6 @@ enum { #define OP_COPY_FUSE(flags) ((flags) | OP_COPY_FORWARD_SAFE | OP_FUSIBLE) #define OP_BINARY (OP_SRC0_REG | OP_SRC1_REG | OP_BINARY_ALU) #define OP_UNARY OP_SRC0_REG -#define OP_INTEGER (OP_BINARY | OP_INTEGER_BINARY) /* Each macro expands to an initializer and comma. */ /* clang-format off */ static const unsigned short op_properties[] = { @@ -210,38 +206,37 @@ static const unsigned short op_properties[] = { OP_PROPERTY(OP_global_store, OP_COPY(OP_SRC0_REG)) OP_PROPERTY(OP_read, OP_DEST(OP_COPY(OP_SRC0_REG))) OP_PROPERTY(OP_write, OP_COPY(OP_SRC0_REG | OP_SRC1_REG)) - OP_PROPERTY(OP_add, OP_DEST(OP_COPY_FUSE(OP_INTEGER))) - OP_PROPERTY(OP_sub, OP_DEST(OP_COPY_FUSE(OP_INTEGER))) - OP_PROPERTY(OP_mul, OP_DEST(OP_COPY_FUSE(OP_INTEGER))) - OP_PROPERTY(OP_div, OP_DEST(OP_FUSE(OP_INTEGER))) - OP_PROPERTY(OP_mod, OP_DEST(OP_FUSE(OP_INTEGER))) + OP_PROPERTY(OP_add, OP_DEST(OP_COPY_FUSE(OP_BINARY))) + OP_PROPERTY(OP_sub, OP_DEST(OP_COPY_FUSE(OP_BINARY))) + OP_PROPERTY(OP_mul, OP_DEST(OP_COPY_FUSE(OP_BINARY))) + OP_PROPERTY(OP_div, OP_DEST(OP_FUSE(OP_BINARY))) + OP_PROPERTY(OP_mod, OP_DEST(OP_FUSE(OP_BINARY))) OP_PROPERTY(OP_ternary, OP_DEST(OP_SRC0_REG)) - OP_PROPERTY(OP_lshift, OP_DEST(OP_COPY_FUSE(OP_INTEGER))) - OP_PROPERTY(OP_rshift, OP_DEST(OP_COPY_FUSE(OP_INTEGER))) + OP_PROPERTY(OP_lshift, OP_DEST(OP_COPY_FUSE(OP_BINARY))) + OP_PROPERTY(OP_rshift, OP_DEST(OP_COPY_FUSE(OP_BINARY))) OP_PROPERTY(OP_log_and, OP_DEST(OP_FUSE(OP_UNARY))) OP_PROPERTY(OP_log_or, OP_DEST(OP_FUSE(OP_UNARY))) - OP_PROPERTY(OP_log_not, OP_DEST(OP_COPY_FUSE(OP_UNARY | OP_SCALAR_UNARY))) + OP_PROPERTY(OP_log_not, OP_DEST(OP_COPY_FUSE(OP_UNARY))) OP_PROPERTY(OP_eq, OP_DEST(OP_COPY_FUSE(OP_BINARY | OP_COMPARISON))) OP_PROPERTY(OP_neq, OP_DEST(OP_COPY_FUSE(OP_BINARY | OP_COMPARISON))) OP_PROPERTY(OP_lt, OP_DEST(OP_COPY_FUSE(OP_BINARY | OP_COMPARISON))) OP_PROPERTY(OP_leq, OP_DEST(OP_COPY_FUSE(OP_BINARY | OP_COMPARISON))) OP_PROPERTY(OP_gt, OP_DEST(OP_COPY_FUSE(OP_BINARY | OP_COMPARISON))) OP_PROPERTY(OP_geq, OP_DEST(OP_COPY_FUSE(OP_BINARY | OP_COMPARISON))) - OP_PROPERTY(OP_bit_or, OP_DEST(OP_COPY_FUSE(OP_INTEGER))) - OP_PROPERTY(OP_bit_and, OP_DEST(OP_COPY_FUSE(OP_INTEGER))) - OP_PROPERTY(OP_bit_xor, OP_DEST(OP_COPY_FUSE(OP_INTEGER))) - OP_PROPERTY(OP_bit_not, OP_DEST(OP_COPY_FUSE(OP_UNARY | OP_SCALAR_UNARY))) - OP_PROPERTY(OP_negate, OP_DEST(OP_COPY_FUSE(OP_UNARY | OP_SCALAR_UNARY))) - OP_PROPERTY(OP_trunc, OP_DEST(OP_COPY(OP_SRC0_REG | OP_SCALAR_CAST))) - OP_PROPERTY(OP_sign_ext, OP_DEST(OP_COPY(OP_SRC0_REG | OP_SCALAR_CAST))) - OP_PROPERTY(OP_cast, OP_DEST(OP_SRC0_REG | OP_SCALAR_CAST)) + OP_PROPERTY(OP_bit_or, OP_DEST(OP_COPY_FUSE(OP_BINARY))) + OP_PROPERTY(OP_bit_and, OP_DEST(OP_COPY_FUSE(OP_BINARY))) + OP_PROPERTY(OP_bit_xor, OP_DEST(OP_COPY_FUSE(OP_BINARY))) + OP_PROPERTY(OP_bit_not, OP_DEST(OP_COPY_FUSE(OP_UNARY))) + OP_PROPERTY(OP_negate, OP_DEST(OP_COPY_FUSE(OP_UNARY))) + OP_PROPERTY(OP_trunc, OP_DEST(OP_COPY(OP_SRC0_REG))) + OP_PROPERTY(OP_sign_ext, OP_DEST(OP_COPY(OP_SRC0_REG))) + OP_PROPERTY(OP_cast, OP_DEST(OP_SRC0_REG)) }; /* clang-format on */ #undef OP_COPY #undef OP_FUSE #undef OP_COPY_FUSE #undef OP_BINARY -#undef OP_INTEGER #undef OP_UNARY #undef OP_DEST #undef OP_PROPERTY @@ -262,10 +257,7 @@ static unsigned int op_property(opcode_t op, unsigned int property) } OP_QUERY(op_writes_dest, OP_WRITES_DEST) OP_QUERY(op_is_binary_alu, OP_BINARY_ALU) -OP_QUERY(op_is_integer_binary, OP_INTEGER_BINARY) OP_QUERY(op_is_comparison, OP_COMPARISON) -OP_QUERY(op_is_scalar_unary, OP_SCALAR_UNARY) -OP_QUERY(op_is_scalar_cast, OP_SCALAR_CAST) OP_QUERY(op_src0_is_reg, OP_SRC0_REG) OP_QUERY(op_src1_is_reg, OP_SRC1_REG) #undef OP_QUERY diff --git a/src/vir.h b/src/vir.h index 64a5a5de..2cba43cf 100644 --- a/src/vir.h +++ b/src/vir.h @@ -344,14 +344,6 @@ static inline bool vir_type_is_i32_or_i64(vir_type_t type) { return type == VIR_TYPE_I32 || type == VIR_TYPE_I64; } -static inline bool vir_type_is_i32_or_i64_or_pointer(vir_type_t type) -{ - return vir_type_is_i32_or_i64(type) || type == VIR_TYPE_PTR; -} -static inline bool vir_type_is_pointer_or_i64(vir_type_t type) -{ - return type == VIR_TYPE_PTR || type == VIR_TYPE_I64; -} static inline bool vir_opcode_is_commutative(vir_opcode_t opcode) { return opcode == VIR_OP_ADD || opcode == VIR_OP_MUL || diff --git a/tests/vir-frontend-indirect-call.c b/tests/vir-frontend-indirect-call.c index 1a8ca010..82230188 100644 --- a/tests/vir-frontend-indirect-call.c +++ b/tests/vir-frontend-indirect-call.c @@ -10,7 +10,6 @@ typedef int (*nullary_callback_t)(void); typedef int *(*int_pointer_result_callback_t)(void); typedef int (*pointer_callback_t)(void *); typedef long long (*wide_callback_t)(long long, long long); -typedef int (*variadic_callback_t)(int, ...); static int check_values(signed char a, word_t b, _Bool c) { @@ -110,11 +109,6 @@ static int invoke_pointer_result(int_pointer_result_callback_t callback) return callback()[1]; } -static int invoke_variadic(variadic_callback_t callback) -{ - return callback(1, 2); -} - static int check_pointer(void *pointer) { return pointer != 0; From bf3f5e332b0493515d45d4b3f9bac3b6fe41bf0d Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Thu, 22 Oct 2026 00:53:23 +0800 Subject: [PATCH 2/5] Report failed reads and writes of compiled files A source file was sized with fseek() and ftell() and then read in one request. A pipe or /dev/stdin has no such length, so its text was lost, and a read that failed was taken as a short file. Read instead into a growing buffer until read() reports end of input, and report a failed read as "cannot read source file" rather than compiling whatever arrived. An #include of a file that opens but cannot be read is reported at the directive, and a quoted #include of an absolute path is no longer joined to the including file's directory. On the output side, a short or failed write of the ELF image was ignored, and so was the result of fclose(), where a buffered runtime may only then discover that the device is full. Either now exits with a diagnostic, and the embedded libc's fclose() returns -1 when close() fails instead of always 0. The driver streams a program through /dev/stdin, directly and through #include, at sizes around the reader's first refill and a later growth; passes a directory to check the read diagnostic; and writes a small dynamic and a large static image to /dev/full. --- lib/c.c | 3 +- src/elf.c | 10 +++-- src/lexer.c | 99 +++++++++++++++++++++++++++++----------------- src/preprocessor.c | 9 +++-- tests/driver.sh | 98 +++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 174 insertions(+), 45 deletions(-) diff --git a/lib/c.c b/lib/c.c index 12119669..a94a21fe 100644 --- a/lib/c.c +++ b/lib/c.c @@ -732,7 +732,8 @@ FILE *fopen(const char *filename, const char *mode) int fclose(FILE *stream) { - __syscall(__syscall_close, stream); + if (__syscall(__syscall_close, stream) < 0) + return -1; return 0; } diff --git a/src/elf.c b/src/elf.c index 297ece69..68240d54 100644 --- a/src/elf.c +++ b/src/elf.c @@ -1040,8 +1040,8 @@ void elf_postprocess(void) #ifdef HOST_BUFFERED_STDIO void elf_write_all(FILE *fp, const char *buf, int len) { - if (len > 0) - fwrite(buf, 1, len, fp); + if (len > 0 && (int) fwrite(buf, 1, len, fp) != len) + usage_error("Unable to write output file"); } #else void elf_write_all(FILE *fp, const char *buf, int len) @@ -1051,7 +1051,7 @@ void elf_write_all(FILE *fp, const char *buf, int len) while (off < len) { int n = __syscall(__syscall_write, fp, buf + off, len - off); if (n <= 0) - return; + usage_error("Unable to write output file"); off += n; } } @@ -1139,7 +1139,9 @@ void elf_generate(const char *outfile) elf_write_all(fp, elf_shstrtab->elements, elf_shstrtab->size); elf_write_all(fp, elf_section_header->elements, elf_section_header->size); #endif - fclose(fp); + /* Buffered runtimes may defer the actual write until fclose(). */ + if (fclose(fp) != 0) + usage_error("Unable to close output file"); /* A generated ELF is meant to be runnable directly, but the mode it gets * depends on which libc opened it, not on how the output is linked: the diff --git a/src/lexer.c b/src/lexer.c index 553a246c..43570d3d 100644 --- a/src/lexer.c +++ b/src/lexer.c @@ -273,59 +273,90 @@ char read_char(strbuf_t *buf) return source_char_at(buf, buf->size); } -/* Fill @dst with @len bytes of @f, returning how many arrived. - * - * The whole file is wanted and its size is already known, so it is asked for in - * one piece. Reading it a line at a time instead cost a library call and a - * second copy for every line of every source file -- and shecc's own libc has - * no buffer behind fgets(), so each of those lines was a read(2) as well. +/* Read up to @len bytes, distinguishing an I/O error from end of input. Static + * self-hosted builds use read(2) directly to avoid byte-at-a-time libc I/O. */ #ifdef HOST_BUFFERED_STDIO -int file_read_all(FILE *f, char *dst, int len) +/* The dynamically linked compiler uses the host's stream error indicator; the + * embedded, unbuffered libc has no such state. Host builds take the declaration + * from , where ferror may be a macro. + */ +#ifdef __SHECC__ +int ferror(FILE *stream); +#endif + +int file_read_chunk(FILE *f, char *dst, int len) { - if (len <= 0) - return 0; - return fread(dst, 1, len, f); + int n = fread(dst, 1, len, f); + return ferror(f) ? -1 : n; } #else -int file_read_all(FILE *f, char *dst, int len) +int file_read_chunk(FILE *f, char *dst, int len) { - int got = 0; - - while (got < len) { - int n = __syscall(__syscall_read, f, dst + got, len - got); - if (n <= 0) - return got; - got += n; - } - return got; + return __syscall(__syscall_read, f, dst, len); } #endif -/* @filename's contents, or NULL when it cannot be opened. */ +/* Reports that @filename could not be opened or read: a usage error. */ +__noreturn void source_file_error(const char *action, const char *filename) +{ + char msg[MAX_LINE_LEN]; + + snprintf(msg, MAX_LINE_LEN, "cannot %s source file '%s'", action, filename); + usage_error(msg); +} + +#define SOURCE_BUF_INIT 16384 + +/* Set when the last try_read_file() opened its file but could not read it, so a + * caller can tell "cannot read" from "cannot open". + */ +bool source_read_failed; + +/* @filename's contents, or NULL when it cannot be opened or read. */ strbuf_t *try_read_file(const char *filename) { FILE *f = fopen(filename, "rb"); strbuf_t *src; + source_read_failed = false; if (!f) return NULL; - fseek(f, 0, SEEK_END); - int len = ftell(f); - src = strbuf_create(len + 1); - fseek(f, 0, SEEK_SET); + src = strbuf_create(SOURCE_BUF_INIT); + if (!src) + fatal("Unable to allocate source buffer"); - src->size = file_read_all(f, src->elements, len); + /* Pipes and procfs files do not have a useful seek-derived length. Read + * through EOF into whatever room the buffer has, keeping one byte free for + * the lexer's NUL sentinel. + */ + while (true) { + if (!strbuf_extend(src, 1)) + fatal("Unable to grow source buffer"); + int n = file_read_chunk(f, src->elements + src->size, + src->capacity - src->size - 1); + if (n < 0) { + fclose(f); + strbuf_free(src); + source_read_failed = true; + return NULL; + } + if (!n) + break; + src->size += n; + } fclose(f); - src->elements[len] = '\0'; + src->elements[src->size] = '\0'; + /* The lexer treats the byte at capacity - 1 as the end of input. */ + src->capacity = src->size + 1; return src; } /* @filename's contents, read once and kept for every later request; NULL when - * it cannot be opened, so that a caller with a better place to report the - * mistake can. + * it cannot be opened or read, so that a caller with a better place to report + * the mistake can. */ strbuf_t *try_get_file_buf(char *filename) { @@ -343,14 +374,8 @@ strbuf_t *get_file_buf(char *filename) { strbuf_t *buf = try_get_file_buf(filename); - /* A file that cannot be opened is a mistake in how the compiler was - * invoked, not a broken invariant, so it exits rather than abort()ing. - */ - if (!buf) { - printf("[Error]: cannot open source file '%s'\n", filename); - fflush(NULL); - exit(1); - } + if (!buf) + source_file_error(source_read_failed ? "read" : "open", filename); return buf; } diff --git a/src/preprocessor.c b/src/preprocessor.c index 9ccaa1e0..53a2528b 100644 --- a/src/preprocessor.c +++ b/src/preprocessor.c @@ -2498,8 +2498,10 @@ static token_t *pp_preprocess_body(token_t *tk, preprocess_ctx_t *ctx) continue; } - if (!angle_header) { - /* Quoted headers are relative to the physical source path. */ + if (!angle_header && inclusion_path[0] != '/') { + /* Relative quoted headers use the physical source path; + * absolute names already identify their own location. + */ char path[MAX_LINE_LEN]; const char *file = include_tk->location.file->physical; int c = strlen(file) - 1; @@ -2528,7 +2530,8 @@ static token_t *pp_preprocess_body(token_t *tk, preprocess_ctx_t *ctx) * lexer, under the name the lexer will ask for. */ if (!try_get_file_buf(normalize_filename(inclusion_path))) - error_at("Included file cannot be found", + error_at(source_read_failed ? "Included file cannot be read" + : "Included file cannot be found", &include_tk->location); tk = pp_lex_expect_token(tk, T_newline, true); diff --git a/tests/driver.sh b/tests/driver.sh index 4bb313aa..480c0d6b 100755 --- a/tests/driver.sh +++ b/tests/driver.sh @@ -753,6 +753,104 @@ try_file 0 'F(10) = 55' "$TESTS_DIR/fib.c" try_file 0 $'1\nHello World' "$TESTS_DIR/hello.c" try_file 0 '' "$TESTS_DIR/strength-reduce.c" +# Count one check that does not fit the try helpers. Usage: +# record_check