├── .eslintrc.json ├── .github └── workflows │ └── check.yml ├── .gitignore ├── .husky └── pre-commit ├── .prettierrc ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── .vscodeignore ├── .yarnrc ├── INSTALL.md ├── LICENSE.md ├── README.md ├── icon.png ├── imgs ├── definition.png ├── folding.png └── references.png ├── jest.config.js ├── package.json ├── src ├── test │ ├── __mocks__ │ │ └── vscode.js │ ├── data.ll │ └── lsp_model_provider.test.ts └── web │ ├── extension.ts │ └── llvmir │ ├── common.ts │ ├── definition_provider.ts │ ├── folding_provider.ts │ ├── lsp_model.ts │ ├── lsp_model_provider.ts │ ├── reference_provider.ts │ └── regexp.ts ├── tsconfig.json ├── typedoc.js ├── webpack.config.js └── yarn.lock /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["./node_modules/gts"], 3 | "root": true, 4 | "parser": "@typescript-eslint/parser", 5 | "parserOptions": { 6 | "ecmaVersion": 6, 7 | "sourceType": "module" 8 | }, 9 | "plugins": ["@typescript-eslint"], 10 | "rules": { 11 | "@typescript-eslint/naming-convention": "warn", 12 | "@typescript-eslint/semi": "warn", 13 | "@typescript-eslint/no-namespace": "off", 14 | "@typescript-eslint/no-unused-vars": ["warn", { "args": "none" }], 15 | "curly": "warn", 16 | "eqeqeq": "warn", 17 | "no-throw-literal": "warn", 18 | "node/no-unpublished-require": ["error", { "allowModules": ["mocha"] }], 19 | "quotes": ["warn", "double", { "avoidEscape": true }], 20 | "semi": "off" 21 | }, 22 | "ignorePatterns": ["*.js", "**/*.d.ts"] 23 | } 24 | -------------------------------------------------------------------------------- /.github/workflows/check.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - master 5 | pull_request: 6 | branches: [ main ] 7 | types: [opened, synchronize] 8 | name: Check linting and tests 9 | jobs: 10 | check: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | - name: Install modules 15 | run: yarn 16 | - name: Run ESLint 17 | run: yarn run lint 18 | - name: Run tests 19 | run: yarn run test 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | dist 3 | node_modules 4 | .vscode-test-web/ 5 | *.vsix 6 | .eslintcache 7 | docs 8 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn run lint-staged 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": false, 3 | "printWidth": 120, 4 | "tabWidth": 4, 5 | "bracketSpacing": true, 6 | "singleQuote": false, 7 | "arrowParens": "always", 8 | "trailingComma": "es5" 9 | } 10 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": ["dbaeumer.vscode-eslint", "amodio.tsl-problem-matcher"] 5 | } 6 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "Run Web Extension ", 10 | "type": "pwa-extensionHost", 11 | "debugWebWorkerHost": true, 12 | "request": "launch", 13 | "args": ["--extensionDevelopmentPath=${workspaceFolder}", "--extensionDevelopmentKind=web"], 14 | "outFiles": ["${workspaceFolder}/dist/web/**/*.js"], 15 | "preLaunchTask": "npm: watch-web" 16 | }, 17 | { 18 | "name": "Extension Tests", 19 | "type": "extensionHost", 20 | "debugWebWorkerHost": true, 21 | "request": "launch", 22 | "args": [ 23 | "--extensionDevelopmentPath=${workspaceFolder}", 24 | "--extensionDevelopmentKind=web", 25 | "--extensionTestsPath=${workspaceFolder}/dist/web/test/suite/index" 26 | ], 27 | "outFiles": ["${workspaceFolder}/dist/web/**/*.js"], 28 | "preLaunchTask": "npm: watch-web" 29 | } 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "files.exclude": { 4 | "out": false // set this to true to hide the "out" folder with the compiled JS files 5 | }, 6 | "search.exclude": { 7 | "out": true // set this to false to include "out" folder in search results 8 | }, 9 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts 10 | "typescript.tsc.autoDetect": "off" 11 | } 12 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "compile-web", 9 | "group": { 10 | "kind": "build", 11 | "isDefault": true 12 | }, 13 | "problemMatcher": ["$ts-webpack", "$tslint-webpack"] 14 | }, 15 | { 16 | "type": "npm", 17 | "script": "watch-web", 18 | "group": "build", 19 | "isBackground": true, 20 | "problemMatcher": ["$ts-webpack-watch", "$tslint-webpack-watch"] 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | **/* 2 | !icon.png 3 | !README.md 4 | !imgs/* 5 | !LICENSE.md 6 | !package.json 7 | !dist/web/extension.js 8 | !dist/web/extension.js.LICENSE 9 | -------------------------------------------------------------------------------- /.yarnrc: -------------------------------------------------------------------------------- 1 | # We ignore the engine since yarn cannot 2 | # detect vscode as an engine, whereas the marketplace 3 | # uses the information correctly 4 | --ignore-engines true -------------------------------------------------------------------------------- /INSTALL.md: -------------------------------------------------------------------------------- 1 | ## Compilation Instructions 2 | 3 | Make sure you have `node` and `yarn` installed on the local machine 4 | 5 | Then: 6 | 7 | ``` 8 | yarn 9 | ./node_modules/.bin/vsce package 10 | ``` 11 | 12 | Now there should be a `llvm-ir-.vsix` file present that can be installed via the menu option 'Install from VSIX' in the Extensions tab 13 | 14 | ## Running Tests 15 | 16 | To run tests 17 | 18 | ``` 19 | yarn 20 | yarn run test 21 | ``` 22 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 rev.ng Labs S.r.l. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LLVM IR Language Support 2 | 3 | This extension adds language support features for LLVM IR files (`.ll`) 4 | 5 | ## Features 6 | 7 | - `Go to Definition` for variables, attributes and metadata with line preview 8 | 9 | ![](imgs/definition.png) 10 | 11 | - `Go to References` for variables, attributes and metadata 12 | 13 | ![](imgs/references.png) 14 | 15 | - Folding ranges for functions and labels 16 | 17 | ![](imgs/folding.png) 18 | 19 | ## Syntax Highlighting 20 | 21 | This extension does not provide syntax highlighting. 22 | You can use one of the following: 23 | 24 | - [RReverser.llvm](https://marketplace.visualstudio.com/items?itemName=RReverser.llvm) 25 | - [colejcummins.llvm-syntax-highlighting](https://marketplace.visualstudio.com/items?itemName=colejcummins.llvm-syntax-highlighting) 26 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/revng/vscode-llvm-ir/18a02cf3f71e2c453da8a33b8553593cf2b16cea/icon.png -------------------------------------------------------------------------------- /imgs/definition.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/revng/vscode-llvm-ir/18a02cf3f71e2c453da8a33b8553593cf2b16cea/imgs/definition.png -------------------------------------------------------------------------------- /imgs/folding.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/revng/vscode-llvm-ir/18a02cf3f71e2c453da8a33b8553593cf2b16cea/imgs/folding.png -------------------------------------------------------------------------------- /imgs/references.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/revng/vscode-llvm-ir/18a02cf3f71e2c453da8a33b8553593cf2b16cea/imgs/references.png -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | verbose: true, 3 | roots: ["./src/test"], 4 | transform: { 5 | "^.+\\.tsx?$": "ts-jest", 6 | }, 7 | testRegex: "\\.(test|spec|perf)\\.tsx?$", 8 | testPathIgnorePatterns: ["/node_modules/"], 9 | moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"], 10 | moduleNameMapper: { 11 | "^vscode$": "/src/test/__mocks__/vscode.js", 12 | }, 13 | }; 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "llvm-ir", 3 | "publisher": "revng", 4 | "displayName": "LLVM IR Language Support", 5 | "description": "LLVM IR language support for Visual Studio Code", 6 | "version": "1.0.5", 7 | "engines": { 8 | "vscode": "^1.63.0" 9 | }, 10 | "license": "SEE LICENSE IN LICENSE.md", 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/revng/vscode-llvm-ir.git" 14 | }, 15 | "homepage": "https://github.com/revng/vscode-llvm-ir", 16 | "icon": "icon.png", 17 | "categories": [ 18 | "Programming Languages", 19 | "Language Packs" 20 | ], 21 | "activationEvents": [ 22 | "onStartupFinished" 23 | ], 24 | "browser": "./dist/web/extension.js", 25 | "contributes": { 26 | "languages": [ 27 | { 28 | "id": "llvm", 29 | "extensions": [ 30 | ".ll" 31 | ], 32 | "aliases": [ 33 | "LLVM IR", 34 | "ll" 35 | ] 36 | } 37 | ] 38 | }, 39 | "scripts": { 40 | "integration-test": "vscode-test-web --browserType=chromium --extensionDevelopmentPath=. --extensionTestsPath=dist/web/test/suite/index.js", 41 | "test": "jest", 42 | "pretest": "yarn run compile-web", 43 | "vscode:prepublish": "yarn run package-web", 44 | "compile-web": "webpack", 45 | "watch-web": "webpack --watch", 46 | "package-web": "webpack --mode production --devtool hidden-source-map", 47 | "docs": "typedoc", 48 | "lint": "eslint src --ext ts", 49 | "lint-staged": "lint-staged", 50 | "pretty": "prettier --config .prettierrc 'src/**/*.ts' --write", 51 | "format": "eslint src --fix --ext ts && prettier --config .prettierrc 'src/**/*.ts' --write", 52 | "run-in-browser": "vscode-test-web --browserType=chromium --extensionDevelopmentPath=. .", 53 | "prepare": "husky install" 54 | }, 55 | "devDependencies": { 56 | "@types/jest-when": "^3.5.0", 57 | "@types/mocha": "^9.0.0", 58 | "@types/vscode": "^1.63.0", 59 | "@types/webpack-env": "^1.16.3", 60 | "@typescript-eslint/eslint-plugin": "^5.9.1", 61 | "@typescript-eslint/parser": "^5.9.1", 62 | "@vscode/test-web": "^0.0.15", 63 | "assert": "^2.0.0", 64 | "eslint": "^8.6.0", 65 | "eslint-plugin-node": "^11.1.0", 66 | "eslint-plugin-prettier": "^4.0.0", 67 | "glob": "^7.2.0", 68 | "gts": "^3.1.0", 69 | "husky": "^7.0.4", 70 | "jest": "^27.5.1", 71 | "jest-mock-vscode": "^0.1.3", 72 | "jest-when": "^3.5.1", 73 | "lint-staged": ">=10", 74 | "mocha": "^9.1.3", 75 | "prettier": "^2.5.1", 76 | "process": "^0.11.10", 77 | "ts-jest": "^27.1.3", 78 | "ts-loader": "^9.2.6", 79 | "typedoc": "^0.22.12", 80 | "typescript": "^4.5.4", 81 | "vsce": "^2.6.7", 82 | "webpack": "^5.66.0", 83 | "webpack-cli": "^4.9.1" 84 | }, 85 | "lint-staged": { 86 | "*.(t|j)s": [ 87 | "eslint --cache --fix", 88 | "prettier --write" 89 | ], 90 | "*.(json|md)": [ 91 | "prettier --write" 92 | ] 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/test/__mocks__/vscode.js: -------------------------------------------------------------------------------- 1 | module.exports = require("jest-mock-vscode"); 2 | -------------------------------------------------------------------------------- /src/test/data.ll: -------------------------------------------------------------------------------- 1 | target triple = "x86_64-pc-linux-gnu" 2 | 3 | @.fmtstr = private unnamed_addr constant [5 x i8] c"%d \0A\00", align 1 4 | @.fizz = private unnamed_addr constant [6 x i8] c"Fizz \00", align 1 5 | @.buzz = private unnamed_addr constant [6 x i8] c"Buzz \00", align 1 6 | @.nl = private unnamed_addr constant [2 x i8] c"\0A\00", align 1 7 | @0 = private unnamed_addr constant [13 x i8] c"Hello world!\00", align 1 8 | @"😎" = private unnamed_addr constant [15 x i8] c"Goodbye world!\00", align 1 9 | 10 | declare noundef i32 @printf(i8* nocapture noundef readonly, ...) local_unnamed_addr 11 | 12 | define i32 @main() { 13 | br label %"funny:label" 14 | 15 | "funny:label": 16 | br label %"😊" 17 | 18 | "😊": 19 | br label %"\68ello" 20 | 21 | hello: 22 | br label %before 23 | 24 | before: 25 | %pfout = call i32 (i8*, ...) @printf(i8* getelementptr ([5 x i8], [5 x i8]* @.fmtstr, i64 0, i64 0), i32 69) 26 | br label %loopbegin 27 | 28 | loopbegin: 29 | %i = phi i32 [ 0, %before ], [ %nexti, %loopend ] 30 | %fbcount = add i32 0, 0 31 | 32 | %loopcond = icmp ule i32 %i, 50 33 | br i1 %loopcond, label %loopbody, label %loopout 34 | 35 | loopbody: 36 | %fizzrem = urem i32 %i, 3 37 | %fizzcond = icmp eq i32 %fizzrem, 0 38 | br i1 %fizzcond, label %fizz, label %afterfizz 39 | 40 | fizz: 41 | %pfoutF = call i32 (i8*, ...) @printf(i8* getelementptr ([6 x i8], [6 x i8]* @.fizz, i64 0, i64 0)) 42 | br label %afterfizz 43 | 44 | afterfizz: 45 | %didfizz = phi i1 [ 0, %loopbody ], [ 1, %fizz ] 46 | %buzzrem = urem i32 %i, 5 47 | %buzzcond = icmp eq i32 %buzzrem, 0 48 | br i1 %buzzcond, label %buzz, label %afterbuzz 49 | 50 | buzz: 51 | %pfoutB = call i32 (i8*, ...) @printf(i8* getelementptr ([6 x i8], [6 x i8]* @.buzz, i64 0, i64 0)) 52 | br label %afterbuzz 53 | 54 | afterbuzz: 55 | %didbuzz = phi i1 [ 0, %afterfizz ], [ 1, %buzz ] 56 | %didfizzorbuzz = or i1 %didfizz, %didbuzz 57 | 58 | br i1 %didfizzorbuzz, label %printnl, label %printi 59 | 60 | printnl: 61 | %pfoutNL = call i32 (i8*, ...) @printf(i8* getelementptr ([2 x i8], [2 x i8]* @.nl, i64 0, i64 0)) 62 | br label %loopend 63 | 64 | printi: 65 | %pfoutnum = call i32 (i8*, ...) @printf(i8* getelementptr ([5 x i8], [5 x i8]* @.fmtstr, i64 0, i64 0), i32 %i) 66 | br label %loopend 67 | 68 | loopend: 69 | %nexti = add i32 %i, 1 70 | br label %loopbegin 71 | 72 | loopout: 73 | ret i32 20 74 | } 75 | -------------------------------------------------------------------------------- /src/test/lsp_model_provider.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | 3 | import * as fs from "fs"; 4 | import { EndOfLine, TextDocument, TextLine, Uri } from "vscode"; 5 | import { LspModelProvider } from "../web/llvmir/lsp_model_provider"; 6 | 7 | describe("LspModelProvider", () => { 8 | const lines: string[] = fs.readFileSync("src/test/data.ll", { encoding: "utf-8" }).split(/\r?\n/); 9 | const lineAt = function (line: number): TextLine { 10 | return { 11 | lineNumber: line, 12 | text: lines[line], 13 | range: null as any, 14 | rangeIncludingLineBreak: null as any, 15 | firstNonWhitespaceCharacterIndex: null as any, 16 | isEmptyOrWhitespace: null as any, 17 | }; 18 | }; 19 | const stubDocument: TextDocument = { 20 | uri: Uri.from({ scheme: "mock" }), 21 | fileName: "mock", 22 | isUntitled: false, 23 | isClosed: false, 24 | isDirty: false, 25 | languageId: "llvm-ir", 26 | version: 0, 27 | save: async function () { 28 | return false; 29 | }, 30 | eol: EndOfLine.LF, 31 | lineCount: lines.length, 32 | lineAt: lineAt as any, 33 | getText: null as any, 34 | getWordRangeAtPosition: null as any, 35 | offsetAt: null as any, 36 | positionAt: null as any, 37 | validatePosition: null as any, 38 | validateRange: null as any, 39 | }; 40 | 41 | const lmp = new LspModelProvider(); 42 | const result = lmp.getModel(stubDocument); 43 | 44 | test("Function parsed", () => { 45 | expect(Array.from(result.functions.keys())).toEqual(["@main"]); 46 | }); 47 | 48 | test("Checking globals", () => { 49 | expect(Array.from(result.global.values.keys())).toEqual([ 50 | "@.fmtstr", 51 | "@.fizz", 52 | "@.buzz", 53 | "@.nl", 54 | "@0", 55 | "@😎", 56 | "@printf", 57 | ]); 58 | }); 59 | 60 | test("Checking locals in @main", () => { 61 | const mainFunc = result.functions.get("@main"); 62 | if (mainFunc !== undefined) { 63 | expect(Array.from(mainFunc.values.keys())).toContain("%didfizzorbuzz"); 64 | expect(Array.from(mainFunc.values.keys())).toContain("%before"); 65 | expect(Array.from(mainFunc.values.keys())).toContain("%funny:label"); 66 | expect(Array.from(mainFunc.values.keys())).toContain("%😊"); 67 | } else { 68 | expect(false).toBe(true); 69 | } 70 | }); 71 | 72 | test("Checking labels are normalized", () => { 73 | const mainFunc = result.functions.get("@main"); 74 | if (mainFunc !== undefined) { 75 | expect(Array.from(mainFunc.users.keys())).toContain("%hello"); 76 | } else { 77 | expect(false).toBe(true); 78 | } 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /src/web/extension.ts: -------------------------------------------------------------------------------- 1 | // 2 | // This file is distributed under the MIT License. See LICENSE.md for details. 3 | // 4 | 5 | import * as vscode from "vscode"; 6 | import { LLVMIRDefinitionProvider } from "./llvmir/definition_provider"; 7 | import { LLVMIRFoldingProvider } from "./llvmir/folding_provider"; 8 | import { LLVMReferenceProvider } from "./llvmir/reference_provider"; 9 | import { LspModelProvider } from "./llvmir/lsp_model_provider"; 10 | import { Regexp } from "./llvmir/regexp"; 11 | 12 | export function activate(context: vscode.ExtensionContext) { 13 | const llvmConfiguration: vscode.LanguageConfiguration = { 14 | comments: { lineComment: ";" }, 15 | brackets: [ 16 | ["{", "}"], 17 | ["[", "]"], 18 | ["{", "}"], 19 | ], 20 | wordPattern: Regexp.identifierOrLabel, 21 | }; 22 | const llvmirDocumentFilter: vscode.DocumentFilter = { pattern: "**/*.ll" }; 23 | const lsp = new LspModelProvider(); 24 | 25 | context.subscriptions.push( 26 | vscode.languages.setLanguageConfiguration("llvm", llvmConfiguration), 27 | vscode.languages.registerDefinitionProvider(llvmirDocumentFilter, new LLVMIRDefinitionProvider(lsp)), 28 | vscode.languages.registerReferenceProvider(llvmirDocumentFilter, new LLVMReferenceProvider(lsp)), 29 | vscode.languages.registerFoldingRangeProvider(llvmirDocumentFilter, new LLVMIRFoldingProvider(lsp)) 30 | ); 31 | } 32 | 33 | export function deactivate() {} 34 | -------------------------------------------------------------------------------- /src/web/llvmir/common.ts: -------------------------------------------------------------------------------- 1 | export function removeTrailing(str: string, trail: string): string { 2 | if (str.endsWith(trail)) { 3 | return str.slice(0, -trail.length); 4 | } else { 5 | return str; 6 | } 7 | } 8 | 9 | const escape = /\\[0-9a-fA-F]{2}/; 10 | export function normalizeIdentifier(str: string): string { 11 | const prefix = str.slice(0, 1); 12 | const pureIdentifier = str.endsWith('"') ? str.slice(2, -1) : str.slice(1); 13 | const normalizedIdentifier = pureIdentifier.replace(escape, (match: string) => { 14 | return String.fromCharCode(parseInt(match.slice(1), 16)); 15 | }); 16 | return prefix + normalizedIdentifier; 17 | } 18 | -------------------------------------------------------------------------------- /src/web/llvmir/definition_provider.ts: -------------------------------------------------------------------------------- 1 | // 2 | // This file is distributed under the MIT License. See LICENSE.md for details. 3 | // 4 | 5 | import { CancellationToken, DefinitionLink, DefinitionProvider, Position, Range, TextDocument } from "vscode"; 6 | import { normalizeIdentifier, removeTrailing } from "./common"; 7 | import { getFunctionFromLine } from "./lsp_model"; 8 | import { LspModelProvider } from "./lsp_model_provider"; 9 | import { Regexp } from "./regexp"; 10 | 11 | export class LLVMIRDefinitionProvider implements DefinitionProvider { 12 | constructor(private lspModelProvider: LspModelProvider) {} 13 | 14 | provideDefinition( 15 | document: TextDocument, 16 | position: Position, 17 | token: CancellationToken 18 | ): DefinitionLink[] | undefined { 19 | const lspModel = this.lspModelProvider.getModel(document); 20 | const varRange = document.getWordRangeAtPosition(position, Regexp.identifier); 21 | const labelRange = document.getWordRangeAtPosition(position, Regexp.label); 22 | const functionInfo = getFunctionFromLine(lspModel, position.line); 23 | if (varRange !== undefined) { 24 | const varName = normalizeIdentifier(document.getText(varRange)); 25 | if (functionInfo !== undefined) { 26 | return this.transform(document, varName, lspModel.global.values, functionInfo.info.values); 27 | } else { 28 | return this.transform(document, varName, lspModel.global.values); 29 | } 30 | } else if (labelRange !== undefined && functionInfo !== undefined) { 31 | const identifier = removeTrailing(document.getText(labelRange), ":"); 32 | const normalizedIdentifier = normalizeIdentifier(`%${identifier}`); 33 | return this.transform(document, normalizedIdentifier, functionInfo.info.values); 34 | } else { 35 | return undefined; 36 | } 37 | } 38 | 39 | private transform( 40 | document: TextDocument, 41 | varName: string, 42 | globals: Map, 43 | locals?: Map 44 | ): DefinitionLink[] | undefined { 45 | let position = locals?.get(varName); 46 | 47 | if (position === undefined) { 48 | position = globals.get(varName); 49 | } 50 | 51 | if (position !== undefined) { 52 | const line = document.lineAt(position.line).text; 53 | const range = new Range(position, position.with(undefined, position.character + varName.length)); 54 | const previewRange = new Range(position.line, 0, position.line, line.length); 55 | return [ 56 | { 57 | targetUri: document.uri, 58 | targetRange: previewRange, 59 | targetSelectionRange: range, 60 | }, 61 | ]; 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/web/llvmir/folding_provider.ts: -------------------------------------------------------------------------------- 1 | // 2 | // This file is distributed under the MIT License. See LICENSE.md for details. 3 | // 4 | 5 | import { 6 | CancellationToken, 7 | FoldingContext, 8 | FoldingRange, 9 | FoldingRangeProvider, 10 | ProviderResult, 11 | TextDocument, 12 | } from "vscode"; 13 | import { LspModelProvider } from "./lsp_model_provider"; 14 | 15 | export class LLVMIRFoldingProvider implements FoldingRangeProvider { 16 | constructor(private tokenModelProvider: LspModelProvider) {} 17 | 18 | provideFoldingRanges( 19 | document: TextDocument, 20 | context: FoldingContext, 21 | token: CancellationToken 22 | ): ProviderResult { 23 | const documentMap = this.tokenModelProvider.getModel(document); 24 | return documentMap.foldingRanges; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/web/llvmir/lsp_model.ts: -------------------------------------------------------------------------------- 1 | // 2 | // This file is distributed under the MIT License. See LICENSE.md for details. 3 | // 4 | 5 | import { FoldingRange, Position, Range } from "vscode"; 6 | 7 | export class LspModel { 8 | public version: number; 9 | public global: VariablesInfo; 10 | public functions: Map; 11 | public foldingRanges: FoldingRange[]; 12 | 13 | constructor(version: number) { 14 | this.version = version; 15 | this.global = new VariablesInfo(); 16 | this.functions = new Map(); 17 | this.foldingRanges = []; 18 | } 19 | } 20 | 21 | export class VariablesInfo { 22 | public values: Map; 23 | public users: Map; 24 | 25 | constructor() { 26 | this.values = new Map(); 27 | this.users = new Map(); 28 | } 29 | } 30 | 31 | export class FunctionInfo extends VariablesInfo { 32 | lineStart: number; 33 | lineEnd: number; 34 | 35 | constructor(lineStart: number) { 36 | super(); 37 | this.lineStart = lineStart; 38 | this.lineEnd = -1; 39 | } 40 | } 41 | 42 | export interface FunctionInfoEntry { 43 | name: string; 44 | info: FunctionInfo; 45 | } 46 | 47 | export function getFunctionFromLine(model: LspModel, line: number): FunctionInfoEntry | undefined { 48 | const entries = Array.from(model.functions.entries()); 49 | const find = entries.find((fi) => fi[1].lineStart <= line && fi[1].lineEnd >= line); 50 | return find !== undefined ? { name: find[0], info: find[1] } : undefined; 51 | } 52 | -------------------------------------------------------------------------------- /src/web/llvmir/lsp_model_provider.ts: -------------------------------------------------------------------------------- 1 | // 2 | // This file is distributed under the MIT License. See LICENSE.md for details. 3 | // 4 | 5 | import { FoldingRange, FoldingRangeKind, Position, Range, TextDocument, Uri } from "vscode"; 6 | import { normalizeIdentifier } from "./common"; 7 | import { FunctionInfo, LspModel } from "./lsp_model"; 8 | import { Regexp } from "./regexp"; 9 | 10 | export class LspModelProvider { 11 | private documentMap: Map; 12 | 13 | constructor() { 14 | this.documentMap = new Map(); 15 | } 16 | 17 | public getModel(document: TextDocument): LspModel { 18 | const documentMap = this.documentMap.get(document.uri); 19 | if (documentMap !== undefined && document.version === documentMap.version) { 20 | return documentMap; 21 | } else { 22 | const newDocumentMap = this.scanDocument(document); 23 | this.documentMap.set(document.uri, newDocumentMap); 24 | return newDocumentMap; 25 | } 26 | } 27 | 28 | private scanDocument(document: TextDocument): LspModel { 29 | const res: LspModel = new LspModel(document.version); 30 | let lastFunction: FunctionInfo | undefined; 31 | let lastLabelLine: number | undefined = undefined; 32 | for (let i = 0; i < document.lineCount; i++) { 33 | // Split at the first ';' to exclude comments 34 | const line = document.lineAt(i).text.split(";", 2)[0]; 35 | 36 | // For each line we first check for line-long declarations 37 | // if we find them we skip looking for values/users 38 | const labelMatch = line.match(Regexp.label); 39 | const defineMatch = line.match(Regexp.define); 40 | const declareMatch = line.match(Regexp.declare); 41 | const closeMatch = line.match(Regexp.close); 42 | 43 | if (defineMatch !== null && defineMatch.index !== null && defineMatch.groups !== undefined) { 44 | // Line is define, we add the funcid to the global values and parse 45 | // the parameters as local values 46 | const funcid = defineMatch.groups["funcid"]; 47 | const args = defineMatch.groups["args"]; 48 | const funcmeta = defineMatch.groups["funcmeta"]; 49 | 50 | lastFunction = new FunctionInfo(i); 51 | res.functions.set(normalizeIdentifier(funcid), lastFunction); 52 | 53 | // Take the arguments of the function and add them to the values 54 | const argsOffset = line.indexOf(args); 55 | const argsMatch = args.matchAll(Regexp.argument); 56 | const argsIndexes = []; 57 | for (const am of argsMatch) { 58 | if (am.index !== undefined && am.groups !== undefined) { 59 | const pos = new Position(i, argsOffset + am.index); 60 | argsIndexes.push(am.index); 61 | lastFunction.values.set(normalizeIdentifier(am.groups["value"]), pos); 62 | } 63 | } 64 | 65 | // Grab all other identifiers, add them to the references 66 | const argsIdentifierMatch = args.matchAll(Regexp.valueOrUser); 67 | for (const aim of argsIdentifierMatch) { 68 | if (aim.index !== undefined && !argsIndexes.includes(aim.index) && aim.groups !== undefined) { 69 | this.addUser(res.global.users, aim.groups["user"], i, argsOffset + aim.index); 70 | } 71 | } 72 | 73 | // Grab function metadata 74 | const funcMetaOffset = line.indexOf(funcmeta); 75 | const funcMetaMatches = funcmeta.matchAll(Regexp.valueOrUser); 76 | for (const fmm of funcMetaMatches) { 77 | if (fmm.index !== undefined && fmm.groups !== undefined) { 78 | this.addUser(res.global.users, fmm.groups["user"], i, funcMetaOffset + fmm.index); 79 | } 80 | } 81 | } else if (labelMatch !== null && labelMatch.index !== undefined && labelMatch.groups !== undefined) { 82 | // If a label is found, we add a '%' to make it coherent w.r.t. jump instructions 83 | const pos = new Position(i, labelMatch.index); 84 | if (lastFunction !== undefined) { 85 | lastFunction.values.set(normalizeIdentifier(`%${labelMatch.groups["label"]}`), pos); 86 | } 87 | if (lastLabelLine !== undefined) { 88 | // When a new label is found, add a folding range for the previous 89 | res.foldingRanges.push(new FoldingRange(lastLabelLine, i - 1, FoldingRangeKind.Region)); 90 | } 91 | lastLabelLine = i; 92 | } else if (closeMatch !== null) { 93 | if (lastFunction !== undefined) { 94 | // When a close bracket is detected wrap up the function definition 95 | // * Add a folding range for the function 96 | // * Add the line end 97 | // * undefine lastFunction to return to the 'global' context only 98 | // * If there is an 'open' label, close it 99 | res.foldingRanges.push(new FoldingRange(lastFunction.lineStart, i, FoldingRangeKind.Region)); 100 | lastFunction.lineEnd = i; 101 | lastFunction = undefined; 102 | 103 | if (lastLabelLine !== undefined) { 104 | res.foldingRanges.push(new FoldingRange(lastLabelLine, i, FoldingRangeKind.Region)); 105 | } 106 | } 107 | } else if (declareMatch !== null && declareMatch.groups !== undefined) { 108 | // Treat declarations as global values 109 | const funcid = declareMatch.groups["funcid"]; 110 | const offset = line.indexOf(funcid); 111 | res.global.values.set(normalizeIdentifier(funcid), new Position(i, offset)); 112 | } else { 113 | // If none of these apply search for values/users 114 | const identifierMatches = line.matchAll(Regexp.valueOrUser); 115 | 116 | for (const am of identifierMatches) { 117 | if (am.index !== undefined && am.groups !== undefined) { 118 | const pos = new Position(i, am.index); 119 | if (am.groups["value"] !== undefined) { 120 | const varname = normalizeIdentifier(am.groups["value"]); 121 | if (varname.startsWith("%") && lastFunction !== undefined) { 122 | lastFunction.values.set(varname, pos); 123 | } else { 124 | res.global.values.set(varname, pos); 125 | } 126 | } else if (am.groups["user"] !== undefined) { 127 | const varName = am.groups["user"]; 128 | if (varName.startsWith("%") && lastFunction !== undefined) { 129 | this.addUser(lastFunction.users, varName, i, am.index); 130 | } else { 131 | this.addUser(res.global.users, varName, i, am.index); 132 | } 133 | } 134 | } 135 | } 136 | } 137 | } 138 | return res; 139 | } 140 | 141 | private addUser(users: Map, key: string, lineNum: number, index: number) { 142 | const normalizedKey = normalizeIdentifier(key); 143 | const value = users.get(normalizedKey); 144 | const newRange = new Range(lineNum, index, lineNum, index + key.length); 145 | if (value !== undefined) { 146 | value.push(newRange); 147 | users.set(normalizedKey, value); 148 | } else { 149 | users.set(normalizedKey, [newRange]); 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/web/llvmir/reference_provider.ts: -------------------------------------------------------------------------------- 1 | // 2 | // This file is distributed under the MIT License. See LICENSE.md for details. 3 | // 4 | 5 | import { 6 | CancellationToken, 7 | Location, 8 | Position, 9 | ProviderResult, 10 | Range, 11 | ReferenceContext, 12 | ReferenceProvider, 13 | TextDocument, 14 | Uri, 15 | } from "vscode"; 16 | import { normalizeIdentifier, removeTrailing } from "./common"; 17 | import { getFunctionFromLine } from "./lsp_model"; 18 | import { LspModelProvider } from "./lsp_model_provider"; 19 | import { Regexp } from "./regexp"; 20 | 21 | export class LLVMReferenceProvider implements ReferenceProvider { 22 | constructor(private lspModelProvider: LspModelProvider) {} 23 | 24 | provideReferences( 25 | document: TextDocument, 26 | position: Position, 27 | context: ReferenceContext, 28 | token: CancellationToken 29 | ): ProviderResult { 30 | const lspModel = this.lspModelProvider.getModel(document); 31 | const varRange = document.getWordRangeAtPosition(position, Regexp.identifier); 32 | const labelRange = document.getWordRangeAtPosition(position, Regexp.label); 33 | const functionInfo = getFunctionFromLine(lspModel, position.line); 34 | if (varRange !== undefined) { 35 | const varName = normalizeIdentifier(document.getText(varRange)); 36 | if (functionInfo?.info.values.get(varName) !== undefined) { 37 | return this.transform(document.uri, functionInfo.info.users.get(varName)); 38 | } else { 39 | const globalUsers = lspModel.global.users.get(varName); 40 | return this.transform(document.uri, globalUsers); 41 | } 42 | } else if (labelRange !== undefined && functionInfo !== undefined) { 43 | const identifier = removeTrailing(document.getText(labelRange), ":"); 44 | const normalizedIdentifier = normalizeIdentifier(`%${identifier}`); 45 | return this.transform(document.uri, functionInfo.info.users.get(normalizedIdentifier)); 46 | } else { 47 | return []; 48 | } 49 | } 50 | 51 | private transform(uri: Uri, ranges?: Range[]): Location[] { 52 | return ranges !== undefined ? ranges.map((e) => new Location(uri, e)) : []; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/web/llvmir/regexp.ts: -------------------------------------------------------------------------------- 1 | // 2 | // This file is distributed under the MIT License. See LICENSE.md for details. 3 | // 4 | 5 | /** 6 | * Namespace containing all regexps for parsing LLVM IR 7 | */ 8 | export namespace Regexp { 9 | // Fragments 10 | 11 | /** 12 | * Standard identifier from https://llvm.org/docs/LangRef.html#identifiers 13 | */ 14 | const identifierFrag = xstr(`( 15 | [-a-zA-Z$._][-a-zA-Z$._0-9]*| # Standard Identifier Regex 16 | ".*?" # Quoted identifier 17 | )`); 18 | 19 | /** 20 | * Matches global identifiers 21 | */ 22 | const globalVarFrag = `@(${identifierFrag}|\\d+)`; 23 | 24 | /** 25 | * Matches local identifiers 26 | */ 27 | const allLocalVarFrag = xstr(`%( 28 | ${identifierFrag}| # Named identifiers 29 | \\d+ # Anonymous identifiers 30 | )`); 31 | 32 | /** 33 | * Matches attributes 34 | */ 35 | const attributeGroupFrag = "#\\d+"; 36 | 37 | /** 38 | * Matches metadata 39 | */ 40 | const metadataFrag = `!(${identifierFrag}|\\d+)`; 41 | 42 | /** 43 | * Vacuum up all identifiers by "OR-ing" all of them 44 | */ 45 | const allIdentifiersFrag = xstr(`( 46 | ${globalVarFrag}| # Global Identifiers 47 | ${allLocalVarFrag}| # Local variables 48 | ${attributeGroupFrag}| # Attributes 49 | ${metadataFrag} # Metadata 50 | )`); 51 | 52 | // Regexes 53 | 54 | /** 55 | * Generic identifier regex, without named capture 56 | * Used with getWordRangeAtPosition 57 | */ 58 | export const identifier = new RegExp(`${allIdentifiersFrag}`); 59 | 60 | /** 61 | * Matches an identifier or a label 62 | */ 63 | export const identifierOrLabel = xre( 64 | `( 65 | ${allIdentifiersFrag}| # Normal identifier 66 | (${identifierFrag}|\\d+): # Label identifier 67 | )` 68 | ); 69 | 70 | /** 71 | * We consider an assignment an identifier followed by a '=' 72 | * Since the named capture 'value' is first it will have precedence 73 | * otherwise it is a reference it will show up in the named caputure 'user' 74 | */ 75 | export const valueOrUser = xre( 76 | `( 77 | (?${allIdentifiersFrag})\\s*=| # Assignments are captured first if applicable 78 | (?${allIdentifiersFrag})(\\*|) # Otherwise grab identifiers as uses 79 | )`, 80 | "g" 81 | ); 82 | 83 | /** 84 | * We take all locals followed optionally by a comma 85 | * This is used in function declarations to grab 86 | * the 'assignment' of the function's parameters 87 | */ 88 | export const argument = xre( 89 | ` 90 | (?${allLocalVarFrag}) # Capture local variables in the 'value' capture 91 | \\s* # Whitespace can follow 92 | (,|$) # Must end with a comma or end of string 93 | `, 94 | "g" 95 | ); 96 | 97 | /** 98 | * Labels are matched inside the 'label' capture 99 | */ 100 | export const label = xre(` 101 | ^ # Match start of line 102 | (?