diff --git a/Makefile b/Makefile index 94c0ea7a..86031fe2 100644 --- a/Makefile +++ b/Makefile @@ -190,7 +190,7 @@ config: check-vir-opt-baseline check: check-stage0 check-stage2 check-abi-stage0 check-abi-stage2 \ - check-vir-core check-vir-frontend-stage0 check-vir-frontend-stage2 check-vir-fuzz check-vir-stage0 check-vir-stage2 \ + check-vir-core check-vir-essential check-vir-frontend-stage0 check-vir-frontend-stage2 check-vir-fuzz check-vir-stage0 check-vir-stage2 \ check-vir-multi-input-stage0 check-vir-multi-input-stage2 \ check-vir-baseline check-vir-opt-baseline \ check-vir-corpus-stage0 check-fuzz-stage0 check-vir-frontend-policy-stage0 @@ -375,6 +375,11 @@ check-vir-core: tests/vir-core.c src/vir.c src/vir.h $(CC) $(CFLAGS) -Isrc tests/vir-core.c src/vir.c -o $(OUT)/vir-core $(OUT)/vir-core +.PHONY: check-vir-essential +check-vir-essential: tests/vir-essential-core.c src/vir.c src/vir.h + $(CC) $(CFLAGS) -Isrc tests/vir-essential-core.c src/vir.c -o $(OUT)/vir-essential-core + $(OUT)/vir-essential-core + check-vir-frontend-stage0: $(OUT)/$(STAGE0) tests/vir-frontend.sh tests/vir-frontend-workloads.txt $(VIR_FRONTEND_FIXTURES) TARGET_EXEC="$(TARGET_EXEC)" bash tests/vir-frontend.sh 0 @@ -391,14 +396,16 @@ check-vir-fuzz: tests/vir-fuzz.c src/vir.c src/vir.h $(CC) $(CFLAGS) -Isrc tests/vir-fuzz.c src/vir.c -o $(OUT)/vir-fuzz $(OUT)/vir-fuzz -check-vir-sanitizer: tests/vir-core.c tests/vir-fuzz.c src/vir.c src/vir.h +check-vir-sanitizer: tests/vir-core.c tests/vir-essential-core.c tests/vir-fuzz.c src/vir.c src/vir.h $(CC) $(CFLAGS) $(SAN_CFLAGS) -Isrc tests/vir-core.c src/vir.c -o $(OUT)/vir-core-sanitize ASAN_OPTIONS=detect_leaks=1 $(OUT)/vir-core-sanitize $(CC) $(CFLAGS) $(SAN_CFLAGS) -Isrc tests/vir-fuzz.c src/vir.c -o $(OUT)/vir-fuzz-sanitize ASAN_OPTIONS=detect_leaks=1 $(OUT)/vir-fuzz-sanitize + $(CC) $(CFLAGS) $(SAN_CFLAGS) -Isrc tests/vir-essential-core.c src/vir.c -o $(OUT)/vir-essential-sanitize + ASAN_OPTIONS=detect_leaks=1 $(OUT)/vir-essential-sanitize .PHONY: check-vir -check-vir: check-vir-core check-vir-frontend-stage0 \ +check-vir: check-vir-core check-vir-essential check-vir-frontend-stage0 \ check-vir-frontend-stage2 check-vir-fuzz check-vir-stage0 check-vir-stage2 \ check-vir-frontend-policy-stage0 \ check-vir-frontend-policy-stage2 \ diff --git a/docs/vir.md b/docs/vir.md index f619d08e..32f63ab5 100644 --- a/docs/vir.md +++ b/docs/vir.md @@ -197,6 +197,15 @@ merges, effects, and ABI information are represented alongside those values. typed operands. Construction may fold constants, remove identities, and order commutative operands when their semantics permit it. Common unary and binary operands stay inline; unusual arities use side storage. +- Typed i32/i64 bitwise builders normalize idempotence, complements, + absorption, repeated AND/OR operands, and XOR with an all-one mask. Unary + builders remove double negation and double complement. These construction + folds also apply at O0; raw `vir_binary()` retains its explicit shape. + At O1/O2, `vir_normalize()` reapplies the rules after SSA parameter + simplification and CSE/GVN, which can reveal equal operands after construction. + When normalization changes the graph, SCCP runs again to propagate newly + exposed constants and simplify dependent branches. + Folding a pure result leaves its operands' ordered effects in the graph. - Blocks and edges carry control. SSA merges use block parameters and edge arguments. Edge mutation must preserve argument indices, types, and reverse uses, including duplicate successors and loop backedges. @@ -254,22 +263,79 @@ onto one computed earlier, so it leaves a loop-carried chain. Loop-invariant code motion (LICM) moves eligible pure computations out of loops, and dead-code elimination (DCE) removes unused work. -1. Remove unreachable blocks. +1. Inline small leaf callees defined earlier, then remove unreachable blocks. 2. SCCP and CFG simplification. 3. Block-parameter simplification: a parameter that receives one value on every edge, or itself on a back edge, is replaced by that value, so a loop-invariant input does not look loop-defined to CSE, GVN and LICM. 4. Local CSE. 5. GVN. -6. Reassociation, LICM and pointer-induction strength reduction. -7. Local CSE and DCE, including ordinary overwritten-store elimination. -8. CFG simplification, then a final local CSE and DCE over the simplified graph. -9. Full graph verification before dumps and machine lowering. +6. Integer, bitwise and unary normalization, followed by SCCP when normalization changes + the graph. +7. Reassociation and LICM, followed by affine-loop summarization. If loops + disappear, simplify their parameters and propagate constants again. +8. Pointer-induction strength reduction, local CSE, and DCE, including ordinary + overwritten-store elimination, then reduction unrolling. +9. CFG simplification, then a final local CSE and DCE over the simplified graph. +10. Full graph verification before dumps and machine lowering. +11. Eliminate eligible direct/additive tail recursion, rerun the pipeline when + changed, then compact the graph. | Level | Scheduled transformations | | --- | --- | | O0 | Construction folds, unreachable-block removal, and mandatory verification; optional optimization passes return without transforming the graph. | -| O2 (default) | SCCP, CFG simplification, block-parameter simplification, local CSE, GVN, reassociation, LICM, strength reduction, and DCE. | +| O2 (default) | Leaf inlining, SCCP, CFG simplification, block-parameter simplification, local CSE, GVN, integer/bitwise/unary normalization, reassociation, LICM, affine-loop summarization, tail-recursion elimination, strength reduction, reduction unrolling, and DCE. | + +Integer normalization lowers multiplication by a power of two to a shift, and +unsigned division/remainder to shifts/masks. Positive power-of-two signed +quotients add a sign-dependent bias before shifting to preserve truncation +toward zero; signed remainders use `x - quotient * divisor`. For nonconstant +dividends, zero and negative signed divisors stay explicit. The signed minimum +divided by minus one retains its explicit operation as well. + +Reduction unrolling doubles the step of a counted pointer loop whose body only +loads through the pointer and adds the value into an i32/i64 accumulator, +summing odd elements into a second accumulator that is added to the first after +the loop. The pointer's start and end must be constant offsets from one base an +even number of steps apart, so no remainder iteration is needed; the +accumulator may be read only after the loop. Splitting the add chain halves the +loop-carried dependency, which bounds such loops on wide cores. + +Affine-loop summarization recognizes two word-sized header parameters: an +induction `i += D` and a modular recurrence `a = A*a + B*i + C`. The initial +index and bound must be constant and the controlling `<` test must be signed +or unsigned with a proven positive, nonwrapping step. A single-entry linear +body must have no memory/call effects, trapping operations, or escaping body +values. Repeated composition by squaring computes the exact final recurrence +in logarithmic compile time, even when the accumulator wraps. Its initial +value may be a runtime argument. Zero-trip, negative signed index, dynamic-bound, +nonlinear, and potentially wrapping control loops are left intact. + +Inlining runs first at O2, before SCCP, on each function as its parsing ends. A +direct call to a function defined earlier is replaced by a copy of that +function's already optimized graph: the call's block is split at the call, its +end jumps to the cloned entry with the arguments, and every cloned return jumps +to a continuation block whose parameter carries the result, so multiple returns +need no extra phi. The callee must be a leaf without stack objects, in at most +16 blocks, whose parameter and result types match the call exactly; variadic, +aggregate-returning, address-taken and recursive callees stay calls. A static +callee of up to 40 values is copied. An extern callee is copied only when it +holds at most 6 values, or 16 at a call inside a loop of the caller. Any callee +whose calls are all inlined leaves the image, since only functions reachable +from `main` are emitted when there is one; linkage serves as a proxy for how +many callers keep a copy anyway, and on a self-compile both a uniform limit and +dropping the static one made the generated compiler larger and slower. One +caller absorbs at most 400 cloned values. A call to a function defined later is +not a candidate, which is how a test keeps a call: declare the callee first and +define it after its callers. + +Tail-recursion elimination accepts functions with i32/i64 parameters and returns, +no stack objects, no trapping operations, and no effects other than direct self +calls. Both `return self(args)` and `return self(args) + value` become back edges; +an SSA accumulator preserves addition modulo the result width. Every ordinary +return adds the accumulator, and every recursive edge passes all argument +values together, preserving parameter permutations. Narrow and variadic return +paths and calls with additional result uses remain unchanged. Pass-local tables are temporary. O0 does not allocate the optional CSE, GVN, loop, or optimization worklists. Mandatory verification still checks dominance, @@ -280,6 +346,7 @@ types, use-def links, edges, and ordered effects. | Command | Coverage | | --- | --- | | `make check-vir-core` | Builders, core invariants, and transformations. | +| `make check-vir-essential` | Integer strength reduction, affine recurrences, tail recursion, and pure leaf inlining. | | `make check-vir-lower` | Shared allocation and lowering invariants, plus an executable smoke test for the configured target. | | `make check-vir` | Core, frontend, optimization policy, self-hosted VIR, fuzzing, and sanitizer gates. | | `make check` | Full configured-target stage 0/2 behavior, ABI, and VIR checks. | @@ -300,6 +367,17 @@ verifier, and stage-1/stage-2 bootstrap identity. The AArch64 native harness adds its default dynamic QEMU sysroot only when the configured runner has not already supplied `-L`. +### Reference-driven bitwise normalization + +The construction rules follow the width-aware folding approach in +`ir_fold.h` of [IR](https://github.com/dstogov/ir) and +`src/mim/plug/core/normalizers.cpp` of [MimIR](https://github.com/mimir/mimir). +IR commit `62214815` fixes a wrong-operand return in nested MAX folding; the +regressions here likewise distinguish retaining a nested AND/OR from returning +its absorbed leaf. Mimir commit `6a1694301` ("more idx folding") illustrates why +integer-domain restrictions matter: these rules apply only to shecc's i32/i64 +word domains, with all-one constants masked to the result width. + ## Implementation map | Concern | Location | diff --git a/src/defs.h b/src/defs.h index 9bd02030..21d8b94e 100644 --- a/src/defs.h +++ b/src/defs.h @@ -363,18 +363,25 @@ typedef enum { T_hashhash } token_kind_t; +/* The file a token came from. One record is shared by every token of a file, + * and #line points the tokens after it at a new one, so a location carries one + * pointer for both names. + */ +typedef struct { + /* Immutable physical path used for quoted-include lookup. #line changes the + * logical name only, which diagnostics and __FILE__ report. + */ + char *physical; + char *logical; +} source_file_t; + /* Source location tracking for better error reporting */ typedef struct { int pos; /* raw source file position */ int len; /* length of token */ int line; int column; - - /* Immutable physical path used for quoted-include lookup. #line changes - * filename only, which remains the logical diagnostic/__FILE__ name. - */ - char *physical_filename; - char *filename; + source_file_t *file; } source_location_t; typedef struct token { @@ -1073,6 +1080,12 @@ struct func { /* Callee-saved registers the backend must preserve. */ int saved_regs; + /* Seen as a function pointer while parsing. A heuristic for inlining: such + * a callee keeps its out-of-line copy anyway, so copying it into its direct + * callers too would only duplicate code. + */ + bool address_taken; + /* Information used for dynamic linking */ bool is_used; /* A direct aggregate-return call requires shecc's private sret ABI. */ diff --git a/src/globals.c b/src/globals.c index 7f0fad88..eaf2af8b 100644 --- a/src/globals.c +++ b/src/globals.c @@ -107,6 +107,14 @@ type_t *TY_long_double; /* BLOCK_ARENA is responsible for block_t / var_t allocation */ arena_t *BLOCK_ARENA; +/* TEMP_VAR_ARENA holds the generated temporaries of the function being parsed. + * Nothing reads them once its graph is built, so it is rewound to + * temp_var_empty after each function; they were most of the variables a + * self-compile kept alive until the end. + */ +arena_t *TEMP_VAR_ARENA; +arena_mark_t temp_var_empty; + /* BB_ARENA is responsible for basic_block_t / vir_mop_t allocation */ arena_t *BB_ARENA; @@ -469,6 +477,13 @@ bool arena_rewind(arena_t *arena, arena_mark_t mark) return true; } +/* Drop the temporaries of the function just finished; see TEMP_VAR_ARENA. */ +void release_function_temporaries(void) +{ + if (!arena_rewind(TEMP_VAR_ARENA, temp_var_empty)) + fatal("Cannot release function temporaries"); +} + /* arena_alloc() plus explicit zero‑initialization. * @arena: The arena to allocate memory from. Must not be NULL. * @n: Number of elements. @@ -2399,6 +2414,8 @@ void global_init(void) BLOCK_ARENA = arena_init(DEFAULT_ARENA_SIZE); /* Variables/blocks */ BB_ARENA = arena_init(SMALL_ARENA_SIZE); /* Basic blocks - low usage */ TOKEN_ARENA = arena_init(LARGE_ARENA_SIZE); + TEMP_VAR_ARENA = arena_init(DEFAULT_ARENA_SIZE); + temp_var_empty = arena_mark(TEMP_VAR_ARENA); GENERAL_ARENA = arena_init(DEFAULT_ARENA_SIZE); /* For TYPES and flattened IR */ @@ -2631,6 +2648,8 @@ void release_block_arena(void) { arena_free(BLOCK_ARENA); BLOCK_ARENA = NULL; + arena_free(TEMP_VAR_ARENA); + TEMP_VAR_ARENA = NULL; #ifdef __GLIBC__ /* main() keeps freed memory around for reuse, but these variables sit in * the middle of the heap and the backend allocates nothing like them. @@ -2675,6 +2694,7 @@ void global_release(void) TU_ORDINARY_MAP = NULL; arena_free(BLOCK_ARENA); + arena_free(TEMP_VAR_ARENA); arena_free(BB_ARENA); arena_free(GENERAL_ARENA); /* free TYPES */ @@ -2798,7 +2818,11 @@ __noreturn void error_at(char *msg, source_location_t *loc) len = loc->len; pos = loc->pos; - strbuf_t *src = hashmap_get(SRC_FILE_MAP, loc->filename); + /* The buffer is keyed by the physical path, which #line leaves alone, and + * @pos indexes it; the message names the logical file. + */ + strbuf_t *src = + loc->file ? hashmap_get(SRC_FILE_MAP, loc->file->physical) : NULL; /* 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 @@ -2813,8 +2837,8 @@ __noreturn void error_at(char *msg, source_location_t *loc) if (len < 1) len = 1; - printf("%s:%d:%d: [Error]: %s\n", loc->filename, loc->line, loc->column, - msg); + printf("%s:%d:%d: [Error]: %s\n", loc->file->logical, loc->line, + loc->column, msg); printf("%6d | ", loc->line); /* Finds line's start position */ diff --git a/src/lexer.c b/src/lexer.c index fdef48d0..553a246c 100644 --- a/src/lexer.c +++ b/src/lexer.c @@ -1358,6 +1358,17 @@ static char *normalize_filename(const char *filename) return intern_string(path); } +/* A file record naming @physical, reported as @logical. It outlives the token + * arenas, which compaction retires while the copied tokens still point here. + */ +source_file_t *new_source_file(char *physical, char *logical) +{ + source_file_t *file = arena_alloc(GENERAL_ARENA, sizeof(source_file_t)); + file->physical = physical; + file->logical = logical; + return file; +} + static token_stream_t *cache_token_stream(char *filename, token_t *head, token_t *tail) @@ -1381,7 +1392,7 @@ token_stream_t *gen_file_token_stream(char *filename) * set to 1. */ filename = normalize_filename(filename); - source_location_t loc = {0, 1, 1, 1, filename, filename}; + source_location_t loc = {0, 1, 1, 1, NULL}; strbuf_t *buf; tks = hashmap_get(TOKEN_CACHE, filename); @@ -1389,6 +1400,7 @@ token_stream_t *gen_file_token_stream(char *filename) /* Already cached, just return the computed token stream */ if (tks) return tks; + loc.file = new_source_file(filename, filename); buf = get_file_buf(filename); buf->plain_end = next_phase_mark(buf, 0); @@ -1426,12 +1438,13 @@ token_stream_t *gen_libc_token_stream(void) token_stream_t *tks; char *filename = dynlink ? "lib/c.h" : "lib/c.c"; strbuf_t *buf = LIBC_SRC; - source_location_t loc = {0, 1, 1, 1, filename, filename}; + source_location_t loc = {0, 1, 1, 1, NULL}; tks = hashmap_get(TOKEN_CACHE, filename); if (tks) return tks; + loc.file = new_source_file(filename, filename); if (!hashmap_contains(SRC_FILE_MAP, filename)) hashmap_put_borrowed(SRC_FILE_MAP, filename, LIBC_SRC); diff --git a/src/parser-call.c b/src/parser-call.c index 7ee87f78..f6c476f2 100644 --- a/src/parser-call.c +++ b/src/parser-call.c @@ -66,11 +66,11 @@ void read_func_parameters_with_sret(func_t *func, if (!is_record_object(param)) error_at("Record argument required", cur_token_loc()); - var_t *copy = name_var(require_typed_var(parent, target->type)); + var_t *copy = require_temp_typed_var(parent, target->type); add_insn(parent, *bb, OP_allocat, copy, NULL, NULL, 0, NULL); emit_record_copy(parent, bb, copy, param); - param = name_var(require_ref_var(parent, target->type, 0)); + param = require_temp_ref_var(parent, target->type, 0); add_insn(parent, *bb, OP_address_of, param, copy, NULL, 0, NULL); } else if (!has_effective_pointer(target) && !target->array_size) { @@ -93,7 +93,7 @@ void read_func_parameters_with_sret(func_t *func, var_t *copy = require_typed_var(parent, param->type); add_insn(parent, *bb, OP_allocat, copy, NULL, NULL, 0, NULL); emit_record_copy(parent, bb, copy, param); - param = name_var(require_ref_var(parent, param->type, 0)); + param = require_temp_ref_var(parent, param->type, 0); add_insn(parent, *bb, OP_address_of, param, copy, NULL, 0, NULL); } else if (!is_pointer_like_value(param) && @@ -230,14 +230,12 @@ static var_t *array_subscript_remainder(block_t *parent, for (int i = 0; i < consumed; i++) fixed_array_shape_drop_outer(&shape); if (unevaluated_expression_depth) { - result = - name_var(require_typed_ptr_var(parent, type, element_ptr_level)); + result = require_temp_typed_ptr_var(parent, type, element_ptr_level); fixed_array_shape_to_var(result, &shape); return result; } fixed_array_shape_drop_outer(&shape); - result = - name_var(require_typed_ptr_var(parent, type, element_ptr_level + 1)); + result = require_temp_typed_ptr_var(parent, type, element_ptr_level + 1); fixed_array_shape_to_pointee_var(result, &shape); if (shape.rank) result->pointee_array_element_ptr_level = element_ptr_level; @@ -296,8 +294,8 @@ void lower_call_result_array_postfix(var_t **value, } while (lex_peek(T_open_square, NULL)); if (depth == dimensions) { - var_t *element = name_var( - require_typed_ptr_var(parent, element_type, element_ptr_level)); + var_t *element = + require_temp_typed_ptr_var(parent, element_type, element_ptr_level); /* A call returning a pointer to an array of callbacks carries their * prototype on the result rather than on its scalar base type. @@ -538,7 +536,7 @@ void emit_record_copy_from_address(block_t *parent, var_t *dest, var_t *src_addr) { - var_t *dest_addr = name_var(require_ref_var(parent, dest->type, 0)); + var_t *dest_addr = require_temp_ref_var(parent, dest->type, 0); add_insn(parent, *bb, OP_address_of, dest_addr, dest, NULL, 0, NULL); emit_record_copy_between(parent, bb, dest_addr, src_addr, size_var(dest)); } @@ -625,7 +623,7 @@ var_t *lower_reference_update(var_t *object, handle_pointer_arithmetic(parent, bb, op, object, one); updated = opstack_pop(); } else { - updated = require_named_var(parent); + updated = require_temp_var(parent); updated->type = integer_binary_result_type(op, object, one); add_insn(parent, *bb, op, updated, object, one, 0, NULL); } @@ -673,10 +671,10 @@ void read_builtin_va_start(block_t *parent, basic_block_t **bb) error_at("va_start requires the last named variadic parameter", cur_token_loc()); lex_expect(T_close_bracket); - var_t *value = name_var(require_typed_ptr_var(parent, TY_int, 1)); + var_t *value = require_temp_typed_ptr_var(parent, TY_int, 1); add_insn(parent, *bb, OP_va_start, value, NULL, NULL, 0, NULL); add_insn(parent, *bb, OP_write, NULL, destination, value, PTR_SIZE, NULL); - opstack_push(name_var(require_typed_var(parent, TY_void))); + opstack_push(require_temp_typed_var(parent, TY_void)); } void read_builtin_va_arg(block_t *parent, basic_block_t **bb) @@ -706,9 +704,9 @@ void read_builtin_va_arg(block_t *parent, basic_block_t **bb) if (requested.is_array || requested.is_function) error_at("va_arg type must be an object type", cur_token_loc()); - old = name_var(require_typed_ptr_var(parent, TY_int, 1)); + old = require_temp_typed_ptr_var(parent, TY_int, 1); add_insn(parent, *bb, OP_read, old, ap_addr, NULL, PTR_SIZE, NULL); - step = name_var(require_typed_var(parent, TY_int)); + step = require_temp_typed_var(parent, TY_int); /* A long long on a 32-bit target takes two ABI words starting at an even * one, both for AAPCS32 and for a variadic argument on RV32, and the callee @@ -717,9 +715,9 @@ void read_builtin_va_arg(block_t *parent, basic_block_t **bb) */ if (PTR_SIZE < 8 && !requested.ptr_level && !is_record_type(requested.type) && requested.type->size == 8) { - var_t *mask = name_var(require_typed_var(parent, TY_int)); - var_t *word = name_var(require_typed_var(parent, TY_uint)); - var_t *aligned = name_var(require_typed_var(parent, TY_uint)); + var_t *mask = require_temp_typed_var(parent, TY_int); + var_t *word = require_temp_typed_var(parent, TY_uint); + var_t *aligned = require_temp_typed_var(parent, TY_uint); add_insn(parent, *bb, OP_cast, word, old, NULL, PTR_SIZE, NULL); step->init_val = 7; @@ -729,11 +727,11 @@ void read_builtin_va_arg(block_t *parent, basic_block_t **bb) mask->init_val = -8; mask->is_const = true; add_insn(parent, *bb, OP_load_constant, mask, NULL, NULL, 0, NULL); - word = name_var(require_typed_var(parent, TY_uint)); + word = require_temp_typed_var(parent, TY_uint); add_insn(parent, *bb, OP_bit_and, word, aligned, mask, 0, NULL); - old = name_var(require_typed_ptr_var(parent, TY_int, 1)); + old = require_temp_typed_ptr_var(parent, TY_int, 1); add_insn(parent, *bb, OP_cast, old, word, NULL, PTR_SIZE, NULL); - step = name_var(require_typed_var(parent, TY_int)); + step = require_temp_typed_var(parent, TY_int); } /* This direct IR add is byte-addressed; unlike parsed pointer arithmetic it @@ -746,20 +744,20 @@ void read_builtin_va_arg(block_t *parent, basic_block_t **bb) : PTR_SIZE; step->is_const = true; add_insn(parent, *bb, OP_load_constant, step, NULL, NULL, 0, NULL); - next = name_var(require_typed_ptr_var(parent, TY_int, 1)); + next = require_temp_typed_ptr_var(parent, TY_int, 1); add_insn(parent, *bb, OP_add, next, old, step, 0, NULL); add_insn(parent, *bb, OP_write, NULL, ap_addr, next, PTR_SIZE, NULL); if (is_record_type(requested.type) && !requested.ptr_level) { - var_t *source = name_var(require_ref_var(parent, requested.type, 0)); - var_t *result = name_var(require_typed_var(parent, requested.type)); + var_t *source = require_temp_ref_var(parent, requested.type, 0); + var_t *result = require_temp_typed_var(parent, requested.type); add_insn(parent, *bb, OP_read, source, old, NULL, PTR_SIZE, NULL); add_insn(parent, *bb, OP_allocat, result, NULL, NULL, 0, NULL); emit_record_copy_from_address(parent, bb, result, source); opstack_push(result); } else { - var_t *result = name_var( - require_typed_ptr_var(parent, requested.type, requested.ptr_level)); + var_t *result = require_temp_typed_ptr_var(parent, requested.type, + requested.ptr_level); add_insn(parent, *bb, OP_read, result, old, NULL, requested.ptr_level ? PTR_SIZE : requested.type->size, NULL); result->func_signature = requested.func_signature; @@ -829,15 +827,15 @@ void read_builtin_va_arg(block_t *parent, basic_block_t **bb) * and is a width no narrow backend can load. */ var_t *value = - name_var(require_typed_var(parent, requested.type)); + require_temp_typed_var(parent, requested.type); add_insn(parent, *bb, OP_allocat, value, NULL, NULL, 0, NULL); emit_record_copy_from_address(parent, bb, value, address); base = value; } else if (depth == dimension_count + 1) { - var_t *value = name_var(require_typed_ptr_var( + var_t *value = require_temp_typed_ptr_var( parent, element_type, - requested.pointee_element_ptr_level)); + requested.pointee_element_ptr_level); add_insn(parent, *bb, OP_read, value, address, NULL, element_size, NULL); base = value; @@ -884,8 +882,8 @@ var_t *load_function_pointer_object(block_t *parent, if (object == param) { func_t *param_signature = get_func_signature(object); - var_t *value = name_var(require_typed_ptr_var( - parent, param_signature->return_def.type, 1)); + var_t *value = require_temp_typed_ptr_var( + parent, param_signature->return_def.type, 1); value->func_signature = param_signature; add_insn(parent, *bb, OP_assign, value, object, NULL, 0, NULL); return value; @@ -895,8 +893,8 @@ var_t *load_function_pointer_object(block_t *parent, var_t *address = require_ref_var(parent, object->type, object->ptr_level); func_t *signature = get_func_signature(object); - var_t *target = name_var(require_typed_ptr_var( - parent, signature ? signature->return_def.type : object->type, 1)); + var_t *target = require_temp_typed_ptr_var( + parent, signature ? signature->return_def.type : object->type, 1); add_insn(parent, *bb, OP_address_of, address, object, NULL, 0, NULL); target->func_signature = object->func_signature; add_insn(parent, *bb, OP_read, target, address, NULL, PTR_SIZE, NULL); @@ -929,8 +927,8 @@ var_t *materialize_function_designator(block_t *parent, if (!func) return value; - object = name_var(require_typed_ptr_var(parent, func->return_def.type, - func->return_def.ptr_level)); + object = require_temp_typed_ptr_var(parent, func->return_def.type, + func->return_def.ptr_level); object->is_func = true; object->func_signature = func; add_insn(parent, *bb, OP_allocat, object, NULL, NULL, 0, NULL); @@ -982,14 +980,12 @@ var_t *prepare_aggregate_call_result(block_t *parent, func_t *signature, var_t **sret) { - var_t *result = - name_var(require_typed_var(parent, signature->return_def.type)); + var_t *result = require_temp_typed_var(parent, signature->return_def.type); var_t *destination; add_insn(parent, *bb, OP_allocat, result, NULL, NULL, 0, NULL); - destination = - name_var(require_ref_var(parent, signature->return_def.type, 0)); + destination = require_temp_ref_var(parent, signature->return_def.type, 0); add_insn(parent, *bb, OP_address_of, destination, result, NULL, 0, NULL); *sret = destination; return result; @@ -1021,9 +1017,9 @@ static var_t *callback_slot_call_result(block_t *parent, if (!callback || return_def->type->is_direct_function_type || return_def->type->array_size || return_def->ptr_level < 1) return NULL; - result = name_var(require_typed_ptr_var( + result = require_temp_typed_ptr_var( parent, callback->return_def.type, - callback->return_def.ptr_level + return_def->ptr_level)); + callback->return_def.ptr_level + return_def->ptr_level); result->pointee_func_signature = callback; return result; } @@ -1039,11 +1035,11 @@ static var_t *emit_call_result_value(block_t *parent, func_t *returned_signature = return_def->type->func_signature; bool returns_function = direct_call && returned_signature; - result = name_var(require_typed_ptr_var( + result = require_temp_typed_ptr_var( parent, returns_function ? returned_signature->return_def.type : return_def->type, - returns_function ? 1 : return_def->ptr_level)); + returns_function ? 1 : return_def->ptr_level); result->func_signature = returned_signature; copy_call_result_array_shape(result, return_def); } @@ -1122,7 +1118,7 @@ var_t *bitfield_binary(block_t *parent, var_t *right, type_t *type) { - var_t *result = require_named_var(parent); + var_t *result = require_temp_var(parent); result->type = type; add_insn(parent, *bb, op, result, left, right, 0, NULL); return result; @@ -1139,7 +1135,7 @@ static var_t *read_bitfield_value_impl(block_t *parent, const var_t *field, bool assignment_reload) { - var_t *value = require_named_var(parent); + var_t *value = require_temp_var(parent); value->is_assignment_reload = assignment_reload; value->type = TY_uint; add_insn(parent, *bb, OP_read, value, address, NULL, @@ -1217,7 +1213,7 @@ void write_bitfield_value(block_t *parent, { if (is_bool_type(field->type)) value = normalize_bool(parent, bb, value); - var_t *old = require_named_var(parent); + var_t *old = require_temp_var(parent); old->type = TY_uint; add_insn(parent, *bb, OP_read, old, address, NULL, field->bit_storage_size, NULL); @@ -1229,7 +1225,7 @@ void write_bitfield_value(block_t *parent, parent, bb, OP_lshift, value, bitfield_constant(parent, bb, field->bit_offset), TY_uint); unsigned mask = bitfield_mask(field) << field->bit_offset; - var_t *clear = require_named_var(parent); + var_t *clear = require_temp_var(parent); clear->type = TY_uint; add_insn(parent, *bb, OP_bit_not, clear, bitfield_constant(parent, bb, mask), NULL, 0, NULL); @@ -1329,8 +1325,8 @@ static void take_expression_address(block_t *parent, basic_block_t **bb) if (!effective_pointer_depth(operand) && !operand->array_size) error_at("Cannot dereference from non-pointer typed variable", cur_token_loc()); - result = name_var(require_typed_ptr_var( - parent, operand->type, operand->ptr_level + !!operand->array_size)); + result = require_temp_typed_ptr_var( + parent, operand->type, operand->ptr_level + !!operand->array_size); result->is_const_qualified = operand->is_const_qualified; result->pointer_const_mask = operand->pointer_const_mask; result->func_signature = operand->func_signature; @@ -1362,8 +1358,8 @@ static void take_expression_address(block_t *parent, basic_block_t **bb) } else { error_at("Address-of requires an lvalue operand", cur_token_loc()); } - result = name_var( - require_typed_ptr_var(parent, operand->type, operand->ptr_level + 1)); + result = require_temp_typed_ptr_var(parent, operand->type, + operand->ptr_level + 1); result->is_const_qualified = operand->is_const_qualified; result->is_string_literal = operand->is_string_literal; if (operand->func_signature) @@ -1396,7 +1392,7 @@ void handle_address_of_operator(block_t *parent, basic_block_t **bb) error_at("__func__ is only defined inside a function", next_token_loc()); lex_expect(T_identifier); - vd = name_var(require_typed_ptr_var(parent, TY_char, 1)); + vd = require_temp_typed_ptr_var(parent, TY_char, 1); vd->init_val = write_symbol(function_source_name(parent->func)); vd->is_string_literal = true; vd->is_func_name_array_address = true; @@ -1444,7 +1440,7 @@ void handle_address_of_operator(block_t *parent, basic_block_t **bb) fixed_array_shape_drop_outer(&shape); if (shape.rank) { rs1 = opstack_pop(); - vd = require_named_var(parent); + vd = require_temp_var(parent); vd->type = lvalue.decl->type; vd->ptr_level = lvalue.decl->ptr_level; vd->is_const_qualified = lvalue.decl->is_const_qualified; @@ -1458,7 +1454,7 @@ void handle_address_of_operator(block_t *parent, basic_block_t **bb) if (!lvalue.is_reference) { rs1 = opstack_pop(); - vd = name_var(require_ref_var(parent, lvalue.type, lvalue.ptr_level)); + vd = require_temp_ref_var(parent, lvalue.type, lvalue.ptr_level); /* Address-of moves a pointer object's qualification one level inward: * `&p`, for `int * const p`, is `int * const *`, not `const int **`. @@ -1565,7 +1561,7 @@ static bool lower_pointee_array_dereference(var_t *source, */ if (!element_type->ptr_level) element_type = array_typedef_element_type(element_type); - row = name_var(require_typed_var(parent, element_type)); + row = require_temp_typed_var(parent, element_type); fixed_array_shape_t shape = fixed_array_shape_from_pointee_var(source); fixed_array_shape_to_var(row, &shape); @@ -1696,8 +1692,8 @@ static var_t *decay_dereferenced_array(block_t *parent, basic_block_t **bb, var_t *array) { - var_t *pointer = name_var( - require_typed_ptr_var(parent, array->type, array->ptr_level + 1)); + var_t *pointer = + require_temp_typed_ptr_var(parent, array->type, array->ptr_level + 1); if (array->array_dim2) { fixed_array_shape_t shape = fixed_array_shape_from_var(array); @@ -1751,7 +1747,7 @@ void push_dereference(block_t *parent, basic_block_t **bb, var_t *rs1) * element. Dereferencing it restores the array, which immediately * decays back to char * in this expression context. */ - vd = name_var(require_typed_ptr_var(parent, TY_char, 1)); + vd = require_temp_typed_ptr_var(parent, TY_char, 1); vd->is_string_literal = true; opstack_push(vd); add_insn(parent, *bb, OP_assign, vd, rs1, NULL, 0, NULL); @@ -1848,8 +1844,8 @@ static bool push_dereferenced_function(block_t *parent, /* An array of function pointers decays to the address of its first * element, from which `*fps` reads the pointer, as fps[0] does. */ - var_t *address = name_var(require_typed_ptr_var(parent, rs1->type, 1)); - var_t *target = name_var(require_typed_ptr_var(parent, rs1->type, 1)); + var_t *address = require_temp_typed_ptr_var(parent, rs1->type, 1); + var_t *target = require_temp_typed_ptr_var(parent, rs1->type, 1); add_insn(parent, *bb, OP_assign, address, rs1, NULL, 0, NULL); target->func_signature = rs1->func_signature; add_insn(parent, *bb, OP_read, target, address, NULL, PTR_SIZE, NULL); @@ -2066,8 +2062,8 @@ void lower_member_postfix(var_t *address, if (depth < shape.rank || effective_pointer_depth(field) != 1 || field->is_func || !is_record_type(pointee)) error_at("Invalid record member access", cur_token_loc()); - var_t *pointer = name_var( - require_typed_ptr_var(parent, field->type, field->ptr_level)); + var_t *pointer = require_temp_typed_ptr_var(parent, field->type, + field->ptr_level); add_insn(parent, *bb, OP_read, pointer, address, NULL, PTR_SIZE, NULL); address = pointer; @@ -2101,15 +2097,15 @@ void lower_member_postfix(var_t *address, /* sizeof observes the array itself, before any decay. */ for (int i = 0; i < depth; i++) fixed_array_shape_drop_outer(&shape); - result = name_var( - require_typed_ptr_var(parent, field->type, field->ptr_level)); + result = + require_temp_typed_ptr_var(parent, field->type, field->ptr_level); fixed_array_shape_to_var(result, &shape); opstack_push(result); } else if (decayed) { for (int i = 0; i <= depth; i++) fixed_array_shape_drop_outer(&shape); - result = name_var( - require_typed_ptr_var(parent, field->type, field->ptr_level + 1)); + result = require_temp_typed_ptr_var(parent, field->type, + field->ptr_level + 1); set_pointee_array_shape(result, &shape, field->ptr_level); add_insn(parent, *bb, OP_assign, result, address, NULL, 0, NULL); opstack_push(result); @@ -2119,8 +2115,8 @@ void lower_member_postfix(var_t *address, if (is_lvalue) mark_value_reference(result, address, field); } else { - result = name_var( - require_typed_ptr_var(parent, field->type, field->ptr_level)); + result = + require_temp_typed_ptr_var(parent, field->type, field->ptr_level); result->func_signature = field->func_signature; /* A function-pointer member is loaded as the pointer value a call diff --git a/src/parser-const.c b/src/parser-const.c index 3a1cffbe..be69d1b8 100644 --- a/src/parser-const.c +++ b/src/parser-const.c @@ -524,7 +524,7 @@ static var_t *promote_wide_global_operand(block_t *parent, if (value->ptr_level || wide_global_address_operand(value) || value->type->size >= TY_int->size) return value; - promoted = name_var(require_typed_var(parent, TY_int)); + promoted = require_temp_typed_var(parent, TY_int); promoted->init_val = value->init_val; promoted->init_val_hi = wide_global_narrow_high(value->init_val, false); promoted->is_const = true; @@ -547,8 +547,8 @@ static var_t *read_unevaluated_global_value(block_t *parent, unevaluated_expression_depth--; wide_global_unevaluated_depth = saved_depth; var_t *operand = opstack_pop(); - var_t *value = name_var( - require_typed_ptr_var(parent, operand->type, operand->ptr_level)); + var_t *value = + require_temp_typed_ptr_var(parent, operand->type, operand->ptr_level); value->is_func = operand->is_func; value->func_signature = operand->func_signature; value->pointee_func_signature = operand->pointee_func_signature; @@ -601,7 +601,7 @@ var_t *read_wide_global_literal_primary(block_t *parent, zero->type = value->type; zero->init_val = 0; add_insn(parent, bb, OP_load_constant, zero, NULL, NULL, 0, NULL); - result = require_named_var(parent); + result = require_temp_var(parent); result->type = value->type; emit_wide_global_word_arithmetic(parent, bb, result, OP_sub, zero, value); @@ -612,7 +612,7 @@ var_t *read_wide_global_literal_primary(block_t *parent, value = promote_wide_global_operand( parent, bb, read_wide_global_literal_primary(parent, bb, scope)); - result = require_named_var(parent); + result = require_temp_var(parent); result->type = value->type; emit_wide_global_word_arithmetic(parent, bb, result, OP_bit_not, value, NULL); @@ -622,7 +622,7 @@ var_t *read_wide_global_literal_primary(block_t *parent, var_t *result; value = read_wide_global_literal_primary(parent, bb, scope); - result = name_var(require_typed_var(parent, TY_int)); + result = require_temp_typed_var(parent, TY_int); result->init_val = !wide_global_operand_is_true(value); result->init_val_hi = 0; result->is_const = true; @@ -653,8 +653,7 @@ var_t *read_wide_global_literal_primary(block_t *parent, return address; } if (lex_accept(T_sizeof)) { - value = - name_var(require_typed_var(parent, find_builtin_type("size_t"))); + value = require_temp_typed_var(parent, find_builtin_type("size_t")); value->init_val = read_sizeof_constant(scope); value->init_val_hi = 0; value->is_const = true; @@ -688,7 +687,7 @@ var_t *read_wide_global_literal_primary(block_t *parent, operand->type->size <= TY_int->size) hi = wide_global_narrow_high(operand->init_val, operand->type->is_unsigned); - value = name_var(require_typed_var(parent, type)); + value = require_temp_typed_var(parent, type); fold_integer_constant_cast(value, (unsigned int) operand->init_val, hi); value->is_const = true; add_insn(parent, bb, OP_load_constant, value, NULL, NULL, 0, NULL); @@ -710,7 +709,7 @@ var_t *read_wide_global_literal_primary(block_t *parent, return value; } lex_expect(T_identifier); - value = name_var(require_typed_var(parent, TY_int)); + value = require_temp_typed_var(parent, TY_int); /* Every operand this reader returns carries its high word, the * extension of an int-sized value, so no consumer rebuilds it. @@ -731,7 +730,7 @@ var_t *read_wide_global_literal_primary(block_t *parent, error_at("Wide string literal element is not a constant", next_token_loc()); read_string_literal_element(scope, &element); - value = name_var(require_typed_var(parent, TY_int)); + value = require_temp_typed_var(parent, TY_int); value->init_val = element; value->init_val_hi = wide_global_narrow_high(element, false); value->is_const = true; diff --git a/src/parser-decl.c b/src/parser-decl.c index ac0c7b95..d93dc224 100644 --- a/src/parser-decl.c +++ b/src/parser-decl.c @@ -2090,7 +2090,7 @@ void read_literal_param(block_t *parent, basic_block_t *bb) int length = read_concatenated_string(combined); const int index = write_string_symbol(combined, length); - var_t *vd = name_var(require_typed_ptr_var(parent, TY_char, true)); + var_t *vd = require_temp_typed_ptr_var(parent, TY_char, true); vd->init_val = index; vd->is_const_qualified = true; vd->is_string_literal = true; @@ -2354,7 +2354,7 @@ void read_numeric_param(block_t *parent, basic_block_t *bb, bool is_neg) !has_unsigned_suffix && !long_suffix_count && !value_hi && value == 0x80000000U; - var_t *vd = require_named_var(parent); + var_t *vd = require_temp_var(parent); if (narrow_int_min) { vd->type = TY_int; } else if (is_long_long || value_hi || @@ -2420,8 +2420,8 @@ void read_wstring_param(block_t *parent, basic_block_t *bb) int values[MAX_STRING_LEN]; int length = read_wstring_units(values, MAX_STRING_LEN); - var_t *vd = name_var( - require_typed_ptr_var(parent, find_builtin_type("wchar_t"), 1)); + var_t *vd = + require_temp_typed_ptr_var(parent, find_builtin_type("wchar_t"), 1); vd->init_val = write_wide_symbol(values, length); vd->is_const_qualified = true; vd->is_string_literal = true; diff --git a/src/parser-expr.c b/src/parser-expr.c index 60080957..a110ec32 100644 --- a/src/parser-expr.c +++ b/src/parser-expr.c @@ -64,7 +64,7 @@ static var_t *scale_pointer_index(block_t *parent, integer_promoted_type(index)); scale = load_constant(parent, bb, stride, TY_int); scale->is_const = rematerialize_scale; - result = name_var(require_typed_var(parent, integer_promoted_type(index))); + result = require_temp_typed_var(parent, integer_promoted_type(index)); add_insn(parent, bb, OP_mul, result, index, scale, 0, NULL); return result; } @@ -81,8 +81,7 @@ static var_t *lower_array_element_address(block_t *parent, var_t *address; index = scale_pointer_index(parent, bb, index, stride, rematerialize_scale); - address = - name_var(require_typed_ptr_var(parent, element_type, pointer_level)); + address = require_temp_typed_ptr_var(parent, element_type, pointer_level); add_insn(parent, bb, OP_add, address, base, index, 0, NULL); return address; } @@ -268,8 +267,8 @@ static void lower_value_subscript(block_t *parent, basic_block_t **bb) element_ptr_level += element_type->array_element_ptr_level; element_type = element_type->array_element_type; } - vd = name_var( - require_typed_ptr_var(parent, element_type, element_ptr_level)); + vd = require_temp_typed_ptr_var(parent, element_type, + element_ptr_level); vd->is_const_qualified = base->is_const_qualified; vd->pointer_const_mask = base->pointer_const_mask; vd->pointer_volatile_mask = base->pointer_volatile_mask; @@ -357,7 +356,7 @@ static void lower_value_member(block_t *parent, basic_block_t **bb) * as a call or assignment result, is not. */ is_lvalue = base->is_compound_literal || is_named_object(base, parent); - address = name_var(require_ref_var(parent, base->type, 0)); + address = require_temp_ref_var(parent, base->type, 0); address->is_const_qualified = base->is_const_qualified; add_insn(parent, *bb, OP_address_of, address, base, NULL, 0, NULL); } @@ -419,8 +418,8 @@ void lower_postfix_operators(block_t *parent, basic_block_t **bb) if (!object->is_compound_literal_reference && is_named_object(object, parent)) { /* A grouped named object, `(count)++`, updates the variable. */ - var_t *old = name_var( - require_typed_ptr_var(parent, object->type, object->ptr_level)); + var_t *old = require_temp_typed_ptr_var(parent, object->type, + object->ptr_level); var_t *one; var_t *updated; @@ -533,7 +532,7 @@ static void lower_array_literal_subscripts(block_t *parent, &(lvalue_t) {.size = element_size, .value_ptr_level = compound_var->ptr_level, .type = compound_var->type}); - element = name_var(require_typed_var(parent, compound_var->type)); + element = require_temp_typed_var(parent, compound_var->type); add_insn(parent, *bb, OP_read, element, address, NULL, element_size, NULL); element->is_const_qualified = compound_var->is_const_qualified; @@ -557,7 +556,7 @@ static void lower_record_literal_members(block_t *parent, { char field_name[MAX_ID_LEN]; var_t *base = opstack_pop(); - var_t *base_addr = name_var(require_ref_var(parent, base->type, 0)); + var_t *base_addr = require_temp_ref_var(parent, base->type, 0); var_t *field; var_t *address; var_t *element; @@ -620,8 +619,8 @@ static void lower_record_literal_members(block_t *parent, if (is_bitfield(field)) old = read_bitfield_value(parent, bb, address, field); else { - old = name_var( - require_typed_ptr_var(parent, value_type, value_ptr_level)); + old = + require_temp_typed_ptr_var(parent, value_type, value_ptr_level); add_insn(parent, *bb, OP_read, old, address, NULL, value_size, NULL); } @@ -633,8 +632,8 @@ static void lower_record_literal_members(block_t *parent, element = read_bitfield_value(parent, bb, address, field); element->is_bitfield = false; } else { - element = name_var( - require_typed_ptr_var(parent, value_type, value_ptr_level)); + element = + require_temp_typed_ptr_var(parent, value_type, value_ptr_level); add_insn(parent, *bb, OP_read, element, address, NULL, value_size, NULL); } @@ -670,7 +669,7 @@ static void read_compound_literal_operand(block_t *parent, var_t *compound_object = NULL; /* Create variable for compound literal result */ - var_t *compound_var = name_var(require_typed_var(parent, tn->type)); + var_t *compound_var = require_temp_typed_var(parent, tn->type); compound_var->is_compound_literal = true; compound_var->is_const_qualified = tn->const_qualified; compound_var->is_const_pointer = tn->const_pointer; @@ -737,7 +736,7 @@ static void read_compound_literal_operand(block_t *parent, compound_var->pointee_func_signature = tn->func_signature; } if (lex_peek(T_close_curly, NULL)) { - initializer = require_named_var(parent); + initializer = require_temp_var(parent); initializer->is_const = true; add_insn(parent, *bb, OP_load_constant, initializer, NULL, NULL, 0, NULL); @@ -780,8 +779,8 @@ static void read_compound_literal_operand(block_t *parent, false); } else { /* Empty pointer compound literal: (int*){} */ - initializer = name_var( - require_typed_ptr_var(parent, tn->type, tn->ptr_level)); + initializer = + require_temp_typed_ptr_var(parent, tn->type, tn->ptr_level); initializer->init_val = 0; add_insn(parent, *bb, OP_load_constant, initializer, NULL, NULL, 0, NULL); @@ -806,7 +805,7 @@ static void read_compound_literal_operand(block_t *parent, add_insn(parent, *bb, OP_allocat, compound_var, NULL, NULL, 0, NULL); var_t *compound_addr = - name_var(require_ref_var(parent, compound_var->type, 0)); + require_temp_ref_var(parent, compound_var->type, 0); add_insn(parent, *bb, OP_address_of, compound_addr, compound_var, NULL, 0, NULL); parse_struct_field_init(parent, bb, struct_type, compound_addr); @@ -893,8 +892,8 @@ static void read_compound_literal_operand(block_t *parent, compound_object->is_const_pointer) error_at("assignment of read-only location", cur_token_loc()); opstack_pop(); - old = name_var(require_typed_ptr_var(parent, compound_object->type, - compound_object->ptr_level)); + old = require_temp_typed_ptr_var(parent, compound_object->type, + compound_object->ptr_level); add_insn(parent, *bb, OP_assign, old, compound_object, NULL, 0, NULL); one = load_constant(parent, *bb, 1, TY_int); updated = @@ -999,7 +998,7 @@ static void read_cast_operand(block_t *parent, /* Create variable for cast result */ var_t *cast_var = - name_var(require_typed_ptr_var(parent, tn->type, tn->ptr_level)); + require_temp_typed_ptr_var(parent, tn->type, tn->ptr_level); cast_var->is_const_qualified = tn->const_qualified; cast_var->is_const_pointer = tn->const_pointer; cast_var->pointer_const_mask = tn->pointer_const_mask; @@ -1283,7 +1282,7 @@ static void read_integer_unary_operand(block_t *parent, cur_token_loc()); rs1 = integer_promote_operand(parent, bb, rs1); - vd = require_named_var(parent); + vd = require_temp_var(parent); vd->type = rs1->type; if (rs1->is_const && !rs1->ptr_level && !rs1->is_global) { vd->is_const = true; @@ -1424,14 +1423,14 @@ static void read_expr_operand_body(block_t *parent, basic_block_t **bb) /* Constant folding for logical NOT */ if (rs1 && rs1->is_const && !rs1->ptr_level && !rs1->is_global) { - vd = require_named_var(parent); + vd = require_temp_var(parent); vd->type = TY_int; vd->is_const = true; vd->init_val = !(rs1->init_val || rs1->init_val_hi); opstack_push(vd); add_insn(parent, *bb, OP_load_constant, vd, NULL, NULL, 0, NULL); } else { - vd = require_named_var(parent); + vd = require_temp_var(parent); /* C99 6.5.3.3: logical negation always yields int. Preserving the * operand's type made !pointer inherit the pointed-to record size, @@ -1494,7 +1493,7 @@ static void read_expr_operand_body(block_t *parent, basic_block_t **bb) error_at("__func__ is only defined inside a function", next_token_loc()); lex_expect(T_identifier); - vd = name_var(require_typed_ptr_var(parent, TY_char, true)); + vd = require_temp_typed_ptr_var(parent, TY_char, true); vd->init_val = write_symbol(function_source_name(parent->func)); vd->is_string_literal = true; opstack_push(vd); @@ -1503,7 +1502,7 @@ static void read_expr_operand_body(block_t *parent, basic_block_t **bb) if (lex_accept(T_open_square)) { var_t *address = read_subscript_address( parent, bb, opstack_pop(), TY_char, true, 1); - vd = name_var(require_typed_var(parent, TY_char)); + vd = require_temp_typed_var(parent, TY_char); opstack_push(vd); add_insn(parent, *bb, OP_read, vd, address, NULL, 1, NULL); } @@ -1511,7 +1510,7 @@ static void read_expr_operand_body(block_t *parent, basic_block_t **bb) /* An enumeration constant is an integer constant, so one that is * zero is a null pointer constant (C99 6.3.2.3p3). */ - vd = name_var(require_var(parent)); + vd = require_temp_var(parent); vd->init_val = con->value; vd->is_const = true; opstack_push(vd); @@ -1824,7 +1823,7 @@ static var_t *decay_function_pointer_array(block_t *parent, if (!var || !var->is_func || !var->func_signature || var->array_dim2 || !(var->array_size || var->has_unsized_array)) return var; - pointer = name_var(require_typed_ptr_var(parent, var->type, 1)); + pointer = require_temp_typed_ptr_var(parent, var->type, 1); pointer->pointee_func_signature = var->func_signature; add_insn(parent, *bb, OP_assign, pointer, var, NULL, 0, NULL); return pointer; @@ -1941,7 +1940,7 @@ void handle_pointer_arithmetic(block_t *parent, element_size = pointer_arithmetic_stride(orig_rs1); /* Perform subtraction first */ - var_t *diff = require_named_var(parent); + var_t *diff = require_temp_var(parent); add_insn(parent, *bb, OP_sub, diff, rs1, rs2, 0, NULL); /* Then divide by element size if needed */ @@ -1950,7 +1949,7 @@ void handle_pointer_arithmetic(block_t *parent, var_t *size_const = load_constant( parent, *bb, shift >= 0 ? shift : element_size, TY_int); - var_t *result = require_named_var(parent); + var_t *result = require_temp_var(parent); add_insn(parent, *bb, shift >= 0 ? OP_rshift : OP_div, result, diff, size_const, 0, NULL); opstack_push(result); @@ -1990,7 +1989,7 @@ void handle_pointer_arithmetic(block_t *parent, } /* Perform the operation */ - var_t *vd = name_var(require_var(parent)); + var_t *vd = require_temp_var(parent); /* Preserve pointer type metadata on results of pointer arithmetic */ if (ptr_var) { vd->type = ptr_var->type; @@ -2174,7 +2173,7 @@ static var_t *emit_binary_result(block_t *parent, var_t *right, type_t *result_type) { - var_t *result = require_named_var(parent); + var_t *result = require_temp_var(parent); result->type = result_type; add_insn(parent, *bb, op, result, left, right, 0, NULL); return result; @@ -2245,8 +2244,8 @@ static bool fold_wide_constant_binary(block_t *parent, (rs2->type->size <= TY_int->size || rs2->init_val_hi == -1)) return false; - vd = name_var( - require_typed_var(parent, integer_binary_result_type(op, rs1, rs2))); + vd = require_temp_typed_var(parent, + integer_binary_result_type(op, rs1, rs2)); emit_wide_global_word_arithmetic(parent, *bb, vd, op, rs1, rs2); opstack_push(vd); return true; @@ -2526,7 +2525,7 @@ void read_expr_body(block_t *parent, basic_block_t **bb) if (folded) { /* Create constant result */ - vd = require_named_var(parent); + vd = require_temp_var(parent); vd->type = result_type; vd->is_const = true; vd->init_val = result; @@ -2671,8 +2670,8 @@ static var_t *read_addressed_value(block_t *parent, return assignment_result ? reload_assigned_bitfield(parent, bb, address, lvalue->decl) : read_bitfield_value(parent, bb, address, lvalue->decl); - value = name_var( - require_typed_ptr_var(parent, lvalue->type, lvalue->value_ptr_level)); + value = require_temp_typed_ptr_var(parent, lvalue->type, + lvalue->value_ptr_level); value->is_assignment_reload = removable && assignment_result; add_insn(parent, *bb, OP_read, value, address, NULL, lvalue->size, NULL); return value; @@ -2755,7 +2754,7 @@ static void lower_lvalue_tail(lvalue_t *lvalue, */ if (lvalue->is_reference) { rs1 = operand_stack[operand_stack_idx - 1]; - t = require_named_var(parent); + t = require_temp_var(parent); t->type = pointee_type_from_pointer_typedef( pointer_row_element_type ? pointer_row_element_type : lvalue->type); t->ptr_level = lvalue->value_ptr_level; @@ -2916,7 +2915,7 @@ static void lower_lvalue_tail(lvalue_t *lvalue, var_t *old = opstack_pop(); var_t *address = opstack_pop(); var_t *one = bitfield_constant(parent, bb, 1); - var_t *updated = require_named_var(parent); + var_t *updated = require_temp_var(parent); updated->type = integer_binary_result_type(postfix_op, old, one); add_insn(parent, *bb, postfix_op, updated, old, one, 0, NULL); write_bitfield_value(parent, bb, address, updated, lvalue->decl); @@ -2952,7 +2951,7 @@ static void lower_lvalue_tail(lvalue_t *lvalue, rs1 = opstack_pop(); } else { rs1 = operand_stack[operand_stack_idx - 1]; - t = require_named_var(parent); + t = require_temp_var(parent); t->type = rs1->type; t->ptr_level = rs1->ptr_level; t->func_signature = rs1->func_signature; @@ -2962,7 +2961,7 @@ static void lower_lvalue_tail(lvalue_t *lvalue, copy_pointee_array_shape(t, rs1); add_insn(parent, *bb, OP_assign, t, rs1, NULL, 0, NULL); } - vd = require_named_var(parent); + vd = require_temp_var(parent); if (!lvalue->is_reference && is_pointee_array_pointer(var)) { vd->type = lvalue->type; vd->ptr_level = var->ptr_level; @@ -3148,7 +3147,7 @@ void read_lvalue(lvalue_t *lvalue, (lvalue->value_ptr_level || pending_fixed_array_pointer_slot) && is_member) { rs1 = opstack_pop(); - vd = require_named_var(parent); + vd = require_temp_var(parent); vd->type = pointee_type_from_pointer_typedef(lvalue->type); vd->ptr_level = lvalue->value_ptr_level; if (pending_scalar_row_pointer_slot) { @@ -3338,7 +3337,7 @@ void read_lvalue(lvalue_t *lvalue, rs2 = opstack_pop(); rs1 = opstack_pop(); - vd = name_var(require_var(parent)); + vd = require_temp_var(parent); /* A subscript expression computes the address of its selected * element. Preserve that pointer provenance: unary '&' leaves an @@ -3472,7 +3471,7 @@ void read_lvalue(lvalue_t *lvalue, */ if (is_member) { rs1 = opstack_pop(); - vd = require_named_var(parent); + vd = require_temp_var(parent); vd->type = pointee_type_from_pointer_typedef(lvalue->type); vd->ptr_level = lvalue->value_ptr_level; opstack_push(vd); @@ -3486,7 +3485,7 @@ void read_lvalue(lvalue_t *lvalue, if (!is_address_got) { rs1 = opstack_pop(); - vd = require_named_var(parent); + vd = require_temp_var(parent); opstack_push(vd); add_insn(parent, *bb, OP_address_of, vd, rs1, NULL, 0, NULL); @@ -3533,14 +3532,14 @@ void read_lvalue(lvalue_t *lvalue, lvalue->is_reference = !is_array_declarator(var); /* move pointer to offset of structure */ - vd = require_named_var(parent); + vd = require_temp_var(parent); vd->init_val = var->offset; opstack_push(vd); add_insn(parent, *bb, OP_load_constant, vd, NULL, NULL, 0, NULL); rs2 = opstack_pop(); rs1 = opstack_pop(); - vd = require_named_var(parent); + vd = require_temp_var(parent); vd->is_volatile_access = object_is_volatile; if (is_array_declarator(var)) { @@ -3718,7 +3717,7 @@ void finalize_logical(opcode_t op, vd = load_constant(parent, op == OP_log_and ? then_next : else_bb, op == OP_log_and, TY_int); - log_op_res = require_named_var(parent); + log_op_res = require_temp_var(parent); add_insn(parent, op == OP_log_and ? then_next : else_bb, OP_assign, log_op_res, vd, NULL, 0, NULL); @@ -3885,13 +3884,13 @@ static void read_ternary_operation_body(block_t *parent, basic_block_t **bb) */ true_addr = record_value_address(parent, then_, true_val); false_addr = record_value_address(parent, else_, false_val); - selected = name_var(require_ref_var(parent, true_val->type, 0)); + selected = require_temp_ref_var(parent, true_val->type, 0); add_insn(parent, then_, OP_assign, selected, true_addr, NULL, 0, NULL); add_insn(parent, else_, OP_assign, selected, false_addr, NULL, 0, NULL); bb_connect(then_, end_ternary, NEXT); bb_connect(else_, end_ternary, NEXT); - vd = name_var(require_typed_var(parent, true_val->type)); + vd = require_temp_typed_var(parent, true_val->type); add_insn(parent, end_ternary, OP_allocat, vd, NULL, NULL, 0, NULL); emit_record_copy_from_address(parent, &end_ternary, vd, selected); opstack_push(vd); @@ -3899,7 +3898,7 @@ static void read_ternary_operation_body(block_t *parent, basic_block_t **bb) return; } - vd = require_named_var(parent); + vd = require_temp_var(parent); if (true_val->type == TY_void && !true_val->ptr_level && false_val->type == TY_void && !false_val->ptr_level) { vd->type = TY_void; @@ -4058,7 +4057,7 @@ bool read_body_assignment(var_t *var, */ if (lvalue.is_reference) { rs1 = opstack_pop(); - vd = require_named_var(parent); + vd = require_temp_var(parent); opstack_push(vd); add_insn(parent, *bb, OP_read, vd, rs1, NULL, PTR_SIZE, NULL); } @@ -4099,7 +4098,7 @@ bool read_body_assignment(var_t *var, rs2 = vd; rs1 = opstack_pop(); - vd = require_named_var(parent); + vd = require_temp_var(parent); add_insn(parent, *bb, op, vd, rs1, rs2, 0, NULL); if (lvalue.is_reference) @@ -4199,11 +4198,10 @@ bool read_body_assignment(var_t *var, * function address. */ if (rs2->is_func && find_var(rs2->var_name, parent) == rs2) { - t = name_var( - require_ref_var(parent, rs2->type, rs2->ptr_level)); + t = require_temp_ref_var(parent, rs2->type, rs2->ptr_level); add_insn(parent, *bb, OP_address_of, t, rs2, NULL, 0, NULL); - vd = require_named_var(parent); + vd = require_temp_var(parent); add_insn(parent, *bb, OP_read, vd, t, NULL, PTR_SIZE, NULL); rs2 = vd; } @@ -4212,8 +4210,8 @@ bool read_body_assignment(var_t *var, * variable. */ if (!lvalue.is_reference) { - var_t *addr = name_var( - require_ref_var(parent, lvalue.type, lvalue.ptr_level)); + var_t *addr = require_temp_ref_var(parent, lvalue.type, + lvalue.ptr_level); add_insn(parent, *bb, OP_address_of, addr, rs1, NULL, 0, NULL); rs1 = addr; diff --git a/src/parser-global.c b/src/parser-global.c index db6b1abf..37e6fa70 100644 --- a/src/parser-global.c +++ b/src/parser-global.c @@ -626,7 +626,7 @@ void parse_global_compound_array_init(var_t *var, block_t *block) parenthesized_array = read_compound_array_shape( GLOBAL_BLOCK, element_ptr_level, true, &shape) && shape.parenthesized_array; - array = name_var(require_typed_var(GLOBAL_BLOCK, element_type)); + array = require_temp_typed_var(GLOBAL_BLOCK, element_type); array->is_global = true; array->ptr_level = parenthesized_array ? shape.array_element_ptr_level : element_ptr_level; diff --git a/src/parser-init.c b/src/parser-init.c index 6e8cb772..62f106b3 100644 --- a/src/parser-init.c +++ b/src/parser-init.c @@ -68,7 +68,7 @@ static var_t *compute_byte_offset_address(block_t *parent, var_t *offset = load_constant(parent, *bb, byte_offset, TY_int); - var_t *addr = require_named_var(parent); + var_t *addr = require_temp_var(parent); add_insn(parent, *bb, OP_add, addr, base_addr, offset, 0, NULL); return addr; } @@ -143,7 +143,7 @@ void emit_record_copy_between(block_t *parent, compute_element_address(parent, bb, src_addr, offset, 1); var_t *dest_part = compute_element_address(parent, bb, dest_addr, offset, 1); - var_t *value = require_named_var(parent); + var_t *value = require_temp_var(parent); add_insn(parent, *bb, OP_read, value, src_part, NULL, width, NULL); add_insn(parent, *bb, OP_write, NULL, dest_part, value, width, NULL); offset += width; @@ -160,7 +160,7 @@ var_t *record_value_address(block_t *parent, basic_block_t *bb, var_t *record) if (record->defers_record_copy) return record->compound_literal_address; - address = name_var(require_ref_var(parent, record->type, 0)); + address = require_temp_ref_var(parent, record->type, 0); add_insn(parent, bb, OP_address_of, address, record, NULL, 0, NULL); return address; } @@ -552,7 +552,7 @@ static var_t *read_global_object_address(block_t *scope, bool decays) { var_t *address = - name_var(require_ref_var(parent, object->type, object->ptr_level)); + require_temp_ref_var(parent, object->type, object->ptr_level); address->is_global_address = true; add_insn(parent, *bb, OP_address_of, address, object, NULL, 0, NULL); @@ -622,7 +622,7 @@ static var_t *read_global_compound_address(block_t *parent, declaration.is_func = callback != NULL; declaration.func_signature = callback; validate_global_compound_address(ctx->dest, &declaration); - var_t *literal = name_var(require_typed_var(GLOBAL_BLOCK, type)); + var_t *literal = require_temp_typed_var(GLOBAL_BLOCK, type); literal->is_global = true; literal->ptr_level = callback && type->func_signature ? 0 : depth; literal->is_func = callback != NULL; @@ -639,8 +639,7 @@ static var_t *read_global_compound_address(block_t *parent, lex_accept(T_comma); lex_expect(T_close_curly); } - var_t *address = - name_var(require_ref_var(parent, type, literal->ptr_level)); + var_t *address = require_temp_ref_var(parent, type, literal->ptr_level); address->is_global_address = true; address->pointee_func_signature = callback; add_insn(parent, *bb, OP_address_of, address, literal, NULL, 0, NULL); @@ -2029,7 +2028,7 @@ bool parse_struct_field_values(block_t *parent, void parse_array_literal_expr(block_t *parent, basic_block_t **bb) { - var_t *array_var = require_named_var(parent); + var_t *array_var = require_temp_var(parent); array_var->is_compound_literal = true; int element_count = 0; @@ -2096,7 +2095,7 @@ basic_block_t *handle_return_statement(block_t *parent, basic_block_t *bb) cur_token_loc()); if (rs1 && rs1->array_size > 0 && rs1->var_name[0] == '.' && !strict_c99) { - var_t *val = name_var(require_typed_var(parent, rs1->type)); + var_t *val = require_temp_typed_var(parent, rs1->type); val->init_val = rs1->init_val; add_insn(parent, bb, OP_load_constant, val, NULL, NULL, 0, NULL); rs1 = val; @@ -2631,7 +2630,7 @@ var_t *scalarize_array_literal(block_t *parent, /* Create a new scalar temporary, giving it a unique name and copying over * the literal data so downstream code can treat it like a normal value. */ - var_t *scalar = name_var(require_typed_ptr_var(parent, result_type, 0)); + var_t *scalar = require_temp_typed_ptr_var(parent, result_type, 0); scalar->init_val = array_var->init_val; /* Materialize the literal data into the scalar temporary via an OP_read. */ diff --git a/src/parser-stmt.c b/src/parser-stmt.c index e306cdbf..81935349 100644 --- a/src/parser-stmt.c +++ b/src/parser-stmt.c @@ -157,14 +157,14 @@ basic_block_t *read_switch_label_statement(block_t *parent, basic_block_t *body) context->seen_cases = seen; } - constant = name_var(require_typed_var(context->dispatch_parent, - context->control->type)); + constant = require_temp_typed_var(context->dispatch_parent, + context->control->type); constant->init_val = case_lo; constant->init_val_hi = case_hi; add_insn(context->dispatch_parent, context->dispatch_tail, OP_load_constant, constant, NULL, NULL, 0, NULL); - comparison = require_named_var(context->dispatch_parent); + comparison = require_temp_var(context->dispatch_parent); add_insn(context->dispatch_parent, context->dispatch_tail, OP_eq, comparison, constant, context->control, 0, NULL); add_insn(context->dispatch_parent, context->dispatch_tail, OP_branch, @@ -831,7 +831,7 @@ static void emit_scalar_initializer(block_t *parent, var->type->base_type == TYPE_short) && expr_result->var_name[0] == '.') { /* Extract first element from compound literal array */ - var_t *first_elem = name_var(require_typed_var(parent, var->type)); + var_t *first_elem = require_temp_typed_var(parent, var->type); /* Read first element from array at offset 0 expr_result is the array * itself, so we can read directly from it @@ -905,7 +905,7 @@ static void read_block_declarator_storage(block_t *parent, struct_type->base_struct) struct_type = struct_type->base_struct; - var_t *struct_addr = require_named_var(parent); + var_t *struct_addr = require_temp_var(parent); add_insn(parent, *bb, OP_address_of, struct_addr, var, NULL, 0, NULL); lex_expect(T_open_curly); @@ -1913,4 +1913,5 @@ void read_func_body(func_t *func) backpatch_bb_idx = 0; label_idx = 0; vir_frontend_end(func); + release_function_temporaries(); } diff --git a/src/parser.c b/src/parser.c index efe41e50..e0e78a24 100644 --- a/src/parser.c +++ b/src/parser.c @@ -681,11 +681,19 @@ char *gen_name(void) return arena_strdup(GENERAL_ARENA, buf); } -var_t *require_var(block_t *blk) +/* A new variable of @blk. A temporary takes a generated name, which never + * matches an identifier, so it stays off the scope's lookup list; over a + * hundred thousand of them would otherwise sit in front of every name lookup. + * Inside a function it lives in TEMP_VAR_ARENA, rewound once the function's + * graph is built; at file scope it belongs to the global initializer, built at + * the end of parsing. + */ +static var_t *new_var(block_t *blk, bool temp) { var_list_t *var_list = &blk->locals; + arena_t *arena = temp && blk != GLOBAL_BLOCK ? TEMP_VAR_ARENA : BLOCK_ARENA; - if (var_list->size >= var_list->capacity) { + if (!temp && var_list->size >= var_list->capacity) { int old_cap = var_list->capacity; var_list->capacity <<= 1; var_list->elements = arena_realloc( @@ -693,14 +701,15 @@ var_t *require_var(block_t *blk) var_list->capacity * sizeof(var_t *)); } - var_t *var = arena_calloc(BLOCK_ARENA, 1, sizeof(var_t)); + var_t *var = arena_calloc(arena, 1, sizeof(var_t)); stats_var_count++; - var_list->elements[var_list->size++] = var; + if (!temp) + 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. */ - var->var_name = ""; + var->var_name = temp ? gen_name() : ""; var->type = TY_int; var->scope = blk; var->space_is_allocated = false; @@ -708,41 +717,42 @@ var_t *require_var(block_t *blk) return var; } -/* A generated name never matches an identifier, so a temporary just made by - * require_var() leaves its scope's lookup list again. Over a hundred thousand - * of them would otherwise sit in front of every name lookup. - */ -static var_t *name_var(var_t *var) +var_t *require_var(block_t *blk) { - var_list_t *locals = &((block_t *) var->scope)->locals; - - if (locals->size && locals->elements[locals->size - 1] == var) - locals->size--; - var->var_name = gen_name(); - return var; + return new_var(blk, false); } -static var_t *require_named_var(block_t *blk) +static var_t *require_temp_var(block_t *blk) { - return name_var(require_var(blk)); + return new_var(blk, true); } -var_t *require_typed_var(block_t *blk, type_t *type) +static var_t *new_typed_var(block_t *blk, type_t *type, bool temp) { if (!type) error_at("Type must not be NULL", cur_token_loc()); - var_t *var = require_var(blk); + var_t *var = new_var(blk, temp); var->type = type; return var; } +var_t *require_typed_var(block_t *blk, type_t *type) +{ + return new_typed_var(blk, type, false); +} + +static var_t *require_temp_typed_var(block_t *blk, type_t *type) +{ + return new_typed_var(blk, type, true); +} + static var_t *load_constant(block_t *parent, basic_block_t *bb, int value, type_t *type) { - var_t *constant = name_var(require_typed_var(parent, type)); + var_t *constant = require_temp_typed_var(parent, type); constant->init_val = value; add_insn(parent, bb, OP_load_constant, constant, NULL, NULL, 0, NULL); return constant; @@ -778,18 +788,35 @@ var_t *require_typed_ptr_var(block_t *blk, type_t *type, int ptr) return var; } +static var_t *require_temp_typed_ptr_var(block_t *blk, type_t *type, int ptr) +{ + var_t *var = require_temp_typed_var(blk, type); + var->ptr_level = ptr; + return var; +} + type_t *pointee_type_from_pointer_typedef(type_t *type); -var_t *require_ref_var(block_t *blk, type_t *type, int ptr) +static var_t *new_ref_var(block_t *blk, type_t *type, int ptr, bool temp) { if (!type) error_at("Cannot reference variable from NULL type", cur_token_loc()); - var_t *var = require_typed_var(blk, type); + var_t *var = new_typed_var(blk, type, temp); var->ptr_level = ptr + 1; return var; } +var_t *require_ref_var(block_t *blk, type_t *type, int ptr) +{ + return new_ref_var(blk, type, ptr, false); +} + +static var_t *require_temp_ref_var(block_t *blk, type_t *type, int ptr) +{ + return new_ref_var(blk, type, ptr, true); +} + var_t *require_deref_var(block_t *blk, type_t *type, int ptr) { if (!type) @@ -1722,8 +1749,8 @@ void discard_operand(block_t *parent, basic_block_t *bb) (!var->is_func && !var->ptr_level && is_record_type(var->type))) return; - var_t *copy = name_var(require_typed_ptr_var( - parent, var->type, var->is_func ? 1 : var->ptr_level)); + var_t *copy = require_temp_typed_ptr_var(parent, var->type, + var->is_func ? 1 : var->ptr_level); add_insn(parent, bb, OP_assign, copy, var, NULL, 0, NULL); } @@ -1842,7 +1869,7 @@ var_t *promote_unchecked(block_t *block, type_t *target_type, int target_ptr) { - var_t *rd = name_var(require_typed_ptr_var(block, target_type, target_ptr)); + var_t *rd = require_temp_typed_ptr_var(block, target_type, target_ptr); /* Encode both source and target sizes in src1: Lower 16 bits: target size * Upper 16 bits: source size This allows codegen to distinguish between @@ -1883,7 +1910,7 @@ var_t *truncate_unchecked(block_t *block, type_t *target_type, int target_ptr) { - var_t *rd = name_var(require_typed_ptr_var(block, target_type, target_ptr)); + var_t *rd = require_temp_typed_ptr_var(block, target_type, target_ptr); add_insn(block, *bb, OP_trunc, rd, var, NULL, target_ptr ? PTR_SIZE : target_type->size, NULL); preserve_integer_constant(rd, var); @@ -1907,7 +1934,7 @@ var_t *normalize_bool(block_t *block, basic_block_t **bb, var_t *var) zero = load_constant(block, *bb, 0, TY_int); zero->is_const = true; - rd = name_var(require_typed_var(block, TY_bool)); + rd = require_temp_typed_var(block, TY_bool); add_insn(block, *bb, OP_neq, rd, var, zero, 0, NULL); return rd; } diff --git a/src/preprocessor.c b/src/preprocessor.c index c82a3ed2..9ccaa1e0 100644 --- a/src/preprocessor.c +++ b/src/preprocessor.c @@ -564,7 +564,7 @@ bool resolve_angle_include(token_t *open, char *resolved, int resolved_size) { - strbuf_t *source = get_file_buf(open->location.physical_filename); + strbuf_t *source = get_file_buf(open->location.file->physical); int pos = open->location.pos + open->location.len; int end = close->location.pos; int len = 0; @@ -640,7 +640,7 @@ token_t *file_macro_handler(token_t *tk) { token_t *new_tk = copy_token(tk); new_tk->kind = T_string; - new_tk->literal = tk->location.filename; + new_tk->literal = tk->location.file->logical; memcpy(&new_tk->location, &tk->location, sizeof(source_location_t)); return new_tk; } @@ -697,12 +697,17 @@ void pp_apply_line_directive(token_t *first, char *logical_filename, int line_delta) { + source_file_t *renamed = NULL; for (token_t *it = first; it && it->kind != T_eof; it = it->next) { - if (strcmp(it->location.physical_filename, original_filename)) + if (strcmp(it->location.file->physical, original_filename)) continue; it->location.line += line_delta; - if (logical_filename) - it->location.filename = logical_filename; + if (!logical_filename) + continue; + if (!renamed) + renamed = + new_source_file(it->location.file->physical, logical_filename); + it->location.file = renamed; } } @@ -1804,8 +1809,7 @@ token_t *pp_pragma_operator(token_t *tk, token_t *owner) while (len > 0 && (p[len - 1] == ' ' || p[len - 1] == '\t')) len--; if (len == 4 && !strncmp(p, "once", 4)) - hashmap_put_borrowed(PRAGMA_ONCE, owner->location.physical_filename, - NULL); + hashmap_put_borrowed(PRAGMA_ONCE, owner->location.file->physical, NULL); return pp_lex_expect_token(tk, T_close_bracket, true); } @@ -2497,7 +2501,7 @@ static token_t *pp_preprocess_body(token_t *tk, preprocess_ctx_t *ctx) if (!angle_header) { /* Quoted headers are relative to the physical source path. */ char path[MAX_LINE_LEN]; - const char *file = include_tk->location.physical_filename; + const char *file = include_tk->location.file->physical; int c = strlen(file) - 1; while (c > 0 && file[c] != '/') @@ -2538,7 +2542,7 @@ static token_t *pp_preprocess_body(token_t *tk, preprocess_ctx_t *ctx) * include directive. */ if (hashmap_contains(PRAGMA_ONCE, - file_tks->head->location.physical_filename)) + file_tks->head->location.file->physical)) continue; token_t *included = @@ -2720,7 +2724,7 @@ static token_t *pp_preprocess_body(token_t *tk, preprocess_ctx_t *ctx) if (!strcmp("once", tk->literal)) hashmap_put_borrowed(PRAGMA_ONCE, - tk->location.physical_filename, NULL); + tk->location.file->physical, NULL); } while (!pp_lex_peek_token(tk, T_newline, true)) @@ -2730,7 +2734,7 @@ static token_t *pp_preprocess_body(token_t *tk, preprocess_ctx_t *ctx) continue; } case T_cppd_line: { - char *original_filename = tk->location.physical_filename; + char *original_filename = tk->location.file->physical; char *logical_filename = NULL; int requested_line; @@ -2940,7 +2944,7 @@ token_t *preprocess(token_t *tk) synth_built_in_loc.len = 1; synth_built_in_loc.column = 1; synth_built_in_loc.line = 1; - synth_built_in_loc.filename = ""; + synth_built_in_loc.file = new_source_file("", ""); macro_t *macro = new_macro("__FILE__"); macro->handler = file_macro_handler; diff --git a/src/vir-frontend.c b/src/vir-frontend.c index f6ac23b1..fa64af24 100644 --- a/src/vir-frontend.c +++ b/src/vir-frontend.c @@ -520,8 +520,12 @@ static vir_value_t *native_read(native_function_t *ctx, (!var->ptr_level && is_record_type(var->type) && !native_array_parameter(ctx, var))) return native_address(ctx, block, var); - if (var->is_function_designator) + if (var->is_function_designator) { + func_t *target = find_func(var->var_name); + if (target) + target->address_taken = true; return vir_function_addr(&ctx->graph, block, var->var_name); + } if (native_memory(ctx, var) || native_object(var) || native_source_info(ctx, var)->is_object) { vir_value_t *address = native_address(ctx, block, var); @@ -1182,9 +1186,14 @@ static void check_native_pass(const char *name, int result) } } +static vir_opt_level_t native_opt_level(void) +{ + return optimize ? VIR_OPT_O2 : VIR_OPT_O0; +} + void vir_frontend_optimize(vir_function_t *graph) { - vir_opt_level_t level = optimize ? VIR_OPT_O2 : VIR_OPT_O0; + vir_opt_level_t level = native_opt_level(); if (!vir_remove_unreachable(graph)) fatal("Invalid VIR reachability"); @@ -1193,12 +1202,23 @@ void vir_frontend_optimize(vir_function_t *graph) check_native_pass("params", vir_simplify_params(graph, level)); check_native_pass("cse", vir_local_cse(graph, level)); check_native_pass("gvn", vir_gvn_with_stats(graph, level, NULL)); + int normalized = vir_normalize(graph, level); + check_native_pass("normalize", normalized); + if (normalized) + check_native_pass("normalize-sccp", vir_sccp(graph, level)); check_native_pass("reassociate", vir_reassociate(graph, level)); check_native_pass("licm", vir_licm(graph, level)); + int summarized = vir_summarize_affine_loops(graph, level); + check_native_pass("affine-loops", summarized); + if (summarized) { + check_native_pass("affine-params", vir_simplify_params(graph, level)); + check_native_pass("affine-sccp", vir_sccp(graph, level)); + } int strengthened = vir_strength_reduce(graph, level); check_native_pass("strength", strengthened); check_native_pass("cse", vir_local_cse(graph, level)); check_native_pass("dce", vir_dce(graph, level)); + check_native_pass("unroll", vir_unroll_reductions(graph, level)); check_native_pass("cfg", vir_simplify_cfg(graph, level)); check_native_pass("final-cse", vir_local_cse(graph, level)); check_native_pass("final-dce", vir_dce(graph, level)); @@ -1212,6 +1232,25 @@ void vir_frontend_optimize(vir_function_t *graph) } } +/* The optimized graph of a function defined earlier, for inlining into the + * function being finished: only one whose calls the VIR can rewrite without its + * ABI (no variadic or aggregate-returning ones) and that no one used as a + * function pointer, since that keeps its out-of-line copy anyway. + */ +static const vir_function_t *native_inline_callee(const char *name, + void *context, + bool *is_static) +{ + func_t *func = find_func((char *) name); + native_function_t *callee = func ? func->vir_context : NULL; + (void) context; + if (!callee || !callee->finished || callee->released || + func->returns_aggregate || func->address_taken) + return NULL; + *is_static = func->is_static; + return &callee->graph; +} + /* A finished function is optimized and compacted at once; only the global * initializer graph waits for the end of parsing. Promotion and the passes * leave most of what they allocated unreachable, and only the copy gives that @@ -1225,7 +1264,15 @@ void vir_frontend_end(func_t *func) native_finish(ctx); fatal_function_context = func->return_def.var_name; + check_native_pass("inline", + vir_inline_calls(&ctx->graph, native_inline_callee, ctx, + native_opt_level())); vir_frontend_optimize(&ctx->graph); + int tail_calls = vir_eliminate_tail_recursion( + &ctx->graph, func->return_def.var_name, native_opt_level()); + check_native_pass("tail-recursion", tail_calls); + if (tail_calls) + vir_frontend_optimize(&ctx->graph); fatal_function_context = context; native_require(vir_function_compact(&ctx->graph), "graph compaction"); } diff --git a/src/vir-lower.c b/src/vir-lower.c index ac49a487..85bdb67d 100644 --- a/src/vir-lower.c +++ b/src/vir-lower.c @@ -826,6 +826,14 @@ static void vir_machine_value(vir_lower_t *lower, vir_value_t *value) if (value->is_block_param || value->def_effect) return; + + /* read() rebuilds an unpinned literal where it is used. Holding it from its + * definition would only take a register, and under pressure push a live + * value out to its frame slot. + */ + if (lower->locations[value->id].rematerializable && + lower->locations[value->id].fixed < 0) + return; lower->locked = 0; vir_machine_expire(lower); if (op0) @@ -1601,19 +1609,25 @@ static bool vir_machine_slots(vir_lower_t *lower) for (int index = 0; param && index < use->operand; index++) param = param->param_next; - /* A value from inside the parameter's region, or an outer - * loop's parameter entering an inner loop. Only these meet - * the parameter's register on every path, and - * vir_machine_pin_safe() still rules out interference. + /* A value from inside the parameter's region, an outer + * loop's parameter entering an inner loop, or one arm's + * result computed in the block whose edge passes it to a + * join. Only these meet the parameter's register on every + * path, and vir_machine_pin_safe() still rules out + * interference. The arm-result case leaves a literal to its + * edge copy: pinning an entry literal into a loop-carried + * register this way miscompiled a counted loop on x64. + * Literals the first two cases admit are pinned as before. */ if (!param || param->type != value->type || lower->locations[param->id].fixed < 0 || (!vir_dominance_block_dominates( lower->dominance, param->block, value->block) && !(value->is_block_param && - vir_dominance_block_dominates(lower->dominance, - value->block, - param->block)))) { + vir_dominance_block_dominates( + lower->dominance, value->block, param->block)) && + (vir_use_edge(use)->from != value->block || + lower->locations[id].rematerializable))) { safe = false; break; } diff --git a/src/vir.c b/src/vir.c index 5d3fe906..3be6a67a 100644 --- a/src/vir.c +++ b/src/vir.c @@ -1670,7 +1670,7 @@ static vir_value_t *vir_load_impl(vir_function_t *func, vir_use_t *use; vir_value_t *value; - if (!vir_block_open(func, block) || + if (!vir_has_block(func, block) || (address && (!vir_has_value(func, address) || address->type != VIR_TYPE_PTR)) || !vir_type_width(func, type)) @@ -1696,7 +1696,7 @@ static vir_value_t *vir_load_impl(vir_function_t *func, vir_value_t *name(vir_function_t *func, vir_block_t *block, \ vir_value_t *address, vir_type_t type) \ { \ - if (!address) \ + if (!address || !vir_block_open(func, block)) \ return NULL; \ return vir_load_impl(func, block, address, type, is_volatile); \ } @@ -2095,58 +2095,210 @@ static void vir_order_commutative(vir_value_t **op0, vir_value_t **op1) return helper(func, block, op0, op1, opcode); \ } -static vir_value_t *vir_integer_binary(vir_function_t *func, - vir_block_t *block, - vir_value_t *op0, - vir_value_t *op1, - vir_opcode_t opcode) +static vir_value_t *vir_normalize_bitwise(vir_function_t *func, + vir_block_t *block, + vir_value_t *op0, + vir_value_t *op1, + vir_opcode_t opcode) { - unsigned long long result; - bool division = vir_opcode_is_division(opcode); - bool shift = vir_opcode_is_shift(opcode); + /* Normalize bitwise identities before allocating a value. Operands remain + * in the graph, so discarding a result cannot discard memory effects. + */ + if (opcode == VIR_OP_BITAND || opcode == VIR_OP_BITOR || + opcode == VIR_OP_BITXOR) { + if (op0 == op1) + return opcode == VIR_OP_BITXOR + ? vir_const_int(func, block, op0->type, 0) + : op0; + if ((op0->opcode == VIR_OP_BITNOT && op0->op0 == op1) || + (op1->opcode == VIR_OP_BITNOT && op1->op0 == op0)) + return vir_const_int( + func, block, op0->type, + opcode == VIR_OP_BITAND ? 0 : vir_integer_mask(op0->type)); + if (opcode == VIR_OP_BITOR || opcode == VIR_OP_BITXOR) { + vir_value_t *other = NULL; + if (vir_is_const_int(op0, vir_integer_mask(op0->type))) + other = op1; + else if (vir_is_const_int(op1, vir_integer_mask(op1->type))) + other = op0; + if (other) + return opcode == VIR_OP_BITOR + ? vir_const_int(func, block, op0->type, + vir_integer_mask(op0->type)) + : vir_bitnot(func, block, other); + } + if (opcode != VIR_OP_BITXOR) { + if (op0->opcode == opcode && (op0->op0 == op1 || op0->op1 == op1)) + return op0; + if (op1->opcode == opcode && (op1->op0 == op0 || op1->op1 == op0)) + return op1; + vir_opcode_t dual = + opcode == VIR_OP_BITAND ? VIR_OP_BITOR : VIR_OP_BITAND; + if (op0->opcode == dual && (op0->op0 == op1 || op0->op1 == op1)) + return op1; + if (op1->opcode == dual && (op1->op0 == op0 || op1->op1 == op0)) + return op0; + } + } + return NULL; +} - if (!(shift ? vir_binary_operands_are_local(func, block, op0, op1) && - (vir_type_is_i32_or_i64(op0->type) && - op1->type == VIR_TYPE_I32) - : vir_integer_operands_are_local(func, block, op0, op1))) - return NULL; - if (shift && vir_is_const_int(op1, 0)) - return op0; +/* Identities and constant folds that construction applies at every level. */ +static vir_value_t *vir_normalize_integer(vir_function_t *func, + vir_block_t *block, + vir_value_t *op0, + vir_value_t *op1, + vir_opcode_t opcode) +{ + if (opcode == VIR_OP_ADD || opcode == VIR_OP_BITOR || + opcode == VIR_OP_BITXOR) { + if (vir_is_const_int(op0, 0)) + return op1; + if (vir_is_const_int(op1, 0)) + return op0; + } if (opcode == VIR_OP_SUB) { if (op0 == op1) return vir_const_int(func, block, op0->type, 0); if (vir_is_const_int(op1, 0)) return op0; - } else if (opcode == VIR_OP_MUL) { + } + if (opcode == VIR_OP_BITAND) { if (vir_is_const_int(op0, 0) || vir_is_const_int(op1, 0)) return vir_const_int(func, block, op0->type, 0); - if (vir_is_const_int(op0, 1)) + if (vir_is_const_int(op0, vir_integer_mask(op0->type))) return op1; - if (vir_is_const_int(op1, 1)) + if (vir_is_const_int(op1, vir_integer_mask(op0->type))) return op0; - } else if (opcode == VIR_OP_BITAND) { + } + if (vir_opcode_is_shift(opcode) && vir_is_const_int(op1, 0)) + return op0; + if (opcode == VIR_OP_MUL) { if (vir_is_const_int(op0, 0) || vir_is_const_int(op1, 0)) return vir_const_int(func, block, op0->type, 0); - if (vir_is_const_int(op0, vir_integer_mask(op0->type))) - return op1; - if (vir_is_const_int(op1, vir_integer_mask(op1->type))) - return op0; - } else if (!division && !shift) { - if (vir_is_const_int(op0, 0)) + if (vir_is_const_int(op0, 1)) return op1; - if (vir_is_const_int(op1, 0)) + if (vir_is_const_int(op1, 1)) return op0; } - if (vir_opcode_is_commutative(opcode)) - vir_order_commutative(&op0, &op1); - if (vir_is_constant(op0) && vir_is_constant(op1)) { - if (!vir_fold_binary(func, opcode, op0->type, op0->type, op0->constant, - op1->constant, &result)) - return division || shift ? vir_user_operation(func, block, opcode, - op0->type, op0, op1) - : NULL; + unsigned long long result; + if (vir_is_constant(op0) && vir_is_constant(op1) && + vir_fold_binary(func, opcode, op0->type, op0->type, op0->constant, + op1->constant, &result)) return vir_const_int(func, block, op0->type, result); + return NULL; +} + +/* Rewrite multiplication, division and remainder by a power of two as shifts + * and masks. Only vir_normalize() applies it, so construction keeps the + * arithmetic shape the frontend asked for. + */ +static vir_value_t *vir_strength_reduce_integer(vir_function_t *func, + vir_block_t *block, + vir_value_t *op0, + vir_value_t *op1, + vir_opcode_t opcode) +{ + if (opcode != VIR_OP_MUL && !vir_opcode_is_division(opcode)) + return NULL; + if (opcode == VIR_OP_MUL && vir_is_constant(op0)) { + vir_value_t *swap = op0; + op0 = op1; + op1 = swap; } + if (!vir_is_constant(op1)) + return NULL; + unsigned long long divisor = op1->constant; + int width = vir_integer_type_width(op0->type); + if (!divisor || (divisor & (divisor - 1))) + return NULL; + bool remainder = opcode == VIR_OP_SREM || opcode == VIR_OP_UREM; + bool signed_division = opcode == VIR_OP_SDIV || opcode == VIR_OP_SREM; + if (signed_division && (divisor >> (width - 1))) + return NULL; + if (divisor == 1) + return remainder ? vir_const_int(func, block, op0->type, 0) : op0; + int shift = 0; + for (unsigned long long bits = divisor; bits > 1; bits >>= 1) + shift++; + vir_value_t *count = vir_const_i32(func, block, shift); + if (!count) + return NULL; + if (opcode == VIR_OP_MUL) + return vir_shl(func, block, op0, count); + if (opcode == VIR_OP_UDIV) + return vir_lshr(func, block, op0, count); + vir_value_t *mask = vir_const_int(func, block, op0->type, divisor - 1); + if (!mask) + return NULL; + if (opcode == VIR_OP_UREM) + return vir_bitand(func, block, op0, mask); + + /* Signed quotients truncate toward zero. Bias negative dividends by d-1 + * before the arithmetic shift; their addition cannot overflow the type. + * Remainders retain the dividend's sign via x - quotient*d. + */ + vir_value_t *sign_count = vir_const_i32(func, block, width - 1); + vir_value_t *sign = + sign_count ? vir_ashr(func, block, op0, sign_count) : NULL; + vir_value_t *bias = sign ? vir_bitand(func, block, sign, mask) : NULL; + vir_value_t *biased = bias ? vir_add(func, block, op0, bias) : NULL; + vir_value_t *quotient = + biased ? vir_ashr(func, block, biased, count) : NULL; + if (!quotient || !remainder) + return quotient; + vir_value_t *product = vir_shl(func, block, quotient, count); + return product ? vir_sub(func, block, op0, product) : NULL; +} + +static vir_value_t *vir_normalize_binary(vir_function_t *func, + vir_block_t *block, + vir_value_t *op0, + vir_value_t *op1, + vir_opcode_t opcode) +{ + vir_value_t *normalized = + vir_normalize_bitwise(func, block, op0, op1, opcode); + return normalized ? normalized + : vir_normalize_integer(func, block, op0, op1, opcode); +} + +/* NEG and BITNOT undo themselves: op(op(x)) is x. */ +static vir_value_t *vir_unary_involution(vir_opcode_t opcode, + const vir_value_t *operand) +{ + return (opcode == VIR_OP_NEG || opcode == VIR_OP_BITNOT) && + operand->opcode == opcode + ? operand->op0 + : NULL; +} + +static vir_value_t *vir_integer_binary(vir_function_t *func, + vir_block_t *block, + vir_value_t *op0, + vir_value_t *op1, + vir_opcode_t opcode) +{ + bool division = vir_opcode_is_division(opcode); + bool shift = vir_opcode_is_shift(opcode); + + if (!(shift ? vir_binary_operands_are_local(func, block, op0, op1) && + (vir_type_is_i32_or_i64(op0->type) && + op1->type == VIR_TYPE_I32) + : vir_integer_operands_are_local(func, block, op0, op1))) + return NULL; + vir_value_t *normalized = + vir_normalize_binary(func, block, op0, op1, opcode); + if (normalized) + return normalized; + if (vir_opcode_is_commutative(opcode)) + vir_order_commutative(&op0, &op1); + + /* Normalization already folded every foldable constant pair; only a + * trapping division or out-of-range shift keeps its explicit operation. + */ + if (vir_is_constant(op0) && vir_is_constant(op1) && !division && !shift) + return NULL; return vir_user_operation(func, block, opcode, op0->type, op0, op1); } @@ -2188,6 +2340,9 @@ static vir_value_t *vir_integer_unary(vir_function_t *func, !vir_has_value(func, operand) || !vir_unary_types_valid(func, opcode, type, operand->type)) return NULL; + vir_value_t *involution = vir_unary_involution(opcode, operand); + if (involution) + return involution; if (!vir_is_constant(operand)) return vir_user_operation(func, block, opcode, type, operand, NULL); if (cast) @@ -2367,22 +2522,14 @@ static void vir_remove_effect_uses(vir_effect_t *effect) vir_remove_effect_use(effect->args[operand], effect, operand); } -bool vir_effect_remove(vir_function_t *func, vir_effect_t *effect) +/* Unlink EFFECT, its result value and its operand uses from its block. The + * caller has already dropped every use of the result. + */ +static bool vir_effect_unlink(vir_effect_t *effect) { - vir_effect_t **link; - vir_effect_t *previous = NULL; - - if (!func || !effect || - (effect->kind != VIR_EFFECT_STORE && - (effect->kind != VIR_EFFECT_LOAD || !effect->result || - effect->result->uses)) || - !vir_has_block(func, effect->block)) - return false; - link = &effect->block->effects; - while (*link && *link != effect) { - previous = *link; + vir_effect_t **link = &effect->block->effects; + while (*link && *link != effect) link = &(*link)->next; - } if (!*link) return false; if (effect->result) { @@ -2398,13 +2545,22 @@ bool vir_effect_remove(vir_function_t *func, vir_effect_t *effect) } vir_remove_effect_uses(effect); *link = effect->next; - if (effect->block->last_effect == effect) - effect->block->last_effect = previous; effect->next = NULL; vir_repair_value_positions(effect->block); return true; } +bool vir_effect_remove(vir_function_t *func, vir_effect_t *effect) +{ + if (!func || !effect || + (effect->kind != VIR_EFFECT_STORE && + (effect->kind != VIR_EFFECT_LOAD || !effect->result || + effect->result->uses)) || + !vir_has_block(func, effect->block)) + return false; + return vir_effect_unlink(effect); +} + static void vir_set_use_slot(vir_value_t **slot, vir_value_t *value, vir_use_t *use) @@ -2445,18 +2601,31 @@ static vir_value_t **vir_use_value_slot(const vir_use_t *use) vir_block_t *vir_use_block(const vir_use_t *use) { - return vir_use_user(use) ? vir_use_user(use)->block - : vir_use_edge(use) ? vir_use_edge(use)->from - : vir_use_effect(use) ? vir_use_effect(use)->block - : vir_use_return_block(use) ? vir_use_return_block(use) - : vir_use_branch_block(use); + switch (use->kind) { + case VIR_USE_USER: + return ((vir_value_t *) use->owner)->block; + case VIR_USE_EDGE: + return ((vir_edge_t *) use->owner)->from; + case VIR_USE_EFFECT: + return ((vir_effect_t *) use->owner)->block; + case VIR_USE_RETURN: + case VIR_USE_BRANCH: + return use->owner; + default: + return NULL; + } } int vir_use_order(const vir_use_t *use) { - return vir_use_user(use) ? vir_use_user(use)->order - : vir_use_effect(use) ? vir_use_effect(use)->order - : vir_use_block(use)->next_order; + switch (use->kind) { + case VIR_USE_USER: + return ((vir_value_t *) use->owner)->order; + case VIR_USE_EFFECT: + return ((vir_effect_t *) use->owner)->order; + default: + return vir_use_block(use)->next_order; + } } static bool vir_replacement_dominates_with_context( @@ -2551,19 +2720,18 @@ static unsigned int vir_cse_hash(const vir_value_t *value) return hash * 33u + (unsigned int) value->op1->id; } -static unsigned int vir_cse_capacity(const vir_function_t *func, - const vir_block_t *only_block) +/* Local CSE sizes each block's table from its order counter, which bounds the + * values it holds: one scan per block per run was a measurable share of a + * self-compile. The table size only changes probe order, not the leaders. The + * power-of-two table size keeping @count entries at most half full, or 0 when + * it would not fit an int. + */ +static unsigned int vir_cse_table_size(int count) { - int values = 0; unsigned int capacity = 1; - const vir_block_t *block = only_block ? only_block : func->blocks; - const vir_block_t *stop = only_block ? only_block->next : NULL; - - for (; block != stop; block = block->next) - for (const vir_value_t *value = block->head; value; value = value->next) - if (value->nr_ops == 2 && ++values > INT_MAX / 2) - return 0; - while (capacity < (unsigned int) values * 2u) { + if (count > INT_MAX / 2) + return 0; + while (capacity < (unsigned int) count * 2u) { if (capacity > (unsigned int) INT_MAX / 2u) return 0; capacity <<= 1; @@ -2571,6 +2739,22 @@ static unsigned int vir_cse_capacity(const vir_function_t *func, return capacity; } +static unsigned int vir_cse_block_capacity(const vir_block_t *block) +{ + return vir_cse_table_size(block->next_order); +} + +static unsigned int vir_cse_capacity(const vir_function_t *func) +{ + int values = 0; + + for (const vir_block_t *block = func->blocks; block; block = block->next) + for (const vir_value_t *value = block->head; value; value = value->next) + if (value->nr_ops == 2 && ++values > INT_MAX / 2) + return 0; + return vir_cse_table_size(values); +} + /* Look up each binary value of @block in the open-addressed @table and fold it * into an equal leader. Local CSE clears and reuses the table per block; GVN * keeps it across blocks and relies on vir_replace_all_uses() to refuse a @@ -2635,10 +2819,10 @@ static int vir_cse_run(vir_function_t *func, if (global ? opt_level != VIR_OPT_O2 : opt_level == VIR_OPT_O0) return 0; if (global) - capacity = vir_cse_capacity(func, NULL); + capacity = vir_cse_capacity(func); else for (vir_block_t *block = func->blocks; block; block = block->next) { - unsigned int block_capacity = vir_cse_capacity(func, block); + unsigned int block_capacity = vir_cse_block_capacity(block); if (block_capacity > capacity) capacity = block_capacity; @@ -2651,7 +2835,7 @@ static int vir_cse_run(vir_function_t *func, } for (vir_block_t *block = func->blocks; block; block = block->next) { unsigned int block_capacity = - global ? capacity : vir_cse_capacity(func, block); + global ? capacity : vir_cse_block_capacity(block); if (!block_capacity) continue; @@ -2777,6 +2961,65 @@ int vir_gvn_with_stats(vir_function_t *func, return vir_cse_run(func, opt_level, stats, true); } +/* Move the values a builder appended after OLD_TAIL in front of the value at + * *LINK, giving them its order so they keep its place among the effects. + * Return the link that now holds that value. + */ +static vir_value_t **vir_move_appended_before(vir_block_t *block, + vir_value_t *old_tail, + vir_value_t **link) +{ + vir_value_t *value = *link; + vir_value_t *first = old_tail->next; + vir_value_t *last = block->tail; + old_tail->next = NULL; + last->next = value; + *link = first; + for (vir_value_t *inserted = first; inserted != value; + inserted = inserted->next) + inserted->order = value->order; + vir_repair_value_positions(block); + return &last->next; +} + +/* SSA parameter replacement and CSE expose identities after construction. Reuse + * the builders' rules, placing any fresh pure replacement before its original + * definition. Every replacement is then either new and in front of the value or + * one of its transitive operands, so it dominates all existing uses. + */ +int vir_normalize(vir_function_t *func, vir_opt_level_t opt_level) +{ + int changed = 0; + if (!func || !vir_opt_level_valid(opt_level)) + return -1; + if (opt_level == VIR_OPT_O0) + return 0; + for (vir_block_t *block = func->blocks; block; block = block->next) { + for (vir_value_t **link = &block->head; *link; link = &(*link)->next) { + vir_value_t *value = *link; + if (!value->uses || value->def_effect) + continue; + vir_value_t *replacement = NULL; + vir_value_t *old_tail = block->tail; + if (value->nr_ops == 2 && vir_type_is_i32_or_i64(value->type)) { + replacement = vir_normalize_binary(func, block, value->op0, + value->op1, value->opcode); + if (!replacement) + replacement = vir_strength_reduce_integer( + func, block, value->op0, value->op1, value->opcode); + } else if (value->nr_ops == 1) + replacement = vir_unary_involution(value->opcode, value->op0); + if (!replacement || replacement->type != value->type) + continue; + if (block->tail != old_tail) + link = vir_move_appended_before(block, old_tail, link); + vir_replace_all_uses_unchecked(value, replacement); + changed++; + } + } + return changed; +} + static bool vir_value_is_dead(const vir_value_t *value) { const vir_effect_t *effect = value->def_effect; @@ -2827,8 +3070,10 @@ static void vir_repair_value_positions(vir_block_t *block) /* Delay nontrapping pointer arithmetic until old operands have been consumed, * so an outgoing pointer can reuse their register without changing effects. */ -static void vir_sink_pointer_adds(vir_block_t *block) +static bool vir_sink_pointer_adds(vir_block_t *block) { + bool changed = false; + vir_effect_t *barrier = block->effects; for (vir_value_t **link = &block->head; *link; link = &(*link)->next) { vir_value_t *value = *link; if (value->opcode != VIR_OP_PTRADD) @@ -2837,22 +3082,28 @@ static void vir_sink_pointer_adds(vir_block_t *block) for (vir_use_t *use = value->uses; use; use = use->next) if (vir_use_block(use) == block && vir_use_order(use) < limit) limit = vir_use_order(use); - for (vir_effect_t *effect = block->effects; - effect && effect->order < limit; effect = effect->next) - if (effect->order > value->order && effect->kind != VIR_EFFECT_LOAD) - limit = effect->order; + + /* The value walk only advances, even after sinking a value. Keep the + * first following barrier rather than rescanning all earlier effects. + */ + while (barrier && (barrier->order <= value->order || + barrier->kind == VIR_EFFECT_LOAD)) + barrier = barrier->next; + if (barrier && barrier->order < limit) + limit = barrier->order; + if (limit <= value->order + 1) + continue; vir_value_t *after = NULL; for (int operand = 0; operand < value->nr_ops; operand++) { vir_value_t *input = operand ? value->op1 : value->op0; for (vir_use_t *use = input->uses; use; use = use->next) { - int order = vir_use_order(use); - if (vir_use_block(use) != block || order <= value->order || - order >= limit) - continue; vir_value_t *user = vir_use_user(use); if (!user && vir_use_effect(use)) user = vir_use_effect(use)->result; - if (user && (!after || user->order > after->order)) + if (!user || user->block != block || + user->order <= value->order || user->order >= limit) + continue; + if (!after || user->order > after->order) after = user; } } @@ -2872,7 +3123,9 @@ static void vir_sink_pointer_adds(vir_block_t *block) value->next = after->next; after->next = value; link = &after->next; + changed = true; } + return changed; } /* Promote a nonescaping stack scalar used only by direct, nonvolatile memory @@ -2967,6 +3220,7 @@ static void vir_promote_private_stack_values(vir_function_t *func) */ static void vir_drop_overwritten_stores(vir_block_t *block) { + bool changed = false; for (vir_effect_t **link = &block->effects; *link;) { vir_effect_t *effect = *link; if (effect->kind != VIR_EFFECT_STORE) { @@ -2982,11 +3236,13 @@ static void vir_drop_overwritten_stores(vir_block_t *block) effect->stored_value->type == next->stored_value->type) { vir_remove_effect_uses(effect); *link = effect->next; + changed = true; } else { link = &effect->next; } } - vir_repair_value_positions(block); + if (changed) + vir_repair_value_positions(block); } int vir_dce_with_stats(vir_function_t *func, @@ -3073,6 +3329,7 @@ int vir_dce_with_stats(vir_function_t *func, for (vir_block_t *block = func->blocks; block; block = block->next) { vir_value_t **link = &block->head; vir_effect_t **effect_link = &block->effects; + bool changed = false; while (*link) { vir_value_t *value = *link; @@ -3081,6 +3338,7 @@ int vir_dce_with_stats(vir_function_t *func, *link = value->next; value->member = NULL; removed++; + changed = true; } else { link = &value->next; } @@ -3091,12 +3349,14 @@ int vir_dce_with_stats(vir_function_t *func, if (effect->kind == VIR_EFFECT_LOAD && effect->result && dead[effect->result->id]) { *effect_link = effect->next; + changed = true; } else { effect_link = &effect->next; } } - vir_sink_pointer_adds(block); - vir_repair_value_positions(block); + changed |= vir_sink_pointer_adds(block); + if (changed) + vir_repair_value_positions(block); } result = removed; cleanup: @@ -4157,6 +4417,1115 @@ int vir_simplify_params(vir_function_t *func, vir_opt_level_t opt_level) return changed; } +/* Inlining limits in VIR values. With a main, only functions reachable from it + * are emitted, so any callee whose calls are all inlined leaves the image, + * static or not. Linkage still predicts the cost: a static function is a helper + * with a few local callers, so it may hold up to VIR_INLINE_STATIC_LIMIT, while + * an extern one tends to have callers that are not inlined and keep it, so it + * is copied only when tiny, or into a loop, where the call costs every + * iteration. Measured on a self-compile, raising the extern limits or dropping + * the static one both made the generated compiler larger and slower. One caller + * absorbs at most VIR_INLINE_CALLER_BUDGET cloned values. + */ +#define VIR_INLINE_STATIC_LIMIT 40 +#define VIR_INLINE_EXTERN_LIMIT 6 +#define VIR_INLINE_LOOP_LIMIT 16 +#define VIR_INLINE_CALLER_BUDGET 400 +#define VIR_INLINE_MAX_BLOCKS 16 +#define VIR_INLINE_MAX_ARGS 16 + +/* The value count of @callee if it can replace @call and holds at most @limit + * values, else -1: a leaf with no stack objects whose parameters and returns + * match the call's types exactly, so no ABI conversion is skipped. + */ +static int vir_inline_size(const vir_function_t *callee, + const vir_effect_t *call, + int limit) +{ + const vir_block_t *entry = callee->blocks; + int values = 0; + if (!entry || callee->block_count > VIR_INLINE_MAX_BLOCKS || + entry->param_count != call->arg_count || + (call->signature && + (call->signature->is_variadic || call->signature->result.is_bool))) + return -1; + int index = 0; + for (const vir_value_t *param = entry->params; param; + param = param->param_next, index++) + if (param->type != call->args[index]->type || + (call->signature && call->signature->params[index].is_bool)) + return -1; + for (const vir_block_t *block = entry; block; block = block->next) { + if (block->param_count > VIR_INLINE_MAX_ARGS || + block->terminator == VIR_TERM_NONE || + (block->terminator == VIR_TERM_RETURN && call->result && + (!block->return_value || + block->return_value->type != call->result_type))) + return -1; + for (const vir_effect_t *effect = block->effects; effect; + effect = effect->next) + if ((effect->kind != VIR_EFFECT_LOAD && + effect->kind != VIR_EFFECT_VOLATILE_LOAD && + effect->kind != VIR_EFFECT_STORE && + effect->kind != VIR_EFFECT_VOLATILE_STORE) || + (vir_effect_is_store(effect->kind) && !effect->stored_value)) + return -1; + for (const vir_value_t *value = block->head; value; value = value->next) + if (value->opcode == VIR_OP_STACK_ADDR || + value->opcode == VIR_OP_CALL || value->opcode == VIR_OP_PARAM || + ++values > limit) + return -1; + } + return values; +} + +/* Clone one callee value whose operands are already mapped. */ +static vir_value_t *vir_inline_value(vir_function_t *caller, + vir_block_t *block, + const vir_value_t *value, + vir_value_t **map) +{ + vir_value_t *op0 = value->op0 ? map[value->op0->id] : NULL; + vir_value_t *op1 = value->op1 ? map[value->op1->id] : NULL; + switch (value->opcode) { + case VIR_OP_CONST: + return vir_const_int(caller, block, value->type, value->constant); + case VIR_OP_GLOBAL_ADDR: + return vir_global_addr(caller, block, value->addr->name, + value->addr->size, value->addr->alignment); + case VIR_OP_RODATA_ADDR: + return vir_rodata_addr(caller, block, (unsigned int) value->constant); + case VIR_OP_FUNC_ADDR: + return vir_function_addr(caller, block, value->addr->name); + default: + if (value->nr_ops < 1 || value->nr_ops > 2 || !op0 || + (value->nr_ops == 2 && !op1)) + return NULL; + return vir_user_operation(caller, block, value->opcode, value->type, + op0, value->nr_ops == 2 ? op1 : NULL); + } +} + +static bool vir_inline_effect(vir_function_t *caller, + vir_block_t *block, + const vir_effect_t *effect, + vir_value_t **map) +{ + vir_value_t *address = effect->address ? map[effect->address->id] : NULL; + if (!address) + return false; + if (vir_effect_is_load(effect->kind)) { + vir_value_t *value = + vir_load_impl(caller, block, address, effect->result->type, + effect->kind == VIR_EFFECT_VOLATILE_LOAD); + map[effect->result->id] = value; + return value != NULL; + } + vir_value_t *stored = map[effect->stored_value->id]; + if (!stored) + return false; + return (effect->kind == VIR_EFFECT_VOLATILE_STORE + ? vir_volatile_store(caller, block, address, stored) + : vir_store(caller, block, address, stored)) != NULL; +} + +/* Clone @source's values and effects into @block in their shared order. */ +static bool vir_inline_block_body(vir_function_t *caller, + vir_block_t *block, + const vir_block_t *source, + vir_value_t **map) +{ + const vir_value_t *value = source->head; + const vir_effect_t *effect = source->effects; + while (value || effect) { + if (value && value->def_effect) { + value = value->next; + continue; + } + if (effect && (!value || effect->order <= value->order)) { + if (!vir_inline_effect(caller, block, effect, map)) + return false; + effect = effect->next; + continue; + } + if (!(map[value->id] = vir_inline_value(caller, block, value, map))) + return false; + value = value->next; + } + return true; +} + +/* An edge's arguments mapped into the caller; parameter counts are bounded. */ +static void vir_inline_edge_args(const vir_edge_t *edge, + vir_value_t **map, + vir_value_t **args) +{ + for (int i = 0; i < edge->arg_count; i++) + args[i] = map[edge->args[i]->id]; +} + +/* Move everything @call's block holds after @call -- values, effects and the + * terminator with its edges -- into a new block, and return it. The caller + * unlinks @call next, which repairs @call's block. + */ +static vir_block_t *vir_inline_split(vir_function_t *caller, vir_effect_t *call) +{ + vir_block_t *block = call->block, *rest = vir_block_create(caller); + if (!rest) + return NULL; + vir_value_t **link = &block->head; + while (*link && (*link)->order <= call->order) + link = &(*link)->next; + rest->head = *link; + *link = NULL; + for (vir_value_t *value = rest->head; value; value = value->next) + value->block = rest; + rest->effects = call->next; + call->next = NULL; + for (vir_effect_t *effect = rest->effects; effect; effect = effect->next) + effect->block = rest; + + /* The terminator fields of vir_block_t, which move as one. */ + rest->terminator = block->terminator; + rest->return_value = block->return_value; + rest->branch_condition = block->branch_condition; + rest->true_edge = block->true_edge; + rest->false_edge = block->false_edge; + rest->outgoing = block->outgoing; + for (vir_edge_t *edge = rest->outgoing; edge; edge = edge->next_outgoing) + edge->from = rest; + vir_value_t *owned[2] = {rest->return_value, rest->branch_condition}; + vir_use_owner_t kinds[2] = {VIR_USE_RETURN, VIR_USE_BRANCH}; + for (int i = 0; i < 2; i++) + for (vir_use_t *use = owned[i] ? owned[i]->uses : NULL; use; + use = use->next) + if (vir_use_matches(use, kinds[i], block, -1)) + vir_use_set_owner(use, kinds[i], rest); + block->terminator = VIR_TERM_NONE; + block->return_value = NULL; + block->branch_condition = NULL; + block->true_edge = NULL; + block->false_edge = NULL; + block->outgoing = NULL; + vir_repair_value_positions(rest); + return rest; +} + +/* Store the indices of the callee blocks reachable from its entry in reverse + * post-order, which reaches every dominator of a block before the block, so + * every operand is cloned before its users; returns how many. + */ +static int vir_inline_order(const vir_function_t *callee, + int *order, + unsigned char *seen) +{ + vir_block_t *stack[VIR_INLINE_MAX_BLOCKS]; + vir_edge_t *cursor[VIR_INLINE_MAX_BLOCKS]; + int depth = 0, count = callee->block_count; + stack[depth] = callee->blocks; + cursor[depth++] = callee->blocks->outgoing; + seen[callee->blocks->index] = 1; + while (depth) { + vir_edge_t *edge = cursor[depth - 1]; + if (!edge) { + order[--count] = stack[--depth]->index; + continue; + } + cursor[depth - 1] = edge->next_outgoing; + if (!seen[edge->to->index]) { + seen[edge->to->index] = 1; + stack[depth] = edge->to; + cursor[depth++] = edge->to->outgoing; + } + } + /* Shift the reachable blocks to the front. */ + int reachable = callee->block_count - count; + for (int i = 0; i < reachable; i++) + order[i] = order[count + i]; + return reachable; +} + +/* Replace @call with a copy of @callee and return the block now holding what + * followed the call, or NULL. The call's block jumps to the cloned entry with + * the arguments, and every cloned return jumps to that block, whose parameter + * carries the result. + */ +static vir_block_t *vir_inline_call(vir_function_t *caller, + vir_effect_t *call, + const vir_function_t *callee) +{ + int order[VIR_INLINE_MAX_BLOCKS]; + unsigned char reachable[VIR_INLINE_MAX_BLOCKS] = {0}; + vir_block_t *clone_of[VIR_INLINE_MAX_BLOCKS] = {0}; + vir_value_t *args[VIR_INLINE_MAX_ARGS], *other[VIR_INLINE_MAX_ARGS]; + vir_block_t *rest = NULL, *result_block = NULL; + vir_value_t *result = NULL; + vir_value_t **map = + calloc((size_t) callee->next_value_id + 1, sizeof(*map)); + if (!map) + goto done; + int count = vir_inline_order(callee, order, reachable); + + /* Blocks and parameters first: edges and operands may name any of them. */ + for (int i = 0; i < count; i++) { + const vir_block_t *source = callee->block_table[order[i]]; + if (!(clone_of[order[i]] = vir_block_create(caller))) + goto done; + for (vir_value_t *param = source->params; param; + param = param->param_next) + if (!(map[param->id] = vir_block_add_param( + caller, clone_of[order[i]], param->type))) + goto done; + } + for (int i = 0; i < count; i++) + if (!vir_inline_block_body(caller, clone_of[order[i]], + callee->block_table[order[i]], map)) + goto done; + + /* Splice the clone in, then give each cloned block its terminator. */ + if (!(rest = vir_inline_split(caller, call))) + goto done; + if (call->result && call->result->uses) { + if (!(result = vir_block_add_param(caller, rest, call->result_type))) + goto done; + vir_replace_all_uses_unchecked(call->result, result); + } + if (!vir_edge_create(caller, call->block, clone_of[callee->blocks->index], + call->args, call->arg_count) || + !vir_effect_unlink(call)) + goto done; + for (int i = 0; i < count; i++) { + const vir_block_t *source = callee->block_table[order[i]]; + vir_block_t *block = clone_of[order[i]]; + if (source->terminator == VIR_TERM_RETURN) { + vir_value_t *value = result ? map[source->return_value->id] : NULL; + if (!vir_edge_create(caller, block, rest, value ? &value : NULL, + value ? 1 : 0)) + goto done; + continue; + } + const vir_edge_t *taken = source->terminator == VIR_TERM_JUMP + ? source->outgoing + : source->true_edge; + vir_inline_edge_args(taken, map, args); + if (source->terminator == VIR_TERM_JUMP) { + if (!vir_edge_create(caller, block, clone_of[taken->to->index], + args, taken->arg_count)) + goto done; + continue; + } + const vir_edge_t *other_edge = source->false_edge; + vir_inline_edge_args(other_edge, map, other); + vir_edge_args_t true_edge = {clone_of[taken->to->index], args, + taken->arg_count}; + vir_edge_args_t false_edge = {clone_of[other_edge->to->index], other, + other_edge->arg_count}; + if (!vir_block_set_branch(caller, block, + map[source->branch_condition->id], &true_edge, + &false_edge)) + goto done; + } + result_block = rest; +done: + free(map); + return result_block; +} + +/* Mark the blocks that lie on a cycle of @func: Tarjan's strongly connected + * components, iterative so a long CFG cannot exhaust the stack. LICM's natural + * loops need dominance, which is not built yet here, and would miss the + * irreducible cycles a goto can make. + */ +static bool vir_inline_cycles(const vir_function_t *func, unsigned char *cyclic) +{ + int count = func->block_count; + int *index = malloc(sizeof(int) * (size_t) count * 3); + vir_edge_t **cursor = malloc(sizeof(*cursor) * (size_t) count); + vir_block_t **stack = malloc(sizeof(*stack) * (size_t) count * 2); + if (!index || !cursor || !stack) { + free(index); + free(cursor); + free(stack); + return false; + } + int *low = index + count, *on_stack = low + count; + vir_block_t **walk = stack + count; + int next = 0, top = 0; + for (int i = 0; i < count; i++) { + index[i] = -1; + on_stack[i] = 0; + cyclic[i] = 0; + } + for (int root = 0; root < count; root++) { + if (index[root] >= 0) + continue; + int depth = 0; + walk[depth++] = func->block_table[root]; + index[root] = low[root] = next++; + stack[top++] = func->block_table[root]; + on_stack[root] = 1; + cursor[root] = func->block_table[root]->outgoing; + while (depth) { + vir_block_t *block = walk[depth - 1]; + vir_edge_t *edge = cursor[block->index]; + if (edge) { + cursor[block->index] = edge->next_outgoing; + int to = edge->to->index; + if (to == block->index) + cyclic[to] = 1; + if (index[to] < 0) { + index[to] = low[to] = next++; + stack[top++] = edge->to; + on_stack[to] = 1; + cursor[to] = edge->to->outgoing; + walk[depth++] = edge->to; + } else if (on_stack[to] && index[to] < low[block->index]) { + low[block->index] = index[to]; + } + continue; + } + depth--; + if (depth && low[block->index] < low[walk[depth - 1]->index]) + low[walk[depth - 1]->index] = low[block->index]; + if (low[block->index] != index[block->index]) + continue; + int first = top; + do + on_stack[stack[--first]->index] = 0; + while (stack[first] != block); + if (top - first > 1) + for (int k = first; k < top; k++) + cyclic[stack[k]->index] = 1; + top = first; + } + } + free(index); + free(cursor); + free(stack); + return true; +} + +int vir_inline_calls(vir_function_t *caller, + vir_inline_lookup_t lookup, + void *context, + vir_opt_level_t opt_level) +{ + if (!caller || !lookup || !vir_opt_level_valid(opt_level)) + return -1; + if (opt_level != VIR_OPT_O2) + return 0; + int inlined = 0, budget = VIR_INLINE_CALLER_BUDGET; + int original = caller->block_count; + + /* Loop membership decides only an extern callee too large to copy anywhere, + * so it is computed for the first such candidate. Inlining only appends + * blocks: cloned ones hold no call, and the rest of a split block is + * rescanned in its place. + */ + unsigned char *cyclic = NULL; + for (int index = 0; index < original; index++) { + vir_block_t *block = caller->block_table[index]; + for (vir_effect_t *effect = block->effects; effect;) { + if (effect->kind != VIR_EFFECT_CALL || !effect->callee) { + effect = effect->next; + continue; + } + bool is_static = false; + const vir_function_t *callee = + lookup(effect->callee, context, &is_static); + int cap = + is_static ? VIR_INLINE_STATIC_LIMIT : VIR_INLINE_LOOP_LIMIT; + if (cap > budget) + cap = budget; + int size = callee && callee != caller && + callee->pointer_bits == caller->pointer_bits + ? vir_inline_size(callee, effect, cap) + : -1; + if (size > VIR_INLINE_EXTERN_LIMIT && !is_static) { + if (!cyclic) { + /* Sized for the blocks earlier inlines appended too. */ + cyclic = calloc((size_t) caller->block_count + 1, 1); + if (!cyclic || !vir_inline_cycles(caller, cyclic)) { + free(cyclic); + return -1; + } + } + if (!cyclic[index]) + size = -1; + } + if (size < 0) { + effect = effect->next; + continue; + } + vir_block_t *rest = vir_inline_call(caller, effect, callee); + if (!rest) { + free(cyclic); + return -1; + } + budget -= size; + inlined++; + effect = rest->effects; + } + } + free(cyclic); + return inlined; +} + +/* vir_tail_recursion_type() has already proven every effect is a nonvariadic + * self call with matching arguments; check only how its result is returned. + */ +static vir_effect_t *vir_tail_call(vir_block_t *block, + vir_type_t type, + vir_value_t **addend) +{ + vir_value_t *returned = block->return_value; + vir_effect_t *call = block->last_effect; + *addend = NULL; + if (block->terminator != VIR_TERM_RETURN || !returned || !call || + !call->result || call->result_type != type || + (call->signature && call->signature->result.is_bool)) + return NULL; + vir_value_t *result = call->result; + if (!result->uses || result->uses->next) + return NULL; + if (returned == result) + return vir_use_return_block(result->uses) == block ? call : NULL; + if (returned->opcode != VIR_OP_ADD || returned->type != type || + vir_use_user(result->uses) != returned || !returned->uses || + returned->uses->next || vir_use_return_block(returned->uses) != block) + return NULL; + *addend = returned->op0 == result ? returned->op1 : returned->op0; + return call; +} + +static void vir_tail_unlink_value(vir_block_t *block, vir_value_t *value) +{ + for (int operand = 0; operand < value->nr_ops; operand++) + vir_remove_user_use(operand ? value->op1 : value->op0, value, operand); + vir_value_t **link = &block->head; + while (*link != value) + link = &(*link)->next; + *link = value->next; + value->next = NULL; + value->member = NULL; +} + +/* Return the common i32/i64 result type when FUNC may become a loop, or + * VIR_TYPE_VOID when a parameter, value, effect or return rules it out. + */ +static vir_type_t vir_tail_recursion_type(const vir_function_t *func, + const char *name) +{ + const vir_block_t *header = func->blocks; + int parameters = header->param_count; + vir_type_t type = VIR_TYPE_VOID; + bool self_tail = false; + /* Most functions end in no self call at all; say so before any scan. */ + for (const vir_block_t *block = header; block && !self_tail; + block = block->next) { + const vir_effect_t *call = block->last_effect; + self_tail = block->terminator == VIR_TERM_RETURN && call && + call->kind == VIR_EFFECT_CALL && call->callee && + !strcmp(call->callee, name); + } + if (!self_tail) + return VIR_TYPE_VOID; + for (vir_value_t *param = header->params; param; param = param->param_next) + if (!vir_type_is_i32_or_i64(param->type)) + return VIR_TYPE_VOID; + for (const vir_block_t *block = header; block; block = block->next) { + for (vir_effect_t *effect = block->effects; effect; + effect = effect->next) { + if (effect->kind != VIR_EFFECT_CALL || !effect->callee || + strcmp(effect->callee, name) || + effect->arg_count != parameters || + (effect->signature && effect->signature->is_variadic)) + return VIR_TYPE_VOID; + int index = 0; + for (vir_value_t *param = header->params; param; + param = param->param_next, index++) + if (effect->args[index]->type != param->type) + return VIR_TYPE_VOID; + } + for (vir_value_t *value = block->head; value; value = value->next) + if (value->opcode == VIR_OP_STACK_ADDR || + vir_opcode_may_trap(value->opcode)) + return VIR_TYPE_VOID; + if (block->terminator == VIR_TERM_RETURN) { + if (!block->return_value || + !vir_type_is_i32_or_i64(block->return_value->type) || + (type != VIR_TYPE_VOID && type != block->return_value->type)) + return VIR_TYPE_VOID; + type = block->return_value->type; + } + } + return type; +} + +/* Turn direct and additive tail recursion into a loop. Requiring scalar word + * parameters and no effects other than self calls avoids escaped stack objects, + * narrowing ABI conversions, memory ordering, and external function behavior. + */ +int vir_eliminate_tail_recursion(vir_function_t *func, + const char *name, + vir_opt_level_t opt_level) +{ + if (!func || !name || !vir_opt_level_valid(opt_level)) + return -1; + if (opt_level == VIR_OPT_O0 || !func->blocks || func->blocks->incoming) + return 0; + vir_type_t type = vir_tail_recursion_type(func, name); + if (type == VIR_TYPE_VOID) + return 0; + vir_block_t *header = func->blocks; + int parameters = header->param_count; + int candidates = 0; + for (vir_block_t *block = header; block; block = block->next) { + vir_value_t *addend; + if (vir_tail_call(block, type, &addend)) + candidates++; + } + if (!candidates || parameters >= INT_MAX / (int) sizeof(vir_value_t *) - 1) + return 0; + vir_value_t **args = malloc((size_t) (parameters + 1) * sizeof(*args)); + if (!args) + return -1; + int result = -1; + vir_block_t *last = func->last_block; + vir_block_t *entry = vir_block_create(func); + vir_value_t *accumulator = vir_block_add_param(func, header, type); + if (!entry || !accumulator) + goto fail; + int index = 0; + for (vir_value_t *param = header->params; param != accumulator; + param = param->param_next, index++) { + args[index] = vir_block_add_param(func, entry, param->type); + if (!args[index]) + goto fail; + } + args[parameters] = vir_const_int(func, entry, type, 0); + if (!args[parameters]) + goto fail; + + /* The new ABI entry initializes the accumulator; the original entry is now + * the recurrence header and receives every recursive argument in SSA. + */ + last->next = NULL; + entry->next = header; + func->blocks = entry; + func->last_block = last; + for (int i = func->block_count - 1; i > 0; i--) + func->block_table[i] = func->block_table[i - 1]; + func->block_table[0] = entry; + for (int i = 0; i < func->block_count; i++) + func->block_table[i]->index = i; + if (!vir_edge_create(func, entry, header, args, parameters + 1)) + goto fail; + for (vir_block_t *block = header; block; block = block->next) { + if (block->terminator != VIR_TERM_RETURN) + continue; + vir_value_t *addend; + vir_effect_t *call = vir_tail_call(block, type, &addend); + vir_value_t *returned = block->return_value; + vir_value_t *next = call && !addend ? accumulator + : vir_add(func, block, accumulator, + call ? addend : returned); + if (!next) + goto fail; + vir_remove_return_use(returned, block); + block->return_value = NULL; + block->terminator = VIR_TERM_NONE; + if (!call) { + if (!vir_block_set_return(func, block, next)) + goto fail; + continue; + } + for (int i = 0; i < parameters; i++) + args[i] = call->args[i]; + args[parameters] = next; + if (addend) + vir_tail_unlink_value(block, returned); + if (!vir_effect_unlink(call) || + !vir_edge_create(func, block, header, args, parameters + 1)) + goto fail; + } + result = candidates; +fail: + free(args); + return result; +} + +/* Coefficients in the result type's modular integer domain. */ +typedef struct { + unsigned long long accumulator, index, constant; +} vir_affine_form_t; + +static bool vir_affine_form(const vir_value_t *value, + const vir_value_t *iv, + const vir_value_t *accumulator, + vir_affine_form_t *form, + int depth, + int *budget) +{ + vir_affine_form_t left = {0}, right = {0}; + if (!value || depth == 32 || --*budget < 0 || value->type != iv->type) + return false; + memset(form, 0, sizeof(*form)); + if (value == iv) + form->index = 1; + else if (value == accumulator) + form->accumulator = 1; + else if (vir_is_constant(value)) + form->constant = value->constant; + else { + vir_opcode_t opcode = value->opcode; + if (value->is_block_param || value->def_effect || + (opcode != VIR_OP_ADD && opcode != VIR_OP_SUB && + opcode != VIR_OP_MUL && opcode != VIR_OP_SHL && + opcode != VIR_OP_NEG) || + !vir_affine_form(value->op0, iv, accumulator, &left, depth + 1, + budget)) + return false; + /* NEG is 0 - x and SHL by k is MUL by 2^k in the modular domain. */ + if (opcode == VIR_OP_NEG) { + right = left; + memset(&left, 0, sizeof(left)); + opcode = VIR_OP_SUB; + } else if (opcode == VIR_OP_SHL) { + if (!vir_is_constant(value->op1) || + value->op1->constant >= + (unsigned int) vir_integer_type_width(iv->type)) + return false; + right.constant = 1ULL << value->op1->constant; + opcode = VIR_OP_MUL; + } else if (!vir_affine_form(value->op1, iv, accumulator, &right, + depth + 1, budget)) + return false; + if (opcode == VIR_OP_MUL) { + if (!left.accumulator && !left.index) { + vir_affine_form_t swap = left; + left = right; + right = swap; + } + if (right.accumulator || right.index) + return false; + form->accumulator = left.accumulator * right.constant; + form->index = left.index * right.constant; + form->constant = left.constant * right.constant; + } else { + bool subtract = opcode == VIR_OP_SUB; + form->accumulator = + left.accumulator + + (subtract ? -right.accumulator : right.accumulator); + form->index = left.index + (subtract ? -right.index : right.index); + form->constant = + left.constant + (subtract ? -right.constant : right.constant); + } + } + unsigned long long mask = vir_integer_mask(iv->type); + form->accumulator &= mask; + form->index &= mask; + form->constant &= mask; + return true; +} + +/* Accept a single-entry straight-line loop with no observable body effects. + * Values produced in its body must not escape the loop: only header parameters + * have a well-defined value when the body is replaced by its summary. + */ +static vir_edge_t *vir_affine_loop_body(vir_block_t *header, + unsigned char *members, + int block_count) +{ + vir_block_t *block = header->true_edge->to; + if (block == header) + return NULL; + members[header->index] = 1; + for (;;) { + if (members[block->index] || block->effects || block->params || + block->terminator != VIR_TERM_JUMP || !block->outgoing || + block->outgoing->next_outgoing || !block->incoming || + block->incoming->next_incoming) + return NULL; + members[block->index] = 1; + if (block->outgoing->to == header) + break; + if (block->outgoing->arg_count) + return NULL; + block = block->outgoing->to; + } + for (vir_block_t *body = header->true_edge->to; body != header; + body = body->outgoing->to) { + for (vir_value_t *value = body->head; value; value = value->next) { + if (vir_opcode_is_division(value->opcode)) + return NULL; + for (vir_use_t *use = value->uses; use; use = use->next) { + vir_block_t *user = vir_use_block(use); + if (!user || user->index < 0 || user->index >= block_count || + !members[user->index]) + return NULL; + } + } + } + return block->outgoing; +} + +/* A loop header carrying two parameters, entered once and from one back edge, + * whose only control is its branch. + */ +static bool vir_two_param_loop_header(const vir_function_t *func, + const vir_block_t *header) +{ + return header != func->blocks && header->param_count == 2 && + !header->effects && header->terminator == VIR_TERM_BRANCH && + header->incoming && header->incoming->next_incoming && + !header->incoming->next_incoming->next_incoming; +} + +static int vir_summarize_affine_loop(vir_function_t *func, + vir_block_t *header, + unsigned char *members) +{ + if (!vir_two_param_loop_header(func, header)) + return 0; + for (vir_value_t *value = header->head; value; value = value->next) + if (vir_opcode_may_trap(value->opcode)) + return 0; + vir_value_t *condition = header->branch_condition; + if ((condition->opcode != VIR_OP_ULT && condition->opcode != VIR_OP_SLT) || + !vir_is_constant(condition->op1)) + return 0; + vir_value_t *iv = condition->op0; + if (!iv->is_block_param || iv->block != header || + !vir_type_is_i32_or_i64(iv->type)) + return 0; + int index = header->params == iv ? 0 : 1; + vir_value_t *accumulator = index ? header->params : iv->param_next; + if (accumulator->type != iv->type) + return 0; + vir_edge_t *back = vir_affine_loop_body(header, members, func->block_count); + bool leaves = back && !members[header->false_edge->to->index]; + /* Clear only the marked chain, so each candidate costs its body size. */ + members[header->index] = 0; + for (vir_block_t *block = header->true_edge->to; members[block->index]; + block = block->outgoing->to) + members[block->index] = 0; + if (!leaves) + return 0; + + /* The header has exactly two incoming edges and only the body's last block + * reaches it, so the other edge enters from outside the loop. + */ + vir_edge_t *entry = + header->incoming == back ? back->next_incoming : header->incoming; + if (!vir_is_constant(entry->args[index])) + return 0; + vir_affine_form_t increment, recurrence; + int budget = 256; + if (!vir_affine_form(back->args[index], iv, accumulator, &increment, 0, + &budget) || + increment.accumulator || increment.index != 1 || !increment.constant || + !vir_affine_form(back->args[1 - index], iv, accumulator, &recurrence, 0, + &budget)) + return 0; + unsigned long long first = entry->args[index]->constant; + unsigned long long limit = condition->op1->constant; + unsigned long long step = increment.constant; + unsigned long long mask = vir_integer_mask(iv->type); + unsigned long long maximum = + condition->opcode == VIR_OP_SLT ? mask >> 1 : mask; + if (first > maximum || limit > maximum || first >= limit) + return 0; + unsigned long long trips = (limit - first - 1) / step + 1; + if (trips > (maximum - first) / step) + return 0; + + /* Exponentiate (a,i) -> (A*a+B*i+C,i+D). Composition is exact modulo the + * integer width, including overflow in the accumulator. The separate + * trip-count proof above rules out wrapping the controlling induction. + */ + unsigned long long a = 1, b = 0, c = 0, d = 0; + unsigned long long aa = recurrence.accumulator, bb = recurrence.index; + unsigned long long cc = recurrence.constant, dd = step; + for (unsigned long long remaining = trips; remaining; remaining >>= 1) { + if (remaining & 1) { + c = (aa * c + bb * d + cc) & mask; + b = (aa * b + bb) & mask; + a = (aa * a) & mask; + d = (d + dd) & mask; + } + cc = (aa * cc + bb * dd + cc) & mask; + bb = (aa * bb + bb) & mask; + aa = (aa * aa) & mask; + dd = (dd + dd) & mask; + } + vir_block_t *pre = entry->from; + vir_value_t *scale = vir_const_int(func, pre, iv->type, a); + vir_value_t *bias = vir_const_int(func, pre, iv->type, b * first + c); + vir_value_t *final_iv = + vir_const_int(func, pre, iv->type, first + trips * step); + vir_value_t *product = + scale ? vir_mul(func, pre, entry->args[1 - index], scale) : NULL; + vir_value_t *final_acc = + product && bias ? vir_add(func, pre, product, bias) : NULL; + if (!final_acc || !final_iv) + return -1; + vir_set_use_slot(&entry->args[index], final_iv, &entry->arg_uses[index]); + vir_set_use_slot(&entry->args[1 - index], final_acc, + &entry->arg_uses[1 - index]); + return vir_edge_delete(func, header->true_edge) ? 1 : -1; +} + +int vir_summarize_affine_loops(vir_function_t *func, vir_opt_level_t opt_level) +{ + if (!func || !vir_opt_level_valid(opt_level)) + return -1; + if (opt_level == VIR_OPT_O0 || !func->blocks) + return 0; + unsigned char *members = + calloc((size_t) func->block_count, sizeof(*members)); + if (!members) + return -1; + int changed = 0; + for (vir_block_t *block = func->blocks; block; block = block->next) { + int result = vir_summarize_affine_loop(func, block, members); + if (result < 0) { + changed = -1; + break; + } + changed += result; + } + free(members); + if (changed > 0 && !vir_remove_unreachable(func)) + return -1; + return changed; +} + +/* Split a pointer address into a base and a constant byte offset. */ +static const vir_value_t *vir_unroll_base(const vir_value_t *pointer, + long long *offset, + int pointer_bits) +{ + *offset = 0; + if (pointer->opcode == VIR_OP_PTRADD && vir_is_constant(pointer->op1)) { + unsigned long long bits = pointer->op1->constant; + if (pointer_bits < 64 && (bits >> (pointer_bits - 1) & 1)) + bits |= ~0ULL << pointer_bits; + *offset = (long long) bits; + return pointer->op0; + } + return pointer; +} + +static vir_value_t *vir_sr_condition(vir_value_t *value, bool *positive); + +/* Unroll one counted pointer reduction by two into a second accumulator. The + * header carries an accumulator and a pointer and exits when the pointer equals + * a loop-invariant end; the body's only effect is one ordinary load through the + * pointer, which it adds to the accumulator before stepping the pointer by a + * constant. Start and end must share a base and lie an even number of steps + * apart, so no remainder iteration is needed, and modular addition makes the + * regrouping exact. The accumulator may only be read after the loop, where the + * two partial sums are added. + */ +static int vir_unroll_reduction(vir_function_t *func, + vir_block_t *header, + unsigned char *members) +{ + if (!vir_two_param_loop_header(func, header) || + header->true_edge->arg_count || header->false_edge->arg_count) + return 0; + bool positive = true; + vir_value_t *condition = + vir_sr_condition(header->branch_condition, &positive); + if (!condition || condition->opcode != VIR_OP_EQ) + return 0; + int index = + condition->op0->is_block_param && condition->op0->block == header ? 0 + : 1; + vir_value_t *pointer = index ? condition->op1 : condition->op0; + vir_value_t *end = index ? condition->op0 : condition->op1; + if (!pointer->is_block_param || pointer->block != header || + pointer->type != VIR_TYPE_PTR || end->type != VIR_TYPE_PTR) + return 0; + int pointer_index = header->params == pointer ? 0 : 1; + vir_value_t *accumulator = + pointer_index ? header->params : pointer->param_next; + if (!vir_type_is_i32_or_i64(accumulator->type)) + return 0; + + /* The body: a straight chain from the edge taken while the pointer has not + * reached the end back to the header. + */ + vir_block_t *after = + positive ? header->true_edge->to : header->false_edge->to; + vir_block_t *start = + positive ? header->false_edge->to : header->true_edge->to; + vir_block_t *block = start; + vir_effect_t *load = NULL; + if (start == header) + return 0; + members[header->index] = 1; + int length = 0; + for (;;) { + if (members[block->index] || block->params || ++length > 8 || + block->terminator != VIR_TERM_JUMP || !block->outgoing || + block->outgoing->next_outgoing || !block->incoming || + block->incoming->next_incoming) + return 0; + members[block->index] = 1; + for (vir_effect_t *effect = block->effects; effect; + effect = effect->next) { + if (load || effect->kind != VIR_EFFECT_LOAD || + effect->address != pointer || !effect->result || + effect->result->type != accumulator->type) + return 0; + load = effect; + } + if (block->outgoing->to != header && block->outgoing->arg_count) + return 0; + if (block->outgoing->to == header) + break; + block = block->outgoing->to; + } + if (!load || members[after->index] || after->incoming->next_incoming || + members[end->block->index]) + return 0; + vir_edge_t *back = block->outgoing; + vir_edge_t *entry = + header->incoming == back ? back->next_incoming : header->incoming; + vir_value_t *sum = back->args[1 - pointer_index]; + vir_value_t *next = back->args[pointer_index]; + long long step; + if (sum->opcode != VIR_OP_ADD || sum->type != accumulator->type || + !((sum->op0 == accumulator && sum->op1 == load->result) || + (sum->op1 == accumulator && sum->op0 == load->result)) || + vir_unroll_base(next, &step, func->pointer_bits) != pointer || !step) + return 0; + + /* Body values may leave the loop only through the back edge, and the + * accumulator only after it, through the exit. + */ + for (vir_block_t *body = start; body != header; body = body->outgoing->to) + for (vir_value_t *value = body->head; value; value = value->next) + for (vir_use_t *use = value->uses; use; use = use->next) + if (!members[vir_use_block(use)->index]) + return 0; + for (vir_use_t *use = accumulator->uses; use; use = use->next) + if (vir_use_user(use) != sum && vir_use_block(use) != after) + return 0; + + /* The unrolled loop skips every other pointer value; nothing else in the + * loop may observe one. After the loop it is the end either way. + */ + for (vir_use_t *use = pointer->uses; use; use = use->next) + if (vir_use_user(use) != condition && vir_use_user(use) != next && + vir_use_effect(use) != load && vir_use_block(use) != after) + return 0; + + /* An even trip count between constant offsets from one base. */ + long long first, last; + const vir_value_t *base = + vir_unroll_base(entry->args[pointer_index], &first, func->pointer_bits); + if (vir_unroll_base(end, &last, func->pointer_bits) != base) + return 0; + unsigned long long step_bits = (unsigned long long) step; + long long distance = last - first; + if (step == LLONG_MIN || distance % step || distance / step < 0 || + (distance / step) % 2) + return 0; + + vir_param_edit_t edit; + if (!vir_param_edit_prepare(func, header, NULL, header->param_count, + header->param_count, true, &edit)) + return -1; + vir_type_t width = func->pointer_bits == 64 ? VIR_TYPE_I64 : VIR_TYPE_I32; + vir_block_t *at = load->block; + vir_value_t *second = vir_value_alloc(func, header, accumulator->type); + vir_value_t *zero = vir_const_int(func, entry->from, accumulator->type, 0); + vir_value_t *offset = vir_const_int(func, at, width, step_bits); + vir_value_t *address = + offset ? vir_ptradd(func, at, pointer, offset) : NULL; + + /* The public builders take effects only while a block is open; the second + * load joins the first in a body block that already ends in its jump. + */ + vir_value_t *value = + address ? vir_load_impl(func, at, address, accumulator->type, false) + : NULL; + /* The new parameter joins the header only when the edit applies. */ + vir_value_t *second_sum = + value && second ? vir_user_operation(func, at, VIR_OP_ADD, + accumulator->type, second, value) + : NULL; + vir_value_t *double_step = + vir_const_int(func, back->from, width, step_bits * 2); + vir_value_t *advanced = + double_step ? vir_ptradd(func, back->from, pointer, double_step) : NULL; + if (!zero || !second_sum || !advanced) { + vir_param_edit_release(&edit); + return -1; + } + for (int i = 0; i < edit.count; i++) + edit.edges[i].inserted = + edit.edges[i].edge == entry ? zero : second_sum; + vir_param_edit_apply(header, second, &edit); + vir_param_edit_release(&edit); + vir_set_use_slot(&back->args[pointer_index], advanced, + &back->arg_uses[pointer_index]); + + /* Combine the partial sums at the top of the block after the loop, ahead of + * its effects too: a store or call there may read the result before any + * value of the block is defined. + */ + vir_value_t *old_tail = after->tail; + vir_value_t *total = vir_add(func, after, accumulator, second); + if (!total) + return -1; + if (old_tail) { + old_tail->next = NULL; + total->next = after->head; + after->head = total; + } + total->order = -1; + vir_repair_value_positions(after); + for (vir_use_t *use = accumulator->uses, *following; use; use = following) { + following = use->next; + if (vir_use_user(use) != sum && vir_use_user(use) != total) + vir_set_use_slot(vir_use_value_slot(use), total, use); + } + return 1; +} + +int vir_unroll_reductions(vir_function_t *func, vir_opt_level_t opt_level) +{ + if (!func || !vir_opt_level_valid(opt_level)) + return -1; + if (opt_level != VIR_OPT_O2 || !func->blocks) + return 0; + unsigned char *members = + calloc((size_t) func->block_count, sizeof(*members)); + if (!members) + return -1; + int changed = 0; + for (vir_block_t *block = func->blocks; block; block = block->next) { + int result = vir_unroll_reduction(func, block, members); + + /* A candidate marks its header before any body block, so most blocks + * leave nothing to clear. + */ + if (members[block->index]) + memset(members, 0, (size_t) func->block_count); + if (result < 0) { + changed = -1; + break; + } + changed += result; + } + free(members); + return changed; +} + /* An affine offset is safe to recur only while every narrower intermediate * stays nonnegative and fits its signed type. Pointer-width arithmetic itself * is modular, so ILP32 does not need the LP64 narrowing proof. @@ -4259,7 +5628,13 @@ static vir_value_t *vir_sr_base(vir_function_t *func, typedef struct { unsigned long long first; unsigned long long step; + + /* A runtime first offset, and a constant byte bias applied to the pointer + * it gives: the subscript's own address is then shared, and the bias folds + * into an addressing displacement. + */ vir_value_t *dynamic_first; + unsigned long long dynamic_bias; } vir_sr_offset_t; static int vir_sr_pointer(vir_function_t *func, @@ -4281,10 +5656,18 @@ static int vir_sr_pointer(vir_function_t *func, : initial->opcode == VIR_OP_PTRADD && initial->op0 == base && vir_is_const_int(initial->op1, first); - if (offsets->dynamic_first) - same_initial = initial->opcode == VIR_OP_PTRADD && - initial->op0 == base && - initial->op1 == offsets->dynamic_first; + if (offsets->dynamic_first) { + vir_value_t *scaled = initial; + if (offsets->dynamic_bias) + scaled = initial->opcode == VIR_OP_PTRADD && + vir_is_const_int(initial->op1, + offsets->dynamic_bias) + ? initial->op0 + : NULL; + same_initial = scaled && scaled->opcode == VIR_OP_PTRADD && + scaled->op0 == base && + scaled->op1 == offsets->dynamic_first; + } if (param->type == VIR_TYPE_PTR && same_initial && next->opcode == VIR_OP_PTRADD && next->op0 == param && vir_is_const_int(next->op1, offsets->step)) { @@ -4302,8 +5685,21 @@ static int vir_sr_pointer(vir_function_t *func, vir_value_t *offset = offsets->dynamic_first ? offsets->dynamic_first : vir_const_int(func, entry->from, width, first); - vir_value_t *initial = - offset ? vir_ptradd(func, entry->from, base, offset) : NULL; + vir_value_t *initial = NULL; + /* The subscript usually computed this address already, before the loop. */ + if (offsets->dynamic_first) + for (vir_value_t *value = entry->from->head; value && !initial; + value = value->next) + if (value->opcode == VIR_OP_PTRADD && value->op0 == base && + vir_same_address(value->op1, offsets->dynamic_first, 4)) + initial = value; + if (!initial && offset) + initial = vir_ptradd(func, entry->from, base, offset); + if (initial && offsets->dynamic_first && offsets->dynamic_bias) { + vir_value_t *bias = + vir_const_int(func, entry->from, width, offsets->dynamic_bias); + initial = bias ? vir_ptradd(func, entry->from, initial, bias) : NULL; + } vir_value_t *step = vir_const_int(func, back->from, width, offsets->step); vir_value_t *param = vir_value_alloc(func, header, VIR_TYPE_PTR); vir_value_t *next = @@ -4691,21 +6087,22 @@ int vir_strength_reduce(vir_function_t *func, vir_opt_level_t opt_level) } vir_value_t *dynamic_first = NULL; if (descending) { - dynamic_first = + /* Scale the index exactly as normalization rewrote the + * subscript, so vir_sr_pointer() finds the subscript's own + * address in the entry block; the bias is applied to it. + */ + vir_value_t *widened = vir_zext(func, entry->from, initial, VIR_TYPE_I64); vir_value_t *factor = vir_const_int(func, entry->from, VIR_TYPE_I64, scale); - dynamic_first = - dynamic_first && factor - ? vir_mul(func, entry->from, dynamic_first, factor) - : NULL; - if (dynamic_first && bias) { - vir_value_t *add = vir_const_int(func, entry->from, - VIR_TYPE_I64, bias); + dynamic_first = widened && factor + ? vir_strength_reduce_integer( + func, entry->from, widened, + factor, VIR_OP_MUL) + : NULL; + if (!dynamic_first && widened && factor) dynamic_first = - add ? vir_add(func, entry->from, dynamic_first, add) - : NULL; - } + vir_mul(func, entry->from, widened, factor); if (!dynamic_first) { changed = -1; goto done; @@ -4715,7 +6112,8 @@ int vir_strength_reduce(vir_function_t *func, vir_opt_level_t opt_level) descending ? 0 : (initial->constant * scale + bias) & vir_width_mask(func->pointer_bits), - descending ? 0ULL - scale : scale, dynamic_first}; + descending ? 0ULL - scale : scale, dynamic_first, + descending ? bias : 0}; if (vir_sr_pointer(func, header, entry, back, address, base, &offsets, &pointer) < 0) { changed = -1; diff --git a/src/vir.h b/src/vir.h index 55bbbb1e..64a5a5de 100644 --- a/src/vir.h +++ b/src/vir.h @@ -569,6 +569,35 @@ vir_value_t *vir_ptradd(vir_function_t *func, int vir_replace_all_uses(vir_function_t *func, vir_value_t *from, vir_value_t *to); + +/* Reapply integer/bitwise/unary identities after SSA/CSE; returns replacements, + * -1 for invalid arguments, or 0 at O0. Allocation failure skips that fold; any + * values a partial strength reduction built stay unused for DCE. + */ +int vir_normalize(vir_function_t *func, vir_opt_level_t opt_level); +/* Summarize proven finite, effect-free modular affine recurrences. */ +int vir_summarize_affine_loops(vir_function_t *func, vir_opt_level_t opt_level); +/* Unroll even-count pointer add reductions by two into a second accumulator. */ +int vir_unroll_reductions(vir_function_t *func, vir_opt_level_t opt_level); + +/* The graph of a direct callee the caller may inline, or NULL. *@is_static is + * set for a static callee, which vir_inline_calls() copies at a larger size. + */ +typedef const vir_function_t *(*vir_inline_lookup_t)(const char *name, + void *context, + bool *is_static); + +/* Replace calls to small leaf callees with copies of their graphs. + * + * Returns the number inlined, or -1 when the graph could not be rewritten. + */ +int vir_inline_calls(vir_function_t *caller, + vir_inline_lookup_t lookup, + void *context, + vir_opt_level_t opt_level); +int vir_eliminate_tail_recursion(vir_function_t *func, + const char *name, + vir_opt_level_t opt_level); int vir_local_cse(vir_function_t *func, vir_opt_level_t opt_level); int vir_strength_reduce(vir_function_t *func, vir_opt_level_t opt_level); int vir_reassociate(vir_function_t *func, vir_opt_level_t opt_level); diff --git a/src/x64-codegen.c b/src/x64-codegen.c index 4586502b..ec772cba 100644 --- a/src/x64-codegen.c +++ b/src/x64-codegen.c @@ -387,6 +387,12 @@ vir_mop_t *emit_next_ir; int fused_cc; bool fused_cc_pending; +/* The register an AND would have written when it emitted only a TEST for the + * "eq x, 0" and branch right behind it; that comparison must use the flags. -1 + * when unused. + */ +int and_test_dest = -1; + /* With fused_cc_pending, a comparison of two known constants that emitted * nothing at all: 1 when its branch goes to the then block, 0 to the else. */ @@ -797,7 +803,7 @@ void fold_state_check(int first_ir) { bool pending = addr_fold || addr_sib || cmp_mem_slot >= 0 || cmp_mem_read || src0_override >= 0 || fused_cc_pending || - gaddr_pending_ir >= 0; + gaddr_pending_ir >= 0 || and_test_dest >= 0; for (int i = 0; i < MAX_SKIP_IR; i++) { if (skip_ir_index[i] >= first_ir) @@ -871,6 +877,36 @@ static bool branch_result_dead(int reg) !(bb->machine_liveout & (1u << reg)); } +/* Whether the AND being emitted feeds only "load_constant 0; eq; branch" in its + * block, the shape a masked test against zero lowers to. The comparison then + * fuses into the branch on the AND's flags, so a TEST can stand in for the AND + * when neither its result nor the comparison's leaves the block. + */ +static bool and_feeds_zero_branch(const vir_mop_t *and_mop) +{ + int zero = emit_ir_index + 1, eq = zero + 1, branch = eq + 1; + if (branch >= mop_flat_count || branch >= instruction_to_bb_capacity || + instruction_to_bb[branch] != instruction_to_bb[emit_ir_index]) + return false; + const vir_mop_t *constant = MOP_FLAT[zero]; + const vir_mop_t *compare = MOP_FLAT[eq]; + + /* The comparison reuses the flags only when it sees the zero as a known + * constant, which the table records for a register destination alone. + */ + return constant->op == OP_load_constant && constant->src0 == 0 && + constant->src1 == 0 && constant->dest >= 0 && + constant->dest < REG_CNT && compare->op == OP_eq && + compare->src0 == and_mop->dest && compare->src1 == constant->dest && + compare->src0 != compare->src1 && + compare->size_bytes == and_mop->size_bytes && + (and_mop->size_bytes == 4 || and_mop->size_bytes == 8) && + MOP_FLAT[branch]->op == OP_branch && + MOP_FLAT[branch]->src0 == compare->dest && + branch_result_dead(compare->dest) && + branch_result_dead(and_mop->dest); +} + /* The opcode extension selecting an operation in the 0x81/0x83 group, or -1. */ int alu_ext_for(opcode_t op) { @@ -3112,18 +3148,30 @@ void emit_mop(vir_mop_t *mop) reg_mirror_size[mop->dest] = load_width(mop); reg_mirror_sext[mop->dest] = false; } else if (mop->op == OP_store) { + /* A load elided into src0_override leaves its value in another + * register, which is the one this store writes and which mirrors + * the slot afterwards; the IR source never received it. + */ + int stored = mop->src0; + if (stored >= 0 && stored < REG_CNT && rs1 != map_ir_reg(stored)) { + stored = -1; + for (int i = 0; i < REG_CNT; i++) + if (map_ir_reg(i) == rs1) + stored = i; + } + /* 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 (mop->src0 >= 0 && mop->src0 < REG_CNT && !mop->is_volatile && - reg_mirror_valid[mop->src0] && - reg_mirror_slot[mop->src0] == mop->src1) { + if (stored >= 0 && stored < REG_CNT && !mop->is_volatile && + reg_mirror_valid[stored] && + reg_mirror_slot[stored] == mop->src1) { int keep = mop->size_bytes; if (mop->is_pointer && keep != PTR_SIZE) keep = PTR_SIZE; - if (reg_mirror_size[mop->src0] == keep) + if (reg_mirror_size[stored] == keep) return; } @@ -3141,15 +3189,15 @@ void emit_mop(vir_mop_t *mop) * slot sign-extends what was written -- which the sext flag * records, so the reload becomes a MOVSX rather than a copy. */ - if (mop->src0 >= 0 && mop->src0 < REG_CNT) { + if (stored >= 0 && stored < REG_CNT) { int eff = mop->size_bytes; if (mop->is_pointer && eff != PTR_SIZE) eff = PTR_SIZE; if (eff == 1 || eff == 2 || eff == 4 || eff == 8) { - reg_mirror_valid[mop->src0] = true; - reg_mirror_slot[mop->src0] = mop->src1; - reg_mirror_size[mop->src0] = eff; - reg_mirror_sext[mop->src0] = eff < 8; + reg_mirror_valid[stored] = true; + reg_mirror_slot[stored] = mop->src1; + reg_mirror_size[stored] = eff; + reg_mirror_sext[stored] = eff < 8; } } } @@ -3217,6 +3265,14 @@ void emit_mop(vir_mop_t *mop) MOP_FLAT[producer]->dest == mop->src0 && MOP_FLAT[producer]->size_bytes == mop->size_bytes && (mop->size_bytes == 4 || mop->size_bytes == 8); + if (and_test_dest >= 0) { + /* The AND emitted only a TEST, so its register holds nothing: these + * flags are the only record of the masked value. + */ + if (!flags_ready || and_test_dest != mop->src0) + fatal("x64: a TEST stood in for an AND the comparison skipped"); + and_test_dest = -1; + } if (!flags_ready) emit_cmp(mop->size_bytes, rs1, rs2); else @@ -3232,9 +3288,12 @@ void emit_mop(vir_mop_t *mop) * otherwise emit -- three instructions down to one. Only when the masked * value is dead afterwards, since TEST does not write it. */ - if (mop->op == OP_bit_and && next_in_same_bb() && - emit_next_ir->op == OP_branch && emit_next_ir->src0 == mop->dest && - !folds_off && branch_result_dead(mop->dest)) { + bool and_branches = mop->op == OP_bit_and && !folds_off && + next_in_same_bb() && emit_next_ir->op == OP_branch && + emit_next_ir->src0 == mop->dest && + branch_result_dead(mop->dest); + if (and_branches || + (mop->op == OP_bit_and && !folds_off && and_feeds_zero_branch(mop))) { bool wide = test_is_wide(mop->size_bytes, mop->is_pointer); if (src1_const_known && src1_const >= 0 && src1_const < 0x80) { @@ -3255,8 +3314,11 @@ void emit_mop(vir_mop_t *mop) } else { emit_opcode_reg(0x85, rs1, rs2, wide); /* TEST rs1, rs2 */ } - fused_cc = 0x85; /* JNZ */ - fused_cc_pending = true; + if (and_branches) { + fused_cc = 0x85; /* JNZ */ + fused_cc_pending = true; + } else + and_test_dest = mop->dest; return; } @@ -3931,6 +3993,7 @@ void code_generate(void) src0_override_at = -1; x64_frame_bias = 0; cmp_mem_slot = -1; + and_test_dest = -1; addr_fold = false; addr_sib = false; skip_ir_reset(); @@ -4248,6 +4311,7 @@ void code_generate(void) src0_override = -1; src0_override_at = -1; cmp_mem_slot = -1; + and_test_dest = -1; cmp_mem_read = false; addr_fold = false; addr_sib = false; diff --git a/tests/driver.sh b/tests/driver.sh index cb8db1d4..4bb313aa 100755 --- a/tests/driver.sh +++ b/tests/driver.sh @@ -854,13 +854,7 @@ EOF try_ir_count 0 dot ' = const\.i(32|64) 1$' << EOF int a[8][64]; int b[64][8]; -int dot(int i) -{ - int s = 0; - for (int k = 0; k < 64; k++) - s += a[i][k] * b[k][i]; - return s; -} +int dot(int i); int main() { for (int i = 0; i < 8; i++) @@ -870,6 +864,13 @@ int main() } return dot(3) != 49920; } +int dot(int i) +{ + int s = 0; + for (int k = 0; k < 64; k++) + s += a[i][k] * b[k][i]; + return s; +} EOF try_ 0 << EOF int a[8][64]; @@ -929,13 +930,7 @@ EOF try_ir_count 0 dot ' = const\.i(32|64) 1$' << EOF int a[64]; int b[64]; -int dot(void) -{ - int s = 0; - for (int k = 0; k < 64; k++) - s += a[k] * b[k]; - return s; -} +int dot(void); int main() { for (int i = 0; i < 64; i++) { @@ -944,6 +939,13 @@ int main() } return dot() != 43680; } +int dot(void) +{ + int s = 0; + for (int k = 0; k < 64; k++) + s += a[k] * b[k]; + return s; +} EOF try_ 0 << EOF int a[64]; @@ -1136,6 +1138,20 @@ EOF try_ir_count 0 reuse ' = const\.i(32|64) 1$' << EOF int a[64], b[64]; int m[8][64]; +int reuse(void); +int incl(int r); +void bump(int r); +int main() +{ + for (int i = 0; i < 64; i++) { + a[i] = i; + b[i] = 64 - i; + for (int r = 0; r < 8; r++) + m[r][i] = r * 100 + i; + } + bump(3); + return (reuse() + incl(3) + m[3][5]) & 255; +} int reuse(void) { int s = 0, i; @@ -1159,6 +1175,13 @@ void bump(int r) for (int k = 0; k < 64; k++) m[r][k] = m[r][k] + 1; } +EOF +try_ir_count 2 bump ptrstride:4 << EOF +int a[64], b[64]; +int m[8][64]; +int reuse(void); +int incl(int r); +void bump(int r); int main() { for (int i = 0; i < 64; i++) { @@ -1170,10 +1193,6 @@ int main() bump(3); return (reuse() + incl(3) + m[3][5]) & 255; } -EOF -try_ir_count 2 bump ptrstride:4 << EOF -int a[64], b[64]; -int m[8][64]; int reuse(void) { int s = 0, i; @@ -1197,30 +1216,13 @@ void bump(int r) for (int k = 0; k < 64; k++) m[r][k] = m[r][k] + 1; } -int main() -{ - for (int i = 0; i < 64; i++) { - a[i] = i; - b[i] = 64 - i; - for (int r = 0; r < 8; r++) - m[r][i] = r * 100 + i; - } - bump(3); - return (reuse() + incl(3) + m[3][5]) & 255; -} EOF # "i++" keeps i's old value in a temporary and adds to that. The counter search # has to look through the copy, or no for loop written with ++ is reduced: the # address a[i * 4] then advances by 16 each trip instead of being rebuilt. try_ir_count "$((2 - PTR_SZ / 4))" stride ptrstride:16 << EOF -int stride(int *a, int n) -{ - int s = 0; - for (int i = 0; i < n; i++) - s = s + a[i * 4]; - return s; -} +int stride(int *a, int n); int main() { int a[16]; @@ -1228,6 +1230,13 @@ int main() a[i] = i; return stride(a, 4) != 24; } +int stride(int *a, int n) +{ + int s = 0; + for (int i = 0; i < n; i++) + s = s + a[i * 4]; + return s; +} EOF try_file 0 '' "$TESTS_DIR/escaped-param.c" @@ -2484,6 +2493,8 @@ EOF # stands in for it. try_ir_count 5 f ' = volatile\.load\.i(8|16|32|64) ' << EOF volatile int status; +int f(void); +int main(void) { return f() - 1; } int f(void) { status; @@ -2494,24 +2505,26 @@ int f(void) break; return 1; } -int main(void) { return f() - 1; } EOF # A postfix update of a volatile object reads it once, where it is evaluated. try_ir_count 2 f ' = volatile\.load\.i(8|16|32|64) ' << EOF volatile int status; +int f(void); +int main(void) { return f() - 1; } int f(void) { status++; status--, 0; return 1; } -int main(void) { return f() - 1; } EOF # A volatile local lives in its slot: storing it does not let the next read # reuse the stored register. try_ir_count 2 f ' = volatile\.load\.i(8|16|32|64) ' << EOF +int f(void); +int main(void) { return f(); } int f(void) { volatile int local = 1; @@ -2519,7 +2532,6 @@ int f(void) local; return 0; } -int main(void) { return f(); } EOF # Reads through a pointer to volatile, of a member of a volatile record and of @@ -2530,6 +2542,8 @@ volatile int *p; volatile struct S s; volatile int a[4]; volatile struct S *sp; +int f(void); +int main(void) { return f() - 7; } int f(void) { *p; @@ -2538,59 +2552,63 @@ int f(void) sp->a; return 7; } -int main(void) { return f() - 7; } EOF # A volatile parameter lives in its slot as a volatile local does: it is stored # there on entry, each read reloads it and each assignment stores it, the # register the value was just computed in notwithstanding. try_ir_count 2 f ' = volatile\.load\.i(8|16|32|64) ' << EOF +int f(volatile int x); +int main(void) { return f(1); } int f(volatile int x) { x; x; return 0; } -int main(void) { return f(1); } EOF try_ir_count 2 f '^[[:space:]]+volatile\.store ' << EOF +int f(void); +int main(void) { return f() - 5; } int f(void) { volatile int local = 4; local = local + 1; return local; } -int main(void) { return f() - 5; } EOF # A volatile long long is one typed memory effect, including on targets that # lower it to a register pair. Parameter initialization is a volatile store. try_ir_count 1 f ' = volatile\.load\.i(8|16|32|64) ' << EOF volatile long long wide; +int f(void); +int main(void) { return f(); } int f(void) { wide; return 0; } -int main(void) { return f(); } EOF try_ir_count 3 f ' = volatile\.load\.i(8|16|32|64) ' << EOF +int f(volatile long long y); +int main(void) { return f(3) - 4; } int f(volatile long long y) { y; y = y + 1; return (int) y; } -int main(void) { return f(3) - 4; } EOF try_ir_count 2 f '^[[:space:]]+volatile\.store ' << EOF +int f(volatile long long y); +int main(void) { return f(3) - 4; } int f(volatile long long y) { y; y = y + 1; return (int) y; } -int main(void) { return f(3) - 4; } EOF try_ 10 << EOF volatile long long vw; @@ -2609,6 +2627,8 @@ EOF # and an object nothing reads by name included. try_ir_count 3 f '^[[:space:]]+volatile\.store ' << EOF volatile int control; +int f(void); +int main(void) { return f(); } int f(void) { control = 1; @@ -2616,9 +2636,10 @@ int f(void) control = 1; return 0; } -int main(void) { return f(); } EOF try_ir_count 3 f '^[[:space:]]+volatile\.store ' << EOF +int f(void); +int main(void) { return f(); } int f(void) { volatile int local; @@ -2627,9 +2648,10 @@ int f(void) local = 1; return 0; } -int main(void) { return f(); } EOF try_ir_count 4 f '^[[:space:]]+volatile\.store ' << EOF +int f(volatile int x); +int main(void) { return f(0); } int f(volatile int x) { x = 1; @@ -2637,12 +2659,18 @@ int f(volatile int x) x = 1; return 0; } -int main(void) { return f(0); } EOF try_ir_count 5 f '^[[:space:]]+volatile\.store ' << EOF struct S { int a; int b; }; volatile int *p; volatile struct S s; +int f(void); +int main(void) +{ + static int backing; + p = &backing; + return f(); +} int f(void) { *p = 1; @@ -2652,15 +2680,11 @@ int f(void) s.b = 1; return 0; } -int main(void) -{ - static int backing; - p = &backing; - return f(); -} EOF try_ir_count 3 f '^[[:space:]]+volatile\.store ' << EOF volatile long long wide; +int f(void); +int main(void) { return f(); } int f(void) { wide = 1; @@ -2668,9 +2692,10 @@ int f(void) wide = 1; return 0; } -int main(void) { return f(); } EOF try_ir_count 3 f '^[[:space:]]+volatile\.store ' << EOF +int f(void); +int main(void) { return f(); } int f(void) { volatile long long local; @@ -2679,7 +2704,6 @@ int f(void) local = 1; return 0; } -int main(void) { return f(); } EOF try_ 7 << EOF volatile long long wide; @@ -2701,6 +2725,8 @@ EOF # unit once, for the bits it keeps. try_ir_count 0 f ' = volatile\.load\.i(8|16|32|64) ' << EOF volatile int v; +int f(void); +int main(void) { return f(); } int f(void) { volatile int local; @@ -2709,17 +2735,17 @@ int f(void) v = 4, local = 4; return 0; } -int main(void) { return f(); } EOF try_ir_count 1 f ' = volatile\.load\.i(8|16|32|64) ' << EOF volatile struct S { int a; int b : 3; } s; +int f(void); +int main(void) { return f(); } int f(void) { s.a = 3; s.b = 1; return 0; } -int main(void) { return f(); } EOF try_ 2 << EOF struct S { int a; int b : 3; }; @@ -2738,22 +2764,24 @@ EOF try_ir_count 1 f ' = volatile\.load\.i(8|16|32|64) ' << EOF volatile int v; int x; +int f(void); +int main(void) { return f(); } int f(void) { x = v; sizeof v; return 0; } -int main(void) { return f(); } EOF try_ir_count 0 f ' = volatile\.load\.i(8|16|32|64) ' << EOF volatile int *p; +int f(void); +int main(void) { return f(); } int f(void) { *p = 1; return 0; } -int main(void) { return f(); } EOF # Reading those objects still yields their values. diff --git a/tests/native-throughput.py b/tests/native-throughput.py index 311664be..ad4def16 100755 --- a/tests/native-throughput.py +++ b/tests/native-throughput.py @@ -12,6 +12,12 @@ import subprocess import time +def geometric_mean(values): + """Return None rather than raising when a sample is zero, e.g. coarse CPU time.""" + values = list(values) + return statistics.geometric_mean(values) if values and min(values) > 0 else None + + NAMES = ('rng', 'branch', 'pointer', 'recursion', 'calls', 'sort', 'indexed') FAILURE_OUTPUT = None EXPECTED = (375426474, 2594414144, 2764537856, 7856720, 447236269, @@ -124,6 +130,13 @@ def main(): 'throughput': throughput, 'target_pass': min(throughput.values()) >= args.target} print(name, json.dumps(throughput), flush=True) (output / 'results.json').write_text(json.dumps(proof, indent=2) + '\n') + proof['summary'] = { + 'geometric_mean_throughput': { + kind: geometric_mean(row['throughput'][kind] for row in proof['results'].values()) + for kind in ('wall', 'cpu')}, + 'workloads_at_target': sum(row['target_pass'] for row in proof['results'].values()), + 'workloads': len(proof['results'])} + print('summary', json.dumps(proof['summary']), flush=True) after = {str(path): sha(path) for path in tracked} proof['sha256_after'] = after proof['environment']['load_after'] = os.getloadavg() diff --git a/tests/native-throughput/README.md b/tests/native-throughput/README.md index 52574228..1fac0b76 100644 --- a/tests/native-throughput/README.md +++ b/tests/native-throughput/README.md @@ -30,23 +30,40 @@ and system load. Build/runtime failure writes `failure.json` and returns nonzero completed results remain available. Results describe these seven synthetic workloads on the measured host. -## Native measurements - -October 6, 2026: nine rounds of eight executions on each host; all 1,022 checksum -executions passed per host. Results below are throughput relative to GCC `-O1`. -ARM64: native eMag, Linux 6.8, GCC 13.3, CPU 31, performance governor restored -after measurement. x86-64: Ryzen Threadripper 2990WX, Linux 6.8, GCC 14.2, -CPU 63, unchanged schedutil governor. - -| Workload | ARM64 wall | ARM64 CPU | x86-64 wall | x86-64 CPU | -| --- | ---: | ---: | ---: | ---: | -| rng | 83.74% | 83.41% | 80.74% | 80.28% | -| branch | 92.50% | 92.35% | 91.89% | 91.66% | -| pointer | 89.34% | 88.83% | 82.83% | 81.33% | -| recursion | 89.88% | 89.69% | 92.93% | 92.71% | -| calls | 87.47% | 86.12% | 101.52% | 101.83% | -| sort | 81.52% | 81.28% | 86.57% | 86.48% | -| indexed | 89.29% | 88.85% | 99.67% | 99.73% | +## Latest results + +Native x86-64: AMD Ryzen Threadripper 2990WX, Linux 6.8, GCC 14.2.0 +`-O1 -fwrapv`, CPU 63, unchanged schedutil governor, nine rotating rounds of +eight executions after warmup, `--target 1`. Every checksum execution passed and +the source/compiler/binary SHA256 checks were unchanged. Throughput is GCC time +divided by shecc time; values above 1 favor shecc. + +| Workload | Wall | Child CPU | +| --- | ---: | ---: | +| rng | 150.09x | 265.18x | +| branch | 0.97x | 0.97x | +| pointer | 1.41x | 1.48x | +| recursion | 1.18x | 1.19x | +| calls | 1.00x | 1.00x | +| sort | 1.11x | 1.11x | +| indexed | 1.00x | 1.00x | + +The wall geometric mean is 2.23x and the child-CPU geometric mean 2.43x. RNG's +constant-trip, effect-free affine loop is summarized away, so its process time +is startup dominated. Five of seven workloads meet the strict per-workload +target; branch stays below GCC and calls is at parity, so the command returns +status 1. The geometric mean is descriptive and does not replace the gate. + +```sh +python3 tests/native-throughput.py --shecc out/shecc \ + --output out/throughput --rounds 9 --runs 8 --target 1 +``` + +To compare two compilers, save the baseline binary before rebuilding and run +the same command against each, serially and without concurrent build or test +jobs. Each output directory contains `results.json`, commands, environment, raw +samples, and SHA256 provenance. Use fresh output directories for another +measurement. Replace this section with the newest run rather than appending. ## Compilation time @@ -68,3 +85,32 @@ Add `--no-libc` to measure programs that omit shecc's bundled libc. These measure compilation speed separately from the generated-program throughput measurements above. Results depend on the input and native host. + +### Profiling compilation with uftrace + +Build a separate instrumented host compiler after `make` for the native target: +`src/main.c` includes the generated `config` and `out/libc.inc`. +Use a shallow trace to find expensive phases, then restrict recording to a hot +function. Disabling host inlining exposes helper call counts but increases tracing +overhead; instrumented timings are for attribution, not speedup claims. + +```sh +mkdir -p out/uftrace-compile +cc -O2 -g -std=c99 -pedantic -fwrapv -pg -fno-omit-frame-pointer \ + src/main.c -o out/uftrace-compile/shecc-profile +uftrace record --no-libcall -D 5 -d out/uftrace-compile/phases \ + out/uftrace-compile/shecc-profile -o out/uftrace-compile/self.elf src/main.c +uftrace report -d out/uftrace-compile/phases -s self --color=no +cc -O2 -g -std=c99 -pedantic -fwrapv -pg -fno-inline \ + -fno-omit-frame-pointer src/main.c -o out/uftrace-compile/shecc-detail +uftrace record --no-libcall -F vir_dce_with_stats -D 4 \ + -d out/uftrace-compile/dce out/uftrace-compile/shecc-detail \ + -o out/uftrace-compile/self-detail.elf src/main.c +uftrace report -d out/uftrace-compile/dce -s self --color=no +``` + +Compare an optimization by self-compiling a frozen copy of `src`, `config`, and +`out/libc.inc` with the before and after compilers, alternating rounds after +warmup, and require byte-identical ELF output from every pair. Prefer +instruction counts (`perf stat -e instructions:u`) over wall time on a loaded +host. diff --git a/tests/vir-abstract-return-qualifiers.c b/tests/vir-abstract-return-qualifiers.c index 135a0958..2d46e37d 100644 --- a/tests/vir-abstract-return-qualifiers.c +++ b/tests/vir-abstract-return-qualifiers.c @@ -1,5 +1,15 @@ int value = 7; int *pointer = &value; +volatile int *get(void); +int **get_pointer(void); +int touch(void); +volatile int *(*global_get)(void) = (volatile int *(*) (void) ) get; +volatile int *(**global_slot)(void) = &(volatile int *(*) (void) ) {get}; +int *(*outer_cast)(void) = (int *(*) (void) )(const int *(*) (void) ) get; +int main(void) +{ + return touch() != 7 || *outer_cast() != 7; +} volatile int *get(void) { return &value; @@ -8,9 +18,6 @@ int **get_pointer(void) { return &pointer; } -volatile int *(*global_get)(void) = (volatile int *(*) (void) ) get; -volatile int *(**global_slot)(void) = &(volatile int *(*) (void) ) {get}; -int *(*outer_cast)(void) = (int *(*) (void) )(const int *(*) (void) ) get; int touch(void) { volatile int *(*local)(void) = (volatile int *(*) (void) ) get; @@ -19,7 +26,3 @@ int touch(void) *(*global_slot)(); return **((int *volatile *(*) (void) ) get_pointer)(); } -int main(void) -{ - return touch() != 7 || *outer_cast() != 7; -} diff --git a/tests/vir-adjusted-array.c b/tests/vir-adjusted-array.c index bf175f19..a42d5452 100644 --- a/tests/vir-adjusted-array.c +++ b/tests/vir-adjusted-array.c @@ -1,6 +1,24 @@ struct item { int value; }; +static int fixed(int p[2], int *replacement); +static int unsized(int p[], int *replacement); +static int record(struct item p[], struct item *replacement); +static int ordinary(int p[]); +int main(void) +{ + int first[2] = {1, 2}, second[2] = {7, 8}; + struct item a[1] = {{3}}, b[1] = {{9}}; + if (fixed(first, second) != 7) + return 1; + if (unsized(first, second) != 7) + return 2; + if (record(a, b) != 9) + return 3; + if (ordinary(first) != 1) + return 4; + return 0; +} static int fixed(int p[2], int *replacement) { int **alias = &p; @@ -23,17 +41,3 @@ static int ordinary(int p[]) { return p[0]; } -int main(void) -{ - int first[2] = {1, 2}, second[2] = {7, 8}; - struct item a[1] = {{3}}, b[1] = {{9}}; - if (fixed(first, second) != 7) - return 1; - if (unsized(first, second) != 7) - return 2; - if (record(a, b) != 9) - return 3; - if (ordinary(first) != 1) - return 4; - return 0; -} diff --git a/tests/vir-bitwise-normalization.c b/tests/vir-bitwise-normalization.c new file mode 100644 index 00000000..d91b5117 --- /dev/null +++ b/tests/vir-bitwise-normalization.c @@ -0,0 +1,53 @@ +volatile int observed; +int reads; + +/* Defined after main, so each keeps its graph for the checks. */ +int read_value(void); +int absorb_and(int x, int y); +int absorb_or(int x, int y); +int repeat_and(int x, int y); +int repeat_or(int x, int y); +int complement(int x); +int main(void) +{ + for (int x = -17; x < 18; x++) { + for (int y = -17; y < 18; y++) { + if (absorb_and(x, y) != x || absorb_or(x, y) != x || + repeat_and(x, y) != (x & y) || repeat_or(x, y) != (x | y) || + complement(x) != -2) + return 1; + } + } + observed = 37; + int value = read_value(); + if ((value ^ value) != 0 || reads != 1) + return 2; + if ((observed ^ observed) != 0) + return 3; + return 0; +} +int read_value(void) +{ + reads++; + return observed; +} +int absorb_and(int x, int y) +{ + return x & (x | y); +} +int absorb_or(int x, int y) +{ + return x | (x & y); +} +int repeat_and(int x, int y) +{ + return x & (x & y); +} +int repeat_or(int x, int y) +{ + return x | (x | y); +} +int complement(int x) +{ + return (x ^ ~x) + (x & ~x) + (x | ~x); +} diff --git a/tests/vir-bitwise-normalization.sh b/tests/vir-bitwise-normalization.sh new file mode 100755 index 00000000..4bd8d730 --- /dev/null +++ b/tests/vir-bitwise-normalization.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +set -euo pipefail +compiler=("$@") +[ "${#compiler[@]}" -gt 0 ] || compiler=(out/shecc) +task_dir=$(mktemp -d) +trap 'rm -rf "$task_dir"' EXIT +"${compiler[@]}" --dump-ir -o "$task_dir/test" tests/vir-bitwise-normalization.c > "$task_dir/graph" +python3 - "$task_dir/graph" << 'PY' +import re +import sys + +text = open(sys.argv[1]).read() +functions = dict(re.findall(r'function (\w+)\n(.*?)(?=\nfunction |\Z)', text, re.S)) +for name in ('absorb_and', 'absorb_or', 'complement'): + assert not re.search(r' = bit(?:and|or|xor|not)\.', functions[name]), name +for name, opcode in (('repeat_and', 'bitand'), ('repeat_or', 'bitor')): + assert len(re.findall(r' = ' + opcode + r'\.', functions[name])) == 1, name +assert not re.search(r' = add\.', functions['complement']) +assert 'const.i32 4294967294' in functions['complement'] +assert functions['main'].count('volatile.load.') == 2 +assert 'call.i32 @read_value()' in functions['main'] +PY +echo 'Native VIR bitwise normalization graph checks passed' diff --git a/tests/vir-call-result-pins.c b/tests/vir-call-result-pins.c index 0ed672f5..c3c8e249 100644 --- a/tests/vir-call-result-pins.c +++ b/tests/vir-call-result-pins.c @@ -1,32 +1,11 @@ -static unsigned long long wide(int n) -{ - return 0x123456789abcdef0ULL + (unsigned long long) n * 0x100000001ULL; -} - -static unsigned long long combine(unsigned long long a, unsigned long long b) -{ - return a * 3ULL ^ (b + 17ULL); -} - -static signed char narrow(int n) -{ - return n - 130; -} +static unsigned long long wide(int n); +static unsigned long long combine(unsigned long long a, unsigned long long b); +static signed char narrow(int n); +static _Bool truth(int n); +static short narrow16(int n); +static unsigned int step(unsigned int x); -static _Bool truth(int n) -{ - return n & 1; -} -static short narrow16(int n) -{ - return 30000 - n; -} - -static unsigned int step(unsigned int x) -{ - return x * 1664525u + 1013904223u; -} int main(void) { @@ -49,3 +28,27 @@ int main(void) x != -125 || y != -121 || !t || f || u != -20000 || v != -30000 || state != 463890471u || a + b + c + d != 6577042764100329698ULL; } +static unsigned long long wide(int n) +{ + return 0x123456789abcdef0ULL + (unsigned long long) n * 0x100000001ULL; +} +static unsigned long long combine(unsigned long long a, unsigned long long b) +{ + return a * 3ULL ^ (b + 17ULL); +} +static signed char narrow(int n) +{ + return n - 130; +} +static _Bool truth(int n) +{ + return n & 1; +} +static short narrow16(int n) +{ + return 30000 - n; +} +static unsigned int step(unsigned int x) +{ + return x * 1664525u + 1013904223u; +} diff --git a/tests/vir-call-return-internal.c b/tests/vir-call-return-internal.c index c9379f2a..f74f1db1 100644 --- a/tests/vir-call-return-internal.c +++ b/tests/vir-call-return-internal.c @@ -1,3 +1,14 @@ +_Bool truth(int x); +signed char signed_byte(int x); +unsigned char unsigned_byte(int x); +short signed_short(int x); +unsigned short unsigned_short(int x); +int main(void) +{ + return truth(0) || !truth(256) || signed_byte(0x123456ff) != -1 || + unsigned_byte(0x123456ff) != 255 || signed_short(0x1234fffe) != -2 || + unsigned_short(0x1234fffe) != 65534; +} _Bool truth(int x) { return x; @@ -18,9 +29,3 @@ unsigned short unsigned_short(int x) { return x; } -int main(void) -{ - return truth(0) || !truth(256) || signed_byte(0x123456ff) != -1 || - unsigned_byte(0x123456ff) != 255 || signed_short(0x1234fffe) != -2 || - unsigned_short(0x1234fffe) != 65534; -} diff --git a/tests/vir-callback-pointer-return-alias.c b/tests/vir-callback-pointer-return-alias.c index df8da023..f2cb4e3d 100644 --- a/tests/vir-callback-pointer-return-alias.c +++ b/tests/vir-callback-pointer-return-alias.c @@ -2,12 +2,18 @@ typedef int *pointer_t; typedef pointer_t (*volatile callback_t)(int); typedef pointer_t (*const constant_callback_t)(int); static int values[2] = {17, 23}; +int *get(int n); +int touch(void); +static callback_t callback; +static constant_callback_t constant_callback = get; +int main(void) +{ + return touch(); +} int *get(int n) { return values + n; } -static callback_t callback; -static constant_callback_t constant_callback = get; int touch(void) { callback = get; @@ -15,7 +21,3 @@ int touch(void) callback = get; return *(callback(0) + 1) != 23 || *(constant_callback(1)) != 23; } -int main(void) -{ - return touch(); -} diff --git a/tests/vir-comparison-inversion.c b/tests/vir-comparison-inversion.c index e0186ece..e182a794 100644 --- a/tests/vir-comparison-inversion.c +++ b/tests/vir-comparison-inversion.c @@ -1,3 +1,26 @@ +static int unequal(int a, int b); +static int shared(int a, int b); +static int wide(unsigned long long a, unsigned long long b); +static int pointer(int *a, int *b); +static int nonzero(int a, int b); +static int bit(int a); +int main(void) +{ + int a = 1, b = 2; + if (unequal(-3, -3) || unequal(-3, 3) != 1) + return 1; + if (shared(-3, -3) != 2 || shared(-3, 3) != 1) + return 2; + if (wide(0x100000000ULL, 0) != 1 || wide(0x100000000ULL, 0x100000000ULL)) + return 3; + if (pointer(&a, &a) || pointer(&a, &b) != 1) + return 4; + if (nonzero(3, 3) || nonzero(3, 4)) + return 5; + if (bit(8) != 7 || bit(0) != 9 || bit(-1) != 7) + return 6; + return 0; +} static int unequal(int a, int b) { return a != b; @@ -25,20 +48,3 @@ static int bit(int a) return 7; return 9; } -int main(void) -{ - int a = 1, b = 2; - if (unequal(-3, -3) || unequal(-3, 3) != 1) - return 1; - if (shared(-3, -3) != 2 || shared(-3, 3) != 1) - return 2; - if (wide(0x100000000ULL, 0) != 1 || wide(0x100000000ULL, 0x100000000ULL)) - return 3; - if (pointer(&a, &a) || pointer(&a, &b) != 1) - return 4; - if (nonzero(3, 3) || nonzero(3, 4)) - return 5; - if (bit(8) != 7 || bit(0) != 9 || bit(-1) != 7) - return 6; - return 0; -} diff --git a/tests/vir-core.c b/tests/vir-core.c index 31152c5a..72e4395e 100644 --- a/tests/vir-core.c +++ b/tests/vir-core.c @@ -155,6 +155,47 @@ static void test_independent_pointer_add_scheduling(void) } } +static void test_pointer_add_barrier_groups(void) +{ + for (int level = VIR_OPT_O1; level <= VIR_OPT_O2; level++) { + vir_function_t f; + char *error = NULL; + vir_function_init(&f, 64); + assert(vir_function_set_pointer_bits(&f, 64)); + vir_block_t *block = vir_block_create(&f); + vir_value_t *pointer = vir_block_add_param(&f, block, VIR_TYPE_PTR); + vir_value_t *addresses[3], *loads[3]; + vir_effect_t *stores[3]; + + /* Each address is consumed only after every group, so the volatile + * store, not the use limit, must stop it. Later plain loads of the base + * pointer would otherwise pull it across that store. + */ + for (int i = 0; i < 3; i++) { + vir_value_t *offset = vir_const_i32(&f, block, 4 * (i + 1)); + addresses[i] = vir_ptradd(&f, block, pointer, offset); + loads[i] = vir_load(&f, block, pointer, VIR_TYPE_I32); + stores[i] = vir_volatile_store(&f, block, pointer, loads[i]); + assert(addresses[i] && loads[i] && stores[i]); + } + for (int i = 0; i < 2; i++) + assert(vir_volatile_store(&f, block, addresses[i], loads[2])); + assert(vir_block_set_return(&f, block, addresses[2])); + assert(vir_verify(&f, &error)); + for (int pass = 0; pass < 2; pass++) { + assert(vir_dce(&f, level) >= 0); + for (int i = 0; i < 3; i++) { + assert(loads[i]->order < addresses[i]->order); + assert(addresses[i]->order < stores[i]->order); + if (i) + assert(stores[i - 1]->order < addresses[i]->order); + } + assert(vir_verify(&f, &error)); + } + vir_function_release(&f); + } +} + static void test_overwritten_stores(void) { for (int barrier = 0; barrier < 7; barrier++) { @@ -680,13 +721,145 @@ static void test_compaction(void) vir_function_release(&f); } +static void test_post_ssa_normalization(void) +{ + for (int wide = 0; wide < 2; wide++) { + for (int mode = 0; mode < 10; mode++) { + vir_function_t f; + char *error = NULL; + vir_function_init(&f, 64); + assert(vir_function_set_pointer_bits(&f, 64)); + vir_block_t *b = vir_block_create(&f); + vir_type_t type = wide ? VIR_TYPE_I64 : VIR_TYPE_I32; + vir_value_t *x = vir_block_add_param(&f, b, type); + vir_value_t *y = vir_block_add_param(&f, b, type); + vir_value_t *pointer = vir_block_add_param(&f, b, VIR_TYPE_PTR); + vir_value_t *loaded = vir_volatile_load(&f, b, pointer, type); + vir_value_t *inv = vir_bitnot(&f, b, x); + vir_value_t *inner = vir_bitor(&f, b, x, y); + vir_value_t *result; + if (mode == 0) + result = vir_binary(&f, b, VIR_OP_BITXOR, type, loaded, loaded); + else if (mode == 1) + result = vir_binary(&f, b, VIR_OP_BITAND, type, x, inv); + else if (mode == 2) + result = vir_binary(&f, b, VIR_OP_BITOR, type, x, inv); + else if (mode == 3) + result = vir_binary(&f, b, VIR_OP_BITAND, type, x, inner); + else if (mode == 4) + result = vir_binary(&f, b, VIR_OP_BITOR, type, x, inner); + else if (mode == 5) { + vir_value_t *ones = vir_const_int(&f, b, type, ~0ULL); + result = vir_binary(&f, b, VIR_OP_BITXOR, type, y, ones); + } else if (mode < 8) { + /* Create two distinct leaves, then simulate SSA replacement. */ + result = + vir_binary(&f, b, mode == 6 ? VIR_OP_BITAND : VIR_OP_BITOR, + type, x, y); + assert(vir_replace_all_uses(&f, y, x)); + } else { + vir_value_t *leaf = vir_block_add_param(&f, b, type); + vir_value_t *first = mode == 8 ? vir_neg(&f, b, x) : inv; + result = + mode == 8 ? vir_neg(&f, b, leaf) : vir_bitnot(&f, b, leaf); + assert(vir_replace_all_uses(&f, leaf, first)); + } + assert(result && loaded && vir_block_set_return(&f, b, result)); + assert(vir_verify(&f, &error)); + assert(vir_normalize(&f, VIR_OPT_O0) == 0); + assert(vir_normalize(&f, VIR_OPT_O1) == 1); + assert(vir_verify(&f, &error)); + vir_value_t *normalized = b->return_value; + if (mode < 3) + assert(normalized->opcode == VIR_OP_CONST && + normalized->constant == + (mode == 2 ? (wide ? ~0ULL : 0xffffffffULL) : 0)); + else if (mode == 3 || mode >= 6) + assert(normalized == x); + else if (mode == 4) + assert(normalized == inner); + else + assert(normalized->opcode == VIR_OP_BITNOT && + normalized->op0 == y); + assert(vir_normalize(&f, VIR_OPT_O2) == 0); + assert(vir_dce(&f, VIR_OPT_O1) >= 0); + assert(b->effects && b->effects->kind == VIR_EFFECT_VOLATILE_LOAD); + assert(vir_verify(&f, &error)); + vir_function_release(&f); + } + } +} + +static void test_bitwise_normalization(void) +{ + for (int wide = 0; wide < 2; wide++) { + vir_function_t f; + char *error = NULL; + vir_function_init(&f, 64); + assert(vir_function_set_pointer_bits(&f, 64)); + vir_block_t *b = vir_block_create(&f); + vir_type_t type = wide ? VIR_TYPE_I64 : VIR_TYPE_I32; + unsigned long long mask = wide ? ~0ULL : 0xffffffffULL; + vir_value_t *x = vir_block_add_param(&f, b, type); + vir_value_t *y = vir_block_add_param(&f, b, type); + vir_value_t *ones = vir_const_int(&f, b, type, mask); + vir_value_t *inv = vir_bitnot(&f, b, x); + assert(vir_bitnot(&f, b, inv) == x); + assert(vir_neg(&f, b, vir_neg(&f, b, x)) == x); + assert(vir_bitand(&f, b, x, x) == x); + assert(vir_bitor(&f, b, x, x) == x); + assert(vir_bitxor(&f, b, x, x)->constant == 0); + for (int reverse = 0; reverse < 2; reverse++) { + vir_value_t *a = reverse ? inv : x; + vir_value_t *c = reverse ? x : inv; + assert(vir_bitand(&f, b, a, c)->constant == 0); + assert(vir_bitor(&f, b, a, c)->constant == mask); + assert(vir_bitxor(&f, b, a, c)->constant == mask); + a = reverse ? ones : x; + c = reverse ? x : ones; + assert(vir_bitor(&f, b, a, c)->constant == mask); + vir_value_t *negated = vir_bitxor(&f, b, a, c); + assert(negated->opcode == VIR_OP_BITNOT && negated->op0 == x); + for (int inner_reverse = 0; inner_reverse < 2; inner_reverse++) { + vir_value_t *first = inner_reverse ? y : x; + vir_value_t *second = inner_reverse ? x : y; + vir_value_t *both = vir_bitand(&f, b, first, second); + vir_value_t *either = vir_bitor(&f, b, first, second); + a = reverse ? both : x; + c = reverse ? x : both; + assert(vir_bitand(&f, b, a, c) == both); + assert(vir_bitor(&f, b, a, c) == x); + a = reverse ? either : x; + c = reverse ? x : either; + assert(vir_bitor(&f, b, a, c) == either); + assert(vir_bitand(&f, b, a, c) == x); + } + } + /* Similar shapes with a different leaf must survive normalization. */ + assert(vir_bitand(&f, b, inv, y)->opcode == VIR_OP_BITAND); + assert(vir_bitxor(&f, b, x, y)->opcode == VIR_OP_BITXOR); + vir_value_t *pointer = vir_block_add_param(&f, b, VIR_TYPE_PTR); + vir_value_t *loaded = vir_load(&f, b, pointer, type); + assert(loaded); + vir_effect_t *effect = loaded->def_effect; + vir_value_t *zero = vir_bitxor(&f, b, loaded, loaded); + assert(zero->constant == 0 && loaded->def_effect == effect); + assert(vir_block_set_return(&f, b, zero)); + assert(vir_verify(&f, &error)); + vir_function_release(&f); + } +} + int main(void) { + test_bitwise_normalization(); + test_post_ssa_normalization(); test_compaction(); test_incoming_load_reuse(); test_constant_edge_threading(); test_pointer_add_scheduling(); test_independent_pointer_add_scheduling(); + test_pointer_add_barrier_groups(); test_overwritten_stores(); test_va_start_signature_metadata(); test_sccp_effect_first_param(); @@ -1257,7 +1430,7 @@ int main(void) assert(cse_stats.tables == 1 && cse_stats.values == 2 && cse_stats.lookups == 2 && cse_stats.hits == 1 && cse_stats.probes == 1 && cse_stats.replacements == 1 && - cse_stats.table_slots == 4); + cse_stats.table_slots == 8); assert(vir_local_cse(&func, VIR_OPT_O2) == 0 && vir_local_cse_with_stats(&func, (vir_opt_level_t) 99, &cse_stats) == -1 && diff --git a/tests/vir-direct-arm64-call-bool-arg.c b/tests/vir-direct-arm64-call-bool-arg.c index f7ff30a4..9ff09ee1 100644 --- a/tests/vir-direct-arm64-call-bool-arg.c +++ b/tests/vir-direct-arm64-call-bool-arg.c @@ -1,9 +1,10 @@ -int helper(_Bool value) -{ - return value == 0 ? 7 : 42; -} +int helper(_Bool value); int main(int argc) { return helper(argc == 1); } +int helper(_Bool value) +{ + return value == 0 ? 7 : 42; +} diff --git a/tests/vir-direct-arm64-call-bool.c b/tests/vir-direct-arm64-call-bool.c index 32e8444c..0655445f 100644 --- a/tests/vir-direct-arm64-call-bool.c +++ b/tests/vir-direct-arm64-call-bool.c @@ -1,7 +1,4 @@ -_Bool helper(int value) -{ - return value != 0; -} +_Bool helper(int value); int main(void) { @@ -10,3 +7,7 @@ int main(void) return positive && !negative ? 0 : 1; } +_Bool helper(int value) +{ + return value != 0; +} diff --git a/tests/vir-direct-arm64-call-i32-argc.c b/tests/vir-direct-arm64-call-i32-argc.c index 62e9492a..712fa26c 100644 --- a/tests/vir-direct-arm64-call-i32-argc.c +++ b/tests/vir-direct-arm64-call-i32-argc.c @@ -1,9 +1,10 @@ -int helper(int value) -{ - return value + 1; -} +int helper(int value); int main(int argc) { return helper(argc) + argc; } +int helper(int value) +{ + return value + 1; +} diff --git a/tests/vir-direct-arm64-call-i32-branch.c b/tests/vir-direct-arm64-call-i32-branch.c index 5ad4083c..cc4137ba 100644 --- a/tests/vir-direct-arm64-call-i32-branch.c +++ b/tests/vir-direct-arm64-call-i32-branch.c @@ -1,7 +1,4 @@ -int helper(int value) -{ - return value + 1; -} +int helper(int value); int main(int argc) { @@ -9,3 +6,7 @@ int main(int argc) return helper(argc); return helper(2); } +int helper(int value) +{ + return value + 1; +} diff --git a/tests/vir-direct-arm64-call-i32-eight.c b/tests/vir-direct-arm64-call-i32-eight.c index 5ce5c9dc..2577d7b5 100644 --- a/tests/vir-direct-arm64-call-i32-eight.c +++ b/tests/vir-direct-arm64-call-i32-eight.c @@ -1,9 +1,10 @@ -int helper(int a, int b, int c, int d, int e, int f, int g, int h) -{ - return a + b + c + d + e + f + g + h; -} +int helper(int a, int b, int c, int d, int e, int f, int g, int h); int main(void) { return helper(1, 2, 4, 8, 16, 32, 64, 128); } +int helper(int a, int b, int c, int d, int e, int f, int g, int h) +{ + return a + b + c + d + e + f + g + h; +} diff --git a/tests/vir-direct-arm64-call-i32-pointer.c b/tests/vir-direct-arm64-call-i32-pointer.c index 3848905f..9848eae6 100644 --- a/tests/vir-direct-arm64-call-i32-pointer.c +++ b/tests/vir-direct-arm64-call-i32-pointer.c @@ -1,7 +1,4 @@ -int helper(int *value) -{ - return *value + 1; -} +int helper(int *value); int main(void) { @@ -9,3 +6,7 @@ int main(void) return helper(&value); } +int helper(int *value) +{ + return *value + 1; +} diff --git a/tests/vir-direct-arm64-call-i32-zero.c b/tests/vir-direct-arm64-call-i32-zero.c index 23278ab5..10b24c6b 100644 --- a/tests/vir-direct-arm64-call-i32-zero.c +++ b/tests/vir-direct-arm64-call-i32-zero.c @@ -1,9 +1,10 @@ -int helper(void) -{ - return 42; -} +int helper(void); int main(void) { return helper(); } +int helper(void) +{ + return 42; +} diff --git a/tests/vir-direct-arm64-call-i32.c b/tests/vir-direct-arm64-call-i32.c index 024b3b2b..b055f728 100644 --- a/tests/vir-direct-arm64-call-i32.c +++ b/tests/vir-direct-arm64-call-i32.c @@ -1,9 +1,10 @@ -int helper(int value) -{ - return value + 1; -} +int helper(int value); int main(void) { return helper(41); } +int helper(int value) +{ + return value + 1; +} diff --git a/tests/vir-direct-arm64-call-i64.c b/tests/vir-direct-arm64-call-i64.c index 89923dc1..a24dcb90 100644 --- a/tests/vir-direct-arm64-call-i64.c +++ b/tests/vir-direct-arm64-call-i64.c @@ -1,9 +1,10 @@ -long long helper(long long value) -{ - return value + 1LL; -} +long long helper(long long value); int main(void) { return (int) (helper(0x123456789abcdef0LL) >> 32); } +long long helper(long long value) +{ + return value + 1LL; +} diff --git a/tests/vir-direct-arm64-call-narrow.c b/tests/vir-direct-arm64-call-narrow.c index 2e2cc9ff..a1b739c5 100644 --- a/tests/vir-direct-arm64-call-narrow.c +++ b/tests/vir-direct-arm64-call-narrow.c @@ -1,14 +1,16 @@ +signed char add8(signed char value); +unsigned short add16(unsigned short value); + + +int main(void) +{ + return add8(-2) == -1 && add16(0xff00u) == 0xff01u ? 42 : 7; +} signed char add8(signed char value) { return value + 1; } - unsigned short add16(unsigned short value) { return value + 1; } - -int main(void) -{ - return add8(-2) == -1 && add16(0xff00u) == 0xff01u ? 42 : 7; -} diff --git a/tests/vir-direct-arm64-call-pointer.c b/tests/vir-direct-arm64-call-pointer.c index 6c58cdbc..273707f9 100644 --- a/tests/vir-direct-arm64-call-pointer.c +++ b/tests/vir-direct-arm64-call-pointer.c @@ -1,7 +1,4 @@ -int *helper(int *value) -{ - return value; -} +int *helper(int *value); int main(void) { @@ -10,3 +7,7 @@ int main(void) return *result + 1; } +int *helper(int *value) +{ + return value; +} diff --git a/tests/vir-direct-arm64-call-void.c b/tests/vir-direct-arm64-call-void.c index abf0df68..17a3215d 100644 --- a/tests/vir-direct-arm64-call-void.c +++ b/tests/vir-direct-arm64-call-void.c @@ -1,7 +1,4 @@ -void helper(int *value) -{ - *value = 41; -} +void helper(int *value); int main(void) { @@ -10,3 +7,7 @@ int main(void) helper(&value); return value + 1; } +void helper(int *value) +{ + *value = 41; +} diff --git a/tests/vir-direct-arm64-frame-runtime.c b/tests/vir-direct-arm64-frame-runtime.c index a69a8f97..92a6c09c 100644 --- a/tests/vir-direct-arm64-frame-runtime.c +++ b/tests/vir-direct-arm64-frame-runtime.c @@ -3,28 +3,15 @@ sum10(int a, int b, int c, int d, int e, int f, int g, int h, int i, int j) { return a + b + c + d + e + f + g + h + i + j; } -static int frame(int n) -{ - volatile unsigned char bytes[5000]; - bytes[0] = n; - bytes[4999] = n + 1; - return bytes[0] + bytes[4999] + sum10(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); -} -static int recursive(int n) -{ - volatile int saved[8]; - saved[0] = n; - saved[7] = n + 1; - return n ? recursive(n - 1) + saved[0] + saved[7] : saved[7]; -} +static int frame(int n); +static int recursive(int n); +static int pass(int n); +static int windows(void); +static int live_condition(int a, int b); /* Frames whose locals end on both sides of the 480 bytes a prologue folds into * its last STP: LDP's writeback reaches 504 bytes, the pair's 16 included. */ -static int pass(int n) -{ - return n; -} #define WINDOW(size) \ static int window##size(int n) \ { \ @@ -43,6 +30,29 @@ WINDOW(440) WINDOW(448) WINDOW(456) WINDOW(464) +int main(void) +{ + return frame(7) != 70 || recursive(7) != 64 || live_condition(7, 8) != 71 || + live_condition(8, 7) != 0 || windows() != 120; +} +static int frame(int n) +{ + volatile unsigned char bytes[5000]; + bytes[0] = n; + bytes[4999] = n + 1; + return bytes[0] + bytes[4999] + sum10(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); +} +static int recursive(int n) +{ + volatile int saved[8]; + saved[0] = n; + saved[7] = n + 1; + return n ? recursive(n - 1) + saved[0] + saved[7] : saved[7]; +} +static int pass(int n) +{ + return n; +} static int windows(void) { return window392(1) + window400(2) + window408(3) + window416(4) + @@ -56,8 +66,3 @@ static int live_condition(int a, int b) return c + frame(a); return c; } -int main(void) -{ - return frame(7) != 70 || recursive(7) != 64 || live_condition(7, 8) != 71 || - live_condition(8, 7) != 0 || windows() != 120; -} diff --git a/tests/vir-direct-arm64-helper-branch-unsigned.c b/tests/vir-direct-arm64-helper-branch-unsigned.c index 461d12b2..a613e37d 100644 --- a/tests/vir-direct-arm64-helper-branch-unsigned.c +++ b/tests/vir-direct-arm64-helper-branch-unsigned.c @@ -1,11 +1,12 @@ +static int helper(unsigned int value); + +int main(int argc) +{ + return helper(argc) + helper(argc - 2); +} static int helper(unsigned int value) { if (value < 2U) return 23; return 47; } - -int main(int argc) -{ - return helper(argc) + helper(argc - 2); -} diff --git a/tests/vir-direct-arm64-helper-branch.c b/tests/vir-direct-arm64-helper-branch.c index 51be66ca..4045e6a1 100644 --- a/tests/vir-direct-arm64-helper-branch.c +++ b/tests/vir-direct-arm64-helper-branch.c @@ -1,11 +1,12 @@ +static int helper(int value); + +int main(int argc) +{ + return helper(argc); +} static int helper(int value) { if (value < 2) return 23; return 47; } - -int main(int argc) -{ - return helper(argc); -} diff --git a/tests/vir-direct-arm64-helper-global-branch-volatile.c b/tests/vir-direct-arm64-helper-global-branch-volatile.c index adc2286e..c36bebd2 100644 --- a/tests/vir-direct-arm64-helper-global-branch-volatile.c +++ b/tests/vir-direct-arm64-helper-global-branch-volatile.c @@ -1,6 +1,13 @@ volatile int slot; volatile int mirror; +static int helper(int value); + +int main(int argc, char **argv) +{ + (void) argv; + return helper(argc); +} static int helper(int value) { if (value < 2) { @@ -10,9 +17,3 @@ static int helper(int value) mirror = value; return mirror; } - -int main(int argc, char **argv) -{ - (void) argv; - return helper(argc); -} diff --git a/tests/vir-direct-arm64-helper-global-branch.c b/tests/vir-direct-arm64-helper-global-branch.c index 83f0e2ff..d3162472 100644 --- a/tests/vir-direct-arm64-helper-global-branch.c +++ b/tests/vir-direct-arm64-helper-global-branch.c @@ -1,14 +1,6 @@ static int slot; -static int helper(int value) -{ - if (value < 0) { - slot = value; - return slot; - } - slot = value; - return slot; -} +static int helper(int value); int main(int argc, char **argv) { @@ -21,3 +13,12 @@ int main(int argc, char **argv) second = helper(5); return first + second + slot; } +static int helper(int value) +{ + if (value < 0) { + slot = value; + return slot; + } + slot = value; + return slot; +} diff --git a/tests/vir-direct-arm64-helper-global-load-store.c b/tests/vir-direct-arm64-helper-global-load-store.c index 26b685ad..5af6a505 100644 --- a/tests/vir-direct-arm64-helper-global-load-store.c +++ b/tests/vir-direct-arm64-helper-global-load-store.c @@ -1,13 +1,7 @@ static int first; static int second; -static int helper(int value) -{ - int old = first; - - second = value; - return old; -} +static int helper(int value); int main(int argc, char **argv) { @@ -19,3 +13,10 @@ int main(int argc, char **argv) result = helper(3); return result + first + second; } +static int helper(int value) +{ + int old = first; + + second = value; + return old; +} diff --git a/tests/vir-direct-arm64-helper-global-loop-deep.c b/tests/vir-direct-arm64-helper-global-loop-deep.c index 48aa8880..4e5a8eeb 100644 --- a/tests/vir-direct-arm64-helper-global-loop-deep.c +++ b/tests/vir-direct-arm64-helper-global-loop-deep.c @@ -1,5 +1,12 @@ static int slot; +static int helper(int value); + +int main(int argc, char **argv) +{ + (void) argv; + return helper(argc + 1); +} static int helper(int value) { int i = 0; @@ -20,9 +27,3 @@ static int helper(int value) } return slot; } - -int main(int argc, char **argv) -{ - (void) argv; - return helper(argc + 1); -} diff --git a/tests/vir-direct-arm64-helper-global-loop-nested.c b/tests/vir-direct-arm64-helper-global-loop-nested.c index abaf5fee..ffda77e9 100644 --- a/tests/vir-direct-arm64-helper-global-loop-nested.c +++ b/tests/vir-direct-arm64-helper-global-loop-nested.c @@ -1,5 +1,12 @@ static int slot; +static int helper(int value); + +int main(int argc, char **argv) +{ + (void) argv; + return helper(argc); +} static int helper(int value) { int i = 0; @@ -15,9 +22,3 @@ static int helper(int value) } return slot; } - -int main(int argc, char **argv) -{ - (void) argv; - return helper(argc); -} diff --git a/tests/vir-direct-arm64-helper-global-loop-ten.c b/tests/vir-direct-arm64-helper-global-loop-ten.c index 10841883..95616e7c 100644 --- a/tests/vir-direct-arm64-helper-global-loop-ten.c +++ b/tests/vir-direct-arm64-helper-global-loop-ten.c @@ -1,5 +1,12 @@ static int slot; +static int helper(int value); + +int main(int argc, char **argv) +{ + (void) argv; + return helper(argc); +} static int helper(int value) { int i = 0; @@ -55,9 +62,3 @@ static int helper(int value) } return slot; } - -int main(int argc, char **argv) -{ - (void) argv; - return helper(argc); -} diff --git a/tests/vir-direct-arm64-helper-global-loop.c b/tests/vir-direct-arm64-helper-global-loop.c index 27b4e5e8..49753643 100644 --- a/tests/vir-direct-arm64-helper-global-loop.c +++ b/tests/vir-direct-arm64-helper-global-loop.c @@ -1,5 +1,12 @@ static int slot; +static int helper(int value); + +int main(int argc, char **argv) +{ + (void) argv; + return helper(argc) + 1; +} static int helper(int value) { int i = 0; @@ -10,9 +17,3 @@ static int helper(int value) } return slot; } - -int main(int argc, char **argv) -{ - (void) argv; - return helper(argc) + 1; -} diff --git a/tests/vir-direct-arm64-helper-global-pair.c b/tests/vir-direct-arm64-helper-global-pair.c index 365abc80..889b3f35 100644 --- a/tests/vir-direct-arm64-helper-global-pair.c +++ b/tests/vir-direct-arm64-helper-global-pair.c @@ -1,14 +1,15 @@ static int first; static int second; +static int helper(int value); + +int main(int argc) +{ + return helper(argc); +} static int helper(int value) { first = value; second = value; return second; } - -int main(int argc) -{ - return helper(argc); -} diff --git a/tests/vir-direct-arm64-helper-global-volatile-pair.c b/tests/vir-direct-arm64-helper-global-volatile-pair.c index 31aeabfd..00c816d8 100644 --- a/tests/vir-direct-arm64-helper-global-volatile-pair.c +++ b/tests/vir-direct-arm64-helper-global-volatile-pair.c @@ -1,14 +1,15 @@ volatile int slot; volatile int mirror; +static int helper(int value); + +int main(int argc) +{ + return helper(argc); +} static int helper(int value) { slot = value; mirror = value; return slot; } - -int main(int argc) -{ - return helper(argc); -} diff --git a/tests/vir-direct-arm64-helper-global-volatile.c b/tests/vir-direct-arm64-helper-global-volatile.c index ee5cab80..9aeac3d8 100644 --- a/tests/vir-direct-arm64-helper-global-volatile.c +++ b/tests/vir-direct-arm64-helper-global-volatile.c @@ -1,12 +1,13 @@ volatile int slot; -static int helper(int value) -{ - slot = value; - return slot; -} +static int helper(int value); int main(int argc) { return helper(argc); } +static int helper(int value) +{ + slot = value; + return slot; +} diff --git a/tests/vir-direct-arm64-helper-global.c b/tests/vir-direct-arm64-helper-global.c index 093b0bea..38535848 100644 --- a/tests/vir-direct-arm64-helper-global.c +++ b/tests/vir-direct-arm64-helper-global.c @@ -1,12 +1,13 @@ int slot; -static int helper(int value) -{ - slot = value; - return slot; -} +static int helper(int value); int main(int argc) { return helper(argc); } +static int helper(int value) +{ + slot = value; + return slot; +} diff --git a/tests/vir-direct-arm64-helper-i64-local-mixed.c b/tests/vir-direct-arm64-helper-i64-local-mixed.c index 294090b9..718680c9 100644 --- a/tests/vir-direct-arm64-helper-i64-local-mixed.c +++ b/tests/vir-direct-arm64-helper-i64-local-mixed.c @@ -1,9 +1,4 @@ -static long long helper(long long value) -{ - int low = (int) value; - - return value + low; -} +static long long helper(long long value); int main(int argc) { @@ -11,3 +6,9 @@ int main(int argc) return helper(value) == value + 1 ? argc : -1; } +static long long helper(long long value) +{ + int low = (int) value; + + return value + low; +} diff --git a/tests/vir-direct-arm64-helper-i64-mixed.c b/tests/vir-direct-arm64-helper-i64-mixed.c index df1186db..732908c7 100644 --- a/tests/vir-direct-arm64-helper-i64-mixed.c +++ b/tests/vir-direct-arm64-helper-i64-mixed.c @@ -1,9 +1,10 @@ -static long long helper(long long value, int extra) -{ - return value + extra; -} +static long long helper(long long value, int extra); int main(int argc) { return (int) (helper((long long) argc << 32, argc) >> 32); } +static long long helper(long long value, int extra) +{ + return value + extra; +} diff --git a/tests/vir-direct-arm64-helper-i64-two.c b/tests/vir-direct-arm64-helper-i64-two.c index 0e84045e..6426ae26 100644 --- a/tests/vir-direct-arm64-helper-i64-two.c +++ b/tests/vir-direct-arm64-helper-i64-two.c @@ -1,7 +1,4 @@ -static long long helper(long long left, long long right) -{ - return left + (right >> 1); -} +static long long helper(long long left, long long right); int main(int argc) { @@ -10,3 +7,7 @@ int main(int argc) return (int) (helper(left, right) >> 32); } +static long long helper(long long left, long long right) +{ + return left + (right >> 1); +} diff --git a/tests/vir-direct-arm64-helper-i64.c b/tests/vir-direct-arm64-helper-i64.c index 4236aa84..3815ba3b 100644 --- a/tests/vir-direct-arm64-helper-i64.c +++ b/tests/vir-direct-arm64-helper-i64.c @@ -1,9 +1,10 @@ -static long long helper(long long value) -{ - return value + 0x100000000LL; -} +static long long helper(long long value); int main(int argc) { return (int) (helper((long long) argc << 32) >> 32); } +static long long helper(long long value) +{ + return value + 0x100000000LL; +} diff --git a/tests/vir-direct-arm64-helper-leaf.c b/tests/vir-direct-arm64-helper-leaf.c index d1f79ade..4fb2fb77 100644 --- a/tests/vir-direct-arm64-helper-leaf.c +++ b/tests/vir-direct-arm64-helper-leaf.c @@ -1,9 +1,10 @@ -static int helper(int x) -{ - return x + 0x40000001; -} +static int helper(int x); int main(int argc) { return helper(argc); } +static int helper(int x) +{ + return x + 0x40000001; +} diff --git a/tests/vir-direct-arm64-helper-loop-break.c b/tests/vir-direct-arm64-helper-loop-break.c index 8fc62e0a..dce83bcf 100644 --- a/tests/vir-direct-arm64-helper-loop-break.c +++ b/tests/vir-direct-arm64-helper-loop-break.c @@ -1,3 +1,9 @@ +static int helper(int value); + +int main(int argc) +{ + return helper(3 * argc - 2); +} static int helper(int value) { int i = 0; @@ -9,8 +15,3 @@ static int helper(int value) } return i; } - -int main(int argc) -{ - return helper(3 * argc - 2); -} diff --git a/tests/vir-direct-arm64-helper-loop.c b/tests/vir-direct-arm64-helper-loop.c index 92635605..dc0f527a 100644 --- a/tests/vir-direct-arm64-helper-loop.c +++ b/tests/vir-direct-arm64-helper-loop.c @@ -1,3 +1,9 @@ +static int helper(int value); + +int main(int argc) +{ + return helper(argc); +} static int helper(int value) { int i = 0; @@ -6,8 +12,3 @@ static int helper(int value) i++; return i; } - -int main(int argc) -{ - return helper(argc); -} diff --git a/tests/vir-direct-arm64-helper-merge.c b/tests/vir-direct-arm64-helper-merge.c index 223703aa..1c24c21d 100644 --- a/tests/vir-direct-arm64-helper-merge.c +++ b/tests/vir-direct-arm64-helper-merge.c @@ -1,9 +1,10 @@ -static int helper(int value) -{ - return value < 2 ? 23 : 47; -} +static int helper(int value); int main(int argc) { return helper(argc); } +static int helper(int value) +{ + return value < 2 ? 23 : 47; +} diff --git a/tests/vir-direct-arm64-helper-three.c b/tests/vir-direct-arm64-helper-three.c index 6a8691d4..d02eb6dc 100644 --- a/tests/vir-direct-arm64-helper-three.c +++ b/tests/vir-direct-arm64-helper-three.c @@ -1,9 +1,10 @@ -static int helper(int first, int second, int third) -{ - return first * 17 + second * 5 + third * 2; -} +static int helper(int first, int second, int third); int main(int argc) { return helper(argc, argc + 1, argc + 2); } +static int helper(int first, int second, int third) +{ + return first * 17 + second * 5 + third * 2; +} diff --git a/tests/vir-direct-arm64-helper-two.c b/tests/vir-direct-arm64-helper-two.c index cb1bf651..8fa86d89 100644 --- a/tests/vir-direct-arm64-helper-two.c +++ b/tests/vir-direct-arm64-helper-two.c @@ -1,9 +1,10 @@ -static int helper(int left, int right) -{ - return left * 17 + right; -} +static int helper(int left, int right); int main(int argc) { return helper(argc, argc + 1); } +static int helper(int left, int right) +{ + return left * 17 + right; +} diff --git a/tests/vir-direct-arm64.sh b/tests/vir-direct-arm64.sh index 5f435580..0b0415d6 100755 --- a/tests/vir-direct-arm64.sh +++ b/tests/vir-direct-arm64.sh @@ -649,54 +649,44 @@ vir_extract_function main "$work/global-ptradd.vir" \ | awk '/^ %d[0-9]+ = globaladdr @values, 8, align 4$/ { root++ } /^ %d[0-9]+ = ptradd %d[0-9]+, %d[0-9]+$/ { ptradd++ } END { exit root < 1 || ptradd < 1 || ptradd > 2 }' # Binary dump names retain .i32; the operand producers prove I64 scaling. A -# forwarded reload drops its own extension, scale and ptradd. -run_compiler --dump-vir -o "$work/global-ptradd-dynamic-dump" \ - tests/vir-direct-global-ptradd-dynamic.c \ - 2> "$work/global-ptradd-dynamic.vir" -vir_extract_function main \ - "$work/global-ptradd-dynamic.vir" \ - | awk ' - /^ %d[0-9]+ = globaladdr @values, 8, align 4$/ { root++ } - $3 == "sext.i32.i64" { extended[$1] = 1; extensions++ } - $3 == "zext.i32.i64" { bad = 1 } - $3 == "const.i64" && $4 == "4" { scale[$1] = 1 } - $3 == "mul.i32" { - sub(/,$/, "", $4) - if (extended[$4] && scale[$5] || scale[$4] && extended[$5]) - scaled[$1] = 1 - else bad = 1 - multiplies++ - } - $3 == "ptradd" { if (!scaled[$5]) bad = 1; ptradd++ } - END { - exit bad || root < 1 || extensions < 1 || extensions > 2 || - multiplies != extensions || ptradd != extensions - } - ' - -run_compiler --dump-vir -o "$work/global-ptradd-unsigned-dump" \ - tests/vir-direct-global-ptradd-unsigned.c \ - 2> "$work/global-ptradd-unsigned.vir" -vir_extract_function main \ - "$work/global-ptradd-unsigned.vir" \ - | awk ' - /^ %d[0-9]+ = globaladdr @values, 8, align 4$/ { root++ } - $3 == "zext.i32.i64" { extended[$1] = 1; extensions++ } - $3 == "sext.i32.i64" { bad = 1 } - $3 == "const.i64" && $4 == "4" { scale[$1] = 1 } - $3 == "mul.i32" { - sub(/,$/, "", $4) - if (extended[$4] && scale[$5] || scale[$4] && extended[$5]) - scaled[$1] = 1 - else bad = 1 - multiplies++ - } - $3 == "ptradd" { if (!scaled[$5]) bad = 1; ptradd++ } - END { - exit bad || root < 1 || extensions < 1 || extensions > 2 || - multiplies != extensions || ptradd != extensions - } - ' +# forwarded reload drops its own extension, scale and ptradd. Check a scaled +# global index: EXTENSION widens it, OPPOSITE must not appear, and each scale is +# a multiply by 4 or, after normalization, a shift by 2. +check_ptradd_scale() +{ + local name=$1 extension=$2 opposite=$3 + run_compiler --dump-vir -o "$work/global-ptradd-$name-dump" \ + "tests/vir-direct-global-ptradd-$name.c" \ + 2> "$work/global-ptradd-$name.vir" + vir_extract_function main "$work/global-ptradd-$name.vir" \ + | awk -v extension="$extension" -v opposite="$opposite" ' + /^ %d[0-9]+ = globaladdr @values, 8, align 4$/ { root++ } + $3 == extension ".i32.i64" { extended[$1] = 1; extensions++ } + $3 == opposite ".i32.i64" { bad = 1 } + $3 == "const.i64" && $4 == "4" { scale[$1] = 1 } + $3 == "const.i32" && $4 == "2" { shift[$1] = 1 } + $3 == "shl.i32" { + sub(/,$/, "", $4) + if (extended[$4] && shift[$5]) scaled[$1] = 1 + else bad = 1 + multiplies++ + } + $3 == "mul.i32" { + sub(/,$/, "", $4) + if (extended[$4] && scale[$5] || scale[$4] && extended[$5]) + scaled[$1] = 1 + else bad = 1 + multiplies++ + } + $3 == "ptradd" { if (!scaled[$5]) bad = 1; ptradd++ } + END { + exit bad || root < 1 || extensions < 1 || extensions > 2 || + multiplies != extensions || ptradd != extensions + } + ' +} +check_ptradd_scale dynamic sext zext +check_ptradd_scale unsigned zext sext vir_extract_function main "$work/narrow.vir" \ | grep -Eq '^ %d[0-9]+ = zext\.i16\.i32 %d[0-9]+$' diff --git a/tests/vir-direct-x64-branch-local-root-call.c b/tests/vir-direct-x64-branch-local-root-call.c index e1230e09..286e1469 100644 --- a/tests/vir-direct-x64-branch-local-root-call.c +++ b/tests/vir-direct-x64-branch-local-root-call.c @@ -1,8 +1,4 @@ -static int set_then_read(int *pointer, int value) -{ - *pointer = value; - return *pointer; -} +static int set_then_read(int *pointer, int value); int main(int argc) { @@ -13,3 +9,8 @@ int main(int argc) return set_then_read(&left, 17); return set_then_read(&right, 23); } +static int set_then_read(int *pointer, int value) +{ + *pointer = value; + return *pointer; +} diff --git a/tests/vir-direct-x64-call-aggregate.c b/tests/vir-direct-x64-call-aggregate.c index 36cd1266..43bcb04a 100644 --- a/tests/vir-direct-x64-call-aggregate.c +++ b/tests/vir-direct-x64-call-aggregate.c @@ -2,6 +2,12 @@ struct result { int value; }; +struct result helper(void); + +int main(void) +{ + return helper().value; +} struct result helper(void) { struct result r; @@ -9,8 +15,3 @@ struct result helper(void) r.value = 29; return r; } - -int main(void) -{ - return helper().value; -} diff --git a/tests/vir-direct-x64-call-args.c b/tests/vir-direct-x64-call-args.c index 70c2b2c3..35959032 100644 --- a/tests/vir-direct-x64-call-args.c +++ b/tests/vir-direct-x64-call-args.c @@ -1,9 +1,10 @@ -int combine(int left, int right) -{ - return left * 10 + right; -} +int combine(int left, int right); int main(int argc, char **argv) { return combine(argc, argc + 1) + argc; } +int combine(int left, int right) +{ + return left * 10 + right; +} diff --git a/tests/vir-direct-x64-call-argv.c b/tests/vir-direct-x64-call-argv.c index 1bbca9e7..82d3f389 100644 --- a/tests/vir-direct-x64-call-argv.c +++ b/tests/vir-direct-x64-call-argv.c @@ -1,7 +1,4 @@ -int combine(int left, int right) -{ - return left + right * 2; -} +int combine(int left, int right); int main(int argc, char **argv) { @@ -10,3 +7,7 @@ int main(int argc, char **argv) return result + (first != (char *) 0 ? 10 : 0); } +int combine(int left, int right) +{ + return left + right * 2; +} diff --git a/tests/vir-direct-x64-call-bool-arg-stack.c b/tests/vir-direct-x64-call-bool-arg-stack.c index 98fe3796..11d635f9 100644 --- a/tests/vir-direct-x64-call-bool-arg-stack.c +++ b/tests/vir-direct-x64-call-bool-arg-stack.c @@ -1,7 +1,4 @@ -_Bool helper(_Bool a, _Bool b, _Bool c, _Bool d, _Bool e, _Bool f, _Bool g) -{ - return a && b && c && d && e && f && g; -} +_Bool helper(_Bool a, _Bool b, _Bool c, _Bool d, _Bool e, _Bool f, _Bool g); int main(int argc) { @@ -11,3 +8,7 @@ int main(int argc) return helper(value, value, value, value, value, value, stack_value) ? 0 : 1; } +_Bool helper(_Bool a, _Bool b, _Bool c, _Bool d, _Bool e, _Bool f, _Bool g) +{ + return a && b && c && d && e && f && g; +} diff --git a/tests/vir-direct-x64-call-bool-arg.c b/tests/vir-direct-x64-call-bool-arg.c index df94d300..01b6dc0b 100644 --- a/tests/vir-direct-x64-call-bool-arg.c +++ b/tests/vir-direct-x64-call-bool-arg.c @@ -1,9 +1,10 @@ -static _Bool helper(_Bool value) -{ - return value == 1; -} +static _Bool helper(_Bool value); int main(int argc) { return helper(argc == 1) ? 0 : 1; } +static _Bool helper(_Bool value) +{ + return value == 1; +} diff --git a/tests/vir-direct-x64-call-eight.c b/tests/vir-direct-x64-call-eight.c index f7118249..7559a0be 100644 --- a/tests/vir-direct-x64-call-eight.c +++ b/tests/vir-direct-x64-call-eight.c @@ -1,9 +1,10 @@ -int sum8(int a, int b, int c, int d, int e, int f, int g, int h) -{ - return a + b + c + d + e + f + g + h; -} +int sum8(int a, int b, int c, int d, int e, int f, int g, int h); int main(void) { return sum8(1, 2, 3, 4, 5, 6, 7, 8); } +int sum8(int a, int b, int c, int d, int e, int f, int g, int h) +{ + return a + b + c + d + e + f + g + h; +} diff --git a/tests/vir-direct-x64-call-global-fnptr.c b/tests/vir-direct-x64-call-global-fnptr.c index 275e8b5e..d48f821e 100644 --- a/tests/vir-direct-x64-call-global-fnptr.c +++ b/tests/vir-direct-x64-call-global-fnptr.c @@ -1,16 +1,18 @@ -static int shared(int value) -{ - return value + 1; -} +static int shared(int value); +int caller(int value); int (*saved)(int) = shared; -int caller(int value) -{ - return saved(value); -} int main(void) { return shared(10) + caller(18); } +static int shared(int value) +{ + return value + 1; +} +int caller(int value) +{ + return saved(value); +} diff --git a/tests/vir-direct-x64-call-i64.c b/tests/vir-direct-x64-call-i64.c index 7ce33eed..9fa33e0a 100644 --- a/tests/vir-direct-x64-call-i64.c +++ b/tests/vir-direct-x64-call-i64.c @@ -1,10 +1,11 @@ -static long long add_wide(long long left, long long right) -{ - return left + right; -} +static long long add_wide(long long left, long long right); int main(int argc) { return (int) (add_wide(0x100000000LL, 0x100000000LL + (long long) argc) >> 32); } +static long long add_wide(long long left, long long right) +{ + return left + right; +} diff --git a/tests/vir-direct-x64-call-local-root.c b/tests/vir-direct-x64-call-local-root.c index f04ef22b..d1395640 100644 --- a/tests/vir-direct-x64-call-local-root.c +++ b/tests/vir-direct-x64-call-local-root.c @@ -1,3 +1,9 @@ +static int local_value(void); + +int main(void) +{ + return local_value(); +} static int local_value(void) { int value = 29; @@ -5,8 +11,3 @@ static int local_value(void) return *address; } - -int main(void) -{ - return local_value(); -} diff --git a/tests/vir-direct-x64-call-narrow.c b/tests/vir-direct-x64-call-narrow.c index bbecf22a..e4736126 100644 --- a/tests/vir-direct-x64-call-narrow.c +++ b/tests/vir-direct-x64-call-narrow.c @@ -13,18 +13,20 @@ static short combine(signed char a, return 37; } +static signed char negative(void); +static unsigned short maximum(void); + + +int main(void) +{ + return combine(-5, 250, -20, 40000, 2, 3, 4, 50000) + negative() + + (maximum() == 65535); +} static signed char negative(void) { return -1; } - static unsigned short maximum(void) { return 65535; } - -int main(void) -{ - return combine(-5, 250, -20, 40000, 2, 3, 4, 50000) + negative() + - (maximum() == 65535); -} diff --git a/tests/vir-direct-x64-call-pointer-high.c b/tests/vir-direct-x64-call-pointer-high.c index 23735272..b384541c 100644 --- a/tests/vir-direct-x64-call-pointer-high.c +++ b/tests/vir-direct-x64-call-pointer-high.c @@ -1,9 +1,10 @@ -int sum_pointer_slots(int a, int b, int c, int d, char **left, char **right) -{ - return left[0] != (char *) 0 && right[0] != (char *) 0 ? a + b + c + d : 0; -} +int sum_pointer_slots(int a, int b, int c, int d, char **left, char **right); int main(int argc, char **argv) { return sum_pointer_slots(1, 2, 3, 4, argv, argv) + argc; } +int sum_pointer_slots(int a, int b, int c, int d, char **left, char **right) +{ + return left[0] != (char *) 0 && right[0] != (char *) 0 ? a + b + c + d : 0; +} diff --git a/tests/vir-direct-x64-call-pointer-result.c b/tests/vir-direct-x64-call-pointer-result.c index b96ab7a1..c300af3a 100644 --- a/tests/vir-direct-x64-call-pointer-result.c +++ b/tests/vir-direct-x64-call-pointer-result.c @@ -1,7 +1,4 @@ -char *identity(char *value) -{ - return value; -} +char *identity(char *value); int main(int argc, char **argv) { @@ -10,3 +7,7 @@ int main(int argc, char **argv) return value == original ? 29 : argc - argc; } +char *identity(char *value) +{ + return value; +} diff --git a/tests/vir-direct-x64-call-pointer.c b/tests/vir-direct-x64-call-pointer.c index ed7395ab..53f4f0e6 100644 --- a/tests/vir-direct-x64-call-pointer.c +++ b/tests/vir-direct-x64-call-pointer.c @@ -1,9 +1,10 @@ -int present(char **values) -{ - return values[0] != (char *) 0 ? 29 : 0; -} +int present(char **values); int main(int argc, char **argv) { return present(argv) + argc; } +int present(char **values) +{ + return values[0] != (char *) 0 ? 29 : 0; +} diff --git a/tests/vir-direct-x64-call-seven.c b/tests/vir-direct-x64-call-seven.c index 0d68c3f6..f05b84c2 100644 --- a/tests/vir-direct-x64-call-seven.c +++ b/tests/vir-direct-x64-call-seven.c @@ -1,9 +1,10 @@ -int sum7(int a, int b, int c, int d, int e, int f, int g) -{ - return a + b + c + d + e + f + g; -} +int sum7(int a, int b, int c, int d, int e, int f, int g); int main(void) { return sum7(1, 2, 3, 4, 5, 6, 7); } +int sum7(int a, int b, int c, int d, int e, int f, int g) +{ + return a + b + c + d + e + f + g; +} diff --git a/tests/vir-direct-x64-call-six.c b/tests/vir-direct-x64-call-six.c index b89301ff..61885e49 100644 --- a/tests/vir-direct-x64-call-six.c +++ b/tests/vir-direct-x64-call-six.c @@ -1,9 +1,10 @@ -int sum6(int a, int b, int c, int d, int e, int f) -{ - return a + b + c + d + e + f; -} +int sum6(int a, int b, int c, int d, int e, int f); int main(void) { return sum6(1, 2, 3, 4, 5, 6); } +int sum6(int a, int b, int c, int d, int e, int f) +{ + return a + b + c + d + e + f; +} diff --git a/tests/vir-direct-x64-call-stack-pointer.c b/tests/vir-direct-x64-call-stack-pointer.c index 03c905e8..ab0c8d04 100644 --- a/tests/vir-direct-x64-call-stack-pointer.c +++ b/tests/vir-direct-x64-call-stack-pointer.c @@ -1,9 +1,10 @@ -int seventh_pointer(int a, int b, int c, int d, int e, int f, char **values) -{ - return values[0] != (char *) 0 ? a + b + c + d + e + f : 0; -} +int seventh_pointer(int a, int b, int c, int d, int e, int f, char **values); int main(int argc, char **argv) { return seventh_pointer(1, 2, 3, 4, 5, 6, argv) + argc; } +int seventh_pointer(int a, int b, int c, int d, int e, int f, char **values) +{ + return values[0] != (char *) 0 ? a + b + c + d + e + f : 0; +} diff --git a/tests/vir-direct-x64-call-void.c b/tests/vir-direct-x64-call-void.c index 8c5cce4d..daec49c3 100644 --- a/tests/vir-direct-x64-call-void.c +++ b/tests/vir-direct-x64-call-void.c @@ -1,13 +1,14 @@ int result; -void set_result(int value) -{ - result = value; - return; -} +void set_result(int value); int main(void) { set_result(29); return result; } +void set_result(int value) +{ + result = value; + return; +} diff --git a/tests/vir-direct-x64-call.c b/tests/vir-direct-x64-call.c index 1e45bc47..0dbe8855 100644 --- a/tests/vir-direct-x64-call.c +++ b/tests/vir-direct-x64-call.c @@ -1,14 +1,16 @@ +static int inner(void); +static int outer(void); + + +int main(void) +{ + return outer(); +} static int inner(void) { return 29; } - static int outer(void) { return inner(); } - -int main(void) -{ - return outer(); -} diff --git a/tests/vir-direct-x64-div.c b/tests/vir-direct-x64-div.c index 2643c5e4..b478e5ee 100644 --- a/tests/vir-direct-x64-div.c +++ b/tests/vir-direct-x64-div.c @@ -1,3 +1,13 @@ +static int divrem(int dividend, int divisor, int after); + +int main(int argc, char **argv) +{ + int value = divrem(-17 * argc, argc + 2, 7); + int expected = argc == 1 ? -508 : -862; + + (void) argv; + return value != expected; +} static int divrem(int dividend, int divisor, int after) { int quotient = dividend / divisor; @@ -13,12 +23,3 @@ static int divrem(int dividend, int divisor, int after) constant_remainder + negative_quotient * 10 + negative_remainder + nested_result; } - -int main(int argc, char **argv) -{ - int value = divrem(-17 * argc, argc + 2, 7); - int expected = argc == 1 ? -508 : -862; - - (void) argv; - return value != expected; -} diff --git a/tests/vir-direct-x64-global-pointer-call.c b/tests/vir-direct-x64-global-pointer-call.c index 0cc88fbe..fd642404 100644 --- a/tests/vir-direct-x64-global-pointer-call.c +++ b/tests/vir-direct-x64-global-pointer-call.c @@ -2,11 +2,7 @@ static int left = 0; static int right = 0; int *selected = &left; -static int select_right(void) -{ - selected = &right; - return 1; -} +static int select_right(void); int main(int argc) { @@ -15,3 +11,8 @@ int main(int argc) *selected = 7; return left + 2 * right; } +static int select_right(void) +{ + selected = &right; + return 1; +} diff --git a/tests/vir-direct-x64-merged-pointer-effect-void-call.c b/tests/vir-direct-x64-merged-pointer-effect-void-call.c index cb8aa7f0..5778c9ed 100644 --- a/tests/vir-direct-x64-merged-pointer-effect-void-call.c +++ b/tests/vir-direct-x64-merged-pointer-effect-void-call.c @@ -2,10 +2,7 @@ int left; int right; int result; -static void set_result(int *address) -{ - result = *address; -} +static void set_result(int *address); int main(int argc) { @@ -19,3 +16,7 @@ int main(int argc) set_result(address); return result; } +static void set_result(int *address) +{ + result = *address; +} diff --git a/tests/vir-direct-x64-param-scratch.c b/tests/vir-direct-x64-param-scratch.c index c8026aa4..a0c7ea95 100644 --- a/tests/vir-direct-x64-param-scratch.c +++ b/tests/vir-direct-x64-param-scratch.c @@ -1,9 +1,10 @@ -static int combine(int a, int b, int c, int d, int e, int f) -{ - return (a * b) + (e + f); -} +static int combine(int a, int b, int c, int d, int e, int f); int main(int argc, char **argv) { return combine(1, 2, 3, 4, 5, 6); } +static int combine(int a, int b, int c, int d, int e, int f) +{ + return (a * b) + (e + f); +} diff --git a/tests/vir-direct-x64-param-shift.c b/tests/vir-direct-x64-param-shift.c index ac463794..59884b80 100644 --- a/tests/vir-direct-x64-param-shift.c +++ b/tests/vir-direct-x64-param-shift.c @@ -1,9 +1,10 @@ -static int shift_and_use(int a, int b, int c, int d) -{ - return (a << b) - d; -} +static int shift_and_use(int a, int b, int c, int d); int main(int argc, char **argv) { return shift_and_use(2, 1, 1, 10); } +static int shift_and_use(int a, int b, int c, int d) +{ + return (a << b) - d; +} diff --git a/tests/vir-direct-x64-r11-call.c b/tests/vir-direct-x64-r11-call.c index 04886cc0..c3efe9d2 100644 --- a/tests/vir-direct-x64-r11-call.c +++ b/tests/vir-direct-x64-r11-call.c @@ -1,8 +1,18 @@ +static int id(int x); +static int clobber(int x); + + +int main(int argc, char **argv) +{ + int value = argc * 17; + int called = clobber(value); + + return id(value) + called; +} static int id(int x) { return x; } - static int clobber(int x) { int value = x * 7; @@ -10,11 +20,3 @@ static int clobber(int x) return value + called; } - -int main(int argc, char **argv) -{ - int value = argc * 17; - int called = clobber(value); - - return id(value) + called; -} diff --git a/tests/vir-direct-x64-unknown-pointer-alias.c b/tests/vir-direct-x64-unknown-pointer-alias.c index 259f8b10..7f0056d3 100644 --- a/tests/vir-direct-x64-unknown-pointer-alias.c +++ b/tests/vir-direct-x64-unknown-pointer-alias.c @@ -1,8 +1,4 @@ -static int store_then_read(int *store_pointer, int *read_pointer) -{ - *store_pointer = 13; - return *read_pointer; -} +static int store_then_read(int *store_pointer, int *read_pointer); static int first = 0; static int second = 0; @@ -13,3 +9,9 @@ int main(int argc) return store_then_read(&first, &first); return store_then_read(&first, &second); } + +static int store_then_read(int *store_pointer, int *read_pointer) +{ + *store_pointer = 13; + return *read_pointer; +} diff --git a/tests/vir-direct-x64.sh b/tests/vir-direct-x64.sh index 8a4121f8..6696faf0 100755 --- a/tests/vir-direct-x64.sh +++ b/tests/vir-direct-x64.sh @@ -122,6 +122,13 @@ check_vir_dump() awk -v extension="$extension" ' $3 == extension ".i32.i64" { extended[$1] = 1 } / = const.i64 2$/ { scale[$1] = 1 } + / = const.i32 1$/ { shift[$1] = 1 } + / = shl\.i[0-9]+ / { + sub(/,$/, "", $4) + if (extended[$4] && shift[$5]) scaled[$1] = 1 + else failed = 1 + multiplies++ + } / = mul\.i[0-9]+ / { sub(/,$/, "", $4) if (extended[$4] && scale[$5] || scale[$4] && extended[$5]) diff --git a/tests/vir-entry-parameters.c b/tests/vir-entry-parameters.c index 9ae98266..3f102313 100644 --- a/tests/vir-entry-parameters.c +++ b/tests/vir-entry-parameters.c @@ -1,5 +1,38 @@ #include int object = 19; +long long add(long long a, long long b); +long long live(long long a, long long b); +long long pair(int n, long long a, long long b); +long long split(int a, int b, int c, int d, int e, int f, int g, long long w); +signed char narrow(signed char x); +int *choose(int *p, int n); +long long unused(int ignored, long long value); +long long variadic(int ignored, long long last, ...); +long long pinned(long long value, int n); +int unused_pair(long long ignored, int value); +int main(void) +{ + if (live(7, 13) != 27) + return 1; + if (pair(-3, 0x12345678ffffffffLL, 5) != 0x1234567900000001LL) + return 2; + if (split(1, 2, 3, 4, 5, 6, 7, 0x12345678ffffffffLL) != + 0x123456790000001bLL) + return 3; + if (narrow(-127) != -127) + return 4; + if (choose(&object, 1) != &object || choose(&object, 0) != 0) + return 5; + if (unused(99, 0x12345678ffffffffLL) != 0x12345678ffffffffLL) + return 6; + if (variadic(99, 7LL, 0x12345678ffffffffLL, 3) != 0x1234567900000009LL) + return 7; + if (pinned(0x12345678ffffffffLL, 3) != 0x1234567900000005LL) + return 8; + if (pinned(9, 0) != 9 || unused_pair(0x12345678ffffffffLL, 17) != 17) + return 9; + return 0; +} long long add(long long a, long long b) { return a + b; @@ -50,26 +83,3 @@ int unused_pair(long long ignored, int value) { return value; } -int main(void) -{ - if (live(7, 13) != 27) - return 1; - if (pair(-3, 0x12345678ffffffffLL, 5) != 0x1234567900000001LL) - return 2; - if (split(1, 2, 3, 4, 5, 6, 7, 0x12345678ffffffffLL) != - 0x123456790000001bLL) - return 3; - if (narrow(-127) != -127) - return 4; - if (choose(&object, 1) != &object || choose(&object, 0) != 0) - return 5; - if (unused(99, 0x12345678ffffffffLL) != 0x12345678ffffffffLL) - return 6; - if (variadic(99, 7LL, 0x12345678ffffffffLL, 3) != 0x1234567900000009LL) - return 7; - if (pinned(0x12345678ffffffffLL, 3) != 0x1234567900000005LL) - return 8; - if (pinned(9, 0) != 9 || unused_pair(0x12345678ffffffffLL, 17) != 17) - return 9; - return 0; -} diff --git a/tests/vir-essential-core.c b/tests/vir-essential-core.c new file mode 100644 index 00000000..c57b138e --- /dev/null +++ b/tests/vir-essential-core.c @@ -0,0 +1,625 @@ +#include "vir.h" + +#include +#include +#include +#include + +/* Block parameters bound by run_function(), newest last. */ +static vir_value_t *bound_param[64]; +static unsigned long long bound_bits[64]; +static int bound_count; + +static unsigned long long evaluate(vir_value_t *value, + vir_value_t *input, + unsigned long long bits) +{ + if (value == input) + return bits; + if (value->is_block_param) { + for (int i = bound_count - 1; i >= 0; i--) + if (bound_param[i] == value) + return bound_bits[i]; + assert(0); + } + if (value->opcode == VIR_OP_CONST) + return value->constant; + unsigned long long left = evaluate(value->op0, input, bits); + unsigned long long right = + value->op1 ? evaluate(value->op1, input, bits) : 0; + unsigned long long mask = + value->type == VIR_TYPE_I32 ? 0xffffffffULL : ~0ULL; + int width = value->type == VIR_TYPE_I32 ? 32 : 64; + unsigned long long result = 0; + switch (value->opcode) { + case VIR_OP_ADD: + result = left + right; + break; + case VIR_OP_SUB: + result = left - right; + break; + case VIR_OP_NEG: + result = 0 - left; + break; + case VIR_OP_SLT: { + unsigned long long sign = value->op0->type == VIR_TYPE_I32 + ? 0x80000000ULL + : 0x8000000000000000ULL; + return (left ^ sign) < (right ^ sign); + } + case VIR_OP_MUL: + result = left * right; + break; + case VIR_OP_BITAND: + result = left & right; + break; + case VIR_OP_SHL: + result = left << right; + break; + case VIR_OP_LSHR: + result = left >> right; + break; + case VIR_OP_ASHR: + result = left >> right; + if (right && (left & (1ULL << (width - 1)))) + result |= mask ^ (mask >> right); + break; + default: + assert(0); + } + return result & mask; +} + +static void test_affine_loops(void) +{ + for (int test = 0; test < 200; test++) { + vir_function_t f; + char *error = NULL; + vir_function_init(&f, 4096); + assert(vir_function_set_pointer_bits(&f, 64)); + vir_type_t type = test & 1 ? VIR_TYPE_I32 : VIR_TYPE_I64; + unsigned long long mask = type == VIR_TYPE_I32 ? 0xffffffffULL : ~0ULL; + unsigned long long first = test % 7, step = test % 5 + 1; + unsigned long long limit = first + test % 31 + 1; + unsigned long long a = test % 8 == 0 ? mask : (unsigned int) test % 4; + unsigned long long b = + test % 9 == 0 ? mask - 6 : (unsigned int) test % 7; + unsigned long long c = test % 11 == 0 ? mask : (unsigned int) test % 13; + bool extreme = test >= 192; + bool signed_index = extreme ? (test & 2) != 0 : (test & 4) != 0; + if (extreme) { + first = 0; + step = 1; + limit = signed_index ? mask >> 1 : mask; + a = c = 1; + b = 0; + } + vir_block_t *entry = vir_block_create(&f); + vir_block_t *header = vir_block_create(&f); + vir_block_t *body = vir_block_create(&f); + vir_block_t *latch = vir_block_create(&f); + vir_block_t *exit = vir_block_create(&f); + vir_value_t *input = vir_block_add_param(&f, entry, type); + vir_value_t *iv, *acc; + int iv_index = test & 2 ? 1 : 0; + vir_value_t *params[2]; + for (int i = 0; i < 2; i++) + params[i] = vir_block_add_param(&f, header, type); + iv = params[iv_index]; + acc = params[1 - iv_index]; + vir_value_t *args[2]; + args[iv_index] = vir_const_int(&f, entry, type, first); + args[1 - iv_index] = input; + assert(vir_edge_create(&f, entry, header, args, 2)); + vir_value_t *bound = vir_const_int(&f, header, type, limit); + vir_value_t *condition = signed_index ? vir_slt(&f, header, iv, bound) + : vir_ult(&f, header, iv, bound); + vir_edge_args_t yes = {body, NULL, 0}, no = {exit, NULL, 0}; + assert(vir_block_set_branch(&f, header, condition, &yes, &no)); + vir_value_t *scale = vir_const_int(&f, body, type, a); + vir_value_t *factor = vir_const_int(&f, body, type, b); + vir_value_t *bias = vir_const_int(&f, body, type, c); + vir_value_t *product = vir_mul(&f, body, acc, scale); + vir_value_t *index = vir_mul(&f, body, iv, factor); + vir_value_t *sum = vir_add(&f, body, product, index); + vir_value_t *next = vir_add(&f, body, sum, bias); + assert(vir_edge_create(&f, body, latch, NULL, 0)); + args[iv_index] = + vir_add(&f, latch, iv, vir_const_int(&f, latch, type, step)); + args[1 - iv_index] = next; + assert(vir_edge_create(&f, latch, header, args, 2)); + assert(vir_block_set_return(&f, exit, acc)); + assert(vir_verify(&f, &error)); + assert(vir_summarize_affine_loops(&f, VIR_OPT_O0) == 0); + assert(vir_summarize_affine_loops(&f, VIR_OPT_O2) == 1); + assert(vir_verify(&f, &error)); + assert(vir_simplify_params(&f, VIR_OPT_O2) >= 0); + assert(vir_dce(&f, VIR_OPT_O2) >= 0); + assert(vir_verify(&f, &error)); + for (int seed = 0; seed < 16; seed++) { + unsigned long long initial = (mask - seed * 7919ULL) & mask; + unsigned long long expected = initial; + if (extreme) + expected = (initial + limit) & mask; + else + for (unsigned long long i = first; i < limit; i += step) + expected = (expected * a + b * i + c) & mask; + assert(evaluate(exit->return_value, input, initial) == expected); + } + assert(f.block_count == 3); + vir_function_release(&f); + } +} + +/* Every mode changes one proof obligation of an otherwise eligible loop. */ +static void test_affine_rejections(void) +{ + for (int mode = 0; mode < 10; mode++) { + vir_function_t f; + char *error = NULL; + vir_function_init(&f, 4096); + assert(vir_function_set_pointer_bits(&f, 64)); + vir_block_t *entry = vir_block_create(&f); + vir_block_t *header = vir_block_create(&f); + vir_block_t *body = vir_block_create(&f); + vir_block_t *exit = vir_block_create(&f); + vir_value_t *pointer = vir_block_add_param(&f, entry, VIR_TYPE_PTR); + vir_value_t *iv = vir_block_add_param(&f, header, VIR_TYPE_I32); + vir_value_t *acc = vir_block_add_param(&f, header, VIR_TYPE_I32); + vir_value_t *one = vir_const_i32(&f, entry, 1); + vir_value_t *first = vir_const_i32(&f, entry, + mode == 5 ? -2 + : mode == 8 ? 1 + : 0); + vir_value_t *args[2] = {first, one}; + assert(vir_edge_create(&f, entry, header, args, 2)); + vir_value_t *bound = vir_const_i32(&f, header, mode == 5 ? -1 : 10); + vir_value_t *condition = mode == 6 ? vir_slt(&f, header, first, bound) + : vir_ult(&f, header, iv, bound); + vir_edge_args_t yes = {body, NULL, 0}, no = {exit, NULL, 0}; + assert(vir_block_set_branch(&f, header, condition, &yes, &no)); + vir_value_t *next = vir_add(&f, body, acc, iv); + if (mode == 0) + assert(vir_store(&f, body, pointer, next)); + if (mode == 1) + assert(vir_load(&f, body, pointer, VIR_TYPE_I32)); + if (mode == 2) + assert(vir_volatile_load(&f, body, pointer, VIR_TYPE_I32)); + if (mode == 3) + assert(vir_call(&f, body, "other", NULL, 0, VIR_TYPE_VOID)); + if (mode == 4) + next = vir_mul(&f, body, acc, acc); + args[0] = + vir_add(&f, body, iv, mode == 5 ? vir_const_i32(&f, body, 2) : one); + args[1] = next; + if (mode == 7) + args[0] = iv; + if (mode == 8) + args[0] = vir_sub(&f, body, iv, one); + if (mode == 9) { + for (int i = 0; i < 20; i++) + next = vir_add(&f, body, next, next); + args[1] = next; + } + assert(vir_edge_create(&f, body, header, args, 2)); + assert(vir_block_set_return(&f, exit, acc)); + assert(vir_verify(&f, &error)); + assert(vir_summarize_affine_loops(&f, VIR_OPT_O2) == 0); + assert(vir_verify(&f, &error)); + vir_function_release(&f); + } +} + +static void test_tail_recursion(void) +{ + for (int mode = 0; mode < 10; mode++) { + vir_function_t f; + char *error = NULL; + vir_function_init(&f, 4096); + assert(vir_function_set_pointer_bits(&f, 64)); + vir_block_t *entry = vir_block_create(&f); + vir_block_t *base = vir_block_create(&f); + vir_block_t *body = vir_block_create(&f); + vir_value_t *n = vir_block_add_param(&f, entry, VIR_TYPE_I32); + vir_value_t *small = + mode == 7 ? vir_block_add_param(&f, entry, VIR_TYPE_I8) : NULL; + vir_value_t *one = vir_const_i32(&f, entry, 1); + vir_value_t *condition = vir_ult(&f, entry, n, one); + vir_edge_args_t yes = {base, NULL, 0}, no = {body, NULL, 0}; + assert(vir_block_set_branch(&f, entry, condition, &yes, &no)); + assert(vir_block_set_return(&f, base, n)); + vir_value_t *args[2] = {vir_sub(&f, body, n, one), small}; + int argc = small ? 2 : 1; + vir_effect_t *call = + vir_call(&f, body, "recursive", args, argc, VIR_TYPE_I32); + assert(call); + vir_value_t *result = call->result; + if (mode == 2) { + vir_effect_t *second = + vir_call(&f, body, "recursive", args, argc, VIR_TYPE_I32); + assert(second); + result = vir_add(&f, body, result, second->result); + } else if (mode == 3) { + result = vir_add(&f, body, result, result); + } else if (mode != 0) { + result = vir_add(&f, body, result, n); + } + if (mode == 4) { + vir_value_t *global = vir_global_addr(&f, body, "visible", 4, 4); + assert(vir_store(&f, body, global, one)); + } + if (mode == 5) + assert(vir_stack_addr(&f, body, 0, 4, 4)); + if (mode == 6) + assert(vir_call(&f, body, "external", NULL, 0, VIR_TYPE_VOID)); + if (mode == 8) + assert(vir_bitnot(&f, body, call->result)); + if (mode == 9) { + vir_call_abi_type_t param = {VIR_TYPE_I32, false, false}; + vir_call_signature_t signature = {0}; + signature.result.type = VIR_TYPE_I32; + signature.params = ¶m; + signature.param_count = signature.fixed_param_count = 1; + signature.is_variadic = true; + signature.va_start_slot = (unsigned int) -1; + assert(vir_call_set_signature(&f, call, &signature)); + } + assert(vir_block_set_return(&f, body, result)); + assert(vir_verify(&f, &error)); + assert(vir_eliminate_tail_recursion(&f, "recursive", VIR_OPT_O0) == 0); + int transformed = + vir_eliminate_tail_recursion(&f, "recursive", VIR_OPT_O2); + assert(transformed == (mode < 3 ? 1 : 0)); + assert(vir_verify(&f, &error)); + if (transformed) { + assert(f.blocks != entry && f.blocks->param_count == 1); + assert(entry->param_count == 2 && entry->incoming && + entry->incoming->next_incoming); + assert(body->terminator == VIR_TERM_JUMP); + assert((body->effects != NULL) == (mode == 2)); + assert(vir_dce(&f, VIR_OPT_O2) >= 0); + assert(vir_verify(&f, &error)); + } + vir_function_release(&f); + } +} + +/* Run @func from its entry, whose first parameter is @input, through jumps and + * branches to the value it returns. + */ +static unsigned long long run_function(vir_function_t *func, + unsigned long long bits) +{ + vir_block_t *block = func->blocks; + vir_value_t *input = block->params; + bound_count = 0; + for (int steps = 0; steps < 32; steps++) { + if (block->terminator == VIR_TERM_RETURN) + return evaluate(block->return_value, input, bits); + vir_edge_t *edge = block->outgoing; + if (block->terminator == VIR_TERM_BRANCH) + edge = evaluate(block->branch_condition, input, bits) + ? block->true_edge + : block->false_edge; + unsigned long long args[8]; + assert(edge->arg_count <= 8); + for (int i = 0; i < edge->arg_count; i++) + args[i] = evaluate(edge->args[i], input, bits); + int i = 0; + for (vir_value_t *param = edge->to->params; param; + param = param->param_next) { + assert(bound_count < 64); + bound_param[bound_count] = param; + bound_bits[bound_count++] = args[i++]; + } + block = edge->to; + } + assert(0); + return 0; +} + +static const vir_function_t *inline_target; +static bool inline_static = true; + +static const vir_function_t *inline_lookup(const char *name, + void *context, + bool *is_static) +{ + (void) context; + *is_static = inline_static; + return !strcmp(name, "leaf") ? inline_target : NULL; +} + +static int count_effects(const vir_function_t *func, vir_effect_kind_t kind) +{ + int count = 0; + for (const vir_block_t *block = func->blocks; block; block = block->next) + for (const vir_effect_t *effect = block->effects; effect; + effect = effect->next) + count += effect->kind == kind; + return count; +} + +/* Start a caller and a "leaf" callee whose entry @source takes i32 @x and y and + * computes the returned 3 * x + y. + */ +static vir_value_t *start_leaf(vir_function_t *caller, + vir_function_t *callee, + vir_block_t **source, + vir_value_t **x) +{ + vir_function_init(caller, 4096); + vir_function_init(callee, 4096); + assert(vir_function_set_pointer_bits(caller, 64)); + assert(vir_function_set_pointer_bits(callee, 64)); + *source = vir_block_create(callee); + *x = vir_block_add_param(callee, *source, VIR_TYPE_I32); + vir_value_t *y = vir_block_add_param(callee, *source, VIR_TYPE_I32); + vir_value_t *three = vir_const_i32(callee, *source, 3); + return vir_add(callee, *source, vir_mul(callee, *source, *x, three), y); +} + +/* Modes: 0 pure, 1 stores a global, 2 too large, 3 two blocks, 4 argument type + * mismatch, 5 one value passed twice, 6 two returns, 7 calls out. + */ +static void test_call_inlining(void) +{ + for (int mode = 0; mode < 8; mode++) { + vir_function_t caller, callee; + char *error = NULL; + vir_block_t *source; + vir_value_t *x; + vir_value_t *sum = start_leaf(&caller, &callee, &source, &x); + if (mode == 1) { + vir_value_t *address = + vir_global_addr(&callee, source, "visible", 4, 4); + assert(vir_store(&callee, source, address, x)); + } + if (mode == 2) + for (int i = 0; i < 45; i++) + sum = vir_add(&callee, source, sum, x); + if (mode == 3) { + vir_block_t *exit = vir_block_create(&callee); + assert(vir_edge_create(&callee, source, exit, NULL, 0)); + source = exit; + } + if (mode == 7) + assert(vir_call(&callee, source, "other", NULL, 0, VIR_TYPE_VOID)); + if (mode == 6) { + vir_block_t *negative = vir_block_create(&callee); + vir_block_t *positive = vir_block_create(&callee); + vir_value_t *zero = vir_const_i32(&callee, source, 0); + vir_edge_args_t then_edge = {negative, NULL, 0}; + vir_edge_args_t else_edge = {positive, NULL, 0}; + assert(vir_block_set_branch(&callee, source, + vir_slt(&callee, source, x, zero), + &then_edge, &else_edge)); + assert(vir_block_set_return(&callee, negative, + vir_neg(&callee, negative, x))); + assert(vir_block_set_return(&callee, positive, x)); + } else { + assert(vir_block_set_return(&callee, source, sum)); + } + assert(vir_verify(&callee, &error)); + inline_target = &callee; + + vir_block_t *entry = vir_block_create(&caller); + vir_value_t *input = vir_block_add_param(&caller, entry, VIR_TYPE_I32); + vir_value_t *seven = vir_const_i32(&caller, entry, 7); + vir_value_t *args[2] = {input, seven}; + if (mode == 4) + args[0] = vir_const_int(&caller, entry, VIR_TYPE_I64, 1); + if (mode == 5) + args[1] = input; + vir_effect_t *call = + vir_call(&caller, entry, "leaf", args, 2, VIR_TYPE_I32); + assert(call && call->result); + vir_value_t *one = vir_const_i32(&caller, entry, 1); + assert(vir_block_set_return( + &caller, entry, vir_add(&caller, entry, call->result, one))); + assert(vir_verify(&caller, &error)); + assert(vir_inline_calls(&caller, inline_lookup, NULL, VIR_OPT_O0) == 0); + assert(count_effects(&caller, VIR_EFFECT_CALL) == 1); + + bool expected = mode != 2 && mode != 4 && mode != 7; + assert(vir_inline_calls(&caller, inline_lookup, NULL, VIR_OPT_O2) == + expected); + assert(vir_verify(&caller, &error)); + assert(count_effects(&caller, VIR_EFFECT_CALL) == !expected); + for (int i = 0; expected && i < 16; i++) { + unsigned long long bits = 0xffffffffULL - 17 * i; + unsigned long long want = 3 * bits + (mode == 5 ? bits : 7); + if (mode == 6) + want = bits & 0x80000000ULL ? 0 - bits : bits; + assert(run_function(&caller, bits) == ((want + 1) & 0xffffffffULL)); + } + if (mode == 1) + assert(count_effects(&caller, VIR_EFFECT_STORE) == 1); + vir_function_release(&caller); + vir_function_release(&callee); + } +} + +static void test_integer_strength_reduction(void) +{ + vir_opcode_t opcodes[] = {VIR_OP_MUL, VIR_OP_UDIV, VIR_OP_UREM, VIR_OP_SDIV, + VIR_OP_SREM}; + for (int wide = 0; wide < 2; wide++) { + int width = wide ? 64 : 32; + int shifts[] = {1, 2, 3, 4, 7, width - 2, width - 1}; + vir_type_t type = wide ? VIR_TYPE_I64 : VIR_TYPE_I32; + unsigned long long mask = wide ? ~0ULL : 0xffffffffULL; + for (int op = 0; op < 5; op++) { + for (int k = 0; k < 7; k++) { + int shift = shifts[k]; + if (op >= 3 && shift == width - 1) + continue; + vir_function_t f; + char *error = NULL; + vir_function_init(&f, 4096); + assert(vir_function_set_pointer_bits(&f, 64)); + vir_block_t *entry = vir_block_create(&f); + vir_value_t *input = vir_block_add_param(&f, entry, type); + vir_value_t *pointer = + vir_block_add_param(&f, entry, VIR_TYPE_PTR); + assert(vir_volatile_load(&f, entry, pointer, type)); + unsigned long long divisor = 1ULL << shift; + vir_value_t *constant = vir_const_int(&f, entry, type, divisor); + vir_value_t *result = + vir_binary(&f, entry, opcodes[op], type, input, constant); + assert(vir_block_set_return(&f, entry, result)); + assert(vir_verify(&f, &error)); + assert(vir_normalize(&f, VIR_OPT_O0) == 0); + assert(vir_normalize(&f, VIR_OPT_O2) == 1); + assert(vir_verify(&f, &error)); + unsigned long long boundaries[] = {0, + 1, + divisor - 1, + divisor, + divisor + 1, + mask, + mask - divisor, + 1ULL << (width - 1), + (1ULL << (width - 1)) - 1, + (1ULL << (width - 1)) + 1}; + unsigned long long random = 0x123456789abcdef0ULL; + for (int sample = 0; sample < 138; sample++) { + random = random * 6364136223846793005ULL + 1; + unsigned long long bits = + sample < 10 ? boundaries[sample] & mask : random & mask; + unsigned long long expected; + if (op == 0) + expected = (bits * divisor) & mask; + else if (op == 1) + expected = bits / divisor; + else if (op == 2) + expected = bits % divisor; + else { + long long signed_value = + wide ? (long long) bits : (int) (unsigned int) bits; + long long signed_divisor = (long long) divisor; + expected = + (unsigned long long) (op == 3 + ? signed_value / + signed_divisor + : signed_value % + signed_divisor) & + mask; + } + assert(evaluate(entry->return_value, input, bits) == + expected); + } + assert(vir_dce(&f, VIR_OPT_O2) >= 0); + assert(entry->effects && + entry->effects->kind == VIR_EFFECT_VOLATILE_LOAD); + assert(vir_verify(&f, &error)); + vir_function_release(&f); + } + } + } +} + +static void test_integer_strength_rejections(void) +{ + for (int wide = 0; wide < 2; wide++) { + for (int mode = 0; mode < 6; mode++) { + vir_function_t f; + char *error = NULL; + vir_function_init(&f, 4096); + vir_type_t type = wide ? VIR_TYPE_I64 : VIR_TYPE_I32; + unsigned long long mask = wide ? ~0ULL : 0xffffffffULL; + unsigned long long minimum = wide ? 1ULL << 63 : 1ULL << 31; + vir_block_t *entry = vir_block_create(&f); + vir_value_t *input = vir_block_add_param(&f, entry, type); + if (mode >= 4) + input = vir_const_int(&f, entry, type, minimum); + unsigned long long bits = mode == 0 ? 0 + : mode == 1 ? mask + : mode == 2 ? minimum + : mode == 3 ? mask - 1 + : mask; + vir_value_t *divisor = vir_const_int(&f, entry, type, bits); + vir_value_t *result = + vir_binary(&f, entry, mode == 5 ? VIR_OP_SREM : VIR_OP_SDIV, + type, input, divisor); + assert(vir_block_set_return(&f, entry, result)); + assert(vir_normalize(&f, VIR_OPT_O2) == 0); + assert(entry->return_value == result); + assert(vir_verify(&f, &error)); + vir_function_release(&f); + } + } +} + +/* An extern callee of 3 + @extra values, called once outside any loop or from a + * block that branches back to itself. Only the tiny one, or the loop call of + * the larger one, is copied. + */ +static void test_extern_inlining(void) +{ + for (int mode = 0; mode < 4; mode++) { + int extra = mode & 1 ? 5 : 0; + bool loop = mode & 2; + vir_function_t caller, callee; + char *error = NULL; + vir_block_t *source; + vir_value_t *x; + vir_value_t *sum = start_leaf(&caller, &callee, &source, &x); + for (int i = 0; i < extra; i++) + sum = vir_add(&callee, source, sum, x); + assert(vir_block_set_return(&callee, source, sum)); + assert(vir_verify(&callee, &error)); + inline_target = &callee; + inline_static = false; + + vir_block_t *entry = vir_block_create(&caller); + vir_block_t *body = vir_block_create(&caller); + vir_block_t *exit = vir_block_create(&caller); + vir_value_t *input = vir_block_add_param(&caller, entry, VIR_TYPE_I32); + vir_value_t *seven = vir_const_i32(&caller, entry, 7); + vir_value_t *args[2] = {input, seven}; + assert(vir_edge_create(&caller, entry, body, NULL, 0)); + vir_effect_t *call = + vir_call(&caller, body, "leaf", args, 2, VIR_TYPE_I32); + assert(call && call->result); + if (loop) { + /* slt r, r is false, so the walk leaves on the first pass. */ + vir_edge_args_t again = {body, NULL, 0}; + vir_edge_args_t leave = {exit, NULL, 0}; + assert(vir_block_set_branch( + &caller, body, + vir_slt(&caller, body, call->result, call->result), &again, + &leave)); + } else { + assert(vir_edge_create(&caller, body, exit, NULL, 0)); + } + assert(vir_block_set_return(&caller, exit, call->result)); + assert(vir_verify(&caller, &error)); + + bool expected = !extra || loop; + assert(vir_inline_calls(&caller, inline_lookup, NULL, VIR_OPT_O2) == + expected); + assert(vir_verify(&caller, &error)); + assert(count_effects(&caller, VIR_EFFECT_CALL) == !expected); + for (int i = 0; expected && i < 16; i++) { + unsigned long long bits = 0xffffffffULL - 17 * i; + assert(run_function(&caller, bits) == + ((3 * bits + 7 + extra * bits) & 0xffffffffULL)); + } + inline_static = true; + vir_function_release(&caller); + vir_function_release(&callee); + } +} + +int main(void) +{ + test_integer_strength_rejections(); + test_integer_strength_reduction(); + test_call_inlining(); + test_extern_inlining(); + test_tail_recursion(); + test_affine_loops(); + test_affine_rejections(); + return 0; +} diff --git a/tests/vir-essential.c b/tests/vir-essential.c new file mode 100644 index 00000000..7d1ff656 --- /dev/null +++ b/tests/vir-essential.c @@ -0,0 +1,244 @@ +volatile unsigned int visible; +unsigned int reduce_data[37]; +unsigned long long reduce_wide_data[20]; + +/* The block after the loop stores the sum before defining any value, so the + * combined sum must precede that store. + */ +unsigned int reduce_sink; + +/* Helpers are defined after main, so they keep their calls and graphs; the + * leaves above main are the ones inlining replaces. + */ +unsigned int jump32(unsigned int seed); +unsigned int reference32(unsigned int seed); +unsigned long long jump64(unsigned long long seed); +unsigned long long reference64(unsigned long long seed); +unsigned int triangle(unsigned int n); +unsigned int rotate(unsigned int n, unsigned int a, unsigned int b); +unsigned int small_fib(unsigned int n); +unsigned int effectful(unsigned int n); +unsigned char narrow(unsigned int n); +int signed_jump(int seed); +int signed_reference(int seed); +int narrow_tail(signed char n, int total); +unsigned int reduce_even(void); +unsigned int reduce_odd(void); +void reduce_store(void); +unsigned long long reduce_wide(void); +int signed_divide(int x); +int signed_remainder(int x); +unsigned int unsigned_divide(unsigned int x); +unsigned int unsigned_remainder(unsigned int x); +long long wide_divide(long long x); +long long wide_remainder(long long x); +static unsigned int mix_leaf(unsigned int x) +{ + x ^= x >> 16; + x *= 2246822519u; + x ^= x >> 13; + x *= 3266489917u; + return x ^ (x >> 16); +} +static unsigned char narrow_leaf(unsigned char x, signed char y) +{ + return x * 3 + y; +} +static short short_leaf(short x) +{ + return x * 1000; +} +int main(void) +{ + for (unsigned int seed = 0; seed < 32; seed++) { + unsigned int input = 4294967295u - seed * 7919u; + if (jump32(input) != reference32(input)) + return 1; + unsigned long long wide = 18446744073709551615ULL - seed * 7919u; + if (jump64(wide) != reference64(wide)) + return 2; + if (triangle(seed) != seed * (seed + 1) / 2) + return 3; + } + if (rotate(10, 1, 1) != 89 || small_fib(13) != 233 || narrow(7) != 43) + return 4; + visible = 0; + if (effectful(20) != 210 || visible != 21) + return 5; + if (mix_leaf(1) != 1617762472u) + return 6; + for (int i = -1025; i <= 1025; i++) { + int q = signed_divide(i), r = signed_remainder(i); + if (q * 16 + r != i || r <= -16 || r >= 16 || (i < 0 ? r > 0 : r < 0)) + return 7; + unsigned int u = (unsigned int) i; + if (unsigned_divide(u) * 128u + unsigned_remainder(u) != u || + unsigned_remainder(u) >= 128u) + return 8; + } + for (int seed = -40; seed < 40; seed += 7) + if (signed_jump(seed) != signed_reference(seed)) + return 10; + for (int x = 0; x < 300; x += 37) + for (int y = -130; y < 130; y += 51) + if (narrow_leaf(x, y) != + (unsigned char) ((unsigned char) x * 3 + (signed char) y)) + return 11; + if (short_leaf(40) != (short) 40000 || short_leaf(-32) != -32000) + return 12; + if (narrow_tail(100, 0) != 5050 || narrow_tail(-1, 7) != 7 || + narrow_tail(300, 0) != 990) + return 13; + for (unsigned int i = 0; i < 37; i++) + reduce_data[i] = i * 7 + 3 + (i == 36 ? 4294967000u : 0); + for (unsigned int i = 0; i < 20; i++) + reduce_wide_data[i] = 0x100000000ULL * i + 4294967295ULL; + /* 7 * 35 * 36 / 2 + 3 * 36 + 5; the last element wraps the odd sum. */ + if (reduce_even() != 4523u || reduce_odd() != 4523u + 4294967255u || + reduce_wide() != 0x100000000ULL * 190 + 4294967295ULL * 20) + return 14; + reduce_store(); + if (reduce_sink != 4523u) + return 15; + if (signed_divide(-2147483647 - 1) != -134217728 || + signed_remainder(-2147483647 - 1) != 0 || + wide_divide(-9223372036854775807LL - 1) != -8388608 || + wide_remainder(-9223372036854775807LL - 1) != 0 || + wide_divide(-1099511627777LL) != -1 || + wide_remainder(-1099511627777LL) != -1) + return 9; + return 0; +} +unsigned int jump32(unsigned int seed) +{ + for (unsigned int i = 2; i < 37; i += 3) + seed = seed * 1664525u + i * 7u + 1013904223u; + return seed; +} +unsigned int reference32(unsigned int seed) +{ + for (unsigned int i = 2; i < 37; i += 3) { + visible++; + seed = seed * 1664525u + i * 7u + 1013904223u; + } + return seed; +} +unsigned long long jump64(unsigned long long seed) +{ + for (unsigned long long i = 3; i < 102; i += 5) + seed = + seed * 6364136223846793005ULL + i * 23ULL + 1442695040888963407ULL; + return seed; +} +unsigned long long reference64(unsigned long long seed) +{ + for (unsigned long long i = 3; i < 102; i += 5) { + visible++; + seed = + seed * 6364136223846793005ULL + i * 23ULL + 1442695040888963407ULL; + } + return seed; +} +unsigned int triangle(unsigned int n) +{ + if (!n) + return 0; + return triangle(n - 1) + n; +} +unsigned int rotate(unsigned int n, unsigned int a, unsigned int b) +{ + if (!n) + return a; + return rotate(n - 1, b, a + b); +} +unsigned int small_fib(unsigned int n) +{ + if (n < 2) + return n; + return small_fib(n - 1) + small_fib(n - 2); +} +unsigned int effectful(unsigned int n) +{ + visible++; + if (!n) + return 0; + return effectful(n - 1) + n; +} +unsigned char narrow(unsigned int n) +{ + if (!n) + return 250; + return narrow(n - 1) + 7; +} +int signed_jump(int seed) +{ + for (int i = 1; i < 40; i += 4) + seed = seed * 3 - i; + return seed; +} +int signed_reference(int seed) +{ + for (int i = 1; i < 40; i += 4) { + visible++; + seed = seed * 3 - i; + } + return seed; +} +int narrow_tail(signed char n, int total) +{ + if (n <= 0) + return total; + return narrow_tail(n - 1, total + n); +} +unsigned int reduce_even(void) +{ + unsigned int s = 5, *p = reduce_data; + for (unsigned int i = 0; i < 36; i++) + s += *p++; + return s; +} +unsigned int reduce_odd(void) +{ + unsigned int s = 5, *p = reduce_data; + for (unsigned int i = 0; i < 37; i++) + s += *p++; + return s; +} +void reduce_store(void) +{ + unsigned int s = 5, *p = reduce_data; + for (unsigned int i = 0; i < 36; i++) + s += *p++; + reduce_sink = s; +} +unsigned long long reduce_wide(void) +{ + unsigned long long s = 0, *p = reduce_wide_data; + for (unsigned int i = 0; i < 20; i++) + s += *p++; + return s; +} +int signed_divide(int x) +{ + return x / 16; +} +int signed_remainder(int x) +{ + return x % 16; +} +unsigned int unsigned_divide(unsigned int x) +{ + return x / 128u; +} +unsigned int unsigned_remainder(unsigned int x) +{ + return x % 128u; +} +long long wide_divide(long long x) +{ + return x / 1099511627776LL; +} +long long wide_remainder(long long x) +{ + return x % 1099511627776LL; +} diff --git a/tests/vir-essential.sh b/tests/vir-essential.sh new file mode 100755 index 00000000..b5dd8e8e --- /dev/null +++ b/tests/vir-essential.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +set -euo pipefail +compiler=("$@") +[ "${#compiler[@]}" -gt 0 ] || compiler=(out/shecc) +task_dir=$(mktemp -d) +trap 'rm -rf "$task_dir"' EXIT +# The helpers follow main, so they keep their own graphs for inspection. +"${compiler[@]}" --dump-ir -o "$task_dir/test" tests/vir-essential.c > "$task_dir/graph" +python3 - "$task_dir/graph" << 'PY' +import re +import sys + +text = open(sys.argv[1]).read() +functions = dict(re.findall(r'function (\w+)\n(.*?)(?=\nfunction |\Z)', text, re.S)) +for name in ('jump32', 'jump64', 'signed_jump'): + assert 'branch ' not in functions[name], name +for name in ('reference32', 'reference64', 'signed_reference'): + assert 'branch ' in functions[name] and 'volatile.store' in functions[name], name +for name in ('triangle', 'rotate'): + assert '@' + name + '(' not in functions[name], name +assert functions['small_fib'].count('@small_fib(') == 1 +# An even-count pointer reduction gains a second load and accumulator; an odd +# count would need a remainder iteration, so it stays as written. +for name, loads in (('reduce_even', 2), ('reduce_wide', 2), ('reduce_store', 2), + ('reduce_odd', 1)): + assert functions[name].count(' = load.') == loads, name +assert '@effectful(' in functions['effectful'] +assert '@narrow(' in functions['narrow'] +# Narrow parameters need ABI conversion, so the recursion stays a call. +assert '@narrow_tail(' in functions['narrow_tail'] +for name in ('signed_divide', 'signed_remainder', 'unsigned_divide', + 'unsigned_remainder', 'wide_divide', 'wide_remainder'): + assert not re.search(r' = [su](?:div|rem)\.', functions[name]), name +PY + +# The leaves defined above main are inlined, narrow arguments included: their +# calls and their unreachable definitions both disappear. +for leaf in mix_leaf narrow_leaf short_leaf; do + if grep -q "call.* @$leaf(\|^function $leaf\$" "$task_dir/graph"; then + echo "Leaf $leaf was not inlined" >&2 + exit 1 + fi +done +echo 'Essential VIR optimization graph checks passed' diff --git a/tests/vir-frontend-aggregate-call-result.c b/tests/vir-frontend-aggregate-call-result.c index fe2a3fac..90e02d93 100644 --- a/tests/vir-frontend-aggregate-call-result.c +++ b/tests/vir-frontend-aggregate-call-result.c @@ -2,6 +2,14 @@ struct pair { int value; }; +static struct pair make_pair(int value); +int read_pair(void); + + +int main(void) +{ + return read_pair(); +} static struct pair make_pair(int value) { struct pair result; @@ -9,13 +17,7 @@ static struct pair make_pair(int value) result.value = value; return result; } - int read_pair(void) { return make_pair(31).value; } - -int main(void) -{ - return read_pair(); -} diff --git a/tests/vir-frontend-bitwise.c b/tests/vir-frontend-bitwise.c index afb4121c..a9f17128 100644 --- a/tests/vir-frontend-bitwise.c +++ b/tests/vir-frontend-bitwise.c @@ -1,20 +1,23 @@ +int bits(int a, int b); +int shifts(int a, int b); +unsigned shifts_unsigned(unsigned a); + + + +int main(void) +{ + return bits(6, 3) + shifts(6, 8) + + (int) (shifts_unsigned(0xfffffffeu) & 255u); +} int bits(int a, int b) { return (~a & 15) | (a ^ b); } - int shifts(int a, int b) { return (a << 1) + (b >> 2); } - unsigned shifts_unsigned(unsigned a) { return a >> 1; } - -int main(void) -{ - return bits(6, 3) + shifts(6, 8) + - (int) (shifts_unsigned(0xfffffffeu) & 255u); -} diff --git a/tests/vir-frontend-bool-call-arg.c b/tests/vir-frontend-bool-call-arg.c index 3f2a3fb7..aac1d9f1 100644 --- a/tests/vir-frontend-bool-call-arg.c +++ b/tests/vir-frontend-bool-call-arg.c @@ -2,18 +2,27 @@ * Truth values passed to a direct call: a _Bool known at once, one resolved * after a join, and a comparison passed as is. */ +int take(_Bool b, int k); +int direct(int a, int b); +int deferred(int a, int b); +int cmp_arg(int a, int b); + + + +int main() +{ + return direct(1, 2) + deferred(1, 2) * 2 + cmp_arg(2, 1) * 4; +} int take(_Bool b, int k) { return b * 10 + k; } - int direct(int a, int b) { _Bool f = a < b; return take(f, 1); } - int deferred(int a, int b) { if (!a || !b) @@ -22,13 +31,7 @@ int deferred(int a, int b) return take(f, 2); } - int cmp_arg(int a, int b) { return take(a < b, 3); } - -int main() -{ - return direct(1, 2) + deferred(1, 2) * 2 + cmp_arg(2, 1) * 4; -} diff --git a/tests/vir-frontend-calls.c b/tests/vir-frontend-calls.c index 709db1ea..ecc6d02b 100644 --- a/tests/vir-frontend-calls.c +++ b/tests/vir-frontend-calls.c @@ -1,14 +1,16 @@ +int add(int left, int right); +int call_add(void); + + +int main(void) +{ + return call_add(); +} int add(int left, int right) { return left + right; } - int call_add(void) { return add(4, 9); } - -int main(void) -{ - return call_add(); -} diff --git a/tests/vir-frontend-casts.c b/tests/vir-frontend-casts.c index e3828fbb..669a3029 100644 --- a/tests/vir-frontend-casts.c +++ b/tests/vir-frontend-casts.c @@ -1,33 +1,36 @@ +int signed_char_promote(signed char value); +int unsigned_char_promote(unsigned char value); +int signed_short_promote(short value); +int unsigned_short_promote(unsigned short value); +int narrow_then_extend(int value); + + + +int main(void) +{ + return signed_char_promote(-2) + unsigned_char_promote(254) + + signed_short_promote(-2) + unsigned_short_promote(2) + + narrow_then_extend(258); +} int signed_char_promote(signed char value) { return value + 1; } - int unsigned_char_promote(unsigned char value) { return value + 1; } - int signed_short_promote(short value) { return value + 1; } - int unsigned_short_promote(unsigned short value) { return value + 1; } - int narrow_then_extend(int value) { signed char narrowed = (signed char) value; return narrowed; } - -int main(void) -{ - return signed_char_promote(-2) + unsigned_char_promote(254) + - signed_short_promote(-2) + unsigned_short_promote(2) + - narrow_then_extend(258); -} diff --git a/tests/vir-frontend-comparisons.c b/tests/vir-frontend-comparisons.c index 6a6e2234..9142c438 100644 --- a/tests/vir-frontend-comparisons.c +++ b/tests/vir-frontend-comparisons.c @@ -1,51 +1,54 @@ +int not_equal(int a, int b); +int less_equal(int a, int b); +int greater_equal(int a, int b); +int greater(int a, int b); +int logical_not(int a); +int branch_not(int a); +int logical_arith(int a); +int comparison_arith(int a, int b); + + + +int main(void) +{ + if (not_equal(3, 7) + less_equal(3, 7) + greater_equal(7, 3) + + greater(7, 3) + logical_not(0) + branch_not(0) == + 6 && + logical_arith(0) + comparison_arith(3, 7) == 10) + return 0; + return 1; +} int not_equal(int a, int b) { return a != b; } - int less_equal(int a, int b) { return a <= b; } - int greater_equal(int a, int b) { return a >= b; } - int greater(int a, int b) { return a > b; } - int logical_not(int a) { return !a; } - int branch_not(int a) { if (!a) return 1; return 0; } - int logical_arith(int a) { return !a + 3; } - int comparison_arith(int a, int b) { return (a < b) + 5; } - -int main(void) -{ - if (not_equal(3, 7) + less_equal(3, 7) + greater_equal(7, 3) + - greater(7, 3) + logical_not(0) + branch_not(0) == - 6 && - logical_arith(0) + comparison_arith(3, 7) == 10) - return 0; - return 1; -} diff --git a/tests/vir-frontend-cse.c b/tests/vir-frontend-cse.c index 3939cd77..e7d2b30f 100644 --- a/tests/vir-frontend-cse.c +++ b/tests/vir-frontend-cse.c @@ -1,3 +1,9 @@ +int local_cse(int a, int b); + +int main(void) +{ + return local_cse(6, 8); +} int local_cse(int a, int b) { int first = a + b; @@ -5,8 +11,3 @@ int local_cse(int a, int b) return first + second; } - -int main(void) -{ - return local_cse(6, 8); -} diff --git a/tests/vir-frontend-dce.c b/tests/vir-frontend-dce.c index 5a3bc6cb..e1254db5 100644 --- a/tests/vir-frontend-dce.c +++ b/tests/vir-frontend-dce.c @@ -1,8 +1,15 @@ +int callee(int value); +int dce_chain(int value); + + +int main(void) +{ + return dce_chain(12); +} int callee(int value) { return value + 1; } - int dce_chain(int value) { int dead0 = value + 2; @@ -10,8 +17,3 @@ int dce_chain(int value) return callee(value); } - -int main(void) -{ - return dce_chain(12); -} diff --git a/tests/vir-frontend-deferred-bool.c b/tests/vir-frontend-deferred-bool.c index 2b81ae06..cefbcb29 100644 --- a/tests/vir-frontend-deferred-bool.c +++ b/tests/vir-frontend-deferred-bool.c @@ -2,6 +2,12 @@ * A comparison stored in a _Bool and returned, after a join the frontend * resolves only once the whole function has been read. */ +_Bool not_six(int a, int b); + +int main() +{ + return not_six(2, 3) + not_six(2, 4) * 2 + not_six(0, 1) * 4; +} _Bool not_six(int a, int b) { if (!a || !b) @@ -10,8 +16,3 @@ _Bool not_six(int a, int b) _Bool r = n != 6; return r; } - -int main() -{ - return not_six(2, 3) + not_six(2, 4) * 2 + not_six(0, 1) * 4; -} diff --git a/tests/vir-frontend-deferred-call-arg.c b/tests/vir-frontend-deferred-call-arg.c index 0d6f36ed..9c8bac25 100644 --- a/tests/vir-frontend-deferred-call-arg.c +++ b/tests/vir-frontend-deferred-call-arg.c @@ -5,11 +5,18 @@ */ static int g; +int take(int *p, int flag, long long wide, int n); +int deferred(int a, int b); + + +int main() +{ + return deferred(1, 3) + deferred(0, 1) + 1; +} int take(int *p, int flag, long long wide, int n) { return (p == &g) + flag * 2 + (int) (wide >> 32) * 4 + n * 16; } - int deferred(int a, int b) { if (!a || !b) @@ -20,8 +27,3 @@ int deferred(int a, int b) int *p = &g; return take(p, flag, wide, n); } - -int main() -{ - return deferred(1, 3) + deferred(0, 1) + 1; -} diff --git a/tests/vir-frontend-deferred-order.c b/tests/vir-frontend-deferred-order.c index 9315aab4..7ecf7198 100644 --- a/tests/vir-frontend-deferred-order.c +++ b/tests/vir-frontend-deferred-order.c @@ -5,16 +5,32 @@ */ int g; +void h_g(int x); +int h_mix(int i, int u, int v); +int reread(int c); +int rebind_bool(int c, int a); +int rebind_arg(int c, int a); +int rebind_loop(int n); + + + +int main() +{ + int a = reread(1); + int b = rebind_bool(1, 5); + int c = rebind_arg(1, 1); + int d = rebind_loop(4); + + return a + b + c * 2 + g * 3 + (d == 758) * 20; +} void h_g(int x) { g = x; } - int h_mix(int i, int u, int v) { return i * 100 + u * 10 + v; } - int reread(int c) { if (!c || c > 5) @@ -23,7 +39,6 @@ int reread(int c) r = c + 1; return r; } - int rebind_bool(int c, int a) { if (!c || !a) @@ -32,7 +47,6 @@ int rebind_bool(int c, int a) a = 0; return r * 10 + a; } - int rebind_arg(int c, int a) { int x = 1; @@ -42,7 +56,6 @@ int rebind_arg(int c, int a) x = 7; return x; } - int rebind_loop(int n) { int s = 0, u = 1, v = 2; @@ -53,13 +66,3 @@ int rebind_loop(int n) } return s; } - -int main() -{ - int a = reread(1); - int b = rebind_bool(1, 5); - int c = rebind_arg(1, 1); - int d = rebind_loop(4); - - return a + b + c * 2 + g * 3 + (d == 758) * 20; -} diff --git a/tests/vir-frontend-do-exit-value.c b/tests/vir-frontend-do-exit-value.c index 93371e05..76236968 100644 --- a/tests/vir-frontend-do-exit-value.c +++ b/tests/vir-frontend-do-exit-value.c @@ -1,3 +1,9 @@ +int last_before(int limit); + +int main(void) +{ + return last_before(4); +} int last_before(int limit) { int value; @@ -9,8 +15,3 @@ int last_before(int limit) } while (index < limit); return value; } - -int main(void) -{ - return last_before(4); -} diff --git a/tests/vir-frontend-do-loop.c b/tests/vir-frontend-do-loop.c index 213b564e..2c82726b 100644 --- a/tests/vir-frontend-do-loop.c +++ b/tests/vir-frontend-do-loop.c @@ -1,3 +1,9 @@ +int count_to(int limit); + +int main(void) +{ + return count_to(9); +} int count_to(int limit) { int value = 0; @@ -7,8 +13,3 @@ int count_to(int limit) } while (value < limit); return value; } - -int main(void) -{ - return count_to(9); -} diff --git a/tests/vir-frontend-early-return.c b/tests/vir-frontend-early-return.c index 0ede6406..a391fea2 100644 --- a/tests/vir-frontend-early-return.c +++ b/tests/vir-frontend-early-return.c @@ -1,11 +1,12 @@ +int choose(int value); + +int main(void) +{ + return choose(-1) + choose(1); +} int choose(int value) { if (value < 0) return 7; return 3; } - -int main(void) -{ - return choose(-1) + choose(1); -} diff --git a/tests/vir-frontend-for-loop.c b/tests/vir-frontend-for-loop.c index 05be864e..af8ceec3 100644 --- a/tests/vir-frontend-for-loop.c +++ b/tests/vir-frontend-for-loop.c @@ -1,3 +1,9 @@ +int count_to(int limit); + +int main(void) +{ + return count_to(9); +} int count_to(int limit) { int value = 0; @@ -6,8 +12,3 @@ int count_to(int limit) ; return value; } - -int main(void) -{ - return count_to(9); -} diff --git a/tests/vir-frontend-global-array.c b/tests/vir-frontend-global-array.c index 72cb523c..f4821b8e 100644 --- a/tests/vir-frontend-global-array.c +++ b/tests/vir-frontend-global-array.c @@ -5,18 +5,20 @@ int table[4] = {1, 2, 3, 4}; char bytes[16]; +int sum(int *p); +int pick(int i); + + +int main() +{ + bytes[3] = 5; + return sum(table) + pick(3); +} int sum(int *p) { return p[0] + p[3] * 10; } - int pick(int i) { return bytes[i]; } - -int main() -{ - bytes[3] = 5; - return sum(table) + pick(3); -} diff --git a/tests/vir-frontend-gvn.c b/tests/vir-frontend-gvn.c index ba53f030..d6328e8f 100644 --- a/tests/vir-frontend-gvn.c +++ b/tests/vir-frontend-gvn.c @@ -1,3 +1,9 @@ +int gvn_cross(int flag, int a, int b); + +int main(void) +{ + return gvn_cross(0, 9, 12); +} int gvn_cross(int flag, int a, int b) { int value = a + b; @@ -9,8 +15,3 @@ int gvn_cross(int flag, int a, int b) result = a + b; return result; } - -int main(void) -{ - return gvn_cross(0, 9, 12); -} diff --git a/tests/vir-frontend-join-arith.c b/tests/vir-frontend-join-arith.c index 039451d3..4872cbdd 100644 --- a/tests/vir-frontend-join-arith.c +++ b/tests/vir-frontend-join-arith.c @@ -1,3 +1,9 @@ +int choose(int flag, int left, int right); + +int main(void) +{ + return choose(-1, 7, 2) + choose(1, 7, 2); +} int choose(int flag, int left, int right) { int value; @@ -8,8 +14,3 @@ int choose(int flag, int left, int right) value = left - right; return value * 2 + 1; } - -int main(void) -{ - return choose(-1, 7, 2) + choose(1, 7, 2); -} diff --git a/tests/vir-frontend-join-branch.c b/tests/vir-frontend-join-branch.c index 53322c39..4c5de300 100644 --- a/tests/vir-frontend-join-branch.c +++ b/tests/vir-frontend-join-branch.c @@ -1,3 +1,9 @@ +int choose(int flag, int left, int right); + +int main(void) +{ + return choose(-1, 7, 2) + choose(1, 7, 2); +} int choose(int flag, int left, int right) { int value; @@ -13,8 +19,3 @@ int choose(int flag, int left, int right) result = 2; return result; } - -int main(void) -{ - return choose(-1, 7, 2) + choose(1, 7, 2); -} diff --git a/tests/vir-frontend-join-trivial.c b/tests/vir-frontend-join-trivial.c index 08fa7fba..aa065577 100644 --- a/tests/vir-frontend-join-trivial.c +++ b/tests/vir-frontend-join-trivial.c @@ -1,3 +1,9 @@ +int choose(int flag, int value); + +int main(void) +{ + return choose(-1, 7) + choose(1, 7); +} int choose(int flag, int value) { int result; @@ -8,8 +14,3 @@ int choose(int flag, int value) result = value; return result; } - -int main(void) -{ - return choose(-1, 7) + choose(1, 7); -} diff --git a/tests/vir-frontend-join-uninitialized.c b/tests/vir-frontend-join-uninitialized.c index b29ec0c6..bc3e04d5 100644 --- a/tests/vir-frontend-join-uninitialized.c +++ b/tests/vir-frontend-join-uninitialized.c @@ -1,3 +1,9 @@ +int choose(int flag, int value); + +int main(void) +{ + return choose(-1, 7); +} int choose(int flag, int value) { int result; @@ -6,8 +12,3 @@ int choose(int flag, int value) result = value; return result + 1; } - -int main(void) -{ - return choose(-1, 7); -} diff --git a/tests/vir-frontend-join.c b/tests/vir-frontend-join.c index fea10cf6..c918b81a 100644 --- a/tests/vir-frontend-join.c +++ b/tests/vir-frontend-join.c @@ -1,3 +1,9 @@ +int choose(int flag, int left, int right); + +int main(void) +{ + return choose(-1, 7, 2) + choose(1, 7, 2); +} int choose(int flag, int left, int right) { int value; @@ -8,8 +14,3 @@ int choose(int flag, int left, int right) value = left - right; return value; } - -int main(void) -{ - return choose(-1, 7, 2) + choose(1, 7, 2); -} diff --git a/tests/vir-frontend-large-cfg.c b/tests/vir-frontend-large-cfg.c index cb2dc201..b8d1603c 100644 --- a/tests/vir-frontend-large-cfg.c +++ b/tests/vir-frontend-large-cfg.c @@ -3,6 +3,12 @@ * forty blocks before O2 cleanup, exercising deferred large-CFG pruning through * LICM without relying on a target-specific backend form. */ +static int vir_large_cfg(int x); + +int main(void) +{ + return vir_large_cfg(0) != 23; +} static int vir_large_cfg(int x) { int result = 0; @@ -59,8 +65,3 @@ static int vir_large_cfg(int x) result += 23; return result; } - -int main(void) -{ - return vir_large_cfg(0) != 23; -} diff --git a/tests/vir-frontend-licm.c b/tests/vir-frontend-licm.c index 17510faa..7b57350f 100644 --- a/tests/vir-frontend-licm.c +++ b/tests/vir-frontend-licm.c @@ -1,3 +1,9 @@ +int hoist(int limit, int a, int b); + +int main(void) +{ + return hoist(3, 4, 5); +} int hoist(int limit, int a, int b) { int value = 0; @@ -6,8 +12,3 @@ int hoist(int limit, int a, int b) value = value + a * b; return value; } - -int main(void) -{ - return hoist(3, 4, 5); -} diff --git a/tests/vir-frontend-load.c b/tests/vir-frontend-load.c index 532ad384..2d37a66d 100644 --- a/tests/vir-frontend-load.c +++ b/tests/vir-frontend-load.c @@ -1,7 +1,4 @@ -int load_value(int *pointer) -{ - return *pointer; -} +int load_value(int *pointer); int main(void) { @@ -9,3 +6,7 @@ int main(void) return load_value(&value); } +int load_value(int *pointer) +{ + return *pointer; +} diff --git a/tests/vir-frontend-local-array.c b/tests/vir-frontend-local-array.c index a4df5e03..03d7554e 100644 --- a/tests/vir-frontend-local-array.c +++ b/tests/vir-frontend-local-array.c @@ -2,6 +2,18 @@ * Local arrays are represented as stack roots. Their backing storage comes from * OP_allocat, so each object must retain its full size and alignment. */ +int local_arrays(int seed); +int last_element(void); + +/* The pointer is spilled while the store through it is pending; with only a + * pointer slot reserved, that spill landed inside the array and the store + * overwrote it. + */ + +int main(int argc) +{ + return local_arrays(argc) + last_element(); +} int local_arrays(int seed) { int words[3]; @@ -17,11 +29,6 @@ int local_arrays(int seed) *alias = *alias + words[2]; return words[0] + words[1] + words[2] + bytes[4] + scalar; } - -/* The pointer is spilled while the store through it is pending; with only a - * pointer slot reserved, that spill landed inside the array and the store - * overwrote it. - */ int last_element(void) { int a[3]; @@ -30,8 +37,3 @@ int last_element(void) *p = 7; return a[2] - 7; } - -int main(int argc) -{ - return local_arrays(argc) + last_element(); -} diff --git a/tests/vir-frontend-loop-index.c b/tests/vir-frontend-loop-index.c index 4a143e27..218e2b8f 100644 --- a/tests/vir-frontend-loop-index.c +++ b/tests/vir-frontend-loop-index.c @@ -2,12 +2,7 @@ * The loop index is a block parameter. Treating it as the constant 0 used to * drop the pointer addition, so every iteration copied element 0. */ -int copy_bytes(char *dst, char *src, int n) -{ - for (int i = 0; i < n; i++) - dst[i] = src[i]; - return n; -} +int copy_bytes(char *dst, char *src, int n); int main(void) { @@ -21,3 +16,9 @@ int main(void) dst[3] = 0; return copy_bytes(dst, src, 4) + dst[3] + dst[1]; } +int copy_bytes(char *dst, char *src, int n) +{ + for (int i = 0; i < n; i++) + dst[i] = src[i]; + return n; +} diff --git a/tests/vir-frontend-merged-effects.c b/tests/vir-frontend-merged-effects.c index 7e0652a6..0d791cb3 100644 --- a/tests/vir-frontend-merged-effects.c +++ b/tests/vir-frontend-merged-effects.c @@ -1,6 +1,12 @@ static int left; static int right; +static int merged_effect(int *first, int *second, int select); + +int main(void) +{ + return merged_effect(&left, &right, 1); +} static int merged_effect(int *first, int *second, int select) { int *address; @@ -12,8 +18,3 @@ static int merged_effect(int *first, int *second, int select) *address = 29; return *address; } - -int main(void) -{ - return merged_effect(&left, &right, 1); -} diff --git a/tests/vir-frontend-multi-latch.c b/tests/vir-frontend-multi-latch.c index 2e5cbf55..cc4c70fe 100644 --- a/tests/vir-frontend-multi-latch.c +++ b/tests/vir-frontend-multi-latch.c @@ -1,4 +1,10 @@ /* The continue edge and the fallthrough edge form distinct loop backedges. */ +static int multi_latch(int n, int a, int b); + +int main(void) +{ + return multi_latch(5, 2, 3) != 30; +} static int multi_latch(int n, int a, int b) { int i = 0; @@ -12,8 +18,3 @@ static int multi_latch(int n, int a, int b) } return sum; } - -int main(void) -{ - return multi_latch(5, 2, 3) != 30; -} diff --git a/tests/vir-frontend-narrow-call.c b/tests/vir-frontend-narrow-call.c index c0e3a738..40b438eb 100644 --- a/tests/vir-frontend-narrow-call.c +++ b/tests/vir-frontend-narrow-call.c @@ -1,25 +1,28 @@ +static signed char pass_char(signed char value); +static short pass_short(short value); +static unsigned char pass_uchar(unsigned char value); +static unsigned short pass_ushort(unsigned short value); + + + +int main(void) +{ + return pass_char(-7) + pass_short(36) + (pass_uchar(0x80) == 128) + + (pass_ushort(0xffff) == 65535); +} static signed char pass_char(signed char value) { return value; } - static short pass_short(short value) { return value; } - static unsigned char pass_uchar(unsigned char value) { return value; } - static unsigned short pass_ushort(unsigned short value) { return value; } - -int main(void) -{ - return pass_char(-7) + pass_short(36) + (pass_uchar(0x80) == 128) + - (pass_ushort(0xffff) == 65535); -} diff --git a/tests/vir-frontend-nested-loop.c b/tests/vir-frontend-nested-loop.c index 1ffdf8c8..1940d1b6 100644 --- a/tests/vir-frontend-nested-loop.c +++ b/tests/vir-frontend-nested-loop.c @@ -1,3 +1,9 @@ +int nested_licm(int outer_limit, int inner_limit, int a, int b, int c); + +int main(void) +{ + return nested_licm(2, 3, 4, 5, 6); +} int nested_licm(int outer_limit, int inner_limit, int a, int b, int c) { int i = 0; @@ -14,8 +20,3 @@ int nested_licm(int outer_limit, int inner_limit, int a, int b, int c) } return total; } - -int main(void) -{ - return nested_licm(2, 3, 4, 5, 6); -} diff --git a/tests/vir-frontend-param.c b/tests/vir-frontend-param.c index 2f2484f7..0c96b56f 100644 --- a/tests/vir-frontend-param.c +++ b/tests/vir-frontend-param.c @@ -1,9 +1,10 @@ -int add(int left, int right) -{ - return left + right; -} +int add(int left, int right); int main(void) { return add(5, 9); } +int add(int left, int right) +{ + return left + right; +} diff --git a/tests/vir-frontend-pointer-equality.c b/tests/vir-frontend-pointer-equality.c index 1dddab20..7cf1c5f6 100644 --- a/tests/vir-frontend-pointer-equality.c +++ b/tests/vir-frontend-pointer-equality.c @@ -1,7 +1,4 @@ -int same_pointer(int *left, int *right) -{ - return left == right; -} +int same_pointer(int *left, int *right); int main(void) { @@ -9,3 +6,7 @@ int main(void) return same_pointer(&value, &value) ? 29 : 0; } +int same_pointer(int *left, int *right) +{ + return left == right; +} diff --git a/tests/vir-frontend-pointer-load.c b/tests/vir-frontend-pointer-load.c index 210c4965..14a9686a 100644 --- a/tests/vir-frontend-pointer-load.c +++ b/tests/vir-frontend-pointer-load.c @@ -1,7 +1,4 @@ -int load_indirect(int **address) -{ - return **address; -} +int load_indirect(int **address); int main(void) { @@ -10,3 +7,7 @@ int main(void) return load_indirect(&pointer); } +int load_indirect(int **address) +{ + return **address; +} diff --git a/tests/vir-frontend-pointer-store.c b/tests/vir-frontend-pointer-store.c index 839c9554..9e2bc9be 100644 --- a/tests/vir-frontend-pointer-store.c +++ b/tests/vir-frontend-pointer-store.c @@ -1,8 +1,4 @@ -int store_pointer(int **address, int *value) -{ - *address = value; - return **address; -} +int store_pointer(int **address, int *value); int main(void) { @@ -11,3 +7,8 @@ int main(void) return store_pointer(&pointer, &value); } +int store_pointer(int **address, int *value) +{ + *address = value; + return **address; +} diff --git a/tests/vir-frontend-pointer-truth.c b/tests/vir-frontend-pointer-truth.c index a8169431..0f80e3c6 100644 --- a/tests/vir-frontend-pointer-truth.c +++ b/tests/vir-frontend-pointer-truth.c @@ -2,32 +2,35 @@ * Testing a pointer for null: "!p", a comparison with a literal 0 on either * side, and a pointer used directly as a branch condition. */ +int not_ptr(int *p); +int eq_null(int *p); +int ne_null(int *p); +int branch_ptr(int *p); + + + +int main() +{ + int x = 1; + + return not_ptr(&x) + eq_null(&x) * 2 + ne_null(&x) * 4 + + branch_ptr(&x) * 8 + not_ptr(0) * 64 + ne_null(0) * 128; +} int not_ptr(int *p) { return !p; } - int eq_null(int *p) { return p == 0; } - int ne_null(int *p) { return 0 != p; } - int branch_ptr(int *p) { if (p) return 3; return 4; } - -int main() -{ - int x = 1; - - return not_ptr(&x) + eq_null(&x) * 2 + ne_null(&x) * 4 + - branch_ptr(&x) * 8 + not_ptr(0) * 64 + ne_null(0) * 128; -} diff --git a/tests/vir-frontend-riscv-variadic.c b/tests/vir-frontend-riscv-variadic.c index c8a0a8de..54058f9c 100644 --- a/tests/vir-frontend-riscv-variadic.c +++ b/tests/vir-frontend-riscv-variadic.c @@ -1,15 +1,31 @@ #include +long long fixed_target(long long value); +long long fixed_forward(long long value); +long long variadic_target(int count, ...); +long long variadic_forward(long long value); + + + +/* A variadic callee must keep this otherwise fixed-width forwarder out of VIR. + * RV32 realigns an unnamed i64 differently from a named one. + */ + +int main(void) +{ + return fixed_forward(0x100000003LL) == 0x300000009LL && + variadic_forward(0x100000003LL) == 0x50000000fLL + ? 47 + : 1; +} long long fixed_target(long long value) { return value * 3; } - long long fixed_forward(long long value) { return fixed_target(value); } - long long variadic_target(int count, ...) { va_list args; @@ -20,19 +36,7 @@ long long variadic_target(int count, ...) va_end(args); return count == 1 ? value * 5 : 0; } - -/* A variadic callee must keep this otherwise fixed-width forwarder out of VIR. - * RV32 realigns an unnamed i64 differently from a named one. - */ long long variadic_forward(long long value) { return variadic_target(1, value); } - -int main(void) -{ - return fixed_forward(0x100000003LL) == 0x300000009LL && - variadic_forward(0x100000003LL) == 0x50000000fLL - ? 47 - : 1; -} diff --git a/tests/vir-frontend-roots-init.c b/tests/vir-frontend-roots-init.c index 4b76b35f..73f7e07f 100644 --- a/tests/vir-frontend-roots-init.c +++ b/tests/vir-frontend-roots-init.c @@ -1,3 +1,9 @@ +int initialized_local(void); + +int main(void) +{ + return initialized_local(); +} int initialized_local(void) { int object = 42; @@ -5,8 +11,3 @@ int initialized_local(void) return *pointer; } - -int main(void) -{ - return initialized_local(); -} diff --git a/tests/vir-frontend-roots.c b/tests/vir-frontend-roots.c index 495c292a..d3bc63a1 100644 --- a/tests/vir-frontend-roots.c +++ b/tests/vir-frontend-roots.c @@ -1,5 +1,13 @@ int global_object; +int store_local(int value); +int store_global(int value); + + +int main(void) +{ + return store_local(17) + store_global(25); +} int store_local(int value) { int object; @@ -8,7 +16,6 @@ int store_local(int value) *pointer = value; return *pointer; } - int store_global(int value) { int *pointer = &global_object; @@ -16,8 +23,3 @@ int store_global(int value) *pointer = value; return *pointer; } - -int main(void) -{ - return store_local(17) + store_global(25); -} diff --git a/tests/vir-frontend-sccp-widths.c b/tests/vir-frontend-sccp-widths.c index 029fb0c7..b1ff9b8f 100644 --- a/tests/vir-frontend-sccp-widths.c +++ b/tests/vir-frontend-sccp-widths.c @@ -1,5 +1,13 @@ volatile int selector; +int signed_sccp_i32(void); +int unsigned_sccp_i32(void); + + +int main(void) +{ + return signed_sccp_i32() && unsigned_sccp_i32() ? 0 : 1; +} int signed_sccp_i32(void) { int value; @@ -10,7 +18,6 @@ int signed_sccp_i32(void) value = -7; return value / 3 == -2 && value % 3 == -1; } - int unsigned_sccp_i32(void) { unsigned int value; @@ -21,8 +28,3 @@ int unsigned_sccp_i32(void) value = 0xffffffffU; return value / 3U == 0x55555555U && value % 3U == 0; } - -int main(void) -{ - return signed_sccp_i32() && unsigned_sccp_i32() ? 0 : 1; -} diff --git a/tests/vir-frontend-sccp.c b/tests/vir-frontend-sccp.c index 54854c07..968c8e5e 100644 --- a/tests/vir-frontend-sccp.c +++ b/tests/vir-frontend-sccp.c @@ -1,3 +1,9 @@ +int sccp_branch(void); + +int main(void) +{ + return sccp_branch(); +} int sccp_branch(void) { int first = 8; @@ -7,8 +13,3 @@ int sccp_branch(void) return second * 7; return first; } - -int main(void) -{ - return sccp_branch(); -} diff --git a/tests/vir-frontend-store.c b/tests/vir-frontend-store.c index d5fec45d..86a4e263 100644 --- a/tests/vir-frontend-store.c +++ b/tests/vir-frontend-store.c @@ -1,8 +1,4 @@ -int store_then_load(int *pointer, int value) -{ - *pointer = value; - return *pointer; -} +int store_then_load(int *pointer, int value); int main(void) { @@ -10,3 +6,8 @@ int main(void) return store_then_load(&value, 42); } +int store_then_load(int *pointer, int value) +{ + *pointer = value; + return *pointer; +} diff --git a/tests/vir-frontend-string-literal.c b/tests/vir-frontend-string-literal.c index e2b7f30d..4bdf444f 100644 --- a/tests/vir-frontend-string-literal.c +++ b/tests/vir-frontend-string-literal.c @@ -2,6 +2,14 @@ * String literals as values: passed to a call, chosen between by a branch, and * walked through by the callee. */ +int count(char *s, char c); +int greet(int k); + + +int main() +{ + return greet(1) + greet(0); +} int count(char *s, char c) { int n = 0; @@ -13,15 +21,9 @@ int count(char *s, char c) } return n; } - int greet(int k) { char *m = k ? "hello, world" : "bye"; return count(m, 'o') + count("xoxo", 'o') * 10; } - -int main() -{ - return greet(1) + greet(0); -} diff --git a/tests/vir-frontend-swap.c b/tests/vir-frontend-swap.c index 662c6d59..d9add62d 100644 --- a/tests/vir-frontend-swap.c +++ b/tests/vir-frontend-swap.c @@ -2,6 +2,12 @@ * The back edge passes the two loop-carried values in swapped order. Edge * arguments must capture both values before assigning either destination. */ +int swap_loop(int n); + +int main(int argc) +{ + return swap_loop(argc + 2); +} int swap_loop(int n) { int a = 1; @@ -15,8 +21,3 @@ int swap_loop(int n) } return a * 10 + b; } - -int main(int argc) -{ - return swap_loop(argc + 2); -} diff --git a/tests/vir-frontend-ternary-array.c b/tests/vir-frontend-ternary-array.c index 75287ba6..f57a2af8 100644 --- a/tests/vir-frontend-ternary-array.c +++ b/tests/vir-frontend-ternary-array.c @@ -11,13 +11,14 @@ typedef struct { static const entry_t table[] = {{"a", 10}, {"b", 20}, {"c", 30}, {"d", 40}}; -int pick(int t) -{ - return (unsigned int) t < sizeof(table) / sizeof(*table) ? table[t].width - : 0; -} +int pick(int t); int main() { return pick(1) + pick(3) + pick(4) + pick(-1); } +int pick(int t) +{ + return (unsigned int) t < sizeof(table) / sizeof(*table) ? table[t].width + : 0; +} diff --git a/tests/vir-frontend-void-call-tail.c b/tests/vir-frontend-void-call-tail.c index 645dac22..702c06c4 100644 --- a/tests/vir-frontend-void-call-tail.c +++ b/tests/vir-frontend-void-call-tail.c @@ -4,21 +4,23 @@ */ static int total; +void bump(int n); +void bump_if(int n); + + +int main() +{ + bump_if(5); + bump_if(1); + return total; +} void bump(int n) { total += n; } - void bump_if(int n) { if (n > 2) bump(n); bump(1); } - -int main() -{ - bump_if(5); - bump_if(1); - return total; -} diff --git a/tests/vir-frontend-void.c b/tests/vir-frontend-void.c index cdb53446..7974ba2f 100644 --- a/tests/vir-frontend-void.c +++ b/tests/vir-frontend-void.c @@ -1,31 +1,15 @@ int total; -void set_explicit(int x) -{ - total = x; - return; -} +void set_explicit(int x); +void add_implicit(int x); +void add_early(int x); +void do_nothing(void); +void copy_bytes(char *dst, char *src, int n); -void add_implicit(int x) -{ - total = total + x; -} -void add_early(int x) -{ - if (x < 0) - return; - total = total + x; -} /* No values at all: empty blocks still need valid CFG representation. */ -void do_nothing(void) {} -void copy_bytes(char *dst, char *src, int n) -{ - for (int i = 0; i < n; i++) - dst[i] = src[i]; -} int main(void) { @@ -44,3 +28,24 @@ int main(void) copy_bytes(dst, src, 4); return total + dst[3]; } +void set_explicit(int x) +{ + total = x; + return; +} +void add_implicit(int x) +{ + total = total + x; +} +void add_early(int x) +{ + if (x < 0) + return; + total = total + x; +} +void do_nothing(void) {} +void copy_bytes(char *dst, char *src, int n) +{ + for (int i = 0; i < n; i++) + dst[i] = src[i]; +} diff --git a/tests/vir-frontend-volatile-mixed.c b/tests/vir-frontend-volatile-mixed.c index d5d19617..d2b35ac6 100644 --- a/tests/vir-frontend-volatile-mixed.c +++ b/tests/vir-frontend-volatile-mixed.c @@ -1,5 +1,11 @@ int sink; +int access_mixed(int *address, int value); + +int main(int argc) +{ + return access_mixed(&sink, argc + 28); +} int access_mixed(int *address, int value) { *address = value; @@ -7,8 +13,3 @@ int access_mixed(int *address, int value) *address = value + 1; return *address; } - -int main(int argc) -{ - return access_mixed(&sink, argc + 28); -} diff --git a/tests/vir-frontend-volatile.c b/tests/vir-frontend-volatile.c index 8154da2f..4fe5992b 100644 --- a/tests/vir-frontend-volatile.c +++ b/tests/vir-frontend-volatile.c @@ -1,12 +1,13 @@ volatile int sink; -int access_volatile(volatile int *address, int value) -{ - *address = value; - return *address; -} +int access_volatile(volatile int *address, int value); int main(int argc) { return access_volatile(&sink, argc + 28); } +int access_volatile(volatile int *address, int value) +{ + *address = value; + return *address; +} diff --git a/tests/vir-frontend-while-loop.c b/tests/vir-frontend-while-loop.c index c1a238f7..918c77d2 100644 --- a/tests/vir-frontend-while-loop.c +++ b/tests/vir-frontend-while-loop.c @@ -1,3 +1,9 @@ +int count_to(int limit); + +int main(void) +{ + return count_to(9); +} int count_to(int limit) { int value = 0; @@ -6,8 +12,3 @@ int count_to(int limit) value = value + 1; return value; } - -int main(void) -{ - return count_to(9); -} diff --git a/tests/vir-if-join.c b/tests/vir-if-join.c index cd367c6a..5ad35ff7 100644 --- a/tests/vir-if-join.c +++ b/tests/vir-if-join.c @@ -1,3 +1,32 @@ +int arm(int x); +int walk(int n); +int choose(int x); +int crossed(int x); +int main(void) +{ + int a = 0, b = 0; + if (1) + if (0) + a = 2; + else + a = 3; + else + a = 4; + if (0) + b = 2; + b += 7; + if (a != 3 || b != 7) + return 1; + if (arm(0) != 4 || arm(1) != 3) + return 2; + if (walk(9) != 13) + return 3; + if (choose(0) != 7 || choose(1) != 2) + return 4; + if (crossed(0) != 4 || crossed(1) != 7) + return 5; + return 0; +} int arm(int x) { if (x) @@ -48,28 +77,3 @@ int crossed(int x) done: return n; } -int main(void) -{ - int a = 0, b = 0; - if (1) - if (0) - a = 2; - else - a = 3; - else - a = 4; - if (0) - b = 2; - b += 7; - if (a != 3 || b != 7) - return 1; - if (arm(0) != 4 || arm(1) != 3) - return 2; - if (walk(9) != 13) - return 3; - if (choose(0) != 7 || choose(1) != 2) - return 4; - if (crossed(0) != 4 || crossed(1) != 7) - return 5; - return 0; -} diff --git a/tests/vir-loop-update.c b/tests/vir-loop-update.c index cdaffe86..57fc6688 100644 --- a/tests/vir-loop-update.c +++ b/tests/vir-loop-update.c @@ -14,19 +14,7 @@ struct Value { struct Block { Value *params, *head; }; -int guard(Block *block) -{ - for (int list = 0; list < 2; list++) - for (Value *value = list ? block->head : block->params; value; - value = list ? value->next : value->param_next) { - if (list && value->opcode != 1 && value->opcode != 2) - return 0; - for (Use *use = value->uses; use; use = use->next) - if (use->owner != block) - return 0; - } - return 1; -} +int guard(Block *block); int main(void) { unsigned hash = 0; @@ -90,3 +78,16 @@ int main(void) return 6; return 0; } +int guard(Block *block) +{ + for (int list = 0; list < 2; list++) + for (Value *value = list ? block->head : block->params; value; + value = list ? value->next : value->param_next) { + if (list && value->opcode != 1 && value->opcode != 2) + return 0; + for (Use *use = value->uses; use; use = use->next) + if (use->owner != block) + return 0; + } + return 1; +} diff --git a/tests/vir-lower-smoke.c b/tests/vir-lower-smoke.c index e5552a4c..005d582f 100644 --- a/tests/vir-lower-smoke.c +++ b/tests/vir-lower-smoke.c @@ -1,13 +1,59 @@ volatile int guard; -long long test_add(long long a, long long b) -{ - return a + b; -} +long long test_add(long long a, long long b); +long long vir_mixed(void); +long long test_va(int last, ...); +long long vir_vararg(void); +int vir_loop(void); +long long vir_wide(void); +long long vir_signed(void); +int vir_sr(void); +long long test_va_aggregate(int *last, ...); +long long vir_mul_left(long long a, long long b); +long long vir_mul_right(long long a, long long b); +long long vir_pointer_add(void *base, long long offset); long long test_mix(int x0, int x1, int x2, int x3, int x4, int x5, int x6, long long y) { return x0 + x1 + x2 + x3 + x4 + x5 + x6 + y; } +int main(void) +{ + if ((unsigned long long) vir_mul_left(0x12345678ffffffffULL, + 0xfedcba9812345678ULL) != + 0x314c741fedcba988ULL || + (unsigned long long) vir_mul_right(0x12345678ffffffffULL, + 0xfedcba9812345678ULL) != + 0x314c741fedcba988ULL || + (unsigned long long) vir_mul_left(-0x100000001LL, 0x200000003LL) != + 0xfffffffafffffffdULL || + (unsigned long long) vir_mul_right(-0x100000001LL, 0x200000003LL) != + 0xfffffffafffffffdULL) + return 8; + if (sizeof(void *) == 8 && + (vir_pointer_add((void *) 5, 0x100000000LL) != 0x100000005LL || + (unsigned long long) vir_pointer_add((void *) 5, -0x100000001LL) != + 0xffffffff00000004ULL)) + return 9; + if (test_va_aggregate(&guard, 0x123456789abcdef0LL) != 0x123456789abcdef0LL) + return 7; + if (vir_sr() != 28) + return 6; + if (vir_loop() != 97) + return 1; + if (vir_wide() != 0x369d036afffffffdLL) + return 2; + if (vir_signed() != -1LL) + return 3; + if (vir_mixed() != 0x123456789abcdee3LL) + return 4; + if (vir_vararg() != 0x123456789abcdef0LL) + return 5; + return 0; +} +long long test_add(long long a, long long b) +{ + return a + b; +} long long vir_mixed(void) { if (guard) @@ -72,37 +118,3 @@ long long vir_pointer_add(void *base, long long offset) return (long long) base + offset; return 0; } -int main(void) -{ - if ((unsigned long long) vir_mul_left(0x12345678ffffffffULL, - 0xfedcba9812345678ULL) != - 0x314c741fedcba988ULL || - (unsigned long long) vir_mul_right(0x12345678ffffffffULL, - 0xfedcba9812345678ULL) != - 0x314c741fedcba988ULL || - (unsigned long long) vir_mul_left(-0x100000001LL, 0x200000003LL) != - 0xfffffffafffffffdULL || - (unsigned long long) vir_mul_right(-0x100000001LL, 0x200000003LL) != - 0xfffffffafffffffdULL) - return 8; - if (sizeof(void *) == 8 && - (vir_pointer_add((void *) 5, 0x100000000LL) != 0x100000005LL || - (unsigned long long) vir_pointer_add((void *) 5, -0x100000001LL) != - 0xffffffff00000004ULL)) - return 9; - if (test_va_aggregate(&guard, 0x123456789abcdef0LL) != 0x123456789abcdef0LL) - return 7; - if (vir_sr() != 28) - return 6; - if (vir_loop() != 97) - return 1; - if (vir_wide() != 0x369d036afffffffdLL) - return 2; - if (vir_signed() != -1LL) - return 3; - if (vir_mixed() != 0x123456789abcdee3LL) - return 4; - if (vir_vararg() != 0x123456789abcdef0LL) - return 5; - return 0; -} diff --git a/tests/vir-machine-affinity.c b/tests/vir-machine-affinity.c index 02e2e636..9c27470c 100644 --- a/tests/vir-machine-affinity.c +++ b/tests/vir-machine-affinity.c @@ -1,4 +1,17 @@ /* Alternate incoming values and overlapping old/new values must not alias. */ +static unsigned int alternatives(int n); +static unsigned long long mix(unsigned long long x); +static unsigned long long overlapping(unsigned int n); +int main(void) +{ + unsigned int narrow = 0; + unsigned long long wide = 0; + for (int n = 0; n < 100; n++) { + narrow = narrow * 33u + alternatives(n); + wide = wide * 33 + overlapping(n); + } + return narrow != 1813477397u || wide != 7931923482355807455ULL; +} static unsigned int alternatives(int n) { unsigned int a = 1, b = 3, c = 7; @@ -31,13 +44,3 @@ static unsigned long long overlapping(unsigned int n) } return a + b + s; } -int main(void) -{ - unsigned int narrow = 0; - unsigned long long wide = 0; - for (int n = 0; n < 100; n++) { - narrow = narrow * 33u + alternatives(n); - wide = wide * 33 + overlapping(n); - } - return narrow != 1813477397u || wide != 7931923482355807455ULL; -} diff --git a/tests/vir-machine-live.c b/tests/vir-machine-live.c index 51b485c7..c57f12a8 100644 --- a/tests/vir-machine-live.c +++ b/tests/vir-machine-live.c @@ -1,8 +1,5 @@ /* Constants and branch results remain live through allocated edge copies. */ -static unsigned int tick(unsigned int x) -{ - return x * 1664525u + 1013904223u; -} +static unsigned int tick(unsigned int x); int main(void) { unsigned int x = 1, sum = 0; @@ -24,3 +21,7 @@ int main(void) } return a != 0x345678901LL || b != 0x123456789LL; } +static unsigned int tick(unsigned int x) +{ + return x * 1664525u + 1013904223u; +} diff --git a/tests/vir-machine-transfers.c b/tests/vir-machine-transfers.c index 4905d4d0..b4aafde8 100644 --- a/tests/vir-machine-transfers.c +++ b/tests/vir-machine-transfers.c @@ -13,10 +13,7 @@ static unsigned long long mix(signed char a, return (unsigned long long) a + 3ull * b + 5ull * c + 7ull * d + 11ull * e + 13ull * f + 17ull * g + 19ull * h; } -static unsigned long long swap(unsigned long long a, unsigned long long b) -{ - return a * 3 + b * 7; -} +static unsigned long long swap(unsigned long long a, unsigned long long b); int main(void) { unsigned long long (*call)(signed char, unsigned char, short, @@ -34,3 +31,7 @@ int main(void) } return 0; } +static unsigned long long swap(unsigned long long a, unsigned long long b) +{ + return a * 3 + b * 7; +} diff --git a/tests/vir-multi-input.sh b/tests/vir-multi-input.sh index 7251209f..e75319a0 100644 --- a/tests/vir-multi-input.sh +++ b/tests/vir-multi-input.sh @@ -106,19 +106,24 @@ if "${compiler[@]}" --no-libc -o "$work/typedef-leak" \ exit 1 fi +# Each helper follows its caller, so the calls the checks below resolve stay +# calls instead of being inlined. printf '%s\n' \ 'static int unit_value = 17;' \ - 'static int unit_helper(void) { return unit_value; }' \ + 'static int unit_helper(void);' \ 'int unit_result(void) { return unit_value + unit_helper(); }' \ + 'static int unit_helper(void) { return unit_value; }' \ > "$work/static-root.c" printf '%s\n' \ 'static int unit_value = 29;' \ - 'static int unit_helper(void) { return unit_value; }' \ + 'static int unit_helper(void);' \ 'extern int unit_result(void);' \ 'int main(void) {' \ ' return unit_value == 29 && unit_helper() == 29 &&' \ ' unit_result() == 34 ? 0 : 1;' \ - '}' > "$work/static-main.c" + '}' \ + 'static int unit_helper(void) { return unit_value; }' \ + > "$work/static-main.c" "${compiler[@]}" --no-libc -o "$work/static-scope" \ "$work/static-root.c" "$work/static-main.c" "${runner[@]}" "$work/static-scope" diff --git a/tests/vir-native-regressions.sh b/tests/vir-native-regressions.sh index dd874d6e..a3cb3cdc 100755 --- a/tests/vir-native-regressions.sh +++ b/tests/vir-native-regressions.sh @@ -6,7 +6,7 @@ compiler=("$@") task_dir=$(mktemp -d) trap 'rm -rf "$task_dir"' EXIT read -r -a runner <<< "${TARGET_EXEC:-}" -for fixture in shift-width adjusted-array operand-signedness global-scratch call-return-internal volatile-member void-callback-array bool-compound file-seek pointer-offset typed-arithmetic constant-relations global-value-reader machine-live machine-affinity pair-scratch subtract-alias typedef-alias-builder machine-transfers loop-update if-join call-result-pins entry-parameters comparison-inversion global-slot-void global-cast-metadata global-cast-scope global-wide-string-operands global-dead-operands typedef-callback-rows typedef-nested-row-sizes typedef-pointer-row-elements volatile-pointer-row-elements \ +for fixture in essential bitwise-normalization store-override shift-width adjusted-array operand-signedness global-scratch call-return-internal volatile-member void-callback-array bool-compound file-seek pointer-offset typed-arithmetic constant-relations global-value-reader machine-live machine-affinity pair-scratch subtract-alias typedef-alias-builder machine-transfers loop-update if-join call-result-pins entry-parameters comparison-inversion global-slot-void global-cast-metadata global-cast-scope global-wide-string-operands global-dead-operands typedef-callback-rows typedef-nested-row-sizes typedef-pointer-row-elements volatile-pointer-row-elements \ pointer-scale-width pointer-scale-rows x64-and-loop global-conditional-compound \ arm64-select-runtime arm64-indexed-widths; do source="tests/vir-$fixture.c" @@ -30,5 +30,7 @@ grep -q 'Typedef parenthesized declarator must be a function pointer' "$task_dir bash tests/vir-global-slot-initializers.sh "${compiler[@]}" bash tests/vir-global-conditional-compound.sh "${compiler[@]}" bash tests/vir-strength.sh "${compiler[@]}" +bash tests/vir-bitwise-normalization.sh "${compiler[@]}" +bash tests/vir-essential.sh "${compiler[@]}" bash tests/vir-qualifiers.sh "${compiler[@]}" echo 'Native VIR regression programs passed with and without optimization' diff --git a/tests/vir-pair-scratch.c b/tests/vir-pair-scratch.c index f97d1e7c..31673049 100644 --- a/tests/vir-pair-scratch.c +++ b/tests/vir-pair-scratch.c @@ -1,4 +1,10 @@ /* A live scalar and two wide operands must leave a complete result pair. */ +long long pair_source(int i); +long long pair_sum(int n, long long factor); +int main(void) +{ + return pair_sum(5, 4) != 75; +} long long pair_source(int i) { return i + 1; @@ -12,7 +18,3 @@ long long pair_sum(int n, long long factor) } return sum; } -int main(void) -{ - return pair_sum(5, 4) != 75; -} diff --git a/tests/vir-pointer-offset.c b/tests/vir-pointer-offset.c index 1f5643be..0810b0c3 100644 --- a/tests/vir-pointer-offset.c +++ b/tests/vir-pointer-offset.c @@ -1,13 +1,7 @@ volatile long long wide_index; int object = 73; -long long address_bits(char *base, long long offset) -{ - return (long long) (base + offset); -} -int read_scaled(char *base, long long index) -{ - return *(int *) (base + (index << 2)); -} +long long address_bits(char *base, long long offset); +int read_scaled(char *base, long long index); int main(void) { if (sizeof(void *) != 8) @@ -24,3 +18,11 @@ int main(void) base = (char *) ((unsigned long long) &object + 0x400000004ULL); return read_scaled(base, wide_index) != 73; } +long long address_bits(char *base, long long offset) +{ + return (long long) (base + offset); +} +int read_scaled(char *base, long long index) +{ + return *(int *) (base + (index << 2)); +} diff --git a/tests/vir-pointer-scale-rows.c b/tests/vir-pointer-scale-rows.c index eace3c03..02526e0d 100644 --- a/tests/vir-pointer-scale-rows.c +++ b/tests/vir-pointer-scale-rows.c @@ -1,11 +1,5 @@ -int first(int x) -{ - return x + 1; -} -int second(int x) -{ - return x + 3; -} +int first(int x); +int second(int x); int main(void) { int rows[3][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}}; @@ -24,3 +18,11 @@ int main(void) return 3; return 0; } +int first(int x) +{ + return x + 1; +} +int second(int x) +{ + return x + 3; +} diff --git a/tests/vir-shift-width.c b/tests/vir-shift-width.c index ab924cd7..05f69f9f 100644 --- a/tests/vir-shift-width.c +++ b/tests/vir-shift-width.c @@ -1,14 +1,37 @@ static unsigned long long folded_left = 0x80000000u << 1LL; static unsigned long long folded_inverted = ~(0x80000000u << 1LL); -static unsigned long long narrow_left(unsigned int value, long long count) -{ - return value << count; -} +static unsigned long long narrow_left(unsigned int value, long long count); +static unsigned long long inverted(unsigned int value, long long count); +static unsigned long long widened(unsigned int value, long long count); +static long long signed_right(int value, unsigned long long count); +static int promoted_left(unsigned short value, long long count); +static unsigned long long wide_left(unsigned long long value, long long count); +static long long wide_right(long long value, unsigned long long count); +static unsigned long long compound(unsigned int value, long long count); static unsigned long long narrow_right(unsigned int value, unsigned long long count) { return value >> count; } +int main(void) +{ + return folded_left != 0ULL || folded_inverted != 0xffffffffULL || + narrow_left(0x80000000u, 1LL) != 0ULL || + narrow_left(0xffffffffu, 4LL) != 0xfffffff0ULL || + narrow_right(0x80000000u, 31ULL) != 1ULL || + inverted(0x80000000u, 1LL) != 0xffffffffULL || + widened(0x80000000u, 1LL) != 0x100000000ULL || + signed_right(-2147483647 - 1, 31ULL) != -1LL || + promoted_left(65535, 8LL) != 16776960 || + wide_left(1ULL, 32LL) != 0x100000000ULL || + wide_left(0x8000000000000000ULL, 1LL) != 0ULL || + wide_right(-4294967296LL, 32ULL) != -1LL || + compound(0x80000000u, 1LL) != 0ULL; +} +static unsigned long long narrow_left(unsigned int value, long long count) +{ + return value << count; +} static unsigned long long inverted(unsigned int value, long long count) { return ~(value << count); @@ -37,18 +60,3 @@ static unsigned long long compound(unsigned int value, long long count) { return value <<= count; } -int main(void) -{ - return folded_left != 0ULL || folded_inverted != 0xffffffffULL || - narrow_left(0x80000000u, 1LL) != 0ULL || - narrow_left(0xffffffffu, 4LL) != 0xfffffff0ULL || - narrow_right(0x80000000u, 31ULL) != 1ULL || - inverted(0x80000000u, 1LL) != 0xffffffffULL || - widened(0x80000000u, 1LL) != 0x100000000ULL || - signed_right(-2147483647 - 1, 31ULL) != -1LL || - promoted_left(65535, 8LL) != 16776960 || - wide_left(1ULL, 32LL) != 0x100000000ULL || - wide_left(0x8000000000000000ULL, 1LL) != 0ULL || - wide_right(-4294967296LL, 32ULL) != -1LL || - compound(0x80000000u, 1LL) != 0ULL; -} diff --git a/tests/vir-store-override.c b/tests/vir-store-override.c new file mode 100644 index 00000000..4af1c72c --- /dev/null +++ b/tests/vir-store-override.c @@ -0,0 +1,52 @@ +/* + * An inlined lookup leaves map[node->id] in a register whose reload the x64 + * backend elides through src0_override. The store behind it must record the + * register it really wrote: recording the IR source made the NULL test after + * the join read a register that never held the value, so an operand with id 0 + * looked missing. + */ +typedef struct node { + int id; + int kind; + int type; + int arity; + struct node *first; + struct node *second; +} node_t; +static void *lookup(void **map, const node_t *node) +{ + return node ? map[node->id] : 0; +} +void *build(void *a, void *b); +void *clone(const node_t *node, void **map) +{ + switch (node->kind) { + case 0: + return map[0]; + case 26: + return map[1]; + default: + if (node->arity < 1 || node->arity > 2 || !lookup(map, node->first) || + (node->arity == 2 && !lookup(map, node->second))) + return 0; + return build(map[node->first->id], + node->arity == 2 ? map[node->second->id] : 0); + } +} +int built; +void *build(void *a, void *b) +{ + built++; + return a; +} +int main(void) +{ + int x0, x1; + void *map[4] = {&x0, &x1, 0, 0}; + node_t param = {0, 99, 6, 0, 0, 0}, index = {1, 99, 5, 0, 0, 0}; + node_t sum = {2, 17, 6, 2, ¶m, &index}; + node_t neg = {3, 18, 5, 1, &index, 0}; + if (clone(&sum, map) != &x0 || clone(&neg, map) != &x1 || built != 2) + return 1; + return 0; +} diff --git a/tests/vir-strength.c b/tests/vir-strength.c index 029e74cd..97192585 100644 --- a/tests/vir-strength.c +++ b/tests/vir-strength.c @@ -1,4 +1,37 @@ int a[64], b[64]; +int dot(void); +void bump(void); +int stride(void); +int dynamic_stride(int *p, int n); +int unsigned_walk(int *p); +int unsigned_walk_start(int *p); +int unsigned_walk_index(int *p); +int unsigned_walk_zero(int *p); +int unsigned_walk_volatile(volatile int *p); +int unsigned_reverse_sum(int *p, unsigned n); +int unsigned_reverse_volatile(volatile int *p, unsigned n); +int main(void) +{ + for (int i = 0; i < 64; i++) { + a[i] = i; + b[i] = 64 - i; + } + if (dot() != 43680) + return 1; + bump(); + if (stride() != 120 || dynamic_stride(a, 4) != 28) + return 2; + if (unsigned_walk(a) != 2080 || unsigned_walk_start(a) != 28) + return 3; + if (unsigned_walk_index(a) != 64 || unsigned_walk_zero(a)) + return 4; + if (unsigned_walk_volatile(a) != 36) + return 5; + if (unsigned_reverse_sum(a, 0) || unsigned_reverse_sum(a, 1) != 1 || + unsigned_reverse_sum(a, 64) != 2080) + return 6; + return unsigned_reverse_volatile(a, 8) != 36; +} int dot(void) { int s = 0; @@ -78,25 +111,3 @@ int unsigned_reverse_volatile(volatile int *p, unsigned n) } return s; } -int main(void) -{ - for (int i = 0; i < 64; i++) { - a[i] = i; - b[i] = 64 - i; - } - if (dot() != 43680) - return 1; - bump(); - if (stride() != 120 || dynamic_stride(a, 4) != 28) - return 2; - if (unsigned_walk(a) != 2080 || unsigned_walk_start(a) != 28) - return 3; - if (unsigned_walk_index(a) != 64 || unsigned_walk_zero(a)) - return 4; - if (unsigned_walk_volatile(a) != 36) - return 5; - if (unsigned_reverse_sum(a, 0) || unsigned_reverse_sum(a, 1) != 1 || - unsigned_reverse_sum(a, 64) != 2080) - return 6; - return unsigned_reverse_volatile(a, 8) != 36; -} diff --git a/tests/vir-strength.sh b/tests/vir-strength.sh index cb8de3e0..f6e91652 100755 --- a/tests/vir-strength.sh +++ b/tests/vir-strength.sh @@ -22,7 +22,9 @@ def strides(name): if base in params and offset in constants] assert not re.search(r' = const\.i(?:32|64) 1\n', functions['dot']) assert strides('bump') == [4] -assert strides('stride') == [16] +# Eight iterations is an even count, so reduction unrolling also reads the +# element 16 bytes on and steps by 32. +assert sorted(strides('stride')) == [16, 32] # The LP64 sign-extension discontinuity prohibits a constant pointer step. expected = [] if 'sext.i32.i64' in functions['dynamic_stride'] else [16] assert strides('dynamic_stride') == expected diff --git a/tests/vir-subtract-alias.c b/tests/vir-subtract-alias.c index 37e6804f..9df65113 100644 --- a/tests/vir-subtract-alias.c +++ b/tests/vir-subtract-alias.c @@ -1,19 +1,7 @@ /* Phi affinity can reuse the subtrahend's register for the loop result. */ -int subtract_loop(int seed, int count) -{ - int sum = seed; - for (int index = 0; index < count; index++) - sum = index - sum; - return sum; -} +int subtract_loop(int seed, int count); +long long subtract_wide_loop(long long seed, int count); -long long subtract_wide_loop(long long seed, int count) -{ - long long sum = seed; - for (int index = 0; index < count; index++) - sum = 4294967311LL + index - sum; - return sum; -} int main(void) { @@ -27,3 +15,17 @@ int main(void) return 4; return 0; } +int subtract_loop(int seed, int count) +{ + int sum = seed; + for (int index = 0; index < count; index++) + sum = index - sum; + return sum; +} +long long subtract_wide_loop(long long seed, int count) +{ + long long sum = seed; + for (int index = 0; index < count; index++) + sum = 4294967311LL + index - sum; + return sum; +} diff --git a/tests/vir-typed-arithmetic.c b/tests/vir-typed-arithmetic.c index dcf03028..893f43e4 100644 --- a/tests/vir-typed-arithmetic.c +++ b/tests/vir-typed-arithmetic.c @@ -1,3 +1,98 @@ +unsigned int uproduct(unsigned int x, unsigned int y); +int product(int x, int y); +long long wide(long long x, long long y); +long long subtract_min(long long x); +int subtract_min_int(int x); +int add(int x, int y); +int sub(int x, int y); +int signed_bits(int x); +unsigned int shifted_bits(unsigned int x); +int signed_shift(int x); +unsigned long long wide_bits(unsigned long long x); +unsigned int left_wrap(unsigned int x); +long long left_signed(unsigned int x); +unsigned long long left_unsigned(unsigned int x); +unsigned long long left_wide(unsigned long long x); +unsigned long long left_mixed(unsigned int x); +long long scaled_negative(int x); +long long shifted_sign_bit(unsigned int x); +int memory_flags(unsigned int *p); +#define MEMORY_ARITHMETIC(name, type, op) \ + type name(type *p, type x) \ + { \ + for (int i = 0; i < 4; i++) \ + x op *p++; \ + return x; \ + } +MEMORY_ARITHMETIC(memory_sum, unsigned int, +=) +MEMORY_ARITHMETIC(memory_sub, unsigned int, -=) +MEMORY_ARITHMETIC(memory_and, unsigned int, &=) +MEMORY_ARITHMETIC(memory_or, unsigned int, |=) +MEMORY_ARITHMETIC(memory_xor, unsigned int, ^=) +MEMORY_ARITHMETIC(memory_signed, int, +=) +MEMORY_ARITHMETIC(memory_wide, long long, +=) +int main(void) +{ + if ((long long) product(-12345, 7) != -86415LL) + return 1; + if ((unsigned long long) uproduct(4294967295u, 2u) != 4294967294ULL) + return 2; + if (wide(4294967296LL, 3LL) != 17179869181LL) + return 3; + if ((long long) add(-2147483647, -1) != -2147483648LL) + return 4; + if ((long long) sub(-12345, 321) != -12666LL) + return 5; + int a[3]; + int *p = a + 2; + if (p - a != 2) + return 6; + if (product(12345, 31) != 382695) + return 7; + if (product(-12345, 16) != -197520) + return 8; + if (subtract_min(10LL) != 2147483668LL) + return 9; + if ((long long) product(-12345, 1) != -12345LL) + return 10; + if ((unsigned long long) uproduct(4294967295u, 1u) != 4294967295ULL) + return 11; + if (subtract_min_int(10) != (int) 2147483668u) + return 12; + if ((long long) signed_bits(0x12345678) != -768321928LL) + return 13; + if (shifted_bits(0x80000000u) != 0x25a5u) + return 14; + if ((long long) signed_shift(-123456789) != -15071LL) + return 15; + if (wide_bits(0x123456789abcdef0ULL) != 0x92345679ULL) + return 16; + if (left_wrap(0x80000001u) != 4u) + return 17; + if (left_signed(0x40000000) != -2147483648LL) + return 18; + if (left_unsigned(0x80000001u) != 2ULL) + return 19; + if (left_wide(0x80000001ULL) != 0x200000004ULL) + return 20; + if (left_mixed(0x80000001u) != 0x200000008ULL) + return 21; + if (scaled_negative(-12345) != -197520LL) + return 22; + if (shifted_sign_bit(1) != -2147483648LL) + return 23; + unsigned int values[4] = {1, 2, 4, 8}; + int negatives[4] = {-1, -2, -4, -8}; + long long wide_values[4] = {-0x100000001LL, 2, 4, 8}; + if (memory_sum(values, 0xfffffff0u) != 0xffffffffu || + memory_sub(values, 0) != 0xfffffff1u || memory_and(values, 15) != 0 || + memory_or(values, 0) != 15 || memory_xor(values, 15) != 0 || + memory_flags(values) != 3 || + (long long) memory_signed(negatives, 0) != -15LL || + memory_wide(wide_values, 0) != -0xfffffff3LL) + return 24; + return 0; +} unsigned int uproduct(unsigned int x, unsigned int y) { return x * y; @@ -72,20 +167,6 @@ long long shifted_sign_bit(unsigned int x) { return (long long) (int) (x << 31); } -#define MEMORY_ARITHMETIC(name, type, op) \ - type name(type *p, type x) \ - { \ - for (int i = 0; i < 4; i++) \ - x op *p++; \ - return x; \ - } -MEMORY_ARITHMETIC(memory_sum, unsigned int, +=) -MEMORY_ARITHMETIC(memory_sub, unsigned int, -=) -MEMORY_ARITHMETIC(memory_and, unsigned int, &=) -MEMORY_ARITHMETIC(memory_or, unsigned int, |=) -MEMORY_ARITHMETIC(memory_xor, unsigned int, ^=) -MEMORY_ARITHMETIC(memory_signed, int, +=) -MEMORY_ARITHMETIC(memory_wide, long long, +=) int memory_flags(unsigned int *p) { unsigned int x = 15; @@ -95,65 +176,3 @@ int memory_flags(unsigned int *p) zeros++; return zeros; } -int main(void) -{ - if ((long long) product(-12345, 7) != -86415LL) - return 1; - if ((unsigned long long) uproduct(4294967295u, 2u) != 4294967294ULL) - return 2; - if (wide(4294967296LL, 3LL) != 17179869181LL) - return 3; - if ((long long) add(-2147483647, -1) != -2147483648LL) - return 4; - if ((long long) sub(-12345, 321) != -12666LL) - return 5; - int a[3]; - int *p = a + 2; - if (p - a != 2) - return 6; - if (product(12345, 31) != 382695) - return 7; - if (product(-12345, 16) != -197520) - return 8; - if (subtract_min(10LL) != 2147483668LL) - return 9; - if ((long long) product(-12345, 1) != -12345LL) - return 10; - if ((unsigned long long) uproduct(4294967295u, 1u) != 4294967295ULL) - return 11; - if (subtract_min_int(10) != (int) 2147483668u) - return 12; - if ((long long) signed_bits(0x12345678) != -768321928LL) - return 13; - if (shifted_bits(0x80000000u) != 0x25a5u) - return 14; - if ((long long) signed_shift(-123456789) != -15071LL) - return 15; - if (wide_bits(0x123456789abcdef0ULL) != 0x92345679ULL) - return 16; - if (left_wrap(0x80000001u) != 4u) - return 17; - if (left_signed(0x40000000) != -2147483648LL) - return 18; - if (left_unsigned(0x80000001u) != 2ULL) - return 19; - if (left_wide(0x80000001ULL) != 0x200000004ULL) - return 20; - if (left_mixed(0x80000001u) != 0x200000008ULL) - return 21; - if (scaled_negative(-12345) != -197520LL) - return 22; - if (shifted_sign_bit(1) != -2147483648LL) - return 23; - unsigned int values[4] = {1, 2, 4, 8}; - int negatives[4] = {-1, -2, -4, -8}; - long long wide_values[4] = {-0x100000001LL, 2, 4, 8}; - if (memory_sum(values, 0xfffffff0u) != 0xffffffffu || - memory_sub(values, 0) != 0xfffffff1u || memory_and(values, 15) != 0 || - memory_or(values, 0) != 15 || memory_xor(values, 15) != 0 || - memory_flags(values) != 3 || - (long long) memory_signed(negatives, 0) != -15LL || - memory_wide(wide_values, 0) != -0xfffffff3LL) - return 24; - return 0; -} diff --git a/tests/vir-typedef-pointer-row-elements.c b/tests/vir-typedef-pointer-row-elements.c index 7bc4bfea..789fe8d8 100644 --- a/tests/vir-typedef-pointer-row-elements.c +++ b/tests/vir-typedef-pointer-row-elements.c @@ -12,19 +12,10 @@ double_pointer_row_t double_row = {&indirect, &indirect}; double_row_slots_t double_slots = {&double_row, &double_row, &double_row}; typedef int (*callback_row_t[2])(int); -int add_one(int value) -{ - return value + 1; -} -int add_two(int value) -{ - return value + 2; -} +int add_one(int value); +int add_two(int value); +callback_row_t *callback_rows(void); static int (*callbacks[2])(int) = {add_one, add_two}; -callback_row_t *callback_rows(void) -{ - return &callbacks; -} int main(void) { @@ -69,3 +60,15 @@ int main(void) local_slots[1] = &local_row; return *(*local_slots[1])[1] != 7; } +int add_one(int value) +{ + return value + 1; +} +int add_two(int value) +{ + return value + 2; +} +callback_row_t *callback_rows(void) +{ + return &callbacks; +} diff --git a/tests/vir-void-callback-array.c b/tests/vir-void-callback-array.c index 8d536e69..52513b55 100644 --- a/tests/vir-void-callback-array.c +++ b/tests/vir-void-callback-array.c @@ -1,8 +1,5 @@ int target; -void set_target(int value) -{ - target = value; -} +void set_target(int value); int main(void) { typedef void (*setters_t[1][1][1][1])(int); @@ -10,3 +7,7 @@ int main(void) setters[0][0][0][0](7); return target == 7 ? 0 : 1; } +void set_target(int value) +{ + target = value; +} diff --git a/tests/vir-volatile-arithmetic.c b/tests/vir-volatile-arithmetic.c index f3f5af18..237ab069 100644 --- a/tests/vir-volatile-arithmetic.c +++ b/tests/vir-volatile-arithmetic.c @@ -1,5 +1,10 @@ static int backing[3] = {1, 2, 3}; static volatile int *p = backing; +int touch(void); +int main(void) +{ + return touch(); +} int touch(void) { *(p + 1); @@ -7,7 +12,3 @@ int touch(void) *(p - (-1)); return 0; } -int main(void) -{ - return touch(); -} diff --git a/tests/vir-volatile-callback-pointer-return.c b/tests/vir-volatile-callback-pointer-return.c index f8f62363..3735c92c 100644 --- a/tests/vir-volatile-callback-pointer-return.c +++ b/tests/vir-volatile-callback-pointer-return.c @@ -1,11 +1,21 @@ typedef int *(*volatile callback_t)(int); static int value; +int *get(int n); +int typedef_object(void); +int typedef_slot(void); +int typedef_outer_slot(void); +int direct_slot(void); +static callback_t callback; +int main(void) +{ + return typedef_object() != 7 || typedef_slot() != 8 || + typedef_outer_slot() != 10 || direct_slot() != 9; +} int *get(int n) { value = n; return &value; } -static callback_t callback; int typedef_object(void) { callback = get; @@ -33,8 +43,3 @@ int direct_slot(void) *slot; return *(*slot)(9); } -int main(void) -{ - return typedef_object() != 7 || typedef_slot() != 8 || - typedef_outer_slot() != 10 || direct_slot() != 9; -} diff --git a/tests/vir-volatile-callback-return-pointer.c b/tests/vir-volatile-callback-return-pointer.c index 1079e530..f7a410c5 100644 --- a/tests/vir-volatile-callback-return-pointer.c +++ b/tests/vir-volatile-callback-return-pointer.c @@ -1,15 +1,17 @@ static char text[1] = {'a'}; +char *get(int n); +int touch(void); +static char *(*volatile callback)(int) = get; +int main(void) +{ + return touch(); +} char *get(int n) { return text + n; } -static char *(*volatile callback)(int) = get; int touch(void) { callback; return callback(0) == text ? 0 : 1; } -int main(void) -{ - return touch(); -} diff --git a/tests/vir-volatile-callback.c b/tests/vir-volatile-callback.c index ce95afd1..fc02cbc2 100644 --- a/tests/vir-volatile-callback.c +++ b/tests/vir-volatile-callback.c @@ -1,12 +1,18 @@ typedef int (*F)(int); typedef volatile F VF; +static int add(int v); +int touch(void); +static F volatile a = add; +static VF b = add; +static int (*volatile c)(int) = add; +int main(void) +{ + return touch(); +} static int add(int v) { return v + 1; } -static F volatile a = add; -static VF b = add; -static int (*volatile c)(int) = add; int touch(void) { a; @@ -14,7 +20,3 @@ int touch(void) c; return a(1) + b(2) + c(3) - 9; } -int main(void) -{ - return touch(); -} diff --git a/tests/vir-volatile-local-getter.c b/tests/vir-volatile-local-getter.c index 6bf2b510..0d200df9 100644 --- a/tests/vir-volatile-local-getter.c +++ b/tests/vir-volatile-local-getter.c @@ -1,4 +1,11 @@ typedef int (*callback_t)(int); +int increment(int value); +callback_t get(void); +int touch(void); +int main(void) +{ + return touch(); +} int increment(int value) { return value + 1; @@ -14,7 +21,3 @@ int touch(void) getter; return 0; } -int main(void) -{ - return touch(); -} diff --git a/tests/vir-volatile-member.c b/tests/vir-volatile-member.c index c6bdd258..1600d379 100644 --- a/tests/vir-volatile-member.c +++ b/tests/vir-volatile-member.c @@ -10,12 +10,7 @@ static int values[1] = {11}; static volatile struct owner owner; /* The pointer members are volatile objects. Their pointees are ordinary. */ -int discard_pointees(void) -{ - owner.item->value; - owner.values[0]; - return 0; -} +int discard_pointees(void); int main(void) { owner.item = &item; @@ -24,3 +19,9 @@ int main(void) return 1; return owner.item->value == 7 && owner.values[0] == 11 ? 0 : 2; } +int discard_pointees(void) +{ + owner.item->value; + owner.values[0]; + return 0; +} diff --git a/tests/vir-volatile-pointer-member.c b/tests/vir-volatile-pointer-member.c index e3897a46..6a8030e1 100644 --- a/tests/vir-volatile-pointer-member.c +++ b/tests/vir-volatile-pointer-member.c @@ -5,15 +5,16 @@ struct holder { static int x = 3; static volatile int y = 4; static struct holder h; -int touch(void) -{ - *h.p; - *h.q; - return 0; -} +int touch(void); int main(void) { h.p = &y; h.q = &x; return touch(); } +int touch(void) +{ + *h.p; + *h.q; + return 0; +} diff --git a/tests/vir-volatile-pointer-row-elements.c b/tests/vir-volatile-pointer-row-elements.c index 622aa7b2..3e484f48 100644 --- a/tests/vir-volatile-pointer-row-elements.c +++ b/tests/vir-volatile-pointer-row-elements.c @@ -6,13 +6,14 @@ int b = 11; pointer_row_t row = {&a, &b}; row_slots_t slots = {&row, &row}; -int touch_row(void) -{ - int *value = (*slots[0])[0]; - return *value + *(*slots[1])[1]; -} +int touch_row(void); int main(void) { return touch_row() != 18; } +int touch_row(void) +{ + int *value = (*slots[0])[0]; + return *value + *(*slots[1])[1]; +} diff --git a/tests/vir-volatile-storage.c b/tests/vir-volatile-storage.c index a447b0e7..9055c5cd 100644 --- a/tests/vir-volatile-storage.c +++ b/tests/vir-volatile-storage.c @@ -8,6 +8,11 @@ static volatile int *pointee = &volvalue; static volatile P aliasown = &value; static VP aliaspointee = &volvalue; static PV aliasstar = &value; +int check(void); +int main(void) +{ + return check(); +} int check(void) { own; @@ -17,7 +22,3 @@ int check(void) *aliaspointee; return 0; } -int main(void) -{ - return check(); -} diff --git a/tests/vir-x64-liveout.sh b/tests/vir-x64-liveout.sh index 63db9a79..7821cfbf 100755 --- a/tests/vir-x64-liveout.sh +++ b/tests/vir-x64-liveout.sh @@ -20,14 +20,17 @@ sed '/^void emit_mop(vir_mop_t \*mop)$/,/^{/ { /^{$/a\ "${cc[@]}" -O0 -g -std=c99 -fwrapv \ "-DVIR_LIVEOUT_TEST_MAIN=\"$task_dir/main.c\"" -iquote "$task_dir" -iquote src -Isrc \ tests/vir-x64-liveout.c -o "$task_dir/compiler" +# The probes follow main, so they stay calls for the hook to replace. cat > "$task_dir/source.c" << 'SOURCE' -int probe_cmp(int a, int b) { return -1; } -int probe_and(int a, int b) { return -1; } +int probe_cmp(int a, int b); +int probe_and(int a, int b); int main(int argc, char **argv) { if (argc > 2) return probe_and(5, 3) != 1; return probe_cmp(5, 5) != 1; } +int probe_cmp(int a, int b) { return -1; } +int probe_and(int a, int b) { return -1; } SOURCE for mode in default no-folds; do flags=() diff --git a/tests/vir-x64-shift-counts.c b/tests/vir-x64-shift-counts.c index 2fcf9d3b..4e1339f9 100644 --- a/tests/vir-x64-shift-counts.c +++ b/tests/vir-x64-shift-counts.c @@ -1,7 +1,4 @@ -static int shift_count(int count) -{ - return count; -} +static int shift_count(int count); int main(void) { int wide = shift_count(40); @@ -11,3 +8,7 @@ int main(void) (1 << -1) == (1 << negative) && (-1 << 3) == -8 && (one << 31) == 0x80000000u && (one << 3) == 8); } +static int shift_count(int count) +{ + return count; +}