├── .eslintrc.json ├── .gitattributes ├── .gitignore ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── .vscodeignore ├── .yarnrc ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── doc ├── callgraph.png ├── compare.png ├── highlight.png ├── pass-view.png ├── step1.png ├── step2.png └── 功能需求.md ├── example ├── .vscode │ └── launch.json ├── main.S ├── main.bc ├── main.c ├── main.cpp ├── main.cu ├── main.i ├── main.ll └── test │ ├── main.bc │ ├── main.c │ ├── main.c.s │ └── main.i ├── image.png ├── media ├── main.css ├── main.js ├── reset.css └── vscode.css ├── package.json ├── resources ├── LLVM.svg ├── disassembly.json └── llvm-ast.json ├── src ├── asm.ts ├── available-panel.ts ├── clang.ts ├── config.ts ├── control-panel.ts ├── core.ts ├── debug.ts ├── decorator.ts ├── document.ts ├── extension.ts ├── pipeline-panel.ts ├── test │ ├── runTest.ts │ └── suite │ │ ├── clang.test.ts │ │ ├── extension.test.ts │ │ └── index.ts └── utils.ts ├── tsconfig.json ├── vsc-extension-quickstart.md ├── webpack.config.js └── yarn.lock /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "parserOptions": { 5 | "ecmaVersion": 6, 6 | "sourceType": "module" 7 | }, 8 | "plugins": [ 9 | "@typescript-eslint" 10 | ], 11 | "rules": { 12 | "@typescript-eslint/naming-convention": "warn", 13 | "@typescript-eslint/semi": "warn", 14 | "curly": "warn", 15 | "eqeqeq": "warn", 16 | "no-throw-literal": "warn", 17 | "semi": "off" 18 | }, 19 | "ignorePatterns": [ 20 | "out", 21 | "dist", 22 | "**/*.d.ts" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | example/** linguist-detectable=false 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | dist 3 | node_modules 4 | .vscode-test/ 5 | *.vsix 6 | -------------------------------------------------------------------------------- /.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 Extension", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "args": [ 13 | // "--disable-extensions", 14 | "--extensionDevelopmentPath=${workspaceFolder}", 15 | "${workspaceFolder}/example" 16 | ], 17 | "outFiles": [ 18 | "${workspaceFolder}/dist/**/*.js" 19 | ], 20 | // "preLaunchTask": "${defaultBuildTask}" 21 | }, 22 | { 23 | "name": "Extension Tests", 24 | "type": "extensionHost", 25 | "request": "launch", 26 | "args": [ 27 | "--disable-extensions", 28 | "--extensionDevelopmentPath=${workspaceFolder}", 29 | "--extensionTestsPath=${workspaceFolder}/out/test/suite/index" 30 | ], 31 | "outFiles": [ 32 | "${workspaceFolder}/out/**/*.js", 33 | "${workspaceFolder}/dist/**/*.js" 34 | ], 35 | // "preLaunchTask": "tasks: watch-tests" 36 | } 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /.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 | "dist": false // set this to true to hide the "dist" folder with the compiled JS files 6 | }, 7 | "search.exclude": { 8 | "out": true, // set this to false to include "out" folder in search results 9 | "dist": true // set this to false to include "dist" folder in search results 10 | }, 11 | // Turn off tsc task auto detection since we have the necessary tasks as npm scripts 12 | "typescript.tsc.autoDetect": "off", 13 | "disasexpl.associations": { 14 | "**/*.c": "${fileDirname}/${fileBasenameNoExtension}.S" 15 | } 16 | } -------------------------------------------------------------------------------- /.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": "watch", 9 | "problemMatcher": "$ts-webpack-watch", 10 | "isBackground": true, 11 | "presentation": { 12 | "reveal": "never", 13 | "group": "watchers" 14 | }, 15 | "group": { 16 | "kind": "build", 17 | "isDefault": true 18 | } 19 | }, 20 | { 21 | "type": "npm", 22 | "script": "watch-tests", 23 | "problemMatcher": "$tsc-watch", 24 | "isBackground": true, 25 | "presentation": { 26 | "reveal": "never", 27 | "group": "watchers" 28 | }, 29 | "group": "build" 30 | }, 31 | { 32 | "label": "tasks: watch-tests", 33 | "dependsOn": [ 34 | "npm: watch", 35 | "npm: watch-tests" 36 | ], 37 | "problemMatcher": [] 38 | } 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | out/** 4 | node_modules/** 5 | src/** 6 | .gitignore 7 | .yarnrc 8 | webpack.config.js 9 | vsc-extension-quickstart.md 10 | **/tsconfig.json 11 | **/.eslintrc.json 12 | **/*.map 13 | **/*.ts 14 | -------------------------------------------------------------------------------- /.yarnrc: -------------------------------------------------------------------------------- 1 | --ignore-engines true -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to the "vscode-llvm" extension will be documented in this file. 4 | 5 | Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file. 6 | 7 | ## v0.4.1 8 | show clang AST and highlight it 9 | 10 | ## v0.4 11 | A more complete version: 12 | 1. show input files 13 | 2. add settings 14 | 3. add mapping view of source code to assembly code 15 | 4. add assembly highlight 16 | 17 | ## v0.3 18 | This release fully implemented the core features of vscode-llvm. Lots of code are refactored. 19 | 20 | 1. command parsing - it knows clang commands, can get the inner workflow of compiler and show IR for before and after each pass. 21 | 2. add available pass view and it can run specific llvm pass 22 | 3. compare mode to compare two compiler workflows. 23 | 24 | ## v0.2 25 | Implement the pipeline view and clang IR presentation. 26 | 27 | ## v0.1 28 | 29 | A very basic version with only command parsing and parsing. 30 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | CONTRIBUTING 2 | ======================= 3 | 4 | 5 | ## How to develope this extension 6 | 7 | We use yarn to manage dependencies. You can install yarn from [here](https://yarnpkg.com/en/docs/install). 8 | Then, please run `yarn` to install dependencies. 9 | 10 | ### Some useful scripts 11 | 12 | - `yarn run compile`: compile typescript files 13 | - `yarn run watch`: watch and compile typescript files automatically 14 | - `yarn run package`: package extension 15 | 16 | ### How to submit to the marketplace 17 | 18 | Please see [here](https://code.visualstudio.com/docs/extensions/publish-extension). 19 | 20 | Make sure you have Node.js installed. Then run: 21 | ```sh 22 | npm install -g @vscode/vsce 23 | ``` 24 | 25 | Remember to change the version number in `package.json` before publishing. 26 | Then you can publish the extension by running: 27 | ```sh 28 | vsce package 29 | vsce publish 30 | ``` 31 | 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Xiaofan Sun 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 | # VSCode LLVM Compiler Explorer 2 | Download from [VSCode marketplace](https://marketplace.visualstudio.com/items?itemName=XiaofanSun.vscode-llvm). 3 | 4 | This is a tool for compiler developers of LLVM. This vscode extension can support exploring LLVM IR and machine IR after each pass. 5 | 6 | ## Features 7 | 8 | 1. Run a clang command and explore preprocessing phase, clang AST building phase, each phase of LLVM passes, and final generated assembly code. 9 | 2. Compare difference between IRs before and after running a pass. 10 | 3. Support custom clang or modified version. 11 | 12 | ## How to use 13 | 14 | ![](./doc/step1.png) 15 | 16 | 1. Click 'New config' for creating a new configuration 17 | 2. Type your command to compile the file 18 | 19 | ![](./doc/step2.png) 20 | 21 | 3. (Optional) If you want to focus on one function. You can type the function name in the 'filter function' field. Please note, this function name should be mangle name if it's C++. Then, click the command name to run the command. 22 | 4. Now, you can explore the IRs after each pass. 23 | 24 | ![](./doc/pass-view.png) 25 | 26 | 27 | ## Compare Mode 28 | 29 | To debug a pass, you may want to compare a clang command with and without the pass. Or you want to debug a different version of clang with the stable version. This extension can help you to compare the difference between two IRs after each pass. 30 | 31 | ![](./doc/compare.png) 32 | 33 | ## Source2Asm Mapping View 34 | 35 | Inspired by Compiler Explorer, this extension can highlight the mapping between source code and the assembly code. 36 | 37 | ![](./doc/highlight.png) 38 | 39 | 40 | ## Run Print Pass to View CallGraph/CFG/DOMTree 41 | 42 | There is a list of utility passes available in this plugin. You can run these passes to view the CallGraph, CFG, and DOMTree of an IR file. 43 | 44 | When you open an IR file, you can click the 'Print Call Graph' button to generate a callgraph.dot file. Then, you can use the Graphviz plugin to view the callgraph. 45 | 46 | Note: You need to have 'opt' tool in your PATH (or specify the path in settings) and installed [Graphviz preview plugin](https://marketplace.visualstudio.com/items?itemName=tintinweb.graphviz-interactive-preview). 47 | 48 | ![](./doc/callgraph.png) 49 | 50 | 51 | ## Syntax Highlighting 52 | 53 | This plugin provides syntax highlighting for Clang AST and assembly language. You may need to install additional LLVM syntax highlighting plugins, there are some options: 54 | 55 | - [RReverser.llvm](https://marketplace.visualstudio.com/items?itemName=RReverser.llvm) 56 | - [colejcummins.llvm-syntax-highlighting](https://marketplace.visualstudio.com/items?itemName=colejcummins.llvm-syntax-highlighting) 57 | 58 | -------------------------------------------------------------------------------- /doc/callgraph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunxfancy/vscode-llvm/2d6218755b450888b80fd9831d7e200325ff5d96/doc/callgraph.png -------------------------------------------------------------------------------- /doc/compare.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunxfancy/vscode-llvm/2d6218755b450888b80fd9831d7e200325ff5d96/doc/compare.png -------------------------------------------------------------------------------- /doc/highlight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunxfancy/vscode-llvm/2d6218755b450888b80fd9831d7e200325ff5d96/doc/highlight.png -------------------------------------------------------------------------------- /doc/pass-view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunxfancy/vscode-llvm/2d6218755b450888b80fd9831d7e200325ff5d96/doc/pass-view.png -------------------------------------------------------------------------------- /doc/step1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunxfancy/vscode-llvm/2d6218755b450888b80fd9831d7e200325ff5d96/doc/step1.png -------------------------------------------------------------------------------- /doc/step2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunxfancy/vscode-llvm/2d6218755b450888b80fd9831d7e200325ff5d96/doc/step2.png -------------------------------------------------------------------------------- /doc/功能需求.md: -------------------------------------------------------------------------------- 1 | 2 | VSCode LLVM Compiler Explorer 是一款用于开发LLVM相关编译器的VSCode插件。 3 | 该插件可以支持在每个LLVM Pass运行后,查看LLVM IR和机器码的变化。同时,可以支持自定义编译器命令,以及自定义LLVM版本。 4 | 5 | 这个项目的基础是一个本地版本的Compiler Explorer,我们需要将源码和asm进行映射,使得查看方便。 6 | 更进一步,我们希望将源码和llvm-ir进行映射,还有llvm-ir和asm进行映射。 7 | 8 | 我们的重点应该是设计不同方式的显示Pass的方式 9 | 10 | ## 普通运行单Pass 11 | 12 | 提供三种显示Pass的方式: 13 | 1. 顺序显示执行过的Pass 14 | 2. 按树型结构显示执行过的Pass 15 | 3. 安装函数变换的方式显示执行过的pass 16 | 17 | ## 我们提供快速调试的功能 18 | 19 | 对于LLVM的开发,我们可以缓存一些中间结果,以便于快速调试。 20 | 比如你发现某个Pass的结果不对,你可以选择从某个Pass开始重新运行,而不是从头开始运行。 21 | 22 | ## 列出LLVM下面的Pass 23 | 24 | 我们可以打印所有可用的Pass,然后用户可以选择需要运行的Pass。 25 | 26 | 27 | ## 可视化LLVM编译器内部工作流 28 | 29 | 我们可以将内部流程可视化出来,创建流程图的方式,比树型图更加直观 30 | 31 | 32 | ## 对于LTO的支持 33 | 34 | 为了开发ThinLTO,我们的链接工作流有很大的不同,这里需要特殊处理。 35 | 36 | ## 对于CUDA编译器的支持 37 | 38 | 我们也很希望能支持CUDA的nvcc编译器 39 | 40 | -------------------------------------------------------------------------------- /example/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "type": "lldb", 5 | "request": "launch", 6 | "name": "(lldb) Launch clang", 7 | "program": "clang", 8 | "args": [ 9 | "-O3", 10 | "-S", 11 | "-save-temps", 12 | "-mllvm", 13 | "-print-before-all", 14 | "-mllvm", 15 | "-print-after-all", 16 | "-o", 17 | "-", 18 | "main.c" 19 | ], 20 | "cwd": "${workspaceFolder}" 21 | } 22 | ] 23 | } -------------------------------------------------------------------------------- /example/main.S: -------------------------------------------------------------------------------- 1 | .text 2 | .file "main.c" 3 | .globl main # -- Begin function main 4 | .p2align 4, 0x90 5 | .type main,@function 6 | main: # @main 7 | .Lfunc_begin0: 8 | .file 0 "/home/sxf/Workspace/vscode-llvm/example" "main.c" md5 0x8c57ab0da351f3f97477b5e7b26455e8 9 | .loc 0 3 0 # main.c:3:0 10 | .cfi_startproc 11 | # %bb.0: 12 | pushq %rbp 13 | .cfi_def_cfa_offset 16 14 | .cfi_offset %rbp, -16 15 | movq %rsp, %rbp 16 | .cfi_def_cfa_register %rbp 17 | subq $16, %rsp 18 | movl $0, -4(%rbp) 19 | movl %edi, -8(%rbp) 20 | movq %rsi, -16(%rbp) 21 | .Ltmp0: 22 | .loc 0 4 5 prologue_end # main.c:4:5 23 | leaq .L.str(%rip), %rdi 24 | movb $0, %al 25 | callq printf@PLT 26 | .loc 0 5 5 # main.c:5:5 27 | xorl %eax, %eax 28 | addq $16, %rsp 29 | popq %rbp 30 | .cfi_def_cfa %rsp, 8 31 | retq 32 | .Ltmp1: 33 | .Lfunc_end0: 34 | .size main, .Lfunc_end0-main 35 | .cfi_endproc 36 | # -- End function 37 | .type .L.str,@object # @.str 38 | .section .rodata.str1.1,"aMS",@progbits,1 39 | .L.str: 40 | .asciz "Hello, world!\n" 41 | .size .L.str, 15 42 | 43 | .section .debug_abbrev,"",@progbits 44 | .byte 1 # Abbreviation Code 45 | .byte 17 # DW_TAG_compile_unit 46 | .byte 1 # DW_CHILDREN_yes 47 | .byte 37 # DW_AT_producer 48 | .byte 37 # DW_FORM_strx1 49 | .byte 19 # DW_AT_language 50 | .byte 5 # DW_FORM_data2 51 | .byte 3 # DW_AT_name 52 | .byte 37 # DW_FORM_strx1 53 | .byte 114 # DW_AT_str_offsets_base 54 | .byte 23 # DW_FORM_sec_offset 55 | .byte 16 # DW_AT_stmt_list 56 | .byte 23 # DW_FORM_sec_offset 57 | .byte 27 # DW_AT_comp_dir 58 | .byte 37 # DW_FORM_strx1 59 | .byte 17 # DW_AT_low_pc 60 | .byte 27 # DW_FORM_addrx 61 | .byte 18 # DW_AT_high_pc 62 | .byte 6 # DW_FORM_data4 63 | .byte 115 # DW_AT_addr_base 64 | .byte 23 # DW_FORM_sec_offset 65 | .byte 0 # EOM(1) 66 | .byte 0 # EOM(2) 67 | .byte 2 # Abbreviation Code 68 | .byte 46 # DW_TAG_subprogram 69 | .byte 1 # DW_CHILDREN_yes 70 | .byte 17 # DW_AT_low_pc 71 | .byte 27 # DW_FORM_addrx 72 | .byte 18 # DW_AT_high_pc 73 | .byte 6 # DW_FORM_data4 74 | .byte 64 # DW_AT_frame_base 75 | .byte 24 # DW_FORM_exprloc 76 | .byte 3 # DW_AT_name 77 | .byte 37 # DW_FORM_strx1 78 | .byte 58 # DW_AT_decl_file 79 | .byte 11 # DW_FORM_data1 80 | .byte 59 # DW_AT_decl_line 81 | .byte 11 # DW_FORM_data1 82 | .byte 39 # DW_AT_prototyped 83 | .byte 25 # DW_FORM_flag_present 84 | .byte 73 # DW_AT_type 85 | .byte 19 # DW_FORM_ref4 86 | .byte 63 # DW_AT_external 87 | .byte 25 # DW_FORM_flag_present 88 | .byte 0 # EOM(1) 89 | .byte 0 # EOM(2) 90 | .byte 3 # Abbreviation Code 91 | .byte 5 # DW_TAG_formal_parameter 92 | .byte 0 # DW_CHILDREN_no 93 | .byte 2 # DW_AT_location 94 | .byte 24 # DW_FORM_exprloc 95 | .byte 3 # DW_AT_name 96 | .byte 37 # DW_FORM_strx1 97 | .byte 58 # DW_AT_decl_file 98 | .byte 11 # DW_FORM_data1 99 | .byte 59 # DW_AT_decl_line 100 | .byte 11 # DW_FORM_data1 101 | .byte 73 # DW_AT_type 102 | .byte 19 # DW_FORM_ref4 103 | .byte 0 # EOM(1) 104 | .byte 0 # EOM(2) 105 | .byte 4 # Abbreviation Code 106 | .byte 36 # DW_TAG_base_type 107 | .byte 0 # DW_CHILDREN_no 108 | .byte 3 # DW_AT_name 109 | .byte 37 # DW_FORM_strx1 110 | .byte 62 # DW_AT_encoding 111 | .byte 11 # DW_FORM_data1 112 | .byte 11 # DW_AT_byte_size 113 | .byte 11 # DW_FORM_data1 114 | .byte 0 # EOM(1) 115 | .byte 0 # EOM(2) 116 | .byte 5 # Abbreviation Code 117 | .byte 15 # DW_TAG_pointer_type 118 | .byte 0 # DW_CHILDREN_no 119 | .byte 73 # DW_AT_type 120 | .byte 19 # DW_FORM_ref4 121 | .byte 0 # EOM(1) 122 | .byte 0 # EOM(2) 123 | .byte 0 # EOM(3) 124 | .section .debug_info,"",@progbits 125 | .Lcu_begin0: 126 | .long .Ldebug_info_end0-.Ldebug_info_start0 # Length of Unit 127 | .Ldebug_info_start0: 128 | .short 5 # DWARF version number 129 | .byte 1 # DWARF Unit Type 130 | .byte 8 # Address Size (in bytes) 131 | .long .debug_abbrev # Offset Into Abbrev. Section 132 | .byte 1 # Abbrev [1] 0xc:0x50 DW_TAG_compile_unit 133 | .byte 0 # DW_AT_producer 134 | .short 12 # DW_AT_language 135 | .byte 1 # DW_AT_name 136 | .long .Lstr_offsets_base0 # DW_AT_str_offsets_base 137 | .long .Lline_table_start0 # DW_AT_stmt_list 138 | .byte 2 # DW_AT_comp_dir 139 | .byte 0 # DW_AT_low_pc 140 | .long .Lfunc_end0-.Lfunc_begin0 # DW_AT_high_pc 141 | .long .Laddr_table_base0 # DW_AT_addr_base 142 | .byte 2 # Abbrev [2] 0x23:0x26 DW_TAG_subprogram 143 | .byte 0 # DW_AT_low_pc 144 | .long .Lfunc_end0-.Lfunc_begin0 # DW_AT_high_pc 145 | .byte 1 # DW_AT_frame_base 146 | .byte 86 147 | .byte 3 # DW_AT_name 148 | .byte 0 # DW_AT_decl_file 149 | .byte 3 # DW_AT_decl_line 150 | # DW_AT_prototyped 151 | .long 73 # DW_AT_type 152 | # DW_AT_external 153 | .byte 3 # Abbrev [3] 0x32:0xb DW_TAG_formal_parameter 154 | .byte 2 # DW_AT_location 155 | .byte 145 156 | .byte 120 157 | .byte 5 # DW_AT_name 158 | .byte 0 # DW_AT_decl_file 159 | .byte 3 # DW_AT_decl_line 160 | .long 73 # DW_AT_type 161 | .byte 3 # Abbrev [3] 0x3d:0xb DW_TAG_formal_parameter 162 | .byte 2 # DW_AT_location 163 | .byte 145 164 | .byte 112 165 | .byte 6 # DW_AT_name 166 | .byte 0 # DW_AT_decl_file 167 | .byte 3 # DW_AT_decl_line 168 | .long 77 # DW_AT_type 169 | .byte 0 # End Of Children Mark 170 | .byte 4 # Abbrev [4] 0x49:0x4 DW_TAG_base_type 171 | .byte 4 # DW_AT_name 172 | .byte 5 # DW_AT_encoding 173 | .byte 4 # DW_AT_byte_size 174 | .byte 5 # Abbrev [5] 0x4d:0x5 DW_TAG_pointer_type 175 | .long 82 # DW_AT_type 176 | .byte 5 # Abbrev [5] 0x52:0x5 DW_TAG_pointer_type 177 | .long 87 # DW_AT_type 178 | .byte 4 # Abbrev [4] 0x57:0x4 DW_TAG_base_type 179 | .byte 7 # DW_AT_name 180 | .byte 6 # DW_AT_encoding 181 | .byte 1 # DW_AT_byte_size 182 | .byte 0 # End Of Children Mark 183 | .Ldebug_info_end0: 184 | .section .debug_str_offsets,"",@progbits 185 | .long 36 # Length of String Offsets Set 186 | .short 5 187 | .short 0 188 | .Lstr_offsets_base0: 189 | .section .debug_str,"MS",@progbits,1 190 | .Linfo_string0: 191 | .asciz "clang version 14.0.6" # string offset=0 192 | .Linfo_string1: 193 | .asciz "main.c" # string offset=21 194 | .Linfo_string2: 195 | .asciz "/home/sxf/Workspace/vscode-llvm/example" # string offset=28 196 | .Linfo_string3: 197 | .asciz "main" # string offset=68 198 | .Linfo_string4: 199 | .asciz "int" # string offset=73 200 | .Linfo_string5: 201 | .asciz "argc" # string offset=77 202 | .Linfo_string6: 203 | .asciz "argv" # string offset=82 204 | .Linfo_string7: 205 | .asciz "char" # string offset=87 206 | .section .debug_str_offsets,"",@progbits 207 | .long .Linfo_string0 208 | .long .Linfo_string1 209 | .long .Linfo_string2 210 | .long .Linfo_string3 211 | .long .Linfo_string4 212 | .long .Linfo_string5 213 | .long .Linfo_string6 214 | .long .Linfo_string7 215 | .section .debug_addr,"",@progbits 216 | .long .Ldebug_addr_end0-.Ldebug_addr_start0 # Length of contribution 217 | .Ldebug_addr_start0: 218 | .short 5 # DWARF version number 219 | .byte 8 # Address size 220 | .byte 0 # Segment selector size 221 | .Laddr_table_base0: 222 | .quad .Lfunc_begin0 223 | .Ldebug_addr_end0: 224 | .ident "clang version 14.0.6" 225 | .section ".note.GNU-stack","",@progbits 226 | .addrsig 227 | .addrsig_sym printf 228 | .section .debug_line,"",@progbits 229 | .Lline_table_start0: 230 | -------------------------------------------------------------------------------- /example/main.bc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunxfancy/vscode-llvm/2d6218755b450888b80fd9831d7e200325ff5d96/example/main.bc -------------------------------------------------------------------------------- /example/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main(int argc, char *argv[]) { 4 | printf("Hello, world!\n"); 5 | return 0; 6 | } -------------------------------------------------------------------------------- /example/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main(int argc, char *argv[]) { 4 | printf("Hello, world!\n"); 5 | return 0; 6 | } -------------------------------------------------------------------------------- /example/main.cu: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved. 2 | * 3 | * Redistribution and use in source and binary forms, with or without 4 | * modification, are permitted provided that the following conditions 5 | * are met: 6 | * * Redistributions of source code must retain the above copyright 7 | * notice, this list of conditions and the following disclaimer. 8 | * * Redistributions in binary form must reproduce the above copyright 9 | * notice, this list of conditions and the following disclaimer in the 10 | * documentation and/or other materials provided with the distribution. 11 | * * Neither the name of NVIDIA CORPORATION nor the names of its 12 | * contributors may be used to endorse or promote products derived 13 | * from this software without specific prior written permission. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY 16 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 18 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 19 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 20 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 21 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 22 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 23 | * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | /** 29 | * Vector addition: C = A + B. 30 | * 31 | * This sample is a very basic sample that implements element by element 32 | * vector addition. It is the same as the sample illustrating Chapter 2 33 | * of the programming guide with some additions like error checking. 34 | */ 35 | 36 | #include 37 | 38 | // For the CUDA runtime routines (prefixed with "cuda_") 39 | #include 40 | 41 | /** 42 | * CUDA Kernel Device code 43 | * 44 | * Computes the vector addition of A and B into C. The 3 vectors have the same 45 | * number of elements numElements. 46 | */ 47 | __global__ void vectorAdd(const float *A, const float *B, float *C, 48 | int numElements) { 49 | int i = blockDim.x * blockIdx.x + threadIdx.x; 50 | 51 | if (i < numElements) { 52 | C[i] = A[i] + B[i] + 0.0f; 53 | } 54 | } 55 | 56 | /** 57 | * Host main routine 58 | */ 59 | int main(void) { 60 | // Error code to check return values for CUDA calls 61 | cudaError_t err = cudaSuccess; 62 | 63 | // Print the vector length to be used, and compute its size 64 | int numElements = 50000; 65 | size_t size = numElements * sizeof(float); 66 | printf("[Vector addition of %d elements]\n", numElements); 67 | 68 | // Allocate the host input vector A 69 | float *h_A = (float *)malloc(size); 70 | 71 | // Allocate the host input vector B 72 | float *h_B = (float *)malloc(size); 73 | 74 | // Allocate the host output vector C 75 | float *h_C = (float *)malloc(size); 76 | 77 | // Verify that allocations succeeded 78 | if (h_A == NULL || h_B == NULL || h_C == NULL) { 79 | fprintf(stderr, "Failed to allocate host vectors!\n"); 80 | exit(EXIT_FAILURE); 81 | } 82 | 83 | // Initialize the host input vectors 84 | for (int i = 0; i < numElements; ++i) { 85 | h_A[i] = rand() / (float)RAND_MAX; 86 | h_B[i] = rand() / (float)RAND_MAX; 87 | } 88 | 89 | // Allocate the device input vector A 90 | float *d_A = NULL; 91 | err = cudaMalloc((void **)&d_A, size); 92 | 93 | if (err != cudaSuccess) { 94 | fprintf(stderr, "Failed to allocate device vector A (error code %s)!\n", 95 | cudaGetErrorString(err)); 96 | exit(EXIT_FAILURE); 97 | } 98 | 99 | // Allocate the device input vector B 100 | float *d_B = NULL; 101 | err = cudaMalloc((void **)&d_B, size); 102 | 103 | if (err != cudaSuccess) { 104 | fprintf(stderr, "Failed to allocate device vector B (error code %s)!\n", 105 | cudaGetErrorString(err)); 106 | exit(EXIT_FAILURE); 107 | } 108 | 109 | // Allocate the device output vector C 110 | float *d_C = NULL; 111 | err = cudaMalloc((void **)&d_C, size); 112 | 113 | if (err != cudaSuccess) { 114 | fprintf(stderr, "Failed to allocate device vector C (error code %s)!\n", 115 | cudaGetErrorString(err)); 116 | exit(EXIT_FAILURE); 117 | } 118 | 119 | // Copy the host input vectors A and B in host memory to the device input 120 | // vectors in 121 | // device memory 122 | printf("Copy input data from the host memory to the CUDA device\n"); 123 | err = cudaMemcpy(d_A, h_A, size, cudaMemcpyHostToDevice); 124 | 125 | if (err != cudaSuccess) { 126 | fprintf(stderr, 127 | "Failed to copy vector A from host to device (error code %s)!\n", 128 | cudaGetErrorString(err)); 129 | exit(EXIT_FAILURE); 130 | } 131 | 132 | err = cudaMemcpy(d_B, h_B, size, cudaMemcpyHostToDevice); 133 | 134 | if (err != cudaSuccess) { 135 | fprintf(stderr, 136 | "Failed to copy vector B from host to device (error code %s)!\n", 137 | cudaGetErrorString(err)); 138 | exit(EXIT_FAILURE); 139 | } 140 | 141 | // Launch the Vector Add CUDA Kernel 142 | int threadsPerBlock = 256; 143 | int blocksPerGrid = (numElements + threadsPerBlock - 1) / threadsPerBlock; 144 | printf("CUDA kernel launch with %d blocks of %d threads\n", blocksPerGrid, 145 | threadsPerBlock); 146 | vectorAdd<<>>(d_A, d_B, d_C, numElements); 147 | err = cudaGetLastError(); 148 | 149 | if (err != cudaSuccess) { 150 | fprintf(stderr, "Failed to launch vectorAdd kernel (error code %s)!\n", 151 | cudaGetErrorString(err)); 152 | exit(EXIT_FAILURE); 153 | } 154 | 155 | // Copy the device result vector in device memory to the host result vector 156 | // in host memory. 157 | printf("Copy output data from the CUDA device to the host memory\n"); 158 | err = cudaMemcpy(h_C, d_C, size, cudaMemcpyDeviceToHost); 159 | 160 | if (err != cudaSuccess) { 161 | fprintf(stderr, 162 | "Failed to copy vector C from device to host (error code %s)!\n", 163 | cudaGetErrorString(err)); 164 | exit(EXIT_FAILURE); 165 | } 166 | 167 | // Verify that the result vector is correct 168 | for (int i = 0; i < numElements; ++i) { 169 | if (fabs(h_A[i] + h_B[i] - h_C[i]) > 1e-5) { 170 | fprintf(stderr, "Result verification failed at element %d!\n", i); 171 | exit(EXIT_FAILURE); 172 | } 173 | } 174 | 175 | printf("Test PASSED\n"); 176 | 177 | // Free device global memory 178 | err = cudaFree(d_A); 179 | 180 | if (err != cudaSuccess) { 181 | fprintf(stderr, "Failed to free device vector A (error code %s)!\n", 182 | cudaGetErrorString(err)); 183 | exit(EXIT_FAILURE); 184 | } 185 | 186 | err = cudaFree(d_B); 187 | 188 | if (err != cudaSuccess) { 189 | fprintf(stderr, "Failed to free device vector B (error code %s)!\n", 190 | cudaGetErrorString(err)); 191 | exit(EXIT_FAILURE); 192 | } 193 | 194 | err = cudaFree(d_C); 195 | 196 | if (err != cudaSuccess) { 197 | fprintf(stderr, "Failed to free device vector C (error code %s)!\n", 198 | cudaGetErrorString(err)); 199 | exit(EXIT_FAILURE); 200 | } 201 | 202 | // Free host memory 203 | free(h_A); 204 | free(h_B); 205 | free(h_C); 206 | 207 | printf("Done\n"); 208 | return 0; 209 | } -------------------------------------------------------------------------------- /example/main.ll: -------------------------------------------------------------------------------- 1 | ; ModuleID = 'main.bc' 2 | source_filename = "main.c" 3 | target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" 4 | target triple = "x86_64-pc-linux-gnu" 5 | 6 | @.str = private unnamed_addr constant [15 x i8] c"Hello, world!\0A\00", align 1 7 | 8 | ; Function Attrs: noinline nounwind optnone sspstrong uwtable 9 | define dso_local i32 @main(i32 noundef %0, i8** noundef %1) #0 !dbg !10 { 10 | %3 = alloca i32, align 4 11 | %4 = alloca i32, align 4 12 | %5 = alloca i8**, align 8 13 | store i32 0, i32* %3, align 4 14 | store i32 %0, i32* %4, align 4 15 | call void @llvm.dbg.declare(metadata i32* %4, metadata !19, metadata !DIExpression()), !dbg !20 16 | store i8** %1, i8*** %5, align 8 17 | call void @llvm.dbg.declare(metadata i8*** %5, metadata !21, metadata !DIExpression()), !dbg !22 18 | %6 = call i32 (i8*, ...) @printf(i8* noundef getelementptr inbounds ([15 x i8], [15 x i8]* @.str, i64 0, i64 0)), !dbg !23 19 | ret i32 0, !dbg !24 20 | } 21 | 22 | ; Function Attrs: nofree nosync nounwind readnone speculatable willreturn 23 | declare void @llvm.dbg.declare(metadata, metadata, metadata) #1 24 | 25 | declare i32 @printf(i8* noundef, ...) #2 26 | 27 | attributes #0 = { noinline nounwind optnone sspstrong uwtable "frame-pointer"="all" "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } 28 | attributes #1 = { nofree nosync nounwind readnone speculatable willreturn } 29 | attributes #2 = { "frame-pointer"="all" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } 30 | 31 | !llvm.dbg.cu = !{!0} 32 | !llvm.module.flags = !{!2, !3, !4, !5, !6, !7, !8} 33 | !llvm.ident = !{!9} 34 | 35 | !0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, producer: "clang version 14.0.6", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None) 36 | !1 = !DIFile(filename: "main.c", directory: "/home/sxf/Workspace/vscode-llvm/example", checksumkind: CSK_MD5, checksum: "fe57099e146a3c928599d8d8652044a2") 37 | !2 = !{i32 7, !"Dwarf Version", i32 5} 38 | !3 = !{i32 2, !"Debug Info Version", i32 3} 39 | !4 = !{i32 1, !"wchar_size", i32 4} 40 | !5 = !{i32 7, !"PIC Level", i32 2} 41 | !6 = !{i32 7, !"PIE Level", i32 2} 42 | !7 = !{i32 7, !"uwtable", i32 1} 43 | !8 = !{i32 7, !"frame-pointer", i32 2} 44 | !9 = !{!"clang version 14.0.6"} 45 | !10 = distinct !DISubprogram(name: "main", scope: !11, file: !11, line: 3, type: !12, scopeLine: 3, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !18) 46 | !11 = !DIFile(filename: "main.c", directory: "/home/sxf/Workspace/vscode-llvm/example") 47 | !12 = !DISubroutineType(types: !13) 48 | !13 = !{!14, !14, !15} 49 | !14 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) 50 | !15 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !16, size: 64) 51 | !16 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !17, size: 64) 52 | !17 = !DIBasicType(name: "char", size: 8, encoding: DW_ATE_signed_char) 53 | !18 = !{} 54 | !19 = !DILocalVariable(name: "argc", arg: 1, scope: !10, file: !11, line: 3, type: !14) 55 | !20 = !DILocation(line: 3, column: 14, scope: !10) 56 | !21 = !DILocalVariable(name: "argv", arg: 2, scope: !10, file: !11, line: 3, type: !15) 57 | !22 = !DILocation(line: 3, column: 26, scope: !10) 58 | !23 = !DILocation(line: 4, column: 5, scope: !10) 59 | !24 = !DILocation(line: 5, column: 5, scope: !10) 60 | -------------------------------------------------------------------------------- /example/test/main.bc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunxfancy/vscode-llvm/2d6218755b450888b80fd9831d7e200325ff5d96/example/test/main.bc -------------------------------------------------------------------------------- /example/test/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main(int argc, char *argv[]) { 4 | printf("Hello\n"); 5 | printf("World\n"); 6 | return 0; 7 | } -------------------------------------------------------------------------------- /image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunxfancy/vscode-llvm/2d6218755b450888b80fd9831d7e200325ff5d96/image.png -------------------------------------------------------------------------------- /media/main.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: transparent; 3 | } 4 | 5 | .command-list { 6 | list-style: none; 7 | padding: 0; 8 | } 9 | 10 | .command-entry { 11 | width: 100%; 12 | display: flex; 13 | margin-bottom: 0.4em; 14 | border: 1px solid var(--vscode-input-border); 15 | } 16 | 17 | .command-checkbox { 18 | width: 1em; 19 | height: 1em; 20 | align-self: center; 21 | } 22 | 23 | .command-label { 24 | flex: 1; 25 | } 26 | 27 | .command-label:hover{ 28 | color: var(--vscode-textLink-activeForeground); 29 | } 30 | 31 | .command-label:active{ 32 | background-color: var(--vscode-input-background); 33 | } 34 | 35 | .command-input { 36 | display: block; 37 | flex: 1; 38 | width: 100%; 39 | color: var(--vscode-input-foreground); 40 | background-color: var(--vscode-input-background); 41 | border: none; 42 | padding: 0 0.6em; 43 | } 44 | 45 | .btn-container { 46 | display: flex; 47 | } 48 | 49 | .config-btn { 50 | display: block; 51 | border: none; 52 | width: 33.33333333333%; 53 | } -------------------------------------------------------------------------------- /media/main.js: -------------------------------------------------------------------------------- 1 | // This script will be run within the webview itself 2 | // It cannot access the main VS Code APIs directly. 3 | (function () { 4 | const vscode = acquireVsCodeApi(); 5 | 6 | const oldState = vscode.getState() || { configs: [], filter: '' }; 7 | 8 | /** @type {Array<{ value: string }>} */ 9 | let configs = oldState.configs || []; 10 | let filter = oldState.filter; 11 | 12 | updateCommandList(configs); 13 | 14 | document.querySelector('#add-config-button').addEventListener('click', () => { 15 | addConfig(); 16 | }); 17 | 18 | document.querySelector('#delete-config-button').addEventListener('click', () => { 19 | deleteConfig(); 20 | }); 21 | 22 | document.querySelector('#compare-button').addEventListener('click', () => { 23 | runCompare(); 24 | }); 25 | 26 | let text_box = document.querySelector('.filter-text'); 27 | text_box.value = filter; 28 | text_box.addEventListener('keyup', (e) => { 29 | filter = text_box.value; 30 | vscode.postMessage({ type: 'update-filter', value: filter }); 31 | vscode.setState({ configs: configs, filter: filter }); 32 | }); 33 | 34 | // Handle messages sent from the extension to the webview 35 | window.addEventListener('message', event => { 36 | const message = event.data; // The json data that the extension sent 37 | switch (message.type) { 38 | case 'addColor': { 39 | addConfig(); 40 | break; 41 | } 42 | case 'reloadConfig': { 43 | configs = []; 44 | updateCommandList(configs); 45 | break; 46 | } 47 | case 'addConfig': { 48 | configs.push({ value: message.value }); 49 | updateCommandList(configs); 50 | break; 51 | } 52 | } 53 | }); 54 | 55 | function createEditableLable(li, config, colors) { 56 | // here we create the checkbox 57 | const checkbox = document.createElement('input'); 58 | checkbox.type = 'checkbox'; 59 | checkbox.className = 'command-checkbox'; 60 | li.appendChild(checkbox); 61 | 62 | const label = document.createElement('label'); 63 | const input = document.createElement('input'); 64 | 65 | label.className = 'command-label'; 66 | label.textContent = config.value; 67 | label.addEventListener('click', () => { 68 | // here we open the pipeline 69 | vscode.postMessage({ type: 'select', value: label.textContent }); 70 | }); 71 | 72 | label.addEventListener('dblclick', () => { 73 | // edit the command 74 | label.style.display = "none"; 75 | input.style.display = "block"; 76 | }); 77 | 78 | li.appendChild(label); 79 | 80 | input.className = 'command-input'; 81 | input.type = 'text'; 82 | input.value = config.value; 83 | input.style.display = "none"; 84 | input.addEventListener('keypress', (e) => { 85 | if (e.key === 'Enter') { 86 | // here we save the command 87 | label.style.display = "block"; 88 | input.style.display = "none"; 89 | label.textContent = config.value = input.value; 90 | } 91 | }); 92 | 93 | li.appendChild(input); 94 | } 95 | 96 | /** 97 | * @param {Array<{ value: string }>} configs 98 | */ 99 | function updateCommandList(configs) { 100 | if (configs === undefined) { 101 | vscode.setState({ configs: [] }); 102 | return; 103 | } 104 | console.log(configs); 105 | const ul = document.querySelector('.command-list'); 106 | ul.textContent = ''; 107 | for (const config of configs) { 108 | const li = document.createElement('li'); 109 | li.className = 'command-entry'; 110 | 111 | createEditableLable(li, config, configs); 112 | ul.appendChild(li); 113 | } 114 | 115 | // Update the saved state 116 | vscode.setState({ configs: configs, filter: filter }); 117 | } 118 | 119 | /** 120 | * @param {string} color 121 | */ 122 | function onRunClicked(id) { 123 | vscode.postMessage({ type: 'run', value: id }); 124 | } 125 | 126 | function onDebugClicked(id) { 127 | vscode.postMessage({ type: 'debug', value: id }); 128 | } 129 | 130 | function onSelectionChanged(id) { 131 | vscode.postMessage({ type: 'selection', value: id }); 132 | } 133 | 134 | function addConfig() { 135 | vscode.postMessage({ type: 'new' }); 136 | } 137 | 138 | function deleteConfig() { 139 | const checkboxes = document.querySelectorAll('.command-checkbox'); 140 | for (let i = checkboxes.length-1; i >= 0; i--) { 141 | if (checkboxes[i].checked) { 142 | configs.splice(i, 1); 143 | } 144 | } 145 | updateCommandList(configs); 146 | } 147 | 148 | function runCompare() { 149 | let new_configs = []; 150 | const checkboxes = document.querySelectorAll('.command-checkbox'); 151 | for (let i = checkboxes.length-1; i >= 0; i--) { 152 | if (checkboxes[i].checked) { 153 | new_configs.push(configs[i]); 154 | } 155 | } 156 | 157 | vscode.postMessage({ type: 'compare', value: new_configs }); 158 | } 159 | }()); 160 | 161 | -------------------------------------------------------------------------------- /media/reset.css: -------------------------------------------------------------------------------- 1 | html { 2 | box-sizing: border-box; 3 | font-size: 13px; 4 | } 5 | 6 | *, 7 | *:before, 8 | *:after { 9 | box-sizing: inherit; 10 | } 11 | 12 | body, 13 | h1, 14 | h2, 15 | h3, 16 | h4, 17 | h5, 18 | h6, 19 | p, 20 | ol, 21 | ul { 22 | margin: 0; 23 | padding: 0; 24 | font-weight: normal; 25 | } 26 | 27 | img { 28 | max-width: 100%; 29 | height: auto; 30 | } -------------------------------------------------------------------------------- /media/vscode.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --container-paddding: 20px; 3 | --input-padding-vertical: 6px; 4 | --input-padding-horizontal: 4px; 5 | --input-margin-vertical: 4px; 6 | --input-margin-horizontal: 0; 7 | } 8 | 9 | body { 10 | padding: 0 var(--container-paddding); 11 | color: var(--vscode-foreground); 12 | font-size: var(--vscode-font-size); 13 | font-weight: var(--vscode-font-weight); 14 | font-family: var(--vscode-font-family); 15 | background-color: var(--vscode-editor-background); 16 | } 17 | 18 | ol, 19 | ul { 20 | padding-left: var(--container-paddding); 21 | } 22 | 23 | body > *, 24 | form > * { 25 | margin-block-start: var(--input-margin-vertical); 26 | margin-block-end: var(--input-margin-vertical); 27 | } 28 | 29 | *:focus { 30 | outline-color: var(--vscode-focusBorder) !important; 31 | } 32 | 33 | a { 34 | color: var(--vscode-textLink-foreground); 35 | } 36 | 37 | a:hover, 38 | a:active { 39 | color: var(--vscode-textLink-activeForeground); 40 | } 41 | 42 | code { 43 | font-size: var(--vscode-editor-font-size); 44 | font-family: var(--vscode-editor-font-family); 45 | } 46 | 47 | button { 48 | border: none; 49 | padding: var(--input-padding-vertical) var(--input-padding-horizontal); 50 | width: 100%; 51 | text-align: center; 52 | outline: 1px solid transparent; 53 | outline-offset: 2px !important; 54 | color: var(--vscode-button-foreground); 55 | background: var(--vscode-button-background); 56 | } 57 | 58 | button:hover { 59 | cursor: pointer; 60 | background: var(--vscode-button-hoverBackground); 61 | } 62 | 63 | button:focus { 64 | outline-color: var(--vscode-focusBorder); 65 | } 66 | 67 | button.secondary { 68 | color: var(--vscode-button-secondaryForeground); 69 | background: var(--vscode-button-secondaryBackground); 70 | } 71 | 72 | button.secondary:hover { 73 | background: var(--vscode-button-secondaryHoverBackground); 74 | } 75 | 76 | input:not([type='checkbox']), 77 | textarea { 78 | display: block; 79 | width: 100%; 80 | border: none; 81 | font-family: var(--vscode-font-family); 82 | padding: var(--input-padding-vertical) var(--input-padding-horizontal); 83 | color: var(--vscode-input-foreground); 84 | outline-color: var(--vscode-input-border); 85 | background-color: var(--vscode-input-background); 86 | } 87 | 88 | input::placeholder, 89 | textarea::placeholder { 90 | color: var(--vscode-input-placeholderForeground); 91 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vscode-llvm", 3 | "displayName": "VSCode LLVM Compiler Explorer", 4 | "description": "A tool for LLVM pass developer", 5 | "version": "0.4.2", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/sunxfancy/vscode-llvm.git" 9 | }, 10 | "publisher": "XiaofanSun", 11 | "engines": { 12 | "vscode": "^1.69.0" 13 | }, 14 | "categories": [ 15 | "Other" 16 | ], 17 | "activationEvents": [ 18 | "onView:llvm-control", 19 | "onView:llvm-pipeline-view", 20 | "onView:llvm-pass-view" 21 | ], 22 | "main": "./dist/extension.js", 23 | "contributes": { 24 | "languages": [ 25 | { 26 | "id": "vscode-llvm.disassembly", 27 | "aliases": [ 28 | "Disassembly" 29 | ] 30 | }, 31 | { 32 | "id": "vscode-llvm.llvm-ast", 33 | "aliases": [ 34 | "LLVM AST" 35 | ] 36 | } 37 | ], 38 | "grammars": [ 39 | { 40 | "language": "vscode-llvm.disassembly", 41 | "scopeName": "source.disassembly", 42 | "path": "./resources/disassembly.json" 43 | }, 44 | { 45 | "language": "vscode-llvm.llvm-ast", 46 | "scopeName": "source.llvm-ast", 47 | "path": "./resources/llvm-ast.json" 48 | } 49 | ], 50 | "commands": [ 51 | { 52 | "command": "llvmPipelineView.settings", 53 | "title": "LLVM: Pipeline View Settings", 54 | "icon": "$(gear)" 55 | }, 56 | { 57 | "command": "llvmPipelineView.run", 58 | "title": "LLVM: Pipeline Run Compiliation", 59 | "icon": "$(run)" 60 | }, 61 | { 62 | "command": "llvmPipelineView.run-debug", 63 | "title": "LLVM: Pipeline Debug Compiliation", 64 | "icon": "$(bug)" 65 | }, 66 | { 67 | "command": "llvmPipelineView.openPreprocessed", 68 | "title": "LLVM: Open Preprocessed File" 69 | }, 70 | { 71 | "command": "llvmPipelineView.openAST", 72 | "title": "LLVM: Open AST" 73 | }, 74 | { 75 | "command": "llvmPipelineView.openLLVM", 76 | "title": "LLVM: Open LLVM IR" 77 | } 78 | ], 79 | "viewsContainers": { 80 | "activitybar": [ 81 | { 82 | "id": "vscode-llvm", 83 | "title": "LLVM", 84 | "icon": "resources/LLVM.svg" 85 | } 86 | ] 87 | }, 88 | "views": { 89 | "vscode-llvm": [ 90 | { 91 | "type": "webview", 92 | "id": "llvm-control", 93 | "name": "Compiler Configuration" 94 | }, 95 | { 96 | "id": "llvm-pipeline-view", 97 | "name": "LLVM Pipeline" 98 | }, 99 | { 100 | "id": "llvm-pass-view", 101 | "name": "LLVM Available Passes" 102 | } 103 | ] 104 | }, 105 | "menus": { 106 | "view/title": [ 107 | { 108 | "command": "llvmPipelineView.run", 109 | "when": "view == llvm-pipeline-view", 110 | "group": "navigation" 111 | }, 112 | { 113 | "command": "llvmPipelineView.run-debug", 114 | "when": "view == llvm-pipeline-view", 115 | "group": "navigation" 116 | }, 117 | { 118 | "command": "llvmPipelineView.settings", 119 | "when": "view == llvm-pipeline-view", 120 | "group": "navigation" 121 | } 122 | ] 123 | }, 124 | "configuration": [ 125 | { 126 | "title": "LLVM Compiler Explorer", 127 | "properties": { 128 | "vscode-llvm.optPath": { 129 | "type": "string", 130 | "default": "opt", 131 | "description": "Path of 'opt' binary" 132 | }, 133 | "vscode-llvm.dimUnusedSourceLines": { 134 | "type": "boolean", 135 | "default": true, 136 | "description": "Dim the lines that was thrown away by compiler", 137 | "scope": "resource" 138 | } 139 | } 140 | } 141 | ] 142 | }, 143 | "scripts": { 144 | "vscode:prepublish": "yarn run package", 145 | "compile": "webpack", 146 | "watch": "webpack --watch", 147 | "package": "webpack --mode production --devtool hidden-source-map", 148 | "compile-tests": "tsc -p . --outDir out", 149 | "watch-tests": "tsc -p . -w --outDir out", 150 | "pretest": "yarn run compile-tests && yarn run compile && yarn run lint", 151 | "lint": "eslint src --ext ts", 152 | "test": "yarn run runtest", 153 | "runtest": "node ./out/test/runTest.js" 154 | }, 155 | "devDependencies": { 156 | "@types/glob": "^7.2.0", 157 | "@types/mocha": "^9.1.1", 158 | "@types/node": "16.x", 159 | "@types/vscode": "^1.69.0", 160 | "@types/which": "^2.0.1", 161 | "@typescript-eslint/eslint-plugin": "^5.31.0", 162 | "@typescript-eslint/parser": "^5.31.0", 163 | "@vscode/test-electron": "^2.1.5", 164 | "eslint": "^8.20.0", 165 | "glob": "^8.0.3", 166 | "mocha": "^10.0.0", 167 | "ts-loader": "^9.3.1", 168 | "typescript": "^4.7.4", 169 | "webpack": "^5.89.0", 170 | "webpack-cli": "^4.10.0" 171 | }, 172 | "dependencies": { 173 | "which": "^2.0.2" 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /resources/LLVM.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/disassembly.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "VSCodeLLVM Disassembly", 3 | "scopeName": "source.disassembly", 4 | "patterns": [ 5 | { 6 | "comment": "Address, bytes and opcode", 7 | "name": "meta.instruction", 8 | "match": "^([A-Za-z0-9]+):\\s([A-Z0-9]{2}\\s)+>?\\s+(\\w+)", 9 | "captures": { 10 | "1": { "name": "constant.numeric" }, 11 | "3": { "name": "keyword.opcode" } 12 | } 13 | }, 14 | { 15 | "comment": "Numeric constant", 16 | "name": "constant.numeric", 17 | "match": "(\\$|\\b)((0x)|[0-9])[A-Za-z0-9]+\\b" 18 | }, 19 | { 20 | "comment": "Register", 21 | "name": "variable.language", 22 | "match": "%[A-Za-z][A-Za-z0-9]*" 23 | }, 24 | { 25 | "comment": "End of line comment", 26 | "name": "comment.line.semicolon", 27 | "match": ";.*$" 28 | } 29 | ] 30 | } -------------------------------------------------------------------------------- /resources/llvm-ast.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "LLVM AST", 3 | "scopeName": "source.llvm-ast", 4 | "patterns": [ 5 | { 6 | "comment": "Numeric constant", 7 | "name": "constant.numeric", 8 | "match": "(\\$|\\b)((0x)|[0-9])[A-Za-z0-9]+\\b" 9 | }, 10 | { 11 | "comment": "Declaration", 12 | "name": "entity.name", 13 | "match": "[A-Za-z]+Decl" 14 | }, 15 | { 16 | "comment": "Type define", 17 | "name": "support.type", 18 | "match": "[A-Za-z]+Type" 19 | }, 20 | { 21 | "comment": "C keywords", 22 | "name": "keyword.control", 23 | "match": "struct|union|enum|typedef|auto|register|true|false|inline|__inline|__inline__|__restrict|__restrict__|__attribute__|__asm__|__asm|__typeof__|__typeof" 24 | }, 25 | { 26 | "comment": "C Types", 27 | "name": "storage.type", 28 | "match": "int|char|short|long|float|double|signed|unsigned|void|bool|complex|wint_t|__time64_t|uintptr_t|__int128|__uint128_t|imaginary|_Bool|_Complex|_Imaginary|_Bool|_Complex|_Imaginary" 29 | }, 30 | { 31 | "comment": "C Modifiers", 32 | "name": "storage.modifier", 33 | "match": "const|volatile|extern|static|__inline__|__inline|__extension__|__extension|__const__|__const|__volatile__|__volatile|__signed__|__signed" 34 | }, 35 | { 36 | "comment": "Names", 37 | "name": "variable.parameter", 38 | "match": "[A-Za-z_]+" 39 | } 40 | ] 41 | } -------------------------------------------------------------------------------- /src/asm.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, Compiler Explorer Authors 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are met: 6 | // 7 | // * Redistributions of source code must retain the above copyright notice, 8 | // this list of conditions and the following disclaimer. 9 | // * Redistributions in binary form must reproduce the above copyright 10 | // notice, this list of conditions and the following disclaimer in the 11 | // documentation and/or other materials provided with the distribution. 12 | // 13 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 17 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 19 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 20 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 21 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 22 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 23 | // POSSIBILITY OF SUCH DAMAGE. 24 | 25 | // Following code is a bit differs from Compiler Explorer's version 26 | // to better fit needs of Disassembly Explorer 27 | 28 | import { splitLines, expandTabs, squashHorizontalWhitespace } from './utils'; 29 | 30 | export class AsmFilter { 31 | trim = true; 32 | binary = false; 33 | commentOnly = false; 34 | directives = true; 35 | labels = true; 36 | } 37 | 38 | export class AsmSource { 39 | file: string | undefined; 40 | line: number; 41 | column: number; 42 | 43 | constructor(file: string | undefined, line: number) { 44 | this.file = file; 45 | this.line = line; 46 | this.column = 0; 47 | } 48 | } 49 | 50 | export class AsmLabelRange { 51 | startCol: number; 52 | endCol: number; 53 | 54 | constructor(startCol: number, endCol: number) { 55 | this.startCol = startCol; 56 | this.endCol = endCol; 57 | } 58 | } 59 | 60 | export class AsmLabel { 61 | name: string; 62 | range: AsmLabelRange; 63 | 64 | constructor(name: string, range: AsmLabelRange) { 65 | this.name = name; 66 | this.range = range; 67 | } 68 | } 69 | 70 | export class AsmLine { 71 | text: string; 72 | source: AsmSource | undefined; 73 | labels: AsmLabel[]; 74 | 75 | constructor(text: string, source: AsmSource | undefined, labels: AsmLabel[]) { 76 | this.text = text; 77 | this.source = source; 78 | this.labels = labels; 79 | } 80 | 81 | get value(): string { 82 | return this.text + '\n'; 83 | } 84 | } 85 | 86 | export class BinaryAsmLine extends AsmLine { 87 | address: number; 88 | opcodes: string; 89 | 90 | constructor(text: string, source: AsmSource | undefined, labels: AsmLabel[], address: number, opcodes: string) { 91 | super(text, source, labels); 92 | this.address = address; 93 | this.opcodes = opcodes; 94 | } 95 | 96 | get value(): string { 97 | const address = ('0000000' + this.address.toString(16)).substr(-8); 98 | return `<${address}> ${this.text}\n`; 99 | } 100 | } 101 | 102 | export class AsmParserResult { 103 | asm: AsmLine[]; 104 | labelDefinitions: Map; 105 | 106 | constructor(asm: AsmLine[], labelDefinitions: Map) { 107 | this.asm = asm; 108 | this.labelDefinitions = labelDefinitions; 109 | } 110 | } 111 | 112 | export class AsmParser { 113 | 114 | labelDef = /^(?:.proc\s+)?([.a-z_$@][a-z0-9$_@.]*):/i; 115 | 116 | labelFindNonMips = /[.A-Z_a-z][\w$.]*/g; 117 | // MIPS labels can start with a $ sign, but other assemblers use $ to mean literal. 118 | labelFindMips = /[$.A-Z_a-z][\w$.]*/g; 119 | mipsLabelDefinition = /^\$[\w$.]+:/; 120 | dataDefn = /^\s*\.(string|asciz|ascii|[1248]?byte|short|x?word|long|quad|value|zero)/; 121 | fileFind = /^\s*\.file\s+(\d+)\s+"([^"]+)"(\s+"([^"]+)")?.*/; 122 | cvfileFind = /^\s*\.cv_file\s+(\d+)\s+"([^"]+)"(\s+"([^"]+)")?.*/; 123 | // Opcode expression here matches LLVM-style opcodes of the form `%blah = opcode` 124 | hasOpcodeRe = /^\s*(%[$.A-Z_a-z][\w$.]*\s*=\s*)?[A-Za-z]/; 125 | instructionRe = /^\s*[A-Za-z]+/; 126 | identifierFindRe = /[$.@A-Z_a-z][\dA-z]*/g; 127 | hasNvccOpcodeRe = /^\s*[@A-Za-z|]/; 128 | definesFunction = /^\s*\.(type.*,\s*[#%@]function|proc\s+[.A-Z_a-z][\w$.]*:.*)$/; 129 | definesGlobal = /^\s*\.(?:globa?l|GLB|export)\s*([.A-Z_a-z][\w$.]*)/; 130 | definesWeak = /^\s*\.(?:weakext|weak)\s*([.A-Z_a-z][\w$.]*)/; 131 | indentedLabelDef = /^\s*([$.A-Z_a-z][\w$.]*):/; 132 | assignmentDef = /^\s*([$.A-Z_a-z][\w$.]*)\s*=/; 133 | directive = /^\s*\..*$/; 134 | startAppBlock = /\s*#APP.*/; 135 | endAppBlock = /\s*#NO_APP.*/; 136 | startAsmNesting = /\s*# Begin ASM.*/; 137 | endAsmNesting = /\s*# End ASM.*/; 138 | cudaBeginDef = /\.(entry|func)\s+(?:\([^)]*\)\s*)?([$.A-Z_a-z][\w$.]*)\($/; 139 | cudaEndDef = /^\s*\)\s*$/; 140 | 141 | asmOpcodeRe = /^\s*(?
[\da-f]+):(?:[|\\\/>X+-]|\s)*(?([\da-f]{2} ?)+)\s*(?.*)/; 142 | lineRe = /^(\/[^:]+):(?\d+).*/; 143 | 144 | // labelRe is made very greedy as it's also used with demangled objdump 145 | // output (eg. it can have c++ template with <>). 146 | labelRe = /^([\da-f]+)\s+<(.+)>:$/; 147 | destRe = /\s([\da-f]+)\s+<([^+>]+)(\+0x[\da-f]+)?>$/; 148 | commentRe = /[#;]/; 149 | instOpcodeRe = /(\.inst\.?\w?)\s*(.*)/; 150 | 151 | binaryHideFuncRe: RegExp | undefined; 152 | 153 | private hasOpcode(line: string, inNvccCode: boolean) { 154 | // Remove any leading label definition... 155 | const match = line.match(this.labelDef); 156 | if (match) { 157 | line = line.substr(match[0].length); 158 | } 159 | // Strip any comments 160 | line = line.split(this.commentRe, 1)[0]; 161 | // .inst generates an opcode, so also counts 162 | if (this.instOpcodeRe.test(line)) { 163 | return true; 164 | } 165 | // Detect assignment, that's not an opcode... 166 | if (this.assignmentDef.test(line)) { 167 | return false; 168 | } 169 | if (inNvccCode) { 170 | return !!this.hasNvccOpcodeRe.test(line); 171 | } 172 | return !!this.hasOpcodeRe.test(line); 173 | } 174 | 175 | private filterAsmLine(line: string, filter: AsmFilter) { 176 | if (!filter.trim) { 177 | return line; 178 | } 179 | return squashHorizontalWhitespace(line, true); 180 | } 181 | 182 | private labelFindFor(asmLines: string[]) { 183 | const isMips = asmLines.some(line => { 184 | return !!this.mipsLabelDefinition.test(line); 185 | }); 186 | return isMips ? this.labelFindMips : this.labelFindNonMips; 187 | } 188 | 189 | private findUsedLabels(asmLines: string[], filterDirectives: boolean) { 190 | const labelsUsed = new Set(); 191 | const weakUsages = new Map(); 192 | const labelFind = this.labelFindFor(asmLines); 193 | // The current label set is the set of labels all pointing at the current code, so: 194 | // foo: 195 | // bar: 196 | // add r0, r0, #1 197 | // in this case [foo, bar] would be the label set for the add instruction. 198 | let currentLabelSet = new Array(); 199 | let inLabelGroup = false; 200 | let inCustomAssembly = 0; 201 | const startBlock = /\.cfi_startproc/; 202 | const endBlock = /\.cfi_endproc/; 203 | let inFunction = false; 204 | 205 | // Scan through looking for definite label usages (ones used by opcodes), 206 | // and ones that are weakly used: that is, their use is conditional on another label. 207 | // For example: 208 | // .foo: .string "moo" 209 | // .baz: .quad .foo 210 | // mov eax, .baz 211 | // In this case, the '.baz' is used by an opcode, and so is strongly used. 212 | // The '.foo' is weakly used by .baz. 213 | // Also, if we have random data definitions within a block of a function (between 214 | // cfi_startproc and cfi_endproc), we assume they are strong usages. This covers things 215 | // like jump tables embedded in ARM code. 216 | // See https://github.com/compiler-explorer/compiler-explorer/issues/2788 217 | for (let line of asmLines) { 218 | if (this.startAppBlock.test(line) || this.startAsmNesting.test(line)) { 219 | inCustomAssembly++; 220 | } else if (this.endAppBlock.test(line) || this.endAsmNesting.test(line)) { 221 | inCustomAssembly--; 222 | } else if (startBlock.test(line)) { 223 | inFunction = true; 224 | } else if (endBlock.test(line)) { 225 | inFunction = false; 226 | } 227 | 228 | if (inCustomAssembly > 0) { 229 | line = this.fixLabelIndentation(line); 230 | } 231 | 232 | let match = line.match(this.labelDef); 233 | if (match) { 234 | if (inLabelGroup) { 235 | currentLabelSet.push(match[1]); 236 | } else { 237 | currentLabelSet = [match[1]]; 238 | } 239 | inLabelGroup = true; 240 | } else { 241 | inLabelGroup = false; 242 | } 243 | 244 | match = line.match(this.definesGlobal); 245 | if (!match) { 246 | match = line.match(this.definesWeak); 247 | } 248 | if (!match) { 249 | match = line.match(this.cudaBeginDef); 250 | } 251 | if (match) { 252 | labelsUsed.add(match[1]); 253 | } 254 | 255 | const definesFunction = line.match(this.definesFunction); 256 | if (!definesFunction && (!line || line[0] === '.')) { 257 | continue; 258 | } 259 | 260 | match = line.match(labelFind); 261 | if (!match) { 262 | continue; 263 | } 264 | 265 | if (!filterDirectives || this.hasOpcode(line, false) || definesFunction) { 266 | // Only count a label as used if it's used by an opcode, or else we're not filtering directives. 267 | for (const label of match) 268 | labelsUsed.add(label); 269 | } else { 270 | // If we have a current label, then any subsequent opcode or data definition's labels are referred to 271 | // weakly by that label. 272 | const isDataDefinition = !!this.dataDefn.test(line); 273 | const isOpcode = this.hasOpcode(line, false); 274 | if (isDataDefinition || isOpcode) { 275 | for (const currentLabel of currentLabelSet) { 276 | if (inFunction && isDataDefinition) { 277 | // Data definitions in the middle of code should be treated as if they were used strongly. 278 | for (const label of match) 279 | labelsUsed.add(label); 280 | } else { 281 | if (weakUsages.get(currentLabel) === undefined) { 282 | weakUsages.set(currentLabel, []); 283 | } 284 | for (const label of match) 285 | weakUsages.get(currentLabel)!.push(label); 286 | } 287 | } 288 | } 289 | } 290 | } 291 | 292 | // Now follow the chains of used labels, marking any weak references they refer 293 | // to as also used. We iteratively do this until either no new labels are found, 294 | // or we hit a limit (only here to prevent a pathological case from hanging). 295 | const MaxLabelIterations = 10; 296 | for (let iter = 0; iter < MaxLabelIterations; ++iter) { 297 | const toAdd: string[] = []; 298 | 299 | labelsUsed.forEach(label => { 300 | const labelWeakUsages = weakUsages.get(label); 301 | if (labelWeakUsages === undefined) { 302 | return; 303 | } 304 | labelWeakUsages.forEach(nowused => { 305 | if (labelsUsed.has(nowused)) { 306 | return; 307 | } 308 | toAdd.push(nowused); 309 | }); 310 | }); 311 | if (!toAdd) { 312 | break; 313 | } 314 | 315 | toAdd.forEach(label => { 316 | labelsUsed.add(label); 317 | }); 318 | } 319 | return labelsUsed; 320 | } 321 | 322 | private parseFiles(asmLines: string[]) { 323 | const files = new Map(); 324 | 325 | for (const line of asmLines) { 326 | const match = line.match(this.fileFind); 327 | if (match) { 328 | const lineNum = parseInt(match[1]); 329 | if (match[4]) { 330 | // Clang-style file directive '.file X "dir" "filename"' 331 | files.set(lineNum, match[2] + '/' + match[4]); 332 | } else { 333 | files.set(lineNum, match[2]); 334 | } 335 | continue; 336 | } 337 | 338 | const cv_match = line.match(this.cvfileFind); 339 | if (cv_match) { 340 | const lineNum = parseInt(cv_match[1]); 341 | // Clang-style file directive '.cv_file "filename"' 342 | files.set(lineNum, cv_match[2]); 343 | } 344 | } 345 | 346 | return files; 347 | } 348 | 349 | // Remove labels which do not have a definition. 350 | private removeLabelsWithoutDefinition(astResult: AsmLine[], labelDefinitions: Map) { 351 | astResult.forEach(obj => { 352 | obj.labels = obj.labels.filter(label => labelDefinitions.get(label.name)); 353 | }); 354 | } 355 | 356 | // Get labels which are used in the given line. 357 | private getUsedLabelsInLine(line: string) { 358 | const labelsInLine: AsmLabel[] = []; 359 | 360 | // Strip any comments 361 | const instruction = line.split(this.commentRe, 1)[0]; 362 | 363 | // Remove the instruction. 364 | const params = instruction.replace(this.instructionRe, ''); 365 | 366 | const removedCol = instruction.length - params.length + 1; 367 | params.replace(this.identifierFindRe, (label, index) => { 368 | const startCol = removedCol + index; 369 | labelsInLine.push(new AsmLabel( 370 | label, 371 | new AsmLabelRange(startCol, startCol + label.length) 372 | )); 373 | return ''; 374 | }); 375 | 376 | return labelsInLine; 377 | } 378 | 379 | private processAsm(asmResult: string, filter: AsmFilter) { 380 | if (filter.commentOnly) { 381 | // Remove any block comments that start and end on a line if we're removing comment-only lines. 382 | const blockComments = /^[\t ]*\/\*(\*(?!\/)|[^*])*\*\/\s*/gm; 383 | asmResult = asmResult.replace(blockComments, ''); 384 | } 385 | 386 | const asm: AsmLine[] = []; 387 | const labelDefinitions = new Map(); 388 | const asmLines = splitLines(asmResult); 389 | 390 | const labelsUsed = this.findUsedLabels(asmLines, filter.directives); 391 | const files = this.parseFiles(asmLines); 392 | let prevLabel: string | undefined = ''; 393 | 394 | // Lines matching the following pattern are considered comments: 395 | // - starts with '#', '@', '//' or a single ';' (non repeated) 396 | // - starts with ';;' and the first non-whitespace before end of line is not # 397 | const commentOnly = /^\s*(((#|@|\/\/).*)|(\/\*.*\*\/)|(;\s*)|(;[^;].*)|(;;\s*[^\s#].*))$/; 398 | 399 | const commentOnlyNvcc = /^\s*(((#|;|\/\/).*)|(\/\*.*\*\/))$/; 400 | const sourceTag = /^\s*\.loc\s+(\d+)\s+(\d+)\s+(.*)/; 401 | const sourceD2Tag = /^\s*\.d2line\s+(\d+),?\s*(\d*).*/; 402 | const sourceCvTag = /^\s*\.cv_loc\s+(\d+)\s+(\d+)(\s+(\d+))?(\s+(\d+))?(.*)/; 403 | const source6502Dbg = /^\s*\.dbg\s+line,\s*"([^"]+)",\s*(\d+)/; 404 | const source6502DbgEnd = /^\s*\.dbg\s+line[^,]/; 405 | const sourceStab = /^\s*\.stabn\s+(\d+),0,(\d+),.*/; 406 | const stdInLooking = /.*|^-$|example\.[^/]+$|/; 407 | const endBlock = /\.(cfi_endproc|data|text|section)/; 408 | let source: AsmSource | undefined; 409 | 410 | function maybeAddBlank() { 411 | const lastBlank = asm.length === 0 || asm[asm.length - 1].text === ''; 412 | if (!lastBlank) { 413 | asm.push(new AsmLine('', undefined, [])); 414 | } 415 | } 416 | 417 | function handleSource(line: string) { 418 | let match = line.match(sourceTag); 419 | if (match) { 420 | const file = files.get(parseInt(match[1])); 421 | const sourceLine = parseInt(match[2]); 422 | if (file) { 423 | source = new AsmSource( 424 | !stdInLooking.test(file) ? file : undefined, 425 | sourceLine 426 | ); 427 | const sourceCol = parseInt(match[3]); 428 | if (!isNaN(sourceCol) && sourceCol !== 0) { 429 | source.column = sourceCol; 430 | } 431 | } else { 432 | source = undefined; 433 | } 434 | return; 435 | } 436 | match = line.match(sourceCvTag); 437 | if (match && match[4]) { 438 | const file = files.get(parseInt(match[2])); 439 | const sourceLine = parseInt(match[4]); 440 | if (file) { 441 | source = new AsmSource( 442 | !stdInLooking.test(file) ? file : undefined, 443 | sourceLine 444 | ); 445 | if (match[6]) { 446 | const sourceCol = parseInt(match[6]); 447 | if (!isNaN(sourceCol) && sourceCol !== 0) { 448 | source.column = sourceCol; 449 | } 450 | } 451 | } else { 452 | source = undefined; 453 | } 454 | return; 455 | } 456 | match = line.match(sourceD2Tag); 457 | if (match) { 458 | const sourceLine = parseInt(match[1]); 459 | source = new AsmSource( 460 | undefined, 461 | sourceLine, 462 | ); 463 | return; 464 | } 465 | } 466 | 467 | function handleStabs(line: string) { 468 | const match = line.match(sourceStab); 469 | if (!match) { 470 | return; 471 | } 472 | // cf http://www.math.utah.edu/docs/info/stabs_11.html#SEC48 473 | switch (parseInt(match[1])) { 474 | case 68: 475 | source = new AsmSource(undefined, parseInt(match[2])); 476 | break; 477 | case 132: 478 | case 100: 479 | source = undefined; 480 | prevLabel = undefined; 481 | break; 482 | } 483 | } 484 | 485 | function handle6502(line: string) { 486 | const match = line.match(source6502Dbg); 487 | if (match) { 488 | const file = match[1]; 489 | const sourceLine = parseInt(match[2]); 490 | source = new AsmSource( 491 | !stdInLooking.test(file) ? file : undefined, 492 | sourceLine 493 | ); 494 | } else if (source6502DbgEnd.test(line)) { 495 | source = undefined; 496 | } 497 | } 498 | 499 | let inNvccDef = false; 500 | let inNvccCode = false; 501 | 502 | let inCustomAssembly = 0; 503 | for (let line of asmLines) { 504 | if (line.trim() === '') { 505 | maybeAddBlank(); 506 | continue; 507 | } 508 | 509 | if (this.startAppBlock.test(line) || this.startAsmNesting.test(line)) { 510 | inCustomAssembly++; 511 | } else if (this.endAppBlock.test(line) || this.endAsmNesting.test(line)) { 512 | inCustomAssembly--; 513 | } 514 | 515 | handleSource(line); 516 | handleStabs(line); 517 | handle6502(line); 518 | 519 | if (endBlock.test(line) || (inNvccCode && /}/.test(line))) { 520 | source = undefined; 521 | prevLabel = undefined; 522 | } 523 | 524 | if (filter.commentOnly && 525 | ((commentOnly.test(line) && !inNvccCode) || 526 | (commentOnlyNvcc.test(line) && inNvccCode)) 527 | ) { 528 | continue; 529 | } 530 | 531 | if (inCustomAssembly > 0) { 532 | line = this.fixLabelIndentation(line); 533 | } 534 | 535 | let match = line.match(this.labelDef); 536 | if (!match) { 537 | match = line.match(this.assignmentDef); 538 | } 539 | 540 | if (!match) { 541 | match = line.match(this.cudaBeginDef); 542 | if (match) { 543 | inNvccDef = true; 544 | inNvccCode = true; 545 | } 546 | } 547 | if (match) { 548 | // It's a label definition. 549 | if (!labelsUsed.has(match[1])) { 550 | // It's an unused label. 551 | if (filter.labels) { 552 | continue; 553 | } 554 | } else { 555 | // A used label. 556 | prevLabel = match[0]; 557 | labelDefinitions.set(match[1], asm.length + 1); 558 | } 559 | } 560 | if (inNvccDef) { 561 | if (this.cudaEndDef.test(line)) { 562 | inNvccDef = false; 563 | } 564 | } else if (!match && filter.directives) { 565 | // Check for directives only if it wasn't a label; the regexp would 566 | // otherwise misinterpret labels as directives. 567 | if (this.dataDefn.test(line) && prevLabel) { 568 | // We're defining data that's being used somewhere. 569 | } else { 570 | if (this.directive.test(line) && !this.instOpcodeRe.test(line)) { 571 | continue; 572 | } 573 | } 574 | } 575 | 576 | line = expandTabs(line); 577 | const text = this.filterAsmLine(line, filter); 578 | 579 | const labelsInLine: AsmLabel[] = match ? [] : this.getUsedLabelsInLine(text); 580 | 581 | asm.push(new AsmLine( 582 | text, 583 | this.hasOpcode(line, inNvccCode) ? source : undefined, 584 | labelsInLine 585 | )); 586 | } 587 | 588 | this.removeLabelsWithoutDefinition(asm, labelDefinitions); 589 | 590 | return new AsmParserResult(asm, labelDefinitions); 591 | } 592 | 593 | private fixLabelIndentation(line: string) { 594 | const match = line.match(this.indentedLabelDef); 595 | if (match) { 596 | return line.replace(/^\s+/, ''); 597 | } else { 598 | return line; 599 | } 600 | } 601 | 602 | private isUserFunction(func: string) { 603 | if (this.binaryHideFuncRe === undefined) { 604 | return true; 605 | } 606 | return !this.binaryHideFuncRe.test(func); 607 | } 608 | 609 | private processBinaryAsm(asmResult: string, filter: AsmFilter) { 610 | const asm: AsmLine[] = []; 611 | const labelDefinitions = new Map(); 612 | 613 | const asmLines = asmResult.split('\n'); 614 | let source: AsmSource | undefined; 615 | let func: string | undefined; 616 | 617 | // Handle "error" documents. 618 | if (asmLines.length === 1 && asmLines[0][0] === '<') { 619 | return new AsmParserResult( 620 | [new AsmLine(asmLines[0], undefined, [])], 621 | labelDefinitions 622 | ); 623 | } 624 | 625 | for (const line of asmLines) { 626 | const labelsInLine: AsmLabel[] = []; 627 | 628 | let match = line.match(this.lineRe); 629 | if (match) { 630 | source = new AsmSource(match[1], parseInt(match.groups!.line)); 631 | continue; 632 | } 633 | 634 | match = line.match(this.labelRe); 635 | if (match) { 636 | func = match[2]; 637 | if (this.isUserFunction(func)) { 638 | asm.push(new AsmLine(func + ':', undefined, labelsInLine)); 639 | labelDefinitions.set(func, asm.length); 640 | } 641 | continue; 642 | } 643 | 644 | if (func && line === `${func}():`) { 645 | continue; 646 | } 647 | 648 | if (!func || !this.isUserFunction(func)) { 649 | continue; 650 | } 651 | 652 | match = line.match(this.asmOpcodeRe); 653 | if (match) { 654 | const address = parseInt(match.groups!.address, 16); 655 | const opcodes = match.groups!.opcodes.split(' ').filter(x => !!x).join(' '); 656 | const disassembly = " " + this.filterAsmLine(match.groups!.disasm, filter); 657 | asm.push(new BinaryAsmLine(disassembly, source, labelsInLine, address, opcodes)); 658 | } 659 | } 660 | 661 | this.removeLabelsWithoutDefinition(asm, labelDefinitions); 662 | 663 | return new AsmParserResult(asm, labelDefinitions); 664 | } 665 | 666 | process(asm: string, filter: AsmFilter): AsmParserResult { 667 | if (filter.binary) { 668 | return this.processBinaryAsm(asm, filter); 669 | } else { 670 | return this.processAsm(asm, filter); 671 | } 672 | } 673 | } 674 | -------------------------------------------------------------------------------- /src/available-panel.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | /** 4 | * This class provides the content for the tree view. 5 | */ 6 | export class AvailableNode { 7 | private children: AvailableNode[] = []; 8 | 9 | constructor( 10 | private label: string, 11 | private command?: string, 12 | private parent?: AvailableNode) { 13 | parent?.children.push(this); 14 | } 15 | 16 | public removeChildren() { 17 | this.children = []; 18 | } 19 | 20 | public isLeaf() { 21 | return this.getParent() ? true : false; 22 | } 23 | 24 | public getChildren() { 25 | if (this.children.length > 0) { 26 | return this.children; 27 | } 28 | 29 | if (this.label === 'print passes') { 30 | new AvailableNode("Print Call Graph", "-passes=dot-callgraph -disable-output", this); 31 | new AvailableNode("Print Control Flow Graph", "-passes=dot-cfg -disable-output", this); 32 | new AvailableNode("Print Simple CFG", "-passes=dot-cfg-only -disable-output", this); 33 | new AvailableNode("Print Dominance Tree", "-passes=dot-dom -disable-output", this); 34 | new AvailableNode("Print Simple DOM Tree", "-passes=dot-dom-only -disable-output", this); 35 | } 36 | 37 | if (this.label === 'transform passes') { 38 | new AvailableNode("Promote Memory to Register (mem2reg - SSA construction)", "-passes=mem2reg", this); 39 | new AvailableNode("Demote All Values to Stack Slots (reg2mem)", "-passes=reg2mem", this); 40 | new AvailableNode("Dead Code Elimination", "-passes=dce", this); 41 | } 42 | 43 | return this.children; 44 | } 45 | 46 | public getParent() { 47 | return this.parent; 48 | } 49 | 50 | public getTreeItem(): vscode.TreeItem { 51 | let cmd = { 52 | title: 'Run Available Pass', 53 | command: 'llvmAvailableView.runAvailPass', 54 | arguments: [this.command] 55 | }; 56 | 57 | const tooltip = new vscode.MarkdownString(`$(zap) Tooltip for ${this.label}`, true); 58 | return { 59 | label: { label: this.label }, 60 | tooltip, 61 | collapsibleState: 62 | this.getChildren().length > 0 ? vscode.TreeItemCollapsibleState.Collapsed : vscode.TreeItemCollapsibleState.None, 63 | command: cmd 64 | }; 65 | } 66 | }; 67 | 68 | 69 | export class LLVMAvailablePassDataProvider implements vscode.TreeDataProvider { 70 | constructor(subscriptions: vscode.Disposable[]) { 71 | subscriptions.push(vscode.window.createTreeView( 72 | 'llvm-pass-view', { treeDataProvider: this, showCollapseAll: true })); 73 | } 74 | private _onDidChangeTreeData = new vscode.EventEmitter(); 75 | readonly onDidChangeTreeData = this._onDidChangeTreeData.event; 76 | 77 | public refresh(): void { 78 | for (let node of LLVMAvailablePassDataProvider.tree_nodes) { 79 | node.removeChildren(); 80 | } 81 | this._onDidChangeTreeData.fire(); 82 | } 83 | 84 | getChildren(element?: AvailableNode): AvailableNode[] { 85 | if (!element) { 86 | return LLVMAvailablePassDataProvider.tree_nodes; 87 | } else { 88 | return element.getChildren(); 89 | } 90 | } 91 | 92 | getTreeItem(element: AvailableNode): vscode.TreeItem { 93 | return element.getTreeItem(); 94 | } 95 | 96 | getParent(element: AvailableNode) { 97 | return element.getParent(); 98 | } 99 | 100 | static readonly tree_nodes = [ 101 | new AvailableNode('print passes'), 102 | new AvailableNode('transform passes'), 103 | ]; 104 | } 105 | -------------------------------------------------------------------------------- /src/clang.ts: -------------------------------------------------------------------------------- 1 | import which = require("which"); 2 | import * as path from 'path'; 3 | import { parseShellArg } from './utils'; 4 | import * as vscode from 'vscode'; 5 | import { exec, spawn } from 'child_process'; 6 | 7 | // This class is an abstraction of clang/lld/llvm commands 8 | // Or, may use to store other commands 9 | export class Command { 10 | public exe: string; 11 | public args: string[]; 12 | public env?: CommandEnv; 13 | 14 | // This should not be directly used 15 | protected constructor(commands: string[]) { 16 | this.exe = commands[0]; 17 | commands.splice(0, 1); 18 | this.args = commands; 19 | } 20 | 21 | // This function create a command and automatically detect the type 22 | public static async create(commands: string[]): Promise { 23 | if (commands.length === 0) { return undefined; } 24 | let clangNames = RegExp("(clang|clang\\+\\+|clang-cpp)(-[0-9]+(\\.[0-9]+)?)?(\.exe)?$"); 25 | let lldNames = RegExp("(ld.lld|ld64.lld|lld|lld-link)(\.exe)?$"); 26 | let nvccNames = RegExp("(nvcc)(\.exe)?$"); 27 | let gccNames = RegExp("(gcc|g\\+\\+)(-[0-9]+(\\.[0-9]+)?)?(\.exe)?$"); 28 | let clNames = RegExp("(cl)(\.exe)?$"); 29 | let ciccNames = RegExp("(cicc)(\.exe)?$"); 30 | let ptxasNames = RegExp("(ptxas)(\.exe)?$"); 31 | let cudafeNames = RegExp("(cudafe)(\\+\\+)?(\.exe)?$"); 32 | let nvlinkNames = RegExp("(nvlink)(\.exe)?$"); 33 | let fatbinaryNames = RegExp("(fatbinary)(\.exe)?$"); 34 | let eraseNames = RegExp("(erase|rm)(\.exe)?$"); 35 | let resourceNames = RegExp("(resource)(\.exe)?$"); 36 | 37 | if (clangNames.test(commands[0])) { 38 | // if this is cc1 command, then we can directly create it 39 | if (commands.indexOf("-cc1") !== -1) { 40 | return new CC1Command(commands); 41 | } 42 | 43 | // for clang command, we need to get the real command by running with '-###' 44 | let clangCmd = new ClangCommand(commands); 45 | clangCmd.addSubCommands(await clangCmd.getRealCommands()); 46 | return clangCmd; 47 | } else if (lldNames.test(commands[0])) { 48 | return new LLDCommand(commands); 49 | } else if (nvccNames.test(commands[0])) { 50 | let nvccCmd = new NVCCCommand(commands); 51 | nvccCmd.addSubCommands(await nvccCmd.getRealCommands()); 52 | return nvccCmd; 53 | } else if (gccNames.test(commands[0])) { 54 | return new GCCCommand(commands); 55 | } else if (clNames.test(commands[0])) { 56 | return new CLCommand(commands); 57 | } else if (ciccNames.test(commands[0])) { 58 | return new CICCCommand(commands); 59 | } else if (ptxasNames.test(commands[0])) { 60 | return new PTXASCommand(commands); 61 | } else if (cudafeNames.test(commands[0])) { 62 | return new CUDAFECommand(commands); 63 | } else if (nvlinkNames.test(commands[0])) { 64 | return new NVLinkCommand(commands); 65 | } else if (fatbinaryNames.test(commands[0])) { 66 | return new FatbinaryCommand(commands); 67 | } else if (eraseNames.test(commands[0])) { 68 | return new EraseCommand(commands); 69 | } else if (resourceNames.test(commands[0])) { 70 | return new ResourceCommand(commands); 71 | } else { 72 | return new Command(commands); 73 | } 74 | } 75 | 76 | // This function will correctly parse the command string 77 | public static async createFromString(cmd: string): Promise { 78 | let commands = parseShellArg(cmd); 79 | if (!commands) { return undefined; } 80 | return Command.create(commands); 81 | } 82 | 83 | public async run(addition?: string[], stdin?: string): Promise<{ code: number | null, stdout: string, stderr: string }> { 84 | let env = this.env; 85 | if (env === undefined) { 86 | env = CommandEnv.getDefault(); 87 | } 88 | let args = this.getArgs(); 89 | if (addition) { 90 | args = args.concat(addition); 91 | } 92 | return env.run(this.exe, args, stdin); 93 | } 94 | public getInputPath(): string | undefined { return undefined; } 95 | public getOutputPath(): string | undefined { return undefined; } 96 | public setFilter(filter: string) { } 97 | 98 | public isExeAbsolute(): boolean { 99 | return path.isAbsolute(this.exe); 100 | } 101 | 102 | public isExeRelative(): boolean { 103 | return !this.isExeAbsolute(); 104 | } 105 | 106 | public isExeOnlyName(): boolean { 107 | return this.exe.indexOf("/") === -1; 108 | } 109 | 110 | public isExeInPath(): boolean { 111 | return which.sync(this.exe, { nothrow: true }) !== null; 112 | } 113 | 114 | public getArgs(): string[] { 115 | return this.args; 116 | } 117 | 118 | public toString() { 119 | return '"' + this.exe + '" "' + this.args.join('" "') + '"'; 120 | } 121 | 122 | public getType(): string { 123 | return "Command"; 124 | } 125 | 126 | public async addSubCommands(realCommands: string[]) { 127 | for (let cmd of realCommands) { 128 | let args = parseShellArg(cmd); 129 | if (args) { 130 | let subCommand = await Command.create(args); 131 | if (subCommand) { this.pushSubCommand(subCommand); } 132 | } 133 | } 134 | } 135 | 136 | public pushSubCommand(command: Command) { 137 | } 138 | } 139 | 140 | export class CC1Command extends Command { 141 | public input?: string; 142 | public output?: string; 143 | public language?: string; 144 | public mode?: string; 145 | 146 | constructor(commands: string[]) { 147 | let language = commands[commands.indexOf("-x") + 1]; 148 | let input = commands[commands.indexOf("-x") + 2]; 149 | let output = commands[commands.indexOf("-o") + 1]; 150 | commands.splice(commands.indexOf("-x"), 3); 151 | commands.splice(commands.indexOf("-o"), 2); 152 | super(commands); 153 | this.input = input; 154 | this.output = output; 155 | this.language = language; 156 | 157 | let modeRe = RegExp("-E|-S|-emit-llvm|-emit-llvm-bc"); 158 | this.mode = commands.find((value) => { return value.match(modeRe); }); 159 | } 160 | 161 | public getArgs(): string[] { 162 | let args = []; 163 | if (this.output) { args.push("-o", this.output); } 164 | if (this.language) { args.push("-x", this.language); } 165 | if (this.input) { args.push(this.input); } 166 | return this.args.concat(args); 167 | } 168 | 169 | public getInputPath() { 170 | return this.input; 171 | } 172 | 173 | public getOutputPath() { 174 | return this.output; 175 | } 176 | 177 | public toString() { 178 | return super.toString() + 179 | (this.output ? '"-o" "' + this.output + '"' : "") + 180 | (this.language ? '"-x" "' + this.language + '"' : "") + 181 | (this.input ? '"' + this.input + '"' : ""); 182 | } 183 | 184 | public getType(): string { 185 | return "CC1Command"; 186 | } 187 | } 188 | 189 | export class ClangCommand extends Command { 190 | public input: string[]; 191 | public output?: string; 192 | public subCommands: Command[] = []; 193 | 194 | constructor(commands: string[]) { 195 | // get output file 196 | let outputIdx = commands.indexOf("-o"); 197 | let output = undefined; 198 | if (outputIdx !== -1) { 199 | output = commands[outputIdx + 1]; 200 | commands.splice(outputIdx, 2); 201 | } 202 | 203 | // get input files 204 | let input: string[] = []; 205 | let cppName = RegExp("\\.(C|c|cpp|cxx|cc|c\\+\\+)$"); 206 | for (let i = 0; i < commands.length; i++) { 207 | if (cppName.test(commands[i])) { 208 | input.push(commands[i]); 209 | commands[i] = ""; 210 | } 211 | } 212 | commands = commands.filter((value) => { return value !== ""; }); 213 | 214 | // get mode configuration 215 | let modeRe = RegExp("-S|-c|-E"); 216 | let mode = commands.find((value) => { return value.match(modeRe); }); 217 | 218 | super(commands); 219 | this.output = output; 220 | this.input = input; 221 | 222 | if (mode) { this.mode = mode; } 223 | if (output != undefined) this.bOutputToStdout = false; 224 | } 225 | 226 | public async getRealCommands() { 227 | const { stdout, stderr } = await this.run(["-###"]); 228 | console.log("stderr of get real command: ", stdout, stderr); 229 | 230 | let cmd: string; 231 | if (stderr !== "") { 232 | cmd = stderr; 233 | } else if (stdout !== "") { 234 | cmd = stdout; 235 | } else { 236 | return []; 237 | } 238 | 239 | let lines = cmd.split(/\r?\n/); 240 | let k = 4; 241 | while (k < lines.length && !lines[k] && !lines[k].trim().startsWith("\"")) { 242 | k++; 243 | } 244 | return lines.slice(k); 245 | } 246 | 247 | public getArgs(): string[] { 248 | let args = this.args; 249 | 250 | if (args.indexOf("-S") === -1) { 251 | args = args.concat(["-S"]); 252 | } 253 | 254 | if (this.bDebug && args.indexOf('-g') === -1) { 255 | args = args.concat(["-g"]); 256 | } 257 | 258 | if (this.bDumpAST && args.indexOf('-ast-dump') === -1) { 259 | args = args.concat(["-Xclang", "-ast-dump"]); 260 | } 261 | 262 | if (this.bDisableOptnone && args.indexOf('-disable-O0-optnone') === -1) { 263 | args = args.concat(["-Xclang", "-disable-O0-optnone"]); 264 | } 265 | 266 | if (this.bSaveTemps && args.indexOf('-save-temps') === -1) { 267 | args = args.concat(["-save-temps=obj"]); 268 | } 269 | 270 | if (this.sFilter) { 271 | args = args.concat(["-mllvm", "-filter-print-funcs=" + this.sFilter]); 272 | } 273 | 274 | if (this.bPrintModuleScope && args.indexOf('-print-module-scope') === -1) { 275 | args = args.concat(["-mllvm", "-print-module-scope"]); 276 | } 277 | 278 | if (this.bPrintBefore && args.indexOf('-print-before-all') === -1) { 279 | args = args.concat(["-mllvm", "-print-before-all"]); 280 | } 281 | 282 | if (this.bPrintAfter && args.indexOf('-print-after-all') === -1) { 283 | args = args.concat(["-mllvm", "-print-after-all"]); 284 | } 285 | 286 | if (this.bPrintChanged && args.indexOf('-print-changed') === -1) { 287 | args = args.concat(["-mllvm", "-print-changed"]); 288 | } 289 | 290 | if (this.bOutputToStdout) 291 | args = args.concat(["-o", "-"]); 292 | else { 293 | if (this.output === undefined) 294 | this.output = this.input[0] + ".s"; 295 | args = args.concat(["-o", this.output]); 296 | } 297 | 298 | if (this.bInputFromStdin == false && this.input.length > 0) { 299 | args = args.concat(this.input); 300 | } 301 | 302 | return args; 303 | } 304 | 305 | public getInputPath(): string | undefined { 306 | if (this.input.length > 0) { 307 | return this.input[0]; 308 | } 309 | return undefined; 310 | } 311 | public getOutputPath(): string | undefined { 312 | if (this.bOutputToStdout) return '-'; 313 | return this.output; 314 | } 315 | 316 | public isInputFromStdin(): boolean { 317 | return this.bInputFromStdin || this.input.length === 0; 318 | } 319 | 320 | public isOutputToStdout(): boolean { 321 | return this.bOutputToStdout || this.output === undefined; 322 | } 323 | 324 | public setFilter(filter: string) { this.sFilter = filter; } 325 | 326 | public getType(): string { 327 | return "ClangCommand"; 328 | } 329 | 330 | public pushSubCommand(command: Command) { 331 | this.subCommands.push(command); 332 | } 333 | 334 | // Here is a list of configurable options 335 | public bInputFromStdin = false; 336 | public bSaveTemps = true; 337 | public bOutputToStdout = true; 338 | public bDisableOptnone = true; 339 | public bDebug = true; 340 | public sFilter?: string; 341 | public bPrintModuleScope = true; 342 | public bPrintBefore = false; 343 | public bPrintAfter = false; 344 | public bPrintChanged = true; 345 | public bDumpAST = false; 346 | public mode?: string; 347 | } 348 | 349 | export class LLDCommand extends Command { 350 | public input: string[]; 351 | public output?: string; 352 | 353 | constructor(commands: string[]) { 354 | // get output file 355 | let outputIdx = commands.indexOf("-o"); 356 | let output = undefined; 357 | if (outputIdx !== -1) { 358 | output = commands[outputIdx + 1]; 359 | commands.splice(outputIdx, 2); 360 | } 361 | 362 | // get input files 363 | let input: string[] = []; 364 | let obj = RegExp("\\.(o)$"); 365 | for (let i = 0; i < commands.length; i++) { 366 | if (obj.test(commands[i])) { 367 | input.push(commands[i]); 368 | commands[i] = ""; 369 | } 370 | } 371 | commands = commands.filter((value) => { return value !== ""; }); 372 | 373 | super(commands); 374 | this.output = output; 375 | this.input = input; 376 | } 377 | 378 | public getType(): string { 379 | return "LLDCommand"; 380 | } 381 | } 382 | 383 | export class CLCommand extends Command { 384 | public input: string[]; 385 | public output?: string; 386 | 387 | constructor(commands: string[]) { 388 | // get output file 389 | let outputIdx = commands.indexOf(">"); 390 | let output = undefined; 391 | if (outputIdx !== -1) { 392 | output = commands[outputIdx + 1]; 393 | commands.splice(outputIdx, 2); 394 | } else { 395 | outputIdx = commands.findIndex((value) => value.startsWith("-Fo")); 396 | if (outputIdx !== -1) { 397 | output = commands[outputIdx].substring(3); 398 | commands.splice(outputIdx, 1); 399 | } 400 | } 401 | 402 | // get input files 403 | let input: string[] = []; 404 | let clInput = RegExp("\\.(cu|cpp|stub)$"); 405 | for (let i = 0; i < commands.length; i++) { 406 | if (clInput.test(commands[i])) { 407 | input.push(commands[i]); 408 | commands[i] = ""; 409 | } 410 | } 411 | commands = commands.filter((value) => { return value !== ""; }); 412 | 413 | super(commands); 414 | this.output = output; 415 | this.input = input; 416 | } 417 | 418 | } 419 | 420 | export class GCCCommand extends Command { 421 | public input: string[]; 422 | public output?: string; 423 | 424 | constructor(commands: string[]) { 425 | // get output file 426 | let outputIdx = commands.indexOf("-o"); 427 | let output = undefined; 428 | if (outputIdx !== -1) { 429 | output = commands[outputIdx + 1]; 430 | commands.splice(outputIdx, 2); 431 | } 432 | 433 | // get input files 434 | let input: string[] = []; 435 | let obj = RegExp("\\.(cu|cpp|stub)$"); 436 | for (let i = 0; i < commands.length; i++) { 437 | if (obj.test(commands[i])) { 438 | input.push(commands[i]); 439 | commands[i] = ""; 440 | } 441 | } 442 | commands = commands.filter(value => value !== ""); 443 | 444 | super(commands); 445 | this.output = output; 446 | this.input = input; 447 | } 448 | } 449 | 450 | export class CICCCommand extends Command { 451 | public input?: string; 452 | public output?: string; 453 | 454 | public orig_src_path_name?: string; 455 | public include_file_name?: string; 456 | public module_id_file_name?: string; 457 | public gen_c_file_name?: string; 458 | public stub_file_name?: string; 459 | public gen_device_file_name?: string; 460 | 461 | constructor(commands: string[]) { 462 | let orig_src_path_name = commands[commands.indexOf("--orig_src_path_name") + 1]; 463 | let include_file_name = commands[commands.indexOf("--include_file_name") + 1]; 464 | let module_id_file_name = commands[commands.indexOf("--module_id_file_name") + 1]; 465 | let gen_c_file_name = commands[commands.indexOf("--gen_c_file_name") + 1]; 466 | let stub_file_name = commands[commands.indexOf("--stub_file_name") + 1]; 467 | let gen_device_file_name = commands[commands.indexOf("--gen_device_file_name") + 1]; 468 | 469 | commands.splice(commands.indexOf("--orig_src_path_name"), 2); 470 | commands.splice(commands.indexOf("--include_file_name"), 2); 471 | commands.splice(commands.indexOf("--module_id_file_name"), 2); 472 | commands.splice(commands.indexOf("--gen_c_file_name"), 2); 473 | commands.splice(commands.indexOf("--stub_file_name"), 2); 474 | commands.splice(commands.indexOf("--gen_device_file_name"), 2); 475 | 476 | // get input file 477 | let ii = RegExp("\\.cpp1.ii$"); 478 | let idx = commands.findIndex(value => ii.test(value)); 479 | let input = undefined; 480 | if (idx === -1) { 481 | input = commands[idx]; 482 | commands.splice(idx, 1); 483 | } 484 | 485 | // get output file 486 | let outputIdx = commands.indexOf("-o"); 487 | let output = undefined; 488 | if (outputIdx !== -1) { 489 | output = commands[outputIdx + 1]; 490 | commands.splice(outputIdx, 2); 491 | } 492 | 493 | super(commands); 494 | this.input = input; 495 | this.orig_src_path_name = orig_src_path_name; 496 | this.include_file_name = include_file_name; 497 | this.module_id_file_name = module_id_file_name; 498 | this.gen_c_file_name = gen_c_file_name; 499 | this.stub_file_name = stub_file_name; 500 | this.gen_device_file_name = gen_device_file_name; 501 | } 502 | } 503 | 504 | export class PTXASCommand extends Command { 505 | public input?: string; 506 | public output?: string; 507 | 508 | constructor(commands: string[]) { 509 | // get output file 510 | let outputIdx = commands.indexOf("-o"); 511 | let output = undefined; 512 | if (outputIdx !== -1) { 513 | output = commands[outputIdx + 1]; 514 | commands.splice(outputIdx, 2); 515 | } 516 | 517 | // get input file 518 | let ii = RegExp("\\.ptx$"); 519 | let idx = commands.findIndex(value => ii.test(value)); 520 | let input = undefined; 521 | if (idx === -1) { 522 | input = commands[idx]; 523 | commands.splice(idx, 1); 524 | } 525 | 526 | super(commands); 527 | this.input = input; 528 | this.output = output; 529 | } 530 | 531 | } 532 | 533 | export class CUDAFECommand extends Command { 534 | public input: string; 535 | public gen_c_file_name: string; 536 | public stub_file_name: string; 537 | public module_id_file_name: string; 538 | 539 | constructor(commands: string[]) { 540 | // get output files 541 | let gen_c_file_name = commands[commands.indexOf("--gen_c_file_name") + 1]; 542 | let stub_file_name = commands[commands.indexOf("--stub_file_name") + 1]; 543 | let module_id_file_name = commands[commands.indexOf("--module_id_file_name") + 1]; 544 | commands.splice(commands.indexOf("--gen_c_file_name"), 2); 545 | commands.splice(commands.indexOf("--stub_file_name"), 2); 546 | commands.splice(commands.indexOf("--module_id_file_name"), 2); 547 | 548 | // get input file 549 | let ii = RegExp("\\.cpp4.ii$"); 550 | let idx = commands.findIndex(value => ii.test(value)); 551 | let input = commands[idx]; 552 | commands.splice(idx, 1); 553 | 554 | super(commands); 555 | this.input = input; 556 | this.gen_c_file_name = gen_c_file_name; 557 | this.stub_file_name = stub_file_name; 558 | this.module_id_file_name = module_id_file_name; 559 | 560 | } 561 | } 562 | 563 | export class NVLinkCommand extends Command { 564 | 565 | 566 | } 567 | 568 | export class FatbinaryCommand extends Command { 569 | public input: string[]; 570 | public output?: string; 571 | 572 | constructor(commands: string[]) { 573 | 574 | // get output file 575 | let out = RegExp("^--embedded-fatbin=(.*)$"); 576 | let idx = commands.findIndex(value => out.test(value)); 577 | let output = undefined; 578 | if (idx !== -1) { 579 | output = commands[idx].substring(19); 580 | commands.splice(idx, 1); 581 | } 582 | 583 | // get input files 584 | let input: string[] = []; 585 | let obj = RegExp("\\.(cubin|ptx)$"); 586 | for (let i = 0; i < commands.length; i++) { 587 | if (obj.test(commands[i])) { 588 | input.push(commands[i]); 589 | commands[i] = ""; 590 | } 591 | } 592 | commands = commands.filter(value => value !== ""); 593 | 594 | super(commands); 595 | this.input = input; 596 | this.output = output; 597 | } 598 | 599 | } 600 | 601 | export class EraseCommand extends Command { 602 | public path?: string; 603 | 604 | constructor(commands: string[]) { 605 | super(commands); 606 | if (commands.length > 1) { 607 | this.path = commands[1]; 608 | } 609 | } 610 | } 611 | 612 | export class ResourceCommand extends Command { 613 | public file: string; 614 | 615 | constructor(commands: string[]) { 616 | let path = commands[commands.indexOf('file')+1]; 617 | path = path.substring(0, path.length-1); 618 | commands.splice(commands.indexOf('file'), 2); 619 | 620 | // remove the [] 621 | commands[1] = commands[1].substring(1); 622 | let last = commands[commands.length-1]; 623 | commands[commands.length-1] = last.substring(0, last.length-1); 624 | 625 | super(commands); 626 | this.file = path; 627 | } 628 | } 629 | 630 | export class NVCCCommand extends Command { 631 | 632 | public subCommands: Command[] = []; 633 | 634 | public getType(): string { 635 | return "NVCCCommand"; 636 | } 637 | 638 | public pushSubCommand(command: Command) { 639 | this.subCommands.push(command); 640 | } 641 | 642 | public async getRealCommands() { 643 | const { stdout, stderr } = await this.run(["-v", "--dryrun"]); 644 | console.log("stderr of get real command: ", stdout, stderr); 645 | 646 | let cmd: string; 647 | if (stderr !== "") { 648 | cmd = stderr; 649 | } else if (stdout !== "") { 650 | cmd = stdout; 651 | } else { 652 | return []; 653 | } 654 | 655 | let lines = cmd.split(/\r?\n/); 656 | let k = 16; 657 | let results = []; 658 | while (k < lines.length && lines[k] && lines[k].startsWith("#$ ")) { 659 | results.push(lines[k].substring(3).trim()); 660 | k++; 661 | } 662 | return results; 663 | } 664 | 665 | } 666 | 667 | 668 | export class CommandEnv { 669 | constructor(workDir?: string, env?: NodeJS.ProcessEnv) { 670 | // if not specified, use the first workspace folder 671 | // if not open any workspace folder, use the current working directory of process 672 | if (workDir) { 673 | this.workdir = workDir; 674 | } else if (vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length > 0) { 675 | this.workdir = vscode.workspace.workspaceFolders[0].uri.fsPath; 676 | } else { 677 | this.workdir = process.cwd(); 678 | } 679 | 680 | // if not specified, use the current process env 681 | if (env) { 682 | this.env = env; 683 | } else { 684 | this.env = process.env; 685 | } 686 | } 687 | 688 | public async run(exe: string, args: string[], stdin?: string): Promise<{ code: number | null, stdout: string, stderr: string }> { 689 | return new Promise((resolve, reject) => { 690 | let stderr: string[] = []; 691 | let stdout: string[] = []; 692 | const run = spawn(exe, args, { cwd: this.workdir, env: this.env }); 693 | run.stderr.on('data', (data) => { 694 | stderr.push(data); 695 | }); 696 | run.stdout.on('data', (data) => { 697 | stdout.push(data); 698 | }); 699 | run.on('close', (code) => { 700 | resolve({ code: code, stdout: stdout.join(""), stderr: stderr.join("") }); 701 | }); 702 | run.on('error', (err) => { 703 | reject(err); 704 | }); 705 | if (stdin) { 706 | run.stdin.write(stdin); 707 | run.stdin.end(); 708 | } 709 | }); 710 | } 711 | 712 | static defaultEnv?: CommandEnv; 713 | static getDefault(): CommandEnv { 714 | if (!CommandEnv.defaultEnv) { 715 | CommandEnv.defaultEnv = new CommandEnv(); 716 | } 717 | return CommandEnv.defaultEnv; 718 | } 719 | 720 | private workdir?: string; 721 | private env?: NodeJS.ProcessEnv; 722 | } 723 | -------------------------------------------------------------------------------- /src/config.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import * as fs from 'fs'; 3 | import * as path from 'path'; 4 | 5 | export async function fsStat(filePath: fs.PathLike): Promise { 6 | let stats: fs.Stats | undefined; 7 | try { 8 | stats = await fs.promises.stat(filePath); 9 | } catch (e) { 10 | // File doesn't exist 11 | return undefined; 12 | } 13 | return stats; 14 | } 15 | 16 | 17 | /** Test whether a directory exists */ 18 | export async function checkDirectoryExists(dirPath: string): Promise { 19 | const stats: fs.Stats | undefined = await fsStat(dirPath); 20 | return !!stats && stats.isDirectory(); 21 | } 22 | 23 | export async function checkPathExists(filePath: string): Promise { 24 | return !!(await fsStat(filePath)); 25 | } 26 | 27 | /** Test whether a file exists */ 28 | export async function checkFileExists(filePath: string): Promise { 29 | const stats: fs.Stats | undefined = await fsStat(filePath); 30 | return !!stats && stats.isFile(); 31 | } 32 | 33 | /** Writes content to a text file */ 34 | export async function writeFileText(filePath: string, content: string, encoding: BufferEncoding = "utf8"): Promise { 35 | const folders: string[] = filePath.split(path.sep).slice(0, -1); 36 | if (folders.length) { 37 | // create folder path if it doesn't exist 38 | folders.reduce((previous, folder) => { 39 | const folderPath: string = previous + path.sep + folder; 40 | if (!fs.existsSync(folderPath)) { 41 | fs.mkdirSync(folderPath); 42 | } 43 | return folderPath; 44 | }); 45 | } 46 | fs.writeFileSync(filePath, content, { encoding }); 47 | } 48 | 49 | export async function ensurePropertiesFile(): Promise { 50 | if (vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length > 0) { 51 | for (let i: number = 0; i < vscode.workspace.workspaceFolders.length; ++i) { 52 | const configFolder = path.join(vscode.workspace.workspaceFolders[i].uri.fsPath, '.vscode'); 53 | const configFile = path.join(configFolder, 'llvm.json'); 54 | 55 | if (await checkFileExists(configFile)) { 56 | continue; 57 | } else { 58 | try { 59 | if (!await checkDirectoryExists(configFolder)) { 60 | fs.mkdirSync(configFolder); 61 | } 62 | await writeFileText(configFile, "{}\n"); 63 | } catch (errJS) { 64 | const err: Error = errJS as Error; 65 | vscode.window.showErrorMessage(`Failed to create llvm.json: ${err.message}`); 66 | } 67 | } 68 | } 69 | } 70 | } 71 | 72 | export async function loadConfig() { 73 | var cfg : any = {}; 74 | if (vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length > 0) { 75 | for (let i: number = 0; i < vscode.workspace.workspaceFolders.length; ++i) { 76 | const configFolder = path.join(vscode.workspace.workspaceFolders[i].uri.fsPath, '.vscode'); 77 | const configFile = path.join(configFolder, 'llvm.json'); 78 | 79 | let rawdata = fs.readFileSync(configFile); 80 | let config = JSON.parse(rawdata.toString()); 81 | console.log(config); 82 | for(var k in config) cfg[k]=config[k]; 83 | } 84 | } 85 | return cfg; 86 | } -------------------------------------------------------------------------------- /src/control-panel.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import { CommandEnv } from './clang'; 3 | import { Core } from './core'; 4 | import { getNonce } from './utils'; 5 | 6 | export class ConfigViewProvider implements vscode.WebviewViewProvider { 7 | 8 | public static readonly viewType = 'llvm-control'; 9 | private view?: vscode.WebviewView; 10 | 11 | constructor( 12 | private readonly extensionUri: vscode.Uri, 13 | private core: Core, 14 | subscriptions: vscode.Disposable[] 15 | ) { 16 | subscriptions.push(vscode.window.registerWebviewViewProvider(ConfigViewProvider.viewType, this)); 17 | } 18 | 19 | public resolveWebviewView(webviewView: vscode.WebviewView, context: vscode.WebviewViewResolveContext, token: vscode.CancellationToken) { 20 | this.view = webviewView; 21 | 22 | webviewView.webview.options = { 23 | enableScripts: true, 24 | localResourceRoots: [this.extensionUri] 25 | }; 26 | 27 | webviewView.webview.html = this.getHtmlForWebview(webviewView.webview); 28 | 29 | webviewView.webview.onDidReceiveMessage(data => { 30 | switch (data.type) { 31 | case 'select': { 32 | vscode.commands.executeCommand('llvmPipelineView.ensure', data.value.trim()); 33 | break; 34 | } 35 | case 'compare': { 36 | if (!data.value || data.value.length != 2) { 37 | vscode.window.showErrorMessage('Please select two configs to compare'); 38 | break; 39 | } 40 | vscode.commands.executeCommand('llvmPipelineView.compare', data.value[0].value, data.value[1].value); 41 | break; 42 | } 43 | case 'new': { 44 | vscode.window.showInputBox({ prompt: 'Enter a new command' }).then(command => { 45 | this.addConfig(command as string); 46 | }); 47 | break; 48 | } 49 | case 'update-filter': { 50 | this.core.filter = data.value; 51 | break; 52 | } 53 | } 54 | }); 55 | } 56 | 57 | public addConfig(command: string) { 58 | if (this.view) { 59 | this.view.show?.(true); // `show` is not implemented in 1.49 but is for 1.50 insiders 60 | this.view.webview.postMessage({ type: 'addConfig', value: command }); 61 | } 62 | } 63 | 64 | public reloadConfig() { 65 | console.log('reloadConfig!'); 66 | if (this.view) { 67 | this.view.webview.postMessage({ type: 'reloadConfig' }); 68 | } 69 | } 70 | 71 | private getHtmlForWebview(webview: vscode.Webview) { 72 | // Get the local path to main script run in the webview, then convert it to a uri we can use in the webview. 73 | const scriptUri = webview.asWebviewUri(vscode.Uri.joinPath(this.extensionUri, 'media', 'main.js')); 74 | 75 | // Do the same for the stylesheet. 76 | const styleResetUri = webview.asWebviewUri(vscode.Uri.joinPath(this.extensionUri, 'media', 'reset.css')); 77 | const styleVSCodeUri = webview.asWebviewUri(vscode.Uri.joinPath(this.extensionUri, 'media', 'vscode.css')); 78 | const styleMainUri = webview.asWebviewUri(vscode.Uri.joinPath(this.extensionUri, 'media', 'main.css')); 79 | 80 | // Use a nonce to only allow a specific script to be run. 81 | const nonce = getNonce(); 82 | 83 | return ` 84 | 85 | 86 | 87 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 |
    99 |
