-
Notifications
You must be signed in to change notification settings - Fork 78
LLVM verification #356
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
LLVM verification #356
Changes from 38 commits
Commits
Show all changes
42 commits
Select commit
Hold shift + click to select a range
7f82b46
Add simple Docker environment variable
AFOliveira 7141a9c
Fix errors due to incorrect parsing of VM
AFOliveira 6e45c3b
First Refactor to pytest
AFOliveira ac04c28
Allow 16 bit instructions for C extension
AFOliveira f1b8613
Revert bad parsing
AFOliveira e3e7456
Allow only one value
AFOliveira 4355eb0
Use AsmString instead of name
AFOliveira 8492581
Small Refactor on parsing.py
AFOliveira 3bd1d2c
refactor to do unit tests
AFOliveira 6b9fdda
refactor to file name
AFOliveira 831a10b
Modify to have seveal Unit tests instead of just one
AFOliveira 655e1d6
Clean up and code reorganization
AFOliveira d61fb3b
Ensure it is not pseudo
AFOliveira 5992124
Skip aq/rl instructions
AFOliveira c2e6e92
add pytest to requirements
AFOliveira 7c9c65b
add LLVM path as environment variable
AFOliveira 3178538
remove and ignor python cache
AFOliveira 11d4c60
Add LLVM test to the Rakefile
AFOliveira 2185239
Optimizations to test logic and modify test order
AFOliveira 64a7525
Merge branch 'main' into AFOliveira/LLVM
AFOliveira 9b9bce5
Add prerequisites syntax
3939224
Fix merge conflict
daae5d5
Fix pre-commit related issues
450c65b
Merge branch 'main' into AFOliveira/LLVM
AFOliveira d9e45b3
Add LLVM tblgen to regress.yaml && change Rakefile for new changes
AFOliveira 0fa5bdd
Fix caching
AFOliveira 56412ac
Fix caching
AFOliveira b2748e0
Fix caching
AFOliveira 6968919
Add dependencie for smoke test
AFOliveira 25aa928
Change logic for LLVM's path
AFOliveira 5ee8496
Change CI logic for LLVM
AFOliveira 92f9c99
Add cache and update paths
AFOliveira 34e769e
Add corner case when implementation of LLVM does not need to follow t…
AFOliveira b3c523c
Merge branch 'main' into AFOliveira/LLVM
AFOliveira 15b12f3
Merge branch 'main' into AFOliveira/LLVM
AFOliveira af0acae
Work around for FENCE. ISA and compiler should treat it differently
AFOliveira b1e0ee6
Set CM instruction length 16 bit instead of 32
AFOliveira f99e556
Merge branch 'main' into AFOliveira/LLVM
AFOliveira 4510bed
merged
AFOliveira 7b1697f
Merge remote-tracking branch 'origin/main' into AFOliveira/LLVM
dhower-qc 7bfe623
Merge branch 'main' into AFOliveira/LLVM
AFOliveira b2f102e
Add LICENSE compliant to UDB native files.
AFOliveira File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,12 +12,24 @@ jobs: | |
| - uses: actions/setup-python@v5 | ||
| - uses: pre-commit/[email protected] | ||
| regress-smoke: | ||
| needs: build-llvm | ||
| runs-on: ubuntu-latest | ||
| env: | ||
| SINGULARITY: 1 | ||
| steps: | ||
| - name: Clone Github Repo Action | ||
| uses: actions/checkout@v4 | ||
| - name: Get current LLVM submodule commit SHA | ||
| id: get-llvm-sha | ||
| run: echo "LLVM_SHA=$(git ls-tree HEAD ext/llvm-project | awk '{print $3}')" >> $GITHUB_ENV | ||
| - name: Restore cache RISC-V JSON | ||
| id: cache-riscv | ||
| uses: actions/cache@v4 | ||
| with: | ||
| path: ext/llvm-project/riscv.json | ||
| key: ${{ runner.os }}-riscv-json-${{ env.LLVM_SHA }} | ||
| restore-keys: | | ||
| ${{ runner.os }}-riscv-json- | ||
| - name: Setup apptainer | ||
| uses: eWaterCycle/[email protected] | ||
| - name: Get container from cache | ||
|
|
@@ -185,3 +197,53 @@ jobs: | |
| run: ./bin/build_container | ||
| - name: Generate extension PDF | ||
| run: ./do gen:profile[MockProfileRelease] | ||
|
|
||
| build-llvm: | ||
| runs-on: ubuntu-latest | ||
|
|
||
| steps: | ||
| - name: Check out repository (no submodules, shallow fetch) | ||
| uses: actions/checkout@v4 | ||
| with: | ||
| submodules: false | ||
| fetch-depth: 1 | ||
| - name: Get current LLVM submodule commit SHA | ||
| id: get-llvm-sha | ||
| run: echo "LLVM_SHA=$(git ls-tree HEAD ext/llvm-project | awk '{print $3}')" >> $GITHUB_ENV | ||
| - name: Cache RISC-V JSON | ||
| id: cache-riscv | ||
| uses: actions/cache@v4 | ||
| with: | ||
| path: ext/llvm-project/riscv.json | ||
| key: ${{ runner.os }}-riscv-json-${{ env.LLVM_SHA }} | ||
| restore-keys: | | ||
| ${{ runner.os }}-riscv-json- | ||
| - name: Initialize LLVM submodule (shallow + sparse) | ||
| if: ${{ steps.cache-riscv.outputs.cache-hit != 'true' }} | ||
| run: | | ||
| git submodule sync --recursive | ||
| git submodule update --init --recursive --depth=1 ext/llvm-project | ||
|
|
||
| - name: Check for required directories and files | ||
| if: ${{ steps.cache-riscv.outputs.cache-hit != 'true' }} | ||
| run: | | ||
| ls -l ext/llvm-project/llvm/include | ||
| ls -l ext/llvm-project/llvm/lib/Target/RISCV | ||
| ls -l ext/llvm-project/llvm/lib/Target/RISCV/RISCV.td | ||
| - name: Configure and build llvm-tblgen | ||
| if: ${{ steps.cache-riscv.outputs.cache-hit != 'true' }} | ||
| run: | | ||
| cmake -S ext/llvm-project/llvm -B ext/llvm-project/build -DCMAKE_BUILD_TYPE=Release | ||
| cmake --build ext/llvm-project/build --target llvm-tblgen | ||
| - name: Generate RISC-V JSON | ||
| if: ${{ steps.cache-riscv.outputs.cache-hit != 'true' }} | ||
| run: | | ||
| chmod +x ./ext/llvm-project/build/bin/llvm-tblgen | ||
| ./ext/llvm-project/build/bin/llvm-tblgen \ | ||
| -I ext/llvm-project/llvm/include \ | ||
| -I ext/llvm-project/llvm/lib/Target/RISCV \ | ||
| ext/llvm-project/llvm/lib/Target/RISCV/RISCV.td \ | ||
| --dump-json \ | ||
| -o ext/llvm-project/riscv.json | ||
| - name: Show riscv.json output | ||
| run: ls -l ext/llvm-project/riscv.json | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,4 +14,7 @@ gen | |
| node_modules | ||
| _site | ||
| images | ||
| __pycache__/ | ||
| *.pyc | ||
| .pytest_cache/ | ||
| *.log | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,274 @@ | ||
| import os | ||
| import re | ||
| import yaml | ||
| from pathlib import Path | ||
| import pytest | ||
|
|
||
| yaml_instructions = {} | ||
| REPO_DIRECTORY = None | ||
|
|
||
|
|
||
| def safe_get(data, key, default=""): | ||
| """Safely get a value from a dictionary, return default if not found or error.""" | ||
| try: | ||
| if isinstance(data, dict): | ||
| return data.get(key, default) | ||
| return default | ||
| except: | ||
| return default | ||
|
|
||
|
|
||
| def get_json_path(): | ||
| """ | ||
| Resolves the path to riscv.json in the repository. | ||
| Returns the Path object if file exists, otherwise skips the test. | ||
| """ | ||
| # Print current working directory and script location for debugging | ||
| cwd = Path.cwd() | ||
| script_dir = Path(__file__).parent.resolve() | ||
| print(f"Current working directory: {cwd}") | ||
| print(f"Script directory: {script_dir}") | ||
|
|
||
| # Try to find the repository root | ||
| repo_root = os.environ.get("GITHUB_WORKSPACE", cwd) | ||
| repo_root = Path(repo_root) | ||
|
|
||
| llvm_json_path = repo_root / "ext" / "llvm-project" / "riscv.json" | ||
| print(f"Looking for riscv.json at: {llvm_json_path}") | ||
|
|
||
| if not llvm_json_path.is_file(): | ||
| print(f"\nNo 'riscv.json' found at {llvm_json_path}.") | ||
| print("Tests will be skipped.\n") | ||
| pytest.skip("riscv.json does not exist in the repository at the expected path.") | ||
|
|
||
| return llvm_json_path | ||
|
|
||
|
|
||
| def get_yaml_directory(): | ||
| return "arch/inst/" | ||
|
|
||
|
|
||
| def load_inherited_variable(var_path, repo_dir): | ||
| """Load variable definition from an inherited YAML file.""" | ||
| try: | ||
| path, anchor = var_path.split("#") | ||
| if anchor.startswith("/"): | ||
| anchor = anchor[1:] | ||
|
|
||
| full_path = os.path.join(repo_dir, path) | ||
|
|
||
| if not os.path.exists(full_path): | ||
| print(f"Warning: Inherited file not found: {full_path}") | ||
| return None | ||
|
|
||
| with open(full_path) as f: | ||
| data = yaml.safe_load(f) | ||
|
|
||
| for key in anchor.split("/"): | ||
| if key in data: | ||
| data = data[key] | ||
| else: | ||
| print(f"Warning: Anchor path {anchor} not found in {path}") | ||
| return None | ||
|
|
||
| return data | ||
| except Exception as e: | ||
| print(f"Error loading inherited variable {var_path}: {str(e)}") | ||
| return None | ||
|
|
||
|
|
||
| def resolve_variable_definition(var, repo_dir): | ||
| """Resolve variable definition, handling inheritance if needed.""" | ||
| if "location" in var: | ||
| return var | ||
| elif "$inherits" in var: | ||
| print(f"Warning: Failed to resolve inheritance for variable: {var}") | ||
| return None | ||
|
|
||
|
|
||
| def parse_location(loc_str): | ||
| """Parse location string that may contain multiple ranges.""" | ||
| if not loc_str: | ||
| return [] | ||
|
|
||
| loc_str = str(loc_str).strip() | ||
| ranges = [] | ||
|
|
||
| for range_str in loc_str.split("|"): | ||
| range_str = range_str.strip() | ||
| if "-" in range_str: | ||
| high, low = map(int, range_str.split("-")) | ||
| ranges.append((high, low)) | ||
| else: | ||
| try: | ||
| val = int(range_str) | ||
| ranges.append((val, val)) | ||
| except ValueError: | ||
| print(f"Warning: Invalid location format: {range_str}") | ||
| continue | ||
|
|
||
| return ranges | ||
|
|
||
|
|
||
| def load_yaml_encoding(instr_name): | ||
| """Load YAML encoding data for an instruction.""" | ||
| candidates = set() | ||
| lower_name = instr_name.lower() | ||
| candidates.add(lower_name) | ||
| candidates.add(lower_name.replace("_", ".")) | ||
|
|
||
| yaml_file_path = None | ||
| for cand in candidates: | ||
| if cand in yaml_instructions: | ||
| yaml_category = yaml_instructions[cand] | ||
| yaml_file_path = os.path.join(REPO_DIRECTORY, yaml_category, cand + ".yaml") | ||
| if os.path.isfile(yaml_file_path): | ||
| break | ||
| else: | ||
| yaml_file_path = None | ||
|
|
||
| if not yaml_file_path or not os.path.isfile(yaml_file_path): | ||
| return None, None | ||
|
|
||
| with open(yaml_file_path) as yf: | ||
| ydata = yaml.safe_load(yf) | ||
|
|
||
| encoding = safe_get(ydata, "encoding", {}) | ||
| yaml_match = safe_get(encoding, "match", None) | ||
| yaml_vars = safe_get(encoding, "variables", []) | ||
|
|
||
| return yaml_match, yaml_vars | ||
|
|
||
|
|
||
| def compare_yaml_json_encoding( | ||
| instr_name, yaml_match, yaml_vars, json_encoding_str, repo_dir | ||
| ): | ||
| """Compare the YAML encoding with the JSON encoding.""" | ||
| if not yaml_match: | ||
| return ["No YAML match field available for comparison."] | ||
| if not json_encoding_str: | ||
| return ["No JSON encoding available for comparison."] | ||
|
|
||
| expected_length = ( | ||
| 16 if instr_name.lower().startswith(("c_", "c.", "cm_", "cm.")) else 32 | ||
| ) | ||
|
|
||
| yaml_pattern_str = yaml_match.replace("-", ".") | ||
| if len(yaml_pattern_str) != expected_length: | ||
| return [ | ||
| f"YAML match pattern length is {len(yaml_pattern_str)}, expected {expected_length}. Cannot compare properly." | ||
| ] | ||
|
|
||
| yaml_var_positions = {} | ||
| for var in yaml_vars or []: | ||
| resolved_var = resolve_variable_definition(var, repo_dir) | ||
| if not resolved_var or "location" not in resolved_var: | ||
| print( | ||
| f"Warning: Could not resolve variable definition for {var.get('name', 'unknown')}" | ||
| ) | ||
| continue | ||
|
|
||
| ranges = parse_location(resolved_var["location"]) | ||
| if ranges: | ||
| yaml_var_positions[var["name"]] = ranges | ||
|
|
||
| tokens = re.findall(r"(?:[01]|[A-Za-z0-9]+(?:\[\d+\]|\[\?\])?)", json_encoding_str) | ||
| json_bits = [] | ||
| bit_index = expected_length - 1 | ||
| for t in tokens: | ||
| json_bits.append((bit_index, t)) | ||
| bit_index -= 1 | ||
|
|
||
| if bit_index != -1: | ||
| return [ | ||
| f"JSON encoding does not appear to be {expected_length} bits. Ends at bit {bit_index+1}." | ||
| ] | ||
|
|
||
| normalized_json_bits = [] | ||
| for pos, tt in json_bits: | ||
| if re.match(r"vm\[[^\]]*\]", tt): | ||
| tt = "vm" | ||
| normalized_json_bits.append((pos, tt)) | ||
| json_bits = normalized_json_bits | ||
|
|
||
| differences = [] | ||
|
|
||
| for b in range(expected_length): | ||
| yaml_bit = yaml_pattern_str[expected_length - 1 - b] | ||
| token = [tt for (pos, tt) in json_bits if pos == b] | ||
| if not token: | ||
| differences.append(f"Bit {b}: No corresponding JSON bit found.") | ||
| continue | ||
| json_bit_str = token[0] | ||
|
|
||
| if yaml_bit in ["0", "1"]: | ||
| if json_bit_str not in ["0", "1"]: | ||
| differences.append( | ||
| f"Bit {b}: YAML expects fixed bit '{yaml_bit}' but JSON has '{json_bit_str}'" | ||
| ) | ||
| elif json_bit_str != yaml_bit: | ||
| differences.append( | ||
| f"Bit {b}: YAML expects '{yaml_bit}' but JSON has '{json_bit_str}'" | ||
| ) | ||
| else: | ||
| if json_bit_str in ["0", "1"]: | ||
| differences.append( | ||
| f"Bit {b}: YAML variable bit but JSON is fixed '{json_bit_str}'" | ||
| ) | ||
|
|
||
| for var_name, ranges in yaml_var_positions.items(): | ||
| for high, low in ranges: | ||
| if high >= expected_length or low < 0: | ||
| differences.append( | ||
| f"Variable {var_name}: location {high}-{low} is out of range for {expected_length}-bit instruction." | ||
| ) | ||
| continue | ||
|
|
||
| json_var_fields = [] | ||
| for bb in range(low, high + 1): | ||
| token = [tt for (pos, tt) in json_bits if pos == bb] | ||
| if token: | ||
| json_var_fields.append(token[0]) | ||
| else: | ||
| json_var_fields.append("?") | ||
|
|
||
| field_names = set( | ||
| re.findall( | ||
| r"([A-Za-z0-9]+)(?:\[\d+\]|\[\?\])?", " ".join(json_var_fields) | ||
| ) | ||
| ) | ||
| if len(field_names) == 0: | ||
| differences.append( | ||
| f"Variable {var_name}: No corresponding field found in JSON bits {high}-{low}" | ||
| ) | ||
| elif len(field_names) > 1: | ||
| differences.append( | ||
| f"Variable {var_name}: Multiple fields {field_names} found in JSON for bits {high}-{low}" | ||
| ) | ||
|
|
||
| return differences | ||
|
|
||
|
|
||
| def get_yaml_instructions(repo_directory): | ||
| """Recursively find all YAML files in the repository and load their encodings.""" | ||
| global yaml_instructions, REPO_DIRECTORY | ||
| REPO_DIRECTORY = repo_directory | ||
| yaml_instructions = {} | ||
|
|
||
| for root, _, files in os.walk(repo_directory): | ||
| for file in files: | ||
| if file.endswith(".yaml"): | ||
| instr_name = os.path.splitext(file)[0] | ||
| relative_path = os.path.relpath(root, repo_directory) | ||
| yaml_instructions[instr_name.lower()] = relative_path | ||
|
|
||
| instructions_with_encodings = {} | ||
| for instr_name_lower, path in yaml_instructions.items(): | ||
| yaml_match, yaml_vars = load_yaml_encoding(instr_name_lower) | ||
| instructions_with_encodings[instr_name_lower] = { | ||
| "category": path, | ||
| "yaml_match": yaml_match, | ||
| "yaml_vars": yaml_vars, | ||
| } | ||
|
|
||
| return instructions_with_encodings | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.