diff --git a/.ci/check-commentflow.sh b/.ci/check-commentflow.sh new file mode 100755 index 00000000..a2853e6f --- /dev/null +++ b/.ci/check-commentflow.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash + +# Verify, or with --write impose, commentflow reflow of the comment blocks. + +set -uo pipefail + +source "$(dirname "${BASH_SOURCE[0]}")/common.sh" || exit 2 + +write=0 +case "${1:-}" in + --check) shift ;; + --write) + write=1 + shift + ;; +esac + +COMMENTFLOW=${COMMENTFLOW:-commentflow} + +files=() +if [ "$#" -gt 0 ]; then + files=("$@") +else + require_repo + collect_files '*.c' '*.h' '*.sh' + files=(${FILES[@]+"${FILES[@]}"}) +fi + +[ "${#files[@]}" -gt 0 ] || exit 0 +if ! command -v "$COMMENTFLOW" > /dev/null 2>&1; then + echo "Error: $COMMENTFLOW not found" >&2 + echo "Install it from https://github.com/sysprog21/commentflow" >&2 + exit 2 +fi +if [ "$write" -eq 1 ]; then + exec "$COMMENTFLOW" -- "${files[@]}" +fi + +status=0 +"$COMMENTFLOW" --check -- "${files[@]}" || status=$? +if [ "$status" -eq 1 ]; then + echo "Run 'make indent' to reflow comments." >&2 +fi +exit "$status" diff --git a/.ci/check-format.sh b/.ci/check-format.sh index c6da0c97..b92a2df0 100755 --- a/.ci/check-format.sh +++ b/.ci/check-format.sh @@ -1,12 +1,61 @@ #!/usr/bin/env bash -SOURCES=$(find $(git rev-parse --show-toplevel) | egrep "\.(c|cxx|cpp|h|hpp)\$") +# Verify, or with --write impose, clang-format conformance for the C sources. -set -x +set -uo pipefail -for file in ${SOURCES}; -do - clang-format-18 ${file} > expected-format - diff -u -p --label="${file}" --label="expected coding style" ${file} expected-format +source "$(dirname "${BASH_SOURCE[0]}")/common.sh" || exit 2 + +write=0 +case "${1:-}" in + --check) shift ;; + --write) + write=1 + shift + ;; +esac + +CLANG_FORMAT=$(find_clang_format) || { + echo "Error: clang-format version 20 is required" >&2 + exit 2 +} + +files=() +if [ "$#" -gt 0 ]; then + files=("$@") +else + require_repo + collect_files '*.c' '*.h' + files=(${FILES[@]+"${FILES[@]}"}) +fi + +[ "${#files[@]}" -gt 0 ] || exit 0 +if [ "$write" -eq 1 ]; then + exec "$CLANG_FORMAT" -i "${files[@]}" +fi + +# One batched pass answers "is anything unformatted" in a third of the time the +# per-file loop below takes. The loop only has to run when the answer is yes, +# and then only to produce the diff that says what to change. +list=$(mktemp) || exit 2 +trap 'rm -f "$list"' EXIT +printf '%s\n' "${files[@]}" > "$list" +"$CLANG_FORMAT" --dry-run -Werror --files="$list" > /dev/null 2>&1 && exit 0 + +failed=0 +expected=$(mktemp) || exit 2 +trap 'rm -f "$list" "$expected"' EXIT +for file in "${files[@]}"; do + + # An index entry with no file behind it -- a sparse checkout, or a deletion + # staged but not yet committed -- is nothing to format. + [ -f "$file" ] || continue + if ! "$CLANG_FORMAT" "$file" > "$expected"; then + echo "Error: $CLANG_FORMAT failed on $file" >&2 + exit 1 + fi + diff -u -p --label="$file" --label="expected coding style" \ + "$file" "$expected" || failed=1 done -exit $(clang-format-18 --output-replacements-xml ${SOURCES} | egrep -c "") + +exit "$failed" diff --git a/.ci/check-newline.sh b/.ci/check-newline.sh index 1d7d5470..794e2679 100755 --- a/.ci/check-newline.sh +++ b/.ci/check-newline.sh @@ -1,17 +1,88 @@ #!/usr/bin/env bash -ret=0 -show=0 -# Reference: https://medium.com/@alexey.inkin/how-to-force-newline-at-end-of-files-and-why-you-should-do-it-fdf76d1d090e -while IFS= read -rd '' f; do - if file --mime-encoding "$f" | grep -qv binary; then - tail -c1 < "$f" | read -r _ || show=1 - if [ $show -eq 1 ]; then - echo "Warning: No newline at end of file $f" - ret=1 - show=0 - fi +# Ensure every text file uses LF line endings and ends with a newline, which is +# what the [*] section of .editorconfig asks editors to do. + +set -uo pipefail + +source "$(dirname "${BASH_SOURCE[0]}")/common.sh" || exit 2 + +write=0 +case "${1:-}" in + --check) shift ;; + --write) + write=1 + shift + ;; +esac + +files=() +if [ "$#" -gt 0 ]; then + files=("$@") +else + require_repo + collect_files + files=(${FILES[@]+"${FILES[@]}"}) +fi + +[ "${#files[@]}" -gt 0 ] || exit 0 +if ! command -v file > /dev/null 2>&1; then + echo "Error: file not found" >&2 + exit 2 +fi + +# One file(1) run for the whole set: it spends most of its time loading +# libmagic, so paying that once rather than per file is the difference between +# the newline check dominating "make check-style" and disappearing into it. +encodings=() +while IFS= read -r encoding; do + encodings+=("$encoding") +done < <(file -b --mime-encoding -- "${files[@]}") + +# Answers are matched to inputs by position, so a reply that ran short would +# quietly reclassify the rest of the tree as text and report nonsense about it. +if [ "${#encodings[@]}" -ne "${#files[@]}" ]; then + echo "Error: file described ${#encodings[@]} of ${#files[@]} files" >&2 + exit 2 +fi + +text=() +for i in "${!files[@]}"; do + [ "${encodings[i]}" = binary ] && continue + text+=("${files[i]}") +done +[ "${#text[@]}" -gt 0 ] || exit 0 + +failed=0 +for path in "${text[@]}"; do + last=$(tail -c1 < "$path") || exit 2 + [ -n "$last" ] || continue + if [ "$write" -eq 1 ]; then + printf '\n' >> "$path" + continue fi -done < <(git ls-files -z src tools tests) + echo "No newline at end of file: $path" >&2 + failed=1 +done + +# grep exits 0 having found a carriage return, 1 having found none, and 2 on a +# real error, which says nothing about the tree and must not read as clean. Its +# answer goes through a file because the names it prints are NUL separated and a +# command substitution would drop the separators along with them. +matches=$(mktemp) || exit 2 +trap 'rm -f "$matches"' EXIT +grep -lZ $'\r' -- "${text[@]}" > "$matches" +status=$? +if [ "$status" -gt 1 ]; then + echo "Error: grep failed to scan for carriage returns" >&2 + exit 2 +fi +while IFS= read -r -d '' path; do + + # Reported even under --write: a carriage return sits inside the text, and + # stripping one is an edit to content rather than to layout. + echo "CRLF line ending: $path" >&2 + failed=1 +done < "$matches" -exit $ret +exit "$failed" diff --git a/.ci/check-shell.sh b/.ci/check-shell.sh new file mode 100755 index 00000000..6a58017e --- /dev/null +++ b/.ci/check-shell.sh @@ -0,0 +1,66 @@ +#!/usr/bin/env bash + +# Verify, or with --write impose, shfmt formatting for the shell scripts, and +# lint them with ShellCheck. + +set -uo pipefail + +source "$(dirname "${BASH_SOURCE[0]}")/common.sh" || exit 2 + +write=0 +case "${1:-}" in + --check) shift ;; + --write) + write=1 + shift + ;; +esac + +SHFMT=${SHFMT:-shfmt} +SHELLCHECK=${SHELLCHECK:-shellcheck} + +files=() +if [ "$#" -gt 0 ]; then + files=("$@") +else + require_repo + collect_files '*.sh' + files=(${FILES[@]+"${FILES[@]}"}) +fi + +[ "${#files[@]}" -gt 0 ] || exit 0 +if ! command -v "$SHFMT" > /dev/null 2>&1; then + echo "Error: $SHFMT not found" >&2 + exit 2 +fi +if [ "$write" -eq 1 ]; then + exec "$SHFMT" -w -- "${files[@]}" +fi + +failed=0 +"$SHFMT" -d -- "${files[@]}" || failed=1 + +# The test scripts embed shecc expressions that read as shell and trip +# ShellCheck. Naming what is exempt rather than what is covered keeps a new +# directory linted by default instead of silently skipped. +lint_files=() +for file in "${files[@]}"; do + case "$file" in + tests/*) ;; + *) lint_files+=("$file") ;; + esac +done + +[ "${#lint_files[@]}" -gt 0 ] || exit "$failed" +if ! command -v "$SHELLCHECK" > /dev/null 2>&1; then + echo "Error: $SHELLCHECK not found" >&2 + + # A formatting violation shfmt already found is a verdict on the tree, and + # outranks the linter that could not run: reporting 2 here would file it + # under "unavailable" and let the pre-commit hook wave it through. + [ "$failed" -eq 0 ] || exit "$failed" + exit 2 +fi +"$SHELLCHECK" --severity=warning -- "${lint_files[@]}" || failed=1 + +exit "$failed" diff --git a/.ci/common.sh b/.ci/common.sh new file mode 100644 index 00000000..5d4e03a8 --- /dev/null +++ b/.ci/common.sh @@ -0,0 +1,74 @@ +#!/usr/bin/env bash + +# Shared helpers for the style checks. Sourced, not executed. +# +# Every .ci/check-*.sh honors the same exit-code contract, which +# scripts/git-pre-commit.sh depends on to stay advisory: 0 when clean, 1 when +# the tree violates the rule, and 2 when the check could not run at all, which +# in practice means a missing tool. Only 1 blocks a commit. + +# Abort unless the current directory sits inside a git repository. Without this +# the checks below find no files and report success, which reads exactly like a +# clean tree. +require_repo() +{ + if ! git rev-parse --git-dir > /dev/null 2>&1; then + echo "Error: not a git repository" >&2 + exit 2 + fi +} + +# Fill the FILES array with every file matching the given pathspecs that git +# would either track or add, so that a new file is checked by the same rule that +# formats it. +# +# The listing goes through a temporary file rather than a process substitution +# because git's exit status has to be read in this shell: inside "< <(...)" a +# failure exits only the subshell, the loop sees end of input, and an +# enumeration that never happened arrives as an empty list, which every checker +# reads as a clean tree. +# +# An index entry can also outlive its file, during a staged deletion or in a +# sparse checkout, and there is nothing for any checker to read at that path. +collect_files() +{ + local listing status file + listing=$(mktemp) || exit 2 + trap 'rm -f "$listing"' EXIT + git ls-files -z --cached --others --exclude-standard -- "$@" > "$listing" + status=$? + FILES=() + if [ "$status" -ne 0 ]; then + echo "Error: could not enumerate the worktree" >&2 + exit 2 + fi + while IFS= read -r -d '' file; do + + # -f rather than -e: a submodule gitlink and a symlink to a directory + # are both entries in the index that no file-oriented checker can read. + [ -f "$file" ] || continue + FILES+=("$file") + done < "$listing" + rm -f "$listing" + trap - EXIT +} + +# Print the name of a clang-format at the version the project pins, or nothing. +# CLANG_FORMAT names a specific binary to use instead of searching. +find_clang_format() +{ + local candidate candidates + if [ -n "${CLANG_FORMAT:-}" ]; then + candidates=("$CLANG_FORMAT") + else + candidates=(clang-format-20 clang-format) + fi + for candidate in "${candidates[@]}"; do + if command -v "$candidate" > /dev/null 2>&1 \ + && "$candidate" --version 2> /dev/null | grep -qE 'version 20\.'; then + echo "$candidate" + return 0 + fi + done + return 1 +} diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..d6f439d0 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,24 @@ +# Top-level EditorConfig file +root = true + +# Enforced repository-wide by .ci/check-newline.sh +[*] +end_of_line = lf +insert_final_newline = true + +# Matching .clang-format's "UseTab: Never" and "IndentWidth: 4" +[*.{c,h}] +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true + +# Shell adds the shfmt-specific keys, which no editor reads but which keep +# "shfmt -d" and "shfmt -w" agreeing without duplicating flags in the Makefile. +[*.sh] +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true +function_next_line = true +switch_case_indent = true +space_redirects = true +binary_next_line = true diff --git a/.github/actions/setup-build-env/action.yml b/.github/actions/setup-build-env/action.yml index aa4d6001..a52bcf3f 100644 --- a/.github/actions/setup-build-env/action.yml +++ b/.github/actions/setup-build-env/action.yml @@ -16,6 +16,14 @@ inputs: GitHub. Only the anonymous rate limit applies without one. required: false default: '' + style-tools: + description: > + Install the formatters and linters that "make check-style" runs: + clang-format at the major version the project requires, and shfmt, + ShellCheck and commentflow at their latest release, each checked against + the digest that release publishes. + required: false + default: 'false' runs: using: composite @@ -25,12 +33,16 @@ runs: env: ARCHITECTURE: ${{ inputs.architecture }} LINK_MODE: ${{ inputs.link-mode }} + STYLE_TOOLS: ${{ inputs.style-tools }} DEBIAN_FRONTEND: noninteractive run: | set -euo pipefail # jq is used by the release-asset helper below. Graphviz is not needed # by the build or test suite. packages=(build-essential jq) + if [ "$STYLE_TOOLS" = true ]; then + packages+=(ca-certificates curl file gnupg) + fi if [ "$ARCHITECTURE" = arm ] && [ "$(uname -m)" = aarch64 ]; then # An Arm64 host runs the Arm output natively, through the armhf # loader and libc rather than an emulator. @@ -50,6 +62,87 @@ runs: sudo apt-get update -q -y sudo apt-get install -q -y --no-install-recommends "${packages[@]}" + - name: Install clang-format 20 + if: inputs.style-tools == 'true' + shell: bash + run: | + set -euo pipefail + key="$RUNNER_TEMP/llvm-snapshot.gpg.key" + curl --fail --silent --show-error --location \ + -o "$key" https://apt.llvm.org/llvm-snapshot.gpg.key + expected=6084F3CF814B57C1CF12EFD515CF4D18AF4F7421 + # Import into a throwaway keyring and export only the pinned key. + # Checking the first fingerprint and then installing the whole bundle + # would let a second key riding along in the same file sign packages. + ring="$RUNNER_TEMP/llvm-keyring" + rm -rf "$ring" + mkdir -m 0700 "$ring" + gpg --homedir "$ring" --batch --import "$key" + sudo install -d -m 0755 /etc/apt/keyrings + gpg --homedir "$ring" --export "$expected" | + sudo tee /etc/apt/keyrings/llvm.gpg > /dev/null + test -s /etc/apt/keyrings/llvm.gpg + echo "deb [signed-by=/etc/apt/keyrings/llvm.gpg] https://apt.llvm.org/noble/ llvm-toolchain-noble-20 main" | + sudo tee /etc/apt/sources.list.d/llvm-20.list > /dev/null + sudo apt-get update -q -y + sudo apt-get install -q -y --no-install-recommends clang-format-20 + clang-format-20 --version + + # shfmt and ShellCheck come from their own releases rather than from apt: + # a formatter that the distribution moves out from under the tree rewraps + # scripts a contributor's shfmt just formatted, and turns "make check-style" + # into a disagreement between two machines rather than a statement about the + # code. + - name: Install shfmt + if: inputs.style-tools == 'true' + shell: bash + env: + GITHUB_TOKEN: ${{ inputs.github-token }} + run: | + set -euo pipefail + asset=$("$GITHUB_WORKSPACE"/.github/scripts/release-asset.sh mvdan/sh \ + 'shfmt_{tag}_linux_amd64') + read -r _ url sha256 <<< "$asset" + binary="$RUNNER_TEMP/shfmt" + curl --fail --silent --show-error --location -o "$binary" "$url" + echo "$sha256 $binary" | sha256sum -c - + sudo install -m 0755 "$binary" /usr/local/bin/shfmt + shfmt --version + + - name: Install ShellCheck + if: inputs.style-tools == 'true' + shell: bash + env: + GITHUB_TOKEN: ${{ inputs.github-token }} + run: | + set -euo pipefail + asset=$("$GITHUB_WORKSPACE"/.github/scripts/release-asset.sh \ + koalaman/shellcheck 'shellcheck-{tag}.linux.x86_64.tar.xz') + read -r _ url sha256 <<< "$asset" + archive="$RUNNER_TEMP/shellcheck.tar.xz" + curl --fail --silent --show-error --location -o "$archive" "$url" + echo "$sha256 $archive" | sha256sum -c - + # The archive nests the binary under a directory named for the release. + sudo tar -Jxf "$archive" -C /usr/local/bin --strip-components=1 \ + --wildcards '*/shellcheck' + shellcheck --version + + - name: Install commentflow + if: inputs.style-tools == 'true' + shell: bash + env: + GITHUB_TOKEN: ${{ inputs.github-token }} + run: | + set -euo pipefail + asset=$("$GITHUB_WORKSPACE"/.github/scripts/release-asset.sh \ + sysprog21/commentflow \ + commentflow-x86_64-unknown-linux-gnu.tar.gz) + read -r _ url sha256 <<< "$asset" + archive="$RUNNER_TEMP/commentflow.tar.gz" + curl --fail --silent --show-error --location -o "$archive" "$url" + echo "$sha256 $archive" | sha256sum -c - + sudo tar -xzf "$archive" -C /usr/local/bin commentflow + # mk/arm.mk asks fastfetch what machine this is, and skips the emulator # when the answer is one that runs the Arm output natively. - name: Install fastfetch diff --git a/.github/scripts/release-asset.sh b/.github/scripts/release-asset.sh index eb1160db..4ae8ac01 100755 --- a/.github/scripts/release-asset.sh +++ b/.github/scripts/release-asset.sh @@ -1,14 +1,16 @@ #!/usr/bin/env bash -# Print " " for one asset of the latest release of -# a GitHub repository. Both toolchain downloads in the workflows resolve what -# they fetch this way, so that neither pins a version that goes stale nor -# trusts an archive it has not checksummed. +# Print " " for one asset of the latest release of a +# GitHub repository. Every download in the workflows resolves what it fetches +# this way, so that none pins a version that goes stale nor trusts an archive it +# has not checksummed. An asset name may carry "{tag}" where the release stamps +# its own tag into the file name, which is only knowable once the release is in +# hand. set -euo pipefail if [ "$#" -ne 2 ]; then - echo "Usage: $0 " >&2 + echo "Usage: $0 " >&2 exit 1 fi @@ -30,10 +32,11 @@ release=$(curl --fail --silent --show-error --location \ # printing nulls and leaving the caller to download from the string "null". jq -er --arg name "$ASSET" ' . as $release - | (.assets[] | select(.name == $name)) as $asset + | ($name | gsub("\\{tag\\}"; $release.tag_name)) as $wanted + | (.assets[] | select(.name == $wanted)) as $asset | ($asset.digest // "" | sub("^sha256:"; "")) as $sha256 | if $sha256 == "" then - error("\($name) has no sha256 digest in \($release.tag_name)") + error("\($wanted) has no sha256 digest in \($release.tag_name)") else "\($release.tag_name) \($asset.browser_download_url) \($sha256)" - end' <<<"$release" + end' <<< "$release" diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 52e9ed4b..75a29cfb 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -200,12 +200,14 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v7 + - name: Set up style tools + uses: ./.github/actions/setup-build-env + with: + architecture: x64 + link-mode: static + github-token: ${{ github.token }} + style-tools: 'true' - name: Coding convention - shell: bash - env: - DEBIAN_FRONTEND: noninteractive - run: | - sudo apt-get update -q -y - sudo apt-get install -q -y --no-install-recommends clang-format-18 - .ci/check-newline.sh - .ci/check-format.sh + run: make check-style + - name: Git hooks + run: make check-hooks diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0ff73ac0..e88107b9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -51,10 +51,22 @@ However, participation requires adherence to fundamental ground rules: This variant should be considered the standard for all documentation efforts. For instance, opt for "initialize" over "initialise" and "color" rather than "colour". -Software requirement: [clang-format](https://clang.llvm.org/docs/ClangFormat.html) version 18 or later. - -This repository consistently contains an up-to-date `.clang-format` file with rules that match the explained ones. -For maintaining a uniform coding style, execute the command `clang-format -i *.{c,h}`. +Building and testing need none of the style tools. `make check-style` needs all +four, and `make indent` all but ShellCheck, which only ever reports: +[clang-format](https://clang.llvm.org/docs/ClangFormat.html) version 20, +[commentflow](https://github.com/sysprog21/commentflow), +[shfmt](https://github.com/mvdan/sh), and +[ShellCheck](https://www.shellcheck.net/). +The newline check also calls `file(1)` to tell text from binary, which is +already present on most systems and reports itself as missing when it is not. + +The rules live in `.clang-format` for C and in `.editorconfig` for shell, both +kept up to date with the conventions explained here. +Run `make indent` to apply them, and `make check-style` to verify them without +modifying the tree. Note that `indent` also rewraps the text of your comments +through commentflow, so review its output rather than committing it blind. +Run `make install-hooks` to apply the same checks to staged changes; an +existing local hook is preserved. ## Coding Style for Modern C diff --git a/Makefile b/Makefile index 55605256..ed56d7bd 100644 --- a/Makefile +++ b/Makefile @@ -1,20 +1,15 @@ +# -fwrapv is required, not probed: hashmap_hash_index() carries the FNV-1a +# accumulator in a signed int, because shecc has no 'unsigned' to compile +# itself with, and the multiply there overflows by design. CFLAGS := -O -g \ - -std=c99 -pedantic + -std=c99 -pedantic -fwrapv +# Every -Wno- that used to sit here has been earned away rather than renewed: +# the tree is clean under gcc and clang with nothing switched off, so a warning +# that appears from now on is about the code and not about the flag list. CFLAGS_TO_CHECK := \ - -fwrapv \ -Wall -Wextra \ - -Wno-unused-but-set-variable \ - -Wno-unused-parameter \ - -Wno-unused-function \ - -Wshadow \ - -Wno-variadic-macros \ - -Wno-uninitialized \ - -Wno-strict-prototypes \ - -Wno-declaration-after-statement \ - -Wno-format \ - -Wno-format-pedantic \ - -Wno-overflow + -Wshadow SUPPORTED_CFLAGS := # Check if a specific compiler flag is supported, attempting a dummy compilation @@ -25,8 +20,13 @@ check_flag = $(shell $(CC) $(1) -S -o /dev/null -xc /dev/null 2>/dev/null; \ if test $$? -eq 0; then echo "$(1)"; fi) # Iterate through the list of all potential flags, effectively filtering out all -# unsupported flags. +# unsupported flags. Half a second of $(CC) probing that the style and hook +# targets have no use for, so skip it when nothing is being compiled. +STYLE_GOALS := check-style check-newline check-comments check-format check-shell \ + indent install-hooks uninstall-hooks check-hooks +ifneq ($(filter-out $(STYLE_GOALS),$(or $(MAKECMDGOALS),all)),) $(foreach flag, $(CFLAGS_TO_CHECK), $(eval CFLAGS += $(call check_flag, $(flag)))) +endif BUILD_SESSION := .session.mk @@ -53,6 +53,9 @@ BUILTIN_LIBC_HEADER := c.h STAGE0_FLAGS ?= --dump-ir STAGE1_FLAGS ?= DYNLINK ?= 0 + +COMMENTFLOW ?= commentflow +SHFMT ?= shfmt ifeq ($(DYNLINK),1) STAGE0_FLAGS += --dynlink STAGE1_FLAGS += --dynlink @@ -60,12 +63,27 @@ endif SRCS := $(wildcard $(patsubst %,%/main.c, $(SRCDIR))) OBJS := $(SRCS:%.c=$(OUT)/%.o) -deps := $(OBJS:%.o=%.o.d) + +# The sanitizer build keeps its objects apart from the normal one. Sharing them +# lets whichever ran last decide what the other links: a plain "make" after it +# fails outright on the missing runtime, and -- quietly, which is worse -- +# "make sanitizer" after a plain build relinks an uninstrumented object, so +# check-sanitizer then passes having checked nothing. +SAN_OUT := $(OUT)/sanitize +SAN_OBJS := $(SRCS:%.c=$(SAN_OUT)/%.o) +deps := $(OBJS:%.o=%.o.d) $(SAN_OBJS:%.o=%.o.d) all: config bootstrap -sanitizer: CFLAGS += -fsanitize=address -fsanitize=undefined -fno-omit-frame-pointer -O0 -sanitizer: LDFLAGS += -fsanitize=address -fsanitize=undefined +# Both goals carry the flags, because a target-specific variable reaches only +# that target and its prerequisites: "make check-sanitizer" on its own would +# otherwise build $(SAN_OBJS) with the ordinary CFLAGS and run the suite against +# a binary that instruments nothing. +SAN_CFLAGS := -fsanitize=address -fsanitize=undefined -fno-omit-frame-pointer -O0 +SAN_LDFLAGS := -fsanitize=address -fsanitize=undefined + +sanitizer check-sanitizer: CFLAGS += $(SAN_CFLAGS) +sanitizer check-sanitizer: LDFLAGS += $(SAN_LDFLAGS) sanitizer: config $(OUT)/$(STAGE0)-sanitizer $(VECHO) " Built stage 0 compiler with sanitizers\n" @@ -119,8 +137,50 @@ config: $(VECHO) "Target machine code switch to %s\n" $(ARCH) $(Q)$(CONFIG_CHECK_CMD) +.PHONY: $(STYLE_GOALS) + check: check-stage0 check-stage2 check-abi-stage0 check-abi-stage2 +# One checker per target: they share nothing, so "make -j check-style" runs them +# concurrently and finishes in the time the slowest one takes. +check-style: check-newline check-comments check-format check-shell + +check-newline: + $(Q).ci/check-newline.sh + +check-comments: + $(Q)COMMENTFLOW=$(COMMENTFLOW) .ci/check-commentflow.sh + +check-format: + $(Q).ci/check-format.sh + +check-shell: + $(Q)SHFMT=$(SHFMT) .ci/check-shell.sh + +check-hooks: + $(Q)scripts/test-git-hooks.sh + +# Naming both goals would otherwise run each --write pass beside the checker +# reading the same files, so the rewrite goes first and the checkers then report +# on a tree that has stopped moving. Neither goal alone is affected. +ifneq ($(filter indent,$(MAKECMDGOALS)),) +check-newline check-comments check-format check-shell: | indent +endif + +# The checkers own both halves: which files they cover and which tool rewrites +# them. Naming either one here again would only be a second place to update. +indent: + $(Q).ci/check-newline.sh --write + $(Q)SHFMT=$(SHFMT) .ci/check-shell.sh --write + $(Q)COMMENTFLOW=$(COMMENTFLOW) .ci/check-commentflow.sh --write + $(Q).ci/check-format.sh --write + +install-hooks: + $(Q)scripts/install-git-hooks.sh + +uninstall-hooks: + $(Q)scripts/install-git-hooks.sh --uninstall + check-stage0: $(OUT)/$(STAGE0) tests/driver.sh $(VECHO) " TEST STAGE 0\n" tests/driver.sh 0 $(DYNLINK) @@ -151,7 +211,8 @@ $(OUT)/%.o: %.c | config $(OUT)/libc.inc $(VECHO) " CC\t$@\n" $(Q)$(CC) -o $@ $(CFLAGS) -c -MMD -MF $@.d $< -SHELL_HACK := $(shell mkdir -p $(OUT) $(OUT)/$(SRCDIR) $(OUT)/tests) +SHELL_HACK := $(shell mkdir -p $(OUT) $(OUT)/$(SRCDIR) $(OUT)/tests \ + $(SAN_OUT)/$(SRCDIR)) $(OUT)/norm-lf: tools/norm-lf.c $(VECHO) " CC+LD\t$@\n" @@ -172,9 +233,13 @@ $(OUT)/$(STAGE0): $(OUT)/libc.inc $(OBJS) $(VECHO) " LD\t$@\n" $(Q)$(CC) $(OBJS) $(LDFLAGS) -o $@ -$(OUT)/$(STAGE0)-sanitizer: $(OUT)/libc.inc $(OBJS) +$(SAN_OUT)/%.o: %.c | config $(OUT)/libc.inc + $(VECHO) " CC\t$@\n" + $(Q)$(CC) -o $@ $(CFLAGS) -c -MMD -MF $@.d $< + +$(OUT)/$(STAGE0)-sanitizer: $(OUT)/libc.inc $(SAN_OBJS) $(VECHO) " LD\t$@ (with sanitizers)\n" - $(Q)$(CC) $(OBJS) $(LDFLAGS) -o $@ + $(Q)$(CC) $(SAN_OBJS) $(LDFLAGS) -o $@ $(OUT)/$(STAGE1): $(OUT)/$(STAGE0) $(Q)$(STAGE1_CHECK_CMD) @@ -201,7 +266,8 @@ bootstrap: $(OUT)/$(STAGE2) .PHONY: clean clean: -$(RM) $(OUT)/$(STAGE0) $(OUT)/$(STAGE1) $(OUT)/$(STAGE2) - -$(RM) $(OBJS) $(deps) + -$(RM) $(OUT)/$(STAGE0)-sanitizer + -$(RM) $(OBJS) $(SAN_OBJS) $(deps) -$(RM) $(TESTBINS) $(OUT)/tests/*.log $(OUT)/tests/*.lst -$(RM) $(OUT)/shecc*.log -$(RM) $(OUT)/libc.inc diff --git a/lib/c.c b/lib/c.c index b1a0f950..37081966 100644 --- a/lib/c.c +++ b/lib/c.c @@ -1,8 +1,8 @@ /* * shecc - Self-Hosting and Educational C Compiler. * - * shecc is freely redistributable under the BSD 2 clause license. See the - * file "LICENSE" for information on usage and redistribution of this file. + * shecc is freely redistributable under the BSD 2 clause license. See the file + * "LICENSE" for information on usage and redistribution of this file. */ /* minimal libc implementation */ @@ -12,8 +12,8 @@ /* Staging buffer for the printf family that writes straight to a descriptor. * * The longest single call in the tree is ssa.c's "insn_%p [label=%s]": a - * DUMP_INSN_LEN staging buffer plus 26 bytes around it, so 537. Every byte - * here is stack in every program shecc emits, so it stays close to that. + * DUMP_INSN_LEN staging buffer plus 26 bytes around it, so 537. Every byte here + * is stack in every program shecc emits, so it stays close to that. */ #define FMT_BUF_LEN 576 @@ -123,15 +123,16 @@ char *strncat(char *dest, char *src, int len) char *strchr(char *str, int ch) { int i = 0; + /* Compare both sides as bytes. * * A byte above 0x7F is the whole difficulty: comparing str[i] against the * int the caller passed fails wherever char is signed, since one side is * negative and the other is not. Converting the search value to a char is - * not enough either -- the arm backend widens a char loaded from memory - * and a char held in a variable differently, so the two disagree even - * though each promotes to -61 on its own. Masking both to 0..255 leaves - * nothing to disagree about, on any target. + * not enough either -- the arm backend widens a char loaded from memory and + * a char held in a variable differently, so the two disagree even though + * each promotes to -61 on its own. Masking both to 0..255 leaves nothing to + * disagree about, on any target. * * The terminator counts as part of the string, and a masked zero still * finds it. @@ -218,8 +219,8 @@ void *memset(void *s, int c, int n) /* set 10 digits (32bit) without div * - * This function converts a given integer value to its string representation - * in base-10 without using division operations. The method involves calculating + * This function converts a given integer value to its string representation in + * base-10 without using division operations. The method involves calculating * the approximate quotient and remainder using bitwise operations, which are * then used to derive each digit of the result. * @@ -227,11 +228,10 @@ void *memset(void *s, int c, int n) * detailed in the reference link: * http://web.archive.org/web/20180517023231/http://www.hackersdelight.org/divcMore.pdf. * This approach avoids expensive division instructions by using a series of - * bitwise shifts and additions to calculate the quotient and remainder. - */ -/* Pointer width of the target, held in a variable rather than tested with - * the preprocessor: shecc must be able to compile this file for either - * target, and a constant condition would leave statically dead code behind. + * bitwise shifts and additions to calculate the quotient and remainder. Pointer + * width of the target, held in a variable rather than tested with the + * preprocessor: shecc must be able to compile this file for either target, and + * a constant condition would leave statically dead code behind. */ int __ptr_width = __SIZEOF_POINTER__; @@ -243,9 +243,9 @@ void __str_base10(char *pb, int val) /* On a 32-bit target, negating INT_MIN overflows and the digit loop below * cannot make progress, so the value is spelled out directly. On LP64 the - * negation happens in a 64-bit register and the normal path is exact. - * This is an ordinary constant expression rather than a preprocessor - * conditional so that shecc can compile this file for either target. + * negation happens in a 64-bit register and the normal path is exact. This + * is an ordinary constant expression rather than a preprocessor conditional + * so that shecc can compile this file for either target. */ if (__ptr_width == 4 && val == -2147483648) { strncpy(pb + INT_BUF_LEN - 11, "-2147483648", 11); @@ -322,10 +322,10 @@ void __str_base16(char *pb, int val) * - On success, the return value should be the length of the entire converted * string even if n is insufficient to store it. * - * Thus, a structure fmtbuf_t is defined for formatted output conversion for - * the functions in the printf() family. + * Thus, a structure fmtbuf_t is defined for formatted output conversion for the + * functions in the printf() family. * @buf: the current position of the buffer. - * @n : the remaining space of the buffer. + * @n : the remaining space of the buffer. * @len: the number of characters that would have been written (excluding the * null terminator) had n been sufficiently large. * @@ -344,8 +344,8 @@ void __fmtbuf_write_char(fmtbuf_t *fmtbuf, int val) { fmtbuf->len += 1; - /* Write the given character when n is greater than 1. - * This means preserving one position for the null character. + /* Write the given character when n is greater than 1. This means preserving + * one position for the null character. */ if (fmtbuf->n <= 1) return; @@ -360,8 +360,8 @@ void __fmtbuf_write_str(fmtbuf_t *fmtbuf, char *str, int l) { fmtbuf->len += l; - /* Write the given string when n is greater than 1. - * This means preserving one position for the null character. + /* Write the given string when n is greater than 1. This means preserving + * one position for the null character. */ if (fmtbuf->n <= 1) return; @@ -457,8 +457,9 @@ void __format(fmtbuf_t *fmtbuf, void __format_to_buf(fmtbuf_t *fmtbuf, char *format, int *var_args) { int si = 0, pi = 0; - /* A pointer-width view of the same argument area, for %s. Reading a - * pointer argument through an int would truncate it on LP64. + + /* A pointer-width view of the same argument area, for %s. Reading a pointer + * argument through an int would truncate it on LP64. */ char **var_args_p = (char **) var_args; @@ -512,11 +513,11 @@ void __format_to_buf(fmtbuf_t *fmtbuf, char *format, int *var_args) case 'p': { /* Append param as a pointer. * - * A pointer occupies VA_INT_STEP int-sized slots, so on an - * LP64 target the second one carries the high word. Printing - * only @v would drop it, and the graph writer in ssa.c names - * its nodes after these values, so two objects sharing a low - * word would collapse into one node. + * A pointer occupies VA_INT_STEP int-sized slots, so on an LP64 + * target the second one carries the high word. Printing only @v + * would drop it, and the graph writer in ssa.c names its nodes + * after these values, so two objects sharing a low word would + * collapse into one node. * * A pointer has one spelling here, "0x" and its significant * digits, so any width or zero-pad in the format is ignored. @@ -532,6 +533,7 @@ void __format_to_buf(fmtbuf_t *fmtbuf, char *format, int *var_args) __fmtbuf_write_char(fmtbuf, 'x'); if (hi) { __format(fmtbuf, hi, 0, 0, 16, 0); + /* The low word keeps its leading zeros, or the two halves * would run together into a different number. */ @@ -649,9 +651,9 @@ FILE *fopen(char *filename, char *mode) if (!strcmp(mode, "w") || !strcmp(mode, "wb")) { /* Flags below are O_WRONLY | O_CREAT | O_TRUNC. Without O_TRUNC, - * writing a shorter file over a longer one leaves the old tail in - * place -- which turns a rebuilt executable into the new image - * followed by a fragment of the previous one. + * writing a shorter file over a longer one leaves the old tail in place + * -- which turns a rebuilt executable into the new image followed by a + * fragment of the previous one. * * "wb" writes an executable and opens 0775; "w" writes text, which has * no business being executable, and opens 0666 before the umask. diff --git a/lib/c.h b/lib/c.h index 0ed9d6a1..d9be4b15 100644 --- a/lib/c.h +++ b/lib/c.h @@ -1,8 +1,8 @@ /* * shecc - Self-Hosting and Educational C Compiler. * - * shecc is freely redistributable under the BSD 2 clause license. See the - * file "LICENSE" for information on usage and redistribution of this file. + * shecc is freely redistributable under the BSD 2 clause license. See the file + * "LICENSE" for information on usage and redistribution of this file. */ #pragma once @@ -55,6 +55,7 @@ #define __syscall_lseek 8 #define __syscall_mmap 9 #define __syscall_munmap 11 + /* x86-64 provides no mmap2. Every call site passes offset 0, so mmap2's * page-granular offset is indistinguishable from mmap's byte offset here. */ @@ -75,8 +76,8 @@ typedef int *va_list; /* Every variadic argument occupies one pointer-sized stack slot, so an - * int-based va_list must advance this many elements per argument: one on - * the 32-bit targets, two on LP64. + * int-based va_list must advance this many elements per argument: one on the + * 32-bit targets, two on LP64. */ #define VA_INT_STEP (__SIZEOF_POINTER__ / 4) @@ -100,9 +101,10 @@ int fclose(FILE *stream); int fgetc(FILE *stream); char *fgets(char *str, int n, FILE *stream); int fputc(int c, FILE *stream); + /* Only under dynamic linking, where the host libc supplies them and buffers - * behind them. A statically linked program has neither, and moves whole - * blocks through '__syscall' instead. + * behind them. A statically linked program has neither, and moves whole blocks + * through '__syscall' instead. */ int fread(char *ptr, int size, int nmemb, FILE *stream); int fwrite(char *ptr, int size, int nmemb, FILE *stream); diff --git a/scripts/git-pre-commit.sh b/scripts/git-pre-commit.sh new file mode 100755 index 00000000..973567fa --- /dev/null +++ b/scripts/git-pre-commit.sh @@ -0,0 +1,81 @@ +#!/usr/bin/env bash + +# Reject a commit whose staged content violates the project's style rules. +# +# Checks run against a snapshot of the index rather than the working tree, so a +# partially staged file is judged on what is actually being committed. A checker +# that cannot run at all, exit 2 and in practice a missing tool, prints a note +# and lets the commit through: the hook is a convenience, CI is the authority. + +set -uo pipefail + +# git runs hooks from the top of the working tree. +ci_dir=$(git rev-parse --show-toplevel)/.ci || exit 1 + +staged=() +while IFS= read -r -d '' file; do + staged+=("$file") +done < <(git diff --cached --name-only -z --diff-filter=ACMRT) + +failed=0 +run_check() +{ # [file...] + local what=$1 status=0 + shift + (cd "$snapshot" && "$@") || status=$? + case "$status" in + 0) ;; + 1) failed=1 ;; + 2) echo "note: $what unavailable; CI will check it" >&2 ;; + + # Anything else is the checker dying rather than declining, and a signal + # is not a verdict on the tree: block instead of waving it on. + *) + echo "error: $what exited with status $status" >&2 + failed=1 + ;; + esac +} + +if [ "${#staged[@]}" -gt 0 ]; then + snapshot=$(mktemp -d) || exit 1 + trap 'rm -rf "$snapshot"' EXIT + + # Restyling rules apply to the whole tree, so a staged config change has to + # drag every file it governs into the snapshot with it. + style_files=("${staged[@]}") + if [ -n "$(git diff --cached --name-only -- .clang-format .editorconfig)" ]; then + style_files=() + while IFS= read -r -d '' file; do + style_files+=("$file") + done < <(git ls-files -z -- '*.c' '*.h' '*.sh') + fi + + # git ls-files already emits the NUL stream checkout-index wants, and asking + # git which configs exist beats naming one the index may not hold. + { + printf '%s\0' "${staged[@]}" "${style_files[@]}" + git ls-files -z -- .clang-format .editorconfig + } | git checkout-index --stdin -z -f --prefix="$snapshot/" || exit 1 + + c_files=() + sh_files=() + for file in "${style_files[@]}"; do + case "$file" in + *.c | *.h) c_files+=("$file") ;; + *.sh) sh_files+=("$file") ;; + esac + done + + run_check "newline check" "$ci_dir/check-newline.sh" "${staged[@]}" + [ "${#c_files[@]}" -eq 0 ] \ + || run_check "clang-format 20" "$ci_dir/check-format.sh" "${c_files[@]}" + [ $((${#c_files[@]} + ${#sh_files[@]})) -eq 0 ] \ + || run_check commentflow "$ci_dir/check-commentflow.sh" \ + ${c_files[@]+"${c_files[@]}"} ${sh_files[@]+"${sh_files[@]}"} + [ "${#sh_files[@]}" -eq 0 ] \ + || run_check "shell tools" "$ci_dir/check-shell.sh" "${sh_files[@]}" +fi + +git diff --cached --check || failed=1 +exit "$failed" diff --git a/scripts/install-git-hooks.sh b/scripts/install-git-hooks.sh new file mode 100755 index 00000000..de28dcd1 --- /dev/null +++ b/scripts/install-git-hooks.sh @@ -0,0 +1,68 @@ +#!/usr/bin/env bash + +# Link this repository's pre-commit hook without replacing a hook already there. + +set -uo pipefail + +uninstall=0 +if [ "${1:-}" = --uninstall ] && [ "$#" -eq 1 ]; then + uninstall=1 +elif [ "$#" -gt 0 ]; then + echo "usage: $0 [--uninstall]" >&2 + exit 2 +fi + +# .git/hooks is shared by every linked worktree, so the link has to name the +# main one; pointing it at a linked worktree leaves it dangling once that is +# removed. +root=$(git worktree list --porcelain | sed -n '1s/^worktree //p') +hooks=$(git rev-parse --path-format=absolute --git-path hooks) || exit 1 +mkdir -p "$hooks" || exit 1 + +source=$root/scripts/git-pre-commit.sh +target=$hooks/pre-commit + +# .git/hooks being shared has a second consequence: the main worktree may be on +# a branch that does not carry this script, and git runs a dangling hook by +# quietly running nothing. Refuse rather than install a link to a file that is +# not there, so that the hook is never silently absent. +if [ "$uninstall" -eq 0 ] && [ ! -x "$source" ]; then + if [ -e "$source" ]; then + echo "Error: $source is not executable" >&2 + echo "git ignores a hook it cannot run, and says nothing about it." >&2 + else + echo "Error: $source does not exist" >&2 + echo "The main worktree is on a branch without it; check that branch" \ + "out there, or commit this one into it, first." >&2 + fi + exit 2 +fi + +# Whether the link is one this script created. Matching the tail rather than a +# current worktree path keeps the answer right after the checkout has been moved +# or renamed, which is precisely when the link dangles and needs attention. +ours() +{ + local link + link=$(readlink "$target") || return 1 + [ "$link" != "${link%/scripts/git-pre-commit.sh}" ] +} + +if [ "$uninstall" -eq 1 ]; then + if [ ! -L "$target" ]; then + echo "No pre-commit symlink to remove" + elif ours; then + rm -f "$target" && echo "Removed pre-commit" + else + echo "Left $target (not ours)" + fi +elif [ -L "$target" ] && [ ! -e "$target" ] && ours; then + + # A moved or renamed checkout leaves our own link dangling. Keeping it would + # disable the hook for good, so repoint it. + ln -sfn "$source" "$target" && echo "Repointed pre-commit" +elif [ -e "$target" ] || [ -L "$target" ]; then + echo "Kept existing $target" +else + ln -s "$source" "$target" && echo "Installed pre-commit" +fi diff --git a/scripts/test-git-hooks.sh b/scripts/test-git-hooks.sh new file mode 100755 index 00000000..618586ee --- /dev/null +++ b/scripts/test-git-hooks.sh @@ -0,0 +1,187 @@ +#!/usr/bin/env bash + +# Smoke-test hook installation and staged-content checking. + +set -euo pipefail + +root=$(git rev-parse --show-toplevel) +source "$root/.ci/common.sh" + +# The hook downgrades a missing tool to a note and passes, so probe for all of +# them up front; otherwise an absent tool surfaces as a bogus assertion failure. +formatter=$(find_clang_format) || { + echo "clang-format version 20 not found" >&2 + exit 1 +} + +# The names the checkers resolve, not the defaults: an override pointing at a +# tool under another name is a valid configuration, and file(1) is a dependency +# of check-newline.sh that nothing else here would notice was missing. +for tool in "${COMMENTFLOW:-commentflow}" "${SHFMT:-shfmt}" \ + "${SHELLCHECK:-shellcheck}" file; do + command -v "$tool" > /dev/null 2>&1 || { + echo "$tool not found" >&2 + exit 1 + } +done + +# The repositories below must not inherit the developer's own configuration: a +# global core.hooksPath sends install-git-hooks.sh outside this sandbox, where +# the trap does not reach and a stray hook is left behind for every later +# commit. +export GIT_CONFIG_GLOBAL=/dev/null +export GIT_CONFIG_SYSTEM=/dev/null + +test_dir=$(mktemp -d) +linked_dir="$test_dir-linked" +trap 'rm -rf "$test_dir" "$linked_dir"' EXIT + +git -C "$test_dir" init -q +git -C "$test_dir" config user.email test@example.com +git -C "$test_dir" config user.name Test +cp -R "$root/.ci" "$root/scripts" "$test_dir/" +cp "$root/.clang-format" "$root/.editorconfig" "$test_dir/" +git -C "$test_dir" add . +(cd "$test_dir" && scripts/install-git-hooks.sh) > /dev/null +test -L "$test_dir/.git/hooks/pre-commit" + +echo 'int add(int first,int second,int third){return first+second+third;} int main(){return 0;}' > "$test_dir/bad.c" +git -C "$test_dir" add bad.c +if (cd "$test_dir" && .git/hooks/pre-commit) > /dev/null 2>&1; then + echo "pre-commit accepted unformatted C" >&2 + exit 1 +fi + +"$formatter" -i "$test_dir/bad.c" +git -C "$test_dir" add bad.c +(cd "$test_dir" && .git/hooks/pre-commit) +git -C "$test_dir" commit -q -m "Add baseline" + +printf 'no newline' > "$test_dir/bad.txt" +git -C "$test_dir" add bad.txt +if (cd "$test_dir" && .git/hooks/pre-commit) > /dev/null 2>&1; then + echo "pre-commit accepted a text file without a final newline" >&2 + exit 1 +fi +printf 'newline restored\n' > "$test_dir/bad.txt" +git -C "$test_dir" add bad.txt +(cd "$test_dir" && .git/hooks/pre-commit) +git -C "$test_dir" commit -q -m "Add text" + +mkdir -p "$test_dir/.ci" +printf '#!/usr/bin/env bash\nreadonly value="$(false)"\necho "$value"\n' \ + > "$test_dir/.ci/warning.sh" +git -C "$test_dir" add .ci/warning.sh +if (cd "$test_dir" && .git/hooks/pre-commit) > /dev/null 2>&1; then + echo "pre-commit accepted a ShellCheck warning" >&2 + exit 1 +fi +printf '#!/usr/bin/env bash\nreadonly value\nvalue="$(false)"\necho "$value"\n' \ + > "$test_dir/.ci/warning.sh" +git -C "$test_dir" add .ci/warning.sh +(cd "$test_dir" && .git/hooks/pre-commit) +git -C "$test_dir" commit -q -m "Add clean shell" + +sed -i.bak 's/indent_size = 4/indent_size = 2/' "$test_dir/.editorconfig" +rm "$test_dir/.editorconfig.bak" +git -C "$test_dir" add .editorconfig +if (cd "$test_dir" && .git/hooks/pre-commit) > /dev/null 2>&1; then + echo "pre-commit ignored a staged EditorConfig change" >&2 + exit 1 +fi + +git -C "$test_dir" checkout -q HEAD -- .editorconfig +printf '\nColumnLimit: 20\n' >> "$test_dir/.clang-format" +git -C "$test_dir" add .clang-format +if (cd "$test_dir" && .git/hooks/pre-commit) > /dev/null 2>&1; then + echo "pre-commit ignored a staged clang-format change" >&2 + exit 1 +fi +git -C "$test_dir" checkout -q HEAD -- .clang-format + +# A checkout without .editorconfig must still be committable: git checkout-index +# fails outright on a name the index does not hold, which used to block every +# commit on any branch predating the file. +git -C "$test_dir" rm -q .editorconfig +git -C "$test_dir" commit -q -m "Drop EditorConfig" +printf 'int answer(void)\n{\n return 42;\n}\n' > "$test_dir/answer.c" +git -C "$test_dir" add answer.c +(cd "$test_dir" && .git/hooks/pre-commit) +git -C "$test_dir" commit -q -m "Add answer" + +# What is staged is what is being committed, so the snapshot has to be judged +# rather than the working tree, which may hold anything at all. +printf 'int partial(void)\n{\n return 1;\n}\n' > "$test_dir/partial.c" +git -C "$test_dir" add partial.c +printf 'int partial (void){return 1;}\n' > "$test_dir/partial.c" +(cd "$test_dir" && .git/hooks/pre-commit) +git -C "$test_dir" commit -q -m "Add partial" +git -C "$test_dir" checkout -q -- partial.c + +# A checker that cannot run at all must read as a note rather than as a verdict +# on the tree, or a machine missing one tool can no longer commit anything. +printf 'int advisory(void)\n{\n return 0;\n}\n' > "$test_dir/advisory.c" +git -C "$test_dir" add advisory.c +note=$(cd "$test_dir" && COMMENTFLOW=$test_dir/absent .git/hooks/pre-commit 2>&1) || { + echo "pre-commit rejected a commit over an unavailable tool" >&2 + exit 1 +} +case "$note" in + *"commentflow unavailable"*) ;; + *) + echo "pre-commit did not report the unavailable tool" >&2 + exit 1 + ;; +esac +git -C "$test_dir" commit -q -m "Add advisory" + +# .git/hooks is shared, so the hook must run the checkers of the worktree being +# committed to, not those of whichever worktree installed it. +git -C "$test_dir" worktree add -q -b linked-test "$linked_dir" +printf '#!/usr/bin/env bash\nexit 1\n' > "$linked_dir/.ci/check-newline.sh" +git -C "$linked_dir" add .ci/check-newline.sh +hook=$(git -C "$linked_dir" rev-parse --path-format=absolute \ + --git-path hooks/pre-commit) +if (cd "$linked_dir" && "$hook") > /dev/null 2>&1; then + echo "pre-commit used another worktree's checks" >&2 + exit 1 +fi +git -C "$test_dir" worktree remove --force "$linked_dir" + +# A moved or renamed checkout leaves our own symlink dangling; installing again +# must repoint it rather than report it as someone else's hook. +ln -sfn /nonexistent/scripts/git-pre-commit.sh "$test_dir/.git/hooks/pre-commit" +(cd "$test_dir" && scripts/install-git-hooks.sh) > /dev/null +test -x "$test_dir/.git/hooks/pre-commit" + +(cd "$test_dir" && scripts/install-git-hooks.sh --uninstall) > /dev/null +test ! -e "$test_dir/.git/hooks/pre-commit" +printf '#!/bin/sh\nexit 0\n' > "$test_dir/.git/hooks/pre-commit" +(cd "$test_dir" && scripts/install-git-hooks.sh) > /dev/null +test ! -L "$test_dir/.git/hooks/pre-commit" + +# git ignores a hook it cannot run, and a hook whose source is missing or not +# executable is exactly that. Installing one has to refuse rather than report +# success, so neither refusal can go quiet again. +rm -f "$test_dir/.git/hooks/pre-commit" +chmod -x "$test_dir/scripts/git-pre-commit.sh" +if (cd "$test_dir" && scripts/install-git-hooks.sh) > /dev/null 2>&1; then + echo "install accepted a source that cannot be executed" >&2 + exit 1 +fi +test ! -e "$test_dir/.git/hooks/pre-commit" +chmod +x "$test_dir/scripts/git-pre-commit.sh" + +mv "$test_dir/scripts/git-pre-commit.sh" "$test_dir/git-pre-commit.sh" +if (cd "$test_dir" && scripts/install-git-hooks.sh) > /dev/null 2>&1; then + echo "install accepted a source that is not there" >&2 + exit 1 +fi +test ! -e "$test_dir/.git/hooks/pre-commit" +mv "$test_dir/git-pre-commit.sh" "$test_dir/scripts/git-pre-commit.sh" + +# An extra argument is a mistake, not a second opinion about uninstalling. +if (cd "$test_dir" && scripts/install-git-hooks.sh --uninstall extra) > /dev/null 2>&1; then + echo "install accepted --uninstall with a stray argument" >&2 + exit 1 +fi diff --git a/src/arch-lower.c b/src/arch-lower.c index e9cc8545..d8ee19be 100644 --- a/src/arch-lower.c +++ b/src/arch-lower.c @@ -10,11 +10,11 @@ #include "../config" #include "defs.h" -/* Mark detached conditional branches so codegen can decide between - * short/long forms without re-deriving CFG shape. +/* Mark detached conditional branches so codegen can decide between short/long + * forms without re-deriving CFG shape. * - * Only the ARM backend reads 'is_branch_detached'; RISC-V and x86-64 ignore - * it, so the pass runs for ARM alone. + * Only the ARM backend reads 'is_branch_detached'; RISC-V and x86-64 ignore it, + * so the pass runs for ARM alone. */ void arch_lower(void) { diff --git a/src/arm-codegen.c b/src/arm-codegen.c index ccecd96e..bf7615f0 100644 --- a/src/arm-codegen.c +++ b/src/arm-codegen.c @@ -1,8 +1,8 @@ /* * shecc - Self-Hosting and Educational C Compiler. * - * shecc is freely redistributable under the BSD 2 clause license. See the - * file "LICENSE" for information on usage and redistribution of this file. + * shecc is freely redistributable under the BSD 2 clause license. See the file + * "LICENSE" for information on usage and redistribution of this file. */ /* Translate IR to target machine code */ @@ -90,8 +90,8 @@ void update_elf_offset(ph2_ir_t *ph2_ir) if (func->bbs) elf_offset += 4; else if (dynlink) { - /* When calling external functions in dynamic linking mode, - * the following instructions are required: + /* When calling external functions in dynamic linking mode, the + * following instructions are required: * - movw + movt: set r8 to 'elf_data_start' * - ldr: load a word from the address 'elf_data_start' into r12. * (restore the global stack pointer.) @@ -101,6 +101,7 @@ void update_elf_offset(ph2_ir_t *ph2_ir) elf_offset += 16; } else { printf("The '%s' function is not implemented\n", ph2_ir->func_name); + fflush(stdout); /* see fatal() */ abort(); } return; @@ -196,9 +197,8 @@ void cfg_flatten(void) * (to ensure 8-byte alignment after pushing the 9 registers) * - ALIGN_UP(func->stack_size, 8) * - * Note that func->stack_size does not include the 36 + 4 bytes, - * so an additional 40 bytes should be added to - * ALIGN_UP(func->stack_size, 8). + * Note that func->stack_size does not include the 36 + 4 bytes, so an + * additional 40 bytes should be added to ALIGN_UP(func->stack_size, 8). */ int stack_top_ofs = ALIGN_UP(func->stack_size, MIN_ALIGNMENT) + 40; @@ -225,9 +225,9 @@ void cfg_flatten(void) insn->src1 = insn->src1 + stack_top_ofs; break; default: - /* Ignore opcodes with the ofs_based_on_stack_top - * flag set since only the three opcodes above needs - * to access a variable's address. + /* Ignore opcodes with the ofs_based_on_stack_top flag + * set since only the three opcodes above needs to + * access a variable's address. */ break; } @@ -390,20 +390,21 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) is_external_call = true; } else { printf("The '%s' function is not implemented\n", ph2_ir->func_name); + fflush(stdout); /* see fatal() */ abort(); } - /* When calling external functions in dynamic linking mode, - * the following instructions are required: + /* When calling external functions in dynamic linking mode, the + * following instructions are required: * - movw + movt: set r8 to 'elf_data_start' * - ldr: load a word from the address 'elf_data_start' to r12. * (restore the global stack pointer.) * * Since shecc uses r12 to store a global stack pointer and external - * functions can freely modify r12, causing internal functions to - * access global variables incorrectly, additional instructions are - * needed to restore r12 from the global object after the external - * function returns. + * functions can freely modify r12, causing internal functions to access + * global variables incorrectly, additional instructions are needed to + * restore r12 from the global object after the external function + * returns. * * Otherwise, only a 'bl' instruction is generated to call internal * functions because shecc guarantees they do not modify r12. @@ -431,6 +432,7 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) ofs = dynamic_sections.elf_plt_start + func->plt_offset; else { printf("The '%s' function is not implemented\n", ph2_ir->func_name); + fflush(stdout); /* see fatal() */ abort(); } emit(__movw(__AL, __r8, ofs)); @@ -487,8 +489,7 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) return; } interm = __r8; - /* div/mod emulation */ - /* Preserve the values of the dividend and divisor */ + /* div/mod emulation: preserve the dividend and the divisor */ emit(__stmdb(__AL, 1, __sp, (1 << rn) | (1 << rm))); /* Obtain absolute values of the dividend and divisor */ emit(__srl_amt(__AL, 0, arith_rs, __r8, rn, 31)); @@ -524,6 +525,7 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) emit(__srl_amt(__AL, 1, logic_rs, __r9, __r9, 1)); emit(__srl_amt(__CC, 0, logic_rs, rm, rm, 1)); emit(__b(__CC, -20)); + /* After completing the emulation, the quotient and remainder will be * stored in __r8 and __r9, respectively. * @@ -630,32 +632,35 @@ void code_generate(void) emit(__mov_i(__AL, __r3, 0)); emit(__bl(__AL, (dynamic_sections.elf_plt_start + PLT_FIXUP_SIZE) - (elf_code_start + elf_code->size))); + /* Call '_exit' (syscall) to terminate the program if __libc_start_main - * returns. */ + * returns. + */ emit(__mov_i(__AL, __r0, 127)); emit(__mov_i(__AL, __r7, 1)); emit(__svc()); - /* If the compiled program is dynamic linking, the starting - * point of 'main_wrapper' is located here. + /* If the compiled program is dynamic linking, the starting point of + * 'main_wrapper' is located here. * - * Push the contents of r4-r11 and lr onto stack. - * Preserve 'argc' and 'argv' for the 'main' function. + * Push the contents of r4-r11 and lr onto stack. Preserve 'argc' and + * 'argv' for the 'main' function. */ emit(__stmdb(__AL, 1, __sp, 0x4FF0)); emit(__mov_r(__AL, __r9, __r0)); emit(__mov_r(__AL, __r10, __r1)); } - /* For both static and dynamic linking, we need to set up the stack - * and call the main function. + + /* For both static and dynamic linking, we need to set up the stack and call + * the main function. * - * To ensure that the stack remains 8-byte aligned after adjustment, - * 'ofs' is to align(GLOBAL_FUNC->stack_size, 8) to allocate space - * for the global stack. + * To ensure that the stack remains 8-byte aligned after adjustment, 'ofs' + * is to align(GLOBAL_FUNC->stack_size, 8) to allocate space for the global + * stack. * - * In dynamic linking mode, since the preceding __stmdb instruction - * pushes 9 registers onto stack, 'ofs' must be increased by 4 to - * prevent the stack from becoming misaligned. + * In dynamic linking mode, since the preceding __stmdb instruction pushes 9 + * registers onto stack, 'ofs' must be increased by 4 to prevent the stack + * from becoming misaligned. */ ofs = ALIGN_UP(GLOBAL_FUNC->stack_size, MIN_ALIGNMENT); if (dynlink) @@ -664,21 +669,22 @@ void code_generate(void) emit(__movt(__AL, __r8, ofs)); emit(__sub_r(__AL, __sp, __sp, __r8)); emit(__mov_r(__AL, __r12, __sp)); - /* The first object in the .data section is used to store the global - * stack pointer. Therefore, store r12 at the address 'elf_data_start' - * after the global stack has been prepared. + + /* The first object in the .data section is used to store the global stack + * pointer. Therefore, store r12 at the address 'elf_data_start' after the + * global stack has been prepared. */ emit(__movw(__AL, __r8, elf_data_start)); emit(__movt(__AL, __r8, elf_data_start)); emit(__sw(__AL, __r12, __r8, 0)); if (!dynlink) { - /* Jump directly to the main preparation and then execute the - * main function. + /* Jump directly to the main preparation and then execute the main + * function. * * In static linking mode, when the main function completes its - * execution, it will invoke the '_exit' syscall to terminate - * the program. + * execution, it will invoke the '_exit' syscall to terminate the + * program. * * That is, the execution flow is: * @@ -707,12 +713,12 @@ void code_generate(void) /* __syscall - only for static linking * * If the number of arguments is greater than 4, the additional - * arguments need to be retrieved from the stack. However, this - * process must modify the contents of registers r4-r7. + * arguments need to be retrieved from the stack. However, this process + * must modify the contents of registers r4-r7. * * Therefore, __syscall needs to preserve the contents of these - * registers before invoking a syscall, and restore them after - * the syscall has completed. + * registers before invoking a syscall, and restore them after the + * syscall has completed. */ emit(__stmdb(__AL, 1, __sp, 0x00F0)); emit(__lw(__AL, __r4, __sp, 16)); @@ -741,12 +747,12 @@ void code_generate(void) if (dynlink) { emit(__mov_r(__AL, __r0, __r9)); emit(__mov_r(__AL, __r1, __r10)); + /* Call the main function. * - * After the main function returns, the following - * instructions restore the registers r4-r11 and - * return control to __libc_start_main via the - * preserved lr. + * After the main function returns, the following instructions + * restore the registers r4-r11 and return control to + * __libc_start_main via the preserved lr. */ emit(__bl(__AL, MAIN_BB->elf_offset - elf_code->size)); emit(__movw(__AL, __r8, ofs)); @@ -760,12 +766,13 @@ void code_generate(void) emit(__lw(__AL, __r0, __r8, 0)); emit(__add_i(__AL, __r1, __r8, 4)); - /* Call main function, and call '_exit' syscall to - * terminate the program. */ + /* Call main function, and call '_exit' syscall to terminate the + * program. + */ emit(__bl(__AL, MAIN_BB->elf_offset - elf_code->size)); - /* exit with main's return value - r0 already has the - * return value */ + /* exit with main's return value - r0 already has the return value + */ emit(__mov_i(__AL, __r7, 1)); emit(__svc()); } @@ -781,29 +788,28 @@ void plt_generate(void) { /* - PLT code generation explanation - * - * As described in ARM's Platform Standard, PLT code should make register - * ip address the corresponding GOT entry on SVr4-like (Linux-like) - * platforms. + * As described in ARM's Platform Standard, PLT code should make register ip + * address the corresponding GOT entry on SVr4-like (Linux-like) platforms. * - * Therefore, PLT[1] ~ PLT[N] use r12 (ip) to load the address of the - * GOT entry and jump to the function entry via the GOT value. + * Therefore, PLT[1] ~ PLT[N] use r12 (ip) to load the address of the GOT + * entry and jump to the function entry via the GOT value. * * PLT[0] is used to call the resolver, which requires: * - [sp] contains the return address from the original function call. * - ip contains the address of the GOT entry. * - lr points to the address of GOT[2]. * - * The second requirement is alreadly handled by PLT[1] - PLT[N], so - * PLT[0] must take care of the other two. The first one can be achieved - * by a 'push' instruction; for the third, we use r10 to store the address - * of GOT[2] and then move the value to lr. + * The second requirement is alreadly handled by PLT[1] - PLT[N], so PLT[0] + * must take care of the other two. The first one can be achieved by a + * 'push' instruction; for the third, we use r10 to store the address of + * GOT[2] and then move the value to lr. * * - Reason for using r10 in PLT[0] - * * The register allocation assumes 8 available registers, so the ARM code * generator primarily uses r0-r7 for code generation. These registers - * cannot be modified arbitrarily; otherwise, the program may fail if any - * of them are changed by PLT[0]. + * cannot be modified arbitrarily; otherwise, the program may fail if any of + * them are changed by PLT[0]. * * However, r8-r11 can be freely used as temporary registers during code * generation, so PLT[0] arbitrarily chooses r10 to perform the required diff --git a/src/arm.c b/src/arm.c index 8a07ca92..6bc83555 100644 --- a/src/arm.c +++ b/src/arm.c @@ -1,8 +1,8 @@ /* * shecc - Self-Hosting and Educational C Compiler. * - * shecc is freely redistributable under the BSD 2 clause license. See the - * file "LICENSE" for information on usage and redistribution of this file. + * shecc is freely redistributable under the BSD 2 clause license. See the file + * "LICENSE" for information on usage and redistribution of this file. */ /* ARMv7-A instruction encoding */ @@ -22,8 +22,8 @@ * | +------- always * +------------ branch * - * Machine-level "b" instructions have restricted ranges from the address of - * the current instruction. + * Machine-level "b" instructions have restricted ranges from the address of the + * current instruction. */ #include "defs.h" @@ -44,8 +44,7 @@ typedef enum { arm_stmdb = 16 } arm_op_t; -/* Condition code - * Reference: +/* Condition code Reference: * https://community.arm.com/developer/ip-products/processors/b/processors-ip-blog/posts/condition-codes-1-condition-flags-and-codes */ typedef enum { @@ -230,8 +229,7 @@ int __sll_amt(arm_cond_t cond, int __sra(arm_cond_t cond, arm_reg rd, arm_reg rm, arm_reg rs) { - /* Arithmetic right shift with register - * Bit 4 = 1 (register-specified shift) + /* Arithmetic right shift with register Bit 4 = 1 (register-specified shift) * Bits 5-6 = arith_rs (2) for arithmetic right shift */ return arm_encode(cond, 0 + (arm_mov << 1) + (0 << 5), 0, rd, @@ -260,11 +258,10 @@ int __zero(int rd) return __mov_i(__AL, rd, 0); } -/* ARM halfword transfer (immediate offset) using special encoding - * For halfword: bits[11:8] = imm4H, bits[7:4] = encoding, bits[3:0] = imm4L - * imm4H: upper 4 bits of offset - * imm4L: lower 4 bits of offset - * encoding: 0b1011 for unsigned halfword, 0b1111 for signed halfword +/* ARM halfword transfer (immediate offset) using special encoding For halfword: + * bits[11:8] = imm4H, bits[7:4] = encoding, bits[3:0] = imm4L imm4H: upper 4 + * bits of offset imm4L: lower 4 bits of offset encoding: 0b1011 for unsigned + * halfword, 0b1111 for signed halfword */ int arm_halfword_transfer(arm_cond_t cond, int l, diff --git a/src/defs.h b/src/defs.h index 02ba4e4f..c99fcc3e 100644 --- a/src/defs.h +++ b/src/defs.h @@ -1,8 +1,8 @@ /* * shecc - Self-Hosting and Educational C Compiler. * - * shecc is freely redistributable under the BSD 2 clause license. See the - * file "LICENSE" for information on usage and redistribution of this file. + * shecc is freely redistributable under the BSD 2 clause license. See the file + * "LICENSE" for information on usage and redistribution of this file. */ #pragma once @@ -18,10 +18,10 @@ * * A host compiler's runtime does, and so does the one a dynamically linked * shecc reaches through the PLT. The embedded lib/c.c does not: it has no - * buffer behind fgets() or fputc(), so every byte would become its own - * read(2) or write(2). Those builds call the kernel directly instead, which - * is available on exactly the same condition, since '__syscall' is - * synthesized only for static linking. + * buffer behind fgets() or fputc(), so every byte would become its own read(2) + * or write(2). Those builds call the kernel directly instead, which is + * available on exactly the same condition, since '__syscall' is synthesized + * only for static linking. */ #ifdef __SHECC__ #ifdef __SHECC_DYNLINK__ @@ -38,6 +38,7 @@ #define MAX_VAR_LEN 128 /* ".label." plus an int, for basic_block_t's dump name. */ #define MAX_LABEL_LEN 24 + /* Staging buffer for one instruction's Graphviz label in bb_dump(). The widest * case is a binary operator: three MAX_VAR_LEN names, three subscripts, an * operator and 41 bytes of markup. @@ -75,12 +76,14 @@ #define MAX_GOTPLT 1024 #define MAX_CONSTANTS 1024 #define MAX_NESTING 128 + /* How many instructions an if may speculate when flattened into a select, and * how many blocks one of its arms may span. Beyond a handful, running the arm * that would have been skipped costs more than the misprediction it avoids. */ #define MAX_SPECULATED_INSNS 8 #define MAX_IF_ARM_BLOCKS 4 + /* Recursion limits for nesting in the input. The parser descends recursively * for each of these, so a deeply nested program would otherwise exhaust the * machine stack before any diagnostic could be produced. @@ -88,10 +91,16 @@ #define MAX_EXPR_DEPTH 256 #define MAX_BLOCK_DEPTH 256 #define MAX_OPERAND_STACK_SIZE 32 + +/* Depth of the operator stack that read_expr() and the constant-expression + * evaluator shunt through, and of the value stack the latter keeps beside it. + */ +#define MAX_OPERATOR_STACK_SIZE 10 #define MAX_ANALYSIS_STACK_SIZE 1600 -/* Default capacities for common data structures */ -/* Arena sizes optimized based on typical usage patterns */ +/* Default capacities for common data structures, with the arena sizes taken + * from typical usage patterns. + */ #define DEFAULT_ARENA_SIZE 262144 /* 256 KiB - standard default */ #define SMALL_ARENA_SIZE 65536 /* 64 KiB - for small allocations */ #define LARGE_ARENA_SIZE 524288 /* 512 KiB - for instruction arena */ @@ -124,16 +133,30 @@ do { \ ; \ } while (0) -/* shecc runs on the target it compiles for, so the host pointer width is - * the target's. This must not be hardcoded to 4: on an LP64 target the - * var_list allocations and memcpy sizes below would be half what they need. + +/* shecc runs on the target it compiles for, so the host pointer width is the + * target's. This must not be hardcoded to 4: on an LP64 target the var_list + * allocations and memcpy sizes below would be half what they need. */ #define HOST_PTR_SIZE PTR_SIZE + +/* shecc parses no attributes, and does not need the hint: it reports what it + * cannot compile rather than warning about it. + */ +#define __noreturn #else /* suppress GCC/Clang warnings */ #define UNUSED(x) (void) (x) /* configure host data model when using 'memcpy'. */ #define HOST_PTR_SIZE __SIZEOF_POINTER__ + +/* Marks the diagnostic paths that never come back. Without it the host compiler + * cannot see that a variable set on every surviving path is initialized, and + * reports each of those as a maybe-uninitialized use -- which is why the + * warning used to be switched off for the whole tree, taking the real cases + * with it. + */ +#define __noreturn __attribute__((noreturn)) #endif #ifndef MIN_ALIGNMENT @@ -181,8 +204,8 @@ #define MIN_IV_CHAIN 3 #define MAX_IV_PER_LOOP 2 -/* How many blocks one natural loop's walk keeps in hand at once. Past this - * the walk stops widening, which can only understate a depth. +/* How many blocks one natural loop's walk keeps in hand at once. Past this the + * walk stops widening, which can only understate a depth. */ #define MAX_LOOP_WALK 512 @@ -195,18 +218,17 @@ #define MAX_INLINE_VARS 32 #define MAX_INLINE_ROUNDS 3 -/* What a naming inside one loop is worth against one in straight-line code, - * and how many nesting levels still multiply it. +/* What a naming inside one loop is worth against one in straight-line code, and + * how many nesting levels still multiply it. */ #define LOOP_USE_WEIGHT 8 #define MAX_WEIGHTED_LOOP_DEPTH 3 -/* How many registers at the top of the allocator's file a call preserves. - * Only such a register can hold a value across a call, so only these may be - * given to a variable for the whole of a function that calls anything. A - * target states its own count in mk/.mk, beside the REG_CNT that fixes - * the file it counts from; a target that has not had its file checked this way - * keeps none. +/* How many registers at the top of the allocator's file a call preserves. Only + * such a register can hold a value across a call, so only these may be given to + * a variable for the whole of a function that calls anything. A target states + * its own count in mk/.mk, beside the REG_CNT that fixes the file it + * counts from; a target that has not had its file checked this way keeps none. * * HAVE_COND_MOVE comes from the same place and says whether the target can * select between two values without branching, which is what makes flattening @@ -328,15 +350,17 @@ typedef enum { T_cppd_ifdef, T_cppd_ifndef, T_cppd_pragma, - /* C pre-processor specific, these kinds - * will be removed after pre-processing is done. + + /* C pre-processor specific, these kinds will be removed after + * pre-processing is done. */ T_newline, T_backslash, T_whitespace, T_tab, - /* '#' and '##' inside a macro replacement list. Resolved while the macro - * is expanded, so neither ever reaches the parser. + + /* '#' and '##' inside a macro replacement list. Resolved while the macro is + * expanded, so neither ever reaches the parser. */ T_hash, T_hashhash @@ -462,6 +486,7 @@ typedef enum { } opcode_t; /* variable definition */ + /* Depth of the SSA renaming stack: how many definitions of one variable can be * live along a single dominator path. */ @@ -469,6 +494,7 @@ typedef enum { typedef struct { int counter; + /* Grown on demand: only a base variable is ever renamed, so the SSA * versions copied from it -- the large majority of all variables -- would * otherwise each carry an unused MAX_RENAME_STACK array. @@ -504,12 +530,13 @@ typedef struct var_list { struct var { type_t *type; + /* Interned, not copied. A MAX_VAR_LEN array was 128 of this struct's 312 * bytes on every one of the ~86k variables a self-compile creates, and - * var_t is embedded by value MAX_FIELDS times in each type_t and - * MAX_PARAMS times in each func_t, so the array cost another 8 KiB per - * type. Generated temporary names come from gen_name(); source-level names - * come from intern_string(). Never NULL -- an unnamed variable holds "". + * var_t is embedded by value MAX_FIELDS times in each type_t and MAX_PARAMS + * times in each func_t, so the array cost another 8 KiB per type. Generated + * temporary names come from gen_name(); source-level names come from + * intern_string(). Never NULL -- an unnamed variable holds "". */ char *var_name; int ptr_level; @@ -519,29 +546,31 @@ struct var { bool address_taken; /* true if variable address was taken (&var) */ /* Working state for strength_reduce(): how many instructions in the * function write the variable, whether it is written inside the loop being - * examined, and how much its value moves per iteration when it does. - * All three are recomputed per loop; nothing outside that pass reads them. + * examined, and how much its value moves per iteration when it does. All + * three are recomputed per loop; nothing outside that pass reads them. */ int def_cnt; int loop_stamp; int iv_gen; int iv_step; + /* pin_registers()'s tally for the variable: what its namings are worth * weighted by loop depth, the reverse-post-order number of the last block - * that named it, whether it was - * ever named in two, and whether a loop named it. Stamped per function so - * that no array has to hold the candidates -- the file has a handful of - * registers and a function names hundreds of variables, and the one worth - * a register is not reliably among the first few met. + * that named it, whether it was ever named in two, and whether a loop named + * it. Stamped per function so that no array has to hold the candidates -- + * the file has a handful of registers and a function names hundreds of + * variables, and the one worth a register is not reliably among the first + * few met. */ int pin_gen; int pin_weight; int pin_blk; bool pin_cross; bool pin_hot; + /* Defined inside an arm that if_convert() flattened into a select. The - * register its variable is pinned to still holds the value flowing into - * the select, which the arms read and the select overwrites, so a value + * register its variable is pinned to still holds the value flowing into the + * select, which the arms read and the select overwrites, so a value * computed on the way there must go somewhere else. */ bool in_select_arm; @@ -558,6 +587,7 @@ struct var { int merge_gen; struct var *base; int subscript; + /* Every SSA version of this variable, grown on demand. A fixed 128-entry * array made every var_t 1 KiB heavier -- and var_t is embedded by value in * type_t's field table and in every function's parameter list -- while the @@ -567,6 +597,7 @@ struct var { struct var **subscripts; int subscripts_idx; int subscripts_cap; + /* SSA renaming state, allocated on first use by var_rename(). Only a base * variable is ever renamed, so the versions copied from it -- the large * majority of all variables -- each carried an unused 24-byte rename_t @@ -588,13 +619,13 @@ struct var { bool space_is_allocated; /* whether space is allocated for this variable */ bool has_backing_storage; - /* This flag is used to indicate to the compiler that the offset of - * the variable is based on the top of the local stack. + /* This flag is used to indicate to the compiler that the offset of the + * variable is based on the top of the local stack. */ bool ofs_based_on_stack_top; - /* True when this variable was synthesized to hold a compound literal - * (e.g., array or struct literal temporaries). + /* True when this variable was synthesized to hold a compound literal (e.g., + * array or struct literal temporaries). */ bool is_compound_literal; }; @@ -613,7 +644,7 @@ typedef struct block block_t; typedef struct basic_block basic_block_t; /* Definition of a growable buffer for a mutable null-terminated string - * @size: Current number of elements in the array + * @size: Current number of elements in the array * @capacity: Number of elements that can be stored without resizing * @elements: Pointer to the array of characters */ @@ -626,13 +657,11 @@ typedef struct { /* phase-2 IR definition */ struct ph2_ir { /* Grouped by width so the struct carries no interior padding: mixed in - * declaration order it was 72 bytes for 63 bytes of fields, on all ~101k - * of them a self-compile emits. - */ - /* Callee / definition name, interned in GENERAL_ARENA rather than copied. - * A MAX_VAR_LEN array here was 128 of this struct's 192 bytes while only - * OP_define, OP_call and OP_address_of_func ever name anything. NULL when - * unused. + * declaration order it was 72 bytes for 63 bytes of fields, on all ~101k of + * them a self-compile emits. Callee / definition name, interned in + * GENERAL_ARENA rather than copied. A MAX_VAR_LEN array here was 128 of + * this struct's 192 bytes while only OP_define, OP_call and + * OP_address_of_func ever name anything. NULL when unused. */ char *func_name; basic_block_t *next_bb; @@ -650,13 +679,14 @@ struct ph2_ir { int size_bytes; /* Size in bytes for load/store/read/write operations */ bool is_branch_detached; - /* When an instruction uses a variable that its offset is based on - * the top of the stack, this instruction's flag is also set to - * indicate the compiler to recalculate the offset after the function's - * stack size has been determined. + + /* When an instruction uses a variable that its offset is based on the top + * of the stack, this instruction's flag is also set to indicate the + * compiler to recalculate the offset after the function's stack size has + * been determined. * - * Currently, only OP_load, OP_store and OP_address_of need this flag - * to recompute the offset. + * Currently, only OP_load, OP_store and OP_address_of need this flag to + * recompute the offset. */ bool ofs_based_on_stack_top; bool is_pointer; /* True if this operation involves a pointer type */ @@ -670,8 +700,9 @@ struct type { base_type_t base_type; struct type *base_struct; int size; - /* Member table, allocated when the type is created rather than inlined. - * A MAX_FIELDS array of var_t by value made type_t 12 KiB, and TYPES is a + + /* Member table, allocated when the type is created rather than inlined. A + * MAX_FIELDS array of var_t by value made type_t 12 KiB, and TYPES is a * flat MAX_TYPES array that global_init() zeroes up front -- 3 MiB of * resident memory for the 90 types a self-compile actually declares. */ @@ -710,6 +741,7 @@ struct insn { var_t *rd; var_t *rs1; var_t *rs2; + /* The value OP_cmov keeps when its condition does not hold. A select needs * three inputs and the two source fields are taken by the chosen value and * the condition; every other opcode leaves this NULL. @@ -719,6 +751,7 @@ struct insn { bool useful; /* Used in DCE process. Set true if instruction is useful. */ basic_block_t *belong_to; phi_operand_t *phi_ops; + /* Callee or goto-label name, interned rather than copied. add_insn() * already interned the text before copying it in, so the array was 64 of * this struct's 136 bytes for a string the pool owns anyway, on all ~73k @@ -773,23 +806,25 @@ struct basic_block { * while almost every block has one or two predecessors. */ bb_connection_t *prev; + /* Register file on entry to this block, captured by reg_alloc() at the end * of the predecessor it falls out of. Non-NULL only when a file was handed * over, and bb_export_regs() does that only for an edge that is all three * of: the predecessor's single successor, that predecessor's rpo_next, and * this block's single predecessor. A sole predecessor alone is NOT enough - * -- a branch target is emitted wherever the backend's linear walk puts - * it, so the registers reaching it are not the ones the branch left. + * -- a branch target is emitted wherever the backend's linear walk puts it, + * so the registers reaching it are not the ones the branch left. * * Allocated only for a block that actually inherits a file: fewer than one - * block in thirteen does, so a REG_CNT array here cost 64 bytes on all - * ~51k blocks to serve 7% of them. + * block in thirteen does, so a REG_CNT array here cost 64 bytes on all ~51k + * blocks to serve 7% of them. */ struct var **entry_regs; + /* Used in instruction dumping when ir_dump is enabled, and allocated only * then: a fixed array here is 128 bytes on every one of the tens of - * thousands of blocks a self-compile creates, all of it zeroed for - * nothing in the default path. + * thousands of blocks a self-compile creates, all of it zeroed for nothing + * in the default path. */ char *bb_label_name; struct basic_block *next; /* normal BB */ @@ -799,6 +834,7 @@ struct basic_block { struct basic_block *r_idom; struct basic_block *rpo_next; struct basic_block *rpo_r_next; + /* Dominance and reverse-dominance frontiers. These were fixed worst-case * arrays sized MAX_BB_DOM_SUCC / MAX_BB_RDOM_SUCC, which cost 2560 bytes in * every basic block while a typical block uses a handful of entries. Worse, @@ -808,6 +844,7 @@ struct basic_block { */ struct basic_block **DF; struct basic_block **RDF; + /* Dominator-tree children, grown on demand for the same reason as prev[] * and the frontiers: a fixed array cost half a kilobyte in every block. */ @@ -818,6 +855,7 @@ struct basic_block { block_t *scope; int prev_cap; + /* One past the highest slot bb_connect() has ever filled. Scans of prev[] * stop here instead of walking all MAX_BB_PRED slots; a block typically has * one or two predecessors, so the difference is two orders of magnitude. @@ -825,6 +863,7 @@ struct basic_block { * bound and the NULL checks in each loop still skip the holes. */ int prev_idx; + /* Index of this block's first instruction in PH2_IR_FLATTEN, or -1 when it * emitted none. Recorded while that mapping is built so the backend need * not search for it. @@ -832,20 +871,23 @@ struct basic_block { int ph2_base; int rpo; int rpo_r; - /* How many loops enclose the block. - * pin_registers() weights a variable's uses by it: a name inside a loop - * stands for as many reads as the loop has iterations, and ranking by the - * plain count gave a register to a variable named four times in - * straight-line code over one named once in the innermost loop. + + /* How many loops enclose the block. pin_registers() weights a variable's + * uses by it: a name inside a loop stands for as many reads as the loop has + * iterations, and ranking by the plain count gave a register to a variable + * named four times in straight-line code over one named once in the + * innermost loop. */ int loop_depth; + /* What one naming of a variable in this block is worth to pin_registers(), * derived from loop_depth once rather than on every operand of every * instruction the tally walks. */ int loop_weight; - /* Stamp marking the block as already counted for the loop being walked, - * so that one loop raises its depth once however many ways in there are. + + /* Stamp marking the block as already counted for the loop being walked, so + * that one loop raises its depth once however many ways in there are. */ int loop_mark; int df_idx; @@ -888,6 +930,7 @@ struct func { var_t param_defs[MAX_PARAMS]; int num_params; int va_args; + /* inline_calls()'s verdict on this body and the return that ends it, * stamped with the round that reached them: a body is examined once per * round rather than once per call site that names it. @@ -910,10 +953,10 @@ struct func { int saved_regs; /* Registers holding a variable for the whole function, one bit each. The - * backend needs these: its notion of what is live out of a block comes - * from the successor's entry registers, which say nothing about a variable - * that is resident everywhere, and it would otherwise drop the code that - * puts a value into one as dead. + * backend needs these: its notion of what is live out of a block comes from + * the successor's entry registers, which say nothing about a variable that + * is resident everywhere, and it would otherwise drop the code that puts a + * value into one as dead. */ int pinned_regs; @@ -940,8 +983,8 @@ typedef struct { int polluted; } regfile_t; -/* In the ELF specification, the following data types are defined - * for data representation: +/* In the ELF specification, the following data types are defined for data + * representation: * * +-------------+------+-----------+--------------------------+ * | Name | Size | Alignment | Purpose | @@ -960,18 +1003,17 @@ typedef struct { * | char | | | | * +-------------+------+-----------+--------------------------+ * - * However, since the current implementation doesn't support unsigned - * data type definitions, such as 'unsigned int', 'unsigned short' and - * so on, the ELF structures now are implemented using the 'signed' data - * type primarily. - * - Elf32_Addr -> int - * - Elf32_Half -> short - * - Elf32_Off -> int - * - Elf32_Word -> int + * However, since the current implementation doesn't support unsigned data type + * definitions, such as 'unsigned int', 'unsigned short' and so on, the ELF + * structures now are implemented using the 'signed' data type primarily. + * - Elf32_Addr -> int + * - Elf32_Half -> short + * - Elf32_Off -> int + * - Elf32_Word -> int * - unsigned char -> char * - * TODO: Use correct unsigned types for these ELF structures after - * the 'unsigned' specifier is supported. + * TODO: Use correct unsigned types for these ELF structures after the + * 'unsigned' specifier is supported. */ /* ELF header */ @@ -1018,8 +1060,7 @@ typedef struct { int sh_entsize; /* Elf32_Word */ } elf32_shdr_t; -/* Structures for dynamic linked program */ -/* ELF buffers for dynamic sections */ +/* Structures for dynamic linked program ELF buffers for dynamic sections */ typedef struct { strbuf_t *elf_interp; strbuf_t *elf_dynamic; @@ -1039,11 +1080,11 @@ typedef struct { int plt_size; int got_size; - /* Currently, we don't consider the scenarios involving - * a mixture of REL and RELA relocation entries. + /* Currently, we don't consider the scenarios involving a mixture of REL and + * RELA relocation entries. * - * Therefore, use a flag to determine the type of - * relocation entries to be processed: + * Therefore, use a flag to determine the type of relocation entries to be + * processed: * - true: use RELA relocation entries * - false: use REL relocation entries */ diff --git a/src/elf.c b/src/elf.c index c38efdba..44043dc4 100644 --- a/src/elf.c +++ b/src/elf.c @@ -1,8 +1,8 @@ /* * shecc - Self-Hosting and Educational C Compiler. * - * shecc is freely redistributable under the BSD 2 clause license. See the - * file "LICENSE" for information on usage and redistribution of this file. + * shecc is freely redistributable under the BSD 2 clause license. See the file + * "LICENSE" for information on usage and redistribution of this file. */ /* ELF file manipulation */ @@ -21,8 +21,8 @@ void elf_write_str(strbuf_t *elf_array, const char *vals) { /* Note that strbuf_puts() does not push the null character. * - * If necessary, use elf_write_byte() to append the null character - * after calling elf_write_str(). + * If necessary, use elf_write_byte() to append the null character after + * calling elf_write_str(). */ if (!elf_array || !vals) return; @@ -72,15 +72,15 @@ void elf_write_blk(strbuf_t *elf_array, void *blk, int sz) { if (!elf_array || !blk || sz <= 0) return; - char *ptr = blk; + const char *ptr = blk; for (int i = 0; i < sz; i++) strbuf_putc(elf_array, ptr[i]); } -/* The dynamic-linking tables differ only in width between the two ELF - * classes, so the generator below writes them through these helpers rather - * than memcpy-ing a struct: shecc has no 64-bit integer type, so an ELF64 - * entry cannot be expressed as a C struct here at all. +/* The dynamic-linking tables differ only in width between the two ELF classes, + * so the generator below writes them through these helpers rather than + * memcpy-ing a struct: shecc has no 64-bit integer type, so an ELF64 entry + * cannot be expressed as a C struct here at all. */ int elf_sym_size(void) @@ -140,6 +140,7 @@ void elf_write_jmprel(strbuf_t *buf, int offset, int sym_idx) { #if ELF_IS_64 == 1 elf_write_quad(buf, offset); + /* r_info is (symbol << 32) | type, so the two halves are written in * little-endian order as type followed by symbol index. */ @@ -165,8 +166,8 @@ void elf_write_got_slot(strbuf_t *buf, int val) } /* Place the dynamic sections. Every start is derived from the end of .rodata, - * so a backend whose final code size is only known after emission can call - * this again once it is. + * so a backend whose final code size is only known after emission can call this + * again once it is. */ void elf_layout_dynamic(void) { @@ -184,8 +185,8 @@ void elf_layout_dynamic(void) dynamic_sections.elf_plt_start = ro_end + relplt_bytes; /* .interp opens the second load segment, so start it a page clear of the - * first: the two must not share a page, and the offset must stay - * congruent to the address modulo the page size. + * first: the two must not share a page, and the offset must stay congruent + * to the address modulo the page size. */ dynamic_sections.elf_interp_start = dynamic_sections.elf_plt_start + dynamic_sections.plt_size + PAGESIZE; @@ -285,8 +286,9 @@ void elf_generate_header(void) elf_rodata->size + elf_symtab->size + elf_strtab->size + elf_shstrtab->size; } - /* The following table explains the meaning of each field in the - * ELF32 file header. + + /* The following table explains the meaning of each field in the ELF32 file + * header. * * Notice that the following values are hexadecimal. * @@ -366,7 +368,7 @@ void elf_generate_header(void) void elf_generate_program_headers(void) { - strbuf_t *elf_relplt = elf_relplt_buf(); + const strbuf_t *elf_relplt = elf_relplt_buf(); if (!elf_program_header || !elf_code || !elf_data || !elf_rodata || (dynlink && (!dynamic_sections.elf_interp || !elf_relplt || @@ -379,8 +381,7 @@ void elf_generate_program_headers(void) #if ELF_IS_64 == 1 /* Two ELF64 PT_LOAD segments, 56 bytes each. Field order differs from - * ELF32: p_flags sits immediately after p_type rather than before - * p_align. + * ELF32: p_flags sits immediately after p_type rather than before p_align. */ int ro_size = elf_header_len + elf_code->size + elf_rodata->size; if (dynlink) @@ -399,9 +400,9 @@ void elf_generate_program_headers(void) /* read-write segment. Statically linked it holds .data (plus .bss, which * occupies no file space) and starts at the next page boundary so that * p_vaddr === p_offset (mod p_align), which the kernel enforces. - * Dynamically linked it begins at .interp and covers everything the - * loader needs, which elf_preprocess() has already placed a page clear of - * the read-only segment. + * Dynamically linked it begins at .interp and covers everything the loader + * needs, which elf_preprocess() has already placed a page clear of the + * read-only segment. */ int data_file_ofs = ALIGN_UP(ro_size, PAGESIZE); int rw_vaddr = elf_data_start; @@ -566,14 +567,14 @@ void elf_generate_program_headers(void) void elf_generate_section_headers(void) { #if ELF_IS_64 == 0 - /* x86-64 output carries no section headers; the program headers alone - * are sufficient to load and run the image. The body below is therefore - * compiled out entirely for that target -- leaving it after an early - * return would make it unreachable code, which shecc's own parser - * rejects when it compiles this file. + /* x86-64 output carries no section headers; the program headers alone are + * sufficient to load and run the image. The body below is therefore + * compiled out entirely for that target -- leaving it after an early return + * would make it unreachable code, which shecc's own parser rejects when it + * compiles this file. */ - strbuf_t *elf_relplt = elf_relplt_buf(); + const strbuf_t *elf_relplt = elf_relplt_buf(); /* Check for null pointers to prevent crashes */ if (!elf_section_header || !elf_code || !elf_data || !elf_rodata || !elf_symtab || !elf_strtab || !elf_shstrtab || @@ -590,9 +591,8 @@ void elf_generate_section_headers(void) elf32_shdr_t shdr; int ofs = elf_header_len, sh_name = 0; - /* - * The following table uses the text section header as an example - * to explain the ELF32 section header. + /* The following table uses the text section header as an example to explain + * the ELF32 section header. * * | Section | | * & | Header bytes | Explanation | @@ -882,8 +882,8 @@ void elf_align_to(strbuf_t *elf_array, int boundary) elf_write_byte(elf_array, 0); } -/* Pad to a four-byte boundary, which is what the sections holding words want. - * A section whose contents are read as pointers wants elf_align_to(PTR_SIZE) +/* Pad to a four-byte boundary, which is what the sections holding words want. A + * section whose contents are read as pointers wants elf_align_to(PTR_SIZE) * instead: on a 64-bit target four bytes is not enough. */ void elf_align(strbuf_t *elf_array) @@ -901,9 +901,8 @@ void elf_generate_dynamic_sections(void) { strbuf_t *elf_relplt = elf_relplt_buf(); - /* In dynamic linking mode, elf_generate_sections() also generates - * .interp, .dynsym, .dynstr, .rel.plt (.rela.plt), .got and dynamic - * sections. + /* In dynamic linking mode, elf_generate_sections() also generates .interp, + * .dynsym, .dynstr, .rel.plt (.rela.plt), .got and dynamic sections. * * .plt section is generated at the code generation phase. */ @@ -913,8 +912,9 @@ void elf_generate_dynamic_sections(void) /* .interp section */ elf_write_str(dynamic_sections.elf_interp, DYN_LINKER); elf_write_byte(dynamic_sections.elf_interp, 0); - /* .got follows .interp and the loader writes pointers into it, so pad - * to a pointer boundary rather than the usual four bytes. + + /* .got follows .interp and the loader writes pointers into it, so pad to a + * pointer boundary rather than the usual four bytes. */ elf_align_to(dynamic_sections.elf_interp, PTR_SIZE); @@ -936,8 +936,8 @@ void elf_generate_dynamic_sections(void) * - Append the external function name to .dynstr section. * - Set plt_offset for the external function. * - * Since __libc_start_main is not added to the function list, - * it must be handled additionally first. + * Since __libc_start_main is not added to the function list, it must be + * handled additionally first. */ rel_offset = dynamic_sections.elf_got_start + PTR_SIZE * RESERVED_GOT_NUM; elf_write_jmprel(elf_relplt, rel_offset, dymsym_idx); @@ -951,17 +951,18 @@ void elf_generate_dynamic_sections(void) elf_write_byte(dynamic_sections.elf_dynstr, 0); st_name += strlen("__libc_start_main") + 1; - /* Because PLT[1] is reserved for __libc_start_main, its plt_offset - * must be PLT_FIXUP_SIZE. Therefore, no offset assignment is - * required for this function. + /* Because PLT[1] is reserved for __libc_start_main, its plt_offset must be + * PLT_FIXUP_SIZE. Therefore, no offset assignment is required for this + * function. */ func_plt_ofs = PLT_FIXUP_SIZE + PLT_ENT_SIZE; for (func_t *func = FUNC_LIST.head; func; func = func->next) { if (!func->is_used || func->bbs) continue; - /* If the function is used and has no basic block, - * consider it to be an external function. + + /* If the function is used and has no basic block, consider it to be an + * external function. */ rel_offset += PTR_SIZE; elf_write_jmprel(elf_relplt, rel_offset, dymsym_idx); @@ -978,6 +979,7 @@ void elf_generate_dynamic_sections(void) func_plt_ofs += PLT_ENT_SIZE; } + /* .dynsym begins where .dynstr ends, and its entries are read as aligned * words: 24 bytes each under ELF64, which wants 8. Four-byte alignment * would leave a string table ending 4 bytes off an 8-byte boundary, and @@ -1002,9 +1004,8 @@ void elf_generate_dynamic_sections(void) switch (ELF_MACHINE) { case ELF_MACHINE_ARM32: case ELF_MACHINE_X86_64: - /* GOT[0] holds the address of .dynamic. The GOT is still being - * built, so its final size comes from got_size rather than the - * buffer. + /* GOT[0] holds the address of .dynamic. The GOT is still being built, + * so its final size comes from got_size rather than the buffer. */ elf_write_got_slot(dynamic_sections.elf_got, dynamic_sections.elf_got_start + @@ -1024,9 +1025,10 @@ void elf_generate_dynamic_sections(void) i += PTR_SIZE) { int slot = dynamic_sections.elf_plt_start; if (ELF_MACHINE == ELF_MACHINE_X86_64) + /* x86-64 reaches the resolver through the push in its own PLT - * entry, which supplies the relocation index, rather than - * jumping straight to PLT[0]. + * entry, which supplies the relocation index, rather than jumping + * straight to PLT[0]. */ slot = dynamic_sections.elf_plt_start + PLT_FIXUP_SIZE + got_idx * PLT_ENT_SIZE + 6; @@ -1073,8 +1075,8 @@ void elf_generate_dynamic_sections(void) elf_write_dyn(dynamic_sections.elf_dynamic, 0x1, 0x1); #if DYN_BIND_NOW == 1 /* Resolve every PLT entry at load time. This target's PLT[0] does not - * arrange the GOT[1]/GOT[2] hand-off the lazy resolver needs, so the - * loader writes the final addresses straight into the GOT instead. + * arrange the GOT[1]/GOT[2] hand-off the lazy resolver needs, so the loader + * writes the final addresses straight into the GOT instead. */ elf_write_dyn(dynamic_sections.elf_dynamic, 0x18, 0x0); /* DT_BIND_NOW */ elf_write_dyn(dynamic_sections.elf_dynamic, 0x1e, 0x8); /* DF_BIND_NOW */ @@ -1097,7 +1099,7 @@ void elf_reset_dynamic_sections(void) void elf_generate_sections(void) { - strbuf_t *elf_relplt = elf_relplt_buf(); + const strbuf_t *elf_relplt = elf_relplt_buf(); if (!elf_shstrtab || (dynlink && (!dynamic_sections.elf_interp || !elf_relplt || @@ -1111,9 +1113,7 @@ void elf_generate_sections(void) if (dynlink) elf_generate_dynamic_sections(); - /* shstr section; len = 53 - * If using dynamic linking, len = 105. - */ + /* shstr section; len = 53 If using dynamic linking, len = 105. */ elf_write_byte(elf_shstrtab, 0); elf_write_str(elf_shstrtab, ".text"); elf_write_byte(elf_shstrtab, 0); @@ -1203,8 +1203,8 @@ void elf_preprocess(void) * - Common: * - The remaining entries correspond to all external functions. * - * Next, consider the case of __libc_start_main before initializing - * the sizes: + * Next, consider the case of __libc_start_main before initializing the + * sizes: * - .rel.plt (.rela.plt) has the one entry for __libc_start_main. * - .plt includes one fixup entry plus one entry for __libc_start_main. * - .got has RESERVED_GOT_NUM + 1 entries. @@ -1240,9 +1240,9 @@ void elf_preprocess(void) elf_data_start = elf_dynamic_start() + dynamic_sections.elf_dynamic->size; } else { - /* To prevent two load segments from sharing a common page, add - * PAGESIZE to elf_data_start, since the first section of the second - * load segment is .data in static linking mode. + /* To prevent two load segments from sharing a common page, add PAGESIZE + * to elf_data_start, since the first section of the second load segment + * is .data in static linking mode. */ elf_data_start = elf_rodata_start + elf_rodata->size + PAGESIZE; } @@ -1262,8 +1262,8 @@ void elf_postprocess(void) * * The image was written a byte at a time through fputc(), which costs a call * into the C library for every one of the several hundred thousand bytes of a - * self-compile -- and, once shecc is compiled by itself, a write(2) for each - * of them, because its own libc has no buffer behind fputc(). + * self-compile -- and, once shecc is compiled by itself, a write(2) for each of + * them, because its own libc has no buffer behind fputc(). * * That reasoning holds only where lib/c.c is the libc in the output. A host * compiler's runtime already buffers fwrite(), and so does the one a @@ -1271,13 +1271,13 @@ void elf_postprocess(void) * '__syscall' to call, since it is synthesized only for static linking. */ #ifdef HOST_BUFFERED_STDIO -void elf_write_all(FILE *fp, char *buf, int len) +void elf_write_all(FILE *fp, const char *buf, int len) { if (len > 0) fwrite(buf, 1, len, fp); } #else -void elf_write_all(FILE *fp, char *buf, int len) +void elf_write_all(FILE *fp, const char *buf, int len) { int off = 0; @@ -1354,8 +1354,8 @@ void elf_generate(const char *outfile) /* Other sections and section headers. * - * ELF64 output emits no section headers, so the symbol and string - * tables have nothing to reference and are left out of the image. + * ELF64 output emits no section headers, so the symbol and string tables + * have nothing to reference and are left out of the image. */ #if ELF_IS_64 == 0 elf_write_all(fp, elf_symtab->elements, elf_symtab->size); diff --git a/src/globals.c b/src/globals.c index 33d04064..c035b47c 100644 --- a/src/globals.c +++ b/src/globals.c @@ -1,8 +1,8 @@ /* * shecc - Self-Hosting and Educational C Compiler. * - * shecc is freely redistributable under the BSD 2 clause license. See the - * file "LICENSE" for information on usage and redistribution of this file. + * shecc is freely redistributable under the BSD 2 clause license. See the file + * "LICENSE" for information on usage and redistribution of this file. */ #pragma once @@ -54,7 +54,8 @@ arena_t *BLOCK_ARENA; arena_t *BB_ARENA; /* TOKEN_ARENA is responsible for token_t (including literal) / - * source_location_t allocation */ + * source_location_t allocation + */ arena_t *TOKEN_ARENA; /* GENERAL_ARENA is responsible for functions, symbols, constants, aliases, @@ -116,6 +117,7 @@ arena_block_t *arena_block_create(int capacity) if (!block) { printf("Failed to allocate memory for arena block structure\n"); + fflush(stdout); /* see fatal() */ abort(); } @@ -124,6 +126,7 @@ arena_block_t *arena_block_create(int capacity) if (!block->memory) { printf("Failed to allocate memory for arena block buffer\n"); free(block); + fflush(stdout); /* see fatal() */ abort(); } @@ -152,6 +155,7 @@ arena_t *arena_init(int initial_capacity) arena_t *arena = malloc(sizeof(arena_t)); if (!arena) { printf("Failed to allocate memory for arena structure\n"); + fflush(stdout); /* see fatal() */ abort(); } arena->head = arena_block_create(initial_capacity); @@ -161,8 +165,8 @@ arena_t *arena_init(int initial_capacity) return arena; } -/* Allocate memory from the given arena with given size. - * The arena may create a new arena block if no space is available. +/* Allocate memory from the given arena with given size. The arena may create a + * new arena block if no space is available. * @arena: The arena to allocate memory from. Must not be NULL. * @size: The size of memory to allocate. Must be positive. * @@ -173,6 +177,7 @@ void *arena_alloc(arena_t *arena, int size) { if (size <= 0) { printf("arena_alloc: size must be positive\n"); + fflush(stdout); /* see fatal() */ abort(); } @@ -182,7 +187,8 @@ void *arena_alloc(arena_t *arena, int size) if (!arena->head || arena->head->offset + size > arena->head->capacity) { /* Need a new block: choose capacity = max(DEFAULT_ARENA_SIZE, - * arena->block_size, size) */ + * arena->block_size, size) + */ const int base = (arena->block_size > DEFAULT_ARENA_SIZE ? arena->block_size : DEFAULT_ARENA_SIZE); @@ -200,8 +206,8 @@ void *arena_alloc(arena_t *arena, int size) /* arena_alloc() plus explicit zero‑initialization. * @arena: The arena to allocate memory from. Must not be NULL. - * @n: Number of elements. - * @size: Size of each element in bytes. + * @n: Number of elements. + * @size: Size of each element in bytes. * * Internally calls arena_alloc(n * size) and then fills the entire region with * zero bytes. @@ -216,6 +222,7 @@ void *arena_calloc(arena_t *arena, int n, int size) */ if (n <= 0 || size <= 0 || n > 0x7fffffff / size) { printf("arena_calloc: invalid allocation size\n"); + fflush(stdout); /* see fatal() */ abort(); } @@ -250,12 +257,14 @@ void *arena_realloc(arena_t *arena, char *oldptr, int oldsz, int newsz) if (!oldptr) { if (oldsz != 0) { printf("arena_realloc: oldptr == NULL requires oldsz == 0\n"); + fflush(stdout); /* see fatal() */ abort(); } return arena_alloc(arena, newsz); } if (oldsz == 0) { printf("arena_realloc: oldptr != NULL requires oldsz > 0\n"); + fflush(stdout); /* see fatal() */ abort(); } @@ -267,7 +276,7 @@ void *arena_realloc(arena_t *arena, char *oldptr, int oldsz, int newsz) /* From here on, oldptr != NULL and newsz > oldsz and oldsz != 0 */ int delta = newsz - oldsz; arena_block_t *blk = arena->head; - char *block_end = blk->memory + blk->offset; + const char *block_end = blk->memory + blk->offset; /* grow in place if oldptr is the last allocation in the current block */ if (oldptr + oldsz == block_end && blk->offset + delta <= blk->capacity) { @@ -288,7 +297,7 @@ void *arena_realloc(arena_t *arena, char *oldptr, int oldsz, int newsz) * * Return: Pointer to the duplicated string stored in the arena. */ -char *arena_strdup(arena_t *arena, char *str) +char *arena_strdup(arena_t *arena, const char *str) { const int n = strlen(str); char *dup = arena_alloc(arena, n + 1); @@ -337,10 +346,9 @@ void arena_free(arena_t *arena) free(arena); } -/* Hash a string with FNV-1a hash function - * and converts into usable hashmap index. The range of returned - * hashmap index is ranged from "(0 ~ 2,147,483,647) mod size" due to - * lack of unsigned integer implementation. +/* Hash a string with FNV-1a hash function and converts into usable hashmap + * index. The range of returned hashmap index is ranged from "(0 ~ + * 2,147,483,647) mod size" due to lack of unsigned integer implementation. * @size: The size of map. Must not be negative or 0. * @key: The key string. May be NULL. * @@ -374,10 +382,9 @@ int round_up_pow2(int v) return v; } -/* Create a hashmap on heap. Notice that provided size will always be rounded - * up to nearest power of 2. - * @size: The initial bucket size of hashmap. Must not be 0 or - * negative. +/* Create a hashmap on heap. Notice that provided size will always be rounded up + * to nearest power of 2. + * @size: The initial bucket size of hashmap. Must not be 0 or negative. * * Return: The pointer of created hashmap. */ @@ -436,6 +443,7 @@ void hashmap_rehash(hashmap_t *map) index = (index + 1) & (map->cap - 1); if (index == start) { printf("Error: New table is full during rehash\n"); + fflush(stdout); /* see fatal() */ abort(); } } @@ -449,9 +457,8 @@ void hashmap_rehash(hashmap_t *map) free(old_table); } -/* Put a key-value pair into given hashmap. - * If key already contains a value, then replace it with new value, the old - * value will be freed. +/* Put a key-value pair into given hashmap. If key already contains a value, + * then replace it with new value, the old value will be freed. * @map: The hashmap to be put into. Must not be NULL. * @key: The key string. May be NULL. * @val: The value pointer. May be NULL. This value's lifetime is held by @@ -478,6 +485,7 @@ void hashmap_put(hashmap_t *map, char *key, void *val) index = (index + 1) & (map->cap - 1); if (index == start) { printf("Error: Hashmap is full\n"); + fflush(stdout); /* see fatal() */ abort(); } } @@ -561,7 +569,7 @@ void hashmap_free(hashmap_t *map) * * Return: The pointer to the type, or NULL if not found. */ -type_t *find_type(char *type_name, int flag) +type_t *find_type(const char *type_name, int flag) { char head = type_name[0]; @@ -594,6 +602,7 @@ ph2_ir_t *add_existed_ph2_ir(ph2_ir_t *ph2_ir) { if (ph2_ir_idx >= MAX_IR_INSTR) { printf("Error: too many phase-2 IR instructions\n"); + fflush(stdout); /* see fatal() */ abort(); } PH2_IR_FLATTEN[ph2_ir_idx++] = ph2_ir; @@ -609,6 +618,7 @@ ph2_ir_t *add_ph2_ir(opcode_t op) ph2_ir->is_branch_detached = 0; ph2_ir->src0 = 0; ph2_ir->src1 = 0; + /* Only a select names a third source, but the allocation is not zeroed and * every field is set here by hand. */ @@ -619,6 +629,7 @@ ph2_ir_t *add_ph2_ir(opcode_t op) ph2_ir->then_bb = NULL; ph2_ir->else_bb = NULL; ph2_ir->ofs_based_on_stack_top = false; + /* Default to the full slot. Slots are PTR_SIZE wide, so a wide access is * always valid; only an address-taken narrow scalar may be written behind * the allocator's back, and reg-alloc narrows those explicitly. @@ -814,7 +825,7 @@ int unescape_string(const char *input, char *output, int output_size) return j; } -int parse_numeric_constant(char *buffer) +int parse_numeric_constant(const char *buffer) { int i = 0; int value = 0; @@ -853,15 +864,15 @@ int parse_numeric_constant(char *buffer) /* Give @type its field table, on the first field it is asked for. * - * The table is MAX_FIELDS var_t by value, and it has to stay put: a struct - * body hands out a var_t * per declarator and reads it again after the next + * The table is MAX_FIELDS var_t by value, and it has to stay put: a struct body + * hands out a var_t * per declarator and reads it again after the next * declarator has been added, so a table that grew by reallocating would leave * those pointers behind. Allocating it once at full size keeps them valid. * * What it need not do is allocate for a type that never has a field. Most of * what add_type() creates -- every enum, every typedef of a scalar, every - * builtin -- has none, and was paying for the whole table and for the loop - * that walked it. + * builtin -- has none, and was paying for the whole table and for the loop that + * walked it. */ void type_ensure_fields(type_t *type) { @@ -869,6 +880,7 @@ void type_ensure_fields(type_t *type) return; type->fields = arena_calloc(GENERAL_ARENA, MAX_FIELDS, sizeof(var_t)); + /* The field variables come out of a zeroed allocation, so give their * interned name pointers the empty string a reader can dereference. */ @@ -880,6 +892,7 @@ type_t *add_type(void) { if (types_idx >= MAX_TYPES) { printf("Error: Maximum number of types (%d) exceeded\n", MAX_TYPES); + fflush(stdout); /* see fatal() */ abort(); } type_t *t = &TYPES[types_idx++]; @@ -893,8 +906,8 @@ type_t *add_type(void) * so a longer tag would run past it into the fields that follow. Refuse it * rather than corrupting the type. */ -void fatal(char *msg); -void usage_error(char *msg); +__noreturn void fatal(const char *msg); +__noreturn void usage_error(const char *msg); void set_type_name(type_t *type, char *name) { @@ -930,7 +943,7 @@ constant_t *find_constant(char alias[]) return hashmap_get(CONSTANTS_MAP, alias); } -var_t *find_member(char token[], type_t *type) +var_t *find_member(const char token[], type_t *type) { /* If it is a forwardly declared alias of a structure, switch to the base * structure type. A scalar -- or "void", whose size is also 0 -- has no @@ -960,7 +973,7 @@ var_t *find_member(char token[], type_t *type) * -- before making the call. Names are never empty, so reading the first byte * of either side is always in bounds. */ -var_t *find_local_var(char *token, block_t *block) +var_t *find_local_var(const char *token, block_t *block) { func_t *func = block->func; char head = token[0]; @@ -988,7 +1001,7 @@ var_t *find_local_var(char *token, block_t *block) return NULL; } -var_t *find_global_var(char *token) +var_t *find_global_var(const char *token) { var_list_t *var_list = &GLOBAL_BLOCK->locals; char head = token[0]; @@ -1015,8 +1028,8 @@ int size_var(var_t *var) { int size; if (var->ptr_level > 0 || var->is_func) { - /* Pointers and function pointers occupy a target pointer, which is - * 8 bytes on LP64 targets and 4 on the 32-bit ones. + /* Pointers and function pointers occupy a target pointer, which is 8 + * bytes on LP64 targets and 4 on the 32-bit ones. */ size = PTR_SIZE; } else { @@ -1057,11 +1070,11 @@ func_t *add_func(char *func_name, bool synthesize) func->param_defs[i].var_name = ""; /* Use interned string for function name */ func->return_def.var_name = intern_string(func_name); + /* Prepare space for function arguments. * - * For Arm architecture, the first four arguments (arg1 ~ arg4) are - * passed to r0 ~ r3, and any additional arguments (arg5+) are passed - * to the stack. + * For Arm architecture, the first four arguments (arg1 ~ arg4) are passed + * to r0 ~ r3, and any additional arguments (arg5+) are passed to the stack. * * +-------------+ * | local vars | @@ -1077,8 +1090,8 @@ func_t *add_func(char *func_name, bool synthesize) * | arg 5 | * +-------------+ <-- sp * - * If the target architecture is RISC-V, arg1 ~ arg8 are passed to - * registers and arg9+ are passed to the stack. + * If the target architecture is RISC-V, arg1 ~ arg8 are passed to registers + * and arg9+ are passed to the stack. * * We reserve one slot per stack-passed argument at the bottom of every * frame so that each function can use the space to pass extra arguments. @@ -1124,16 +1137,17 @@ basic_block_t *bb_create(block_t *parent) /* Initialize non-zero fields */ bb->scope = parent; bb->belong_to = parent->func; - /* -1 marks "no machine code emitted for this block yet". Backends assign - * a real offset as they emit; 0 is a legitimate offset, so it cannot - * double as the sentinel. + + /* -1 marks "no machine code emitted for this block yet". Backends assign a + * real offset as they emit; 0 is a legitimate offset, so it cannot double + * as the sentinel. */ bb->elf_offset = -1; if (dump_ir || dump_dot) { - /* MAX_VAR_LEN spent 128 bytes on a string that is always ".label." - * plus an int. A self-compile calls bb_create() 52k times, so that - * was 6.4 MiB of arena where 1.2 MiB does. + /* MAX_VAR_LEN spent 128 bytes on a string that is always ".label." plus + * an int. A self-compile calls bb_create() 52k times, so that was 6.4 + * MiB of arena where 1.2 MiB does. */ bb->bb_label_name = arena_alloc(GENERAL_ARENA, MAX_LABEL_LEN); snprintf(bb->bb_label_name, MAX_LABEL_LEN, ".label.%d", bb_label_idx++); @@ -1313,7 +1327,7 @@ void bb_disconnect(basic_block_t *pred, basic_block_t *succ) * prev[], so prev_idx is only a high-water mark and the entries must be counted * rather than trusted. */ -int bb_pred_count(basic_block_t *bb) +int bb_pred_count(const basic_block_t *bb) { int n = 0; @@ -1370,6 +1384,7 @@ void add_insn(block_t *block, n->rd = rd; n->rs1 = rs1; n->rs2 = rs2; + /* Only a select names a third source. The allocation is not zeroed and * every field is set here by hand, so this one has to be too. */ @@ -1483,8 +1498,8 @@ void strbuf_free(strbuf_t *src) free(src); } -/* This routine is required because the global variable initializations are - * not supported now. +/* This routine is required because the global variable initializations are not + * supported now. */ void global_init(void) { @@ -1559,18 +1574,17 @@ void global_init(void) /* Forward declaration for lexer cleanup */ void lexer_cleanup(void); -/* Free empty trailing blocks from an arena safely. - * This only frees blocks that come after the last used block, - * ensuring no pointers are invalidated. +/* Free empty trailing blocks from an arena safely. This only frees blocks that + * come after the last used block, ensuring no pointers are invalidated. * * NOTE: measured over a self-compile, this reclaims nothing. arena_alloc() * prepends each new block at the head, so the list runs newest-to-oldest and * every block behind the head is full by construction: last_used is always the * tail and there is never anything after it to free. The only case that ever * fires is an arena whose very first allocation was larger than its initial - * block, leaving that block at offset 0 behind a newer one. Reclaiming a - * bump allocator's memory needs a phase boundary that can drop a whole arena - * -- see release_token_arena() -- not a scan for empty blocks. + * block, leaving that block at offset 0 behind a newer one. Reclaiming a bump + * allocator's memory needs a phase boundary that can drop a whole arena -- see + * release_token_arena() -- not a scan for empty blocks. * * @arena: The arena to compact. * Return: Bytes freed. @@ -1616,9 +1630,9 @@ int arena_free_trailing_blocks(arena_t *arena) * Every token, macro, hide set and conditional-inclusion record lives in * TOKEN_ARENA, and nothing survives parsing: identifiers and string literals * reach the parser through intern_string(), which copies into GENERAL_ARENA, - * and every parser entry point copies a token's text into a local buffer - * before storing it. So once parse() returns, all 17 MiB of it is garbage that - * would otherwise stay resident through the memory peak in reg_alloc(). + * and every parser entry point copies a token's text into a local buffer before + * storing it. So once parse() returns, all 17 MiB of it is garbage that would + * otherwise stay resident through the memory peak in reg_alloc(). * * The source buffers in SRC_FILE_MAP exist only to quote a line in a parse * error, so they go at the same time. @@ -1649,8 +1663,8 @@ void release_token_arena(void) } } -/* Compact all arenas to reduce memory usage after compilation phases. - * This safely frees only trailing empty blocks without invalidating pointers. +/* Compact all arenas to reduce memory usage after compilation phases. This + * safely frees only trailing empty blocks without invalidating pointers. * * Return: Total bytes freed across all arenas. */ @@ -1668,8 +1682,8 @@ int compact_all_arenas(void) return total_saved; } -/* Compact specific arenas based on compilation phase. - * Different phases have different memory usage patterns. +/* Compact specific arenas based on compilation phase. Different phases have + * different memory usage patterns. * * @phase_mask: Bitmask using COMPACT_ARENA_* defines * to indicate which arenas to compact. @@ -1744,12 +1758,17 @@ void global_release(void) strbuf_free(dynamic_sections.elf_got); } -/* Reports an error without specifying a position */ -void fatal(char *msg) +/* Reports a broken invariant, which has no position in the source to point at + * because nothing in the source is necessarily wrong. This one abort()s: a core + * dump is what makes an internal failure debuggable. A mistake in the input + * belongs in error_at(), and a mistake on the command line in usage_error(). + */ +__noreturn void fatal(const char *msg) { printf("[Error]: %s\n", msg); - /* abort() does not flush, so a diagnostic written to a pipe -- a build - * log, or any invocation whose output is captured -- is discarded and the + + /* abort() does not flush, so a diagnostic written to a pipe -- a build log, + * or any invocation whose output is captured -- is discarded and the * compiler appears to die silently. */ fflush(stdout); @@ -1760,32 +1779,46 @@ void fatal(char *msg) * a broken invariant, so this exits rather than abort()ing: no core dump, and * no "Aborted" line, for an ordinary typo. */ -void usage_error(char *msg) +__noreturn void usage_error(const char *msg) { printf("[Error]: %s\n", msg); fflush(stdout); exit(1); } -/* Reports error and prints occurred position context, - * if the given location is NULL or source file is missing, - * then fallbacks to fatal(char *). +/* Reports a mistake in the input, quoting the line it sits on. A program the + * compiler refuses is not a broken invariant, so this exits the way + * usage_error() does rather than abort()ing: an ordinary syntax error should + * not raise SIGABRT, wake the system crash handler, or leave a core behind. + * + * Falls back to the same message without context when the location is NULL or + * the source file is no longer on hand. */ -void error_at(char *msg, source_location_t *loc) +__noreturn void error_at(char *msg, source_location_t *loc) { int offset, start_idx, i = 0, len, pos; char diagnostic[MAX_LINE_LEN]; - if (!loc) - fatal(msg); + if (!loc) { + printf("[Error]: %s\n", msg); + fflush(stdout); + exit(1); + } len = loc->len; pos = loc->pos; strbuf_t *src = hashmap_get(SRC_FILE_MAP, loc->filename); - if (!src) - fatal(msg); + /* The source text is no longer on hand, which changes what can be shown and + * not what went wrong: still a mistake in the input, so still an exit + * rather than the core dump fatal() would take. + */ + if (!src) { + printf("[Error]: %s\n", msg); + fflush(stdout); + exit(1); + } if (len < 1) len = 1; @@ -1816,7 +1849,7 @@ void error_at(char *msg, source_location_t *loc) printf("%6c | ", ' '); /* Keep room for the note appended after the underline. */ - char *note = " Error occurs here"; + const char *note = " Error occurs here"; int limit = MAX_LINE_LEN - strlen(note) - 1; i = 0; @@ -1829,8 +1862,8 @@ void error_at(char *msg, source_location_t *loc) strcpy(diagnostic + i, note); printf("%s\n", diagnostic); - fflush(stdout); /* see fatal(): abort() discards buffered output */ - abort(); + fflush(stdout); /* exit() flushes, but say so once rather than rely on it */ + exit(1); } void print_indent(int indent) @@ -1839,11 +1872,13 @@ void print_indent(int indent) printf("\t"); } -void dump_bb_insn(func_t *func, basic_block_t *bb, bool *at_func_start) +void dump_bb_insn(const func_t *func, + const basic_block_t *bb, + bool *at_func_start) { if (!bb) return; - var_t *rd, *rs1, *rs2; + const var_t *rd, *rs1, *rs2; if (bb != func->bbs && bb->insn_list.head) { if (!at_func_start[0]) @@ -2108,7 +2143,7 @@ void dump_insn(void) /* Handle implicit return */ for (int i = 0; func->exit && i < func->exit->prev_idx; i++) { - basic_block_t *bb = func->exit->prev[i].bb; + const basic_block_t *bb = func->exit->prev[i].bb; if (!bb) continue; diff --git a/src/lexer.c b/src/lexer.c index c90f2437..562c0a48 100644 --- a/src/lexer.c +++ b/src/lexer.c @@ -1,8 +1,8 @@ /* * shecc - Self-Hosting and Educational C Compiler. * - * shecc is freely redistributable under the BSD 2 clause license. See the - * file "LICENSE" for information on usage and redistribution of this file. + * shecc is freely redistributable under the BSD 2 clause license. See the file + * "LICENSE" for information on usage and redistribution of this file. */ #include #include @@ -28,7 +28,7 @@ hashmap_t *KEYWORD_MAP = NULL; token_kind_t *directive_tokens_storage = NULL; token_kind_t *keyword_tokens_storage = NULL; -void lex_init_directives() +void lex_init_directives(void) { if (DIRECTIVE_MAP) return; @@ -57,7 +57,7 @@ void lex_init_directives() } } -void lex_init_keywords() +void lex_init_keywords(void) { if (KEYWORD_MAP) return; @@ -125,7 +125,7 @@ token_kind_t lookup_keyword(char *token) /* Cleanup function for lexer hashmaps */ -void lexer_cleanup() +void lexer_cleanup(void) { if (DIRECTIVE_MAP) { hashmap_free(DIRECTIVE_MAP); @@ -138,8 +138,8 @@ void lexer_cleanup() } /* Token storage arrays are allocated from GENERAL_ARENA and will be - * automatically freed when the arena is freed in global_release(). - * No need to explicitly free them here. + * automatically freed when the arena is freed in global_release(). No need + * to explicitly free them here. */ directive_tokens_storage = NULL; keyword_tokens_storage = NULL; @@ -189,7 +189,7 @@ int file_read_all(FILE *f, char *dst, int len) } #endif -strbuf_t *read_file(char *filename) +strbuf_t *read_file(const char *filename) { FILE *f = fopen(filename, "rb"); strbuf_t *src; @@ -225,7 +225,7 @@ strbuf_t *get_file_buf(char *filename) return buf; } -token_t *new_token(token_kind_t kind, source_location_t *loc, int len) +token_t *new_token(token_kind_t kind, const source_location_t *loc, int len) { /* Every field is written here, so the allocation does not need zeroing * first -- and tokens are the single largest source of allocations in the @@ -240,12 +240,20 @@ token_t *new_token(token_kind_t kind, source_location_t *loc, int len) return token; } -token_t *lex_token(strbuf_t *buf, source_location_t *loc) +/* Skipping a comment or a run of whitespace resumes the scan, and lex_layout() + * below does that by starting a fresh token here. + */ +token_t *lex_token(strbuf_t *buf, source_location_t *loc); + +/* Preprocessor directives, comments, and the whitespace between tokens. + * + * Returns NULL when 'ch' is none of its business, so that lex_token() can offer + * the character to the next reader in line. + */ +token_t *lex_layout(strbuf_t *buf, source_location_t *loc, char ch) { token_t *token; - char token_buffer[MAX_TOKEN_LEN], ch = peek_char(buf, 0); - - loc->pos = buf->size; + char token_buffer[MAX_TOKEN_LEN]; if (ch == '#') { /* Inside a macro replacement list '#' stringifies the parameter that @@ -392,6 +400,19 @@ token_t *lex_token(strbuf_t *buf, source_location_t *loc) return token; } + return NULL; +} + +/* Integer literals, in every base the language accepts. + * + * Returns NULL when 'ch' is none of its business, so that lex_token() can offer + * the character to the next reader in line. + */ +token_t *lex_number(strbuf_t *buf, source_location_t *loc, char ch) +{ + token_t *token; + char token_buffer[MAX_TOKEN_LEN]; + if (isdigit(ch)) { int sz = 0; token_buffer[sz++] = ch; @@ -479,6 +500,97 @@ token_t *lex_token(strbuf_t *buf, source_location_t *loc) return token; } + return NULL; +} + +/* String and character literals. + * + * Returns NULL when 'ch' is none of its business, so that lex_token() can offer + * the character to the next reader in line. + */ +token_t *lex_literal(strbuf_t *buf, source_location_t *loc, char ch) +{ + token_t *token; + char token_buffer[MAX_TOKEN_LEN]; + + if (ch == '"') { + int sz = 0; + bool special = false; + + ch = read_char(buf); + while (ch != '"' || special) { + if (sz >= MAX_TOKEN_LEN - 1) { + loc->len = sz + 1; + error_at("String literal too long", loc); + } + token_buffer[sz++] = ch; + + if (ch == '\\') + special = true; + else + special = false; + + ch = read_char(buf); + } + token_buffer[sz] = '\0'; + + read_char(buf); + token = new_token(T_string, loc, sz + 2); + token->literal = intern_string(token_buffer); + loc->column += sz + 2; + return token; + } + + if (ch == '\'') { + int sz = 0; + bool escaped = false; + + ch = read_char(buf); + if (ch == '\\') { + token_buffer[sz++] = ch; + ch = read_char(buf); + + do { + if (sz >= MAX_TOKEN_LEN - 1) { + loc->len = sz + 1; + error_at("Character literal too long", loc); + } + token_buffer[sz++] = ch; + ch = read_char(buf); + escaped = true; + } while (ch && ch != '\''); + } else { + token_buffer[sz++] = ch; + } + token_buffer[sz] = '\0'; + + if (!escaped) + ch = read_char(buf); + + if (ch != '\'') { + loc->len = 2; + error_at("Unenclosed character literal", loc); + } + + read_char(buf); + token = new_token(T_char, loc, sz + 2); + token->literal = intern_string(token_buffer); + loc->column += sz + 2; + return token; + } + + return NULL; +} + +/* Punctuation that is never the start of a longer token. + * + * Returns NULL when 'ch' is none of its business, so that lex_token() can offer + * the character to the next reader in line. + */ +token_t *lex_punct(strbuf_t *buf, source_location_t *loc, char ch) +{ + token_t *token; + if (ch == '(') { ch = read_char(buf); token = new_token(T_open_bracket, loc, 1); @@ -528,91 +640,58 @@ token_t *lex_token(strbuf_t *buf, source_location_t *loc) return token; } - if (ch == '^') { + if (ch == '~') { ch = read_char(buf); - - if (ch == '=') { - ch = read_char(buf); - token = new_token(T_xoreq, loc, 2); - loc->column += 2; - return token; - } - - token = new_token(T_bit_xor, loc, 1); + token = new_token(T_bit_not, loc, 1); loc->column++; return token; } - if (ch == '~') { - ch = read_char(buf); - token = new_token(T_bit_not, loc, 1); + if (ch == ';') { + read_char(buf); + token = new_token(T_semicolon, loc, 1); loc->column++; return token; } - if (ch == '"') { - int sz = 0; - bool special = false; - - ch = read_char(buf); - while (ch != '"' || special) { - if (sz >= MAX_TOKEN_LEN - 1) { - loc->len = sz + 1; - error_at("String literal too long", loc); - } - token_buffer[sz++] = ch; - - if (ch == '\\') - special = true; - else - special = false; - - ch = read_char(buf); - } - token_buffer[sz] = '\0'; + if (ch == '?') { + read_char(buf); + token = new_token(T_question, loc, 1); + loc->column++; + return token; + } + if (ch == ':') { read_char(buf); - token = new_token(T_string, loc, sz + 2); - token->literal = intern_string(token_buffer); - loc->column += sz + 2; + token = new_token(T_colon, loc, 1); + loc->column++; return token; } - if (ch == '\'') { - int sz = 0; - bool escaped = false; + return NULL; +} - ch = read_char(buf); - if (ch == '\\') { - token_buffer[sz++] = ch; - ch = read_char(buf); +/* Operators, each of which may or may not continue into a longer one. + * + * Returns NULL when 'ch' is none of its business, so that lex_token() can offer + * the character to the next reader in line. + */ +token_t *lex_operator(strbuf_t *buf, source_location_t *loc, char ch) +{ + token_t *token; - do { - if (sz >= MAX_TOKEN_LEN - 1) { - loc->len = sz + 1; - error_at("Character literal too long", loc); - } - token_buffer[sz++] = ch; - ch = read_char(buf); - escaped = true; - } while (ch && ch != '\''); - } else { - token_buffer[sz++] = ch; - } - token_buffer[sz] = '\0'; + if (ch == '^') { + ch = read_char(buf); - if (!escaped) + if (ch == '=') { ch = read_char(buf); - - if (ch != '\'') { - loc->len = 2; - error_at("Unenclosed character literal", loc); + token = new_token(T_xoreq, loc, 2); + loc->column += 2; + return token; } - read_char(buf); - token = new_token(T_char, loc, sz + 2); - token->literal = intern_string(token_buffer); - loc->column += sz + 2; + token = new_token(T_bit_xor, loc, 1); + loc->column++; return token; } @@ -831,27 +910,6 @@ token_t *lex_token(strbuf_t *buf, source_location_t *loc) return token; } - if (ch == ';') { - read_char(buf); - token = new_token(T_semicolon, loc, 1); - loc->column++; - return token; - } - - if (ch == '?') { - read_char(buf); - token = new_token(T_question, loc, 1); - loc->column++; - return token; - } - - if (ch == ':') { - read_char(buf); - token = new_token(T_colon, loc, 1); - loc->column++; - return token; - } - if (ch == '=') { ch = read_char(buf); @@ -867,15 +925,27 @@ token_t *lex_token(strbuf_t *buf, source_location_t *loc) return token; } + return NULL; +} + +/* Identifiers, and the keywords spelled like them. + * + * Returns NULL when 'ch' is none of its business, so that lex_token() can offer + * the character to the next reader in line. + */ +token_t *lex_word(strbuf_t *buf, source_location_t *loc, char ch) +{ + token_t *token; + char token_buffer[MAX_TOKEN_LEN]; + if (isalnum(ch) || ch == '_') { int sz = 0; do { - /* Bounded by the smallest buffer an identifier is ever copied - * into, not by the token buffer's own size: lex_ident() and - * lex_peek() strcpy into caller arrays of MAX_ID_LEN, so a longer - * name would run off the end of one. Diagnosing it here is what - * keeps a long identifier in the input from corrupting the - * compiler's stack. + /* Bounded by the smallest buffer an identifier is ever copied into, + * not by the token buffer's own size: lex_ident() and lex_peek() + * strcpy into caller arrays of MAX_ID_LEN, so a longer name would + * run off the end of one. Diagnosing it here is what keeps a long + * identifier in the input from corrupting the compiler's stack. */ if (sz >= MAX_ID_LEN - 1) { loc->len = sz; @@ -962,10 +1032,10 @@ token_t *lex_token(strbuf_t *buf, source_location_t *loc) break; } - /* Fall back to the hashmap for anything the switch does not name. - * No keyword is shorter than two characters or longer than eight, so a - * name outside that range cannot be one and needs no lookup -- which - * is most of the identifiers in a real program. + /* Fall back to the hashmap for anything the switch does not name. No + * keyword is shorter than two characters or longer than eight, so a + * name outside that range cannot be one and needs no lookup -- which is + * most of the identifiers in a real program. */ if (kind == T_identifier && sz >= 2 && sz <= 8) kind = lookup_keyword(token_buffer); @@ -976,6 +1046,40 @@ token_t *lex_token(strbuf_t *buf, source_location_t *loc) return token; } + return NULL; +} + +/* Reads one token, dispatching on the character it starts with. Each reader + * above claims the characters it knows and returns NULL for the rest; the order + * of the calls matters only between lex_number() and lex_word(), which would + * otherwise both claim a leading digit. + */ +token_t *lex_token(strbuf_t *buf, source_location_t *loc) +{ + token_t *token; + char ch = peek_char(buf, 0); + + loc->pos = buf->size; + + token = lex_layout(buf, loc, ch); + if (token) + return token; + token = lex_number(buf, loc, ch); + if (token) + return token; + token = lex_literal(buf, loc, ch); + if (token) + return token; + token = lex_punct(buf, loc, ch); + if (token) + return token; + token = lex_operator(buf, loc, ch); + if (token) + return token; + token = lex_word(buf, loc, ch); + if (token) + return token; + error_at("Unexpected token", loc); return NULL; } @@ -988,10 +1092,10 @@ token_stream_t *gen_file_token_stream(char *filename) token_t head; token_t *cur = &head; token_stream_t *tks; - /* initialie source location with the following configuration: - * pos is at 0, - * len is 1 for reporting convenience, - * and the column and line number are set to 1. + + /* initialie source location with the following configuration: pos is at 0, + * len is 1 for reporting convenience, and the column and line number are + * set to 1. */ source_location_t loc = {0, 1, 1, 1, filename}; strbuf_t *buf; @@ -1033,10 +1137,10 @@ token_stream_t *gen_file_token_stream(char *filename) return tks; } -token_stream_t *gen_libc_token_stream() +token_stream_t *gen_libc_token_stream(void) { token_t head; - token_t *cur = &head, *tk; + token_t *cur = &head, *tk = NULL; token_stream_t *tks; char *filename = dynlink ? "lib/c.h" : "lib/c.c"; strbuf_t *buf = LIBC_SRC; @@ -1051,10 +1155,10 @@ token_stream_t *gen_libc_token_stream() hashmap_put(SRC_FILE_MAP, filename, LIBC_SRC); /* This buffer was built by appending, so its capacity is whatever the - * doubling left and runs past the text into memory that was never - * written -- while the scan below, like the one over a file, stops at - * capacity. Terminate it the way read_file() leaves a file: the text, a - * NUL, and capacity naming one past the text. Without this the lexer reads + * doubling left and runs past the text into memory that was never written + * -- while the scan below, like the one over a file, stops at capacity. + * Terminate it the way read_file() leaves a file: the text, a NUL, and + * capacity naming one past the text. Without this the lexer reads * uninitialised bytes, and what it finds there depends on the allocator, * which is enough to make the compiler emit different code from one build * to the next. @@ -1069,9 +1173,8 @@ token_stream_t *gen_libc_token_stream() while (buf->size < buf->capacity) { tk = lex_token(buf, &loc); - /* Early break to discard eof token, so later - * we can concat libc token stream with actual - * input file's token stream. + /* Early break to discard eof token, so later we can concat libc token + * stream with actual input file's token stream. */ if (tk->kind == T_eof) break; @@ -1080,7 +1183,7 @@ token_stream_t *gen_libc_token_stream() cur = cur->next; } - if (!head.next) + if (!tk || !head.next) fatal("Unable to include libc"); if (tk->kind != T_eof) @@ -1095,7 +1198,7 @@ token_stream_t *gen_libc_token_stream() } /* Fetches current token's location. */ -source_location_t *cur_token_loc() +source_location_t *cur_token_loc(void) { return &cur_token->location; } @@ -1103,7 +1206,7 @@ source_location_t *cur_token_loc() /* Finds next token's location; if the current token is eof, returns the eof * token's location instead. */ -source_location_t *next_token_loc() +source_location_t *next_token_loc(void) { if (cur_token->kind == T_eof) return &cur_token->location; @@ -1204,8 +1307,7 @@ void lex_ident(token_kind_t token, char *value) error_at("Unexpected token", &tk->location); } -/* Strictly match next token with given token type. - */ +/* Strictly match next token with given token type. */ void lex_expect(token_kind_t token) { if (cur_token->next && cur_token->next->kind == token) { diff --git a/src/main.c b/src/main.c index 8fcf52d6..1ccd5134 100644 --- a/src/main.c +++ b/src/main.c @@ -1,8 +1,8 @@ /* * shecc - Self-Hosting and Educational C Compiler. * - * shecc is freely redistributable under the BSD 2 clause license. See the - * file "LICENSE" for information on usage and redistribution of this file. + * shecc is freely redistributable under the BSD 2 clause license. See the file + * "LICENSE" for information on usage and redistribution of this file. */ #include @@ -64,16 +64,16 @@ char *last_char(char *text, char needle) /* Derive lacc-style DOT output when the caller did not specify -o. */ char *dot_output_name(char *input) { - char *suffix = last_char(input, '.'); + const char *suffix = last_char(input, '.'); char *slash = last_char(input, '/'); - char *base = input; + const char *base = input; if (slash) base = slash + 1; - /* A dot only introduces a suffix when something in the same path - * component precedes it. That rules out a directory's dot ("dir.d/file") - * and a dotfile's leading one, which would reduce ".bashrc" to ".dot". + /* A dot only introduces a suffix when something in the same path component + * precedes it. That rules out a directory's dot ("dir.d/file") and a + * dotfile's leading one, which would reduce ".bashrc" to ".dot". */ if (suffix && suffix <= base) suffix = NULL; @@ -85,6 +85,7 @@ char *dot_output_name(char *input) if (suffix) base_len = suffix - input; + /* strlen, not sizeof: shecc types a string literal as a pointer, so * sizeof(".dot") is 1 once the compiler is compiling itself. */ @@ -102,7 +103,7 @@ int main(int argc, char *argv[]) { char *out = NULL; char *in = NULL; - token_stream_t *libc_token_stream, *token_stream; + token_stream_t *libc_token_stream = NULL, *token_stream; token_t *tk; for (int i = 1; i < argc; i++) { @@ -148,8 +149,8 @@ int main(int argc, char *argv[]) out = dot_output_name(in); /* The graph is written by truncating its output, so naming the input - * destroys the source. That happens both when -o names it outright and - * when an input already ending in .dot derives its own name. + * destroys the source. That happens both when -o names it outright and when + * an input already ending in .dot derives its own name. */ if (dump_dot && !strcmp(out, in)) usage_error("--dot would overwrite the input; name another output"); @@ -168,7 +169,7 @@ int main(int argc, char *argv[]) token_stream = gen_file_token_stream(in); /* concat libc's and input file's token stream */ - if (libc) { + if (libc_token_stream) { libc_token_stream->tail->next = token_stream->head; token_stream = libc_token_stream; } @@ -207,8 +208,8 @@ int main(int argc, char *argv[]) unwind_phi(); - /* Copy small helpers into their callers before anything else looks at - * them, so the optimizer sees one body rather than a call boundary. + /* Copy small helpers into their callers before anything else looks at them, + * so the optimizer sees one body rather than a call boundary. */ inline_calls(); @@ -227,9 +228,9 @@ int main(int argc, char *argv[]) /* Flatten unpredictable ifs into branchless selects. * * After the optimizer rather than inside ssa_build(): a select reads a - * third operand, and the passes in optimize() walk instructions two - * sources at a time. One of them would rewrite a copy feeding that third - * operand and leave the select reading a value nothing defines. + * third operand, and the passes in optimize() walk instructions two sources + * at a time. One of them would rewrite a copy feeding that third operand + * and leave the select reading a value nothing defines. */ if_convert(); @@ -275,8 +276,7 @@ int main(int argc, char *argv[]) if (dump_ir) dump_ph2_ir(); - /* - * ELF preprocess: + /* ELF preprocess: * 1. generate all sections except for .text section. * 2. calculate the starting addresses of certain sections. */ diff --git a/src/opt-sccp.c b/src/opt-sccp.c index bc1d7541..3421139b 100644 --- a/src/opt-sccp.c +++ b/src/opt-sccp.c @@ -1,8 +1,8 @@ /* * shecc - Self-Hosting and Educational C Compiler. * - * shecc is freely redistributable under the BSD 2 clause license. See the - * file "LICENSE" for information on usage and redistribution of this file. + * shecc is freely redistributable under the BSD 2 clause license. See the file + * "LICENSE" for information on usage and redistribution of this file. */ /* Constant cast optimization pass. @@ -14,8 +14,8 @@ /* Narrow a constant to 'size' bytes, keeping its sign. Every integer type in * this language is signed -- there is no 'unsigned' keyword -- and widening - * sign-extends, so masking alone would make "char c = -1" compare as 255. - * A size the caller does not narrow is returned unchanged. + * sign-extends, so masking alone would make "char c = -1" compare as 255. A + * size the caller does not narrow is returned unchanged. */ int sign_extend_const(int value, int size) { @@ -41,15 +41,12 @@ bool optimize_constant_casts(func_t *func) /* Simple peephole optimization: const + trunc pattern */ for (basic_block_t *bb = func->bbs; bb; bb = bb->rpo_next) { - if (!bb) - continue; - for (insn_t *insn = bb->insn_list.head; insn && insn->next; insn = insn->next) { insn_t *next_insn = insn->next; - /* Look for pattern: const %.tX, VALUE followed by - * %.tY = trunc %.tX, SIZE + /* Look for pattern: const %.tX, VALUE followed by %.tY = trunc + * %.tX, SIZE */ if (insn->opcode == OP_load_constant && next_insn->opcode == OP_trunc && insn->rd && next_insn->rs1 && diff --git a/src/parser.c b/src/parser.c index 34d12cd9..8a2062ee 100644 --- a/src/parser.c +++ b/src/parser.c @@ -1,8 +1,8 @@ /* * shecc - Self-Hosting and Educational C Compiler. * - * shecc is freely redistributable under the BSD 2 clause license. See the - * file "LICENSE" for information on usage and redistribution of this file. + * shecc is freely redistributable under the BSD 2 clause license. See the file + * "LICENSE" for information on usage and redistribution of this file. */ #include #include @@ -37,8 +37,8 @@ var_t *operand_stack[MAX_OPERAND_STACK_SIZE]; int operand_stack_idx = 0; /* Forward declarations */ -source_location_t *cur_token_loc(); -source_location_t *next_token_loc(); +source_location_t *cur_token_loc(void); +source_location_t *next_token_loc(void); basic_block_t *read_body_statement(block_t *parent, basic_block_t *bb); void perform_side_effect(block_t *parent, basic_block_t *bb); @@ -49,7 +49,7 @@ void parse_array_init(var_t *var, basic_block_t **bb, bool emit_code); -label_t *find_label(char *name) +label_t *find_label(const char *name) { for (int i = 0; i < label_idx; i++) { if (!strcmp(name, labels[i].label_name)) @@ -58,7 +58,7 @@ label_t *find_label(char *name) return NULL; } -void add_label(char *name, basic_block_t *bb) +void add_label(const char *name, basic_block_t *bb) { if (label_idx > MAX_LABELS - 1) error_at("Too many labels in function", cur_token_loc()); @@ -68,13 +68,13 @@ void add_label(char *name, basic_block_t *bb) l->bb = bb; } -/* Name for a compiler-generated temporary, interned so that the var_t only - * has to hold a pointer to it. +/* Name for a compiler-generated temporary, interned so that the var_t only has + * to hold a pointer to it. * - * This is the parser's most frequent call by a wide margin -- one per - * temporary value -- and sprintf() spends most of a call parsing a format - * string that never changes. Writing the fixed prefix and the decimal digits - * directly produces the same name for a fraction of the work. + * This is the parser's most frequent call by a wide margin -- one per temporary + * value -- and sprintf() spends most of a call parsing a format string that + * never changes. Writing the fixed prefix and the decimal digits directly + * produces the same name for a fraction of the work. */ char *gen_name(void) { @@ -111,6 +111,7 @@ var_t *require_var(block_t *blk) var_t *var = arena_calloc(BLOCK_ARENA, 1, sizeof(var_t)); var_list->elements[var_list->size++] = var; + /* var_name is a pointer now; every reader dereferences it unconditionally, * so an unnamed variable points at the empty string rather than NULL. */ @@ -138,10 +139,11 @@ var_t *require_typed_var(block_t *blk, type_t *type) return var; } -/* Function-address operands carry a function name, but are not declarations - * in the current scope. Keeping them out of the local lookup list lets - * find_var() distinguish a resolved function-pointer variable from a - * generated function symbol. */ +/* Function-address operands carry a function name, but are not declarations in + * the current scope. Keeping them out of the local lookup list lets find_var() + * distinguish a resolved function-pointer variable from a generated function + * symbol. + */ var_t *require_func_symbol_var(block_t *blk) { var_t *var = require_var(blk); @@ -341,11 +343,10 @@ var_t *promote_unchecked(block_t *block, { var_t *rd = require_typed_ptr_var(block, target_type, target_ptr); rd->var_name = gen_name(); - /* Encode both source and target sizes in src1: - * Lower 16 bits: target size - * Upper 16 bits: source size - * This allows codegen to distinguish between different promotion types - * without changing IR semantics. + + /* Encode both source and target sizes in src1: Lower 16 bits: target size + * Upper 16 bits: source size This allows codegen to distinguish between + * different promotion types without changing IR semantics. */ int encoded_size = ((var->type->size) << 16); if (target_ptr) @@ -406,10 +407,10 @@ var_t *resize_var(block_t *block, basic_block_t **bb, var_t *from, var_t *to) if (from_size < to_size) { /* Widening into a pointer needs no conversion instruction. Values - * already occupy a full register and integer loads sign-extend, so - * the pointer's bits are the value's bits. Emitting the conversion - * here also placed it ahead of the instructions computing its own - * operand, which produced a garbage pointer. + * already occupy a full register and integer loads sign-extend, so the + * pointer's bits are the value's bits. Emitting the conversion here + * also placed it ahead of the instructions computing its own operand, + * which produced a garbage pointer. * * On the 32-bit targets PTR_SIZE equals an int, so this case cannot * arise there and behaviour is unchanged. @@ -451,8 +452,8 @@ void read_parameter_list_decl(func_t *func, bool anon); /* Forward declaration for ternary handling used by initializers */ void read_ternary_operation(block_t *parent, basic_block_t **bb); -/* Parse array initializer to determine size for implicit arrays and - * optionally emit initialization code. +/* Parse array initializer to determine size for implicit arrays and optionally + * emit initialization code. */ var_t *compute_element_address(block_t *parent, basic_block_t **bb, @@ -477,7 +478,7 @@ var_t *compute_element_address(block_t *parent, var_t *compute_field_address(block_t *parent, basic_block_t **bb, var_t *struct_addr, - var_t *field) + const var_t *field) { if (field->offset == 0) return struct_addr; @@ -522,6 +523,7 @@ var_t *parse_global_constant_value(block_t *parent, basic_block_t **bb) add_insn(parent, *bb, OP_load_constant, val, NULL, NULL, 0, NULL); } else if (lex_peek(T_string, NULL)) { lex_accept(T_string); + /* TODO: String fields in structs not yet supported - requires proper * handling of string literals as initializers */ @@ -652,8 +654,8 @@ basic_block_t *handle_return_statement(block_t *parent, basic_block_t *bb) var_t *rs1 = opstack_pop(); - /* Handle array compound literals in return context. - * Convert array compound literals to their first element value. + /* Handle array compound literals in return context. Convert array compound + * literals to their first element value. */ if (rs1 && rs1->array_size > 0 && rs1->var_name[0] == '.') { var_t *val = require_var(parent); @@ -775,14 +777,9 @@ basic_block_t *handle_goto_statement(block_t *parent, basic_block_t *bb) * wrap the goto, and connect the unreachable basic block to the else * branch. Finally, return this else block. * - * after: - * a = b + c; - * goto label; - * c *= d; + * after: a = b + c; goto label; c *= d; * - * before: - * a = b + c; - * if (1) + * before: a = b + c; if (1) * goto label; * c *= d; */ @@ -833,10 +830,10 @@ void parse_array_init(var_t *var, bool is_implicit = (var->array_size == 0); /* Elements of a pointer array are pointer-sized. Using the base type's - * width strided "char *a[2] = {...}" by one byte, so every element but - * the first got a bogus address. An implicit-size array reaches this with - * ptr_level set as a marker rather than as a real pointer type, so only - * an explicitly sized array is treated this way. + * width strided "char *a[2] = {...}" by one byte, so every element but the + * first got a bogus address. An implicit-size array reaches this with + * ptr_level set as a marker rather than as a real pointer type, so only an + * explicitly sized array is treated this way. */ int elem_size = var->type->size; if (!is_implicit && var->ptr_level > 0) @@ -887,8 +884,8 @@ void parse_array_init(var_t *var, } else { /* A global initializer is restricted to simple constants, but * it still has to be stored. Consuming the tokens and dropping - * the value left every global array zero-filled, while the - * same initializer on a local worked. + * the value left every global array zero-filled, while the same + * initializer on a local worked. */ if (parent == GLOBAL_BLOCK && !lex_peek(T_numeric, NULL) && !lex_peek(T_minus, NULL) && !lex_peek(T_string, NULL) && @@ -955,7 +952,7 @@ void parse_array_init(var_t *var, * initialize other elements without explicit assignments to 0. * * Therefore, the first and second cases return 0 and 15, respectively. - * */ + */ for (; count < var->array_size; count++) { var_t *val = require_var(parent); val->var_name = gen_name(); @@ -1033,11 +1030,12 @@ void parse_array_compound_literal(var_t *var, lex_expect(T_close_curly); var->array_size = count; } -/* Identify compiler-emitted temporaries that hold array compound literals. - * They keep array metadata without pointer indirection and are marked via + +/* Identify compiler-emitted temporaries that hold array compound literals. They + * keep array metadata without pointer indirection and are marked via * is_compound_literal when synthesized. */ -bool is_array_literal_placeholder(var_t *var) +bool is_array_literal_placeholder(const var_t *var) { return var && var->array_size > 0 && !var->ptr_level && var->is_compound_literal; @@ -1070,8 +1068,8 @@ var_t *scalarize_array_literal(block_t *parent, if (literal_size <= 0) literal_size = TY_int->size; - /* A caller-provided hint (e.g., assignment target) dictates the result - * type when available so we reuse wider/narrower scalar destinations. + /* A caller-provided hint (e.g., assignment target) dictates the result type + * when available so we reuse wider/narrower scalar destinations. */ type_t *result_type = hint_type ? hint_type : literal_type; if (!result_type) @@ -1091,8 +1089,8 @@ var_t *scalarize_array_literal(block_t *parent, return scalar; } -/* Centralized guard for lowering array literal placeholders when a scalar - * value is expected, keeping the scattered special cases consistent. +/* Centralized guard for lowering array literal placeholders when a scalar value + * is expected, keeping the scattered special cases consistent. */ var_t *scalarize_array_literal_if_needed(block_t *parent, basic_block_t **bb, @@ -1108,12 +1106,12 @@ var_t *scalarize_array_literal_if_needed(block_t *parent, /* Integer constant-expression parser. * - * Array dimensions, and other places C requires an integer constant - * expression, accept far more than a bare literal. These evaluate such an - * expression at parse time without emitting any IR, folding through the same - * precedence table (get_operator_prio()) and the same operator semantics - * (eval_expression_imm()) the rest of the parser already uses, so there is - * only one statement of what C's operators mean. + * Array dimensions, and other places C requires an integer constant expression, + * accept far more than a bare literal. These evaluate such an expression at + * parse time without emitting any IR, folding through the same precedence table + * (get_operator_prio()) and the same operator semantics (eval_expression_imm()) + * the rest of the parser already uses, so there is only one statement of what + * C's operators mean. */ #define MAX_CONST_EXPR_OPS 16 @@ -1221,18 +1219,19 @@ void read_inner_var_decl(var_t *vd, bool anon, bool is_param) /* Preserve typedef pointer level - don't reset if already inherited */ vd->init_val = 0; if (is_param) { - /* However, if the parsed variable is a function parameter, - * reset its pointer level to zero. + /* However, if the parsed variable is a function parameter, reset its + * pointer level to zero. */ vd->ptr_level = 0; } while (lex_accept(T_asterisk)) { vd->ptr_level++; - /* Check for const after asterisk (e.g., int * const ptr). - * For now, we just consume const qualifiers after pointer. - * Full support would require tracking const-ness of the pointer - * itself vs the pointed-to data separately. + + /* Check for const after asterisk (e.g., int * const ptr). For now, we + * just consume const qualifiers after pointer. Full support would + * require tracking const-ness of the pointer itself vs the pointed-to + * data separately. */ while (lex_peek(T_const, NULL)) lex_accept(T_const); @@ -1323,6 +1322,7 @@ void read_full_var_decl(var_t *vd, bool anon, bool is_param) if (!type) { printf("Could not find type %s%s\n", find_type_flag == 2 ? "struct/union " : "", type_name); + fflush(stdout); /* see fatal() */ abort(); } @@ -1334,6 +1334,7 @@ void read_full_var_decl(var_t *vd, bool anon, bool is_param) /* starting next_token, need to check the type */ void read_partial_var_decl(var_t *vd, var_t *template) { + UNUSED(template); read_inner_var_decl(vd, false, false); } @@ -1507,9 +1508,9 @@ void read_func_parameters(func_t *func, block_t *parent, basic_block_t **bb) /* Writing past 'params' corrupts this frame, and the damage only * surfaces later as a wrong argument value. The check has to come * before the conversions below: those index func->param_defs[], a - * MAX_PARAMS-element array embedded in func_t, so an over-long - * argument list reads past it and dereferences a garbage type - * pointer -- the compiler crashed instead of reporting the limit. + * MAX_PARAMS-element array embedded in func_t, so an over-long argument + * list reads past it and dereferences a garbage type pointer -- the + * compiler crashed instead of reporting the limit. */ if (param_num >= MAX_PARAMS) error_at("Too many arguments in function call", cur_token_loc()); @@ -1520,8 +1521,9 @@ void read_func_parameters(func_t *func, block_t *parent, basic_block_t **bb) param = scalarize_array_literal(parent, bb, param, target->type); } - /* Handle parameter type conversion for direct calls. - * Indirect calls currently don't provide function instance. + + /* Handle parameter type conversion for direct calls. Indirect calls + * currently don't provide function instance. */ if (func && param_num >= func->num_params && func->va_args) { /* Default promotions apply to scalar varargs, but pointer-like @@ -1613,6 +1615,7 @@ void handle_single_dereference(block_t *parent, basic_block_t **bb) lex_expect(T_close_bracket); rs1 = opstack_pop(); + /* For pointer dereference, we need to determine the target type and * size. Since we do not have full type tracking in expressions, use * defaults @@ -1679,14 +1682,14 @@ void handle_single_dereference(block_t *parent, basic_block_t **bb) } } -/* Scan ahead for an assignment operator at the top level of the statement - * that starts at the current token, stopping at its terminating semicolon. +/* Scan ahead for an assignment operator at the top level of the statement that + * starts at the current token, stopping at its terminating semicolon. * - * A statement beginning with '*' is either a store through a pointer or a - * plain expression, and the two need opposite treatment of the leading - * asterisk. Deciding by looking at tokens keeps the choice free of side - * effects: by the time an expression has been parsed, its instructions have - * already been emitted and there is no way back. + * A statement beginning with '*' is either a store through a pointer or a plain + * expression, and the two need opposite treatment of the leading asterisk. + * Deciding by looking at tokens keeps the choice free of side effects: by the + * time an expression has been parsed, its instructions have already been + * emitted and there is no way back. */ bool stmt_starts_assignment(void) { @@ -1733,8 +1736,9 @@ void handle_multiple_dereference(block_t *parent, basic_block_t **bb) var_t *vd, *rs1; int sz; - /* Handle consecutive asterisks for multiple dereference: **pp, ***ppp, - * ***(expr) */ + /* Handle consecutive asterisks for multiple dereference: **pp, ***ppp, and + * the parenthesized ***(expr) form. + */ int deref_count = 1; /* We already consumed one asterisk */ while (lex_accept(T_asterisk)) deref_count++; @@ -1928,7 +1932,8 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) } } else if (lex_accept(T_open_bracket)) { /* Check if this is a cast, compound literal, or parenthesized - * expression */ + * expression + */ char lookahead_token[MAX_ID_LEN]; bool is_compound_literal = false; bool is_cast = false; @@ -1957,6 +1962,7 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) bool is_array = false; if (lex_accept(T_open_square)) { is_array = true; + /* Skip the array size: it is discarded, and a numeric * literal can be longer than any small buffer. */ @@ -1972,6 +1978,7 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) is_compound_literal = true; cast_or_literal_type = type; cast_ptr_level = ptr_level; + /* Store is_array flag in cast_ptr_level if it's an * array */ @@ -1993,8 +2000,7 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) } if (is_cast) { - /* Process cast: (type)expr */ - /* Parse the expression to be cast */ + /* Process cast: (type)expr Parse the expression to be cast */ read_expr_operand(parent, bb); /* Get the expression result */ @@ -2058,7 +2064,7 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) if (!lex_peek(T_close_curly, NULL)) { read_expr(parent, bb); read_ternary_operation(parent, bb); - var_t *ptr_val = opstack_pop(); + const var_t *ptr_val = opstack_pop(); /* For pointer compound literals, store the address */ compound_var->init_val = ptr_val->init_val; @@ -2084,8 +2090,8 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) } else if (cast_or_literal_type->base_type == TYPE_struct || cast_or_literal_type->base_type == TYPE_typedef) { /* Struct compound literal support (including typedef structs) + * For typedef structs, the actual struct info is in the type */ - /* For typedef structs, the actual struct info is in the type */ /* Initialize struct compound literal */ compound_var->init_val = 0; @@ -2095,7 +2101,7 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) if (!lex_peek(T_close_curly, NULL)) { read_expr(parent, bb); read_ternary_operation(parent, bb); - var_t *first_field = opstack_pop(); + const var_t *first_field = opstack_pop(); compound_var->init_val = first_field->init_val; /* Consume additional fields if present */ @@ -2131,10 +2137,8 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) read_expr(parent, bb); read_ternary_operation(parent, bb); - /* Check if there are more elements (comma-separated) or if - * it's an explicit array - */ - if (lex_peek(T_comma, NULL) || is_array_literal) { + /* Check if there are more elements (comma-separated) */ + if (lex_peek(T_comma, NULL)) { /* Array compound literal: (int[]){1, 2, 3} */ var_t *first_element = opstack_pop(); @@ -2197,8 +2201,8 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) /* Create result that provides first element access. * This enables array compound literals in scalar - * contexts: int x = (int[]){1,2,3}; // x gets 1 int y - * = 5 + (int[]){10}; // adds 5 + 10 + * contexts: int x = (int[]){1,2,3}; // x gets 1 int y = + * 5 + (int[]){10}; // adds 5 + 10 */ var_t *result_var = require_var(parent); result_var->var_name = gen_name(); @@ -2241,7 +2245,7 @@ void read_expr_operand(block_t *parent, basic_block_t **bb) lex_peek(T_identifier, token); /* is a constant or variable? */ - constant_t *con = find_constant(token); + const constant_t *con = find_constant(token); var_t *var = find_var(token, parent); func_t *func = find_func(token); @@ -2323,11 +2327,11 @@ bool is_logical(opcode_t op) return op == OP_log_and || op == OP_log_or; } -/* Helper function to calculate element size for pointer operations */ /* Consume a compound-assignment operator ("+=", "-=", ...) and report the - * arithmetic it applies. Returns false and consumes nothing when the next - * token is not one, so it can sit in an else-if chain beside the other - * statement forms. + * arithmetic it applies. + * + * Returns false and consumes nothing when the next token is not one, so it can + * sit in an else-if chain beside the other statement forms. */ bool accept_compound_assign_op(opcode_t *op) { @@ -2364,10 +2368,10 @@ int get_pointer_element_size(var_t *ptr_var) /* Direct pointer with type info. * * Only a single level of indirection points at the base type. For deeper - * pointers (int **, char ***, ...) the element is itself a pointer, so - * the step is PTR_SIZE. Returning the base type size there makes - * "q + 1" advance by 4 instead of 8 on LP64 and drops a level of type - * information from the result. + * pointers (int **, char ***, ...) the element is itself a pointer, so the + * step is PTR_SIZE. Returning the base type size there makes "q + 1" + * advance by 4 instead of 8 on LP64 and drops a level of type information + * from the result. */ if (ptr_var->ptr_level && ptr_var->type) { if (ptr_var->ptr_level > 1) @@ -2436,8 +2440,9 @@ void handle_pointer_arithmetic(block_t *parent, rs2_is_ptr = is_pointer_like_value(rs2); if (rs1_is_ptr && rs2_is_ptr) { - /* Both are pointers - this is pointer difference */ - /* Determine element size */ + /* Both are pointers - this is pointer difference Determine element + * size + */ element_size = PTR_SIZE; /* Default */ /* Get element size from the first pointer */ @@ -2569,7 +2574,7 @@ bool is_pointer_operation(opcode_t op, var_t *rs1, var_t *rs2) void read_expr_body(block_t *parent, basic_block_t **bb) { var_t *vd, *rs1, *rs2; - opcode_t oper_stack[10]; + opcode_t oper_stack[MAX_OPERATOR_STACK_SIZE]; int oper_stack_idx = 0; /* These variables used for parsing logical-and/or operation. @@ -2597,7 +2602,7 @@ void read_expr_body(block_t *parent, basic_block_t **bb) has_prev_log_op = true; prev_log_op = op; } else { - if (oper_stack_idx >= 10) + if (oper_stack_idx >= MAX_OPERATOR_STACK_SIZE) fatal("Expression too complex: operator stack exhausted"); oper_stack[oper_stack_idx++] = op; } @@ -2644,9 +2649,8 @@ void read_expr_body(block_t *parent, basic_block_t **bb) has_prev_log_op = true; } else if (prev_log_op == OP_log_and) { /* For example: a && b || c - * previous opcode: prev_log_op == OP_log_and - * current opcode: op == OP_log_or - * current operand: b + * previous opcode: prev_log_op == OP_log_and current opcode: op + * == OP_log_or current operand: b * * Finalize the logical-and operation and test the operand for * the following logical-or operation. @@ -2683,14 +2687,13 @@ void read_expr_body(block_t *parent, basic_block_t **bb) * * Eventually, the current opcode becomes the previous opcode * and pprev opcode is set to 0. - * */ + */ prev_log_op = op; pprev_log_op = 0; } else { /* For example: a || b && c - * previous opcode: prev_log_op == OP_log_or - * current opcode: op == OP_log_and - * current operand: b + * previous opcode: prev_log_op == OP_log_or current opcode: op + * == OP_log_and current operand: b * * Using the logical-and operation to test the current operand * instead of using the logical-or operation. @@ -2729,7 +2732,7 @@ void read_expr_body(block_t *parent, basic_block_t **bb) } read_expr_operand(parent, bb); if (!is_logical(op)) { - if (oper_stack_idx >= 10) + if (oper_stack_idx >= MAX_OPERATOR_STACK_SIZE) fatal("Expression too complex: operator stack exhausted"); oper_stack[oper_stack_idx++] = op; } @@ -2891,8 +2894,8 @@ void read_expr(block_t *parent, basic_block_t **bb) * * @allow_ptr_arith says whether a following "+ expr" belongs to this lvalue. * Normally it does, and the addend is scaled by the element size. The - * dereference handlers pass false, because unary '*' binds tighter than '+': - * in "*p + 1" the sum belongs to the enclosing expression, and reading it as + * dereference handlers pass false, because unary '*' binds tighter than '+': in + * "*p + 1" the sum belongs to the enclosing expression, and reading it as * pointer arithmetic gives p[1] instead of one more than p[0]. It applies to * this lvalue alone -- an lvalue parsed further in, as a subscript or a call * argument, gets the normal behaviour from its own call. @@ -2909,8 +2912,8 @@ void read_lvalue(lvalue_t *lvalue, bool is_address_got = false; bool is_member = false; - /* Callers pass a find_var() result, which is NULL for a name that was - * never declared. + /* Callers pass a find_var() result, which is NULL for a name that was never + * declared. */ if (!var) error_at("Undeclared identifier", next_token_loc()); @@ -2934,9 +2937,8 @@ void read_lvalue(lvalue_t *lvalue, lex_peek(T_dot, NULL)) { if (lex_accept(T_open_square)) { /* if subscripted member's is not yet resolved, dereference to - * resolve base address. - * e.g., dereference of "->" in "data->raw[0]" would be performed - * here. + * resolve base address. e.g., dereference of "->" in "data->raw[0]" + * would be performed here. */ if (lvalue->is_reference && lvalue->ptr_level && is_member) { rs1 = opstack_pop(); @@ -2946,16 +2948,18 @@ void read_lvalue(lvalue_t *lvalue, add_insn(parent, *bb, OP_read, vd, rs1, NULL, PTR_SIZE, NULL); } - /* var must be either a pointer or an array of some type */ - /* For typedef pointers, check the type's ptr_level */ + /* var must be either a pointer or an array of some type For typedef + * pointers, check the type's ptr_level + */ bool is_typedef_pointer = (var->type && var->type->ptr_level > 0); if (var->ptr_level == 0 && var->array_size == 0 && !is_typedef_pointer) error_at("Cannot apply square operator to non-pointer", cur_token_loc()); - /* if nested pointer, still pointer */ - /* Also handle typedef pointers which have ptr_level == 0 */ + /* if nested pointer, still pointer Also handle typedef pointers + * which have ptr_level == 0 + */ if ((var->ptr_level <= 1 || is_typedef_pointer) && var->array_size == 0) { /* For typedef pointers, get the size of the base type that the @@ -2988,8 +2992,9 @@ void read_lvalue(lvalue_t *lvalue, read_expr(parent, bb); - /* multiply by element size */ - /* For 2D arrays, check if this is the first or second dimension */ + /* multiply by element size For 2D arrays, check if this is the + * first or second dimension + */ int multiplier = lvalue->size; /* If this is the first index of a 2D array, multiply by dim2 * @@ -3141,14 +3146,15 @@ void read_lvalue(lvalue_t *lvalue, rs2 = opstack_pop(); rs1 = opstack_pop(); vd = require_var(parent); + /* A pointer plus an integer is still a pointer of the same type; * without this the result looks like a plain int and a later * dereference reads the base type's width instead of a pointer. * - * Only genuine pointers are propagated. An array base has - * ptr_level 0, and copying that would label the sum with the - * element type, making get_size() report the element width for - * what is actually an address. + * Only genuine pointers are propagated. An array base has ptr_level + * 0, and copying that would label the sum with the element type, + * making get_size() report the element width for what is actually + * an address. */ if (var->ptr_level) { vd->type = lvalue->type; @@ -3159,7 +3165,10 @@ void read_lvalue(lvalue_t *lvalue, add_insn(parent, *bb, OP_add, vd, rs1, rs2, 0, NULL); } } else { - var_t *t; + /* Set and read only under 'is_reference'; the initializer says so to a + * compiler that cannot correlate the two tests. + */ + var_t *t = NULL; /* If operand is a reference, read the value and push to stack for the * incoming addition/subtraction. Otherwise, use the top element of @@ -3175,6 +3184,7 @@ void read_lvalue(lvalue_t *lvalue, if (prefix_op != OP_generic) { vd = require_var(parent); vd->var_name = gen_name(); + /* For pointer arithmetic, increment by the size of pointed-to type */ if (lvalue->ptr_level) @@ -3196,6 +3206,7 @@ void read_lvalue(lvalue_t *lvalue, if (lvalue->is_reference) { rs1 = vd; vd = opstack_pop(); + /* The column of arguments of the new insn of 'OP_write' is * different from 'ph1_ir' */ @@ -3333,10 +3344,10 @@ void finalize_logical(opcode_t op, if (op == OP_log_and) { /* For example: a && b * - * If handling the expression, the basic blocks will - * connect to each other as the following illustration: + * If handling the expression, the basic blocks will connect to each + * other as the following illustration: * - * bb1 bb2 bb3 + * bb1 bb2 bb3 * +-----------+ +-----------+ +---------+ * | teq a, #0 | True | teq b, #0 | True | ldr 1 | * | bne bb2 | ----> | bne bb3 | ----> | b bb5 | @@ -3351,9 +3362,8 @@ void finalize_logical(opcode_t op, * +---------+ +--------+ * bb4 bb5 * - * In this case, finalize_logical() should add some - * instructions to bb2 ~ bb5 and properly connect them - * to each other. + * In this case, finalize_logical() should add some instructions to bb2 + * ~ bb5 and properly connect them to each other. * * Notice that * - bb1 has been handled by read_logical(). @@ -3362,10 +3372,10 @@ void finalize_logical(opcode_t op, * - bb4 is 'shared_bb'. * - bb5 needs to be created. * - * Thus, here uses 'then', 'then_next', 'else_bb' and - * 'end' to respectively point to bb2 ~ bb5. Subsequently, - * perform the mentioned operations for finalizing. - * */ + * Thus, here uses 'then', 'then_next', 'else_bb' and 'end' to + * respectively point to bb2 ~ bb5. Subsequently, perform the mentioned + * operations for finalizing. + */ then = *bb; then_next = bb_create(parent); else_bb = shared_bb; @@ -3375,12 +3385,11 @@ void finalize_logical(opcode_t op, } else if (op == OP_log_or) { /* For example: a || b * - * Similar to handling logical-and operations, it should - * add some instructions to the basic blocks and connect - * them to each other for logical-or operations as in - * the figure: + * Similar to handling logical-and operations, it should add some + * instructions to the basic blocks and connect them to each other for + * logical-or operations as in the figure: * - * bb1 bb2 bb3 + * bb1 bb2 bb3 * +-----------+ +-----------+ +---------+ * | teq a, #0 | False | teq b, #0 | False | ldr 0 | * | bne bb4 | ----> | bne bb4 | ----> | b bb5 | @@ -3395,10 +3404,9 @@ void finalize_logical(opcode_t op, * +---------+ +--------+ * bb4 bb5 * - * Similarly, here uses 'else_if', 'else_bb', 'then' and - * 'end' to respectively point to bb2 ~ bb5, and then - * finishes the finalization. - * */ + * Similarly, here uses 'else_if', 'else_bb', 'then' and 'end' to + * respectively point to bb2 ~ bb5, and then finishes the finalization. + */ then = shared_bb; else_if = *bb; else_bb = bb_create(parent); @@ -3415,13 +3423,12 @@ void finalize_logical(opcode_t op, add_insn(parent, op == OP_log_and ? then : else_if, OP_branch, NULL, vd, NULL, 0, NULL); - /* - * If handling logical-and operation, here creates a true branch for the + /* If handling logical-and operation, here creates a true branch for the * logical-and operation and assigns a true value. * * Otherwise, create a false branch and assign a false value for logical-or * operation. - * */ + */ vd = require_var(parent); vd->var_name = gen_name(); vd->init_val = op == OP_log_and; @@ -3440,8 +3447,8 @@ void finalize_logical(opcode_t op, /* Create the shared branch and assign the other value for the other * condition of a logical-and/or operation. * - * If handing a logical-and operation, assign a false value. else, assign - * a true value for a logical-or operation. + * If handing a logical-and operation, assign a false value. else, assign a + * true value for a logical-or operation. */ vd = require_var(parent); vd->var_name = gen_name(); @@ -3586,9 +3593,8 @@ bool read_body_assignment(char *token, if (op != OP_generic) { int increment_size = 1; - /* if we have a pointer, shift it by element size */ - /* But not if we are operating on a dereferenced value (array - * indexing) + /* if we have a pointer, shift it by element size But not if we are + * operating on a dereferenced value (array indexing) */ if (lvalue.ptr_level && !lvalue.is_reference) increment_size = lvalue.type->size; @@ -3705,9 +3711,10 @@ bool read_body_assignment(char *token, rs1 = opstack_pop(); /* is_func labels both function symbols and function-pointer - * variables. A variable on the RHS must contribute its - * stored pointer value, rather than its identifier being - * lowered as a function address. */ + * variables. A variable on the RHS must contribute its stored + * pointer value, rather than its identifier being lowered as a + * function address. + */ if (rs2->is_func && find_var(rs2->var_name, parent) == rs2) { t = require_ref_var(parent, rs2->type, rs2->ptr_level); t->var_name = gen_name(); @@ -3719,8 +3726,8 @@ bool read_body_assignment(char *token, rs2 = vd; } - /* Acquire destination address of lvalue if lvalue is a - * local variable. + /* Acquire destination address of lvalue if lvalue is a local + * variable. */ if (!lvalue.is_reference) { var_t *addr = @@ -3792,6 +3799,7 @@ int eval_expression_imm(opcode_t op, int op1, int op2) if (!op2) error_at("Division by zero in constant expression", cur_token_loc()); + /* INT_MIN / -1 has no representable result; on x86 it raises SIGFPE * rather than producing one. */ @@ -3893,11 +3901,10 @@ bool read_global_assignment(char *token) var = find_global_var(token); if (var) { if (lex_peek(T_string, NULL)) { - /* String literal global initialization: - * String literals are now stored in .rodata section. - * TODO: Implement compile-time address resolution for global - * pointer initialization with rodata addresses - * (e.g., char *p = "str";) + /* String literal global initialization: String literals are now + * stored in .rodata section. TODO: Implement compile-time address + * resolution for global pointer initialization with rodata + * addresses (e.g., char *p = "str";) */ read_literal_param(parent, bb); rs1 = opstack_pop(); @@ -3906,9 +3913,9 @@ bool read_global_assignment(char *token) return true; } - opcode_t op_stack[10]; + opcode_t op_stack[MAX_OPERATOR_STACK_SIZE]; opcode_t op, next_op; - int val_stack[10]; + int val_stack[MAX_OPERATOR_STACK_SIZE]; int op_stack_index = 0, val_stack_index = 0; int operand1, operand2; operand1 = read_primary_constant(); @@ -3944,12 +3951,6 @@ bool read_global_assignment(char *token) add_insn(parent, bb, OP_assign, vd, rs1, NULL, 0, NULL); return true; } - if (op == OP_ternary) { - lex_expect(T_question); - int cond = eval_expression_imm(op, operand1, operand2); - eval_ternary_imm(cond, token); - return true; - } /* using stack if operands more than two */ op_stack[op_stack_index++] = op; @@ -3981,7 +3982,8 @@ bool read_global_assignment(char *token) } while (op_stack_index > 0 && same_op == 0); } /* push next operand on stack */ - if (val_stack_index >= 10 || op_stack_index >= 10) + if (val_stack_index >= MAX_OPERATOR_STACK_SIZE || + op_stack_index >= MAX_OPERATOR_STACK_SIZE) fatal("Constant expression too complex"); val_stack[val_stack_index++] = read_primary_constant(); /* push operator on stack */ @@ -4056,383 +4058,446 @@ basic_block_t *read_code_block(func_t *func, block_t *parent, basic_block_t *bb); -basic_block_t *read_body_statement(block_t *parent, basic_block_t *bb) +/* A switch, its cases, and the block they break out of. */ +basic_block_t *handle_switch_statement(block_t *parent, basic_block_t *bb) { char token[MAX_ID_LEN]; - func_t *func; - type_t *type; - var_t *vd, *rs1, *rs2, *var; - opcode_t prefix_op = OP_generic; - bool is_const = false; + var_t *vd; + var_t *rs1; + var_t *rs2; - if (!bb) - printf("Warning: unreachable code detected\n"); + bool is_default = false; - /* statement can be: - * function call, variable declaration, assignment operation, - * keyword, block - */ + basic_block_t *n = bb_create(parent); + bb_connect(bb, n, NEXT); + bb = n; - if (lex_peek(T_open_curly, NULL)) - return read_code_block(parent->func, parent, bb); + lex_expect(T_open_bracket); + read_expr(parent, &bb); + lex_expect(T_close_bracket); - if (lex_accept(T_return)) { - return handle_return_statement(parent, bb); - } + /* create exit jump for breaks */ + basic_block_t *switch_end = bb_create(parent); + break_bb_push(switch_end); + basic_block_t *true_body_ = bb_create(parent); - if (lex_accept(T_if)) { - return handle_if_statement(parent, bb); - } + lex_expect(T_open_curly); + while (lex_peek(T_default, NULL) || lex_peek(T_case, NULL)) { + if (lex_accept(T_default)) + is_default = true; + else { + int case_val; + + lex_accept(T_case); + char literal[MAX_TOKEN_LEN]; + + if (lex_peek_n(T_numeric, literal, MAX_TOKEN_LEN)) { + case_val = parse_numeric_constant(literal); + lex_expect(T_numeric); + } else if (lex_peek_n(T_char, literal, MAX_TOKEN_LEN)) { + char unescaped[MAX_TOKEN_LEN]; + if (unescape_string(literal, unescaped, MAX_TOKEN_LEN) < 0) + error_at("Invalid escape sequence", next_token_loc()); + case_val = unescaped[0]; + lex_expect(T_char); + } else if (lex_peek(T_identifier, token)) { + const constant_t *cd = find_constant(token); + if (!cd) + error_at("Unknown constant in case label", cur_token_loc()); + case_val = cd->value; + lex_expect(T_identifier); + } else { + fatal("Not a valid case value"); + } - if (lex_accept(T_while)) { - return handle_while_statement(parent, bb); - } + vd = require_var(parent); + vd->var_name = gen_name(); + vd->init_val = case_val; + opstack_push(vd); + add_insn(parent, bb, OP_load_constant, vd, NULL, NULL, 0, NULL); - if (lex_accept(T_switch)) { - bool is_default = false; + vd = require_var(parent); + vd->var_name = gen_name(); + rs1 = opstack_pop(); + rs2 = operand_stack[operand_stack_idx - 1]; + add_insn(parent, bb, OP_eq, vd, rs1, rs2, 0, NULL); - basic_block_t *n = bb_create(parent); - bb_connect(bb, n, NEXT); - bb = n; + add_insn(parent, bb, OP_branch, NULL, vd, NULL, 0, NULL); + } + lex_expect(T_colon); - lex_expect(T_open_bracket); - read_expr(parent, &bb); - lex_expect(T_close_bracket); + if (is_default) + /* there's no condition if it is a default label */ + bb_connect(bb, true_body_, NEXT); + else + bb_connect(bb, true_body_, THEN); - /* create exit jump for breaks */ - basic_block_t *switch_end = bb_create(parent); - break_bb_push(switch_end); - basic_block_t *true_body_ = bb_create(parent); + int control = 0; - lex_expect(T_open_curly); - while (lex_peek(T_default, NULL) || lex_peek(T_case, NULL)) { - if (lex_accept(T_default)) - is_default = true; - else { - int case_val; - - lex_accept(T_case); - char literal[MAX_TOKEN_LEN]; - - if (lex_peek_n(T_numeric, literal, MAX_TOKEN_LEN)) { - case_val = parse_numeric_constant(literal); - lex_expect(T_numeric); - } else if (lex_peek_n(T_char, literal, MAX_TOKEN_LEN)) { - char unescaped[MAX_TOKEN_LEN]; - if (unescape_string(literal, unescaped, MAX_TOKEN_LEN) < 0) - error_at("Invalid escape sequence", next_token_loc()); - case_val = unescaped[0]; - lex_expect(T_char); - } else if (lex_peek(T_identifier, token)) { - constant_t *cd = find_constant(token); - if (!cd) - error_at("Unknown constant in case label", - cur_token_loc()); - case_val = cd->value; - lex_expect(T_identifier); - } else { - fatal("Not a valid case value"); - } + while (!lex_peek(T_case, NULL) && !lex_peek(T_close_curly, NULL) && + !lex_peek(T_default, NULL)) { + true_body_ = read_body_statement(parent, true_body_); + control = 1; + } - vd = require_var(parent); - vd->var_name = gen_name(); - vd->init_val = case_val; - opstack_push(vd); - add_insn(parent, bb, OP_load_constant, vd, NULL, NULL, 0, NULL); + if (control && true_body_) { + /* Create a new body block for next case, and connect the last body + * block which lacks 'break' to it to make that one ignore the + * upcoming cases. + */ + n = bb_create(parent); + bb_connect(true_body_, n, NEXT); + true_body_ = n; + } - vd = require_var(parent); - vd->var_name = gen_name(); - rs1 = opstack_pop(); - rs2 = operand_stack[operand_stack_idx - 1]; - add_insn(parent, bb, OP_eq, vd, rs1, rs2, 0, NULL); + if (!lex_peek(T_close_curly, NULL)) { + if (is_default) + error_at("Label default should be the last one", + next_token_loc()); - add_insn(parent, bb, OP_branch, NULL, vd, NULL, 0, NULL); - } - lex_expect(T_colon); + /* create a new conditional block for next case */ + n = bb_create(parent); + bb_connect(bb, n, ELSE); + bb = n; - if (is_default) - /* there's no condition if it is a default label */ - bb_connect(bb, true_body_, NEXT); - else - bb_connect(bb, true_body_, THEN); + /* create a new body block for next case if the last body block + * exits 'switch'. + */ + if (!true_body_) + true_body_ = bb_create(parent); + } else if (!is_default) { + /* handle missing default label */ + bb_connect(bb, switch_end, ELSE); + } + } - int control = 0; + /* remove the expression in switch() */ + opstack_pop(); + lex_expect(T_close_curly); - while (!lex_peek(T_case, NULL) && !lex_peek(T_close_curly, NULL) && - !lex_peek(T_default, NULL)) { - true_body_ = read_body_statement(parent, true_body_); - control = 1; - } + if (true_body_) + /* if the last label has no explicit break, connect it to the end */ + bb_connect(true_body_, switch_end, NEXT); - if (control && true_body_) { - /* Create a new body block for next case, and connect the last - * body block which lacks 'break' to it to make that one ignore - * the upcoming cases. - */ - n = bb_create(parent); - bb_connect(true_body_, n, NEXT); - true_body_ = n; - } + break_exit_idx--; - if (!lex_peek(T_close_curly, NULL)) { - if (is_default) - error_at("Label default should be the last one", - next_token_loc()); + int dangling = 1; + for (int i = 0; i < switch_end->prev_idx; i++) + if (switch_end->prev[i].bb) + dangling = 0; - /* create a new conditional block for next case */ - n = bb_create(parent); - bb_connect(bb, n, ELSE); - bb = n; + if (dangling) + return NULL; - /* create a new body block for next case if the last body block - * exits 'switch'. - */ - if (!true_body_) - true_body_ = bb_create(parent); - } else if (!is_default) { - /* handle missing default label */ - bb_connect(bb, switch_end, ELSE); - } - } + return switch_end; +} - /* remove the expression in switch() */ - opstack_pop(); - lex_expect(T_close_curly); +/* A for loop: setup, condition, body and increment. */ +basic_block_t *handle_for_statement(block_t *parent, basic_block_t *bb) +{ + char token[MAX_ID_LEN]; + type_t *type; + var_t *vd; + var_t *rs1; + var_t *var; + opcode_t prefix_op = OP_generic; - if (true_body_) - /* if the last label has no explicit break, connect it to the end */ - bb_connect(true_body_, switch_end, NEXT); + lex_expect(T_open_bracket); - break_exit_idx--; + /* synthesize for loop block */ + block_t *blk = add_block(parent, parent->func); - int dangling = 1; - for (int i = 0; i < switch_end->prev_idx; i++) - if (switch_end->prev[i].bb) - dangling = 0; + /* setup - execute once */ + basic_block_t *setup = bb_create(blk); + bb_connect(bb, setup, NEXT); - if (dangling) - return NULL; + if (!lex_accept(T_semicolon)) { + if (!lex_peek(T_identifier, token)) + error_at("Unexpected token when parsing for loop", + next_token_loc()); - return switch_end; - } + int find_type_flag = lex_accept(T_struct) ? 2 : 1; + if (find_type_flag == 1 && lex_accept(T_union)) { + find_type_flag = 2; + } + type = find_type(token, find_type_flag); + if (type) { + var = require_typed_var(blk, type); + read_full_var_decl(var, false, false); + add_insn(blk, setup, OP_allocat, var, NULL, NULL, 0, NULL); + add_symbol(setup, var); + if (lex_accept(T_assign)) { + read_expr(blk, &setup); + read_ternary_operation(blk, &setup); + + rs1 = resize_var(parent, &bb, opstack_pop(), var); + add_insn(blk, setup, OP_assign, var, rs1, NULL, 0, NULL); + } + while (lex_accept(T_comma)) { + var_t *nv; + + /* add sequence point at T_comma */ + perform_side_effect(blk, setup); + + /* multiple (partial) declarations */ + nv = require_typed_var(blk, type); + read_partial_var_decl(nv, var); /* partial */ + add_insn(blk, setup, OP_allocat, nv, NULL, NULL, 0, NULL); + add_symbol(setup, nv); + if (lex_accept(T_assign)) { + read_expr(blk, &setup); + + rs1 = resize_var(parent, &bb, opstack_pop(), nv); + add_insn(blk, setup, OP_assign, nv, rs1, NULL, 0, NULL); + } + } + } else { + read_body_assignment(token, blk, OP_generic, &setup); + } - if (lex_accept(T_break)) { - if (!break_exit_idx) - error_at("'break' outside of a loop or switch", cur_token_loc()); - bb_connect(bb, break_bb[break_exit_idx - 1], NEXT); lex_expect(T_semicolon); - return NULL; } - if (lex_accept(T_continue)) { - if (!continue_pos_idx) - error_at("'continue' outside of a loop", cur_token_loc()); - bb_connect(bb, continue_bb[continue_pos_idx - 1], NEXT); + basic_block_t *cond_ = bb_create(blk); + basic_block_t *for_end = bb_create(parent); + basic_block_t *cond_start = cond_; + break_bb_push(for_end); + bb_connect(setup, cond_, NEXT); + + /* condition - check before the loop */ + if (!lex_accept(T_semicolon)) { + read_expr(blk, &cond_); lex_expect(T_semicolon); - return NULL; + } else { + /* always true */ + vd = require_var(blk); + vd->init_val = 1; + vd->var_name = gen_name(); + opstack_push(vd); + add_insn(blk, cond_, OP_load_constant, vd, NULL, NULL, 0, NULL); } + bb_connect(cond_, for_end, ELSE); - if (lex_accept(T_for)) { - lex_expect(T_open_bracket); - - /* synthesize for loop block */ - block_t *blk = add_block(parent, parent->func); + vd = opstack_pop(); + add_insn(blk, cond_, OP_branch, NULL, vd, NULL, 0, NULL); - /* setup - execute once */ - basic_block_t *setup = bb_create(blk); - bb_connect(bb, setup, NEXT); + basic_block_t *inc_ = bb_create(blk); + continue_bb_push(inc_); - if (!lex_accept(T_semicolon)) { - if (!lex_peek(T_identifier, token)) - error_at("Unexpected token when parsing for loop", - next_token_loc()); + /* increment after each loop */ + if (!lex_accept(T_close_bracket)) { + if (lex_accept(T_increment)) + prefix_op = OP_add; + else if (lex_accept(T_decrement)) + prefix_op = OP_sub; + lex_peek(T_identifier, token); + read_body_assignment(token, blk, prefix_op, &inc_); + lex_expect(T_close_bracket); + } - int find_type_flag = lex_accept(T_struct) ? 2 : 1; - if (find_type_flag == 1 && lex_accept(T_union)) { - find_type_flag = 2; - } - type = find_type(token, find_type_flag); - if (type) { - var = require_typed_var(blk, type); - read_full_var_decl(var, false, false); - add_insn(blk, setup, OP_allocat, var, NULL, NULL, 0, NULL); - add_symbol(setup, var); - if (lex_accept(T_assign)) { - read_expr(blk, &setup); - read_ternary_operation(blk, &setup); + /* loop body */ + basic_block_t *body_ = bb_create(blk); + bb_connect(cond_, body_, THEN); + body_ = read_body_statement(blk, body_); - rs1 = resize_var(parent, &bb, opstack_pop(), var); - add_insn(blk, setup, OP_assign, var, rs1, NULL, 0, NULL); - } - while (lex_accept(T_comma)) { - var_t *nv; - - /* add sequence point at T_comma */ - perform_side_effect(blk, setup); - - /* multiple (partial) declarations */ - nv = require_typed_var(blk, type); - read_partial_var_decl(nv, var); /* partial */ - add_insn(blk, setup, OP_allocat, nv, NULL, NULL, 0, NULL); - add_symbol(setup, nv); - if (lex_accept(T_assign)) { - read_expr(blk, &setup); - - rs1 = resize_var(parent, &bb, opstack_pop(), nv); - add_insn(blk, setup, OP_assign, nv, rs1, NULL, 0, NULL); - } - } - } else { - read_body_assignment(token, blk, OP_generic, &setup); - } + /* Normal fallthrough from the loop body goes through the increment block. A + * continue statement may already have connected another predecessor to + * inc_. + */ + if (body_) + bb_connect(body_, inc_, NEXT); - lex_expect(T_semicolon); + /* An empty increment block still needs its back-edge when it is reachable + * through normal fallthrough or continue. + * + * Do not connect a completely unreachable increment block, such as: + * + * for (;;) { + * break; + * } + */ + bool has_pred = false; + for (int i = 0; i < inc_->prev_idx; i++) { + if (inc_->prev[i].bb) { + has_pred = true; + break; } + } + if (has_pred) + bb_connect(inc_, cond_start, NEXT); + + /* jump to increment */ + continue_pos_idx--; + break_exit_idx--; + return for_end; +} - basic_block_t *cond_ = bb_create(blk); - basic_block_t *for_end = bb_create(parent); - basic_block_t *cond_start = cond_; - break_bb_push(for_end); - bb_connect(setup, cond_, NEXT); +/* A do-while loop, whose condition is tested after the body. */ +basic_block_t *handle_do_statement(block_t *parent, basic_block_t *bb) +{ + var_t *vd; - /* condition - check before the loop */ - if (!lex_accept(T_semicolon)) { - read_expr(blk, &cond_); - lex_expect(T_semicolon); - } else { - /* always true */ - vd = require_var(blk); - vd->init_val = 1; - vd->var_name = gen_name(); - opstack_push(vd); - add_insn(blk, cond_, OP_load_constant, vd, NULL, NULL, 0, NULL); - } - bb_connect(cond_, for_end, ELSE); + basic_block_t *n = bb_create(parent); + bb_connect(bb, n, NEXT); + bb = n; - vd = opstack_pop(); - add_insn(blk, cond_, OP_branch, NULL, vd, NULL, 0, NULL); + basic_block_t *cond_ = bb_create(parent); + basic_block_t *do_while_end = bb_create(parent); - basic_block_t *inc_ = bb_create(blk); - continue_bb_push(inc_); + continue_bb_push(cond_); + break_bb_push(do_while_end); - /* increment after each loop */ - if (!lex_accept(T_close_bracket)) { - if (lex_accept(T_increment)) - prefix_op = OP_add; - else if (lex_accept(T_decrement)) - prefix_op = OP_sub; - lex_peek(T_identifier, token); - read_body_assignment(token, blk, prefix_op, &inc_); - lex_expect(T_close_bracket); - } + basic_block_t *do_body = read_body_statement(parent, bb); + if (do_body) + bb_connect(do_body, cond_, NEXT); - /* loop body */ - basic_block_t *body_ = bb_create(blk); - bb_connect(cond_, body_, THEN); - body_ = read_body_statement(blk, body_); + lex_expect(T_while); + lex_expect(T_open_bracket); + read_expr(parent, &cond_); + lex_expect(T_close_bracket); - /* Normal fallthrough from the loop body goes through the increment - * block. A continue statement may already have connected another - * predecessor to inc_. - */ - if (body_) - bb_connect(body_, inc_, NEXT); + vd = opstack_pop(); + add_insn(parent, cond_, OP_branch, NULL, vd, NULL, 0, NULL); - /* An empty increment block still needs its back-edge when it is - * reachable through normal fallthrough or continue. - * - * Do not connect a completely unreachable increment block, such as: - * - * for (;;) { - * break; - * } - */ - bool has_pred = false; - for (int i = 0; i < inc_->prev_idx; i++) { - if (inc_->prev[i].bb) { - has_pred = true; - break; - } + lex_expect(T_semicolon); + + for (int i = 0; i < cond_->prev_idx; i++) { + if (cond_->prev[i].bb) { + bb_connect(cond_, bb, THEN); + bb_connect(cond_, do_while_end, ELSE); + break; } - if (has_pred) - bb_connect(inc_, cond_start, NEXT); + /* if breaking out of loop, skip condition block */ + } + + continue_pos_idx--; + break_exit_idx--; + return do_while_end; +} + +/* A local struct or union declaration. */ +basic_block_t *handle_record_statement(block_t *parent, basic_block_t *bb) +{ + char token[MAX_ID_LEN]; + type_t *type; + var_t *rs1; + var_t *var; + bool is_const = false; - /* jump to increment */ - continue_pos_idx--; - break_exit_idx--; - return for_end; + int find_type_flag = lex_accept(T_struct) ? 2 : 1; + if (find_type_flag == 1 && lex_accept(T_union)) { + find_type_flag = 2; } + lex_ident(T_identifier, token); + type = find_type(token, find_type_flag); + if (type) { + var = require_typed_var(parent, type); + var->is_const_qualified = is_const; + read_partial_var_decl(var, NULL); + add_insn(parent, bb, OP_allocat, var, NULL, NULL, 0, NULL); + add_symbol(bb, var); + if (lex_accept(T_assign)) { + if (lex_peek(T_open_curly, NULL) && + (var->array_size > 0 || var->ptr_level > 0)) { + parse_array_init(var, parent, &bb, 1); /* Always emit code */ + } else if (lex_peek(T_open_curly, NULL) && + (var->type->base_type == TYPE_struct || + var->type->base_type == TYPE_typedef)) { + /* C90-compliant struct compound literal support */ + type_t *struct_type = var->type; - if (lex_accept(T_do)) { - basic_block_t *n = bb_create(parent); - bb_connect(bb, n, NEXT); - bb = n; + /* Handle typedef by getting actual struct type */ + if (struct_type->base_type == TYPE_typedef && + struct_type->base_struct) + struct_type = struct_type->base_struct; - basic_block_t *cond_ = bb_create(parent); - basic_block_t *do_while_end = bb_create(parent); + lex_expect(T_open_curly); + int field_idx = 0; - continue_bb_push(cond_); - break_bb_push(do_while_end); + if (!lex_peek(T_close_curly, NULL)) { + for (;;) { + /* Parse field value expression */ + read_expr(parent, &bb); + read_ternary_operation(parent, &bb); + var_t *val = opstack_pop(); - basic_block_t *do_body = read_body_statement(parent, bb); - if (do_body) - bb_connect(do_body, cond_, NEXT); + /* Initialize field if within bounds */ + if (field_idx < struct_type->num_fields) { + var_t *field = &struct_type->fields[field_idx]; - lex_expect(T_while); - lex_expect(T_open_bracket); - read_expr(parent, &cond_); - lex_expect(T_close_bracket); + /* Create target variable for field */ + var_t *field_val = + resize_to(parent, &bb, val, field->type, + field->ptr_level); - vd = opstack_pop(); - add_insn(parent, cond_, OP_branch, NULL, vd, NULL, 0, NULL); + /* Compute field address: &struct + field_offset */ + var_t *struct_addr = require_var(parent); + struct_addr->var_name = gen_name(); + add_insn(parent, bb, OP_address_of, struct_addr, + var, NULL, 0, NULL); - lex_expect(T_semicolon); + var_t *field_addr = struct_addr; + if (field->offset > 0) { + var_t *offset = require_var(parent); + offset->var_name = gen_name(); + offset->init_val = field->offset; + add_insn(parent, bb, OP_load_constant, offset, + NULL, NULL, 0, NULL); - for (int i = 0; i < cond_->prev_idx; i++) { - if (cond_->prev[i].bb) { - bb_connect(cond_, bb, THEN); - bb_connect(cond_, do_while_end, ELSE); - break; - } - /* if breaking out of loop, skip condition block */ - } + var_t *addr = require_var(parent); + addr->var_name = gen_name(); + add_insn(parent, bb, OP_add, addr, struct_addr, + offset, 0, NULL); + field_addr = addr; + } - continue_pos_idx--; - break_exit_idx--; - return do_while_end; - } + /* Write field value */ + int field_size = size_var(field); + add_insn(parent, bb, OP_write, NULL, field_addr, + field_val, field_size, NULL); + } - if (lex_accept(T_goto)) - return handle_goto_statement(parent, bb); + field_idx++; + if (!lex_accept(T_comma)) + break; + if (lex_peek(T_close_curly, NULL)) + break; + } + } + lex_expect(T_close_curly); + } else { + read_expr(parent, &bb); + read_ternary_operation(parent, &bb); - /* empty statement */ - if (lex_accept(T_semicolon)) - return bb; + var_t *rhs = opstack_pop(); + rhs = scalarize_array_literal_if_needed( + parent, &bb, rhs, var->type, + !var->ptr_level && var->array_size == 0); - /* struct/union variable declaration */ - if (lex_peek(T_struct, NULL) || lex_peek(T_union, NULL)) { - int find_type_flag = lex_accept(T_struct) ? 2 : 1; - if (find_type_flag == 1 && lex_accept(T_union)) { - find_type_flag = 2; + rs1 = resize_var(parent, &bb, rhs, var); + add_insn(parent, bb, OP_assign, var, rs1, NULL, 0, NULL); + } } - lex_ident(T_identifier, token); - type = find_type(token, find_type_flag); - if (type) { - var = require_typed_var(parent, type); - var->is_const_qualified = is_const; - read_partial_var_decl(var, NULL); - add_insn(parent, bb, OP_allocat, var, NULL, NULL, 0, NULL); - add_symbol(bb, var); + while (lex_accept(T_comma)) { + var_t *nv; + + /* add sequence point at T_comma */ + perform_side_effect(parent, bb); + + /* multiple (partial) declarations */ + nv = require_typed_var(parent, type); + read_inner_var_decl(nv, false, false); + add_insn(parent, bb, OP_allocat, nv, NULL, NULL, 0, NULL); + add_symbol(bb, nv); if (lex_accept(T_assign)) { if (lex_peek(T_open_curly, NULL) && - (var->array_size > 0 || var->ptr_level > 0)) { - parse_array_init(var, parent, &bb, - 1); /* Always emit code */ + (nv->array_size > 0 || nv->ptr_level > 0)) { + parse_array_init(nv, parent, &bb, true); } else if (lex_peek(T_open_curly, NULL) && - (var->type->base_type == TYPE_struct || - var->type->base_type == TYPE_typedef)) { + (nv->type->base_type == TYPE_struct || + nv->type->base_type == TYPE_typedef)) { /* C90-compliant struct compound literal support */ - type_t *struct_type = var->type; + type_t *struct_type = nv->type; /* Handle typedef by getting actual struct type */ if (struct_type->base_type == TYPE_typedef && @@ -4463,7 +4528,7 @@ basic_block_t *read_body_statement(block_t *parent, basic_block_t *bb) var_t *struct_addr = require_var(parent); struct_addr->var_name = gen_name(); add_insn(parent, bb, OP_address_of, struct_addr, - var, NULL, 0, NULL); + nv, NULL, 0, NULL); var_t *field_addr = struct_addr; if (field->offset > 0) { @@ -4497,119 +4562,35 @@ basic_block_t *read_body_statement(block_t *parent, basic_block_t *bb) } else { read_expr(parent, &bb); read_ternary_operation(parent, &bb); - var_t *rhs = opstack_pop(); rhs = scalarize_array_literal_if_needed( - parent, &bb, rhs, var->type, - !var->ptr_level && var->array_size == 0); - - rs1 = resize_var(parent, &bb, rhs, var); - add_insn(parent, bb, OP_assign, var, rs1, NULL, 0, NULL); - } - } - while (lex_accept(T_comma)) { - var_t *nv; - - /* add sequence point at T_comma */ - perform_side_effect(parent, bb); - - /* multiple (partial) declarations */ - nv = require_typed_var(parent, type); - read_inner_var_decl(nv, false, false); - add_insn(parent, bb, OP_allocat, nv, NULL, NULL, 0, NULL); - add_symbol(bb, nv); - if (lex_accept(T_assign)) { - if (lex_peek(T_open_curly, NULL) && - (nv->array_size > 0 || nv->ptr_level > 0)) { - parse_array_init(nv, parent, &bb, true); - } else if (lex_peek(T_open_curly, NULL) && - (nv->type->base_type == TYPE_struct || - nv->type->base_type == TYPE_typedef)) { - /* C90-compliant struct compound literal support */ - type_t *struct_type = nv->type; - - /* Handle typedef by getting actual struct type */ - if (struct_type->base_type == TYPE_typedef && - struct_type->base_struct) - struct_type = struct_type->base_struct; - - lex_expect(T_open_curly); - int field_idx = 0; - - if (!lex_peek(T_close_curly, NULL)) { - for (;;) { - /* Parse field value expression */ - read_expr(parent, &bb); - read_ternary_operation(parent, &bb); - var_t *val = opstack_pop(); - - /* Initialize field if within bounds */ - if (field_idx < struct_type->num_fields) { - var_t *field = - &struct_type->fields[field_idx]; - - /* Create target variable for field */ - var_t *field_val = - resize_to(parent, &bb, val, field->type, - field->ptr_level); - - /* Compute field address: &struct + - * field_offset */ - var_t *struct_addr = require_var(parent); - struct_addr->var_name = gen_name(); - add_insn(parent, bb, OP_address_of, - struct_addr, nv, NULL, 0, NULL); - - var_t *field_addr = struct_addr; - if (field->offset > 0) { - var_t *offset = require_var(parent); - offset->var_name = gen_name(); - offset->init_val = field->offset; - add_insn(parent, bb, OP_load_constant, - offset, NULL, NULL, 0, NULL); - - var_t *addr = require_var(parent); - addr->var_name = gen_name(); - add_insn(parent, bb, OP_add, addr, - struct_addr, offset, 0, NULL); - field_addr = addr; - } - - /* Write field value */ - int field_size = size_var(field); - add_insn(parent, bb, OP_write, NULL, - field_addr, field_val, field_size, - NULL); - } - - field_idx++; - if (!lex_accept(T_comma)) - break; - if (lex_peek(T_close_curly, NULL)) - break; - } - } - lex_expect(T_close_curly); - } else { - read_expr(parent, &bb); - read_ternary_operation(parent, &bb); - var_t *rhs = opstack_pop(); - rhs = scalarize_array_literal_if_needed( - parent, &bb, rhs, nv->type, - !nv->ptr_level && nv->array_size == 0); + parent, &bb, rhs, nv->type, + !nv->ptr_level && nv->array_size == 0); - rs1 = resize_var(parent, &bb, rhs, nv); - add_insn(parent, bb, OP_assign, nv, rs1, NULL, 0, NULL); - } + rs1 = resize_var(parent, &bb, rhs, nv); + add_insn(parent, bb, OP_assign, nv, rs1, NULL, 0, NULL); } } - lex_expect(T_semicolon); - return bb; } - error_at("Unknown struct/union type", next_token_loc()); + lex_expect(T_semicolon); + return bb; } + error_at("Unknown struct/union type", next_token_loc()); +} + +/* Everything a statement can still be: a declaration, an assignment, a call, or + * an expression evaluated for its effect. + */ +basic_block_t *handle_declaration(block_t *parent, basic_block_t *bb) +{ + char token[MAX_ID_LEN]; + func_t *func; + type_t *type; + var_t *rs1; + var_t *var; + opcode_t prefix_op = OP_generic; + bool is_const = false; - /* Handle const qualifier for local variable declarations */ if (lex_accept(T_const)) { is_const = true; /* After const, we expect a type */ @@ -4627,11 +4608,13 @@ basic_block_t *read_body_statement(block_t *parent, basic_block_t *bb) if (!is_const && !lex_peek(T_identifier, token) && !has_asterisk) error_at("Unexpected token", next_token_loc()); - /* is it a variable declaration? */ - /* Special handling when statement starts with asterisk */ + /* is it a variable declaration? Special handling when statement starts with + * asterisk + */ if (has_asterisk) { - /* For "*identifier", check if identifier is a type. - * If not, it's a dereference, not a declaration. */ + /* For "*identifier", check if identifier is a type. If not, it's a + * dereference, not a declaration. + */ token_t *saved_token = cur_token; /* Skip the asterisk to peek at the identifier */ @@ -4753,9 +4736,8 @@ basic_block_t *read_body_statement(block_t *parent, basic_block_t *bb) first_elem->type = var->type; first_elem->var_name = gen_name(); - /* Read first element from array at offset 0 - * expr_result is the array itself, so we can read - * directly from it + /* Read first element from array at offset 0 expr_result is + * the array itself, so we can read directly from it */ add_insn(parent, bb, OP_read, first_elem, expr_result, NULL, var->type->size, NULL); @@ -4876,12 +4858,12 @@ basic_block_t *read_body_statement(block_t *parent, basic_block_t *bb) if (lex_peek(T_asterisk, NULL)) { if (stmt_starts_assignment()) { /* Consume exactly one asterisk and evaluate what follows as an - * ordinary expression. That expression is the address to store - * to: for "*p" it is p, for "**pp" it is the value of *pp, and - * for "*(p + 1)" it is p + 1. Letting read_expr() consume the - * leading asterisk too would dereference once more than the - * assignment asks for, and the store then went to whatever the - * pointee happened to hold. + * ordinary expression. That expression is the address to store to: + * for "*p" it is p, for "**pp" it is the value of *pp, and for "*(p + * + 1)" it is p + 1. Letting read_expr() consume the leading + * asterisk too would dereference once more than the assignment asks + * for, and the store then went to whatever the pointee happened to + * hold. */ lex_expect(T_asterisk); read_expr(parent, &bb); @@ -4936,7 +4918,7 @@ basic_block_t *read_body_statement(block_t *parent, basic_block_t *bb) lex_accept(T_identifier); token_t *id_tk = cur_token; if (lex_accept(T_colon)) { - label_t *l = find_label(token); + const label_t *l = find_label(token); if (l) error_at("label redefinition", &id_tk->location); @@ -4952,6 +4934,71 @@ basic_block_t *read_body_statement(block_t *parent, basic_block_t *bb) return NULL; } +basic_block_t *read_body_statement(block_t *parent, basic_block_t *bb) +{ + if (!bb) + printf("Warning: unreachable code detected\n"); + + /* statement can be: + * function call, variable declaration, assignment operation, + * keyword, block + */ + + if (lex_peek(T_open_curly, NULL)) + return read_code_block(parent->func, parent, bb); + + if (lex_accept(T_return)) { + return handle_return_statement(parent, bb); + } + + if (lex_accept(T_if)) { + return handle_if_statement(parent, bb); + } + + if (lex_accept(T_while)) { + return handle_while_statement(parent, bb); + } + + if (lex_accept(T_switch)) + return handle_switch_statement(parent, bb); + + if (lex_accept(T_break)) { + if (!break_exit_idx) + error_at("'break' outside of a loop or switch", cur_token_loc()); + bb_connect(bb, break_bb[break_exit_idx - 1], NEXT); + lex_expect(T_semicolon); + return NULL; + } + + if (lex_accept(T_continue)) { + if (!continue_pos_idx) + error_at("'continue' outside of a loop", cur_token_loc()); + bb_connect(bb, continue_bb[continue_pos_idx - 1], NEXT); + lex_expect(T_semicolon); + return NULL; + } + + if (lex_accept(T_for)) + return handle_for_statement(parent, bb); + + if (lex_accept(T_do)) + return handle_do_statement(parent, bb); + + if (lex_accept(T_goto)) + return handle_goto_statement(parent, bb); + + /* empty statement */ + if (lex_accept(T_semicolon)) + return bb; + + /* struct/union variable declaration */ + if (lex_peek(T_struct, NULL) || lex_peek(T_union, NULL)) + return handle_record_statement(parent, bb); + + /* Handle const qualifier for local variable declarations */ + return handle_declaration(parent, bb); +} + /* Nesting counter for read_code_block(), which recurses through * read_body_statement() for every nested block. */ @@ -5007,7 +5054,7 @@ void read_func_body(func_t *func) } for (int i = 0; i < label_idx; i++) { - label_t *label = &labels[i]; + const label_t *label = &labels[i]; if (label->used) continue; @@ -5039,7 +5086,7 @@ void print_func_decl(func_t *func, const char *prefix, bool newline) printf("%s(", func->return_def.var_name); for (int i = 0; i < func->num_params; i++) { - var_t *var = &func->param_defs[i]; + const var_t *var = &func->param_defs[i]; if (var->is_const_qualified) printf("const "); @@ -5061,7 +5108,6 @@ void print_func_decl(func_t *func, const char *prefix, bool newline) printf("\n"); } -/* if first token is type */ /* Emit the optional initializer of a global declarator. Arrays and pointers * written with a brace list go through the array initializer; everything else * is a scalar constant. @@ -5118,8 +5164,8 @@ void read_global_decl(block_t *block, bool is_const) read_parameter_list_decl(func, 0); if (check_decl) { - /* Validate whether the previous declaration and the current - * one differ. + /* Validate whether the previous declaration and the current one + * differ. */ if ((func->return_def.type != func_tmp.return_def.type) || (func->return_def.ptr_level != func_tmp.return_def.ptr_level) || @@ -5129,30 +5175,33 @@ void read_global_decl(block_t *block, bool is_const) func->return_def.var_name); print_func_decl(&func_tmp, "before: ", true); print_func_decl(func, "after: ", true); + fflush(stdout); /* see fatal() */ abort(); } if (func->num_params != func_tmp.num_params) { printf( - "Error: confilcting number of arguments for the function " + "Error: conflicting number of arguments for the function " "%s.\n", func->return_def.var_name); print_func_decl(&func_tmp, "before: ", true); print_func_decl(func, "after: ", true); + fflush(stdout); /* see fatal() */ abort(); } for (int i = 0; i < func->num_params; i++) { - var_t *func_var = &func->param_defs[i]; - var_t *func_tmp_var = &func_tmp.param_defs[i]; + const var_t *func_var = &func->param_defs[i]; + const var_t *func_tmp_var = &func_tmp.param_defs[i]; if ((func_var->type != func_tmp_var->type) || (func_var->ptr_level != func_tmp_var->ptr_level) || (func_var->is_const_qualified != func_tmp_var->is_const_qualified)) { - printf("Error: confilcting types for the function %s.\n", + printf("Error: conflicting types for the function %s.\n", func->return_def.var_name); print_func_decl(&func_tmp, "before: ", true); print_func_decl(func, "after: ", true); + fflush(stdout); /* see fatal() */ abort(); } } @@ -5162,6 +5211,7 @@ void read_global_decl(block_t *block, bool is_const) func->return_def.var_name); print_func_decl(&func_tmp, "before: ", true); print_func_decl(func, "after: ", true); + fflush(stdout); /* see fatal() */ abort(); } } @@ -5281,10 +5331,9 @@ void read_global_statement(void) var->array_size == 0 && var->ptr_level == 0 && (decl_type->base_type == TYPE_struct || decl_type->base_type == TYPE_typedef)) { - /* Global struct compound literal support - * Currently we just consume the syntax - actual - * initialization would require runtime code which globals - * don't support + /* Global struct compound literal support Currently we just + * consume the syntax - actual initialization would require + * runtime code which globals don't support */ consume_global_compound_literal(); } else { @@ -5317,8 +5366,7 @@ void read_global_statement(void) return; } - /* struct definition */ - /* has forward declaration? */ + /* struct definition has forward declaration? */ type_t *type = find_type(token, 2); if (!type) type = add_type(); @@ -5534,7 +5582,7 @@ void read_global_statement(void) lex_expect(T_semicolon); } else { char base_type[MAX_ID_LEN]; - type_t *base; + const type_t *base; type_t *type = add_type(); lex_ident(T_identifier, base_type); base = find_type(base_type, true); @@ -5600,20 +5648,20 @@ void parse_internal(void) if (dynlink) { /* In dynamic mode, __syscall won't be implemented. * - * Simply declare a 'syscall' function as follows if the program - * needs to use 'syscall': + * Simply declare a 'syscall' function as follows if the program needs + * to use 'syscall': * * int syscall(int number, ...); * - * shecc will treat it as an external function, and the compiled - * program will eventually use the implementation provided by - * the external C library. + * shecc will treat it as an external function, and the compiled program + * will eventually use the implementation provided by the external C + * library. * * If shecc supports the 'long' data type in the future, it would be * better to declare syscall using its original prototype: * * long syscall(long number, ...); - * */ + */ } else { /* Linux syscall */ func_t *func = add_func("__syscall", true); diff --git a/src/peephole.c b/src/peephole.c index 9b9dbaf1..06fb273d 100644 --- a/src/peephole.c +++ b/src/peephole.c @@ -13,7 +13,7 @@ * instructions are those whose results can be directly written to the final * destination register, eliminating intermediate moves. */ -bool is_fusible_insn(ph2_ir_t *ph2_ir) +bool is_fusible_insn(const ph2_ir_t *ph2_ir) { switch (ph2_ir->op) { case OP_add: /* Arithmetic operations */ @@ -42,10 +42,9 @@ bool is_fusible_insn(ph2_ir_t *ph2_ir) /* Main peephole optimization function that applies pattern matching and * transformation rules to consecutive IR instructions. - * Returns true if any optimization was applied, false otherwise. - */ -/* Drop the instructions after @ir through @last, keeping ph2_ir_list.tail on a - * node still in the list. + * Returns true if any optimization was applied, false otherwise. Drop the + * instructions after @ir through @last, keeping ph2_ir_list.tail on a node + * still in the list. * * Every removal in this file goes through here. A bare "ir->next = last->next" * leaves tail pointing at a removed node, which is why x64-codegen.c used to @@ -369,8 +368,8 @@ bool eliminate_load_store_pairs(basic_block_t *bb, ph2_ir_t *ph2_ir) * links the caller is holding valid. * * Only at equal width. A wide store followed by a narrow one to the - * same slot leaves the bytes the second does not cover holding what - * the first put there, so dropping the first loses them. + * same slot leaves the bytes the second does not cover holding what the + * first put there, so dropping the first loses them. */ if (ph2_ir->src1 == next->src1 && ph2_ir->src1 >= 0 && ph2_ir->size_bytes == next->size_bytes && @@ -553,9 +552,10 @@ bool strength_reduction(ph2_ir_t *ph2_ir) return false; } -/* Simplify bitwise patterns the SSA optimizer cannot see, because they - * only become visible once registers are assigned. Returns true when it - * rewrote something. +/* Simplify bitwise patterns the SSA optimizer cannot see, because they only + * become visible once registers are assigned. + * + * Returns true when it rewrote something. */ bool bitwise_optimization(basic_block_t *bb, ph2_ir_t *ph2_ir) { @@ -724,8 +724,8 @@ bool triple_pattern_optimization(basic_block_t *bb, ph2_ir_t *ph2_ir) * * This runs on ph2_ir_t, after register allocation, and so sees only what * assigning registers makes visible. Constant folding, common subexpression - * elimination and dead code elimination have already run over insn_t in the - * SSA optimizer and are not repeated here. + * elimination and dead code elimination have already run over insn_t in the SSA + * optimizer and are not repeated here. * * What is left to do at this level: * - self-assignment elimination, for assignments allocation itself created @@ -757,13 +757,13 @@ void peephole(void) continue; } - /* Every rewrite below moves this instruction's result to - * the destination of the one after it, dropping the write to - * the register it named. That is fine for a temporary, whose - * value nothing wants again, and wrong for a pinned register: - * a variable lives there for the whole function and nothing - * else ever reloads it, so "li rbx, 0; add rax, rsi, rbx" must - * not become "mov rax, rsi" and leave rbx unwritten. + /* Every rewrite below moves this instruction's result to the + * destination of the one after it, dropping the write to the + * register it named. That is fine for a temporary, whose value + * nothing wants again, and wrong for a pinned register: a + * variable lives there for the whole function and nothing else + * ever reloads it, so "li rbx, 0; add rax, rsi, rbx" must not + * become "mov rax, rsi" and leave rbx unwritten. */ if (ir->dest >= 0 && ir->dest < REG_CNT && ((func->pinned_regs >> ir->dest) & 1)) diff --git a/src/preprocessor.c b/src/preprocessor.c index 984e4b4c..422a9c7e 100644 --- a/src/preprocessor.c +++ b/src/preprocessor.c @@ -1,8 +1,8 @@ /* * shecc - Self-Hosting and Educational C Compiler. * - * shecc is freely redistributable under the BSD 2 clause license. See the - * file "LICENSE" for information on usage and redistribution of this file. + * shecc is freely redistributable under the BSD 2 clause license. See the file + * "LICENSE" for information on usage and redistribution of this file. */ #include "../config" #include "defs.h" @@ -21,7 +21,7 @@ token_t *pp_lex_skip_space(token_t *tk) } /* Whether @tk is whitespace, a tab or a newline. */ -bool pp_is_layout(token_t *tk) +bool pp_is_layout(const token_t *tk) { return tk->kind == T_whitespace || tk->kind == T_newline || tk->kind == T_tab; @@ -30,7 +30,7 @@ bool pp_is_layout(token_t *tk) /* The first token after @tk that is not layout, or NULL at the end. */ token_t *pp_next_significant(token_t *tk) { - token_t *before = pp_lex_skip_space(tk); + const token_t *before = pp_lex_skip_space(tk); return before->next; } @@ -65,11 +65,10 @@ token_t *pp_lex_expect_token(token_t *tk, token_kind_t kind, bool skip_space) } /* Copies and isolate the given copied token */ -token_t *copy_token(token_t *tk) +token_t *copy_token(const token_t *tk) { - /* The copy overwrites every byte, so zeroing the allocation first would - * be wasted work -- and this runs once per token of every macro - * expansion. + /* The copy overwrites every byte, so zeroing the allocation first would be + * wasted work -- and this runs once per token of every macro expansion. */ token_t *new_tk = arena_alloc(TOKEN_ARENA, sizeof(token_t)); memcpy(new_tk, tk, sizeof(token_t)); @@ -91,7 +90,7 @@ typedef struct macro { bool is_macro_defined(char *name) { - macro_t *macro = hashmap_get(MACROS, name); + const macro_t *macro = hashmap_get(MACROS, name); return macro && !macro->is_disabled; } @@ -154,7 +153,7 @@ hide_set_t *hide_set_union(hide_set_t *hs1, hide_set_t *hs2) return head.next; } -bool hide_set_contains(hide_set_t *hs, char *name) +bool hide_set_contains(hide_set_t *hs, const char *name) { for (; hs; hs = hs->next) if (!strcmp(hs->name, name)) @@ -165,8 +164,8 @@ bool hide_set_contains(hide_set_t *hs, char *name) typedef enum { CK_if_then, CK_elif_then, CK_else_then } cond_kind_t; /* cond_incl_t is used as a stack-like context to track conditional macro - * directives' expansion, and gives information to the expansion context - * to process the token stream with correct behavior. + * directives' expansion, and gives information to the expansion context to + * process the token stream with correct behavior. */ typedef struct cond_incl { struct cond_incl *prev; @@ -186,13 +185,12 @@ cond_incl_t *push_cond(cond_incl_t *ci, token_t *tk, bool included) } /* preprocess_ctx_t is used to track various inforamtion when expanding token - * stream, the context state may vary due to the current expanding object, - * but in general case, it will tries to inherit parent context state if - * possible. + * stream, the context state may vary due to the current expanding object, but + * in general case, it will tries to inherit parent context state if possible. * - * Due to the standard that token stream are always ends with EOF token, - * the default behavior is not to trim EOF token, but if the result requires - * EOF token to be present, set trim_eof to true would suffice. + * Due to the standard that token stream are always ends with EOF token, the + * default behavior is not to trim EOF token, but if the result requires EOF + * token to be present, set trim_eof to true would suffice. */ typedef struct preprocess_ctx { hide_set_t *hide_set; @@ -365,9 +363,11 @@ token_t *pp_read_constant_expr_operand(token_t *tk, int *val) ctx.macro_args = NULL; ctx.trim_eof = false; expanded_tk = pp_preprocess_internal(macro->replacement, &ctx); - tmp = tk->next; - tk->next = expanded_tk; - ctx.end_of_token->next = tmp; + if (expanded_tk) { + tmp = tk->next; + tk->next = expanded_tk; + ctx.end_of_token->next = tmp; + } return pp_read_constant_expr_operand(tk, val); } @@ -551,8 +551,8 @@ token_t *pp_skip_cond_incl(token_t *tk) /* Spell an argument's tokens as a string literal, for '#'. * - * T_string literals are stored with their escapes intact and unescaped later - * by the parser, so a quote or a backslash coming from the argument has to be + * T_string literals are stored with their escapes intact and unescaped later by + * the parser, so a quote or a backslash coming from the argument has to be * escaped again here. Tokens are separated by a single space, with none at * either end. */ @@ -607,8 +607,8 @@ token_t *pp_stringify(token_t *arg, source_location_t *loc) token_t *pp_paste_tokens(token_t *lhs, token_t *rhs, source_location_t *loc) { char lbuf[MAX_TOKEN_LEN], rbuf[MAX_TOKEN_LEN], joined[MAX_TOKEN_LEN]; - char *l = token_to_string(lhs, lbuf); - char *r = token_to_string(rhs, rbuf); + const char *l = token_to_string(lhs, lbuf); + const char *r = token_to_string(rhs, rbuf); if (!l || !r) error_at("Operand of '##' cannot be pasted", loc); @@ -647,18 +647,19 @@ token_t *pp_paste_tokens(token_t *lhs, token_t *rhs, source_location_t *loc) * * Both operate on an argument as it was written rather than on its expansion, * so they cannot wait for the expansion loop: by the time that loop reaches a - * parameter it has already expanded it. @args is NULL for an object-like - * macro, which has no parameters to stringify but may still paste. + * parameter it has already expanded it. @args is NULL for an object-like macro, + * which has no parameters to stringify but may still paste. * * An argument with no tokens in it is not "no argument": '#' spells it as the - * empty string, and pasting against it leaves the other operand standing on - * its own. Membership in @args, rather than a non-empty value, is what makes a - * name a parameter. + * empty string, and pasting against it leaves the other operand standing on its + * own. Membership in @args, rather than a non-empty value, is what makes a name + * a parameter. */ token_t *pp_subst_hash(token_t *rep, hashmap_t *args) { token_t head; token_t *tail = &head, *tail_prev = NULL; + /* Whether anything at all precedes a '##' here, and whether that something * was an argument that turned out to be empty. */ @@ -719,9 +720,9 @@ token_t *pp_subst_hash(token_t *rep, hashmap_t *args) tail = tail_prev->next; } - /* Only the first token of a multi-token argument is joined; - * the rest follow it. An argument is a list of its own, so it - * ends where the argument does. A literal operand is not: its + /* Only the first token of a multi-token argument is joined; the + * rest follow it. An argument is a list of its own, so it ends + * where the argument does. A literal operand is not: its * successor is the next token of the replacement list, which * the loop below still has to walk, so copying from here would * emit the remainder of the macro body twice. @@ -744,7 +745,7 @@ token_t *pp_subst_hash(token_t *rep, hashmap_t *args) /* A parameter the next '##' will join is substituted here, unexpanded * -- letting the expansion loop reach it would expand it first. */ - token_t *after = pp_next_significant(tk); + const token_t *after = pp_next_significant(tk); if (args && tk->kind == T_identifier && after && after->kind == T_hashhash && hashmap_contains(args, tk->literal)) { @@ -786,6 +787,14 @@ token_t *pp_preprocess_internal(token_t *tk, preprocess_ctx_t *ctx) token_t *cur = &head; cond_incl_t *ci = NULL; + /* A macro whose replacement list is empty -- "#define NDEBUG", or a + * function-like macro that expands to nothing -- produces no tokens at all, + * and both of the values returned below have to say so. Without the + * initializer the result is whatever the stack held, and end_of_token below + * would name this frame, which the caller splices onto after it has died. + */ + head.next = NULL; + while (tk) { macro_t *macro = NULL; @@ -795,22 +804,23 @@ token_t *pp_preprocess_internal(token_t *tk, preprocess_ctx_t *ctx) preprocess_ctx_t expansion_ctx; /* Initialize expansion context: inherit parent context and enable - * EOF trimming for macro body expansion */ + * EOF trimming for macro body expansion + */ expansion_ctx.expanded_from = ctx->expanded_from ? ctx->expanded_from : tk; expansion_ctx.macro_args = ctx->macro_args; expansion_ctx.trim_eof = true; - token_t *macro_arg_replcaement = NULL; + token_t *macro_arg_replacement = NULL; - /* Check if this identifier is a macro parameter (argument) - * If we're currently expanding a macro body, parameters should be - * replaced with their supplied arguments. + /* Check if this identifier is a macro parameter (argument) If we're + * currently expanding a macro body, parameters should be replaced + * with their supplied arguments. * * Membership decides this, not a non-empty value: an argument with - * no tokens in it still names a parameter, and substituting - * nothing for it is what "M(a,)" means. Testing the value would - * leave the parameter's own name standing in the output. + * no tokens in it still names a parameter, and substituting nothing + * for it is what "M(a,)" means. Testing the value would leave the + * parameter's own name standing in the output. * * '#' and '##' were already resolved by pp_subst_hash(), which had * to run before this expansion could reach their operands. @@ -819,22 +829,23 @@ token_t *pp_preprocess_internal(token_t *tk, preprocess_ctx_t *ctx) ctx->macro_args && hashmap_contains(ctx->macro_args, tk->literal); - if (is_macro_param) - macro_arg_replcaement = - hashmap_get(ctx->macro_args, tk->literal); - if (is_macro_param) { - if (macro_arg_replcaement) { + macro_arg_replacement = + hashmap_get(ctx->macro_args, tk->literal); + if (macro_arg_replacement) { /* Recursively expand the argument to handle nested macros */ expansion_ctx.hide_set = ctx->hide_set; expansion_ctx.macro_args = NULL; /* Don't take account of macro arguments, this - might run into infinite loop */ - macro_arg_replcaement = pp_preprocess_internal( - macro_arg_replcaement, &expansion_ctx); - cur->next = macro_arg_replcaement; - cur = expansion_ctx.end_of_token; + might run into infinite loop + */ + macro_arg_replacement = pp_preprocess_internal( + macro_arg_replacement, &expansion_ctx); + if (macro_arg_replacement) { + cur->next = macro_arg_replacement; + cur = expansion_ctx.end_of_token; + } } tk = pp_lex_next_token(tk, false); continue; @@ -851,8 +862,9 @@ token_t *pp_preprocess_internal(token_t *tk, preprocess_ctx_t *ctx) if (!macro || macro->is_disabled) break; - /* Handle built-in function-like macros (__FILE__, __LINE__) - * These have special handlers that generate tokens directly */ + /* Handle built-in function-like macros (__FILE__, __LINE__) These + * have special handlers that generate tokens directly + */ if (macro->handler) { cur->next = macro->handler(expansion_ctx.expanded_from); cur = cur->next; @@ -874,7 +886,8 @@ token_t *pp_preprocess_internal(token_t *tk, preprocess_ctx_t *ctx) arg_head.next = NULL; /* Add macro name to hide set to prevent re-expansion of itself - * during its own body expansion */ + * during its own body expansion + */ expansion_ctx.hide_set = hide_set_union(ctx->hide_set, new_hide_set(tk->literal)); /* Create parameter mapping table for this macro invocation */ @@ -885,7 +898,8 @@ token_t *pp_preprocess_internal(token_t *tk, preprocess_ctx_t *ctx) /* Parse macro arguments until closing parenthesis * * Handles nested parentheses and comma-separated argument list - * by tracking the nested depth */ + * by tracking the nested depth + */ while (true) { if (pp_lex_peek_token(tk, T_open_bracket, false)) bracket_depth++; @@ -910,8 +924,10 @@ token_t *pp_preprocess_internal(token_t *tk, preprocess_ctx_t *ctx) arg_tk = pp_preprocess_internal(arg_tk, &arg_expansion_ctx); tk = pp_lex_next_token(tk, false); - arg_cur->next = arg_tk; - arg_cur = arg_expansion_ctx.end_of_token; + if (arg_tk) { + arg_cur->next = arg_tk; + arg_cur = arg_expansion_ctx.end_of_token; + } continue; } } @@ -953,7 +969,8 @@ token_t *pp_preprocess_internal(token_t *tk, preprocess_ctx_t *ctx) if (hashmap_contains(expansion_ctx.macro_args, param_tk->literal)) { /* Append to existing variadic args with comma - * separator to preserve argument boundaries */ + * separator to preserve argument boundaries + */ token_t *prev = hashmap_get(expansion_ctx.macro_args, param_tk->literal); @@ -997,23 +1014,31 @@ token_t *pp_preprocess_internal(token_t *tk, preprocess_ctx_t *ctx) error_at("Too few arguments supplied to macro invocation", ¯o_tk->location); - /* Expand macro body with collected arguments - * Replace parameter references with supplied argument tokens */ - cur->next = pp_preprocess_internal( + /* Expand macro body with collected arguments Replace parameter + * references with supplied argument tokens + */ + token_t *expanded = pp_preprocess_internal( pp_subst_hash(macro->replacement, expansion_ctx.macro_args), &expansion_ctx); - cur = expansion_ctx.end_of_token; + if (expanded) { + cur->next = expanded; + cur = expansion_ctx.end_of_token; + } hashmap_free(expansion_ctx.macro_args); } else { - /* Handle object-like macro expansion (no parameters) - * Simply expand the replacement with current hide set plus - * this macro name added to prevent re-expansion */ + /* Handle object-like macro expansion (no parameters) Simply + * expand the replacement with current hide set plus this macro + * name added to prevent re-expansion + */ expansion_ctx.hide_set = hide_set_union(ctx->hide_set, new_hide_set(tk->literal)); - cur->next = pp_preprocess_internal( + token_t *expanded = pp_preprocess_internal( pp_subst_hash(macro->replacement, NULL), &expansion_ctx); - cur = expansion_ctx.end_of_token; + if (expanded) { + cur->next = expanded; + cur = expansion_ctx.end_of_token; + } } tk = pp_lex_next_token(tk, false); @@ -1021,9 +1046,9 @@ token_t *pp_preprocess_internal(token_t *tk, preprocess_ctx_t *ctx) } case T_hash: case T_hashhash: - /* Every '#' a macro body owns is resolved by pp_subst_hash() - * before that body is rescanned, so one arriving here is loose in - * ordinary code. + /* Every '#' a macro body owns is resolved by pp_subst_hash() before + * that body is rescanned, so one arriving here is loose in ordinary + * code. */ error_at("'#' is only meaningful inside a macro definition", &tk->location); @@ -1068,8 +1093,8 @@ token_t *pp_preprocess_internal(token_t *tk, preprocess_ctx_t *ctx) tk = pp_lex_expect_token(tk, T_lt, true); /* The path is ignored (see the FIXME below), so just consume - * it. Stopping at a newline too keeps an unterminated - * "#include at this moment, since - * all libc functions are included done by inlining. + + /* FIXME: We ignore #include <...> at this moment, since all + * libc functions are included done by inlining. */ tk = pp_lex_expect_token(tk, T_newline, true); tk = pp_lex_next_token(tk, false); @@ -1094,8 +1120,12 @@ token_t *pp_preprocess_internal(token_t *tk, preprocess_ctx_t *ctx) continue; file_tks = gen_file_token_stream(intern_string(inclusion_path)); - cur->next = pp_preprocess_internal(file_tks->head, &inclusion_ctx); - cur = inclusion_ctx.end_of_token; + token_t *included = + pp_preprocess_internal(file_tks->head, &inclusion_ctx); + if (included) { + cur->next = included; + cur = inclusion_ctx.end_of_token; + } continue; } case T_cppd_define: { @@ -1299,20 +1329,24 @@ token_t *pp_preprocess_internal(token_t *tk, preprocess_ctx_t *ctx) if (ci) error_at("Unterminated conditional directive", &ci->tk->location); - ctx->end_of_token = cur; + /* NULL rather than '&head' when nothing was produced: the caller must skip + * the splice entirely, and a stale read should fault rather than corrupt + * the token list it is building. + */ + ctx->end_of_token = cur == &head ? NULL : cur; return head.next; } -/* Drop the whitespace, tab and newline tokens from a fully preprocessed - * stream, on the way into the parser. +/* Drop the whitespace, tab and newline tokens from a fully preprocessed stream, + * on the way into the parser. * - * They carry no meaning to the parser, which never names those kinds, and - * every token is created before this runs, so once they are gone the parser - * never meets one again. That is what let the skip-over-layout walk in front - * of each token access -- more than a million iterations over a self-compile - * -- be removed outright. Preprocessed output (-E) still needs them to - * separate one token from the next, so the stripping belongs here and not in - * preprocess() itself. + * They carry no meaning to the parser, which never names those kinds, and every + * token is created before this runs, so once they are gone the parser never + * meets one again. That is what let the skip-over-layout walk in front of each + * token access -- more than a million iterations over a self-compile -- be + * removed outright. Preprocessed output (-E) still needs them to separate one + * token from the next, so the stripping belongs here and not in preprocess() + * itself. */ token_t *pp_strip_layout(token_t *tk) { @@ -1373,9 +1407,9 @@ token_t *preprocess(token_t *tk) macro->replacement->literal = "1"; hashmap_put(MACROS, "__SHECC__", macro); - /* Tells the source being compiled that the embedded libc is not part of - * the output, so the functions lib/c.c would have supplied -- '__syscall' - * above all -- are unavailable and libc resolves through the PLT instead. + /* Tells the source being compiled that the embedded libc is not part of the + * output, so the functions lib/c.c would have supplied -- '__syscall' above + * all -- are unavailable and libc resolves through the PLT instead. */ if (dynlink) { macro = calloc(1, sizeof(macro_t)); @@ -1578,7 +1612,6 @@ char *token_to_string(token_t *tk, char *dest) break; default: error_at("Unknown token kind", &tk->location); - printf("UNKNOWN_TOKEN"); break; } diff --git a/src/reg-alloc.c b/src/reg-alloc.c index 4d6bf472..913646c6 100644 --- a/src/reg-alloc.c +++ b/src/reg-alloc.c @@ -149,7 +149,7 @@ void track_var_use(var_t *var, int insn_idx) var->last_use = insn_idx; } -void refresh(basic_block_t *bb, insn_t *insn) +void refresh(basic_block_t *bb, const insn_t *insn) { for (int i = 0; i < REG_CNT; i++) { if (!REGS[i].var) @@ -173,6 +173,7 @@ ph2_ir_t *bb_add_ph2_ir(basic_block_t *bb, opcode_t op) n->is_branch_detached = 0; /* arch-lowering will set for branches */ n->src0 = 0; n->src1 = 0; + /* Only a select names a third source, but the allocation is not zeroed and * every field is set here by hand. */ @@ -261,8 +262,8 @@ var_t *pinned_base[REG_CNT]; * Preparing one operand can spill another's register to make room, leaving the * number recorded for it naming a register that no longer holds the value. An * instruction reading more operands than the two "avoid" arguments can express - * -- a select reads three -- locks each as it is placed, and the spill - * searches leave those alone. + * -- a select reads three -- locks each as it is placed, and the spill searches + * leave those alone. */ int reg_locked; @@ -275,10 +276,11 @@ bool reg_is_locked(int reg) } /* The register @var's base is pinned to, or -1. */ -int pinned_reg_of(var_t *var) +int pinned_reg_of(const var_t *var) { if (!var || !var->base) return -1; + /* A value an if-converted arm computes shares its variable's base with the * select's result, but the pinned register still holds what the arms read, * so it takes an ordinary register instead. @@ -357,7 +359,7 @@ void slot_var_track(var_t *var) * arithmetic. A store through a pointer is invisible to these scans, so * anything else in the frame is left alone. */ -bool slot_is_private(var_t *var) +bool slot_is_private(const var_t *var) { if (var->address_taken || var->array_size || var->has_backing_storage) return false; @@ -396,19 +398,18 @@ int slot_lookup(int offset) */ void slot_scan(func_t *func) { - /* alloc_var_slot() appends in increasing offset order, but - * phi_slot_merge() then rewrites the offsets of variables already in the - * table to put a chain of phis on one slot, which can leave it out of - * order. slot_lookup() binary-searches, so an unsorted table makes it miss - * a slot that is present: both scans below and dead_store_elim() miss the - * same one, so nothing is misidentified, but the two cleanups skip work - * they could do. + /* alloc_var_slot() appends in increasing offset order, but phi_slot_merge() + * then rewrites the offsets of variables already in the table to put a + * chain of phis on one slot, which can leave it out of order. slot_lookup() + * binary-searches, so an unsorted table makes it miss a slot that is + * present: both scans below and dead_store_elim() miss the same one, so + * nothing is misidentified, but the two cleanups skip work they could do. * * Insertion sort, because the table is nearly sorted already and every - * entry a merge moved sits close to where it belongs. It costs about - * 0.085% of a self-compile and recovers optimizations worth rather less - * than that; it is here to keep the invariant alloc_var_slot() documents - * true, not to pay for itself. + * entry a merge moved sits close to where it belongs. It costs about 0.085% + * of a self-compile and recovers optimizations worth rather less than that; + * it is here to keep the invariant alloc_var_slot() documents true, not to + * pay for itself. */ for (int i = 1; i < slot_var_count; i++) { var_t *var = slot_vars[i]; @@ -490,7 +491,7 @@ void ph2_list_remove(basic_block_t *bb, ph2_ir_t *prev, ph2_ir_t *ir) } /* Whether @ir leaves @reg holding something other than what it held before. */ -bool ph2_writes_reg(ph2_ir_t *ir, int reg) +bool ph2_writes_reg(const ph2_ir_t *ir, int reg) { switch (ir->op) { case OP_store: @@ -521,6 +522,7 @@ bool ph2_writes_reg(ph2_ir_t *ir, int reg) */ void collapse_slot_roundtrip(func_t *func) { + UNUSED(func); for (int i = 0; i < slot_var_count; i++) { if (!slot_private[i] || slot_stores[i] != 1 || slot_loads[i] != 1) continue; @@ -692,7 +694,7 @@ bool reg_is_free(int i) } /* Return the index of register for given variable. Otherwise, return -1. */ -int find_in_regs(var_t *var) +int find_in_regs(const var_t *var) { for (int i = 0; i < REG_CNT; i++) { if (REGS[i].var == var) @@ -701,13 +703,14 @@ int find_in_regs(var_t *var) return -1; } -/* Whether @var can live in a register for a whole function: nothing else may - * be able to reach it, and it must fit in one register. +/* Whether @var can live in a register for a whole function: nothing else may be + * able to reach it, and it must fit in one register. */ bool var_is_pinnable(var_t *var) { if (!var || var->is_const || !var->base) return false; + /* slot_is_private() rules out everything reachable other than by name: * globals, address-taken variables, arrays and backing storage. */ @@ -720,8 +723,8 @@ bool var_is_pinnable(var_t *var) * * Candidates are ranked by how often they are named, which stands in well * enough for how hot they are: a variable a loop carries is named on every - * iteration. At most half the file is given away so expression evaluation - * still has registers to work with. + * iteration. At most half the file is given away so expression evaluation still + * has registers to work with. */ int pin_scan_gen; @@ -748,8 +751,8 @@ void pin_registers(func_t *func) } /* Across a call only the registers the callee preserves will still hold - * their value. Those sit at the top of the file, which is the end the - * loop below hands out from, so capping the count is all that is needed. + * their value. Those sit at the top of the file, which is the end the loop + * below hands out from, so capping the count is all that is needed. */ if (calls) { if (!CALLEE_SAVED_REGS) @@ -760,8 +763,8 @@ void pin_registers(func_t *func) /* Tally what every variable's namings are worth. The count lives on the * variable rather than in an array here: the file has a handful of - * registers and a function names hundreds of variables, and the one worth - * a register is not reliably among the first few met -- a pointer + * registers and a function names hundreds of variables, and the one worth a + * register is not reliably among the first few met -- a pointer * strength_reduce() introduced is named last of all. */ pin_scan_gen++; @@ -779,11 +782,12 @@ void pin_registers(func_t *func) if (!var_is_pinnable(var)) continue; + /* A parameter passed on the stack lives at an offset into the * caller's frame that the callee cannot pin; one passed in a - * register is moved into its pinned register on entry, so it - * is a candidate like any other -- and a good one, since a - * pointer a loop walks is usually a parameter. + * register is moved into its pinned register on entry, so it is + * a candidate like any other -- and a good one, since a pointer + * a loop walks is usually a parameter. */ bool on_stack = false; bool in_reg = false; @@ -804,8 +808,9 @@ void pin_registers(func_t *func) if (base->pin_gen != pin_scan_gen) { base->pin_gen = pin_scan_gen; base->pin_weight = 0; - /* A parameter is written in the entry block before the - * body names it, so its range already spans blocks. + + /* A parameter is written in the entry block before the body + * names it, so its range already spans blocks. */ base->pin_cross = in_reg; base->pin_hot = false; @@ -823,9 +828,8 @@ void pin_registers(func_t *func) /* Take the most-named bases, highest register first: the low registers are * where arguments and return values land. The winner is found by walking - * the tally again rather than sorting it, which costs one pass per - * register handed out -- a handful, against a function's instruction - * count. + * the tally again rather than sorting it, which costs one pass per register + * handed out -- a handful, against a function's instruction count. */ for (int taken = 0; taken < limit; taken++) { var_t *best = NULL; @@ -847,14 +851,14 @@ void pin_registers(func_t *func) if (base->pin_gen != pin_scan_gen) continue; - /* Registers are handed out from the top of the file, - * which is where the preserved ones are, so a pinned - * function saves and restores one in its prologue. A - * variable named once in straight-line code does not earn - * that back -- least of all in a small leaf called from a - * loop, which pays it on every call -- so a candidate has - * to be read inside a loop and to outlive the block that - * names it. + + /* Registers are handed out from the top of the file, which + * is where the preserved ones are, so a pinned function + * saves and restores one in its prologue. A variable named + * once in straight-line code does not earn that back -- + * least of all in a small leaf called from a loop, which + * pays it on every call -- so a candidate has to be read + * inside a loop and to outlive the block that names it. */ if (!base->pin_cross || !base->pin_hot) continue; @@ -876,8 +880,8 @@ void pin_registers(func_t *func) return; /* Incoming arguments arrive in the low registers and are placed there - * before the body runs, which would overwrite anything pinned to one - * of them; the variable would then read a parameter's value instead. + * before the body runs, which would overwrite anything pinned to one of + * them; the variable would then read a parameter's value instead. */ int reg = REG_CNT - 1 - taken; int args_in_reg = func->num_params < MAX_ARGS_IN_REG ? func->num_params @@ -925,10 +929,10 @@ void load_var(basic_block_t *bb, var_t *var, int idx) int prepare_operand(basic_block_t *bb, var_t *var, int operand_0) { - /* A pinned variable is already where it always is -- unless this version - * of it is a constant, which has no home to be in until it is written - * there. Every other version reaches the register by being defined into it - * or by a phi move, so nothing else needs materialising. + /* A pinned variable is already where it always is -- unless this version of + * it is a constant, which has no home to be in until it is written there. + * Every other version reaches the register by being defined into it or by a + * phi move, so nothing else needs materialising. */ int pinned = pinned_reg_of(var); if (pinned >= 0) { @@ -999,8 +1003,9 @@ bool is_pushing_args; * in its live-out set. Asking the block directly is what makes the two arms of * an if-else able to reuse the same register. */ -bool var_read_later_in_bb(basic_block_t *bb, insn_t *from, var_t *var) +bool var_read_later_in_bb(basic_block_t *bb, insn_t *from, const var_t *var) { + UNUSED(bb); for (insn_t *insn = from; insn; insn = insn->next) { if (insn->rs1 == var || insn->rs2 == var || insn->rs3 == var) return true; @@ -1065,8 +1070,8 @@ int coalesce_candidate(basic_block_t *bb, insn_t *insn, int reg) return -1; /* A pinned register is not a scratch one however dead the version it - * currently holds looks: the variable living there is read again further - * on and nothing ever reloads it. Handing it to a temporary destroyed the + * currently holds looks: the variable living there is read again further on + * and nothing ever reloads it. Handing it to a temporary destroyed the * value a select was about to choose between. */ if (pinned_base[reg]) @@ -1146,10 +1151,10 @@ int prepare_dest(basic_block_t *bb, } } - /* Callers normally have at least one register which is not an operand, - * but an instruction with more register inputs than the allocator's two - * avoid arguments can leave every register protected. Let that caller - * make an instruction-specific choice instead of indexing REGS[-1]. + /* Callers normally have at least one register which is not an operand, but + * an instruction with more register inputs than the allocator's two avoid + * arguments can leave every register protected. Let that caller make an + * instruction-specific choice instead of indexing REGS[-1]. */ if (spilled < 0) return -1; @@ -1165,13 +1170,13 @@ int prepare_dest(basic_block_t *bb, return spilled; } -void spill_alive(basic_block_t *bb, insn_t *insn) +void spill_alive(basic_block_t *bb, const insn_t *insn) { /* Spill all locals on pointer writes (conservative aliasing handling) */ if (insn && insn->opcode == OP_write) { for (int i = 0; i < REG_CNT; i++) { - /* A pinned variable has no address, so no write through a - * pointer can reach it. + /* A pinned variable has no address, so no write through a pointer + * can reach it. */ if (REGS[i].var && !REGS[i].var->is_global && !pinned_base[i]) spill_var(bb, REGS[i].var, i); @@ -1243,30 +1248,30 @@ void spill_live_out_keep(basic_block_t *bb) * slots just written. * * Requiring the successor to be next in reverse post-order is what makes - * "emitted immediately after" true: cfg_flatten() walks the same rpo_next - * chain the allocator does, so the two orders are the same traversal. + * "emitted immediately after" true: cfg_flatten() walks the same rpo_next chain + * the allocator does, so the two orders are the same traversal. * * Only a successor emitted immediately after this block qualifies. A branch - * target is emitted wherever the backend's linear walk puts it, and a block - * the walk misses is re-emitted later -- that second copy is reached with - * unrelated registers, so a file handed to it would not hold on every path - * that runs its code. Requiring the successor to be the next block in reverse - * post-order is what rules that out, since that is the order the walk emits. + * target is emitted wherever the backend's linear walk puts it, and a block the + * walk misses is re-emitted later -- that second copy is reached with unrelated + * registers, so a file handed to it would not hold on every path that runs its + * code. Requiring the successor to be the next block in reverse post-order is + * what rules that out, since that is the order the walk emits. * * A conditional branch falls through as well: the backend jumps to one * successor and lets control run on into the other, which is emitted * contiguously exactly as a plain successor is. That edge therefore qualifies - * on the same terms, and it is the one that matters -- it is where an - * if/else chain would otherwise reload on every arm what the test just had in - * a register. + * on the same terms, and it is the one that matters -- it is where an if/else + * chain would otherwise reload on every arm what the test just had in a + * register. */ void bb_export_regs(basic_block_t *bb) { basic_block_t *succ = bb->next; if (!succ) { - /* Take whichever arm the walk placed next; the checks below confirm - * it really is contiguous and has no other way in. + /* Take whichever arm the walk placed next; the checks below confirm it + * really is contiguous and has no other way in. */ if (bb->then_ == bb->rpo_next) succ = bb->then_; @@ -1322,7 +1327,10 @@ void load_entry_regs(basic_block_t *bb) } /* The operand of 'OP_push' should not been killed until function called. */ -void extend_liveness(basic_block_t *bb, insn_t *insn, var_t *var, int offset) +void extend_liveness(basic_block_t *bb, + const insn_t *insn, + var_t *var, + int offset) { if (check_live_out(bb, var)) return; @@ -1346,9 +1354,9 @@ bool abi_lower_call_args(basic_block_t *bb, insn_t *insn) insn = insn->prev; stack_args = num_of_args - MAX_ARGS_IN_REG; while (stack_args) { - /* A pinned variable has no slot to load from: its value only ever - * lives in its register, so reading the frame here handed the callee - * whatever the slot happened to hold. + /* A pinned variable has no slot to load from: its value only ever lives + * in its register, so reading the frame here handed the callee whatever + * the slot happened to hold. */ int held = pinned_reg_of(insn->rs1); @@ -1436,7 +1444,7 @@ bool phi_live_ready; /* The candidate number of @var under the current function's stamp, or -1 when * @var is not one. */ -int phi_cand_index(var_t *var) +int phi_cand_index(const var_t *var) { if (!var) return -1; @@ -1523,7 +1531,7 @@ int phi_live_note(basic_block_t *bb, var_t *var, int flags) } /* Note that @var is live on exit from @bb, when @bb already records it. */ -void phi_live_mark_out(basic_block_t *bb, var_t *var) +void phi_live_mark_out(const basic_block_t *bb, var_t *var) { int idx = phi_cand_index(var); if (idx < 0) @@ -1601,6 +1609,7 @@ void phi_live_index_build(func_t *func) c = phi_cand_index(insn->rs2); if (c >= 0) phi_cand_last[c] = n; + /* A select reads a third operand. Leaving it out of the walk ends * the value's range before the instruction that reads it, and two * variables live at once then look free to share a slot. @@ -1705,7 +1714,7 @@ bool live_iter_next(live_iter_t *it) * outer walk run in the block order of func->bbs, which is increasing rpo, so a * cursor only ever moves forward. */ -bool live_rec_seek(int *cursor, basic_block_t *bb) +bool live_rec_seek(int *cursor, const basic_block_t *bb) { int rec = *cursor; @@ -1990,169 +1999,679 @@ void coalesce_phi_slots(func_t *func) } } -void reg_alloc(void) +/* Place one global initializer, which has no basic block of its own. */ +void reg_alloc_global(insn_t *global_insn) { - /* TODO: Add proper .bss and .data section support for uninitialized / - * initialized globals + ph2_ir_t *ir; + int dest, src0; + + /* Global initializers carry no liveness information, so no operand register + * may be reused as a destination here. */ - for (insn_t *global_insn = GLOBAL_FUNC->bbs->insn_list.head; global_insn; - global_insn = global_insn->next) { - ph2_ir_t *ir; - int dest, src0; - /* Global initializers carry no liveness information, so no operand - * register may be reused as a destination here. - */ + switch (global_insn->opcode) { + case OP_allocat: + if (global_insn->rd->array_size) { + /* Original scheme: pointer slot + backing region. Cache the base + * offset of the backing region into init_val so later global + * initializers can address elements without loading the pointer. + */ + global_insn->rd->offset = GLOBAL_FUNC->stack_size; + global_insn->rd->space_is_allocated = true; + GLOBAL_FUNC->stack_size += PTR_SIZE; + src0 = GLOBAL_FUNC->stack_size; /* base of backing region */ + + /* Stash base offset for this array variable */ + global_insn->rd->init_val = src0; + + if (global_insn->rd->ptr_level) + GLOBAL_FUNC->stack_size += + align_size(PTR_SIZE * global_insn->rd->array_size); + else { + GLOBAL_FUNC->stack_size += align_size( + global_insn->rd->array_size * global_insn->rd->type->size); + } - switch (global_insn->opcode) { - case OP_allocat: - if (global_insn->rd->array_size) { - /* Original scheme: pointer slot + backing region. Cache the - * base offset of the backing region into init_val so later - * global initializers can address elements without loading the - * pointer. - */ - global_insn->rd->offset = GLOBAL_FUNC->stack_size; - global_insn->rd->space_is_allocated = true; + dest = + prepare_dest(GLOBAL_FUNC->bbs, NULL, global_insn->rd, -1, -1); + ir = bb_add_ph2_ir(GLOBAL_FUNC->bbs, OP_global_address_of); + ir->src0 = src0; + ir->dest = dest; + ir->is_pointer = true; + ir->size_bytes = PTR_SIZE; + spill_var(GLOBAL_FUNC->bbs, global_insn->rd, dest); + } else { + global_insn->rd->offset = GLOBAL_FUNC->stack_size; + global_insn->rd->space_is_allocated = true; + if (global_insn->rd->ptr_level) + GLOBAL_FUNC->stack_size += PTR_SIZE; + else if (global_insn->rd->type != TY_int && + global_insn->rd->type != TY_short && + global_insn->rd->type != TY_char && + global_insn->rd->type != TY_bool) { + GLOBAL_FUNC->stack_size += + align_size(global_insn->rd->type->size); + } else + /* 'char' is aligned to one byte for the convenience */ GLOBAL_FUNC->stack_size += PTR_SIZE; - src0 = GLOBAL_FUNC->stack_size; /* base of backing region */ - - /* Stash base offset for this array variable */ - global_insn->rd->init_val = src0; - - if (global_insn->rd->ptr_level) - GLOBAL_FUNC->stack_size += - align_size(PTR_SIZE * global_insn->rd->array_size); - else { - GLOBAL_FUNC->stack_size += - align_size(global_insn->rd->array_size * - global_insn->rd->type->size); + } + break; + case OP_load_constant: + case OP_load_data_address: + case OP_load_rodata_address: + dest = prepare_dest(GLOBAL_FUNC->bbs, NULL, global_insn->rd, -1, -1); + ir = bb_add_ph2_ir(GLOBAL_FUNC->bbs, global_insn->opcode); + ir->src0 = global_insn->rd->init_val; + ir->dest = dest; + break; + case OP_assign: + src0 = prepare_operand(GLOBAL_FUNC->bbs, global_insn->rs1, -1); + dest = prepare_dest(GLOBAL_FUNC->bbs, NULL, global_insn->rd, src0, -1); + ir = bb_add_ph2_ir(GLOBAL_FUNC->bbs, OP_assign); + ir->src0 = src0; + ir->dest = dest; + spill_var(GLOBAL_FUNC->bbs, global_insn->rd, dest); + /* release the unused constant number in register manually */ + REGS[src0].polluted = 0; + vreg_clear_phys(REGS[src0].var); + REGS[src0].var = NULL; + break; + case OP_add: { + /* Special-case address computation for globals: if rs1 is a global base + * and rs2 is a constant, propagate absolute offset to rd so OP_write + * can fold into OP_global_store. + */ + if (global_insn->rs1 && global_insn->rs1->is_global && + global_insn->rs2) { + int base_off = global_insn->rs1->offset; + + /* For global arrays, use backing-region base cached in init_val */ + if (global_insn->rs1->array_size > 0) + base_off = global_insn->rs1->init_val; + global_insn->rd->offset = base_off + global_insn->rs2->init_val; + global_insn->rd->space_is_allocated = true; + global_insn->rd->is_global = true; + break; + } + /* Fallback: generate an add */ + int src1; + src0 = prepare_operand(GLOBAL_FUNC->bbs, global_insn->rs1, -1); + src1 = prepare_operand(GLOBAL_FUNC->bbs, global_insn->rs2, src0); + dest = + prepare_dest(GLOBAL_FUNC->bbs, NULL, global_insn->rd, src0, src1); + ir = bb_add_ph2_ir(GLOBAL_FUNC->bbs, OP_add); + ir->src0 = src0; + ir->src1 = src1; + ir->dest = dest; + break; + } + case OP_write: { + /* Fold (addr, val) where addr carries GP-relative offset */ + if (global_insn->rs1 && (global_insn->rs1->is_global)) { + int vreg = prepare_operand(GLOBAL_FUNC->bbs, global_insn->rs2, -1); + ir = bb_add_ph2_ir(GLOBAL_FUNC->bbs, OP_global_store); + ir->src0 = vreg; + + /* For array variables used as base, store to the backing region's + * base offset (cached in init_val). + */ + int base_off = global_insn->rs1->offset; + if (global_insn->rs1->array_size > 0) + base_off = global_insn->rs1->init_val; + ir->src1 = base_off; + break; + } + /* Fallback generic write */ + int src1; + src0 = prepare_operand(GLOBAL_FUNC->bbs, global_insn->rs1, -1); + src1 = prepare_operand(GLOBAL_FUNC->bbs, global_insn->rs2, src0); + ir = bb_add_ph2_ir(GLOBAL_FUNC->bbs, OP_write); + ir->src0 = src0; + ir->src1 = src1; + ir->dest = global_insn->sz; + break; + } + case OP_trunc: + case OP_sign_ext: + case OP_cast: + /* A narrowing initializer such as "char g[] = {65, 66}" reaches the + * global block as a conversion, so it has to be lowered here exactly as + * it is inside a function. + */ + src0 = prepare_operand(GLOBAL_FUNC->bbs, global_insn->rs1, -1); + dest = prepare_dest(GLOBAL_FUNC->bbs, NULL, global_insn->rd, src0, -1); + ir = bb_add_ph2_ir(GLOBAL_FUNC->bbs, global_insn->opcode); + ir->src0 = src0; + ir->src1 = global_insn->sz; + ir->dest = dest; + break; + default: + printf("Unsupported global operation: %d\n", global_insn->opcode); + fflush(stdout); /* see fatal() */ + abort(); + } +} + +/* Assign registers across one basic block, and emit the phase-2 IR that carries + * the assignment. + */ +void reg_alloc_bb(func_t *func, basic_block_t *bb) +{ + bool handle_abi = false, args_on_stack = false; + + is_pushing_args = false; + int args = 0; + + bb->visited++; + + /* The entry block starts with the incoming arguments already in their + * registers; every other block takes what its predecessor handed over, or + * nothing. + */ + if (bb != func->bbs) + load_entry_regs(bb); + + for (insn_t *insn = bb->insn_list.head; insn; insn = insn->next) { + func_t *callee_func; + ph2_ir_t *ir; + int dest, src0, src1; + int sz, clear_reg; + + refresh(bb, insn); + + switch (insn->opcode) { + case OP_unwound_phi: + track_var_use(insn->rs1, insn->idx); + + /* A pinned destination lives in the same register on every path, so + * the copy this phi stands for is a register move rather than a + * write into a slot nothing reads back. + */ + int to = pinned_reg_of(insn->rd); + if (to >= 0) { + src0 = prepare_operand(bb, insn->rs1, -1); + if (src0 != to) { + ir = bb_add_ph2_ir(bb, OP_assign); + ir->src0 = src0; + ir->dest = to; + ir->is_pointer = is_pointer_like(insn->rd); + ir->size_bytes = var_slot_size(insn->rd); } + REGS[to].var = insn->rd; + REGS[to].polluted = 1; + break; + } - dest = prepare_dest(GLOBAL_FUNC->bbs, NULL, global_insn->rd, -1, - -1); - ir = bb_add_ph2_ir(GLOBAL_FUNC->bbs, OP_global_address_of); - ir->src0 = src0; - ir->dest = dest; - ir->is_pointer = true; - ir->size_bytes = PTR_SIZE; - spill_var(GLOBAL_FUNC->bbs, global_insn->rd, dest); - } else { - global_insn->rd->offset = GLOBAL_FUNC->stack_size; - global_insn->rd->space_is_allocated = true; - if (global_insn->rd->ptr_level) - GLOBAL_FUNC->stack_size += PTR_SIZE; - else if (global_insn->rd->type != TY_int && - global_insn->rd->type != TY_short && - global_insn->rd->type != TY_char && - global_insn->rd->type != TY_bool) { - GLOBAL_FUNC->stack_size += - align_size(global_insn->rd->type->size); - } else - /* 'char' is aligned to one byte for the convenience */ - GLOBAL_FUNC->stack_size += PTR_SIZE; + if (!insn->rd->space_is_allocated) + alloc_var_slot(bb->belong_to, insn->rd); + + /* Sharing a slot with the phi turns the copy into a write of the + * operand into the place it already lives. Only a register the + * block has changed still needs storing -- and reading the slot + * back first, as the general path would, is a load whose value goes + * straight home again. + */ + if (insn->rs1->space_is_allocated && + insn->rs1->offset == insn->rd->offset && + insn->rs1->ofs_based_on_stack_top == + insn->rd->ofs_based_on_stack_top) { + int held = find_in_regs(insn->rs1); + + if (held < 0 || !REGS[held].polluted) + break; /* the slot already holds the value */ + store_var(bb, insn->rs1, held); + break; } + + src0 = prepare_operand(bb, insn->rs1, -1); + ir = bb_add_ph2_ir(bb, OP_store); + ir->src0 = src0; + ir->src1 = insn->rd->offset; + ir->ofs_based_on_stack_top = insn->rd->ofs_based_on_stack_top; + ir->is_pointer = is_pointer_like(insn->rd); + ir->size_bytes = var_slot_size(insn->rd); + break; + case OP_allocat: + if ((insn->rd->type == TY_void || insn->rd->type == TY_int || + insn->rd->type == TY_short || insn->rd->type == TY_char || + insn->rd->type == TY_bool) && + insn->rd->array_size == 0) + break; + + insn->rd->offset = func->stack_size; + insn->rd->space_is_allocated = true; + func->stack_size += PTR_SIZE; + src0 = func->stack_size; + + if (insn->rd->ptr_level) + sz = PTR_SIZE; + else { + sz = insn->rd->type->size; + } + + if (insn->rd->array_size) + func->stack_size += align_size(insn->rd->array_size * sz); + else + func->stack_size += align_size(sz); + + if (!insn->rd->is_global && + aggregate_has_function_pointer(insn->rd->type)) { + insn->rd->has_backing_storage = true; + } + + dest = prepare_dest(bb, insn, insn->rd, -1, -1); + ir = bb_add_ph2_ir(bb, OP_address_of); + ir->src0 = src0; + ir->dest = dest; + ir->ofs_based_on_stack_top = insn->rd->ofs_based_on_stack_top; + + /* For arrays, store the base address just like global arrays do */ + if (insn->rd->array_size) + spill_var(bb, insn->rd, dest); break; case OP_load_constant: case OP_load_data_address: case OP_load_rodata_address: - dest = - prepare_dest(GLOBAL_FUNC->bbs, NULL, global_insn->rd, -1, -1); - ir = bb_add_ph2_ir(GLOBAL_FUNC->bbs, global_insn->opcode); - ir->src0 = global_insn->rd->init_val; + dest = prepare_dest(bb, insn, insn->rd, -1, -1); + ir = bb_add_ph2_ir(bb, insn->opcode); + ir->src0 = insn->rd->init_val; ir->dest = dest; + + /* store global variable immediately after assignment */ + if (insn->rd->is_global) { + ir = bb_add_ph2_ir(bb, OP_global_store); + ir->src0 = dest; + ir->src1 = insn->rd->offset; + REGS[dest].polluted = 0; + } + break; - case OP_assign: - src0 = prepare_operand(GLOBAL_FUNC->bbs, global_insn->rs1, -1); - dest = - prepare_dest(GLOBAL_FUNC->bbs, NULL, global_insn->rd, src0, -1); - ir = bb_add_ph2_ir(GLOBAL_FUNC->bbs, OP_assign); - ir->src0 = src0; + case OP_address_of: + case OP_global_address_of: + /* Mark variable as address-taken, disable constant optimization */ + insn->rs1->address_taken = true; + insn->rs1->is_const = false; + + /* OP_allocat puts a local aggregate's spill slot before its backing + * storage. &aggregate must name the backing storage, not the spill + * slot. + * + * FIXME: This does not support aggregate parameter for now. + */ + bool is_pointer = insn->rs1->ptr_level || + (insn->rs1->type && insn->rs1->type->ptr_level); + if (!insn->rs1->is_global && !is_pointer && + aggregate_has_function_pointer(insn->rs1->type)) { + if (!insn->rs1->has_backing_storage) { + insn->rs1->offset = func->stack_size; + insn->rs1->space_is_allocated = true; + insn->rs1->ofs_based_on_stack_top = false; + func->stack_size += PTR_SIZE; + if (insn->rs1->ptr_level) + sz = PTR_SIZE; + else + sz = insn->rs1->type->size; + if (insn->rs1->array_size) + func->stack_size += + align_size(insn->rs1->array_size * sz); + else + func->stack_size += align_size(sz); + insn->rs1->has_backing_storage = true; + } + + dest = prepare_dest(bb, insn, insn->rd, -1, -1); + ir = bb_add_ph2_ir(bb, OP_address_of); + ir->src0 = insn->rs1->offset + PTR_SIZE; + ir->dest = dest; + ir->ofs_based_on_stack_top = insn->rs1->ofs_based_on_stack_top; + break; + } + + /* make sure variable is on stack */ + if (!insn->rs1->space_is_allocated) { + alloc_var_slot(bb->belong_to, insn->rs1); + + for (int i = 0; i < REG_CNT; i++) + if (REGS[i].var == insn->rs1 && !pinned_base[i]) { + ir = bb_add_ph2_ir(bb, OP_store); + ir->src0 = i; + ir->src1 = insn->rs1->offset; + ir->ofs_based_on_stack_top = + insn->rs1->ofs_based_on_stack_top; + /* Clear stale register tracking */ + REGS[i].var = NULL; + } + } + + dest = prepare_dest(bb, insn, insn->rd, -1, -1); + if (insn->rs1->is_global || insn->opcode == OP_global_address_of) + ir = bb_add_ph2_ir(bb, OP_global_address_of); + else + ir = bb_add_ph2_ir(bb, OP_address_of); + ir->src0 = insn->rs1->offset; ir->dest = dest; - spill_var(GLOBAL_FUNC->bbs, global_insn->rd, dest); - /* release the unused constant number in register manually */ - REGS[src0].polluted = 0; - vreg_clear_phys(REGS[src0].var); - REGS[src0].var = NULL; - break; - case OP_add: { - /* Special-case address computation for globals: if rs1 is a global - * base and rs2 is a constant, propagate absolute offset to rd so - * OP_write can fold into OP_global_store. + ir->ofs_based_on_stack_top = insn->rs1->ofs_based_on_stack_top; + break; + case OP_cmov: { + /* A select reads three registers, one more than the allocator's + * avoid arguments can protect, so each is locked as it is placed. + * With all three safe the destination may land anywhere. */ - if (global_insn->rs1 && global_insn->rs1->is_global && - global_insn->rs2) { - int base_off = global_insn->rs1->offset; + int cond, taken, other; + + track_var_use(insn->rs1, insn->idx); + track_var_use(insn->rs2, insn->idx); + track_var_use(insn->rs3, insn->idx); + + reg_locked = 0; + taken = prepare_operand(bb, insn->rs1, -1); + reg_locked = reg_locked | (1 << taken); + other = prepare_operand(bb, insn->rs3, taken); + reg_locked = reg_locked | (1 << other); + cond = prepare_operand(bb, insn->rs2, taken); + reg_locked = reg_locked | (1 << cond); + dest = prepare_dest(bb, insn, insn->rd, taken, other); + + if (dest < 0) { + /* A select needs a fourth register only while all three inputs + * remain live. Save one unpinned input first, then use its + * physical register as the result. The CMOV emitter + * deliberately supports the destination aliasing either arm; it + * tests the condition before overwriting anything, so the + * condition is safe too if it is the only choice. + * + * spill_var() leaves the machine register unchanged, which is + * exactly what the select still needs. It only removes the + * allocator's association, making the value available for the + * result and forcing a later use of the saved input to reload + * its slot. + */ + int reuse = -1; + const int sources[] = {taken, other, cond}; - /* For global arrays, use backing-region base cached in init_val + for (int i = 0; i < 3; i++) { + int reg = sources[i]; + if (!pinned_base[reg]) { + reuse = reg; + break; + } + } + + /* pin_registers() reserves at most half the file, so one of a + * select's inputs is always reclaimable. */ - if (global_insn->rs1->array_size > 0) - base_off = global_insn->rs1->init_val; - global_insn->rd->offset = base_off + global_insn->rs2->init_val; - global_insn->rd->space_is_allocated = true; - global_insn->rd->is_global = true; + if (reuse < 0) + abort(); + spill_var(bb, REGS[reuse].var, reuse); + dest = prepare_dest(bb, insn, insn->rd, taken, other); + if (dest != reuse) + abort(); + } + reg_locked = 0; + ir = bb_add_ph2_ir(bb, OP_cmov); + ir->src0 = cond; + ir->src1 = taken; + ir->src2 = other; + ir->dest = dest; + ir->size_bytes = var_slot_size(insn->rd); + ir->is_pointer = is_pointer_like(insn->rd); + break; + } + case OP_assign: + if (insn->rd->consumed == -1) break; + + track_var_use(insn->rs1, insn->idx); + src0 = find_in_regs(insn->rs1); + + /* If operand is loaded from stack, clear the original slot after + * moving. + */ + if (src0 > -1) + clear_reg = 0; + else { + clear_reg = 1; + src0 = prepare_operand(bb, insn->rs1, -1); } - /* Fallback: generate an add */ - int src1; - src0 = prepare_operand(GLOBAL_FUNC->bbs, global_insn->rs1, -1); - src1 = prepare_operand(GLOBAL_FUNC->bbs, global_insn->rs2, src0); - dest = prepare_dest(GLOBAL_FUNC->bbs, NULL, global_insn->rd, src0, - src1); - ir = bb_add_ph2_ir(GLOBAL_FUNC->bbs, OP_add); + dest = prepare_dest(bb, insn, insn->rd, src0, -1); + ir = bb_add_ph2_ir(bb, OP_assign); ir->src0 = src0; - ir->src1 = src1; ir->dest = dest; + + /* store global variable immediately after assignment */ + if (insn->rd->is_global) { + ir = bb_add_ph2_ir(bb, OP_global_store); + ir->src0 = dest; + ir->src1 = insn->rd->offset; + REGS[dest].polluted = 0; + } + + if (clear_reg) { + vreg_clear_phys(REGS[src0].var); + REGS[src0].var = NULL; + } + break; - } - case OP_write: { - /* Fold (addr, val) where addr carries GP-relative offset */ - if (global_insn->rs1 && (global_insn->rs1->is_global)) { - int vreg = - prepare_operand(GLOBAL_FUNC->bbs, global_insn->rs2, -1); - ir = bb_add_ph2_ir(GLOBAL_FUNC->bbs, OP_global_store); - ir->src0 = vreg; - - /* For array variables used as base, store to the backing - * region's base offset (cached in init_val). + case OP_read: + src0 = prepare_operand(bb, insn->rs1, -1); + dest = prepare_dest(bb, insn, insn->rd, src0, -1); + ir = bb_add_ph2_ir(bb, OP_read); + ir->src0 = src0; + ir->src1 = insn->sz; + ir->dest = dest; + break; + case OP_write: + if (insn->rs2->is_func) { + src0 = prepare_operand(bb, insn->rs1, -1); + ir = bb_add_ph2_ir(bb, OP_address_of_func); + ir->src0 = src0; + ir->func_name = intern_string(insn->rs2->var_name); + if (dynlink) { + func_t *target_fn = find_func(ir->func_name); + if (target_fn) + target_fn->is_used = true; + } + } else { + /* FIXME: Register content becomes stale after store operation. + * Current workaround causes redundant spilling - need better + * register invalidation strategy. */ - int base_off = global_insn->rs1->offset; - if (global_insn->rs1->array_size > 0) - base_off = global_insn->rs1->init_val; - ir->src1 = base_off; - break; + spill_alive(bb, insn); + src0 = prepare_operand(bb, insn->rs1, -1); + src1 = prepare_operand(bb, insn->rs2, src0); + ir = bb_add_ph2_ir(bb, OP_write); + ir->src0 = src0; + ir->src1 = src1; + ir->dest = insn->sz; + } + break; + case OP_branch: + src0 = prepare_operand(bb, insn->rs1, -1); + + /* REGS[src0].var had been set to NULL, but the actual content is + * still holded in the register. + * + * Write every live-out value back but keep it in its register: the + * arm reached by the jump starts with an empty file and loads from + * the slots just written, while the arm that falls through can + * inherit the registers through bb_export_regs(). + */ + spill_live_out_keep(bb); + + ir = bb_add_ph2_ir(bb, OP_branch); + ir->src0 = src0; + ir->then_bb = bb->then_; + ir->else_bb = bb->else_; + break; + case OP_push: + extend_liveness(bb, insn, insn->rs1, insn->sz); + + if (!is_pushing_args) { + spill_alive(bb, insn); + is_pushing_args = true; + } + if (!handle_abi) { + args_on_stack = abi_lower_call_args(bb, insn); + handle_abi = true; } - /* Fallback generic write */ - int src1; - src0 = prepare_operand(GLOBAL_FUNC->bbs, global_insn->rs1, -1); - src1 = prepare_operand(GLOBAL_FUNC->bbs, global_insn->rs2, src0); - ir = bb_add_ph2_ir(GLOBAL_FUNC->bbs, OP_write); + + if (args_on_stack && args >= MAX_ARGS_IN_REG) + break; + + src0 = prepare_operand(bb, insn->rs1, -1); + ir = bb_add_ph2_ir(bb, OP_assign); + ir->src0 = src0; + ir->dest = args++; + REGS[ir->dest].var = insn->rs1; + REGS[ir->dest].polluted = 0; + break; + case OP_call: + callee_func = find_func(insn->str); + if (!callee_func->num_params) + spill_alive(bb, insn); + + if (dynlink) + callee_func->is_used = true; + + ir = bb_add_ph2_ir(bb, OP_call); + /* add_insn() interned this when the call was created. */ + ir->func_name = insn->str; + + is_pushing_args = false; + args = 0; + handle_abi = false; + + clobber_caller_saved(); + + break; + case OP_indirect: + if (!args) + spill_alive(bb, insn); + + src0 = prepare_operand(bb, insn->rs1, -1); + ir = bb_add_ph2_ir(bb, OP_load_func); + ir->src0 = src0; + + bb_add_ph2_ir(bb, OP_indirect); + + is_pushing_args = false; + args = 0; + handle_abi = false; + + clobber_caller_saved(); + break; + case OP_func_ret: + dest = prepare_dest(bb, insn, insn->rd, -1, -1); + ir = bb_add_ph2_ir(bb, OP_assign); + ir->src0 = 0; + ir->dest = dest; + break; + case OP_return: + if (insn->rs1) + src0 = prepare_operand(bb, insn->rs1, -1); + else + src0 = -1; + + ir = bb_add_ph2_ir(bb, OP_return); + ir->src0 = src0; + break; + case OP_add: + case OP_sub: + case OP_mul: + case OP_div: + case OP_mod: + case OP_lshift: + case OP_rshift: + case OP_eq: + case OP_neq: + case OP_gt: + case OP_geq: + case OP_lt: + case OP_leq: + case OP_bit_and: + case OP_bit_or: + case OP_bit_xor: + track_var_use(insn->rs1, insn->idx); + track_var_use(insn->rs2, insn->idx); + src0 = prepare_operand(bb, insn->rs1, -1); + src1 = prepare_operand(bb, insn->rs2, src0); + dest = prepare_dest(bb, insn, insn->rd, src0, src1); + ir = bb_add_ph2_ir(bb, insn->opcode); ir->src0 = src0; ir->src1 = src1; - ir->dest = global_insn->sz; + ir->dest = dest; + + /* Record whether the result is an address. On LP64 an int-typed + * result has to wrap at 32 bits, while a pointer must keep all + * 64. The backend cannot tell the two apart without this. + */ + ir->is_pointer = is_pointer_like(insn->rd) || + is_pointer_like(insn->rs1) || + is_pointer_like(insn->rs2); + break; + case OP_negate: + case OP_bit_not: + case OP_log_not: + src0 = prepare_operand(bb, insn->rs1, -1); + dest = prepare_dest(bb, insn, insn->rd, src0, -1); + ir = bb_add_ph2_ir(bb, insn->opcode); + ir->src0 = src0; + ir->dest = dest; break; - } case OP_trunc: case OP_sign_ext: case OP_cast: - /* A narrowing initializer such as "char g[] = {65, 66}" reaches the - * global block as a conversion, so it has to be lowered here - * exactly as it is inside a function. - */ - src0 = prepare_operand(GLOBAL_FUNC->bbs, global_insn->rs1, -1); - dest = - prepare_dest(GLOBAL_FUNC->bbs, NULL, global_insn->rd, src0, -1); - ir = bb_add_ph2_ir(GLOBAL_FUNC->bbs, global_insn->opcode); + src0 = prepare_operand(bb, insn->rs1, -1); + dest = prepare_dest(bb, insn, insn->rd, src0, -1); + ir = bb_add_ph2_ir(bb, insn->opcode); + ir->src1 = insn->sz; ir->src0 = src0; - ir->src1 = global_insn->sz; ir->dest = dest; break; default: - printf("Unsupported global operation: %d\n", global_insn->opcode); + printf("Unknown opcode\n"); + fflush(stdout); /* see fatal() */ abort(); } } + if (bb->next) { + spill_live_out_keep(bb); + bb_export_regs(bb); + } else if (bb->then_ || bb->else_) { + /* A conditional branch has already written its live-out values back at + * OP_branch; only the handover is left. + */ + bb_export_regs(bb); + } + + if (bb == func->exit) + return; + + /* append jump instruction for the normal block only */ + if (!bb->next) + return; + + if (bb->next == func->exit) + return; + + /* jump to the beginning of loop or over the else block */ + if (bb->next->visited == func->visited || bb->next->rpo != bb->rpo + 1) { + ph2_ir_t *ir = bb_add_ph2_ir(bb, OP_jump); + ir->next_bb = bb->next; + } +} + +void reg_alloc(void) +{ + /* TODO: Add proper .bss and .data section support for uninitialized / + * initialized globals + */ + for (insn_t *global_insn = GLOBAL_FUNC->bbs->insn_list.head; global_insn; + global_insn = global_insn->next) { + reg_alloc_global(global_insn); + } + for (func_t *func = FUNC_LIST.head; func; func = func->next) { /* Skip function declarations without bodies */ if (!func->bbs) @@ -2274,527 +2793,7 @@ void reg_alloc(void) } for (basic_block_t *bb = func->bbs; bb; bb = bb->rpo_next) { - bool handle_abi = false, args_on_stack = false; - - is_pushing_args = false; - int args = 0; - - bb->visited++; - - /* The entry block starts with the incoming arguments already in - * their registers; every other block takes what its predecessor - * handed over, or nothing. - */ - if (bb != func->bbs) - load_entry_regs(bb); - - for (insn_t *insn = bb->insn_list.head; insn; insn = insn->next) { - func_t *callee_func; - ph2_ir_t *ir; - int dest, src0, src1; - int sz, clear_reg; - - refresh(bb, insn); - - switch (insn->opcode) { - case OP_unwound_phi: - track_var_use(insn->rs1, insn->idx); - - /* A pinned destination lives in the same register on every - * path, so the copy this phi stands for is a register move - * rather than a write into a slot nothing reads back. - */ - int to = pinned_reg_of(insn->rd); - if (to >= 0) { - src0 = prepare_operand(bb, insn->rs1, -1); - if (src0 != to) { - ir = bb_add_ph2_ir(bb, OP_assign); - ir->src0 = src0; - ir->dest = to; - ir->is_pointer = is_pointer_like(insn->rd); - ir->size_bytes = var_slot_size(insn->rd); - } - REGS[to].var = insn->rd; - REGS[to].polluted = 1; - break; - } - - if (!insn->rd->space_is_allocated) - alloc_var_slot(bb->belong_to, insn->rd); - - /* Sharing a slot with the phi turns the copy into a write - * of the operand into the place it already lives. Only a - * register the block has changed still needs storing -- and - * reading the slot back first, as the general path would, - * is a load whose value goes straight home again. - */ - if (insn->rs1->space_is_allocated && - insn->rs1->offset == insn->rd->offset && - insn->rs1->ofs_based_on_stack_top == - insn->rd->ofs_based_on_stack_top) { - int held = find_in_regs(insn->rs1); - - if (held < 0 || !REGS[held].polluted) - break; /* the slot already holds the value */ - store_var(bb, insn->rs1, held); - break; - } - - src0 = prepare_operand(bb, insn->rs1, -1); - ir = bb_add_ph2_ir(bb, OP_store); - ir->src0 = src0; - ir->src1 = insn->rd->offset; - ir->ofs_based_on_stack_top = - insn->rd->ofs_based_on_stack_top; - ir->is_pointer = is_pointer_like(insn->rd); - ir->size_bytes = var_slot_size(insn->rd); - break; - case OP_allocat: - if ((insn->rd->type == TY_void || - insn->rd->type == TY_int || - insn->rd->type == TY_short || - insn->rd->type == TY_char || - insn->rd->type == TY_bool) && - insn->rd->array_size == 0) - break; - - insn->rd->offset = func->stack_size; - insn->rd->space_is_allocated = true; - func->stack_size += PTR_SIZE; - src0 = func->stack_size; - - if (insn->rd->ptr_level) - sz = PTR_SIZE; - else { - sz = insn->rd->type->size; - } - - if (insn->rd->array_size) - func->stack_size += - align_size(insn->rd->array_size * sz); - else - func->stack_size += align_size(sz); - - if (!insn->rd->is_global && - aggregate_has_function_pointer(insn->rd->type)) { - insn->rd->has_backing_storage = true; - } - - dest = prepare_dest(bb, insn, insn->rd, -1, -1); - ir = bb_add_ph2_ir(bb, OP_address_of); - ir->src0 = src0; - ir->dest = dest; - ir->ofs_based_on_stack_top = - insn->rd->ofs_based_on_stack_top; - - /* For arrays, store the base address just like global - * arrays do - */ - if (insn->rd->array_size) - spill_var(bb, insn->rd, dest); - break; - case OP_load_constant: - case OP_load_data_address: - case OP_load_rodata_address: - dest = prepare_dest(bb, insn, insn->rd, -1, -1); - ir = bb_add_ph2_ir(bb, insn->opcode); - ir->src0 = insn->rd->init_val; - ir->dest = dest; - - /* store global variable immediately after assignment */ - if (insn->rd->is_global) { - ir = bb_add_ph2_ir(bb, OP_global_store); - ir->src0 = dest; - ir->src1 = insn->rd->offset; - REGS[dest].polluted = 0; - } - - break; - case OP_address_of: - case OP_global_address_of: - /* Mark variable as address-taken, disable constant - * optimization - */ - insn->rs1->address_taken = true; - insn->rs1->is_const = false; - - /* OP_allocat puts a local aggregate's spill slot before its - * backing storage. &aggregate must name the backing - * storage, not the spill slot. - * - * FIXME: This does not support aggregate parameter for now. - */ - bool is_pointer = - insn->rs1->ptr_level || - (insn->rs1->type && insn->rs1->type->ptr_level); - if (!insn->rs1->is_global && !is_pointer && - aggregate_has_function_pointer(insn->rs1->type)) { - if (!insn->rs1->has_backing_storage) { - insn->rs1->offset = func->stack_size; - insn->rs1->space_is_allocated = true; - insn->rs1->ofs_based_on_stack_top = false; - func->stack_size += PTR_SIZE; - if (insn->rs1->ptr_level) - sz = PTR_SIZE; - else - sz = insn->rs1->type->size; - if (insn->rs1->array_size) - func->stack_size += - align_size(insn->rs1->array_size * sz); - else - func->stack_size += align_size(sz); - insn->rs1->has_backing_storage = true; - } - - dest = prepare_dest(bb, insn, insn->rd, -1, -1); - ir = bb_add_ph2_ir(bb, OP_address_of); - ir->src0 = insn->rs1->offset + PTR_SIZE; - ir->dest = dest; - ir->ofs_based_on_stack_top = - insn->rs1->ofs_based_on_stack_top; - break; - } - - /* make sure variable is on stack */ - if (!insn->rs1->space_is_allocated) { - alloc_var_slot(bb->belong_to, insn->rs1); - - for (int i = 0; i < REG_CNT; i++) - if (REGS[i].var == insn->rs1 && !pinned_base[i]) { - ir = bb_add_ph2_ir(bb, OP_store); - ir->src0 = i; - ir->src1 = insn->rs1->offset; - ir->ofs_based_on_stack_top = - insn->rs1->ofs_based_on_stack_top; - /* Clear stale register tracking */ - REGS[i].var = NULL; - } - } - - dest = prepare_dest(bb, insn, insn->rd, -1, -1); - if (insn->rs1->is_global || - insn->opcode == OP_global_address_of) - ir = bb_add_ph2_ir(bb, OP_global_address_of); - else - ir = bb_add_ph2_ir(bb, OP_address_of); - ir->src0 = insn->rs1->offset; - ir->dest = dest; - ir->ofs_based_on_stack_top = - insn->rs1->ofs_based_on_stack_top; - break; - case OP_cmov: { - /* A select reads three registers, one more than the - * allocator's avoid arguments can protect, so each is - * locked as it is placed. With all three safe the - * destination may land anywhere. - */ - int cond, taken, other; - - track_var_use(insn->rs1, insn->idx); - track_var_use(insn->rs2, insn->idx); - track_var_use(insn->rs3, insn->idx); - - reg_locked = 0; - taken = prepare_operand(bb, insn->rs1, -1); - reg_locked = reg_locked | (1 << taken); - other = prepare_operand(bb, insn->rs3, taken); - reg_locked = reg_locked | (1 << other); - cond = prepare_operand(bb, insn->rs2, taken); - reg_locked = reg_locked | (1 << cond); - dest = prepare_dest(bb, insn, insn->rd, taken, other); - - if (dest < 0) { - /* A select needs a fourth register only while all - * three inputs remain live. Save one unpinned input - * first, then use its physical register as the - * result. The CMOV emitter deliberately supports the - * destination aliasing either arm; it tests the - * condition before overwriting anything, so the - * condition is safe too if it is the only choice. - * - * spill_var() leaves the machine register unchanged, - * which is exactly what the select still needs. It - * only removes the allocator's association, making - * the value available for the result and forcing a - * later use of the saved input to reload its slot. - */ - int reuse = -1; - int sources[] = {taken, other, cond}; - - for (int i = 0; i < 3; i++) { - int reg = sources[i]; - if (!pinned_base[reg]) { - reuse = reg; - break; - } - } - - /* pin_registers() reserves at most half the file, so - * one of a select's inputs is always reclaimable. - */ - if (reuse < 0) - abort(); - spill_var(bb, REGS[reuse].var, reuse); - dest = prepare_dest(bb, insn, insn->rd, taken, other); - if (dest != reuse) - abort(); - } - reg_locked = 0; - ir = bb_add_ph2_ir(bb, OP_cmov); - ir->src0 = cond; - ir->src1 = taken; - ir->src2 = other; - ir->dest = dest; - ir->size_bytes = var_slot_size(insn->rd); - ir->is_pointer = is_pointer_like(insn->rd); - break; - } - case OP_assign: - if (insn->rd->consumed == -1) - break; - - track_var_use(insn->rs1, insn->idx); - src0 = find_in_regs(insn->rs1); - - /* If operand is loaded from stack, clear the original slot - * after moving. - */ - if (src0 > -1) - clear_reg = 0; - else { - clear_reg = 1; - src0 = prepare_operand(bb, insn->rs1, -1); - } - dest = prepare_dest(bb, insn, insn->rd, src0, -1); - ir = bb_add_ph2_ir(bb, OP_assign); - ir->src0 = src0; - ir->dest = dest; - - /* store global variable immediately after assignment */ - if (insn->rd->is_global) { - ir = bb_add_ph2_ir(bb, OP_global_store); - ir->src0 = dest; - ir->src1 = insn->rd->offset; - REGS[dest].polluted = 0; - } - - if (clear_reg) { - vreg_clear_phys(REGS[src0].var); - REGS[src0].var = NULL; - } - - break; - case OP_read: - src0 = prepare_operand(bb, insn->rs1, -1); - dest = prepare_dest(bb, insn, insn->rd, src0, -1); - ir = bb_add_ph2_ir(bb, OP_read); - ir->src0 = src0; - ir->src1 = insn->sz; - ir->dest = dest; - break; - case OP_write: - if (insn->rs2->is_func) { - src0 = prepare_operand(bb, insn->rs1, -1); - ir = bb_add_ph2_ir(bb, OP_address_of_func); - ir->src0 = src0; - ir->func_name = intern_string(insn->rs2->var_name); - if (dynlink) { - func_t *target_fn = find_func(ir->func_name); - if (target_fn) - target_fn->is_used = true; - } - } else { - /* FIXME: Register content becomes stale after store - * operation. Current workaround causes redundant - * spilling - need better register invalidation - * strategy. - */ - spill_alive(bb, insn); - src0 = prepare_operand(bb, insn->rs1, -1); - src1 = prepare_operand(bb, insn->rs2, src0); - ir = bb_add_ph2_ir(bb, OP_write); - ir->src0 = src0; - ir->src1 = src1; - ir->dest = insn->sz; - } - break; - case OP_branch: - src0 = prepare_operand(bb, insn->rs1, -1); - - /* REGS[src0].var had been set to NULL, but the actual - * content is still holded in the register. - * - * Write every live-out value back but keep it in its - * register: the arm reached by the jump starts with an - * empty file and loads from the slots just written, while - * the arm that falls through can inherit the registers - * through bb_export_regs(). - */ - spill_live_out_keep(bb); - - ir = bb_add_ph2_ir(bb, OP_branch); - ir->src0 = src0; - ir->then_bb = bb->then_; - ir->else_bb = bb->else_; - break; - case OP_push: - extend_liveness(bb, insn, insn->rs1, insn->sz); - - if (!is_pushing_args) { - spill_alive(bb, insn); - is_pushing_args = true; - } - if (!handle_abi) { - args_on_stack = abi_lower_call_args(bb, insn); - handle_abi = true; - } - - if (args_on_stack && args >= MAX_ARGS_IN_REG) - break; - - src0 = prepare_operand(bb, insn->rs1, -1); - ir = bb_add_ph2_ir(bb, OP_assign); - ir->src0 = src0; - ir->dest = args++; - REGS[ir->dest].var = insn->rs1; - REGS[ir->dest].polluted = 0; - break; - case OP_call: - callee_func = find_func(insn->str); - if (!callee_func->num_params) - spill_alive(bb, insn); - - if (dynlink) - callee_func->is_used = true; - - ir = bb_add_ph2_ir(bb, OP_call); - /* add_insn() interned this when the call was created. */ - ir->func_name = insn->str; - - is_pushing_args = false; - args = 0; - handle_abi = false; - - clobber_caller_saved(); - - break; - case OP_indirect: - if (!args) - spill_alive(bb, insn); - - src0 = prepare_operand(bb, insn->rs1, -1); - ir = bb_add_ph2_ir(bb, OP_load_func); - ir->src0 = src0; - - bb_add_ph2_ir(bb, OP_indirect); - - is_pushing_args = false; - args = 0; - handle_abi = false; - - clobber_caller_saved(); - break; - case OP_func_ret: - dest = prepare_dest(bb, insn, insn->rd, -1, -1); - ir = bb_add_ph2_ir(bb, OP_assign); - ir->src0 = 0; - ir->dest = dest; - break; - case OP_return: - if (insn->rs1) - src0 = prepare_operand(bb, insn->rs1, -1); - else - src0 = -1; - - ir = bb_add_ph2_ir(bb, OP_return); - ir->src0 = src0; - break; - case OP_add: - case OP_sub: - case OP_mul: - case OP_div: - case OP_mod: - case OP_lshift: - case OP_rshift: - case OP_eq: - case OP_neq: - case OP_gt: - case OP_geq: - case OP_lt: - case OP_leq: - case OP_bit_and: - case OP_bit_or: - case OP_bit_xor: - track_var_use(insn->rs1, insn->idx); - track_var_use(insn->rs2, insn->idx); - src0 = prepare_operand(bb, insn->rs1, -1); - src1 = prepare_operand(bb, insn->rs2, src0); - dest = prepare_dest(bb, insn, insn->rd, src0, src1); - ir = bb_add_ph2_ir(bb, insn->opcode); - ir->src0 = src0; - ir->src1 = src1; - ir->dest = dest; - - /* Record whether the result is an address. On LP64 an - * int-typed result has to wrap at 32 bits, while a pointer - * must keep all 64. The backend cannot tell the two apart - * without this. - */ - ir->is_pointer = is_pointer_like(insn->rd) || - is_pointer_like(insn->rs1) || - is_pointer_like(insn->rs2); - break; - case OP_negate: - case OP_bit_not: - case OP_log_not: - src0 = prepare_operand(bb, insn->rs1, -1); - dest = prepare_dest(bb, insn, insn->rd, src0, -1); - ir = bb_add_ph2_ir(bb, insn->opcode); - ir->src0 = src0; - ir->dest = dest; - break; - case OP_trunc: - case OP_sign_ext: - case OP_cast: - src0 = prepare_operand(bb, insn->rs1, -1); - dest = prepare_dest(bb, insn, insn->rd, src0, -1); - ir = bb_add_ph2_ir(bb, insn->opcode); - ir->src1 = insn->sz; - ir->src0 = src0; - ir->dest = dest; - break; - default: - printf("Unknown opcode\n"); - abort(); - } - } - - if (bb->next) { - spill_live_out_keep(bb); - bb_export_regs(bb); - } else if (bb->then_ || bb->else_) { - /* A conditional branch has already written its live-out values - * back at OP_branch; only the handover is left. - */ - bb_export_regs(bb); - } - - if (bb == func->exit) - continue; - - /* append jump instruction for the normal block only */ - if (!bb->next) - continue; - - if (bb->next == func->exit) - continue; - - /* jump to the beginning of loop or over the else block */ - if (bb->next->visited == func->visited || - bb->next->rpo != bb->rpo + 1) { - ph2_ir_t *ir = bb_add_ph2_ir(bb, OP_jump); - ir->next_bb = bb->next; - } + reg_alloc_bb(func, bb); } /* handle implicit return */ diff --git a/src/riscv-codegen.c b/src/riscv-codegen.c index f87c1f93..d394d10e 100644 --- a/src/riscv-codegen.c +++ b/src/riscv-codegen.c @@ -1,8 +1,8 @@ /* * shecc - Self-Hosting and Educational C Compiler. * - * shecc is freely redistributable under the BSD 2 clause license. See the - * file "LICENSE" for information on usage and redistribution of this file. + * shecc is freely redistributable under the BSD 2 clause license. See the file + * "LICENSE" for information on usage and redistribution of this file. */ /* Translate IR to target machine code */ @@ -14,8 +14,8 @@ /* Explanation: registers preservation/restoration * - * The following table illustrates which registers are caller-saved - * or callee-saved: + * The following table illustrates which registers are caller-saved or + * callee-saved: * +-----------+--------+ * | Register | Saver | * | ABI Name | | @@ -192,21 +192,21 @@ void cfg_flatten(void) func_t *func; if (dynlink) { - /* When using dynamic linking, 20 instructions are generated at - * the program entry point to perform the following operations: + /* When using dynamic linking, 20 instructions are generated at the + * program entry point to perform the following operations: * - prepare arguments and call __libc_start_main() * - preserve a0 ('argc'), a1 ('argv') and sp. * - allocate a global stack and jump to global init function. */ elf_offset = 80; } else { - /* Under static linking, "__syscall" must be generated to allow - * the program to invoke system calls. + /* Under static linking, "__syscall" must be generated to allow the + * program to invoke system calls. * * "__syscall" consists of 9 instructions, preceded by 6 initial - * instructions. Consequently, the elf offset for "__syscall" is - * is 24 bytes, and the offset for the subsequent function - * (GLOBAL_FUNC) is 60 bytes. + * instructions. Consequently, the elf offset for "__syscall" is is 24 + * bytes, and the offset for the subsequent function (GLOBAL_FUNC) is 60 + * bytes. */ func = find_func("__syscall"); func->bbs->elf_offset = 24; @@ -236,11 +236,11 @@ void cfg_flatten(void) flatten_ir->src0 = func->stack_size; flatten_ir->func_name = intern_string(func->return_def.var_name); - /* Except for local variables, it must allocate additional space - * to preserve the content of ra at each function entry point. + /* Except for local variables, it must allocate additional space to + * preserve the content of ra at each function entry point. * - * 'stack_size' doesn't include the additional space, so an extra - * number '4' is added to 'stack_size'. + * 'stack_size' doesn't include the additional space, so an extra number + * '4' is added to 'stack_size'. */ int stack_top_ofs = ALIGN_UP(func->stack_size + 4, RV32_ALIGNMENT); @@ -264,9 +264,9 @@ void cfg_flatten(void) insn->src1 = insn->src1 + stack_top_ofs; break; default: - /* Ignore opcodes with the ofs_based_on_stack_top - * flag set since only the three opcodes above needs - * to access a variable's address. + /* Ignore opcodes with the ofs_based_on_stack_top flag + * set since only the three opcodes above needs to + * access a variable's address. */ break; } @@ -297,8 +297,8 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) int rs2 = ph2_ir->src1 + 10; int ofs; - /* Prepare the variables to reuse the same code for - * the instruction sequence of + /* Prepare the variables to reuse the same code for the instruction sequence + * of * 1. division and modulo. * 2. load and store operations. * 3. address-of operations. @@ -396,6 +396,7 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) (elf_code_start + elf_code->size); } else { printf("The '%s' function is not implemented\n", ph2_ir->func_name); + fflush(stdout); /* see fatal() */ abort(); } emit(__jal(__ra, ofs)); @@ -416,6 +417,7 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) ofs = dynamic_sections.elf_plt_start + func->plt_offset; else { printf("The '%s' function is not implemented\n", ph2_ir->func_name); + fflush(stdout); /* see fatal() */ abort(); } emit(__lui(__t0, rv_hi(ofs))); @@ -579,9 +581,8 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) } return; case OP_sign_ext: { - /* Decode size information: - * Lower 16 bits: target size - * Upper 16 bits: source size + /* Decode size information: Lower 16 bits: target size Upper 16 bits: + * source size */ int target_size = ph2_ir->src1 & 0xFFFF; int source_size = (ph2_ir->src1 >> 16) & 0xFFFF; @@ -590,9 +591,9 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) int shift_amount = (target_size - source_size) * 8; if (source_size == 2) { - /* Sign extend from short to word (16-bit shift) - * For 16-bit sign extension, use only shift operations - * since 0xFFFF is too large for RISC-V immediate field + /* Sign extend from short to word (16-bit shift) For 16-bit sign + * extension, use only shift operations since 0xFFFF is too large + * for RISC-V immediate field */ emit(__slli(rd, rs1, shift_amount)); emit(__srai(rd, rd, shift_amount)); @@ -620,6 +621,7 @@ void code_generate(void) if (dynlink) { plt_generate(); + /* - Initial stack layout when the program starts: * * +----------------+ (high address) @@ -644,9 +646,9 @@ void code_generate(void) * void (*rtld_fini) (void), * void (*stack_end)); * - * Currently, to execute a dynamically linked program with the - * minimal effort required, we perform the following call: - * -> __libc_start_main(main_wrapper, argc, argv, NULL, + * Currently, to execute a dynamically linked program with the minimal + * effort required, we perform the following call: -> + * __libc_start_main(main_wrapper, argc, argv, NULL, * NULL, NULL, stack_end) */ emit(__lui(__a0, rv_hi(elf_code_start + 36))); @@ -665,11 +667,11 @@ void code_generate(void) /* The main wrapper is located here under the dynamic linking mode * - * Use s0 and s1 registers to temporarily store 'argc' and 'argv', - * while preserving ra on the stack. + * Use s0 and s1 registers to temporarily store 'argc' and 'argv', while + * preserving ra on the stack. * - * After the main function completes its execution, it must use - * the original content of ra to transfer control back to + * After the main function completes its execution, it must use the + * original content of ra to transfer control back to * __libc_start_main(). */ emit(__addi(__sp, __sp, -12)); @@ -680,14 +682,15 @@ void code_generate(void) emit(__addi(__s1, __a1, 0)); /* argv */ ofs = ALIGN_UP(GLOBAL_FUNC->stack_size, RV32_ALIGNMENT) + 4; } else { - /* When using static linking, the starting address - * of the main wrapper is here. + /* When using static linking, the starting address of the main wrapper + * is here. * * Save original sp in s0 first. */ ofs = ALIGN_UP(GLOBAL_FUNC->stack_size, RV32_ALIGNMENT); emit(__addi(__s0, __sp, 0)); } + /* Next, the main wrapper performs: * 1. allocate global stack * 2. jump to global init function @@ -755,7 +758,7 @@ void code_generate(void) } } -void plt_generate() +void plt_generate(void) { int addr_of_plt = dynamic_sections.elf_plt_start; int addr_of_got = dynamic_sections.elf_got_start; @@ -773,7 +776,7 @@ void plt_generate() /* Accroding the RISC-V ABI specification, the first PLT entry should * contains the following instructions: * - * 1: auipc t2, %pcrel_hi(.got) + * 1: auipc t2, %pcrel_hi(.got) * sub t1, t1, t3 * lw t3, %pcrel_lo(1b)(t2) * addi t1, t1 -(PLT0_SIZE + 12) # PLT0_SIZE is 32 bytes. @@ -842,12 +845,12 @@ void plt_generate() elf_write_int(dynamic_sections.elf_plt, __lw(__t0, __t0, 4)); elf_write_int(dynamic_sections.elf_plt, __jalr(__zero, __t3, 0)); for (int i = 0; i * PLT_ENT_SIZE < end; i++) { - /* elf_generate() ensures that the .got section is placed - * a higher memory address than the plt section. As a result, - * 'ofs' must always be positive. + /* elf_generate() ensures that the .got section is placed a higher + * memory address than the plt section. As a result, 'ofs' must always + * be positive. * - * addr_of_plt: the starting address of PLT[N]. (N >= 1) - * addr_of_got: the starting address of GOT[N + 1]. + * addr_of_plt: the starting address of PLT[N]. (N >= 1) addr_of_got: + * the starting address of GOT[N + 1]. */ addr_of_plt = dynamic_sections.elf_plt_start + PLT_FIXUP_SIZE + PLT_ENT_SIZE * i; @@ -856,14 +859,14 @@ void plt_generate() /* In RISC-V ABI, a PLT stub takes up 4 instructions to load GOT[N + 2]: * - * 1: auipc t3, %pcrel_hi(function@.got) + * 1: auipc t3, %pcrel_hi(function@.got) * lw t3, %pcrel_lo(1b)(t3) * jalr t1, t3 * nop * - * Each PLT stub uses auipc and lw instructions to perform a - * PC-relative addressing to obtain GOT[N + 1], and then perform - * an unconditional jump. + * Each PLT stub uses auipc and lw instructions to perform a PC-relative + * addressing to obtain GOT[N + 1], and then perform an unconditional + * jump. * * +-------------------------------------+----------------------------+ * | Instruction | Contents of registers | diff --git a/src/riscv.c b/src/riscv.c index 8ebde663..0baf159f 100644 --- a/src/riscv.c +++ b/src/riscv.c @@ -1,8 +1,8 @@ /* * shecc - Self-Hosting and Educational C Compiler. * - * shecc is freely redistributable under the BSD 2 clause license. See the - * file "LICENSE" for information on usage and redistribution of this file. + * shecc is freely redistributable under the BSD 2 clause license. See the file + * "LICENSE" for information on usage and redistribution of this file. */ /* RISC-V instruction encoding */ diff --git a/src/ssa.c b/src/ssa.c index 4281a51b..eb6c9860 100644 --- a/src/ssa.c +++ b/src/ssa.c @@ -1,8 +1,8 @@ /* * shecc - Self-Hosting and Educational C Compiler. * - * shecc is freely redistributable under the BSD 2 clause license. See the - * file "LICENSE" for information on usage and redistribution of this file. + * shecc is freely redistributable under the BSD 2 clause license. See the file + * "LICENSE" for information on usage and redistribution of this file. */ #include #include @@ -10,8 +10,8 @@ #include "defs.h" #include "globals.c" -/* Constant cast optimization. Despite the file name this is not SCCP: - * there is no lattice and no CFG-edge worklist anywhere in the tree. +/* Constant cast optimization. Despite the file name this is not SCCP: there is + * no lattice and no CFG-edge worklist anywhere in the tree. */ #include "opt-sccp.c" @@ -43,7 +43,7 @@ void var_list_ensure_capacity(var_list_t *list, int min_capacity) } /* Whether @var appears in @list. */ -bool var_list_holds(var_list_t *list, var_t *var) +bool var_list_holds(const var_list_t *list, var_t *var) { for (int i = 0; i < list->size; i++) { if (list->elements[i] == var) @@ -77,12 +77,12 @@ void var_list_assign_array(var_list_t *list, var_t **data, int count) list->size = count; } -/* cfront does not accept structure as an argument, pass pointer */ -/* The only thing a step of either traversal changes in the argument block is - * the block it names, so a step sets that field and puts it back on the way - * out. Copying the whole structure per edge instead -- which is what these did - * -- costs a copy on every block of every traversal, and the traversals are - * how nearly every analysis in the middle end walks a function. +/* cfront does not accept structure as an argument, pass pointer The only thing + * a step of either traversal changes in the argument block is the block it + * names, so a step sets that field and puts it back on the way out. Copying the + * whole structure per edge instead -- which is what these did -- costs a copy + * on every block of every traversal, and the traversals are how nearly every + * analysis in the middle end walks a function. */ void bb_forward_traversal(bb_traversal_args_t *args) { @@ -171,7 +171,6 @@ void bb_build_rpo(func_t *func, basic_block_t *bb) } bb->rpo_next = curr; prev->rpo_next = bb; - prev = curr; return; } @@ -251,11 +250,12 @@ void build_idom(void) pred = bb->prev[i].bb; break; } - /* Reverse postorder puts a predecessor of every reachable - * block ahead of it, so one is normally settled by now. A - * block where none is cannot be given an immediate dominator - * yet; leaving it for a later round is what keeps the walk off - * an uninitialised pointer. + + /* Reverse postorder puts a predecessor of every reachable block + * ahead of it, so one is normally settled by now. A block where + * none is cannot be given an immediate dominator yet; leaving + * it for a later round is what keeps the walk off an + * uninitialised pointer. */ if (!pred) continue; @@ -333,8 +333,8 @@ void build_dom(void) * the target no longer does. strength_reduce() and mark_loop_depth() both ask * is_dominate() which edges close a loop, and against a stale tree a forward * edge reads as a back edge -- mark_natural_loop() then walks predecessors out - * of the region it was meant to stay inside, since it has no stop at the - * header and relies on the header dominating the latch. + * of the region it was meant to stay inside, since it has no stop at the header + * and relies on the header dominating the latch. * * The order has to be rebuilt along with the tree. Both passes keep the * rpo_next chain consistent -- a block they drop comes out of it -- but @@ -387,12 +387,12 @@ void bb_build_df(func_t *func, basic_block_t *bb) for (int i = 0; i < bb->prev_idx; i++) { if (bb->prev[i].bb) { - /* Walk up from the predecessor to this block's immediate - * dominator. The walk normally stops there, since a block's - * immediate dominator dominates all of its predecessors -- but an - * edge the dominator tree does not account for runs off the top - * instead, so stop at the root as well. Ending early only widens - * the frontier, which costs a phi that turns out to be trivial. + /* Walk up from the predecessor to this block's immediate dominator. + * The walk normally stops there, since a block's immediate + * dominator dominates all of its predecessors -- but an edge the + * dominator tree does not account for runs off the top instead, so + * stop at the root as well. Ending early only widens the frontier, + * which costs a phi that turns out to be trivial. */ for (basic_block_t *curr = bb->prev[i].bb; curr && curr != bb->idom; curr = curr->idom) @@ -446,7 +446,7 @@ void build_r_idom(void) for (basic_block_t *bb = func->exit->rpo_r_next; bb; bb = bb->rpo_r_next) { /* pick one predecessor */ - basic_block_t *pred; + basic_block_t *pred = NULL; if (bb->next && bb->next->r_idom) { pred = bb->next; } else if (bb->else_ && bb->else_->r_idom) { @@ -455,6 +455,16 @@ void build_r_idom(void) pred = bb->then_; } + /* The mirror of the rule in build_idom(): reverse postorder + * from the exit puts a successor of every block that reaches it + * ahead of that block, so one is normally settled by now. A + * block where none is cannot be given an immediate + * postdominator yet; leaving it for a later round is what keeps + * the walk off an uninitialised pointer. + */ + if (!pred) + continue; + if (bb->next && bb->next != pred && bb->next->r_idom) pred = reverse_intersect(bb->next, pred); if (bb->else_ && bb->else_ != pred && bb->else_->r_idom) @@ -596,7 +606,7 @@ void use_chain_build(void) } } -bool var_check_killed(var_t *var, basic_block_t *bb) +bool var_check_killed(const var_t *var, const basic_block_t *bb) { for (int i = 0; i < bb->live_kill.size; i++) { if (bb->live_kill.elements[i] == var) @@ -694,9 +704,9 @@ void solve_globals(void) } } -bool var_check_in_scope(var_t *var, block_t *block) +bool var_check_in_scope(const var_t *var, block_t *block) { - func_t *func = block->func; + const func_t *func = block->func; while (block) { /* Only the first 'size' entries hold a variable; the rest of the @@ -830,8 +840,8 @@ var_t *require_var(block_t *blk); * * It has to be new. rename_var() gives every use reached by one definition the * same var_t, and mark_const() stamps init_val onto that shared object, so - * rewriting an existing constant's value changes what every other use sees. - * The caller emits the OP_load_constant that defines it. + * rewriting an existing constant's value changes what every other use sees. The + * caller emits the OP_load_constant that defines it. */ var_t *new_const_var(block_t *scope, int val) { @@ -842,7 +852,7 @@ var_t *new_const_var(block_t *scope, int val) var->init_val = val; return var; } -bool is_dominate(basic_block_t *pred, basic_block_t *succ); +bool is_dominate(const basic_block_t *pred, basic_block_t *succ); /* The renaming state of @v, created on first use. */ rename_t *var_rename(var_t *v) @@ -914,6 +924,7 @@ void pop_name(var_t *var) { if (var->is_global) return; + /* Pop unconditionally, creating the state if the variable has none: the * inline rename_t this replaced was always present, so a pop with nothing * pushed drove stack_idx to -1, and the next push then landed one slot @@ -1068,9 +1079,9 @@ void bb_unwind_phi(func_t *func, basic_block_t *bb) int loop_scan_gen; -/* Raise the depth of every block in the natural loop that the edge from - * @latch back to @header closes: the header, and everything that can reach the - * latch without leaving the loop. +/* Raise the depth of every block in the natural loop that the edge from @latch + * back to @header closes: the header, and everything that can reach the latch + * without leaving the loop. */ bool mark_natural_loop(basic_block_t *header, basic_block_t *latch) { @@ -1095,11 +1106,12 @@ bool mark_natural_loop(basic_block_t *header, basic_block_t *latch) if (!p || p->loop_mark == loop_scan_gen) continue; + /* Out of room to widen. Marking this block and then not walking * through it would leave the rest of the loop unmarked while * looking marked, and a reader of loop_mark would take blocks - * inside the loop for blocks outside it. Report the whole answer - * as unusable instead. + * inside the loop for blocks outside it. Report the whole answer as + * unusable instead. */ if (sp >= MAX_LOOP_WALK) return false; @@ -1138,6 +1150,7 @@ void mark_loop_depth(func_t *func) continue; if (succ[k] != p && !is_dominate(succ[k], p)) continue; + /* A walk that ran out of room stopped partway, leaving the depths * it had already raised standing over a region it never finished * measuring. Those feed the weights pin_registers() compares, so a @@ -1209,7 +1222,7 @@ insn_t *new_insn(opcode_t op, var_t *rd, var_t *rs1, var_t *rs2) /* Whether @insn computes a value with no side effect and no way to fault, so * running it on a path that would not have reached it changes nothing. */ -bool insn_is_speculatable(insn_t *insn) +bool insn_is_speculatable(const insn_t *insn) { switch (insn->opcode) { case OP_add: @@ -1245,9 +1258,9 @@ bool insn_is_speculatable(insn_t *insn) * * An arm is not one block: the copy carrying its value to the join is appended * to whichever block immediately precedes the join, which is separate from the - * one holding the arm's computation. The walk follows single-entry, - * single-exit blocks and stops at the first block something else can also - * reach, which is the join. + * one holding the arm's computation. The walk follows single-entry, single-exit + * blocks and stops at the first block something else can also reach, which is + * the join. */ basic_block_t *if_arm_chain(basic_block_t *arm, basic_block_t **chain, @@ -1272,6 +1285,7 @@ basic_block_t *if_arm_chain(basic_block_t *arm, continue; if (!insn_is_speculatable(insn)) return NULL; + /* Writing a global or an address-taken variable is a store, and a * store is a side effect however plain the arithmetic producing it * looks. @@ -1360,6 +1374,7 @@ bool if_convert_bb(func_t *func, basic_block_t *bb) if (!join || join != e_join) return false; + /* Flattening removes both arms, so the join must be reached from them and * nothing else. */ @@ -1405,7 +1420,7 @@ bool if_convert_bb(func_t *func, basic_block_t *bb) for (int step = 0; step < 2; step++) { bool take_t = e_needs_t ? step == 0 : step == 1; basic_block_t **chain = take_t ? t_chain : e_chain; - insn_t *skip = take_t ? t_phi : e_phi; + const insn_t *skip = take_t ? t_phi : e_phi; int len = take_t ? t_len : e_len; for (int i = 0; i < len; i++) { @@ -1413,6 +1428,7 @@ bool if_convert_bb(func_t *func, basic_block_t *bb) next = insn->next; if (insn == skip) continue; + /* Whatever the arm computes is a version of the variable the * select writes as often as not, and that variable's pinned * register still has to carry the value the arms read. @@ -1472,11 +1488,11 @@ int sr_gen; * @skip. * * With @loop_only set, only the blocks the stamped loop covers are searched: a - * reader outside it does not keep a literal alive inside, because the - * allocator materialises one wherever the value is wanted. + * reader outside it does not keep a literal alive inside, because the allocator + * materialises one wherever the value is wanted. */ bool var_read_by(func_t *func, - var_t *var, + const var_t *var, insn_t **skip, int nskip, bool loop_only) @@ -1530,8 +1546,9 @@ bool thread_const_branch(func_t *func, basic_block_t *join) if (!join->then_ || !join->else_) return false; - /* Only a block that does nothing else: anything before the branch would - * be skipped, and a phi copy left in it belongs to a successor. + + /* Only a block that does nothing else: anything before the branch would be + * skipped, and a phi copy left in it belongs to a successor. */ if (!br || br->next || br->opcode != OP_branch || !br->rs1) return false; @@ -1549,8 +1566,9 @@ bool thread_const_branch(func_t *func, basic_block_t *join) if (!pred || pred == join) continue; - /* A predecessor that also goes somewhere else keeps a branch of - * its own, and rewiring one of its edges would need that branch + + /* A predecessor that also goes somewhere else keeps a branch of its + * own, and rewiring one of its edges would need that branch * rewritten. */ if (pred->then_ || pred->else_ || pred->next != join) @@ -1615,7 +1633,7 @@ void thread_const_branches(void) if (!func->bbs) continue; for (basic_block_t *bb = func->bbs; bb; bb = bb->rpo_next) { - insn_t *br = bb->insn_list.head; + const insn_t *br = bb->insn_list.head; if (!br || br->next || br->opcode != OP_branch || !br->rs1) continue; @@ -1630,8 +1648,8 @@ void thread_const_branches(void) * into the argument registers, everything live crosses a call boundary and so * goes to the frame, and the callee builds and tears down a frame of its own. * The benchmark suite's "calls" case spends three instructions on that for - * every one it spends computing. Copying the body in removes all of it and - * lets the optimizer see the caller and the callee together. + * every one it spends computing. Copying the body in removes all of it and lets + * the optimizer see the caller and the callee together. * * Only a body with no control flow of its own is copied, which keeps the * transformation to splicing one instruction list into another. @@ -1640,9 +1658,9 @@ var_t *inline_from[MAX_INLINE_VARS]; var_t *inline_to[MAX_INLINE_VARS]; int inline_map_n; -/* The caller's stand-in for the callee's @var: the argument for a parameter, - * a fresh variable for anything the body computes, and the variable itself - * for anything shared. +/* The caller's stand-in for the callee's @var: the argument for a parameter, a + * fresh variable for anything the body computes, and the variable itself for + * anything shared. */ var_t *inline_lookup(var_t *var, block_t *scope) { @@ -1653,8 +1671,9 @@ var_t *inline_lookup(var_t *var, block_t *scope) for (int i = 0; i < inline_map_n; i++) { if (inline_from[i] == var) return inline_to[i]; - /* Parameters are mapped by the variable they are versions of, so that - * a body naming a later version still finds the argument. + + /* Parameters are mapped by the variable they are versions of, so that a + * body naming a later version still finds the argument. */ if (inline_from[i] && inline_from[i] == var->base) return inline_to[i]; @@ -1678,10 +1697,10 @@ var_t *inline_lookup(var_t *var, block_t *scope) /* Whether @func is small and straight-line enough to copy into its callers. * - * A function is a chain of blocks even when its source has no control flow: - * the entry block holds the declarations and falls into the body. Any block - * that branches, or that something else can reach, ends the chain and makes - * the function too complicated to splice into an instruction list. + * A function is a chain of blocks even when its source has no control flow: the + * entry block holds the declarations and falls into the body. Any block that + * branches, or that something else can reach, ends the chain and makes the + * function too complicated to splice into an instruction list. */ int inline_round; @@ -1695,8 +1714,8 @@ int inline_round; * anything is touched is what keeps that from needing to be undone. * * The count mirrors inline_lookup()'s matching exactly: a global is shared - * rather than copied, a parameter is found through the variable it is a - * version of, and every other version gets an entry of its own. + * rather than copied, a parameter is found through the variable it is a version + * of, and every other version gets an entry of its own. */ bool inline_map_fits(func_t *func) { @@ -1763,6 +1782,7 @@ bool func_is_inlinable(func_t *func) return false; if (bb != func->bbs && bb_pred_count(bb) != 1) return false; + /* The single-predecessor test above is skipped for the entry block, so * a "next" chain that came back round to it would walk for ever. A * chain longer than the instruction budget cannot be inlinable in any @@ -1778,15 +1798,16 @@ bool func_is_inlinable(func_t *func) last = insn; if (insn->opcode == OP_return) continue; + /* A call of its own would have to be copied as a call, which is * what the copying is meant to remove; the pass runs again once - * that callee has been copied in, and by then this body - * qualifies. + * that callee has been copied in, and by then this body qualifies. */ if (!insn_is_speculatable(insn)) return false; if (insn->rd && (insn->rd->is_global || insn->rd->address_taken)) return false; + /* Writing a parameter would mean the copy assigns to the caller's * own variable, since a parameter maps onto the argument. */ @@ -1824,12 +1845,13 @@ insn_t *inline_clone(basic_block_t *bb, return copy; } -/* Replace the push/call/retval sequence ending at @call with the callee's - * body. Returns the instruction to carry on scanning from, which is NULL when - * the copy lands at the end of the block; *@done says whether the call was - * replaced at all. The two have to be reported separately -- a NULL return - * read as "left alone" stopped the round from being counted as progress and - * left the rest of the block unscanned. +/* Replace the push/call/retval sequence ending at @call with the callee's body. + * + * Returns the instruction to carry on scanning from, which is NULL when the + * copy lands at the end of the block; *@done says whether the call was replaced + * at all. The two have to be reported separately -- a NULL return read as "left + * alone" stopped the round from being counted as progress and left the rest of + * the block unscanned. * * @done is an int and not a bool on purpose. A _Bool is one byte, and writing * one byte through a pointer into the caller's slot -- which is pointer-sized @@ -1860,11 +1882,11 @@ insn_t *inline_call_at(basic_block_t *bb, insn_t *call, int *done) if (argc != callee->num_params || argc > MAX_PARAMS) return NULL; - /* A call nested in another call's argument list -- "f(g(x), y)" -- has - * the outer call's pushes already standing before it. The register - * allocator hands those the argument registers as it meets them, so - * anything spliced in between would overwrite arguments the outer call is - * still waiting to make. + /* A call nested in another call's argument list -- "f(g(x), y)" -- has the + * outer call's pushes already standing before it. The register allocator + * hands those the argument registers as it meets them, so anything spliced + * in between would overwrite arguments the outer call is still waiting to + * make. */ for (insn_t *p = first->prev; p; p = p->prev) { if (p->opcode == OP_call || p->opcode == OP_indirect) @@ -1961,9 +1983,10 @@ void inline_calls(void) if (done) { next = resume; changed = true; - /* The body just grew, so the verdict cached for it - * this round no longer describes it -- and losing a - * call may be exactly what makes it copyable. + + /* The body just grew, so the verdict cached for it this + * round no longer describes it -- and losing a call may + * be exactly what makes it copyable. */ func->inline_gen = 0; } @@ -1983,12 +2006,11 @@ void inline_calls(void) * at the bottom leaves the body with the access alone -- matmul's innermost * loop spent six of its sixteen instructions on the two subscripts. * - * Unlike hoisting a loop-invariant value, this pays even when the result has - * to live on the frame: the loop trades a four-instruction recomputation for - * one addition, where hoisting trades a recomputation for a reload. - */ -/* The variable the loop counts with, where the chain walk stops: its value - * before the loop is what the first address is computed from. + * Unlike hoisting a loop-invariant value, this pays even when the result has to + * live on the frame: the loop trades a four-instruction recomputation for one + * addition, where hoisting trades a recomputation for a reload. The variable + * the loop counts with, where the chain walk stops: its value before the loop + * is what the first address is computed from. */ var_t *sr_iv; @@ -2000,12 +2022,12 @@ var_t *sr_base(var_t *var) } /* Whether @var is written inside the loop being examined. A constant never is, - * wherever its materialisation sits: the allocator emits one wherever the - * value is wanted. + * wherever its materialisation sits: the allocator emits one wherever the value + * is wanted. */ bool sr_varies(var_t *var) { - var_t *base; + const var_t *base; if (!var || var->is_const) return false; @@ -2013,12 +2035,12 @@ bool sr_varies(var_t *var) return base && base->loop_stamp == sr_gen; } -/* How much @var moves per iteration, into *step. Reports false when that is - * not known -- which for a variable the loop writes means "not yet derived". +/* How much @var moves per iteration, into *step. Reports false when that is not + * known -- which for a variable the loop writes means "not yet derived". */ bool sr_step_of(var_t *var, int *step) { - var_t *base; + const var_t *base; if (!var) return false; @@ -2050,13 +2072,13 @@ void sr_set_step(var_t *var, int step) base->iv_step = step; } -/* Whether @insn is one the pass is willing to lift out of a loop: it computes - * a value from its operands, touches no memory and faults on nothing. Taking - * an array's address qualifies -- that address is the same on every - * iteration -- and a literal does not, because the allocator materialises one - * wherever it is wanted and moving it would only lengthen a live range. +/* Whether @insn is one the pass is willing to lift out of a loop: it computes a + * value from its operands, touches no memory and faults on nothing. Taking an + * array's address qualifies -- that address is the same on every iteration -- + * and a literal does not, because the allocator materialises one wherever it is + * wanted and moving it would only lengthen a live range. */ -bool sr_movable(insn_t *insn) +bool sr_movable(const insn_t *insn) { switch (insn->opcode) { case OP_add: @@ -2079,7 +2101,7 @@ bool sr_movable(insn_t *insn) } /* The instruction inside the loop that defines @var, or NULL. */ -insn_t *sr_def_of(func_t *func, var_t *var) +insn_t *sr_def_of(func_t *func, const var_t *var) { for (basic_block_t *bb = func->bbs; bb; bb = bb->rpo_next) { if (bb->loop_mark != sr_gen) @@ -2121,7 +2143,7 @@ var_t *sr_basic_iv(func_t *func, basic_block_t *latch, int *step) if (!sum->rs1 || !sum->rs2) continue; - var_t *addend = NULL; + const var_t *addend = NULL; var_t *carried = NULL; int sign = 1; @@ -2137,12 +2159,13 @@ var_t *sr_basic_iv(func_t *func, basic_block_t *latch, int *step) } if (!addend || !addend->init_val) continue; + /* The literal has to be added to the variable itself, not to * something computed from it: a temporary made from a variable - * carries that variable as its base, so "g = g * 2 + 1" matches - * the shape as readily as "i = i + 1" and would be taken for a - * counter stepping by one. The value flowing in is the one the - * loop either carries round in a phi or never writes. + * carries that variable as its base, so "g = g * 2 + 1" matches the + * shape as readily as "i = i + 1" and would be taken for a counter + * stepping by one. The value flowing in is the one the loop either + * carries round in a phi or never writes. */ insn_t *src = sr_def_of(func, carried); @@ -2162,9 +2185,9 @@ var_t *sr_basic_iv(func_t *func, basic_block_t *latch, int *step) if (!found) return NULL; - /* One variable the loop writes twice is not a counter: "i++" in one arm - * and "i--" in another moves it by neither step, and an address derived - * from it would advance by a fixed amount that matches neither. + /* One variable the loop writes twice is not a counter: "i++" in one arm and + * "i--" in another moves it by neither step, and an address derived from it + * would advance by a fixed amount that matches neither. */ int writes = 0; basic_block_t *wblk = NULL; @@ -2175,19 +2198,19 @@ var_t *sr_basic_iv(func_t *func, basic_block_t *latch, int *step) for (insn_t *insn = bb->insn_list.head; insn; insn = insn->next) { /* Only a copy back into the variable counts. The arithmetic that * produces the new value writes a temporary, and a temporary made - * from a variable carries that variable as its base. - */ - /* Only a write of the variable itself counts. The arithmetic - * that produces the new value writes a temporary, and a temporary - * made from a variable carries that variable as its base -- but a - * select names the variable, and one writing the counter means the - * counter does not advance on every trip round. + * from a variable carries that variable as its base. Only a write + * of the variable itself counts. The arithmetic that produces the + * new value writes a temporary, and a temporary made from a + * variable carries that variable as its base -- but a select names + * the variable, and one writing the counter means the counter does + * not advance on every trip round. */ if (insn->opcode != OP_assign && insn->opcode != OP_unwound_phi && insn->opcode != OP_cmov) continue; if (!insn->rd || sr_base(insn->rd) != found) continue; + /* A phi copying the variable to itself carries it around the loop * rather than giving it a new value. */ @@ -2201,9 +2224,9 @@ var_t *sr_basic_iv(func_t *func, basic_block_t *latch, int *step) return NULL; /* The write has to happen on every trip round, because the pointer derived - * from the counter is advanced on every trip round. A "continue" that - * jumps over the sole increment leaves the two disagreeing, and the loop - * reads one element too far from then on. + * from the counter is advanced on every trip round. A "continue" that jumps + * over the sole increment leaves the two disagreeing, and the loop reads + * one element too far from then on. */ if (wblk != latch && !is_dominate(wblk, latch)) return NULL; @@ -2227,7 +2250,7 @@ void sr_derive_steps(func_t *func) if (!insn->rd) continue; - var_t *base = sr_base(insn->rd); + const var_t *base = sr_base(insn->rd); if (!base || base->iv_gen == sr_gen) continue; @@ -2296,7 +2319,7 @@ bool sr_collect_chain(func_t *func, var_t *var, insn_t **chain, int *len) if (!sr_movable(def)) return false; - var_t *base = sr_base(def->rd); + const var_t *base = sr_base(def->rd); if (!base || base->def_cnt != 1) return false; @@ -2331,9 +2354,10 @@ void sr_emit_advance(basic_block_t *latch, var_t *var, int step) insn_t *add; insn_t *assign; - /* A loop latch ends in the jump back to its header. Appending the - * advance after that jump leaves it unreachable, so place the whole - * sequence before the terminator instead. */ + /* A loop latch ends in the jump back to its header. Appending the advance + * after that jump leaves it unreachable, so place the whole sequence before + * the terminator instead. + */ if (after && (after->opcode == OP_branch || after->opcode == OP_jump || after->opcode == OP_return || after->opcode == OP_func_ret)) after = after->prev; @@ -2398,10 +2422,10 @@ bool sr_reduce_access(func_t *func, * The walk collected every definition before its operands, so reversing it * is nearly right -- but only while the chain is a straight line. A value * two of them share is collected under the first, and reversing then puts - * it after the second, which would read a variable nothing had defined - * yet. Choosing repeatedly instead is correct either way: take an entry - * once every chain value it reads has been taken. Nothing is moved until - * the whole order is settled, so a chain that cannot be ordered at all is + * it after the second, which would read a variable nothing had defined yet. + * Choosing repeatedly instead is correct either way: take an entry once + * every chain value it reads has been taken. Nothing is moved until the + * whole order is settled, so a chain that cannot be ordered at all is * declined rather than half-moved. */ int order[MAX_IV_CHAIN + 1]; @@ -2508,7 +2532,7 @@ int sr_latch_count(basic_block_t *header) int n = 0; for (int i = 0; i < header->prev_idx; i++) { - basic_block_t *p = header->prev[i].bb; + const basic_block_t *p = header->prev[i].bb; if (p && p->loop_mark == sr_gen) n++; @@ -2521,8 +2545,8 @@ void sr_loop(func_t *func, basic_block_t *header, basic_block_t *latch) int step = 0, made = 0; /* One way back, and one way in that runs everything before the loop: the - * first address is computed there and advanced there. The test on the - * latch costs nothing and comes before the walk that marks the loop. + * first address is computed there and advanced there. The test on the latch + * costs nothing and comes before the walk that marks the loop. */ if (latch->then_ || latch->else_ || latch->next != header) return; @@ -2567,6 +2591,7 @@ void sr_loop(func_t *func, basic_block_t *header, basic_block_t *latch) next = insn->next; if (insn->opcode != OP_read && insn->opcode != OP_write) continue; + /* A reduction takes instructions out of this block, so the walk * starts again rather than following a pointer into the block they * moved to. @@ -2663,10 +2688,10 @@ void unwind_phi(void) * The walk goes up from @succ rather than down through everything @pred * dominates: the answer lies on the one path to the root, where the downward * search visited @pred's whole subtree and did not stop when it found it. - * dom_prev is the inverse of the dom_next the search followed, so the two - * agree on every pair. + * dom_prev is the inverse of the dom_next the search followed, so the two agree + * on every pair. */ -bool is_dominate(basic_block_t *pred, basic_block_t *succ) +bool is_dominate(const basic_block_t *pred, basic_block_t *succ) { for (basic_block_t *bb = succ; bb; bb = bb->dom_prev) { if (bb->dom_prev == pred) @@ -2675,9 +2700,8 @@ bool is_dominate(basic_block_t *pred, basic_block_t *succ) return false; } -/* - * For any variable, the basic block that defines it must dominate all the - * basic blocks where it is used; otherwise, it is an invalid cross-block +/* For any variable, the basic block that defines it must dominate all the basic + * blocks where it is used; otherwise, it is an invalid cross-block * initialization. */ void bb_check_var_cross_init(func_t *func, basic_block_t *bb) @@ -2688,7 +2712,7 @@ void bb_check_var_cross_init(func_t *func, basic_block_t *bb) if (insn->opcode != OP_allocat) continue; - var_t *var = insn->rd; + const var_t *var = insn->rd; ref_block_t *ref; for (ref = var->ref_block_list.head; ref; ref = ref->next) { if (ref->bb == bb) @@ -2702,14 +2726,12 @@ void bb_check_var_cross_init(func_t *func, basic_block_t *bb) } /** - * A variable's initialization lives in a basic block that does not dominate - * all of its uses, so control flow can reach a use without first passing - * through its initialization (i.e., a possibly-uninitialized use). + * A variable's initialization lives in a basic block that does not dominate all + * of its uses, so control flow can reach a use without first passing through + * its initialization (i.e., a possibly-uninitialized use). * - * For Example: - * // Jumps directly to 'label', skipping the declaration below - * goto label; - * if (1) { + * For Example: // Jumps directly to 'label', skipping the declaration below + * goto label; if (1) { * // This line is never executed when 'goto' is taken * int x; * label: @@ -2717,7 +2739,7 @@ void bb_check_var_cross_init(func_t *func, basic_block_t *bb) * x = 5; * } */ -void check_var_cross_init() +void check_var_cross_init(void) { bb_traversal_args_t *args = arena_alloc_traversal_args(); for (func_t *func = FUNC_LIST.head; func; func = func->next) { @@ -2739,7 +2761,7 @@ void bb_dump_connection(FILE *fd, basic_block_t *next, bb_connection_type_t type) { - char *str; + const char *str; switch (type) { case NEXT: @@ -2755,8 +2777,8 @@ void bb_dump_connection(FILE *fd, fatal("Unknown basic block connection type"); } - char *pred; - void *pred_id; + const char *pred; + const void *pred_id; if (curr->insn_list.tail) { pred = "insn"; pred_id = curr->insn_list.tail; @@ -2765,8 +2787,8 @@ void bb_dump_connection(FILE *fd, pred_id = curr; } - char *succ; - void *succ_id; + const char *succ; + const void *succ_id; if (next->insn_list.tail) { succ = "insn"; succ_id = next->insn_list.head; @@ -2779,7 +2801,7 @@ void bb_dump_connection(FILE *fd, } /* escape character for the tag in dot file */ -char *get_insn_op(insn_t *insn) +char *get_insn_op(const insn_t *insn) { switch (insn->opcode) { case OP_add: @@ -2842,18 +2864,19 @@ void bb_dump(FILE *fd, func_t *func, basic_block_t *bb) if (next_ && (then_ || else_)) printf("Warning: normal BB with condition\n"); - fprintf(fd, "subgraph cluster_%p {\n", bb); - fprintf(fd, "label=\"BasicBlock %p (%s)\"\n", bb, bb->bb_label_name); + fprintf(fd, "subgraph cluster_%p {\n", (void *) bb); + fprintf(fd, "label=\"BasicBlock %p (%s)\"\n", (void *) bb, + bb->bb_label_name); insn_t *insn = bb->insn_list.head; if (!insn) - fprintf(fd, "pseudo_%p [label=\"pseudo\"]\n", bb); + fprintf(fd, "pseudo_%p [label=\"pseudo\"]\n", (void *) bb); if (!insn && (then_ || else_)) printf("Warning: pseudo node should only have NEXT\n"); for (; insn; insn = insn->next) { if (insn->opcode == OP_phi) { - fprintf(fd, "insn_%p [label=", insn); + fprintf(fd, "insn_%p [label=", (void *) insn); fprintf(fd, "<%s%d := PHI(%s%d", insn->rd->var_name, insn->rd->subscript, insn->phi_ops->var->var_name, @@ -3005,11 +3028,12 @@ void bb_dump(FILE *fd, func_t *func, basic_block_t *bb) default: fatal("Unknown opcode in instruction dump"); } - fprintf(fd, "insn_%p [label=%s]\n", insn, str); + fprintf(fd, "insn_%p [label=%s]\n", (void *) insn, str); } if (insn->next) - fprintf(fd, "insn_%p->insn_%p [weight=100]\n", insn, insn->next); + fprintf(fd, "insn_%p->insn_%p [weight=100]\n", (void *) insn, + (void *) insn->next); } fprintf(fd, "}\n"); @@ -3031,7 +3055,7 @@ void bb_dump(FILE *fd, func_t *func, basic_block_t *bb) bb_dump_connection(fd, bb->prev[i].bb, bb, bb->prev[i].type); } -void dump_cfg(char name[]) +void dump_cfg(const char name[]) { FILE *fd = fopen(name, "w"); @@ -3046,8 +3070,9 @@ void dump_cfg(char name[]) continue; func->visited++; - fprintf(fd, "subgraph cluster_%p {\n", func); - fprintf(fd, "label=\"%p (%s)\"\n", func, func->return_def.var_name); + fprintf(fd, "subgraph cluster_%p {\n", (void *) func); + fprintf(fd, "label=\"%p (%s)\"\n", (void *) func, + func->return_def.var_name); bb_dump(fd, func, func->bbs); fprintf(fd, "}\n"); } @@ -3210,7 +3235,7 @@ void ssa_build(void) } /* Check if operation can be subject to CSE */ -bool is_cse_candidate(insn_t *insn) +bool is_cse_candidate(const insn_t *insn) { switch (insn->opcode) { case OP_add: @@ -3237,9 +3262,10 @@ bool is_cse_candidate(insn_t *insn) } } -/* Common Subexpression Elimination (CSE) */ -/* Enhanced to support general binary operations */ -bool cse(insn_t *insn, basic_block_t *bb) +/* Common Subexpression Elimination (CSE) Enhanced to support general binary + * operations + */ +bool cse(insn_t *insn, const basic_block_t *bb) { /* Handle array access pattern: add + read */ if (insn->opcode == OP_read) { @@ -3369,8 +3395,9 @@ bool mark_const(insn_t *insn) */ if (insn->rd->is_global) return false; - /* Copying from such a variable is no better: the value read is whatever - * the pointer last wrote, not the constant the source was assigned. + + /* Copying from such a variable is no better: the value read is whatever the + * pointer last wrote, not the constant the source was assigned. */ if (insn->rs1->address_taken) return false; @@ -3518,7 +3545,7 @@ bool const_folding(insn_t *insn) } /* Check if a basic block is unreachable */ -bool is_block_unreachable(basic_block_t *bb) +bool is_block_unreachable(const basic_block_t *bb) { if (!bb) return true; @@ -3544,6 +3571,8 @@ bool is_block_unreachable(basic_block_t *bb) bool var_escapes(var_t *var) { + UNUSED(var); + /* Reports every variable as escaping, which makes dce_init_mark() treat * every OP_write as useful and so disables SSA-level dead-store * elimination. The is_global/is_func branches that used to precede this @@ -3571,6 +3600,7 @@ void dce_init_push(insn_t *work_list[], int dce_init_mark(insn_t *insn, insn_t *work_list[], int work_list_idx) { int mark_num = 0; + /* mark instruction "useful" if it sets a return value, affects the value in * a storage location, or it is a function call. */ @@ -3633,7 +3663,7 @@ int dce_init_mark(insn_t *insn, insn_t *work_list[], int work_list_idx) } /* Dead Code Elimination (DCE) */ -void dce_insn(basic_block_t *bb) +void dce_insn(const basic_block_t *bb) { insn_t *work_list[DCE_WORKLIST_SIZE]; int work_list_idx = 0; @@ -3715,8 +3745,6 @@ void dce_insn(basic_block_t *bb) void dce_sweep(void) { - int total_eliminated = 0; /* Track effectiveness */ - for (func_t *func = FUNC_LIST.head; func; func = func->next) { /* Skip function declarations without bodies */ if (!func->bbs) @@ -3725,13 +3753,8 @@ void dce_sweep(void) for (basic_block_t *bb = func->bbs; bb; bb = bb->rpo_next) { /* Skip unreachable blocks entirely */ if (is_block_unreachable(bb)) { - /* Count instructions being eliminated */ - for (insn_t *insn = bb->insn_list.head; insn; - insn = insn->next) { - if (!insn->useful) - total_eliminated++; + for (insn_t *insn = bb->insn_list.head; insn; insn = insn->next) insn->useful = false; - } /* Mark entire block as dead */ bb->useful = false; continue; @@ -3741,7 +3764,6 @@ void dce_sweep(void) while (insn) { insn_t *next = insn->next; if (!insn->useful) { - total_eliminated++; /* If a branch instruction is useless, redirect to the * reverse immediate dominator of this basic block and * remove the branch instruction. Later, register allocation @@ -3776,7 +3798,7 @@ void dce_sweep(void) } } -void build_reversed_rpo(); +void build_reversed_rpo(void); void optimize(void) { @@ -3871,8 +3893,9 @@ void optimize(void) } } - /* Enhanced algebraic simplifications */ - /* Self-operation optimizations */ + /* Enhanced algebraic simplifications Self-operation + * optimizations + */ if (insn->rs1 && insn->rs2 && insn->rs1 == insn->rs2) { /* x - x = 0 */ if (insn->opcode == OP_sub && insn->rd) { @@ -4002,8 +4025,9 @@ void optimize(void) } } - /* Multi-instruction analysis and optimization */ - /* Store-to-load forwarding */ + /* Multi-instruction analysis and optimization Store-to-load + * forwarding + */ if (insn->opcode == OP_load && insn->rs1 && insn->rd) { insn_t *search = insn->prev; int search_limit = 10; /* Look back up to 10 instructions */ @@ -4182,7 +4206,6 @@ void bb_build_reversed_rpo(func_t *func, basic_block_t *bb) } bb->rpo_r_next = curr; prev->rpo_r_next = bb; - prev = curr; return; } @@ -4215,7 +4238,7 @@ void build_reversed_rpo(void) } } -void update_consumed(insn_t *insn, var_t *var); +void update_consumed(const insn_t *insn, var_t *var); /* Combined function to reset and solve locals in one pass */ void bb_reset_and_solve_locals(func_t *func, basic_block_t *bb) @@ -4226,10 +4249,10 @@ void bb_reset_and_solve_locals(func_t *func, basic_block_t *bb) bb->live_kill.size = 0; /* Both sets are asked about once per operand and once per destination, and - * answering from the lists themselves means a scan of one of them for - * every one of a block's instructions -- quadratic in the size of the - * block, which is what made this the most expensive part of the analysis - * on shecc's own longer functions. Stamping a variable as it enters a set + * answering from the lists themselves means a scan of one of them for every + * one of a block's instructions -- quadratic in the size of the block, + * which is what made this the most expensive part of the analysis on + * shecc's own longer functions. Stamping a variable as it enters a set * turns each of those questions into one comparison. live_kill was just * emptied, so nothing carries a stale stamp; live_gen is not, so what it * already holds is stamped first. @@ -4244,8 +4267,8 @@ void bb_reset_and_solve_locals(func_t *func, basic_block_t *bb) for (insn_t *insn = bb->insn_list.head; insn; insn = insn->next) { insn->idx = i++; - /* The three source operands are treated alike; the third is the value - * a select keeps when its condition does not hold. + /* The three source operands are treated alike; the third is the value a + * select keeps when its condition does not hold. */ var_t *srcs[3]; srcs[0] = insn->rs1; @@ -4270,7 +4293,7 @@ void bb_reset_and_solve_locals(func_t *func, basic_block_t *bb) } } -void update_consumed(insn_t *insn, var_t *var) +void update_consumed(const insn_t *insn, var_t *var) { if (insn->idx > var->consumed) var->consumed = insn->idx; @@ -4352,8 +4375,9 @@ bool recompute_live_out(basic_block_t *bb) return true; } - /* Size is same, need to check if contents are identical */ - /* Optimize by checking if first few elements match (common case) */ + /* Size is same, need to check if contents are identical Optimize by + * checking if first few elements match (common case) + */ if (live_out_idx > 0) { /* Quick check first element */ bool first_found = false; @@ -4412,7 +4436,7 @@ void liveness_analysis(void) if (!func->bbs) continue; - basic_block_t *bb = func->exit; + basic_block_t *bb; bool changed; do { changed = false; diff --git a/src/x64-codegen.c b/src/x64-codegen.c index d0ca0113..d1560077 100644 --- a/src/x64-codegen.c +++ b/src/x64-codegen.c @@ -1607,252 +1607,20 @@ bool emit_mul_by_const(int rd, int rs1, int c) return false; } -void emit_ph2_ir(ph2_ir_t *ph2_ir) +/* An OP_jump whose target is a single instruction emits that instruction in + * place, so the family emitters below reach back into the dispatcher. + */ +void emit_ph2_ir(ph2_ir_t *ph2_ir); + +/* Integer arithmetic. */ +void emit_arith(ph2_ir_t *ph2_ir, + int rd, + int rs1, + int rs2, + bool src1_const_known, + int src1_const) { - for (int i = 0; i < MAX_SKIP_IR; i++) { - if (skip_ir_index[i] >= 0 && skip_ir_index[i] == emit_ir_index) { - skip_ir_index[i] = -1; - return; - } - } - if (try_fold_mem_dest(ph2_ir)) - return; - if (try_fold_alu_to_slot(ph2_ir)) - return; - - int rd = map_ir_reg(ph2_ir->dest); - int rs1 = map_ir_reg(ph2_ir->src0); - if (src0_override >= 0 && src0_override_at == emit_ir_index) { - rs1 = src0_override; - src0_override = -1; - src0_override_at = -1; - } - int rs2 = map_ir_reg(ph2_ir->src1); - - /* Read the tracked value before invalidating, since dest may alias src1. */ - bool src1_const_known = false; - int src1_const = 0; - if (ph2_ir->src1 >= 0 && ph2_ir->src1 < REG_CNT && - const_reg_valid[ph2_ir->src1]) { - src1_const_known = true; - src1_const = const_reg_val[ph2_ir->src1]; - } - if (op_writes_dest(ph2_ir->op) && ph2_ir->dest >= 0 && - ph2_ir->dest < REG_CNT) { - const_reg_valid[ph2_ir->dest] = false; - shift_cache_kill(ph2_ir->dest); - } - - /* Anything that can touch a register this table does not name -- a call, or - * a division writing RAX and RDX -- invalidates all of it. This is the same - * set that drops frame mirrors below. - */ - if (!op_keeps_frame_mirrors(ph2_ir->op)) { - const_track_reset(); - shift_cache_reset(); - } - if (ph2_ir->op != OP_branch) - fused_cc_pending = false; - if (!branch_cc_for(ph2_ir->op)) { - cmp_mem_slot = -1; - cmp_imm_known = false; - } else if (src1_const_known) { - cmp_imm_known = true; - cmp_imm_val = src1_const; - } - - /* A value loaded only to be compared does not need a register: x86 takes - * the second compare operand from memory. Unlike arithmetic, nothing reuses - * this value afterwards, so folding it costs no later reload. - */ - if (ph2_ir->op == OP_load && emit_ir_index >= 0 && emit_next_ir && - branch_cc_for(emit_next_ir->op) && emit_next_ir->src1 == ph2_ir->dest && - emit_next_ir->src0 != ph2_ir->dest && - reg_dead_after(emit_ir_index + 2, ph2_ir->dest)) { - int w = load_width(ph2_ir); - if (w == 4 || w == 8) { - cmp_mem_slot = ph2_ir->src0; - cmp_mem_width = w; - return; - } - } - - if (!op_keeps_frame_mirrors(ph2_ir->op)) { - frame_mirror_reset(); - } else { - if (ph2_ir->op == OP_load && ph2_ir->dest >= 0 && - ph2_ir->dest < REG_CNT) { - int want = load_width(ph2_ir); - if (reg_mirror_valid[ph2_ir->dest] && - reg_mirror_slot[ph2_ir->dest] == ph2_ir->src0 && - reg_mirror_size[ph2_ir->dest] == want && - !reg_mirror_sext[ph2_ir->dest]) - return; /* the register still holds this slot */ - /* Some other register may already hold it. Copying between - * registers costs the same instruction but avoids waiting on the - * store-to-load forwarding this slot would otherwise require. - */ - for (int i = 0; i < REG_CNT; i++) { - if (!reg_mirror_valid[i] || - reg_mirror_slot[i] != ph2_ir->src0 || - reg_mirror_size[i] != want) - continue; - if (i == ph2_ir->dest && !reg_mirror_sext[i]) - continue; - int held = map_ir_reg(i); - - /* If this value exists only to be stored straight back out, let - * the store read the register that already holds it. Only sound - * when the register is bit-identical to what the load would - * produce, so a narrower mirror is excluded. Only when the - * consumer is the very next instruction: a later one could be - * skipped by another fold, and the copy would already be gone. - * And only when src0 is its one read of the register: the - * override redirects that read alone, so a second read of the - * same register -- src1, or a select's src2 -- would be left - * looking at whatever the load this skips was going to - * overwrite. - */ - if (!reg_mirror_sext[i] && src0_override < 0 && emit_next_ir && - op_src0_is_reg(emit_next_ir->op) && - emit_next_ir->src0 == ph2_ir->dest && - emit_next_ir->src1 != ph2_ir->dest && - !(op_src2_is_reg(emit_next_ir->op) && - emit_next_ir->src2 == ph2_ir->dest) && - reg_dead_after(emit_ir_index + 2, ph2_ir->dest)) { - src0_override = held; - src0_override_at = emit_ir_index + 1; - return; - } - emit_narrow_move(rd, held, want, reg_mirror_sext[i]); - reg_mirror_valid[ph2_ir->dest] = true; - reg_mirror_slot[ph2_ir->dest] = ph2_ir->src0; - reg_mirror_size[ph2_ir->dest] = want; - reg_mirror_sext[ph2_ir->dest] = false; - const_reg_valid[ph2_ir->dest] = false; - return; - } - } - - if (ph2_ir->dest >= 0 && ph2_ir->dest < REG_CNT) - reg_mirror_valid[ph2_ir->dest] = false; - - if (ph2_ir->op == OP_load && ph2_ir->dest >= 0 && - ph2_ir->dest < REG_CNT) { - reg_mirror_valid[ph2_ir->dest] = true; - reg_mirror_slot[ph2_ir->dest] = ph2_ir->src0; - reg_mirror_size[ph2_ir->dest] = load_width(ph2_ir); - reg_mirror_sext[ph2_ir->dest] = false; - } else if (ph2_ir->op == OP_store) { - /* Storing a register into the slot it already mirrors writes the - * bytes that are there. Coalescing a phi with its operand makes - * this common: the value's own write-back and the phi's copy become - * the same store. - */ - if (ph2_ir->src0 >= 0 && ph2_ir->src0 < REG_CNT && - reg_mirror_valid[ph2_ir->src0] && - reg_mirror_slot[ph2_ir->src0] == ph2_ir->src1) { - int keep = ph2_ir->size_bytes; - if (ph2_ir->is_pointer && keep != PTR_SIZE) - keep = PTR_SIZE; - if (reg_mirror_size[ph2_ir->src0] == keep) - return; - } - - /* The slot takes a new value, so any register mirroring it is now - * stale. - */ - for (int i = 0; i < REG_CNT; i++) { - if (reg_mirror_valid[i] && reg_mirror_slot[i] == ph2_ir->src1) - reg_mirror_valid[i] = false; - } - - /* The slot now holds the source register's low bytes, so that - * register mirrors it. A full-width store leaves the two - * bit-identical; a narrower one does not, and a later load of the - * slot sign-extends what was written -- which the sext flag - * records, so the reload becomes a MOVSX rather than a copy. - */ - if (ph2_ir->src0 >= 0 && ph2_ir->src0 < REG_CNT) { - int eff = ph2_ir->size_bytes; - if (ph2_ir->is_pointer && eff != PTR_SIZE) - eff = PTR_SIZE; - if (eff == 1 || eff == 2 || eff == 4 || eff == 8) { - reg_mirror_valid[ph2_ir->src0] = true; - reg_mirror_slot[ph2_ir->src0] = ph2_ir->src1; - reg_mirror_size[ph2_ir->src0] = eff; - reg_mirror_sext[ph2_ir->src0] = eff < 8; - } - } - } - } - - /* When the only consumer of a comparison is the branch right behind it, the - * CMP's flags can drive Jcc directly: SETcc, MOVZX and TEST all go away, - * and the boolean never needs to exist. The branch ends the block, so a - * result that were live elsewhere would have been stored between the two - * instructions -- and then this test would not fire. - */ - int fuse_cc = branch_cc_for(ph2_ir->op); - if (fuse_cc && emit_next_ir && emit_next_ir->op == OP_branch && - emit_next_ir->src0 == ph2_ir->dest) { - emit_cmp(rs1, rs2); - fused_cc = fuse_cc; - fused_cc_pending = true; - return; - } - - /* "if (x & mask)" needs no destination register: TEST leaves exactly the - * flags AND would, and the branch reads nothing else. This drops the AND, - * the MOV that staged its destination, and the TEST the branch would - * otherwise emit -- three instructions down to one. Only when the masked - * value is dead afterwards, since TEST does not write it. - */ - if (ph2_ir->op == OP_bit_and && emit_next_ir && - emit_next_ir->op == OP_branch && emit_next_ir->src0 == ph2_ir->dest && - emit_ir_index >= 0 && reg_dead_after(emit_ir_index + 2, ph2_ir->dest)) { - if (src1_const_known) { - emit_rex(1, 0, rs1); - emit_byte(0xF7); /* TEST rs1, imm32 */ - emit_byte(modrm(MOD_DIRECT, 0, reg_low3(rs1))); - emit_dword(src1_const); - } else { - emit_rex(1, rs2, rs1); - emit_byte(0x85); /* TEST rs1, rs2 */ - emit_byte(modrm(MOD_DIRECT, reg_low3(rs2), reg_low3(rs1))); - } - fused_cc = 0x85; /* JNZ */ - fused_cc_pending = true; - return; - } - switch (ph2_ir->op) { - case OP_load_constant: { - /* MOV r64, imm32 (sign-extended). The immediate is in src0, not a - * register index. - */ - if (ph2_ir->dest >= 0 && ph2_ir->dest < REG_CNT) { - const_reg_valid[ph2_ir->dest] = true; - const_reg_val[ph2_ir->dest] = ph2_ir->src0; - - /* Nothing will read the register itself: every remaining use turns - * into a shift immediate, so the materialisation is dead. - */ - if (const_load_dead(emit_ir_index + 1, ph2_ir->dest, ph2_ir->src0)) - return; - } - emit_rex(1, -1, rd); - emit_byte(0xC7); - emit_byte(modrm(MOD_DIRECT, 0, reg_low3(rd))); - emit_dword(ph2_ir->src0); - return; - } - - case OP_assign: { - emit_mov_reg(rd, rs1); - return; - } - case OP_add: { /* A tracked literal becomes an immediate, which also makes the * instruction that materialised it dead. @@ -2039,6 +1807,20 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) emit_byte(modrm(MOD_DIRECT, 3, reg_low3(rd))); return; } + default: + break; + } +} + +/* Bitwise operations and shifts. */ +void emit_bitwise(ph2_ir_t *ph2_ir, + int rd, + int rs1, + int rs2, + bool src1_const_known, + int src1_const) +{ + switch (ph2_ir->op) { case OP_bit_and: case OP_bit_or: case OP_bit_xor: { @@ -2224,10 +2006,19 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) emit_byte(modrm(MOD_DIRECT, 3, reg_low3(rd))); return; } - case OP_eq: - case OP_neq: - case OP_lt: - case OP_leq: + default: + break; + } +} + +/* Comparisons, and the jumps and branches they feed. */ +void emit_compare_jump(ph2_ir_t *ph2_ir, int rd, int rs1, int rs2) +{ + switch (ph2_ir->op) { + case OP_eq: + case OP_neq: + case OP_lt: + case OP_leq: case OP_gt: case OP_geq: /* CMP rs1, rs2, then turn the flags into 0 or 1 in rd. The six @@ -2348,6 +2139,15 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) } return; } + default: + break; + } +} + +/* Calls, and the returns that unwind them. */ +void emit_call_return(ph2_ir_t *ph2_ir, int rs1) +{ + switch (ph2_ir->op) { case OP_call: /* CALL rel32 - find the target function and calculate relative offset */ @@ -2371,6 +2171,7 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) */ printf("Error: Undefined function called: %s\n", ph2_ir->func_name); + fflush(stdout); /* see fatal() */ abort(); } else { emit_byte(0xE8); /* CALL rel32 */ @@ -2473,6 +2274,15 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) printf("Warning: OP_func_ret should not reach x64 backend\n"); return; + default: + break; + } +} + +/* Stack slots, addresses, conditional moves, loads and stores. */ +void emit_memory(ph2_ir_t *ph2_ir, int rd, int rs1) +{ + switch (ph2_ir->op) { case OP_allocat: { /* SUB rsp, size. * @@ -2608,6 +2418,15 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) } return; + default: + break; + } +} + +/* Indirect access through a pointer. */ +void emit_read_write(ph2_ir_t *ph2_ir, int rd, int rs1, int rs2) +{ + switch (ph2_ir->op) { case OP_read: /* Load *rs1 into rd. The element width lives in src1 (1, 2 or 4); * pointer-typed reads must move a full pointer instead. @@ -2751,6 +2570,15 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) emit_byte(modrm(MOD_DIRECT, 0, 7)); return; + default: + break; + } +} + +/* Globals, functions, and the data sections they live in. */ +void emit_global(ph2_ir_t *ph2_ir, int rd, int rs1) +{ + switch (ph2_ir->op) { case OP_load_func: case OP_global_load_func: /* Stage the callee address in R11 for the OP_indirect that follows. R11 @@ -2964,6 +2792,15 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) emit_dword(0); return; } + default: + break; + } +} + +/* Logical operators and width conversions. */ +void emit_logic_cast(ph2_ir_t *ph2_ir, int rd, int rs1) +{ + switch (ph2_ir->op) { case OP_log_not: { /* TEST rs1, rs1; SETE r11b; MOVZX rd, r11b. * @@ -3058,6 +2895,318 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) } return; + default: + break; + } +} + +void emit_ph2_ir(ph2_ir_t *ph2_ir) +{ + for (int i = 0; i < MAX_SKIP_IR; i++) { + if (skip_ir_index[i] >= 0 && skip_ir_index[i] == emit_ir_index) { + skip_ir_index[i] = -1; + return; + } + } + if (try_fold_mem_dest(ph2_ir)) + return; + if (try_fold_alu_to_slot(ph2_ir)) + return; + + int rd = map_ir_reg(ph2_ir->dest); + int rs1 = map_ir_reg(ph2_ir->src0); + if (src0_override >= 0 && src0_override_at == emit_ir_index) { + rs1 = src0_override; + src0_override = -1; + src0_override_at = -1; + } + int rs2 = map_ir_reg(ph2_ir->src1); + + /* Read the tracked value before invalidating, since dest may alias src1. */ + bool src1_const_known = false; + int src1_const = 0; + if (ph2_ir->src1 >= 0 && ph2_ir->src1 < REG_CNT && + const_reg_valid[ph2_ir->src1]) { + src1_const_known = true; + src1_const = const_reg_val[ph2_ir->src1]; + } + if (op_writes_dest(ph2_ir->op) && ph2_ir->dest >= 0 && + ph2_ir->dest < REG_CNT) { + const_reg_valid[ph2_ir->dest] = false; + shift_cache_kill(ph2_ir->dest); + } + + /* Anything that can touch a register this table does not name -- a call, or + * a division writing RAX and RDX -- invalidates all of it. This is the same + * set that drops frame mirrors below. + */ + if (!op_keeps_frame_mirrors(ph2_ir->op)) { + const_track_reset(); + shift_cache_reset(); + } + if (ph2_ir->op != OP_branch) + fused_cc_pending = false; + if (!branch_cc_for(ph2_ir->op)) { + cmp_mem_slot = -1; + cmp_imm_known = false; + } else if (src1_const_known) { + cmp_imm_known = true; + cmp_imm_val = src1_const; + } + + /* A value loaded only to be compared does not need a register: x86 takes + * the second compare operand from memory. Unlike arithmetic, nothing reuses + * this value afterwards, so folding it costs no later reload. + */ + if (ph2_ir->op == OP_load && emit_ir_index >= 0 && emit_next_ir && + branch_cc_for(emit_next_ir->op) && emit_next_ir->src1 == ph2_ir->dest && + emit_next_ir->src0 != ph2_ir->dest && + reg_dead_after(emit_ir_index + 2, ph2_ir->dest)) { + int w = load_width(ph2_ir); + if (w == 4 || w == 8) { + cmp_mem_slot = ph2_ir->src0; + cmp_mem_width = w; + return; + } + } + + if (!op_keeps_frame_mirrors(ph2_ir->op)) { + frame_mirror_reset(); + } else { + if (ph2_ir->op == OP_load && ph2_ir->dest >= 0 && + ph2_ir->dest < REG_CNT) { + int want = load_width(ph2_ir); + if (reg_mirror_valid[ph2_ir->dest] && + reg_mirror_slot[ph2_ir->dest] == ph2_ir->src0 && + reg_mirror_size[ph2_ir->dest] == want && + !reg_mirror_sext[ph2_ir->dest]) + return; /* the register still holds this slot */ + /* Some other register may already hold it. Copying between + * registers costs the same instruction but avoids waiting on the + * store-to-load forwarding this slot would otherwise require. + */ + for (int i = 0; i < REG_CNT; i++) { + if (!reg_mirror_valid[i] || + reg_mirror_slot[i] != ph2_ir->src0 || + reg_mirror_size[i] != want) + continue; + if (i == ph2_ir->dest && !reg_mirror_sext[i]) + continue; + int held = map_ir_reg(i); + + /* If this value exists only to be stored straight back out, let + * the store read the register that already holds it. Only sound + * when the register is bit-identical to what the load would + * produce, so a narrower mirror is excluded. Only when the + * consumer is the very next instruction: a later one could be + * skipped by another fold, and the copy would already be gone. + * And only when src0 is its one read of the register: the + * override redirects that read alone, so a second read of the + * same register -- src1, or a select's src2 -- would be left + * looking at whatever the load this skips was going to + * overwrite. + */ + if (!reg_mirror_sext[i] && src0_override < 0 && emit_next_ir && + op_src0_is_reg(emit_next_ir->op) && + emit_next_ir->src0 == ph2_ir->dest && + emit_next_ir->src1 != ph2_ir->dest && + !(op_src2_is_reg(emit_next_ir->op) && + emit_next_ir->src2 == ph2_ir->dest) && + reg_dead_after(emit_ir_index + 2, ph2_ir->dest)) { + src0_override = held; + src0_override_at = emit_ir_index + 1; + return; + } + emit_narrow_move(rd, held, want, reg_mirror_sext[i]); + reg_mirror_valid[ph2_ir->dest] = true; + reg_mirror_slot[ph2_ir->dest] = ph2_ir->src0; + reg_mirror_size[ph2_ir->dest] = want; + reg_mirror_sext[ph2_ir->dest] = false; + const_reg_valid[ph2_ir->dest] = false; + return; + } + } + + if (ph2_ir->dest >= 0 && ph2_ir->dest < REG_CNT) + reg_mirror_valid[ph2_ir->dest] = false; + + if (ph2_ir->op == OP_load && ph2_ir->dest >= 0 && + ph2_ir->dest < REG_CNT) { + reg_mirror_valid[ph2_ir->dest] = true; + reg_mirror_slot[ph2_ir->dest] = ph2_ir->src0; + reg_mirror_size[ph2_ir->dest] = load_width(ph2_ir); + reg_mirror_sext[ph2_ir->dest] = false; + } else if (ph2_ir->op == OP_store) { + /* Storing a register into the slot it already mirrors writes the + * bytes that are there. Coalescing a phi with its operand makes + * this common: the value's own write-back and the phi's copy become + * the same store. + */ + if (ph2_ir->src0 >= 0 && ph2_ir->src0 < REG_CNT && + reg_mirror_valid[ph2_ir->src0] && + reg_mirror_slot[ph2_ir->src0] == ph2_ir->src1) { + int keep = ph2_ir->size_bytes; + if (ph2_ir->is_pointer && keep != PTR_SIZE) + keep = PTR_SIZE; + if (reg_mirror_size[ph2_ir->src0] == keep) + return; + } + + /* The slot takes a new value, so any register mirroring it is now + * stale. + */ + for (int i = 0; i < REG_CNT; i++) { + if (reg_mirror_valid[i] && reg_mirror_slot[i] == ph2_ir->src1) + reg_mirror_valid[i] = false; + } + + /* The slot now holds the source register's low bytes, so that + * register mirrors it. A full-width store leaves the two + * bit-identical; a narrower one does not, and a later load of the + * slot sign-extends what was written -- which the sext flag + * records, so the reload becomes a MOVSX rather than a copy. + */ + if (ph2_ir->src0 >= 0 && ph2_ir->src0 < REG_CNT) { + int eff = ph2_ir->size_bytes; + if (ph2_ir->is_pointer && eff != PTR_SIZE) + eff = PTR_SIZE; + if (eff == 1 || eff == 2 || eff == 4 || eff == 8) { + reg_mirror_valid[ph2_ir->src0] = true; + reg_mirror_slot[ph2_ir->src0] = ph2_ir->src1; + reg_mirror_size[ph2_ir->src0] = eff; + reg_mirror_sext[ph2_ir->src0] = eff < 8; + } + } + } + } + + /* When the only consumer of a comparison is the branch right behind it, the + * CMP's flags can drive Jcc directly: SETcc, MOVZX and TEST all go away, + * and the boolean never needs to exist. The branch ends the block, so a + * result that were live elsewhere would have been stored between the two + * instructions -- and then this test would not fire. + */ + int fuse_cc = branch_cc_for(ph2_ir->op); + if (fuse_cc && emit_next_ir && emit_next_ir->op == OP_branch && + emit_next_ir->src0 == ph2_ir->dest) { + emit_cmp(rs1, rs2); + fused_cc = fuse_cc; + fused_cc_pending = true; + return; + } + + /* "if (x & mask)" needs no destination register: TEST leaves exactly the + * flags AND would, and the branch reads nothing else. This drops the AND, + * the MOV that staged its destination, and the TEST the branch would + * otherwise emit -- three instructions down to one. Only when the masked + * value is dead afterwards, since TEST does not write it. + */ + if (ph2_ir->op == OP_bit_and && emit_next_ir && + emit_next_ir->op == OP_branch && emit_next_ir->src0 == ph2_ir->dest && + emit_ir_index >= 0 && reg_dead_after(emit_ir_index + 2, ph2_ir->dest)) { + if (src1_const_known) { + emit_rex(1, 0, rs1); + emit_byte(0xF7); /* TEST rs1, imm32 */ + emit_byte(modrm(MOD_DIRECT, 0, reg_low3(rs1))); + emit_dword(src1_const); + } else { + emit_rex(1, rs2, rs1); + emit_byte(0x85); /* TEST rs1, rs2 */ + emit_byte(modrm(MOD_DIRECT, reg_low3(rs2), reg_low3(rs1))); + } + fused_cc = 0x85; /* JNZ */ + fused_cc_pending = true; + return; + } + + switch (ph2_ir->op) { + case OP_load_constant: { + /* MOV r64, imm32 (sign-extended). The immediate is in src0, not a + * register index. + */ + if (ph2_ir->dest >= 0 && ph2_ir->dest < REG_CNT) { + const_reg_valid[ph2_ir->dest] = true; + const_reg_val[ph2_ir->dest] = ph2_ir->src0; + + /* Nothing will read the register itself: every remaining use turns + * into a shift immediate, so the materialisation is dead. + */ + if (const_load_dead(emit_ir_index + 1, ph2_ir->dest, ph2_ir->src0)) + return; + } + emit_rex(1, -1, rd); + emit_byte(0xC7); + emit_byte(modrm(MOD_DIRECT, 0, reg_low3(rd))); + emit_dword(ph2_ir->src0); + return; + } + + case OP_assign: { + emit_mov_reg(rd, rs1); + return; + } + + case OP_add: + case OP_sub: + case OP_mul: + case OP_div: + case OP_mod: + emit_arith(ph2_ir, rd, rs1, rs2, src1_const_known, src1_const); + break; + case OP_bit_and: + case OP_bit_or: + case OP_bit_xor: + case OP_bit_not: + case OP_negate: + case OP_lshift: + case OP_rshift: + emit_bitwise(ph2_ir, rd, rs1, rs2, src1_const_known, src1_const); + break; + case OP_eq: + case OP_neq: + case OP_lt: + case OP_leq: + case OP_gt: + case OP_geq: + case OP_jump: + case OP_branch: + emit_compare_jump(ph2_ir, rd, rs1, rs2); + break; + case OP_call: + case OP_return: + case OP_func_ret: + emit_call_return(ph2_ir, rs1); + break; + case OP_allocat: + case OP_address_of: + case OP_cmov: + case OP_load: + case OP_store: + emit_memory(ph2_ir, rd, rs1); + break; + case OP_read: + case OP_write: + case OP_indirect: + emit_read_write(ph2_ir, rd, rs1, rs2); + break; + case OP_load_func: + case OP_global_load_func: + case OP_address_of_func: + case OP_global_address_of: + case OP_global_load: + case OP_global_store: + case OP_load_data_address: + case OP_load_rodata_address: + emit_global(ph2_ir, rd, rs1); + break; + case OP_log_not: + case OP_log_and: + case OP_log_or: + case OP_trunc: + case OP_sign_ext: + case OP_cast: + emit_logic_cast(ph2_ir, rd, rs1); + break; case OP_define: /* Update the function's actual offset to current code position */ { diff --git a/src/x64.c b/src/x64.c index f5dce37c..e5b5c77a 100644 --- a/src/x64.c +++ b/src/x64.c @@ -61,10 +61,15 @@ int reg_low3(int reg) return reg & 0x07; } -/* Helper to emit bytes to the code buffer */ -void emit_byte(char byte) +/* Helper to emit bytes to the code buffer Takes an int because shecc has no + * 'unsigned': every opcode above 0x7F would otherwise be a value the call site + * cannot write and the conversion silently changes. The narrowing to the byte + * actually emitted happens here, once and on purpose, rather than 134 times + * implicitly. + */ +void emit_byte(int byte) { - strbuf_putc(elf_code, byte); + strbuf_putc(elf_code, (char) (byte & 0xff)); } /* Emit a REX prefix. 'w' selects a 64-bit operand size; the two register diff --git a/tests/arm-abi.sh b/tests/arm-abi.sh index b4aff266..fd1b2771 100755 --- a/tests/arm-abi.sh +++ b/tests/arm-abi.sh @@ -56,16 +56,20 @@ fi case "$1" in "0") readonly SHECC="$PWD/out/shecc" - readonly STAGE="Stage 0 (Host Compiler)" ;; + readonly STAGE="Stage 0 (Host Compiler)" + ;; "1") readonly SHECC="${TARGET_EXEC:-} $PWD/out/shecc-stage1.elf" - readonly STAGE="Stage 1 (Cross-compiled)" ;; + readonly STAGE="Stage 1 (Cross-compiled)" + ;; "2") readonly SHECC="${TARGET_EXEC:-} $PWD/out/shecc-stage2.elf" - readonly STAGE="Stage 2 (Self-hosted)" ;; + readonly STAGE="Stage 2 (Self-hosted)" + ;; *) echo "Error: Invalid stage '$1'. Use 0, 1, or 2." - exit 1 ;; + exit 1 + ;; esac DYNLINK="${2:-0}" @@ -80,9 +84,10 @@ echo -e "Compiler: $SHECC" echo "" # Helper Functions -update_category_stats() { +update_category_stats() +{ local category="$1" - local result="$2" # "pass" or "fail" + local result="$2" # "pass" or "fail" if [[ -z "${CATEGORY_TESTS[$category]:-}" ]]; then CATEGORY_TESTS[$category]=0 @@ -99,7 +104,8 @@ update_category_stats() { fi } -show_progress() { +show_progress() +{ if [[ "$SHOW_PROGRESS" == "1" ]]; then echo -n "." PROGRESS_COUNT=$((PROGRESS_COUNT + 1)) @@ -110,7 +116,8 @@ show_progress() { } # Test execution function -run_abi_test() { +run_abi_test() +{ local test_name="$1" local category="$2" local source_code="$3" @@ -164,11 +171,10 @@ run_abi_test() { run_output=$(eval "$run_cmd" 2>&1) exit_code=$? - # Check result - # If the exit code is not zero or the output is not expected, - # set 'run_status' to "FAILED". + # If the exit code is not zero or the output is not expected, set + # 'run_status' to "FAILED". run_status="SUCCESS" - if [[ $exit_code -ne 0 || ( -n "$expected_output" && "$run_output" != *"$expected_output"* ) ]]; then + if [[ $exit_code -ne 0 || (-n "$expected_output" && "$run_output" != *"$expected_output"*) ]]; then run_status="FAILED" fi @@ -200,7 +206,8 @@ run_abi_test() { # Parameter Passing Tests -test_one_arg() { +test_one_arg() +{ run_abi_test "One argument (r0)" "Parameter Passing" ' #include int add_42(int x) { return x + 42; } @@ -216,7 +223,8 @@ int main() { ' "PASS" } -test_two_args() { +test_two_args() +{ run_abi_test "Two arguments (r0, r1)" "Parameter Passing" ' #include int add(int a, int b) { return a + b; } @@ -232,7 +240,8 @@ int main() { ' "PASS" } -test_four_args() { +test_four_args() +{ run_abi_test "Four arguments (r0-r3)" "Parameter Passing" ' #include int sum4(int a, int b, int c, int d) { return a + b + c + d; } @@ -248,7 +257,8 @@ int main() { ' "PASS" } -test_five_args() { +test_five_args() +{ run_abi_test "Five arguments (r0-r3 + stack)" "Parameter Passing" ' #include int sum5(int a, int b, int c, int d, int e) { return a + b + c + d + e; } @@ -264,7 +274,8 @@ int main() { ' "PASS" } -test_eight_args() { +test_eight_args() +{ run_abi_test "Eight arguments (stack-heavy)" "Parameter Passing" ' #include int sum8(int a, int b, int c, int d, int e, int f, int g, int h) { @@ -284,7 +295,8 @@ int main() { # Stack Alignment Tests -test_stack_alignment_basic() { +test_stack_alignment_basic() +{ run_abi_test "Basic stack alignment" "Stack Alignment" ' #include int is_aligned(void *ptr) { @@ -306,7 +318,8 @@ int main() { ' "PASS" } -test_stack_alignment_extended() { +test_stack_alignment_extended() +{ run_abi_test "Stack alignment with extended args" "Stack Alignment" ' #include int is_aligned(void *ptr) { @@ -331,7 +344,8 @@ int main() { # Return Value Tests -test_return_char() { +test_return_char() +{ run_abi_test "Return char value" "Return Values" ' #include char get_char(void) { return '\''A'\''; } @@ -346,7 +360,8 @@ int main() { ' "PASS" } -test_return_int() { +test_return_int() +{ run_abi_test "Return int value" "Return Values" ' #include int get_value(void) { return 12345; } @@ -361,7 +376,8 @@ int main() { ' "PASS" } -test_return_pointer() { +test_return_pointer() +{ run_abi_test "Return pointer value" "Return Values" ' #include int *return_ptr(int *p) { return p; } @@ -380,7 +396,8 @@ int main() { # External Function Call Tests (Dynamic Linking Only) -test_printf_one_arg() { +test_printf_one_arg() +{ run_abi_test "printf with 1 argument" "External Calls" ' #include int main() { @@ -390,7 +407,8 @@ int main() { ' "PASS" 1 } -test_printf_multi_args() { +test_printf_multi_args() +{ run_abi_test "printf with 5 arguments" "External Calls" ' #include int main() { @@ -400,7 +418,8 @@ int main() { ' "Values: 1 2 3 4" 1 } -test_strlen() { +test_strlen() +{ run_abi_test "strlen external call" "External Calls" ' #include #include @@ -416,7 +435,8 @@ int main() { ' "PASS" 1 } -test_strcpy() { +test_strcpy() +{ run_abi_test "strcpy external call" "External Calls" ' #include #include @@ -434,7 +454,8 @@ int main() { ' "PASS" 1 } -test_memcpy() { +test_memcpy() +{ run_abi_test "memcpy external call" "External Calls" ' #include #include @@ -454,7 +475,8 @@ int main() { # Register Preservation Tests -test_local_vars_preserved() { +test_local_vars_preserved() +{ run_abi_test "Local variables preserved across calls" "Register Preservation" ' #include int dummy(int a, int b, int c, int d, int e, int f, int g, int h) { @@ -473,7 +495,8 @@ int main() { ' "PASS" } -test_recursive_preservation() { +test_recursive_preservation() +{ run_abi_test "Register preservation in recursion" "Register Preservation" ' #include int factorial(int n) { @@ -495,7 +518,8 @@ int main() { # Structure Passing Tests -test_small_struct() { +test_small_struct() +{ run_abi_test "Small struct passing (≤4 bytes)" "Structure Passing" ' #include typedef struct { char a; char b; short c; } SmallStruct; diff --git a/tests/driver.sh b/tests/driver.sh index ec08547e..08a9dd45 100755 --- a/tests/driver.sh +++ b/tests/driver.sh @@ -11,16 +11,18 @@ readonly SHOW_PROGRESS="${SHOW_PROGRESS:-1}" readonly COLOR_OUTPUT="${COLOR_OUTPUT:-1}" # Substring match against the category name; empty runs everything. readonly TEST_FILTER="${TEST_FILTER:-}" + # 1 stops at the first failure. The default reports every failure and still # exits non-zero at the end, so one bad case no longer hides the other 600. readonly FAIL_FAST="${FAIL_FAST:-0}" -# Everything the run creates goes here, so it can be removed in one step -- -# the suite used to leave ~2400 files in /tmp per invocation. Kept on failure, +# Everything the run creates goes here, so it can be removed in one step -- the +# suite used to leave ~2400 files in /tmp per invocation. Kept on failure, # because report_test_failure names the files it wants you to look at. readonly TEST_TMPDIR="$(mktemp -d)" export TMPDIR="$TEST_TMPDIR" -function cleanup() { +function cleanup() +{ if [ "$FAILED_TESTS" -eq 0 ]; then rm -rf "$TEST_TMPDIR" else @@ -32,7 +34,8 @@ trap cleanup EXIT # Set by begin_category; tests outside the selected categories return early. CATEGORY_SELECTED=1 -function test_selected() { +function test_selected() +{ [ "$CATEGORY_SELECTED" = "1" ] } @@ -43,7 +46,7 @@ readonly TESTS_DIR="$(cd "$(dirname "$0")" && pwd)" # Pointer width of the configured target. The sizeof tests below assert on it, # and it differs between the 32-bit targets and x86-64. PTR_SZ=$(sed -n 's/^#define PTR_SIZE \([0-9]*\).*/\1/p' \ - "$TESTS_DIR/../config" 2>/dev/null | head -1) + "$TESTS_DIR/../config" 2> /dev/null | head -1) [ -n "${PTR_SZ}" ] || PTR_SZ=4 # Variadic arguments occupy one pointer-sized slot each, so an int-based walk @@ -86,16 +89,20 @@ fi case "$1" in "0") readonly SHECC="$PWD/out/shecc" - readonly STAGE="Stage 0 (Host Compiler)" ;; + readonly STAGE="Stage 0 (Host Compiler)" + ;; "1") readonly SHECC="${TARGET_EXEC:-} $PWD/out/shecc-stage1.elf" - readonly STAGE="Stage 1 (Cross-compiled)" ;; + readonly STAGE="Stage 1 (Cross-compiled)" + ;; "2") readonly SHECC="${TARGET_EXEC:-} $PWD/out/shecc-stage2.elf" - readonly STAGE="Stage 2 (Self-hosted)" ;; + readonly STAGE="Stage 2 (Self-hosted)" + ;; *) echo "$1 is not a valid stage" - exit 1 ;; + exit 1 + ;; esac if [ $# -ge 2 ] && [ "$2" = "1" ]; then @@ -109,15 +116,16 @@ fi # Utility Functions # Color output functions -function print_color() { +function print_color() +{ if [ "$COLOR_OUTPUT" = "1" ]; then case "$1" in - green) echo -ne "\033[32m$2\033[0m" ;; - red) echo -ne "\033[31m$2\033[0m" ;; + green) echo -ne "\033[32m$2\033[0m" ;; + red) echo -ne "\033[31m$2\033[0m" ;; yellow) echo -ne "\033[33m$2\033[0m" ;; - blue) echo -ne "\033[34m$2\033[0m" ;; - bold) echo -ne "\033[1m$2\033[0m" ;; - *) echo -n "$2" ;; + blue) echo -ne "\033[34m$2\033[0m" ;; + bold) echo -ne "\033[1m$2\033[0m" ;; + *) echo -n "$2" ;; esac else echo -n "$2" @@ -125,7 +133,8 @@ function print_color() { } # Begin a new test category -function begin_category() { +function begin_category() +{ local category="$1" local description="${2:-}" @@ -146,8 +155,8 @@ function begin_category() { CATEGORY_SELECTED=1 else case "$category" in - *"$TEST_FILTER"*) CATEGORY_SELECTED=1 ;; - *) CATEGORY_SELECTED=0 ;; + *"$TEST_FILTER"*) CATEGORY_SELECTED=1 ;; + *) CATEGORY_SELECTED=0 ;; esac fi CATEGORY_TESTS["$category"]=0 @@ -167,7 +176,8 @@ function begin_category() { } # Show progress indicator -function show_progress() { +function show_progress() +{ if [ "$SHOW_PROGRESS" = "1" ]; then ((PROGRESS_COUNT++)) if [ $((PROGRESS_COUNT % 10)) -eq 0 ]; then @@ -180,7 +190,8 @@ function show_progress() { } # Core test failure reporting function (consolidated) -function report_test_failure() { +function report_test_failure() +{ local test_type="$1" local tmp_in="$2" local tmp_exe="$3" @@ -223,7 +234,8 @@ function report_test_failure() { } # Main test execution function -function try() { +function try() +{ local expected="$1" local expected_output="" local input="" @@ -234,6 +246,7 @@ function try() { elif [ $# -eq 3 ]; then expected_output="$2" input="$3" + # An expectation was supplied, so compare against it -- including when # it is empty, which asserts that the program prints nothing. check_output=1 @@ -245,9 +258,10 @@ function try() { local tmp_exe="$(mktemp)" local tmp_err="$(mktemp)" echo "$input" > "$tmp_in" + # Keep the compiler's diagnostic rather than discarding it: without it a # failure reports only an exit-code mismatch and never says why. - $SHECC $SHECC_CFLAGS -o "$tmp_exe" "$tmp_in" 2>"$tmp_err" + $SHECC $SHECC_CFLAGS -o "$tmp_exe" "$tmp_in" 2> "$tmp_err" chmod +x $tmp_exe local output='' @@ -273,13 +287,15 @@ function try() { fi } -function try_() { +function try_() +{ local expected="$1" local input="$(cat)" try "$expected" "$input" } -function try_output() { +function try_output() +{ local expected="$1" local expected_output="$2" local input="$(cat)" @@ -288,32 +304,32 @@ function try_output() { # Compile and run a checked-in program through the same path as inline cases. # This keeps the small end-to-end programs in both stage-0 and stage-2 runs. -function try_file() { +function try_file() +{ try "$1" "$2" "$(< "$3")" } -# try_compile_error - test shecc with invalid C program -# Usage: -# - try_compile_error invalid_input_code -# compile "invalid_input_code" with shecc so that shecc generates a -# compilation error message. +# try_compile_error - test shecc with invalid C program Usage: +# - try_compile_error invalid_input_code compile "invalid_input_code" with shecc +# so that shecc generates a compilation error message. # -# This function uses shecc to compile invalid code and obtains the exit -# code returned by shecc. The exit code must be a non-zero value to -# indicate that shecc has the ability to parse the invalid code and -# output an error message. -function try_compile_error() { +# This function uses shecc to compile invalid code and obtains the exit code +# returned by shecc. The exit code must be a non-zero value to indicate that +# shecc has the ability to parse the invalid code and output an error message. +function try_compile_error() +{ local input=$(cat) test_selected || return 0 local tmp_in="$(mktemp --suffix .c)" local tmp_exe="$(mktemp)" echo "$input" > "$tmp_in" - # Suppress compiler error output and "Aborted" messages completely - # Run in a subshell with job control disabled + + # Suppress compiler error output and "Aborted" messages completely Run in a + # subshell with job control disabled ( - set +m 2>/dev/null # Disable job control messages + set +m 2> /dev/null # Disable job control messages $SHECC $SHECC_CFLAGS -o "$tmp_exe" "$tmp_in" 2>&1 - ) >/dev/null 2>&1 + ) > /dev/null 2>&1 local exit_code=$? ((TOTAL_TESTS++)) @@ -331,20 +347,23 @@ function try_compile_error() { fi } -function items() { +function items() +{ local expected="$1" local input="$2" try "$expected" "int main(int argc, int argv) { $input }" } -function expr() { +function expr() +{ local expected="$1" local input="$2" items "$expected" "exit($input);" } # Batch test runners for common patterns -function run_expr_tests() { +function run_expr_tests() +{ local -n tests_ref=$1 for test in "${tests_ref[@]}"; do IFS=' ' read -r expected code <<< "$test" @@ -352,7 +371,8 @@ function run_expr_tests() { done } -function run_try_tests() { +function run_try_tests() +{ local -n tests_ref=$1 for test in "${tests_ref[@]}"; do local expected=$(echo "$test" | head -n1) @@ -361,7 +381,8 @@ function run_try_tests() { done } -function run_items_tests() { +function run_items_tests() +{ local -n tests_ref=$1 for test in "${tests_ref[@]}"; do IFS=' ' read -r expected code <<< "$test" @@ -369,12 +390,12 @@ function run_items_tests() { done } -# try_large - test shecc with large return values (> 255) -# Usage: -# - try_large expected_value input_code -# compile "input_code" with shecc and verify the return value by printing it -# instead of using exit code (which is limited to 0-255). -function try_large() { +# try_large - test shecc with large return values (> 255) Usage: +# - try_large expected_value input_code compile "input_code" with shecc and +# verify the return value by printing it instead of using exit code (which is +# limited to 0-255). +function try_large() +{ local expected="$1" local input="$(cat)" @@ -394,7 +415,7 @@ int main() { EOF # Suppress compiler warnings by redirecting stderr - $SHECC $SHECC_CFLAGS -o "$tmp_exe" "$tmp_in" 2>/dev/null + $SHECC $SHECC_CFLAGS -o "$tmp_exe" "$tmp_in" 2> /dev/null chmod +x $tmp_exe local output=$(${TARGET_EXEC:-} "$tmp_exe") @@ -590,7 +611,7 @@ declare -a bitwise_tests=( ) run_expr_tests bitwise_tests -try_output 0 "128 59926 -6 -4 -500283" << EOF +try_output 0 "128 59926 -6 -4 -500283" << EOF int main() { printf("%d %d %d %d %d", 32768 >> 8, 245458999 >> 12, -11 >> 1, -16 >> 2, -1000565 >> 1); return 0; @@ -630,8 +651,8 @@ run_items_tests variable_tests # Category: Compound Literals begin_category "Compound Literals" "Testing C99 compound literal features" -# Compound literal support - C90/C99 compliant implementation -# Basic struct compound literals (verified working) +# Compound literal support - C90/C99 compliant implementation Basic struct +# compound literals (verified working) try_ 42 << EOF typedef struct { int x; int y; } point_t; int main() { @@ -764,7 +785,8 @@ EOF # Enhanced compound literal tests - C99 features with non-standard extensions # These tests validate both standard C99 compound literals and the non-standard -# behavior required by the test suite (array compound literals in scalar contexts) +# behavior required by the test suite (array compound literals in scalar +# contexts) # Test: Array compound literal assigned to scalar int (non-standard) try_ 100 << EOF @@ -968,7 +990,7 @@ items 8 "if (1) return 010; else return 11;" items 10 "int a; a = 012 - 10; int b; b = 0100 - 64; if (a) b = 10; else if (0) return a; else if (a) return b; else return 10;" # The values on both sides of the select, its condition, and unrelated values -# are all used after the join. This keeps the register file full when the +# are all used after the join. This keeps the register file full when the # allocator has to choose the select result's register. try_ 30 << EOF int pick(int a, int b, int c, int d, int e, int f, int g) { @@ -1023,8 +1045,7 @@ items 0 "int i = 0; for (;; i++) { break; } return i;" # Category: Comments begin_category "Comments" "Testing C-style and C++-style comment parsing" -# C-style comments / C++-style comments -# Start +# C-style comments / C++-style comments Start try_ 0 << EOF /* This is a test C-style comments */ int main() { return 0; } @@ -1172,8 +1193,8 @@ try_compile_error << EOF int main(void, int i) {} EOF -# Unreachable declaration should not cause prog segmentation fault -# (prog should leave normally with exit code 0) +# Unreachable declaration should not cause prog segmentation fault (prog should +# leave normally with exit code 0) try_ 0 << EOF int main() { @@ -1503,8 +1524,8 @@ int main() { } EOF -# Pointer difference calculations -# Test basic pointer subtraction returning element count +# Pointer difference calculations Test basic pointer subtraction returning +# element count try_ 5 << EOF int main() { char arr[10]; @@ -1784,8 +1805,8 @@ int main() { } EOF -# A local function pointer shadows a global function. Copying it must load -# the local variable's stored target, rather than materializing the global +# A local function pointer shadows a global function. Copying it must load the +# local variable's stored target, rather than materializing the global # function's address. try_ 9 << EOF int target(int x) { return x + 3; } @@ -1821,9 +1842,8 @@ int main() { } EOF - -# Addressing a pointer to a function-pointer aggregate must return the -# pointer variable's address, not backing storage for its pointee. +# Addressing a pointer to a function-pointer aggregate must return the pointer +# variable's address, not backing storage for its pointee. try_ 5 << EOF typedef struct { int (*fn)(int); @@ -1957,8 +1977,7 @@ int main() { } EOF -# 2D Array Tests -# with proper row-major indexing for multi-dimensional arrays +# 2D Array Tests with proper row-major indexing for multi-dimensional arrays try_ 78 << EOF int main() { int matrix[3][4]; @@ -2151,8 +2170,8 @@ int main() { } EOF -# Mixed subscript and arrow / dot operators, -# excerpted and modified from issue #165 +# Mixed subscript and arrow / dot operators, excerpted and modified from issue +# #165 try_output 0 "DDDDDDMMMEEE1" << EOF #include #include @@ -2426,23 +2445,23 @@ items 24 "short s; s = 6; s *= 4; return s;" begin_category "Sizeof Operator" "Testing sizeof operator on various types" # sizeof -expr 0 "sizeof(void)"; -expr 1 "sizeof(_Bool)"; -expr 1 "sizeof(char)"; -expr 2 "sizeof(short)"; -expr 4 "sizeof(int)"; +expr 0 "sizeof(void)" +expr 1 "sizeof(_Bool)" +expr 1 "sizeof(char)" +expr 2 "sizeof(short)" +expr 4 "sizeof(int)" # sizeof pointers -expr $PTR_SZ "sizeof(void*)"; -expr $PTR_SZ "sizeof(_Bool*)"; -expr $PTR_SZ "sizeof(char*)"; -expr $PTR_SZ "sizeof(short*)"; -expr $PTR_SZ "sizeof(int*)"; +expr $PTR_SZ "sizeof(void*)" +expr $PTR_SZ "sizeof(_Bool*)" +expr $PTR_SZ "sizeof(char*)" +expr $PTR_SZ "sizeof(short*)" +expr $PTR_SZ "sizeof(int*)" # sizeof multi-level pointer -expr $PTR_SZ "sizeof(void**)"; -expr $PTR_SZ "sizeof(_Bool**)"; -expr $PTR_SZ "sizeof(char**)"; -expr $PTR_SZ "sizeof(short**)"; -expr $PTR_SZ "sizeof(int**)"; +expr $PTR_SZ "sizeof(void**)" +expr $PTR_SZ "sizeof(_Bool**)" +expr $PTR_SZ "sizeof(char**)" +expr $PTR_SZ "sizeof(short**)" +expr $PTR_SZ "sizeof(int**)" # sizeof struct try_ $PTR_SZ << EOF typedef struct { @@ -2541,8 +2560,8 @@ EOF begin_category "Memory Management" "Testing malloc, free, and dynamic memory allocation" if [ "$LINK_MODE" = "static" ]; then -# malloc and free -try_ 1 << EOF + # malloc and free + try_ 1 << EOF int main() { /* change test bench if different scheme apply */ @@ -2674,6 +2693,29 @@ int main() } EOF +# An empty replacement list expands to nothing, in both macro shapes. Producing +# no tokens used to hand the caller a pointer into the dead frame that expanded +# them, which spliced the token list into a cycle the parser never left. +try_output 42 "" << EOF +#define EMPTY +#define NOTHING(x) +EMPTY int main(void) +{ + NOTHING(1) + EMPTY return 42; +} +EOF + +try_output 0 "ab" << EOF +#define BLANK +#define JOIN(a, b) printf(a); BLANK printf(b); +int main(void) +{ + JOIN("a", "b") + return 0; +} +EOF + # format try_output 0 "2147483647" << EOF int main() { @@ -2927,10 +2969,10 @@ skip: } EOF -# Forward reference. Statements between a goto and its label are unreachable -# but perfectly legal, and gcc accepts this silently at -Wall -Wextra -# -pedantic. shecc used to abort on the unreachable "return 1;" -- this case -# asserted that abort as a compile error; it now asserts the correct result. +# Forward reference. Statements between a goto and its label are unreachable but +# perfectly legal, and gcc accepts this silently at -Wall -Wextra -pedantic. +# shecc used to abort on the unreachable "return 1;" -- this case asserted that +# abort as a compile error; it now asserts the correct result. try_ 0 << EOF int main() { @@ -3976,16 +4018,16 @@ EOF if [ "$LINK_MODE" = "static" ]; then -# printf family, including truncation and zero size input -try_output 11 "Hello World" << EOF + # printf family, including truncation and zero size input + try_output 11 "Hello World" << EOF int main() { int written = printf("Hello World"); return written; } EOF -# tests printf returns EBADF (errno 9) when stdout is closed -try_output 1 "" << EOF + # tests printf returns EBADF (errno 9) when stdout is closed + try_output 1 "" << EOF int main() { __syscall(__syscall_close, 1); @@ -3994,7 +4036,7 @@ int main() } EOF -try_output 11 "Hello World" << EOF + try_output 11 "Hello World" << EOF int main() { char buffer[50]; int written = sprintf(buffer, "Hello World"); @@ -4003,7 +4045,7 @@ int main() { } EOF -try_output 16 "Hello World 1123" << EOF + try_output 16 "Hello World 1123" << EOF int main() { char buffer[50]; int written = sprintf(buffer, "Hello %s %d", "World", 1123); @@ -4012,12 +4054,11 @@ int main() { } EOF -# The following cases validate the behavior and return value of -# snprintf(). -# -# This case is a normal case and outputs the complete string -# because the given buffer size is large enough. -try_output 16 "Hello World 1123" << EOF + # The following cases validate the behavior and return value of snprintf(). + # + # This case is a normal case and outputs the complete string because the + # given buffer size is large enough. + try_output 16 "Hello World 1123" << EOF int main() { char buffer[50]; int written = snprintf(buffer, 50, "Hello %s %d", "World", 1123); @@ -4026,11 +4067,11 @@ int main() { } EOF -# If n is zero, nothing is written. -# -# Thus, the output should be the string containing 19 characters -# for this test case. -try_output 11 "0000000000000000000" << EOF + # If n is zero, nothing is written. + # + # Thus, the output should be the string containing 19 characters for this + # test case. + try_output 11 "0000000000000000000" << EOF int main() { char buffer[20]; for (int i = 0; i < 19; i++) @@ -4042,10 +4083,10 @@ int main() { } EOF -# In this case, snprintf() only writes at most 10 bytes (including '\0'), -# but the return value is 11, which corresponds to the length of -# "Number: -37". -try_output 11 "Number: -" << EOF + # In this case, snprintf() only writes at most 10 bytes (including '\0'), + # but the return value is 11, which corresponds to the length of "Number: + # -37". + try_output 11 "Number: -" << EOF int main() { char buffer[10]; for (int i = 0; i < 9; i++) @@ -4057,7 +4098,7 @@ int main() { } EOF -try_output 14 " 4e 75 6d 62 65 72 3a 20 2d 0 30 30 30 30 30 30 30 30 30 0" << EOF + try_output 14 " 4e 75 6d 62 65 72 3a 20 2d 0 30 30 30 30 30 30 30 30 30 0" << EOF int main() { char buffer[20]; @@ -4073,8 +4114,8 @@ int main() } EOF -# A complex test case for snprintf(). -ans="written = 24 + # A complex test case for snprintf(). + ans="written = 24 buffer = buf - 00000 written = 13 buffer = aaaa - 0 @@ -4083,7 +4124,7 @@ buffer = aaaa - 000000777777 written = 14 buffer = aaaa - 000000777777 61 61 61 61 20 2d 20 30 30 30 30 30 30 37 37 37 37 37 37 0 30 30 30 30 30 30 30 30 30 0" -try_output 0 "$ans" << EOF + try_output 0 "$ans" << EOF int main() { char buffer[30]; @@ -4107,16 +4148,15 @@ int main() } EOF -# test the return value when calling fputc(). -# -# Since the FILE data type is defined as an int in -# the built-in C library, and most of the functions -# such as fputc(), fgetc(), fclose() and fgets() directly -# treat the "stream" parameter (of type FILE *) as a file -# descriptor for performing input/output operations, the -# following test cases define "stdout" as 1, which is the -# file descriptor for the standard output. -try_output 0 "awritten = a" << EOF + # test the return value when calling fputc(). + # + # Since the FILE data type is defined as an int in the built-in C library, + # and most of the functions such as fputc(), fgetc(), fclose() and fgets() + # directly treat the "stream" parameter (of type FILE *) as a file + # descriptor for performing input/output operations, the following test + # cases define "stdout" as 1, which is the file descriptor for the standard + # output. + try_output 0 "awritten = a" << EOF #define stdout 1 int main() { @@ -4126,7 +4166,7 @@ int main() } EOF -try_output 1 "" << EOF + try_output 1 "" << EOF #define stdout 1 int main() { @@ -4139,8 +4179,7 @@ else echo "Skip test cases because of using dynamic linking mode" fi # "LINK_MODE" = "static" -# tests integer type conversion -# excerpted and modified from issue #166 +# tests integer type conversion excerpted and modified from issue #166 try_output 0 "a = -127, b = -78, c = -93, d = -44" << EOF int main() { @@ -4224,8 +4263,7 @@ int main() } EOF -# Binary literal tests (0b/0B prefix) -# Test basic binary literals +# Binary literal tests (0b/0B prefix) Test basic binary literals expr 0 "0b0" expr 1 "0b1" expr 2 "0b10" @@ -4257,15 +4295,15 @@ items 54 "int a = 0b1111; int b = 0b0011; return (a + b) * 3;" items 160 "int mask = 0b11110000; int value = 0b10101010; return value & mask;" # Test combination of different number bases -expr 45 "0b1111 + 0xF + 017" # 15 + 15 + 15 = 45 -expr 90 "0b110000 + 0x10 + 032" # 48 + 16 + 26 = 90 +expr 45 "0b1111 + 0xF + 017" # 15 + 15 + 15 = 45 +expr 90 "0b110000 + 0x10 + 032" # 48 + 16 + 26 = 90 # Test binary literals in comparisons expr 1 "0b1010 == 10" expr 1 "0b11111111 == 255" expr 0 "0b1000 != 8" expr 1 "0b10000 > 0xF" -expr 1 "0B1111 < 020" # 15 < 16 (octal) +expr 1 "0B1111 < 020" # 15 < 16 (octal) # Test binary literals with large values try_large 1023 << EOF @@ -4305,8 +4343,8 @@ int main() } EOF -# New escape sequence tests (\a, \b, \v, \f) -# Test character literals with new escape sequences +# New escape sequence tests (\a, \b, \v, \f) Test character literals with new +# escape sequences try_ 7 << EOF int main() { char bell = '\a'; /* ASCII 7 - bell/alert */ @@ -4442,8 +4480,8 @@ int main() { } EOF -# Test escape sequences in printf -# Note: The bell character (\a) is non-printable but present in output +# Test escape sequences in printf Note: The bell character (\a) is non-printable +# but present in output try_output 0 "$(printf 'Bell: \a Tab:\t Newline:\n')" << EOF int main() { printf("Bell: %c Tab:%c Newline:%c", '\a', '\t', '\n'); @@ -4476,9 +4514,9 @@ int main() { } EOF -# va_list and variadic function tests -# Note: These tests demonstrate both direct pointer arithmetic and -# va_list typedef forwarding between functions, now fully supported. +# va_list and variadic function tests Note: These tests demonstrate both direct +# pointer arithmetic and va_list typedef forwarding between functions, now fully +# supported. # Test 1: Sum calculation using variadic arguments try_output 0 "Sum: 15" << EOF @@ -4686,8 +4724,8 @@ int main() } EOF -# va_list typedef forwarding tests -# These tests demonstrate va_list typedef forwarding between functions +# va_list typedef forwarding tests These tests demonstrate va_list typedef +# forwarding between functions # Test 11: Basic va_list typedef forwarding try_output 0 "Test: 42" << EOF @@ -4788,8 +4826,8 @@ int main(void) } EOF -# Complex pointer arithmetic tests -# Testing enhanced parser capability to handle expressions like *(ptr + offset) +# Complex pointer arithmetic tests Testing enhanced parser capability to handle +# expressions like *(ptr + offset) # Test 1: Basic pointer arithmetic on RHS try_output 0 "Values: 10 20 30" << EOF @@ -5355,8 +5393,8 @@ int main() } EOF -# Additional struct initialization tests from refine-parser -# Test: Local struct initialization (working with field-by-field assignment) +# Additional struct initialization tests from refine-parser Test: Local struct +# initialization (working with field-by-field assignment) try_ 42 << EOF typedef struct { int x; @@ -5420,8 +5458,7 @@ int main() { } EOF -# Union support tests -# Basic union declaration and field access +# Union support tests Basic union declaration and field access try_ 42 << EOF typedef union { int i; @@ -5638,8 +5675,8 @@ int main() { } EOF -# Sizeof union with mixed types. The largest member is the pointer, so the -# union is one pointer wide: 4 on the 32-bit targets, 8 on LP64. +# Sizeof union with mixed types. The largest member is the pointer, so the union +# is one pointer wide: 4 on the 32-bit targets, 8 on LP64. try_ $PTR_SZ << EOF typedef union { char c; @@ -5907,8 +5944,8 @@ int main() { } EOF -# Local array initializers - verify compilation and correct values -# Test 1: Implicit size array with single element +# Local array initializers - verify compilation and correct values Test 1: +# Implicit size array with single element try_ 1 << 'EOF' int main() { int a[] = {1}; @@ -6139,8 +6176,8 @@ int main() { } EOF -# Pointer dereference assignment tests -# Test Case 1: Simple pointer dereference assignment +# Pointer dereference assignment tests Test Case 1: Simple pointer dereference +# assignment try_ 0 << EOF void f(int *ap) { *ap = 0; // Should work now @@ -6355,7 +6392,7 @@ EOF echo "" if [ "$SHOW_PROGRESS" = "1" ]; then - echo "" # New line after progress indicators + echo "" # New line after progress indicators fi TEST_END_TIME=$(date +%s) @@ -6372,14 +6409,14 @@ echo "Overall Statistics:" echo " Total Tests: $TOTAL_TESTS" print_color green " Passed: $PASSED_TESTS" if [ "$PASSED_TESTS" -gt 0 ] && [ "$TOTAL_TESTS" -gt 0 ]; then - echo " ($(( PASSED_TESTS * 100 / TOTAL_TESTS ))%)" + echo " ($((PASSED_TESTS * 100 / TOTAL_TESTS))%)" else echo "" fi if [ "$FAILED_TESTS" -gt 0 ]; then print_color red " Failed: $FAILED_TESTS" - echo " ($(( FAILED_TESTS * 100 / TOTAL_TESTS ))%)" + echo " ($((FAILED_TESTS * 100 / TOTAL_TESTS))%)" else echo " Failed: 0" fi diff --git a/tests/riscv-abi.sh b/tests/riscv-abi.sh index c71b5a6a..2ec4b338 100755 --- a/tests/riscv-abi.sh +++ b/tests/riscv-abi.sh @@ -56,16 +56,20 @@ fi case "$1" in "0") readonly SHECC="$PWD/out/shecc" - readonly STAGE="Stage 0 (Host Compiler)" ;; + readonly STAGE="Stage 0 (Host Compiler)" + ;; "1") readonly SHECC="${TARGET_EXEC:-} $PWD/out/shecc-stage1.elf" - readonly STAGE="Stage 1 (Cross-compiled)" ;; + readonly STAGE="Stage 1 (Cross-compiled)" + ;; "2") readonly SHECC="${TARGET_EXEC:-} $PWD/out/shecc-stage2.elf" - readonly STAGE="Stage 2 (Self-hosted)" ;; + readonly STAGE="Stage 2 (Self-hosted)" + ;; *) echo "Error: Invalid stage '$1'. Use 0, 1, or 2." - exit 1 ;; + exit 1 + ;; esac DYNLINK="${2:-0}" @@ -80,9 +84,10 @@ echo -e "Compiler: $SHECC" echo "" # Helper Functions -update_category_stats() { +update_category_stats() +{ local category="$1" - local result="$2" # "pass" or "fail" + local result="$2" # "pass" or "fail" if [[ -z "${CATEGORY_TESTS[$category]:-}" ]]; then CATEGORY_TESTS[$category]=0 @@ -99,7 +104,8 @@ update_category_stats() { fi } -show_progress() { +show_progress() +{ if [[ "$SHOW_PROGRESS" == "1" ]]; then echo -n "." PROGRESS_COUNT=$((PROGRESS_COUNT + 1)) @@ -110,7 +116,8 @@ show_progress() { } # Test execution function -run_abi_test() { +run_abi_test() +{ local test_name="$1" local category="$2" local source_code="$3" @@ -164,11 +171,10 @@ run_abi_test() { run_output=$(eval "$run_cmd" 2>&1) exit_code=$? - # Check result - # If the exit code is not zero or the output is not expected, - # set 'run_status' to "FAILED". + # If the exit code is not zero or the output is not expected, set + # 'run_status' to "FAILED". run_status="SUCCESS" - if [[ $exit_code -ne 0 || ( -n "$expected_output" && "$run_output" != *"$expected_output"* ) ]]; then + if [[ $exit_code -ne 0 || (-n "$expected_output" && "$run_output" != *"$expected_output"*) ]]; then run_status="FAILED" fi @@ -200,7 +206,8 @@ run_abi_test() { # Parameter Passing Tests -test_one_arg() { +test_one_arg() +{ run_abi_test "One argument (a0)" "Parameter Passing" ' #include int add_42(int x) { return x + 42; } @@ -216,7 +223,8 @@ int main() { ' "PASS" } -test_two_args() { +test_two_args() +{ run_abi_test "Two arguments (a0, a1)" "Parameter Passing" ' #include int add(int a, int b) { return a + b; } @@ -232,7 +240,8 @@ int main() { ' "PASS" } -test_four_args() { +test_four_args() +{ run_abi_test "Four arguments (a0-a3)" "Parameter Passing" ' #include int sum4(int a, int b, int c, int d) { return a + b + c + d; } @@ -248,7 +257,8 @@ int main() { ' "PASS" } -test_five_args() { +test_five_args() +{ run_abi_test "Five arguments (a0-a4)" "Parameter Passing" ' #include int sum5(int a, int b, int c, int d, int e) { return a + b + c + d + e; } @@ -264,7 +274,8 @@ int main() { ' "PASS" } -test_eight_args() { +test_eight_args() +{ run_abi_test "Eight arguments" "Parameter Passing" ' #include int sum8(int a, int b, int c, int d, int e, int f, int g, int h) { @@ -284,7 +295,8 @@ int main() { # Stack Alignment Tests -test_stack_alignment_basic() { +test_stack_alignment_basic() +{ run_abi_test "Basic stack alignment" "Stack Alignment" ' #include int is_aligned(void *ptr) { @@ -306,7 +318,8 @@ int main() { ' "PASS" } -test_stack_alignment_extended() { +test_stack_alignment_extended() +{ run_abi_test "Stack alignment with extended args" "Stack Alignment" ' #include int is_aligned(void *ptr) { @@ -331,7 +344,8 @@ int main() { # Return Value Tests -test_return_char() { +test_return_char() +{ run_abi_test "Return char value" "Return Values" ' #include char get_char(void) { return '\''A'\''; } @@ -346,7 +360,8 @@ int main() { ' "PASS" } -test_return_int() { +test_return_int() +{ run_abi_test "Return int value" "Return Values" ' #include int get_value(void) { return 12345; } @@ -361,7 +376,8 @@ int main() { ' "PASS" } -test_return_pointer() { +test_return_pointer() +{ run_abi_test "Return pointer value" "Return Values" ' #include int *return_ptr(int *p) { return p; } @@ -380,7 +396,8 @@ int main() { # External Function Call Tests (Dynamic Linking Only) -test_printf_one_arg() { +test_printf_one_arg() +{ run_abi_test "printf with 1 argument" "External Calls" ' #include int main() { @@ -390,7 +407,8 @@ int main() { ' "PASS" 1 } -test_printf_multi_args() { +test_printf_multi_args() +{ run_abi_test "printf with 5 arguments" "External Calls" ' #include int main() { @@ -400,7 +418,8 @@ int main() { ' "Values: 1 2 3 4" 1 } -test_strlen() { +test_strlen() +{ run_abi_test "strlen external call" "External Calls" ' #include #include @@ -416,7 +435,8 @@ int main() { ' "PASS" 1 } -test_strcpy() { +test_strcpy() +{ run_abi_test "strcpy external call" "External Calls" ' #include #include @@ -434,7 +454,8 @@ int main() { ' "PASS" 1 } -test_memcpy() { +test_memcpy() +{ run_abi_test "memcpy external call" "External Calls" ' #include #include @@ -454,7 +475,8 @@ int main() { # Register Preservation Tests -test_local_vars_preserved() { +test_local_vars_preserved() +{ run_abi_test "Local variables preserved across calls" "Register Preservation" ' #include int dummy(int a, int b, int c, int d, int e, int f, int g, int h) { @@ -475,7 +497,8 @@ int main() { ' "PASS" } -test_recursive_preservation() { +test_recursive_preservation() +{ run_abi_test "Register preservation in recursion" "Register Preservation" ' #include int factorial(int n) { @@ -497,7 +520,8 @@ int main() { # Structure Passing Tests -test_small_struct() { +test_small_struct() +{ run_abi_test "Small struct passing (≤4 bytes)" "Structure Passing" ' #include typedef struct { char a; char b; short c; } SmallStruct; diff --git a/tests/strength-reduce.c b/tests/strength-reduce.c index e85bd858..31cc60ef 100644 --- a/tests/strength-reduce.c +++ b/tests/strength-reduce.c @@ -1,7 +1,8 @@ -/* The address of a[i * 4] changes by a fixed amount on each trip through - * these loops. Their explicit goto back edges give the strength-reduction - * pass a latch with an existing SSA jump: it must advance the address before - * that jump, not append the advance after it. +/* + * The address of a[i * 4] changes by a fixed amount on each trip through these + * loops. Their explicit goto back edges give the strength-reduction pass a + * latch with an existing SSA jump: it must advance the address before that + * jump, not append the advance after it. */ int main() { diff --git a/tests/x64-abi.sh b/tests/x64-abi.sh index 36c9f1c7..9bcbdbbd 100755 --- a/tests/x64-abi.sh +++ b/tests/x64-abi.sh @@ -57,16 +57,20 @@ fi case "$1" in "0") readonly SHECC="$PWD/out/shecc" - readonly STAGE="Stage 0 (Host Compiler)" ;; + readonly STAGE="Stage 0 (Host Compiler)" + ;; "1") readonly SHECC="${TARGET_EXEC:-} $PWD/out/shecc-stage1.elf" - readonly STAGE="Stage 1 (Cross-compiled)" ;; + readonly STAGE="Stage 1 (Cross-compiled)" + ;; "2") readonly SHECC="${TARGET_EXEC:-} $PWD/out/shecc-stage2.elf" - readonly STAGE="Stage 2 (Self-hosted)" ;; + readonly STAGE="Stage 2 (Self-hosted)" + ;; *) echo "Error: Invalid stage '$1'. Use 0, 1, or 2." - exit 1 ;; + exit 1 + ;; esac DYNLINK="${2:-0}" @@ -81,9 +85,10 @@ echo -e "Compiler: $SHECC" echo "" # Helper Functions -update_category_stats() { +update_category_stats() +{ local category="$1" - local result="$2" # "pass", "fail" or "skip" + local result="$2" # "pass", "fail" or "skip" if [[ -z "${CATEGORY_TESTS[$category]:-}" ]]; then CATEGORY_TESTS[$category]=0 @@ -103,7 +108,8 @@ update_category_stats() { fi } -show_progress() { +show_progress() +{ if [[ "$SHOW_PROGRESS" == "1" ]]; then echo -n "." PROGRESS_COUNT=$((PROGRESS_COUNT + 1)) @@ -114,7 +120,8 @@ show_progress() { } # Test execution function -run_abi_test() { +run_abi_test() +{ local test_name="$1" local category="$2" local source_code="$3" @@ -169,11 +176,10 @@ run_abi_test() { run_output=$(eval "$run_cmd" 2>&1) exit_code=$? - # Check result - # If the exit code is not zero or the output is not expected, - # set 'run_status' to "FAILED". + # If the exit code is not zero or the output is not expected, set + # 'run_status' to "FAILED". run_status="SUCCESS" - if [[ $exit_code -ne 0 || ( -n "$expected_output" && "$run_output" != *"$expected_output"* ) ]]; then + if [[ $exit_code -ne 0 || (-n "$expected_output" && "$run_output" != *"$expected_output"*) ]]; then run_status="FAILED" fi @@ -205,7 +211,8 @@ run_abi_test() { # Parameter Passing Tests -test_one_arg() { +test_one_arg() +{ run_abi_test "One argument (rdi)" "Parameter Passing" ' #include int add_42(int x) { return x + 42; } @@ -221,7 +228,8 @@ int main() { ' "PASS" } -test_two_args() { +test_two_args() +{ run_abi_test "Two arguments (rdi, rsi)" "Parameter Passing" ' #include int add(int a, int b) { return a + b; } @@ -237,7 +245,8 @@ int main() { ' "PASS" } -test_four_args() { +test_four_args() +{ run_abi_test "Four arguments (rdi-rcx)" "Parameter Passing" ' #include int sum4(int a, int b, int c, int d) { return a + b + c + d; } @@ -253,7 +262,8 @@ int main() { ' "PASS" } -test_five_args() { +test_five_args() +{ run_abi_test "Five arguments (rdi-r8)" "Parameter Passing" ' #include int sum5(int a, int b, int c, int d, int e) { return a + b + c + d + e; } @@ -269,7 +279,8 @@ int main() { ' "PASS" } -test_eight_args() { +test_eight_args() +{ run_abi_test "Eight arguments" "Parameter Passing" ' #include int sum8(int a, int b, int c, int d, int e, int f, int g, int h) { @@ -296,7 +307,8 @@ int main() { # "the frame layout moved", not as a conformance verdict: the ABI does not # require an int local to sit on a 16-byte boundary. -test_stack_alignment_basic() { +test_stack_alignment_basic() +{ run_abi_test "Frame slot alignment" "Stack Alignment" ' #include int is_aligned(void *ptr) { @@ -318,7 +330,8 @@ int main() { ' "PASS" } -test_stack_alignment_extended() { +test_stack_alignment_extended() +{ run_abi_test "Frame slot alignment with stack-passed args" "Stack Alignment" ' #include int is_aligned(void *ptr) { @@ -343,7 +356,8 @@ int main() { # Return Value Tests -test_return_char() { +test_return_char() +{ run_abi_test "Return char value" "Return Values" ' #include char get_char(void) { return '\''A'\''; } @@ -358,7 +372,8 @@ int main() { ' "PASS" } -test_return_int() { +test_return_int() +{ run_abi_test "Return int value" "Return Values" ' #include int get_value(void) { return 12345; } @@ -373,7 +388,8 @@ int main() { ' "PASS" } -test_return_pointer() { +test_return_pointer() +{ run_abi_test "Return pointer value" "Return Values" ' #include int *return_ptr(int *p) { return p; } @@ -392,7 +408,8 @@ int main() { # External Function Call Tests (Dynamic Linking Only) -test_printf_one_arg() { +test_printf_one_arg() +{ run_abi_test "printf with 1 argument" "External Calls" ' #include int main() { @@ -402,7 +419,8 @@ int main() { ' "PASS" 1 } -test_printf_multi_args() { +test_printf_multi_args() +{ run_abi_test "printf with 5 arguments" "External Calls" ' #include int main() { @@ -412,7 +430,8 @@ int main() { ' "Values: 1 2 3 4" 1 } -test_strlen() { +test_strlen() +{ run_abi_test "strlen external call" "External Calls" ' #include #include @@ -428,7 +447,8 @@ int main() { ' "PASS" 1 } -test_strcpy() { +test_strcpy() +{ run_abi_test "strcpy external call" "External Calls" ' #include #include @@ -446,7 +466,8 @@ int main() { ' "PASS" 1 } -test_memcpy() { +test_memcpy() +{ run_abi_test "memcpy external call" "External Calls" ' #include #include @@ -466,7 +487,8 @@ int main() { # Register Preservation Tests -test_local_vars_preserved() { +test_local_vars_preserved() +{ run_abi_test "Local variables preserved across calls" "Register Preservation" ' #include int dummy(int a, int b, int c, int d, int e, int f, int g, int h) { @@ -487,7 +509,8 @@ int main() { ' "PASS" } -test_recursive_preservation() { +test_recursive_preservation() +{ run_abi_test "Register preservation in recursion" "Register Preservation" ' #include int factorial(int n) { @@ -509,7 +532,8 @@ int main() { # Structure Passing Tests -test_small_struct() { +test_small_struct() +{ run_abi_test "Small struct passing (≤4 bytes)" "Structure Passing" ' #include typedef struct { char a; char b; short c; } SmallStruct; diff --git a/tools/inliner.c b/tools/inliner.c index a3e38f6c..052160e5 100644 --- a/tools/inliner.c +++ b/tools/inliner.c @@ -1,18 +1,18 @@ /* * shecc - Self-Hosting and Educational C Compiler. * - * shecc is freely redistributable under the BSD 2 clause license. See the - * file "LICENSE" for information on usage and redistribution of this file. + * shecc is freely redistributable under the BSD 2 clause license. See the file + * "LICENSE" for information on usage and redistribution of this file. */ /* inliner - inline libc source into C file. * - * The inliner is used at build-time, and developers can use the - * "inline C" feature to implement target-specific parts such as - * C runtime and essential libraries. + * The inliner is used at build-time, and developers can use the "inline C" + * feature to implement target-specific parts such as C runtime and essential + * libraries. * - * Note: Input files are preprocessed by norm-lf tool to ensure - * consistent LF (Unix) line endings before processing. + * Note: Input files are preprocessed by norm-lf tool to ensure consistent LF + * (Unix) line endings before processing. */ #include @@ -182,11 +182,11 @@ int main(int argc, char *argv[]) write_str(" strbuf_puts(LIBC_SRC, src);\n"); write_str("}\n"); - write_str("void libc_impl() {\n"); + write_str("void libc_impl(void) {\n"); load_from(argv[1]); write_str("}\n"); - write_str("void libc_decl() {\n"); + write_str("void libc_decl(void) {\n"); load_from(argv[2]); write_str("}\n"); diff --git a/tools/norm-lf.c b/tools/norm-lf.c index 74589ced..2daa6b8a 100644 --- a/tools/norm-lf.c +++ b/tools/norm-lf.c @@ -1,8 +1,8 @@ /* * Convert all line endings to LF (Unix style) * - * This tool ensures consistent line endings before processing with inliner. - * It converts CR-only (old Mac) and CRLF (Windows) to LF (Unix). + * This tool ensures consistent line endings before processing with inliner. It + * converts CR-only (old Mac) and CRLF (Windows) to LF (Unix). */ #include