100 | 101 |
102 | 103 | 104 | 105 |
106 | 107 | 108 | `; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/core.ts: -------------------------------------------------------------------------------- 1 | import { readFileSync } from 'fs'; 2 | import { CC1Command, ClangCommand, Command, CommandEnv } from './clang'; 3 | import { LLVMPipelineTreeDataProvider } from './pipeline-panel'; 4 | import { splitURI } from './utils'; 5 | import { AsmDocument } from './document'; 6 | import * as vscode from 'vscode'; 7 | import { on } from 'events'; 8 | import * as path from 'path'; 9 | 10 | export class Pass { 11 | public same: boolean; 12 | 13 | constructor( 14 | public parent: Pipeline, 15 | public name: string, 16 | public before_ir: string, 17 | public after_ir: string, 18 | public index: number, 19 | public backend: boolean) { 20 | this.same = before_ir == after_ir; 21 | } 22 | } 23 | 24 | 25 | export class Pipeline { 26 | public raw_command: string; 27 | public command: Command; 28 | 29 | public input: string = ""; 30 | public output: string = ""; 31 | public ast: string = ""; 32 | public preprocessed: string = ""; 33 | public llvm: string = ""; 34 | 35 | public passList: Pass[] = []; 36 | public backendList: Pass[] = []; 37 | 38 | constructor(raw: string, cmd: Command,) { 39 | this.raw_command = raw; 40 | this.command = cmd; 41 | } 42 | 43 | public encodeCommand() { 44 | return encodeURIComponent(this.raw_command.replace(/\//g, "%2F")); 45 | } 46 | 47 | public async run() { 48 | const { stdout, stderr } = await this.command.run(); 49 | 50 | // First, get the output ASM from the command 51 | // if the command has specified an output file, read that 52 | // otherwise, read the stdout 53 | let output_path = this.command.getOutputPath(); 54 | if (output_path == '-' || output_path == undefined) 55 | this.output = stdout; 56 | else 57 | this.output = readFileSync(output_path).toString(); 58 | 59 | // Then, we should parse the LLVM dump data in stderr 60 | this.parseLLVMDumpChanged(stderr); 61 | 62 | // Get the human-readable LLVM IR 63 | if (this.command instanceof ClangCommand) { 64 | let c = this.command as ClangCommand; 65 | c.subCommands.forEach(async (cmd) => { 66 | if (cmd instanceof CC1Command) { 67 | let output = cmd.getOutputPath(); 68 | if (cmd.mode == "-E" && output) { 69 | this.preprocessed = readFileSync(output).toString(); 70 | } 71 | if (cmd.mode == "-emit-llvm-bc" && output) { 72 | let convertCmd = await Command.createFromString( 73 | 'clang -S -emit-llvm "' + output + '" -o -'); 74 | if (convertCmd) { 75 | const { stdout, stderr } = await convertCmd.run(); 76 | this.llvm = stdout; 77 | } 78 | } 79 | } 80 | }); 81 | } 82 | 83 | // Get the AST 84 | if (this.command instanceof ClangCommand) { 85 | let c = this.command as ClangCommand; 86 | let cmd = await Command.createFromString( 87 | "clang -fsyntax-only " + c.args.join(" ") + " " + c.input.join(" ")); 88 | if (cmd) { 89 | let clangCmd = cmd as ClangCommand; 90 | clangCmd.bDumpAST = true; 91 | clangCmd.bDebug = false; 92 | clangCmd.bDisableOptnone = false; 93 | clangCmd.bPrintAfter = false; 94 | clangCmd.bPrintBefore = false; 95 | clangCmd.bPrintModuleScope = false; 96 | clangCmd.bSaveTemps = false; 97 | const { stdout, stderr } = await cmd.run(); 98 | this.ast = stdout; 99 | } 100 | } 101 | 102 | let input = this.command.getInputPath(); 103 | let index = input?.lastIndexOf('.'); 104 | let path = input?.substring(0, index); 105 | if (!path) { return; } 106 | this.preprocessed = readFileSync(path + ".i").toString(); 107 | // this.llvm = readFileSync(path + ".ll").toString(); 108 | } 109 | 110 | public parseLLVMDump(data: string) { 111 | const re = /^(# )?\*\*\* (.+) \*\*\*\s*(\(function\: ([^\)]+)\))?\:?\s*$/m; 112 | let pass = data.split(re); 113 | 114 | for (let i = 1; i < pass.length; i += 10) { 115 | let sharp = pass[i]; 116 | let name = pass[i + 1]; 117 | let func_name = pass[i + 3]; 118 | let ir = pass[i + 4]; 119 | let sharp2 = pass[i + 5]; 120 | let name2 = pass[i + 6]; 121 | let func_name2 = pass[i + 8]; 122 | let ir2 = pass[i + 9]; 123 | 124 | if (name.substring(15) !== name2.substring(14)) { 125 | console.log("name mismatch: " + name + " " + name2); 126 | } 127 | 128 | if (sharp === undefined) { 129 | if (sharp2 !== undefined) { 130 | console.log("sharp mismatch: " + sharp + " " + sharp2); 131 | } 132 | this.passList.push(new Pass( 133 | this, name.substring(15), ir, ir2, this.passList.length, false)); 134 | } else { 135 | if (sharp !== "# ") { 136 | console.log("sharp mismatch: " + sharp + " " + sharp2); 137 | } 138 | this.backendList.push(new Pass( 139 | this, name.substring(15), ir, ir2, this.backendList.length, true)); 140 | } 141 | } 142 | } 143 | 144 | public parseLLVMDumpChanged(data: string) { 145 | const re = /^(# )?\*\*\* (.+) \*\*\*\s*(\(function\: ([^\)]+)\))?\:?\s*$/m; 146 | const isMachineCode = /^# Machine code for function (.+)$/m; 147 | let pass = data.split(re); 148 | let last_ir = ""; 149 | for (let i = 1; i < pass.length; i += 5) { 150 | let sharp = pass[i]; 151 | let name = pass[i + 1]; 152 | let func_name = pass[i + 3]; 153 | let ir = pass[i + 4].trim(); 154 | if (ir == '') ir = last_ir; 155 | 156 | if (!isMachineCode.test(ir)) { 157 | this.passList.push(new Pass( 158 | this, name, last_ir, ir, this.passList.length, false)); 159 | } else { 160 | this.backendList.push(new Pass( 161 | this, name, last_ir, ir, this.backendList.length, true)); 162 | } 163 | 164 | last_ir = ir; 165 | } 166 | } 167 | public isCompare() { 168 | return false; 169 | } 170 | } 171 | 172 | export class ComparedPipeline extends Pipeline { 173 | constructor(raw: string, cmd: Command, 174 | public linked_pipeline?: Pipeline, public linked_pipeline2?: Pipeline) { 175 | super(raw, cmd); 176 | } 177 | 178 | public isCompare() { 179 | return true; 180 | } 181 | } 182 | 183 | export class FileWatcher { 184 | public path: string; 185 | public watcher: vscode.FileSystemWatcher; 186 | public dependentDocuments: vscode.TextDocument[] = []; 187 | 188 | constructor(private core: Core, path: string) { 189 | this.path = path; 190 | this.watcher = vscode.workspace.createFileSystemWatcher(path, false, false, true); 191 | this.watcher.onDidChange(this.onDidChange, this); 192 | this.watcher.onDidCreate(this.onDidChange, this); 193 | } 194 | 195 | onDidChange() { 196 | console.log("onDidChange", this.path); 197 | this.dependentDocuments.forEach(doc => { 198 | let { pipeline, catergory, index } = splitURI(doc.uri); 199 | this.core.runWithProgress(this.core.get(pipeline) as Pipeline); 200 | }); 201 | } 202 | } 203 | 204 | export class DocumentWatcher { 205 | private inputFiles = new Map(); 206 | private openURIs : vscode.Uri[] = []; 207 | 208 | constructor(private core: Core, subscriptions: vscode.Disposable[]) { 209 | core.watcher = this; 210 | subscriptions.push(vscode.workspace.onDidOpenTextDocument( 211 | this.onDidOpenTextDocument, this)); 212 | subscriptions.push(vscode.workspace.onDidCloseTextDocument( 213 | this.onDidCloseTextDocument, this)); 214 | } 215 | 216 | onDidOpenTextDocument(doc: vscode.TextDocument) { 217 | if (doc.uri.scheme !== 'vscode-llvm') return; 218 | this.openURIs.push(doc.uri); 219 | 220 | let { pipeline, catergory, index } = splitURI(doc.uri); 221 | if (pipeline) { 222 | let file = this.core.get(pipeline)?.command.getInputPath() as string; 223 | file = path.resolve(file); 224 | if (this.inputFiles.has(file)) { 225 | this.inputFiles.get(file)?.dependentDocuments.push(doc); 226 | } else { 227 | console.log("create new watcher for " + file); 228 | let watcher = new FileWatcher(this.core, file); 229 | watcher.dependentDocuments.push(doc); 230 | this.inputFiles.set(file, watcher); 231 | } 232 | } 233 | } 234 | 235 | onDidCloseTextDocument(doc: vscode.TextDocument) { 236 | if (doc.uri.scheme !== 'vscode-llvm') return; 237 | let d = this.openURIs.indexOf(doc.uri); 238 | if (d >= 0) { this.openURIs.splice(d, 1); } 239 | 240 | let { pipeline, catergory, index } = splitURI(doc.uri); 241 | if (pipeline) { 242 | let file = this.core.get(pipeline)?.command.getInputPath() as string; 243 | file = path.resolve(file); 244 | let watcher = this.inputFiles.get(file); 245 | if (!watcher) { return; } 246 | let index = watcher.dependentDocuments.indexOf(doc); 247 | if (index >= 0) { 248 | watcher.dependentDocuments.splice(index, 1); 249 | if (watcher.dependentDocuments.length === 0) { 250 | watcher.watcher.dispose(); 251 | this.inputFiles.delete(file); 252 | } 253 | } 254 | } 255 | } 256 | 257 | } 258 | 259 | export class Core { 260 | public watcher?: DocumentWatcher; 261 | private pipelines: Map = new Map(); 262 | public active?: Pipeline; 263 | private provider?: LLVMPipelineTreeDataProvider; 264 | public filter = ""; 265 | 266 | public get(cmd: string) { 267 | return this.pipelines.get(cmd); 268 | } 269 | 270 | public setProvider(provider: LLVMPipelineTreeDataProvider) { 271 | this.provider = provider; 272 | } 273 | 274 | public async runPipeline(cmd: string, cenv?: CommandEnv) { 275 | let command = await Command.createFromString(cmd); 276 | if (!command) { return; } 277 | if (cenv) { command.env = cenv; } 278 | if (this.filter != "") { command.setFilter(this.filter); } 279 | let pipeline = new Pipeline(cmd, command); 280 | this.pipelines.set(cmd, pipeline); 281 | this.active = pipeline; 282 | await this.runWithProgress(this.active); 283 | this.provider?.refresh(); 284 | } 285 | 286 | // This method is called when a command is selected from the command palette 287 | // It will ensure that the pipeline is running at once or already have been run 288 | public async ensurePipeline(cmd: string, cenv?: CommandEnv) { 289 | if (this.pipelines.has(cmd)) { 290 | this.active = this.pipelines.get(cmd); 291 | this.provider?.refresh(); 292 | return; 293 | } 294 | await this.runPipeline(cmd, cenv); 295 | } 296 | 297 | public async comparePipeline(cmd1: string, cmd2: string, cenv?: CommandEnv) { 298 | let cmd = cmd1 + " vs " + cmd2; 299 | if (this.pipelines.has(cmd)) { 300 | this.active = this.pipelines.get(cmd); 301 | this.provider?.refresh(); 302 | return; 303 | } 304 | 305 | await this.ensurePipeline(cmd1, cenv); 306 | await this.ensurePipeline(cmd2, cenv); 307 | let command = await Command.createFromString("compare"); 308 | if (!command) { return; } 309 | 310 | let pipeline = new ComparedPipeline(cmd, command, 311 | this.pipelines.get(cmd1), this.pipelines.get(cmd2)); 312 | this.pipelines.set(cmd, pipeline); 313 | this.active = pipeline; 314 | this.provider?.refresh(); 315 | } 316 | 317 | public async runWithProgress(pipeline: Pipeline) { 318 | return vscode.window.withProgress({ 319 | location: vscode.ProgressLocation.Window, 320 | cancellable: true, 321 | title: 'Run Clang Command' 322 | }, async (progress) => { 323 | progress.report({ increment: 0 }); 324 | if (pipeline) await pipeline.run(); 325 | progress.report({ increment: 100 }); 326 | }); 327 | } 328 | 329 | // run debug-only for a pass and filiter the output for that one 330 | public async debugOnePass(pass: Pass) { 331 | 332 | } 333 | 334 | } 335 | 336 | 337 | export class PipelineContentProvider implements vscode.TextDocumentContentProvider { 338 | constructor(private core: Core, subscriptions: vscode.Disposable[]) { 339 | subscriptions.push(vscode.workspace.registerTextDocumentContentProvider( 340 | PipelineContentProvider.scheme, this)); 341 | } 342 | 343 | public static readonly scheme = 'vscode-llvm'; 344 | 345 | provideTextDocumentContent(uri: vscode.Uri) { 346 | console.log("provideTextDocumentContent", uri); 347 | let { pipeline, catergory, index } = splitURI(uri); 348 | 349 | if (catergory == 'output') { 350 | return this.provideAsmDocument(uri)?.value; 351 | } else if (catergory == 'ast') { 352 | return this.core.get(pipeline)?.ast; 353 | } else if (catergory == 'preprocessed') { 354 | return this.core.get(pipeline)?.preprocessed; 355 | } else if (catergory == 'llvm') { 356 | return this.core.get(pipeline)?.llvm; 357 | } else if (catergory == 'before') { 358 | return this.core.get(pipeline)?.passList[index].before_ir; 359 | } else if (catergory == 'after') { 360 | return this.core.get(pipeline)?.passList[index].after_ir; 361 | } else if (catergory == 'before-b') { 362 | return this.core.get(pipeline)?.backendList[index].before_ir; 363 | } else if (catergory == 'after-b') { 364 | return this.core.get(pipeline)?.backendList[index].after_ir; 365 | } 366 | } 367 | 368 | private _documents = new Map(); 369 | provideAsmDocument(uri: vscode.Uri): AsmDocument | undefined{ 370 | let document = this._documents.get(uri.path); 371 | if (!document) { 372 | let { pipeline, catergory, index } = splitURI(uri); 373 | 374 | if (catergory == 'output') { 375 | let output = this.core.get(pipeline)?.output; 376 | if (!output) { return; } 377 | document = new AsmDocument(uri, output); 378 | this._documents.set(uri.path, document); 379 | } 380 | } 381 | return document; 382 | } 383 | 384 | private _onDidChange = new vscode.EventEmitter(); 385 | get onDidChange(): vscode.Event { 386 | return this._onDidChange.event; 387 | } 388 | } -------------------------------------------------------------------------------- /src/debug.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import * as path from 'path'; 3 | import {Pipeline} from './core'; 4 | 5 | export class Debug { 6 | constructor() {} 7 | 8 | public async saveConfig(pipeline: Pipeline) { 9 | if (vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length > 0) { 10 | let launchConfig = vscode.workspace.getConfiguration('launch', vscode.workspace.workspaceFolders[0].uri); 11 | console.log("save Config: ", launchConfig); 12 | let args = pipeline.command.getArgs(); 13 | 14 | let c = { 15 | "type": "lldb", 16 | "request": "launch", 17 | "name": `(lldb) Launch ${pipeline.command.exe}`, 18 | "program": pipeline.command.exe, 19 | "args": args, 20 | "cwd": "${workspaceFolder}" 21 | }; 22 | console.log("config", c); 23 | launchConfig.configurations.push(c); 24 | 25 | launchConfig.update('configurations', launchConfig.configurations, false); 26 | 27 | // launchConfig.update('configurations', launchConfig, true); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/decorator.ts: -------------------------------------------------------------------------------- 1 | // MIT License 2 | 3 | // Copyright (c) 2018-present Dmitry Gerasimov and contributors 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 | 23 | 24 | 'use strict'; 25 | 26 | import { TextEditor, window, TextEditorDecorationType, Range, ThemeColor, workspace, Uri, Disposable, TextEditorRevealType } from 'vscode'; 27 | import { AsmDocument } from './document'; 28 | import { AsmLine } from './asm'; 29 | import * as path from 'path'; 30 | import { PipelineContentProvider } from './core'; 31 | 32 | export class AsmDecorator { 33 | 34 | private srcEditor: TextEditor; 35 | private asmEditor: TextEditor; 36 | private provider: PipelineContentProvider; 37 | private selectedLineDecorationType: TextEditorDecorationType; 38 | private unusedLineDecorationType: TextEditorDecorationType; 39 | private registrations: Disposable; 40 | private document!: AsmDocument; 41 | 42 | // mappings from source lines to assembly lines 43 | private mappings = new Map(); 44 | 45 | constructor(srcEditor: TextEditor, asmEditor: TextEditor, provider: PipelineContentProvider) { 46 | this.srcEditor = srcEditor; 47 | this.asmEditor = asmEditor; 48 | this.provider = provider; 49 | 50 | this.selectedLineDecorationType = window.createTextEditorDecorationType({ 51 | isWholeLine: true, 52 | backgroundColor: new ThemeColor('editor.findMatchHighlightBackground'), 53 | overviewRulerColor: new ThemeColor('editorOverviewRuler.findMatchForeground') 54 | }); 55 | 56 | this.unusedLineDecorationType = window.createTextEditorDecorationType({ 57 | opacity: '0.5' 58 | }); 59 | 60 | const uri = asmEditor.document.uri; 61 | // rebuild decorations on asm document change 62 | const providerEventRegistration = provider.onDidChange(changedUri => { 63 | if (changedUri.toString() === uri.toString()) { 64 | this.load(uri); 65 | } 66 | }); 67 | this.load(uri); 68 | 69 | this.registrations = Disposable.from( 70 | this.selectedLineDecorationType, 71 | this.unusedLineDecorationType, 72 | providerEventRegistration, 73 | window.onDidChangeTextEditorSelection(e => { 74 | this.updateSelection(e.textEditor); 75 | }), 76 | window.onDidChangeVisibleTextEditors(editors => { 77 | // decorations are useless if one of editors become invisible 78 | if (editors.indexOf(srcEditor) === -1 || editors.indexOf(asmEditor) === -1) { 79 | this.dispose(); 80 | } 81 | }) 82 | ); 83 | } 84 | 85 | dispose(): void { 86 | this.registrations.dispose(); 87 | } 88 | 89 | private load(uri: Uri) { 90 | let document = this.provider.provideAsmDocument(uri); 91 | if (document === undefined) return; 92 | this.document = document; 93 | 94 | this.loadMappings(); 95 | 96 | const dimUnused = workspace.getConfiguration('vscode-llvm') 97 | .get('dimUnusedSourceLines', true); 98 | 99 | if (dimUnused) { 100 | this.dimUnusedSourceLines(); 101 | } 102 | } 103 | 104 | private asmLineHasSource(asmLine: AsmLine) { 105 | const sourcePath = this.srcEditor.document.uri.path; 106 | const asmLineSourcePath = asmLine.source?.file; 107 | 108 | if (asmLineSourcePath === undefined) { 109 | return false; 110 | } 111 | 112 | const asmLineSourceBasename = path.basename(asmLineSourcePath); 113 | 114 | // assembly may contain lines from different source files, 115 | // thus we should check that line comes from current opened file 116 | if (!sourcePath.endsWith(asmLineSourceBasename)) { 117 | return false; 118 | } 119 | 120 | return true; 121 | } 122 | 123 | private loadMappings() { 124 | this.mappings.clear(); 125 | 126 | this.document.lines.forEach((line, index) => { 127 | if (!this.asmLineHasSource(line)) { 128 | return; 129 | } 130 | const sourceLine = line.source!.line - 1; 131 | if (this.mappings.get(sourceLine) === undefined) { 132 | this.mappings.set(sourceLine, []); 133 | } 134 | this.mappings.get(sourceLine)!.push(index); 135 | }); 136 | } 137 | 138 | updateSelection(editor: TextEditor): void { 139 | if (editor === this.srcEditor) { 140 | this.srcLineSelected(this.srcEditor.selection.start.line); 141 | } else if (editor === this.asmEditor) { 142 | this.asmLineSelected(this.asmEditor.selection.start.line); 143 | } 144 | } 145 | 146 | private dimUnusedSourceLines() { 147 | const unusedSourceLines: Range[] = []; 148 | for (let line = 0; line < this.srcEditor.document.lineCount; line++) { 149 | if (this.mappings.get(line) === undefined) { 150 | unusedSourceLines.push(this.srcEditor.document.lineAt(line).range); 151 | } 152 | } 153 | this.srcEditor.setDecorations(this.unusedLineDecorationType, unusedSourceLines); 154 | } 155 | 156 | private srcLineSelected(line: number) { 157 | const srcLineRange = this.srcEditor.document.lineAt(line).range; 158 | this.srcEditor.setDecorations(this.selectedLineDecorationType, [srcLineRange]); 159 | 160 | const asmLinesRanges: Range[] = []; 161 | const mapped = this.mappings.get(line); 162 | if (mapped !== undefined) { 163 | mapped.forEach(line => { 164 | if (line >= this.asmEditor.document.lineCount) { 165 | return; 166 | } 167 | asmLinesRanges.push(this.asmEditor.document.lineAt(line).range); 168 | }); 169 | } 170 | this.asmEditor.setDecorations(this.selectedLineDecorationType, asmLinesRanges); 171 | 172 | if (asmLinesRanges.length > 0) { 173 | this.asmEditor.revealRange(asmLinesRanges[0], TextEditorRevealType.InCenterIfOutsideViewport); 174 | } 175 | } 176 | 177 | private asmLineSelected(line: number) { 178 | const asmLine = this.document.lines[line]; 179 | 180 | const asmLineRange = this.asmEditor.document.lineAt(line).range; 181 | this.asmEditor.setDecorations(this.selectedLineDecorationType, [asmLineRange]); 182 | 183 | if (this.asmLineHasSource(asmLine)) { 184 | const srcLineRange = this.srcEditor.document.lineAt(asmLine.source!.line - 1).range; 185 | this.srcEditor.setDecorations(this.selectedLineDecorationType, [srcLineRange]); 186 | this.srcEditor.revealRange(srcLineRange, TextEditorRevealType.InCenterIfOutsideViewport); 187 | } else { 188 | this.srcEditor.setDecorations(this.selectedLineDecorationType, []); 189 | } 190 | } 191 | 192 | } 193 | -------------------------------------------------------------------------------- /src/document.ts: -------------------------------------------------------------------------------- 1 | // MIT License 2 | 3 | // Copyright (c) 2018-present Dmitry Gerasimov and contributors 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 | 23 | // Small changes maded by sunxfancy to make it work with our extension 24 | 25 | 'use strict'; 26 | 27 | import { workspace, Uri, EventEmitter, FileSystemWatcher } from 'vscode'; 28 | import { AsmParser, AsmLine, AsmFilter } from './asm'; 29 | 30 | export class AsmDocument { 31 | private _uri: Uri; 32 | lines: AsmLine[] = []; 33 | sourceToAsmMapping = new Map(); 34 | 35 | constructor(uri: Uri, doc: string, useBinaryParsing = false) { 36 | const filter = new AsmFilter(); 37 | filter.binary = useBinaryParsing; 38 | 39 | this.lines = new AsmParser().process(doc, filter).asm; 40 | this._uri = uri; 41 | } 42 | 43 | get value(): string { 44 | return this.lines.reduce((result, line) => result += line.value, ''); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | // The module 'vscode' contains the VS Code extensibility API 2 | // Import the module and reference it with the alias vscode in your code below 3 | import * as vscode from 'vscode'; 4 | import * as path from 'path'; 5 | import { ConfigViewProvider } from './control-panel'; 6 | import { LLVMPipelineTreeDataProvider, LLVMPipelineTreeItemDecorationProvider } from './pipeline-panel'; 7 | import { LLVMAvailablePassDataProvider } from './available-panel'; 8 | import { ComparedPipeline, Core, DocumentWatcher, Pass, PipelineContentProvider } from './core'; 9 | import { Debug } from './debug'; 10 | import { checkFileExists, ensurePropertiesFile } from './config'; 11 | import { Command } from './clang'; 12 | import { AsmDecorator } from './decorator'; 13 | import { get } from 'http'; 14 | 15 | // this method is called when your extension is activated 16 | // your extension is activated the very first time the command is executed 17 | export async function activate(context: vscode.ExtensionContext) { 18 | 19 | function registerCommand(cmd: string, cb: (...args: any[]) => any) { 20 | let command = vscode.commands.registerCommand(cmd, cb); 21 | context.subscriptions.push(command); 22 | } 23 | 24 | if (vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length > 0) { 25 | for (let i: number = 0; i < vscode.workspace.workspaceFolders.length; ++i) { 26 | const config: string = path.join(vscode.workspace.workspaceFolders[i].uri.fsPath, ".vscode/llvm.json"); 27 | console.log(config); 28 | if (await checkFileExists(config)) { 29 | let doc = await vscode.workspace.openTextDocument(config); 30 | vscode.languages.setTextDocumentLanguage(doc, "jsonc"); 31 | } 32 | } 33 | } 34 | 35 | let core = new Core(); 36 | const provider = new ConfigViewProvider(context.extensionUri, core, context.subscriptions); 37 | new LLVMPipelineTreeDataProvider(core, context.subscriptions); 38 | let contentProvider = new PipelineContentProvider(core, context.subscriptions); 39 | new LLVMPipelineTreeItemDecorationProvider(core, context.subscriptions); 40 | new LLVMAvailablePassDataProvider(context.subscriptions); 41 | new DocumentWatcher(core, context.subscriptions); 42 | 43 | registerCommand('vscode-llvm.reloadConfig', () => { 44 | provider.reloadConfig(); 45 | vscode.window.showInformationMessage('reload config done from vscode-llvm!'); 46 | }); 47 | 48 | registerCommand('llvmPipelineView.settings', async () => { 49 | // The code you place here will be executed every time your command is executed 50 | // Display a message box to the user 51 | await ensurePropertiesFile(); 52 | 53 | // Open the first workspace settings 54 | if (vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length > 0) { 55 | let config = path.join(vscode.workspace.workspaceFolders[0].uri.fsPath, '.vscode', 'llvm.json'); 56 | let doc = await vscode.workspace.openTextDocument(config); 57 | let edit1 = await vscode.window.showTextDocument(doc, undefined, true); 58 | console.log(edit1); 59 | vscode.window.showInformationMessage('llvmPipelineView.settings from vscode-llvm!'); 60 | } 61 | }); 62 | 63 | registerCommand('llvmPipelineView.open', async (pass: Pass) => { 64 | let doc = await vscode.workspace.openTextDocument(vscode.Uri.parse( 65 | `vscode-llvm:/${pass.parent.encodeCommand()}/before${pass.backend ? "-b" : ""}/${pass.index}`)); 66 | let doc2 = await vscode.workspace.openTextDocument(vscode.Uri.parse( 67 | `vscode-llvm:/${pass.parent.encodeCommand()}/after${pass.backend ? "-b" : ""}/${pass.index}`)); 68 | vscode.languages.setTextDocumentLanguage(doc, 'llvm'); 69 | vscode.languages.setTextDocumentLanguage(doc2, 'llvm'); 70 | console.log("openPipelineView vscode.diff"); 71 | vscode.commands.executeCommand("vscode.diff", doc.uri, doc2.uri); 72 | }); 73 | 74 | registerCommand('llvmPipelineView.openCompare', async (pass: Pass, pass2: Pass) => { 75 | let doc = await vscode.workspace.openTextDocument(vscode.Uri.parse( 76 | `vscode-llvm:/${pass.parent.encodeCommand()}/after${pass.backend ? "-b" : ""}/${pass.index}`)); 77 | let doc2 = await vscode.workspace.openTextDocument(vscode.Uri.parse( 78 | `vscode-llvm:/${pass2.parent.encodeCommand()}/after${pass2.backend ? "-b" : ""}/${pass2.index}`)); 79 | vscode.languages.setTextDocumentLanguage(doc, 'llvm'); 80 | vscode.languages.setTextDocumentLanguage(doc2, 'llvm'); 81 | console.log("openPipelineView vscode.diff"); 82 | vscode.commands.executeCommand("vscode.diff", doc.uri, doc2.uri); 83 | }); 84 | 85 | registerCommand('llvmPipelineView.ensure', async (activeCmd: string) => { 86 | if (activeCmd && activeCmd.length > 0) { 87 | if (vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length > 0) 88 | process.chdir(vscode.workspace.workspaceFolders[0].uri.fsPath); 89 | console.log("current dir:", process.cwd()); 90 | core.ensurePipeline(activeCmd); 91 | } 92 | }); 93 | 94 | registerCommand('llvmPipelineView.run', async (activeCmd: string) => { 95 | if (activeCmd && activeCmd.length > 0) { 96 | if (vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length > 0) 97 | process.chdir(vscode.workspace.workspaceFolders[0].uri.fsPath); 98 | console.log("current dir:", process.cwd()); 99 | core.runPipeline(activeCmd); 100 | } 101 | }); 102 | 103 | registerCommand('llvmPipelineView.run-debug', async () => { 104 | if (vscode.window.activeTextEditor && core.active) { 105 | let debugConfig = new Debug(); 106 | debugConfig.saveConfig(core.active); 107 | } 108 | }); 109 | 110 | registerCommand('llvmPipelineView.compare', async (cmd1: string, cmd2: string) => { 111 | if (vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length > 0) 112 | process.chdir(vscode.workspace.workspaceFolders[0].uri.fsPath); 113 | console.log("current dir:", process.cwd()); 114 | 115 | core.comparePipeline(cmd1, cmd2); 116 | }); 117 | 118 | function registerCommandForUri(cmd: string, component: string) { 119 | function getLanguage(component: string) { 120 | if (component == "output") return "vscode-llvm.disassembly"; 121 | if (component == "preprocessed") return "cpp"; 122 | if (component == "llvm") return "llvm"; 123 | if (component == "ast") return "vscode-llvm.llvm-ast"; 124 | return "plaintext"; 125 | } 126 | 127 | let command = vscode.commands.registerCommand(cmd, async () => { 128 | if (core.active?.isCompare() == false) { 129 | let raw = core.active?.raw_command; 130 | if (!raw) return; 131 | let uri = vscode.Uri.parse(`vscode-llvm:/${core.active?.encodeCommand()}/${component}`); 132 | let doc = await vscode.workspace.openTextDocument(uri); 133 | vscode.languages.setTextDocumentLanguage(doc, getLanguage(component)); 134 | vscode.window.showTextDocument(doc, { preserveFocus: true, preview: true }); 135 | } else { 136 | let cpipe = core.active as ComparedPipeline; 137 | let raw1 = cpipe.linked_pipeline?.raw_command; 138 | let raw2 = cpipe.linked_pipeline2?.raw_command; 139 | if (!raw1 || !raw2) return; 140 | let doc1 = await vscode.workspace.openTextDocument(vscode.Uri.parse(`vscode-llvm:/${cpipe.linked_pipeline?.encodeCommand()}/${component}`)); 141 | let doc2 = await vscode.workspace.openTextDocument(vscode.Uri.parse(`vscode-llvm:/${cpipe.linked_pipeline2?.encodeCommand()}/${component}`)); 142 | vscode.languages.setTextDocumentLanguage(doc1, getLanguage(component)); 143 | vscode.languages.setTextDocumentLanguage(doc2, getLanguage(component)); 144 | 145 | vscode.commands.executeCommand("vscode.diff", doc1.uri, doc2.uri); 146 | } 147 | }); 148 | context.subscriptions.push(command); 149 | } 150 | 151 | registerCommandForUri('llvmPipelineView.openOutput', "output"); 152 | registerCommandForUri('llvmPipelineView.openAST', "ast"); 153 | registerCommandForUri('llvmPipelineView.openPreprocessed', "preprocessed"); 154 | registerCommandForUri('llvmPipelineView.openLLVM', "llvm"); 155 | 156 | registerCommand('llvmPipelineView.openInput', async (file: string) => { 157 | if (!vscode.workspace.workspaceFolders || vscode.workspace.workspaceFolders.length == 0) 158 | return; 159 | 160 | if (!path.isAbsolute(file)) { 161 | file = path.join(vscode.workspace.workspaceFolders[0].uri.fsPath, file); 162 | } 163 | 164 | let doc = await vscode.workspace.openTextDocument(file); 165 | vscode.window.showTextDocument(doc, { preserveFocus: true, preview: true }); 166 | }); 167 | 168 | registerCommand('llvmAvailableView.runAvailPass', async (pass: string) => { 169 | if (!vscode.workspace.workspaceFolders || vscode.workspace.workspaceFolders.length == 0) 170 | return; 171 | 172 | process.chdir(vscode.workspace.workspaceFolders[0].uri.fsPath); 173 | // get the current open file 174 | let editor = vscode.window.activeTextEditor; 175 | if (!editor) return; 176 | 177 | const optPath = vscode.workspace.getConfiguration('vscode-llvm') 178 | .get('optPath', 'opt'); 179 | 180 | let cmd = await Command.createFromString(optPath + ' -S ' + pass + ' -o -'); 181 | if (cmd == undefined) return; 182 | const { stdout, stderr } = await cmd.run(undefined, editor?.document.getText()); 183 | if (stdout && stdout.length > 0) { 184 | let outdoc = await vscode.workspace.openTextDocument({ content: stdout, language: 'llvm' }); 185 | await vscode.window.showTextDocument(outdoc, undefined, true); 186 | } 187 | 188 | if (pass.indexOf('dot') != -1) { 189 | let result = stderr.match(/Writing '(.*)'.../g); 190 | if (result && result.length == 1) { 191 | vscode.window.showInformationMessage(result[0]); 192 | } 193 | } 194 | }); 195 | 196 | 197 | // AsmView gives a better experience than the old output view. 198 | // It shows side-by-side source code and assembly code. 199 | // It also highlights the corresponding lines in assembly code when you select a line in source code. 200 | 201 | registerCommand('llvmAsmView.openOutput', async () => { 202 | let input = core.active?.command.getInputPath(); 203 | if (!input) return; 204 | let raw = core.active?.raw_command; 205 | if (!raw) return; 206 | 207 | // get the path if it is not absolute 208 | if (!path.isAbsolute(input)) { 209 | if (!vscode.workspace.workspaceFolders || vscode.workspace.workspaceFolders.length == 0) 210 | return; 211 | input = path.join(vscode.workspace.workspaceFolders[0].uri.fsPath, input); 212 | } 213 | 214 | // open the file in workspace 215 | let srcDoc = await vscode.workspace.openTextDocument(input); 216 | let srcEditor = await vscode.window.showTextDocument(srcDoc, { preserveFocus: true, preview: true }); 217 | 218 | const asmUri = vscode.Uri.parse(`vscode-llvm:/${core.active?.encodeCommand()}/output`); 219 | 220 | const options = { 221 | viewColumn: srcEditor.viewColumn! + 1, 222 | preserveFocus: true, 223 | }; 224 | 225 | let asmDoc = await vscode.workspace.openTextDocument(asmUri); 226 | let asmEditor = await vscode.window.showTextDocument(asmDoc, options); 227 | vscode.languages.setTextDocumentLanguage(asmDoc, 'vscode-llvm.disassembly'); 228 | const decorator = new AsmDecorator(srcEditor, asmEditor, contentProvider); 229 | setTimeout(() => decorator.updateSelection(srcEditor), 500); 230 | }); 231 | 232 | } 233 | 234 | // this method is called when your extension is deactivated 235 | export function deactivate() { } 236 | -------------------------------------------------------------------------------- /src/pipeline-panel.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import { ComparedPipeline, Core, Pass } from './core'; 3 | import { FileDecoration, FileDecorationProvider, ProviderResult, ThemeColor, Uri } from "vscode"; 4 | import { splitURI } from './utils'; 5 | 6 | /** This class will decorate the tree view and dim the item when the IRs are same */ 7 | export class LLVMPipelineTreeItemDecorationProvider implements FileDecorationProvider { 8 | constructor(private core: Core, subscriptions: vscode.Disposable[]) { 9 | subscriptions.push(vscode.window.registerFileDecorationProvider(this)); 10 | } 11 | 12 | private readonly dimColor = new ThemeColor("list.deemphasizedForeground"); 13 | 14 | public provideFileDecoration(uri: Uri): ProviderResult { 15 | if (uri.scheme !== "vscode-llvm") return; 16 | 17 | let { pipeline, catergory, index } = splitURI(uri); 18 | 19 | if (catergory == 'before') { 20 | if (this.core.get(pipeline)?.passList[index].same) { 21 | return { color: this.dimColor }; 22 | } 23 | } 24 | 25 | if (catergory == 'before-b') { 26 | if (this.core.get(pipeline)?.backendList[index].same) { 27 | return { color: this.dimColor }; 28 | } 29 | } 30 | } 31 | } 32 | 33 | /** 34 | * This class provides the content for the tree view. 35 | */ 36 | export class PipelineNode { 37 | private children: PipelineNode[] = []; 38 | 39 | constructor( 40 | private label: string, 41 | private parent?: PipelineNode, 42 | private pass?: Pass, 43 | private pass2?: Pass) { 44 | parent?.children.push(this); 45 | } 46 | 47 | public removeChildren() { 48 | this.children = []; 49 | } 50 | 51 | public isLeaf() { 52 | return this.getParent() ? true : false; 53 | } 54 | 55 | public createNodeForDiff(core: Core, passList1?: Pass[], passList2?: Pass[]) { 56 | if (!passList1 || !passList2) return; 57 | 58 | let theSame = new PipelineNode("The Same", this); 59 | let diff = new PipelineNode("Different", this); 60 | 61 | let min = Math.min(passList1.length, passList2.length); 62 | let i = 0; 63 | for (; i < min; ++i) { 64 | let pass1 = passList1[i]; 65 | let pass2 = passList2[i]; 66 | if (pass1.name === pass2.name && 67 | pass1.before_ir === pass2.before_ir && 68 | pass1.after_ir === pass2.after_ir) { 69 | new PipelineNode(pass1.name, theSame, pass1); 70 | } else { 71 | break; 72 | } 73 | } 74 | 75 | let diff0 = new PipelineNode("Different in Common", diff); 76 | for (let j = i; j < min; ++j) { 77 | let pass1 = passList1[j]; 78 | let pass2 = passList2[j]; 79 | new PipelineNode(pass1.name + " vs " + pass2.name, diff0, pass1, pass2); 80 | } 81 | 82 | let diff1 = new PipelineNode("Different in 1", diff); 83 | for (let j = i; j < passList1.length; ++j) { 84 | let pass1 = passList1[j]; 85 | new PipelineNode(pass1.name, diff1, pass1); 86 | } 87 | 88 | let diff2 = new PipelineNode("Different in 2", diff); 89 | for (let j = min; j < passList2.length; ++j) { 90 | let pass2 = passList2[j]; 91 | new PipelineNode(pass2.name, diff2, pass2); 92 | } 93 | } 94 | 95 | public getChildren(core: Core) { 96 | if (this.children.length > 0) { 97 | return this.children; 98 | } 99 | if (core.active) { 100 | if (this.label === 'input') { 101 | let name = core.active.command.getInputPath(); 102 | if (name) { 103 | new PipelineNode(name, this); 104 | } 105 | } 106 | if (this.label === 'front end') { 107 | new PipelineNode("After Preprocessing", this); 108 | new PipelineNode("Clang AST", this); 109 | new PipelineNode("LLVM IR", this); 110 | } 111 | if (this.label === 'middle end') { 112 | if (core.active?.isCompare() == false) { 113 | for (let pass of core.active.passList) { 114 | new PipelineNode(pass.name, this, pass); 115 | } 116 | } else { 117 | let active = (core.active as ComparedPipeline); 118 | let passList1 = active.linked_pipeline?.passList; 119 | let passList2 = active.linked_pipeline2?.passList; 120 | this.createNodeForDiff(core, passList1, passList2); 121 | } 122 | } 123 | if (this.label === 'back end') { 124 | if (core.active?.isCompare() == false) { 125 | for (let pass of core.active.backendList) { 126 | new PipelineNode(pass.name, this, pass); 127 | } 128 | } else { 129 | let active = (core.active as ComparedPipeline); 130 | let passList1 = active.linked_pipeline?.backendList; 131 | let passList2 = active.linked_pipeline2?.backendList; 132 | this.createNodeForDiff(core, passList1, passList2); 133 | } 134 | } 135 | } 136 | return this.children; 137 | } 138 | 139 | public getParent() { 140 | return this.parent; 141 | } 142 | 143 | public getTreeItem(core: Core): vscode.TreeItem { 144 | var cmd; 145 | if (!this.getParent() && this.label != 'output') { cmd = void 0; } 146 | else { 147 | if (this.label === 'output') { 148 | if (core.active?.isCompare() == false) { 149 | cmd = { 150 | // command: 'llvmPipelineView.openOutput', 151 | // Now, we use AsmView instead the old output view. 152 | command: 'llvmAsmView.openOutput', 153 | title: 'Open Output' 154 | }; 155 | } else { 156 | cmd = { 157 | command: 'llvmPipelineView.openOutput', 158 | title: 'Open Output' 159 | }; 160 | } 161 | } else if (this.pass) { 162 | if (this.pass2 === undefined) { 163 | cmd = { 164 | command: 'llvmPipelineView.open', 165 | arguments: [this.pass], 166 | title: 'Open Pipeline Compare View' 167 | }; 168 | } else { 169 | cmd = { 170 | command: 'llvmPipelineView.openCompare', 171 | arguments: [this.pass, this.pass2], 172 | title: 'Open Pipeline Compare View' 173 | }; 174 | } 175 | 176 | } else if (this.label === 'After Preprocessing') { 177 | cmd = { 178 | command: 'llvmPipelineView.openPreprocessed', 179 | title: 'Open Preprocessed' 180 | }; 181 | } else if (this.label === 'Clang AST') { 182 | cmd = { 183 | command: 'llvmPipelineView.openAST', 184 | title: 'Open AST' 185 | }; 186 | } else if (this.label === 'LLVM IR') { 187 | cmd = { 188 | command: 'llvmPipelineView.openLLVM', 189 | title: 'Open LLVM IR' 190 | }; 191 | } else if (this.parent?.label === 'input') { 192 | cmd = { 193 | command: 'llvmPipelineView.openInput', 194 | arguments: [this.label], 195 | title: 'Open Input' 196 | }; 197 | } 198 | } 199 | 200 | const tooltip = new vscode.MarkdownString(`$(zap) Tooltip for ${this.label}`, true); 201 | return { 202 | label: { label: this.label }, 203 | tooltip, 204 | collapsibleState: core.active ? 205 | (this.getChildren(core).length > 0 ? 206 | vscode.TreeItemCollapsibleState.Collapsed : vscode.TreeItemCollapsibleState.None) 207 | : vscode.TreeItemCollapsibleState.None, 208 | command: cmd, 209 | 210 | // TODO: change this uri for the different in common node. 211 | resourceUri: this.pass ? vscode.Uri.parse( 212 | `vscode-llvm:/${this.pass.parent.encodeCommand()}/`+ 213 | `before${this.pass.backend ? "-b" : ""}/${this.pass.index}`) : undefined 214 | }; 215 | } 216 | }; 217 | 218 | 219 | export class LLVMPipelineTreeDataProvider implements vscode.TreeDataProvider { 220 | constructor(private core: Core, subscriptions: vscode.Disposable[]) { 221 | core.setProvider(this); 222 | subscriptions.push(vscode.window.createTreeView( 223 | 'llvm-pipeline-view', { treeDataProvider: this, showCollapseAll: true })); 224 | } 225 | private _onDidChangeTreeData = new vscode.EventEmitter(); 226 | readonly onDidChangeTreeData = this._onDidChangeTreeData.event; 227 | 228 | public refresh(): void { 229 | for (let node of LLVMPipelineTreeDataProvider.tree_nodes) { 230 | node.removeChildren(); 231 | } 232 | this._onDidChangeTreeData.fire(); 233 | } 234 | 235 | getChildren(element?: PipelineNode): PipelineNode[] { 236 | if (!element) { 237 | return LLVMPipelineTreeDataProvider.tree_nodes; 238 | } else { 239 | return element.getChildren(this.core); 240 | } 241 | } 242 | 243 | getTreeItem(element: PipelineNode): vscode.TreeItem { 244 | return element.getTreeItem(this.core); 245 | } 246 | 247 | getParent(element: PipelineNode) { 248 | return element.getParent(); 249 | } 250 | 251 | static readonly tree_nodes = [ 252 | new PipelineNode('input'), 253 | new PipelineNode('front end'), 254 | new PipelineNode('middle end'), 255 | new PipelineNode('back end'), 256 | new PipelineNode('output') 257 | ]; 258 | } 259 | 260 | -------------------------------------------------------------------------------- /src/test/runTest.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | 3 | import { runTests } from '@vscode/test-electron'; 4 | 5 | async function main() { 6 | try { 7 | // The folder containing the Extension Manifest package.json 8 | // Passed to `--extensionDevelopmentPath` 9 | const extensionDevelopmentPath = path.resolve(__dirname, '../../'); 10 | 11 | // The path to test runner 12 | // Passed to --extensionTestsPath 13 | const extensionTestsPath = path.resolve(__dirname, './suite/index'); 14 | 15 | // Download VS Code, unzip it and run the integration test 16 | await runTests({ extensionDevelopmentPath, extensionTestsPath }); 17 | } catch (err) { 18 | console.error('Failed to run tests'); 19 | process.exit(1); 20 | } 21 | } 22 | 23 | main(); 24 | -------------------------------------------------------------------------------- /src/test/suite/clang.test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | 3 | import { Command, ClangCommand, NVCCCommand } from '../../clang'; 4 | 5 | suite('Command Test Suite', () => { 6 | console.log('Start all tests.'); 7 | var path = require('path'); 8 | let appRoot = path.resolve(__dirname); 9 | console.log(appRoot); 10 | process.chdir(path.join(appRoot, '..', '..', '..', 'example')); 11 | 12 | test('clang++ commands test', async () => { 13 | let cmd = await Command.createFromString("clang++ -std=c++17 -Wall -Wextra -Wpedantic -Werror -o a.exe main.cpp"); 14 | let clang_cmd = cmd as ClangCommand; 15 | 16 | assert.strictEqual(clang_cmd.getInputPath(), "main.cpp"); 17 | assert.strictEqual(clang_cmd.getOutputPath(), "a.exe"); 18 | 19 | let pp = clang_cmd.subCommands[0]; 20 | assert.strictEqual(pp.getType(), "CC1Command"); 21 | assert.strictEqual(pp.getOutputPath(), "main.ii"); 22 | console.log(pp.toString()); 23 | 24 | }); 25 | 26 | test('nvcc commands test', async () => { 27 | let cmd = await Command.createFromString("nvcc -std=c++17 -arch=sm_75 -o a.exe main.cu"); 28 | let nvcc_cmd = cmd as NVCCCommand; 29 | 30 | // assert.strictEqual(nvcc_cmd.getInputPath(), "main.cu"); 31 | // assert.strictEqual(nvcc_cmd.getOutputPath(), "a.exe"); 32 | assert.notStrictEqual(nvcc_cmd.subCommands.length, 0); 33 | let pp = nvcc_cmd.subCommands[0]; 34 | console.log(pp.toString()); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /src/test/suite/extension.test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'assert'; 2 | 3 | // You can import and use all API from the 'vscode' module 4 | // as well as import your extension to test it 5 | import * as vscode from 'vscode'; 6 | // import * as myExtension from '../../extension'; 7 | 8 | suite('Extension Test Suite', () => { 9 | vscode.window.showInformationMessage('Start all tests.'); 10 | 11 | test('Sample test', () => { 12 | assert.strictEqual(-1, [1, 2, 3].indexOf(5)); 13 | assert.strictEqual(-1, [1, 2, 3].indexOf(0)); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /src/test/suite/index.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import * as Mocha from 'mocha'; 3 | import * as glob from 'glob'; 4 | 5 | export function run(): Promise { 6 | // Create the mocha test 7 | const mocha = new Mocha({ 8 | ui: 'tdd', 9 | color: true 10 | }); 11 | 12 | const testsRoot = path.resolve(__dirname, '..'); 13 | 14 | return new Promise((c, e) => { 15 | glob('**/**.test.js', { cwd: testsRoot }, (err, files) => { 16 | if (err) { 17 | return e(err); 18 | } 19 | 20 | // Add files to the test suite 21 | files.forEach(f => mocha.addFile(path.resolve(testsRoot, f))); 22 | 23 | try { 24 | // Run the mocha test 25 | mocha.run(failures => { 26 | if (failures > 0) { 27 | e(new Error(`${failures} tests failed.`)); 28 | } else { 29 | c(); 30 | } 31 | }); 32 | } catch (err) { 33 | console.error(err); 34 | e(err); 35 | } 36 | }); 37 | }); 38 | } 39 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------- 2 | // This file contains a list of utility functions 3 | // -------------------------------------------------------------- 4 | 5 | import * as vscode from 'vscode'; 6 | 7 | export function splitURI(uri: vscode.Uri): { pipeline: string, catergory: string, index: number } { 8 | let path = uri.path.split('/').map(decodeURIComponent); 9 | if (path.length <= 2) return { pipeline: path[1], catergory: path[2], index: -1 }; 10 | return { pipeline: path[1], catergory: path[2], index: Number(path[3]) }; 11 | } 12 | 13 | export function parseShellArg(str: string): string[] | undefined { 14 | return str.match(/"[^"]+"|'[^']+'|\S+/g)?.map( 15 | x => (x[0] === '\"' || x[0] === '\'') ? x.substring(1, x.length - 1) : x); 16 | } 17 | 18 | export function getNonce() { 19 | let text = ''; 20 | const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; 21 | for (let i = 0; i < 32; i++) { 22 | text += possible.charAt(Math.floor(Math.random() * possible.length)); 23 | } 24 | return text; 25 | } 26 | 27 | 28 | // -------------------------------------------------------------- 29 | // The following code is for asm.ts 30 | // -------------------------------------------------------------- 31 | 32 | // Copyright (c) 2016, Matt Godbolt 33 | // All rights reserved. 34 | // 35 | // Redistribution and use in source and binary forms, with or without 36 | // modification, are permitted provided that the following conditions are met: 37 | // 38 | // * Redistributions of source code must retain the above copyright notice, 39 | // this list of conditions and the following disclaimer. 40 | // * Redistributions in binary form must reproduce the above copyright 41 | // notice, this list of conditions and the following disclaimer in the 42 | // documentation and/or other materials provided with the distribution. 43 | // 44 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 45 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 46 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 47 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 48 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 49 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 50 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 51 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 52 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 53 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 54 | // POSSIBILITY OF SUCH DAMAGE. 55 | 56 | const tabsRe = /\t/g; 57 | const lineRe = /\r?\n/; 58 | const findQuotes = /(.*?)("(?:[^"\\]|\\.)*")(.*)/; 59 | 60 | export function splitLines(text: string): string[] { 61 | const result = text.split(lineRe); 62 | if (result.length > 0 && result[result.length - 1] === '') { 63 | return result.slice(0, result.length - 1); 64 | } 65 | return result; 66 | } 67 | 68 | export function expandTabs(line: string): string { 69 | let extraChars = 0; 70 | return line.replace(tabsRe, function (_match, offset) { 71 | const total = offset + extraChars; 72 | const spacesNeeded = (total + 8) & 7; 73 | extraChars += spacesNeeded - 1; 74 | return " ".substring(spacesNeeded); 75 | }); 76 | } 77 | 78 | export function squashHorizontalWhitespace(line: string, atStart: boolean): string { 79 | const quotes = line.match(findQuotes); 80 | if (quotes) { 81 | return squashHorizontalWhitespace(quotes[1], atStart) + quotes[2] + 82 | squashHorizontalWhitespace(quotes[3], false); 83 | } 84 | const splat = line.split(/\s+/); 85 | if (splat[0] === "" && atStart) { 86 | // An indented line: preserve a two-space indent 87 | return " " + splat.slice(1).join(" "); 88 | } else { 89 | return splat.join(" "); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "ES2020", 5 | "lib": [ 6 | "ES2020" 7 | ], 8 | "sourceMap": true, 9 | "rootDir": "src", 10 | "strict": true /* enable all strict type-checking options */ 11 | /* Additional Checks */ 12 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 13 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 14 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /vsc-extension-quickstart.md: -------------------------------------------------------------------------------- 1 | # Welcome to your VS Code Extension 2 | 3 | ## What's in the folder 4 | 5 | * This folder contains all of the files necessary for your extension. 6 | * `package.json` - this is the manifest file in which you declare your extension and command. 7 | * The sample plugin registers a command and defines its title and command name. With this information VS Code can show the command in the command palette. It doesn’t yet need to load the plugin. 8 | * `src/extension.ts` - this is the main file where you will provide the implementation of your command. 9 | * The file exports one function, `activate`, which is called the very first time your extension is activated (in this case by executing the command). Inside the `activate` function we call `registerCommand`. 10 | * We pass the function containing the implementation of the command as the second parameter to `registerCommand`. 11 | 12 | ## Setup 13 | 14 | * install the recommended extensions (amodio.tsl-problem-matcher and dbaeumer.vscode-eslint) 15 | 16 | 17 | ## Get up and running straight away 18 | 19 | * Press `F5` to open a new window with your extension loaded. 20 | * Run your command from the command palette by pressing (`Ctrl+Shift+P` or `Cmd+Shift+P` on Mac) and typing `Hello World`. 21 | * Set breakpoints in your code inside `src/extension.ts` to debug your extension. 22 | * Find output from your extension in the debug console. 23 | 24 | ## Make changes 25 | 26 | * You can relaunch the extension from the debug toolbar after changing code in `src/extension.ts`. 27 | * You can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes. 28 | 29 | 30 | ## Explore the API 31 | 32 | * You can open the full set of our API when you open the file `node_modules/@types/vscode/index.d.ts`. 33 | 34 | ## Run tests 35 | 36 | * Open the debug viewlet (`Ctrl+Shift+D` or `Cmd+Shift+D` on Mac) and from the launch configuration dropdown pick `Extension Tests`. 37 | * Press `F5` to run the tests in a new window with your extension loaded. 38 | * See the output of the test result in the debug console. 39 | * Make changes to `src/test/suite/extension.test.ts` or create new test files inside the `test/suite` folder. 40 | * The provided test runner will only consider files matching the name pattern `**.test.ts`. 41 | * You can create folders inside the `test` folder to structure your tests any way you want. 42 | 43 | ## Go further 44 | 45 | * Reduce the extension size and improve the startup time by [bundling your extension](https://code.visualstudio.com/api/working-with-extensions/bundling-extension). 46 | * [Publish your extension](https://code.visualstudio.com/api/working-with-extensions/publishing-extension) on the VS Code extension marketplace. 47 | * Automate builds by setting up [Continuous Integration](https://code.visualstudio.com/api/working-with-extensions/continuous-integration). 48 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | //@ts-check 2 | 3 | 'use strict'; 4 | 5 | const path = require('path'); 6 | 7 | //@ts-check 8 | /** @typedef {import('webpack').Configuration} WebpackConfig **/ 9 | 10 | /** @type WebpackConfig */ 11 | const extensionConfig = { 12 | target: 'node', // VS Code extensions run in a Node.js-context 📖 -> https://webpack.js.org/configuration/node/ 13 | mode: 'none', // this leaves the source code as close as possible to the original (when packaging we set this to 'production') 14 | 15 | entry: './src/extension.ts', // the entry point of this extension, 📖 -> https://webpack.js.org/configuration/entry-context/ 16 | output: { 17 | // the bundle is stored in the 'dist' folder (check package.json), 📖 -> https://webpack.js.org/configuration/output/ 18 | path: path.resolve(__dirname, 'dist'), 19 | filename: 'extension.js', 20 | libraryTarget: 'commonjs2' 21 | }, 22 | externals: { 23 | vscode: 'commonjs vscode' // the vscode-module is created on-the-fly and must be excluded. Add other modules that cannot be webpack'ed, 📖 -> https://webpack.js.org/configuration/externals/ 24 | // modules added here also need to be added in the .vscodeignore file 25 | }, 26 | resolve: { 27 | // support reading TypeScript and JavaScript files, 📖 -> https://github.com/TypeStrong/ts-loader 28 | extensions: ['.ts', '.js'] 29 | }, 30 | module: { 31 | rules: [ 32 | { 33 | test: /\.ts$/, 34 | exclude: /node_modules/, 35 | use: [ 36 | { 37 | loader: 'ts-loader' 38 | } 39 | ] 40 | } 41 | ] 42 | }, 43 | devtool: 'nosources-source-map', 44 | infrastructureLogging: { 45 | level: "log", // enables logging required for problem matchers 46 | }, 47 | }; 48 | module.exports = [ extensionConfig ]; --------------------------------------------------------------------------------