├── .autocorrectrc ├── .github └── workflows │ └── cicd.yaml ├── .gitignore ├── .prettierignore ├── .prettierrc.yaml ├── README.md ├── docs ├── README.md ├── common │ ├── LightIR.md │ ├── asm_intro.md │ ├── cminusf.md │ ├── figs │ │ ├── asm_structure.png │ │ ├── lightir.png │ │ ├── module_relation.png │ │ ├── stack_frame.png │ │ ├── type_inherit.png │ │ ├── user_inherit.jpg │ │ └── value_inherit.png │ ├── logging.md │ └── simple_cpp.md ├── css │ └── extra.css ├── exp_platform_intro │ ├── README.md │ ├── Untitled.png │ ├── Untitled1.png │ ├── Untitled2.png │ └── Untitled3.png ├── faq │ └── README.md ├── innovative-lab │ ├── assets │ │ └── 编译创新实验群二维码.png │ └── index.md ├── javascripts │ └── katex.js ├── lab0 │ ├── git.md │ ├── index.md │ ├── linux.md │ ├── photos │ │ ├── V1.png │ │ ├── V10.png │ │ ├── V11.png │ │ ├── V12.png │ │ ├── V13.png │ │ ├── V14.png │ │ ├── V15.png │ │ ├── V16.png │ │ ├── V17.png │ │ ├── V18.png │ │ ├── V19.png │ │ ├── V2.png │ │ ├── V20.png │ │ ├── V21.png │ │ ├── V22.png │ │ ├── V23.png │ │ ├── V24.png │ │ ├── V25.png │ │ ├── V3.png │ │ ├── V4.png │ │ ├── V5.png │ │ ├── V6.png │ │ ├── V7.png │ │ ├── V8.png │ │ ├── V9.png │ │ ├── git1.png │ │ ├── git2.png │ │ ├── git3.png │ │ ├── git4.png │ │ ├── image-1.png │ │ ├── image-2.png │ │ ├── image-20230907232746795.png │ │ ├── image-20230907233217641.png │ │ ├── image-20230907233515388.png │ │ ├── image.png │ │ ├── image_3.png │ │ └── ssh_image.png │ └── software.md ├── lab1 │ ├── Bison.md │ ├── Flex.md │ ├── assets │ │ ├── flex.jpg │ │ ├── image-20230913142328935.png │ │ └── image-20230913164805071-1694594895535-1-1694594931543-3.png │ ├── index.md │ ├── 实验细节与要求.md │ └── 正则表达式.md ├── lab2 │ ├── autogen.md │ ├── index.md │ ├── visitor_pattern.md │ └── warmup.md ├── lab3 │ ├── environment.md │ ├── figs │ │ └── stack-frame.png │ ├── framework.md │ ├── guidance.md │ ├── index.md │ └── stack_allocation.md ├── lab4 │ ├── Dom.pdf │ ├── Mem2Reg介绍.pdf │ ├── figs │ │ ├── cmp.png │ │ └── copy-stmt.png │ └── index.md └── 建木杯-编译原理创新实验 │ └── 建木杯-扩展实验-v2.md ├── mkdocs.yml ├── package-lock.json ├── package.json └── requirements.txt /.autocorrectrc: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://huacnlee.github.io/autocorrect/schema.json 2 | rules: 3 | # Default rules: https://github.com/huacnlee/autocorrect/raw/main/autocorrect/.autocorrectrc.default 4 | spellcheck: 1 5 | textRules: 6 | # Config some special rule for some texts 7 | # For example, if we wants to let "Hello你好" just warning, and "Hi你好" to ignore 8 | # "Hello你好": 2 9 | # "Hi你好": 0 10 | fileTypes: 11 | # Config the files associations, you config is higher priority than default. 12 | # "rb": ruby 13 | # "Rakefile": ruby 14 | # "*.js": javascript 15 | # ".mdx": markdown 16 | spellcheck: 17 | words: 18 | # Please do not add a general English word (eg. apple, python) here. 19 | # Users can add their special words to their .autocorrectrc file by their need. 20 | - ActiveMQ 21 | - Flex 22 | - AirPods 23 | - Aliyun 24 | - API 25 | - App Store 26 | - AppKit 27 | - AppStore = App Store 28 | - AWS 29 | - CacheStorage 30 | - CDN 31 | - CentOS 32 | - CloudFront 33 | - Cminusf 34 | - CORS 35 | - CPU 36 | - DNS 37 | - Elasticsearch 38 | - ESLint 39 | - Facebook 40 | - GeForce 41 | - GitHub 42 | - GitLab 43 | - Google 44 | - GPU 45 | - H5 46 | - Hadoop 47 | - HBase 48 | - HDFS 49 | - HKEX 50 | - HTML 51 | - HTTP 52 | - HTTPS 53 | - I10n 54 | - I18n 55 | - iMovie 56 | - IndexedDB 57 | - Intel 58 | - iOS 59 | - iPad 60 | - iPadOS 61 | - iPhone 62 | - iTunes 63 | - JavaScript 64 | - jQuery 65 | - JSON 66 | - JWT 67 | - Linux 68 | - LocalStorage 69 | - macOS 70 | - Markdown 71 | - Microsoft 72 | - MongoDB 73 | - Mozilla 74 | - MVC 75 | - MySQL 76 | - Nasdaq 77 | - Netflix 78 | - NodeJS = Node.js 79 | - NoSQL 80 | - NVDIA 81 | - NYSE 82 | - OAuth 83 | - Objective-C 84 | - OLAP 85 | - OSS 86 | - P2P 87 | - PaaS 88 | - RabbitMQ 89 | - Redis 90 | - RESTful 91 | - RSS 92 | - RubyGem 93 | - RubyGems 94 | - SaaS 95 | - Sass 96 | - SDK 97 | - Shopify 98 | - SQL 99 | - SQLite 100 | - SQLServer 101 | - SSL 102 | - Tesla 103 | - TikTok 104 | - tvOS 105 | - TypeScript 106 | - Ubuntu 107 | - UML 108 | - URI 109 | - URL 110 | - VIM 111 | - watchOS 112 | - WebAssembly 113 | - WebKit 114 | - Webpack 115 | - Wi-Fi 116 | - Windows 117 | - WWDC 118 | - Xcode 119 | - XML 120 | - YAML 121 | - YML 122 | - YouTube 123 | -------------------------------------------------------------------------------- /.github/workflows/cicd.yaml: -------------------------------------------------------------------------------- 1 | name: CI with CD 2 | on: 3 | push: 4 | branches: 5 | - main 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v4 12 | - uses: actions/setup-python@v4 13 | with: 14 | python-version: '3.10' 15 | - run: pip install -r requirements.txt 16 | - uses: actions/setup-node@v4 17 | with: 18 | node-version: '18' 19 | - run: npm install 20 | - run: npm test 21 | 22 | - name: deploy 23 | if: always() 24 | run: mkdocs gh-deploy --force 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 105 | __pypackages__/ 106 | 107 | # Celery stuff 108 | celerybeat-schedule 109 | celerybeat.pid 110 | 111 | # SageMath parsed files 112 | *.sage.py 113 | 114 | # Environments 115 | .env 116 | .venv 117 | env/ 118 | venv/ 119 | ENV/ 120 | env.bak/ 121 | venv.bak/ 122 | 123 | # Spyder project settings 124 | .spyderproject 125 | .spyproject 126 | 127 | # Rope project settings 128 | .ropeproject 129 | 130 | # mkdocs documentation 131 | /site 132 | 133 | # mypy 134 | .mypy_cache/ 135 | .dmypy.json 136 | dmypy.json 137 | 138 | # Pyre type checker 139 | .pyre/ 140 | 141 | # pytype static type analyzer 142 | .pytype/ 143 | 144 | # Cython debug symbols 145 | cython_debug/ 146 | 147 | # Logs 148 | logs 149 | *.log 150 | npm-debug.log* 151 | yarn-debug.log* 152 | yarn-error.log* 153 | lerna-debug.log* 154 | .pnpm-debug.log* 155 | 156 | # Diagnostic reports (https://nodejs.org/api/report.html) 157 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 158 | 159 | # Runtime data 160 | pids 161 | *.pid 162 | *.seed 163 | *.pid.lock 164 | 165 | # Directory for instrumented libs generated by jscoverage/JSCover 166 | lib-cov 167 | 168 | # Coverage directory used by tools like istanbul 169 | coverage 170 | *.lcov 171 | 172 | # nyc test coverage 173 | .nyc_output 174 | 175 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 176 | .grunt 177 | 178 | # Bower dependency directory (https://bower.io/) 179 | bower_components 180 | 181 | # node-waf configuration 182 | .lock-wscript 183 | 184 | # Compiled binary addons (https://nodejs.org/api/addons.html) 185 | build/Release 186 | 187 | # Dependency directories 188 | node_modules/ 189 | jspm_packages/ 190 | 191 | # Snowpack dependency directory (https://snowpack.dev/) 192 | web_modules/ 193 | 194 | # TypeScript cache 195 | *.tsbuildinfo 196 | 197 | # Optional npm cache directory 198 | .npm 199 | 200 | # Optional eslint cache 201 | .eslintcache 202 | 203 | # Optional stylelint cache 204 | .stylelintcache 205 | 206 | # Microbundle cache 207 | .rpt2_cache/ 208 | .rts2_cache_cjs/ 209 | .rts2_cache_es/ 210 | .rts2_cache_umd/ 211 | 212 | # Optional REPL history 213 | .node_repl_history 214 | 215 | # Output of 'npm pack' 216 | *.tgz 217 | 218 | # Yarn Integrity file 219 | .yarn-integrity 220 | 221 | # dotenv environment variable files 222 | .env 223 | .env.development.local 224 | .env.test.local 225 | .env.production.local 226 | .env.local 227 | 228 | # parcel-bundler cache (https://parceljs.org/) 229 | .cache 230 | .parcel-cache 231 | 232 | # Next.js build output 233 | .next 234 | out 235 | 236 | # Nuxt.js build / generate output 237 | .nuxt 238 | dist 239 | 240 | # Gatsby files 241 | .cache/ 242 | # Comment in the public line in if your project uses Gatsby and not Next.js 243 | # https://nextjs.org/blog/next-9-1#public-directory-support 244 | # public 245 | 246 | # vuepress build output 247 | .vuepress/dist 248 | 249 | # vuepress v2.x temp and cache directory 250 | .temp 251 | .cache 252 | 253 | # Docusaurus cache and generated files 254 | .docusaurus 255 | 256 | # Serverless directories 257 | .serverless/ 258 | 259 | # FuseBox cache 260 | .fusebox/ 261 | 262 | # DynamoDB Local files 263 | .dynamodb/ 264 | 265 | # TernJS port file 266 | .tern-port 267 | 268 | # Stores VSCode versions used for testing VSCode extensions 269 | .vscode-test 270 | 271 | # yarn v2 272 | .yarn/cache 273 | .yarn/unplugged 274 | .yarn/build-state.yml 275 | .yarn/install-state.gz 276 | .pnp.* 277 | 278 | # IDE 279 | .vscode/ 280 | .idea/ 281 | 282 | # macOS 283 | .DS_Store -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | docs/css 2 | -------------------------------------------------------------------------------- /.prettierrc.yaml: -------------------------------------------------------------------------------- 1 | printWidth: 120 2 | semi: false 3 | singleQuote: true 4 | arrowParens: avoid 5 | trailingComma: none 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # USTC _Principles and Techniques of Compiler_ 2023 course homepage 2 | 3 | Homepage link: 4 | 5 | This README is a toturial of how to write and preview the docs. 6 | 7 | ## Development 8 | 9 | First install dependencies and init: 10 | 11 | ```bash 12 | # It's recommended to install in a virtual environment. 13 | pip install -r requirements.txt 14 | ``` 15 | 16 | Then make your modifications. You can preview your changes by running: 17 | 18 | ```bash 19 | # In python environment 20 | mkdocs serve 21 | ``` 22 | 23 | ### format 24 | 25 | In order to keep the doc style consistent, we use [Prettier](https://prettier.io/) and [AutoCorrect](https://github.com/huacnlee/autocorrect). 26 | 27 | ```bash 28 | # you need Node.js installed first 29 | npm install # then install Prettier and AutoCorrect 30 | ``` 31 | 32 | See [Prettier Doc: Editor Integration](https://prettier.io/docs/en/editors.html) and [AutoCorrect: VS Code Extension](https://github.com/huacnlee/autocorrect#vs-code-extension) for editor intergration. 33 | 34 | You should **format markdown files before commit**: 35 | 36 | ```bash 37 | # see .prettierignore for ignoring certain files or folders 38 | npm run test # to see if there is a format error 39 | npm run format # to format all files under docs/ 40 | ``` 41 | 42 | ### File Structure 43 | 44 | `mkdocs.yml` is the configuration file. When you want to add a new page, you need to add it to `nav` in `mkdocs.yml`. 45 | 46 | `docs/` is the directory of all Markdown files. You can create subdirectories to organize your files. 47 | 48 | ### Admonitions 49 | 50 | Read for more details. 51 | 52 | ## License 53 | 54 | All texts are All Rights Reserved by default. 55 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # USTC 编译原理和技术 2023 2 | 3 | ## 课程信息 4 | 5 | ??? info "课程简介" 6 | 7 | 本课程是计算机科学与技术专业的核心课,旨在深入探讨程序设计语言的设计和实现技术,涵盖与程序设计语言理论相关的知识,并培养学生将课程中学到的概念和技术应用于软件设计和开发的能力。内容包括编译器构建的基本原理,包括词法分析、语法分析、语义分析、中间代码生成、代码优化和目标代码生成。同时,本课程结合现代编译技术和趋势,使用 LLVM 和龙芯后端,基于自主设计的教学实验框架“建木”,设计现代化的编译器工具。 8 | 9 | ### 授课信息 10 | 11 | - 时间:1~15 周,1(6,7) 和 3(3,4) 12 | - 地点:高新校区 GT-B212 13 | - 主讲教师:李诚([主页](http://staff.ustc.edu.cn/~chengli7/)) 14 | - 助教: 15 | 16 | - 龚平() 17 | - 李晓奇() 18 | - 贺嘉() 19 | - 刘良宇() 20 | - 陈清源() 21 | - 金泽文() 22 | - 许坤钊() 23 | - 傅申() 24 | - 杨柏森() 25 | 26 | - QQ 群:805431263 27 | - 录课视频(需校内统一身份认证): 28 | - 希冀实验平台(作业及实验提交): 29 | - **实验框架已公开,见参考资料[2](#textbook2)** 30 | 31 | ## 公告 32 | 33 | - 2023-09-03:[课程平台介绍](exp_platform_intro/README.md) 已发布 34 | - 2023-09-08:[Lab0](lab0/index.md) 已发布,Deadline:**2023 年 9 月 15 日** 35 | - 2023-09-13:第一次作业:2.1a、2.2、2.3a、2.4h、2.7c、2.15(教材[1](#textbook)),Deadline:**2023 年 9 月 20 日** 36 | - 2023-09-15:[Lab1](lab1/index.md) 已发布,Deadline:**2023 年 9 月 28 日** 37 | - 2023-09-25:第二次作业:3.1a、3.2a、3.10、3.11、3.17、3.19a(教材[1](#textbook)),Deadline:**2023 年 10 月 7 日 14:00** 38 | - 2023-10-05:[Lab2](lab2/index.md) 已发布,注意分阶段 Deadline 39 | - 2023-10-11:第三次作业:3.27、3.37、4.3、4.5、4.9(教材[1](#textbook)),Deadline:**2023 年 10 月 18 日 16:00** 40 | - 2023-11-01:[Lab3](lab3/index.md) 已发布,注意分阶段 Deadline 41 | - 2023-11-01:第四次作业已发布,Deadline:**2023 年 11 月 8 日 17:25** 42 | - 2023-11-14:第五次作业:4.13、4.15、5.5、6.5、6.6(教材[1](#textbook)),Deadline:**2023 年 11 月 21 日 09:00** 43 | - 2023-11-27:[Lab4](lab4/index.md) 已发布,Deadline:**2023 年 12 月 18 日** 44 | - 2023-12-08:第六次作业:7.2c、7.5、7.12、8.1e、8.2e、8.6(教材[1](#textbook)),Deadline:**2023 年 12 月 20 日 24:00** 45 | - 2023-12-08:第七次作业:9.1、9.2、9.3、9.15af(教材[1](#textbook)),Deadline:**2024 年 1 月 3 日 24:00** 46 | - 2024-01-14:[建木杯–编译原理创新实验](innovative-lab/index.md) 已发布 47 | 48 | ## 教学课件 49 | 50 | | 日期 | 标题 | 课件 | 51 | | :--------: | :-----------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | 52 | | 2023-09-04 | 编译原理和技术导论 | [part1](https://rec.ustc.edu.cn/share/be63e5f0-4bbf-11ee-ab8f-8556ef2e1b82) | 53 | | 2023-09-06 | 词法分析 | [part1](https://rec.ustc.edu.cn/share/7c8cb640-4bfa-11ee-801b-996bfe70e4c4), [part2](https://rec.ustc.edu.cn/share/971f5a80-4bfa-11ee-9c40-3f9629e3618a), [part3](https://rec.ustc.edu.cn/share/daf23f60-4bfa-11ee-ba03-e1b373b96f27) | 54 | | 2023-09-11 | 语法分析 - 上下文无关文法 | [part1](https://rec.ustc.edu.cn/share/1adc23a0-5043-11ee-ae58-c51868b36892) | 55 | | 2023-09-13 | 语法分析 - 自顶向下 - 递归下降 | [part2](https://rec.ustc.edu.cn/share/11c40120-5208-11ee-8fb3-0b34e4219c8a) | 56 | | 2023-09-18 | 语法分析 - 自顶向下 - LL(1) 文法 | [part3](https://rec.ustc.edu.cn/share/08643ef0-5564-11ee-88f3-1509f631aa4a) | 57 | | 2023-09-20 | 语法分析 - 自底向上 - 移进规约 & LR 分析器的简单模型 | [part4](https://rec.ustc.edu.cn/share/fcba4990-5772-11ee-a14c-a36e421ab311), [part5](https://rec.ustc.edu.cn/share/08206b90-5773-11ee-a7a9-2766281e042e) | 58 | | 2023-09-25 | 语法分析 - 简单的 LR 方法 & 规范的 LR 方法 | [part6](https://rec.ustc.edu.cn/share/1a7a4df0-5b4c-11ee-8033-1da927361dcb), [part7](https://rec.ustc.edu.cn/share/322166f0-5b4c-11ee-b6aa-cb031e864251) | 59 | | 2023-09-27 | 中间代码表示 | [part1](https://rec.ustc.edu.cn/share/3fba3780-64dc-11ee-b89d-3bff5ec34c27) | 60 | | 2023-10-07 | 语法制导翻译 - 语法制导定义 & S/L 属性的定义 | [part1](https://rec.ustc.edu.cn/share/e0e4d8e0-5cdf-11ee-a097-75437996f503), [part2](https://rec.ustc.edu.cn/share/10e6d050-68c7-11ee-a697-13d9155d40a5) | 61 | | 2023-10-11 | 语法制导翻译 - 语法制导翻译方案 & L 属性定义的翻译方案 | [part3](https://rec.ustc.edu.cn/share/60f07260-68c7-11ee-ac16-c97fd4666cee), [part4](https://rec.ustc.edu.cn/share/71c78a80-68c7-11ee-b670-b5952f4e628a) | 62 | | 2023-10-16 | 中间代码翻译 - 简单语句、控制流与布尔表达式 | [part1](https://rec.ustc.edu.cn/share/aaa7e250-81e3-11ee-b601-cf1e5b684040), [part2](https://rec.ustc.edu.cn/share/b97993e0-81e3-11ee-b4bf-238eae90436f) | 63 | | 2023-10-18 | 中间代码翻译 - 标号回填与布尔表达式 | [part3](https://rec.ustc.edu.cn/share/898d59b0-81e4-11ee-a431-a7ebbdb09b39) | 64 | | 2023-10-30 | 中间代码翻译 - 标号回填与控制流语句 | [part4](https://rec.ustc.edu.cn/share/898d59b0-81e4-11ee-a431-a7ebbdb09b39) | 65 | | 2023-11-01 | 中间代码翻译 - 类型表达式 & 符号表与声明语句 | [part5](https://rec.ustc.edu.cn/share/898d59b0-81e4-11ee-a431-a7ebbdb09b39), [part6](https://rec.ustc.edu.cn/share/898d59b0-81e4-11ee-a431-a7ebbdb09b39) | 66 | | 2023-11-06 | 中间代码翻译 - 数组寻址 & 运行时管理 - 存储组织 | [part7](https://rec.ustc.edu.cn/share/898d59b0-81e4-11ee-a431-a7ebbdb09b39), [part1](https://rec.ustc.edu.cn/share/898d59b0-81e4-11ee-a431-a7ebbdb09b39) | 67 | | 2023-11-08 | 运行时管理 - 栈式分配 & 代码生成 - 概述与简单机器模型 | [part2](https://rec.ustc.edu.cn/share/898d59b0-81e4-11ee-a431-a7ebbdb09b39), [part1](https://rec.ustc.edu.cn/share/898d59b0-81e4-11ee-a431-a7ebbdb09b39) | 68 | | 2023-11-13 | 机器无关代码优化 - 常见的优化方式 | [part1](https://rec.ustc.edu.cn/share/898d59b0-81e4-11ee-a431-a7ebbdb09b39) | 69 | | 2023-11-15 | 机器无关代码优化 - 数据流与到达定值、可用表达式分析、数据流与活跃变量分析 | [part2](https://rec.ustc.edu.cn/share/ec735a10-8372-11ee-a33f-3f4ce56e99ac), [part3](https://rec.ustc.edu.cn/share/f7edf830-8372-11ee-a4ee-89e3b900b6a4), [part4](https://rec.ustc.edu.cn/share/49526e90-9043-11ee-b984-bfcfb1e71335) | 70 | | 2023-11-20 | 机器无关代码优化 - 数据流与活跃变量分析、基本块内的优化 & 寄存器分配方法 | [part4](https://rec.ustc.edu.cn/share/49526e90-9043-11ee-b984-bfcfb1e71335), [part5](https://rec.ustc.edu.cn/share/6b7a21f0-9043-11ee-b553-ab6f413f1658), [part1](https://rec.ustc.edu.cn/share/05972ed0-9044-11ee-9278-b9679a1c57b3) | 71 | | 2023-11-22 | 静态类型检查 & 流图中的循环 | [part1](https://rec.ustc.edu.cn/share/60bbf180-9045-11ee-bc96-e388a9810d5b), [part1](https://rec.ustc.edu.cn/share/4ad00130-9045-11ee-a6b5-83f7236ec56e) | 72 | | 2023-11-27 | 运行时刻环境 - 非局部变量的访问 | [part2](https://rec.ustc.edu.cn/share/bef41780-9045-11ee-8887-1ba1740d80fe) | 73 | | 2023-11-29 | 面向目标机器的代码优化 | [part1](https://rec.ustc.edu.cn/share/d6169380-9045-11ee-8a37-87201671ab8d) | 74 | | 2023-12-04 | guest lecture | 无 slides | 75 | | 2023-12-06 | guest lecture | 无 slides | 76 | | 2023-12-13 | 复习课 | [slides](https://rec.ustc.edu.cn/share/3a4ffcf0-995a-11ee-9fdc-a7ee4ffd604e) | 77 | 78 | ## 参考资料 79 | 80 | ### 教材和参考书 81 | 82 | -
[1] 陈意云、张昱,编译原理(第 3 版),高等教育出版社,2014 83 | -
[2] 李诚、徐伟,现代编译器设计与实现(实验讲义版本,高等教育出版社待出版,2023)。[教材资源及实验框架](https://ustc-compiler-principles.github.io/textbook/) 84 | - A. V. Aho, M. S. Lam, R. Sethi, and J. D. Ullman 著,赵建华等译,编译原理,机械工业出版社,2017 85 | 86 | ### 其他资料 87 | 88 | - Stanford 课程主页: 89 | - MIT 课程主页: 90 | -------------------------------------------------------------------------------- /docs/common/LightIR.md: -------------------------------------------------------------------------------- 1 | # Light IR 2 | 3 | ## Light IR 简介 4 | 5 | 本课程以 Cminusf 语言为源语言,从 LLVM IR 中裁剪出了适用于教学的精简的 IR 子集,并将其命名为 Light IR。同时依据 LLVM 的设计,为 Light IR 提供了配套简化的 [C++ 库](./LightIR.md#c-apis),用于生成 IR。 6 | 7 | 8 | 9 | 如下是一段 C 语言代码 `easy.c` 与 其对应的 IR 文件 `easy.ll` 示例。 10 | 11 | - `easy.c`: 12 | 13 | ```c 14 | int main(){ 15 | int a; 16 | int b; 17 | a = 1; 18 | b = 2; 19 | return a + b; 20 | } 21 | ``` 22 | 23 | - `easy.ll`: 24 | 25 | ```c 26 | ; 整个 .ll 文件称为 module 27 | ; ModuleID = 'easy.c' 28 | ; ... 29 | ; module 中至少有一个 main function 30 | define dso_local i32 @main() #0 { 31 | ; 此处 main function 仅有 1 个 basicblock 32 | ; basicblock 由一系列 instruction 组成 33 | %1 = alloca i32, align 4 34 | %2 = alloca i32, align 4 35 | %3 = alloca i32, align 4 36 | store i32 0, i32* %1, align 4 37 | store i32 1, i32* %2, align 4 38 | store i32 2, i32* %3, align 4 39 | %4 = load i32, i32* %2, align 4 40 | %5 = load i32, i32* %3, align 4 41 | %6 = add nsw i32 %4, %5 42 | ret i32 %6 43 | } 44 | ; ... 45 | ``` 46 | 47 | ## Light IR 指令 48 | 49 | ### Light IR 指令假设 50 | 51 | Light IR 指令从 LLVM IR 中裁剪得到,因此保留了 LLVM IR 如下的指令规范 52 | 53 | - 采用 3 地址的方式 54 | - `%2 = add i32 %0, %1` 55 | - 无限寄存器 + 静态单赋值形式 56 | - IR 中的变量均代表了一个虚拟寄存器,并且数量无上限 57 | - 每个虚拟寄存器只被赋值一次 58 | - 强类型系统 59 | 60 | - 每个操作数都具备自身的类型,分为基本类型,以及组合类型 61 | 62 | 基本类型: 63 | 64 | - `i1`:1 位宽的整数类型 65 | - `i32`:32 位宽的整数类型 66 | - `float`:单精度浮点数类型 67 | - `label`: 基本块的标识符类型 68 | 69 | 组合类型: 70 | 71 | - 指针类型:` *`,例如 `i32*, [10 x i32*]` 72 | - 数组类型:`[n x ]`,例如 `[10 x i32], [10 x [10 x i32]]` 73 | - 函数类型:`@()`,由函数返回值类型与参数类型列表组合成的类型 74 | 75 | 组合类型可以嵌套,例如前面的 `[10 x [10 x i32]]` 就是嵌套后的类型。 76 | 77 | ### Light IR 指令详解 78 | 79 | #### Terminator Instructions 80 | 81 | **注**:`ret` 与 `br` 都是 Terminator Instructions 也就是终止指令,在 llvm 基本块的定义里,基本块是单进单出的,因此只能有一条终止指令(`ret` 或 `br`)。 82 | 83 | ##### Ret 84 | 85 | - 概念:返回指令。用于将控制流(以及可选的值)从函数返回给调用者。`ret`指令有两种形式:一种返回值,然后终结函数,另一种仅终结函数。 86 | - 格式 87 | - `ret ` 88 | - `ret void` 89 | - 例子: 90 | - `ret i32 %0` 91 | - `ret void` 92 | 93 | ##### Br 94 | 95 | - 概念:跳转指令。用于使控制流转移到当前功能中的另一个基本块。该指令有两种形式,分别对应于条件分支和无条件分支。 96 | - 格式: 97 | - `br i1 , label , label ` 98 | - `br label ` 99 | - 例子: 100 | - `br i1 %cond, label %truebb, label %falsebb` 101 | - `br label %bb` 102 | 103 | #### Standard Binary Instructions 104 | 105 | ##### Add FAdd 106 | 107 | - 概念:`add`指令返回其两个`i32`类型的操作数之和,返回值为`i32`类型,`fadd`指令返回其两个`float`类型的操作数之和,返回值为`float`类型。 108 | - 格式: 109 | - ` = add , ` 110 | - ` = fadd , ` 111 | - 例子: 112 | - `%2 = add i32 %1, %0` 113 | - `%2 = fadd float %1, %0` 114 | 115 | ##### Sub FSub 116 | 117 | - 概念:`sub`指令返回其两个`i32`类型的操作数之差,返回值为`i32`类型,`fsub`指令返回其两个`float`类型的操作数之差,返回值为`float`类型。 118 | - 格式与例子与`add`,`fadd`类似 119 | 120 | ##### Mul FMul 121 | 122 | - 概念:`mul`指令返回其两个`i32`类型的操作数之积,返回值为`i32`类型,`fmul`指令返回其两个`float`类型的操作数之积,返回值为`float`类型。 123 | - 格式与例子与`add`,`fadd`类似 124 | 125 | ##### SDiv FDiv 126 | 127 | - 概念:`sdiv`指令返回其两个`i32`类型的操作数之商,返回值为`i32`类型,`fdiv`指令返回其两个`float`类型的操作数之商,返回值为`float`类型。 128 | - 格式与例子与`add`,`fadd`类似 129 | 130 | #### Memory Instructions 131 | 132 | ##### Alloca 133 | 134 | - 概念: `alloca`指令在当前执行函数的栈帧(Stack Frame)上分配内存。 135 | - 格式:` = alloca ` 136 | - 例子: 137 | - `%ptr = alloca i32` 138 | - `%ptr = alloca [10 x i32]` 139 | 140 | ##### Load 141 | 142 | - 概念:`load`指令用于从内存中读取。 143 | - 格式:` = load , * ` 144 | - 例子:`%val = load i32, i32* %ptr` 145 | 146 | ##### Store 147 | 148 | - 概念:`store`指令用于写入内存。 149 | - 格式:`store , * ` 150 | - 例子:`store i32 3, i32* %ptr` 151 | 152 | #### CastInst 153 | 154 | ##### ZExt 155 | 156 | - 概念:`zext`指令将其操作数**零**扩展为`type2`类型。 157 | - 格式:` = zext to ` 158 | - 例子:`%1 = zext i1 %0 to i32` 159 | 160 | ##### FpToSi 161 | 162 | - 概念:`fptosi`指令将浮点值转换为`type2`(整数)类型。 163 | - 格式:` = fptosi to ` 164 | - 例子:`%Y = fptosi float 1.0E-247 to i32` 165 | 166 | ##### SiToFp 167 | 168 | - 概念:`sitofp`指令将有符号整数转换为`type2`(浮点数)类型。 169 | - 格式:` = sitofp to ` 170 | - 例子:`%X = sitofp i32 257 to float` 171 | 172 | #### Other Instructions 173 | 174 | ##### ICmp FCmp 175 | 176 | - 概念:`icmp`指令根据两个整数的比较返回布尔值,`fcmp`指令根据两个浮点数的比较返回布尔值。 177 | - 格式: 178 | - ` = icmp , ` 179 | - ` = eq | ne | sgt | sge | slt | sle` 180 | - ` = fcmp , ` 181 | - ` = eq | ne | ugt | uge | ult | ule` 182 | - 例子:`i1 %2 = icmp sge i32 %0, %1` 183 | 184 | ##### Call 185 | 186 | - 概念:`call`指令用于使控制流转移到指定的函数,其传入参数绑定到指定的值。在被调用函数中执行`ret`指令后,如果被调用函数返回值不为 `void` 类型,控制流程将在函数调用后继续执行该指令,并且该函数的返回值绑定到`result`参数。 187 | - 格式: 188 | - ` = call ()` 189 | - `call ()` 190 | - 例子: 191 | - `%0 = call i32 @func( i32 %1, i32* %0)` 192 | - `call @func( i32 %arg)` 193 | 194 | ##### GetElementPtr 195 | 196 | - 概念:`getelementptr`指令用于获取数组结构的元素的地址。它**仅执行地址计算**,并且不访问内存。 197 | - 格式 198 | - 数组:` = getelementptr , * , i32 0, i32 ` 199 | - 指针(仅在数组作为函数参数时存在):` = getelementptr , * , i32 ` 200 | - 参数解释:`getelementptr` 后面的两个 `` 一定是相同的。`` 表示指针,`` 表示索引(也即数组下标) 201 | - 例子: 202 | - `%2 = getelementptr [10 x i32], [10 x i32]* %1, i32 0, i32 %0` 203 | - `%2 = getelementptr i32, i32* %1 i32 %0` 204 | - 额外阅读:[The Often Misunderstood GEP Instruction](https://llvm.org/docs/GetElementPtr.html) 205 | 206 | ## Light IR C++ 库 207 | 208 | Light IR C++ 库依据 LLVM 设计,用于生成 IR。在介绍其核心类之前,先展示 Light IR 的结构 209 | 210 | !!! warning 211 | 212 | 在必做实验阶段,请不要对 Light IR C++ 库进行直接修改 213 | 214 | ### Light IR 结构 215 | 216 | 217 | 218 | ![image-lightir](figs/lightir.png) 219 | 实验中需要生成的 IR 代码有着相对固定的结构模式: 220 | 221 | - 最上层的是 module,对应一个 Cminusf 源文件。包含全局变量 global_variable 和函数 function。 222 | - function 由头部和函数体组成。function 的头部包括返回值类型、函数名和参数表。函数体可以由一个或多个 basicblock 构成。 223 | - basicblock 是指程序顺序执行的语句序列,只有一个入口和一个出口。basicblock 由若干指令 instruction 构成。 224 | - 注意一个 basicblock 中**只能有一条终止指令**(Ret/Br)。 225 | 226 | !!! note 227 | 228 | 为了区别 Light IR 中的概念与我们实现的 Light IR C++ 库。我们用小写 plain text 来表示 Light IR 中的概念,例如 module;用大写的 code block 来表示 C++ 实现,例如 `Module`。 229 | 230 | ### Light IR C++ 类总览 231 | 232 | 在上一节中,可以看出 Light IR 最顶层的结构是 module,并具有层次化结构,在 Light IR C++ 库中,对应有层次化类的设计,如图所示,`Module`, `Function`, `BasicBlock`, `Instruction` 类分别对应了 Light IR 中 module,function,basicblock,instruction 的概念。 233 | ![module_relation](./figs/module_relation.png) 234 | 235 | ### Light IR C++ 数据基类:Value,User 236 | 237 | #### Value 238 | 239 | `Value` 类代表一个可用于指令操作数的带类型的数据,包含众多子类,`Instruction` 也是其子类之一,表示指令在创建后可以作为另一条指令的操作数。`Value` 成员 `use_list_` 是 `Use` 类的列表,每个 Use 类记录了该 `Value` 的一次被使用的情况 240 | 241 | !!! note "use-list 详解" 242 | 243 | 例如,如果存在指令 `%op2 = add i32 %op0, %op1`,那么 `%op0`、`%op1` 就被 `%op2` 所使用,`%op0` 基类 `Value` 的 `use_list_` 里就会有 `Use(%op2, 0)`(这里的 0 代表 `%op0` 是被使用时的第一个参数)。同理,`%op1` 的 `use_list_` 里有 `Use(%op2, 1)`。 244 | 245 | 246 | 247 | ![value_inherit](figs/value_inherit.png) 248 | !!! note 249 | 250 | `Instruction` 类是 `Value` 的子类,这表示,指令在使用操作数创建后的返回值也可以作为另一条指令创建的操作数。 251 | 252 | #### User 253 | 254 | 255 | 256 | `User` 作为 `Value` 的子类,含义是使用者,`Instruction` 也是其子类之一,`User` 类成员 `operands_` 是`Value` 类的列表,表示该使用者使用的操作数列表。如图是 `User` 类的子类继承关系。 257 | ![user_inherit](figs/user_inherit.jpg) 258 | 259 | !!! note 260 | 261 | `Value` 类的 use-list,与 User 类的 operand-list 构成了指令间的依赖关系图。 262 | 263 | ### Light IR C++ 类型基类:Type 264 | 265 | 在 [Light IR 指令假设](./LightIR.md#lightir-指令假设)中提到,Light IR 保留了 LLVM IR 的强类型系统,包含基本类型与组合类型,`Type` 类是所有类型基类,其子类继承关系如图所示,其中 `IntegerType`, `FloatType` 对应表示 Light IR 中的 `i1`,`i32`,`float` 基本类型。`ArrayType`,`PointerType`,`FunctionType` 对应表示组合类型:数组类型,指针类型,函数类型。 266 | ![type_inherit](figs/type_inherit.png) 267 | 268 | 获取基本类型的接口在 `Module` 类中,获取组合类型的接口则在组合类型对应的类中: 269 | 270 | ```cpp 271 | // 获取 i1 基本类型 272 | auto int1_type = module->get_int1_type(); 273 | // 获取 [2 x i32] 数组类型 274 | auto array_type = ArrayType::get(Int32Type, 2); 275 | ``` 276 | 277 | ### 使用 Light IR C++ 库生成 IR 278 | 279 | 本小节将以下列 Light IR 片段介绍使用 Light IR C++ 接口层次化顺序生成 IR 的过程, 280 | 281 | ```c 282 | define i32 @main() #0 { 283 | entry: 284 |   %1 = alloca i32 285 |   store i32 72, i32* %1 286 |   %2 = load i32, i32* %1 287 |   ret i32 %2 288 | } 289 | ``` 290 | 291 | #### 创建 `Module`, `Function`, `BasicBlock` 的接口 292 | 293 | 创建 module 294 | 295 | ```cpp 296 | auto module = new Module(); 297 | ``` 298 | 299 | 为 module 添加 main function 定义 300 | 301 | ```cpp 302 | // 从 module 中获取 i32 类型 303 | Type *Int32Type = module->get_int32_type(); 304 | auto mainFun = Function::create(FunctionType::get(Int32Type, {Int32Type}), "main", module); 305 | ``` 306 | 307 | 为 main function 创建 function 内的第一个 basicblock 308 | 309 | ```cpp 310 | auto bb = BasicBlock::create(module, "entry", mainFun); 311 | ``` 312 | 313 | 接下来需要用辅助类 `IRBuilder` 向 basicblock 中插入指令 314 | 315 | #### `IRBuilder`: 生成 IR 指令的辅助类 316 | 317 | Light IR C++ 库为生成 IR 指令提供了辅助类:`IRBuilder`。该类提供了独立创建 IR 指令的接口,可以创建指令的同时并将它们插入 basicblock 中,`IRBuilder` 类提供以下接口: 318 | 319 | ```cpp 320 | class IRBuilder { 321 | public: 322 | // 返回当前插入的基本块 323 | BasicBlock *get_insert_block() 324 | // 设置当前插入的基本块 325 | void set_insert_point(BasicBlock *bb) 326 | // 创建一条除法指令,并加入当前基本块中 327 | BinaryInst *create_isdiv(Value *lhs, Value *rhs) 328 | // 创建的 instr_type 类型的指令,并插入到当前基本块中 329 | // 可根据 IRBuilder.h 查看每一条指令的创建细节 330 | Instruction *create_[instr_type](...); 331 | }; 332 | ``` 333 | 334 | 在创建 module,main function,basicblock 后: 335 | 336 | ```cpp 337 | auto module = new Module(); 338 | // 从 module 中获取 i32 类型 339 | auto *Int32Type = module->get_int32_type(); 340 | auto mainFun = Function::create(FunctionType::get(Int32Type, {Int32Type}), "main", module); 341 | auto bb = BasicBlock::create(module, "entry", mainFun); 342 | ``` 343 | 344 | 创建 `IRBuilder`,并使用 `IRBuilder` 创建新指令 345 | 346 | ```cpp 347 | // 实例化 IRBuilder 348 | auto builder = new IRBuilder(nullptr, module); 349 | // 将 IRBuilder 插入指令位置设置为 bb 尾部 350 | builder->set_insert_point(bb); 351 | // 为变量 x 分配栈上空间 352 | auto xAlloca = builder->create_alloca(Int32Type); 353 | // 创建 store 指令,将 72 常数存到 x 分配空间里 354 | builder->create_store(ConstantInt::get(72, module), xAlloca); 355 | // 创建 load 指令,将 x 内存值取出来 356 | auto xLoad = builder->create_load(xAlloca); 357 | // 创建 ret 指令,将 x 取出的值返回 358 | builder->create_ret(xLoad); 359 | ``` 360 | 361 | 至此,使用 Light IR C++ 接口层次化顺序生成 IR 的过程流程结束 362 | 363 | ### Light IR C++ 库核心类定义 364 | 365 | 本节梳理了在生成 IR 过程中可能会用到的接口,学生可按需进行查阅 366 | 367 | 368 | 369 | #### Module 370 | 371 | - 概念:一个编译单元。对应一个 Cminusf 文件。 372 | 373 | ??? info "Module 常用接口" 374 | 375 | ```cpp 376 | class Module 377 | { 378 | public: 379 | // 将函数 f 添加到该模块的函数链表上 380 | // 在函数被创建的时候会自动调用此方法 381 | void add_function(Function *f); 382 | // 将全局变量 g 添加到该模块的全局变量链表上 383 | // 在全局变量被创建的时候会自动调用此方法 384 | void add_global_variable(GlobalVariable* g); 385 | // 获取全局变量列表 386 | std::list get_global_variable(); 387 | // 获得(创建)自定义的 Pointer 类型 388 | PointerType *get_pointer_type(Type *contained); 389 | // 获得(创建)自定义的 Array 类型 390 | ArrayType *get_array_type(Type *contained, unsigned num_elements); 391 | // 获得基本类型 int32 392 | IntegerType *get_int32_type(); 393 | // 其他基本类型类似... 394 | }; 395 | ``` 396 | 397 | #### BasicBlock 398 | 399 | - 概念:基本块。是一个是单入口单出口的代码块,可以作为分支指令目标对象。 400 | 401 | ??? info "BasicBlock 常用接口" 402 | 403 | ```cpp 404 | class BasicBlock : public Value { 405 | public: 406 | // 创建并返回基本块,参数分别是基本块所属的模块,基本块名字(默认为空),基本块所属的函数 407 | static BasicBlock *create(Module *m, const std::string &name, Function *parent); 408 | // 返回该基本块所属的函数 409 | Function *get_parent(); 410 | // 返回该基本块所属的模块 411 | Module *get_module(); 412 | // 返回该基本块的终止指令,若基本块的最后一条指令不是终止指令返回则返回 nullptr 413 | Instruction *get_terminator(); 414 | // 将指令 instr 添加到该基本块的指令链表末端,使用 IRBuilder 来创建函数时会自动调用此方法 415 | void add_instruction(Instruction *instr); 416 | // 将指令 instr 添加到该基本块的指令链表首部 417 | void add_instr_begin(Instruction *instr); 418 | // 将指令 instr 从该基本块的指令链表中移除,该 API 会同时维护好 instr 的操作数的 use 链表。 419 | void erase_instr(Instruction *instr); 420 | // 判断该基本块是否为空 421 | bool empty(); 422 | // 返回该基本块中的指令数目 423 | int get_num_of_instr(); 424 | // 返回该基本块的指令链表 425 | std::list &get_instructions(); 426 | // 将该基本块从所属函数的基本块链表中移除 427 | void erase_from_parent(); 428 | 429 | /****************APIs about cfg****************/ 430 | // 返回前驱基本块集合 431 | std::list &get_pre_basic_blocks(); 432 | // 返回后继基本块集合 433 | std::list &get_succ_basic_blocks(); 434 | // 添加前驱基本块 435 | void add_pre_basic_block(BasicBlock *bb); 436 | // 添加后继基本块 437 | void add_succ_basic_block(BasicBlock *bb); 438 | // 移除前驱基本块 439 | void remove_pre_basic_block(BasicBlock *bb); 440 | // 移除后继基本块 441 | void remove_succ_basic_block(BasicBlock *bb); 442 | /****************APIs about cfg****************/ 443 | }; 444 | ``` 445 | 446 | #### GlobalVariable 447 | 448 | - 概念:全局变量。 449 | 450 | ??? info "GlobalVariable 常用接口" 451 | 452 | ```cpp 453 | class GlobalVariable : public User { 454 | public: 455 | // 创建一个全局变量 456 | static GlobalVariable *create(std::string name, Module *m, Type* ty, 457 | bool is_const, Constant* init ); 458 | }; 459 | ``` 460 | 461 | #### Constant 462 | 463 | - 概念:常量。不同类型的常量由不同类来表示。 464 | 465 | ??? info "Constant 常用接口" 466 | 467 | ```cpp 468 | class Constant : public User { 469 | public: 470 | Constant(Type *ty, const std::string &name = "", unsigned num_ops = 0); 471 | }; 472 | ``` 473 | 474 | ??? info "整型常量 ConstantInt 常用接口" 475 | 476 | ```cpp 477 | class ConstantInt : public Constant { 478 | public: 479 | // 返回该常量中存的数 480 | int get_value(); 481 | // 返回常量 const_val 中存的数 482 | static int get_value(ConstantInt *const_val); 483 | // 以值 val 来创建常量 484 | static ConstantInt *get(int val, Module *m); 485 | // 以值 val 来创建 bool 常量 486 | static ConstantInt *get(bool val, Module *m); 487 | }; 488 | ``` 489 | 490 | ??? info "浮点数常量 ConstantFP 常用接口" 491 | 492 | ```cpp 493 | class ConstantFP : public Constant { 494 | public: 495 | // 以值 val 创建并返回浮点数常量 496 | static ConstantFP *get(float val, Module *m); 497 | // 返回该常量中存的值 498 | float get_value(); 499 | }; 500 | ``` 501 | 502 | ??? info "ConstantZero 常用接口" 503 | 504 | ```cpp 505 | // 用于全局变量初始化的零常量 506 | class ConstantZero : public Constant { 507 | public: 508 | // 创建并返回零常量 509 | static ConstantZero *get(Type *ty, Module *m); 510 | }; 511 | ``` 512 | 513 | #### Argument 514 | 515 | - 概念:函数的参数。 516 | 517 | ??? info "Argument 常用接口" 518 | 519 | ```cpp 520 | class Argument : public Value 521 | { 522 | public: 523 | // 返回该参数的所属的函数 524 | Function *get_parent(); 525 | // 返回该参数在所在函数的参数列表中的序数 526 | unsigned get_arg_no() const; 527 | }; 528 | ``` 529 | 530 | #### Function 531 | 532 | - 概念:函数。该类描述了一个过程,包含多个基本块。 533 | 534 | ??? info "Funtion 常用接口" 535 | 536 | ```cpp 537 | class Function : public Value { 538 | public: 539 | // 创建并返回函数,参数依次是待创建函数类型 ty,函数名字 name (不可为空),函数所属的模块 parent 540 | static Function *create(FunctionType *ty, const std::string &name, Module *parent); 541 | // 返回该函数的函数类型 542 | FunctionType *get_function_type() const; 543 | // 返回该函数的返回值类型 544 | Type *get_return_type() const; 545 | // 将基本块 bb 添加至该函数末端(调用基本块的创建函数时会自动调用此函数来) 546 | void add_basic_block(BasicBlock *bb); 547 | // 得到该函数参数数量 548 | unsigned get_num_of_args() const; 549 | // 得到该函数基本块数量 550 | unsigned get_num_basic_blocks() const; 551 | // 得到该函数所属的 Module 552 | Module *get_parent() const; 553 | // 从函数的基本块链表中删除基本块 bb 554 | void remove(BasicBlock* bb) 555 | // 返回函数基本块链表 556 | std::list &get_basic_blocks() 557 | // 返回函数的参数链表 558 | std::list &get_args() 559 | // 给函数中未命名的基本块和指令命名 560 | void set_instr_name(); 561 | }; 562 | ``` 563 | 564 | #### IRBuilder 565 | 566 | - 概念:生成 IR 的辅助类。该类提供了独立的接口创建各种 IR 指令,并将它们插入基本块中(注意:该辅助类不做任何类型检查)。 567 | 568 | ??? info "IRBuilder 常用接口" 569 | 570 | ```cpp 571 | class IRBuilder { 572 | public: 573 | // 返回当前插入的基本块 574 | BasicBlock *get_insert_block() 575 | // 设置当前插入的基本块 576 | void set_insert_point(BasicBlock *bb) 577 | // 创建的指令并对应插入到基本块中,函数名字和参数名字和 IR 文档是一一对应的 578 | // 具体查看 IRBuilder.h 579 | Instruction *create_[instr_type](); 580 | }; 581 | ``` 582 | 583 | #### Instruction 584 | 585 | - 概念:指令。该类是所有 LLVM 指令的基类。子类包含 IR 部分中的所有指令。 586 | 587 | ??? info "Instruction 常用接口" 588 | 589 | ```c++ 590 | class Instruction : public User { 591 | public: 592 | // 获取指令所在函数 593 | Function *get_function(); 594 | // 获取指令所在 module 595 | Module *get_module(); 596 | // 获取指令类型 597 | OpID get_instr_type() const { return op_id_; } 598 | // 获取指令类型的名字 599 | std::string get_instr_op_name() const; 600 | // 判断指令是否是 instr_type 类型 601 | bool is_[instr_type] const; 602 | // 判断指令是否是二元运算 603 | bool isBinary() const; 604 | // 判断指令是否为终止指令 605 | bool isTerminator() const; 606 | }; 607 | ``` 608 | 609 | #### Type 610 | 611 | - 概念:IR 的类型(包含 `VoidType`、`LabelType`、`FloatType`、`IntegerType`、`ArrayType`、`PointerType`)。module 中可以通过 API 获得基本类型,并创建自定义类型。 612 | - 子类介绍:其中 `ArrayType`、`PointerType` 可以嵌套得到自定义类型,而 `VoidType`、`IntegerType`,`FloatType` 可看做 IR 的基本类型,`LabelType` 是 `BasicBlcok` 的类型,可作为跳转指令的参数,`FunctionType` 表示函数类型。其中 `VoidType` 与 `LabelType` 没有对应的子类,通过 `Type` 中的 `tid_` 字段判别,而其他类型均有对应子类 613 | 614 | ??? info "Type 常用接口" 615 | 616 | ```c++ 617 | class Type { 618 | public: 619 | // 获取 type id 620 | TypeID get_type_id() const; 621 | // 判断是否是 ty 类型 622 | bool is_[ty]_type(); 623 | // 若是 PointerType 则返回指向的类型,若不是则返回 nullptr。 624 | Type *get_pointer_element_type(); 625 | // 若是 ArrayType 则返回数组元素的类型,若不是则返回 nullptr。 626 | Type *get_array_element_type(); 627 | // 返回类型的大小 628 | unsigned get_size() const; 629 | }; 630 | ``` 631 | 632 | ??? info "数组类型 ArrayType 常用接口" 633 | 634 | ```c++ 635 | class ArrayType : public Type { 636 | public: 637 | // 判断数组元素类型是否合法 638 | static bool is_valid_element_type(Type *ty); 639 | // 通过数组元素类型 contained 以及数组长度 num_elements 创建数组类型 640 | static ArrayType *get(Type *contained, unsigned num_elements); 641 | // 得到该数组类型的元素类型 642 | Type *get_element_type() const; 643 | // 获得该数组类型的长度 644 | unsigned get_num_of_elements(); 645 | }; 646 | ``` 647 | 648 | ??? info "指针类型 PointerType 常用接口" 649 | 650 | ```c++ 651 | class PointerType : public Type { 652 | public: 653 | // 获取该指针类型指向的元素类型 654 | Type *get_element_type() const; 655 | // 创建指向类型为 contained 的指针类型 656 | static PointerType *get(Type *contained); 657 | }; 658 | ``` 659 | 660 | ??? info "函数类型 FunctionType 常用接口" 661 | 662 | ```c++ 663 | class FunctionType : public Type { 664 | public: 665 | // 判断返回值类型是否合法 666 | static bool is_valid_return_type(Type *ty); 667 | // 判断参数类型是否合法 668 | static bool is_valid_argument_type(Type *ty); 669 | // 根据返回值类型 result,参数类型列表 params 创建函数类型 670 | static FunctionType *get(Type *result, std::vector params); 671 | // 返回该函数类型的参数个数 672 | unsigned get_num_of_args() const; 673 | // 获得该函数类型第 i 个参数的类型 674 | Type *get_param_type(unsigned i) const; 675 | // 获得该函数类型的参数类型链表的起始迭代器 676 | std::vector::iterator param_begin(); 677 | // 获得该函数类型的参数类型链表的结束迭代器 678 | std::vector::iterator param_end(); 679 | // 获得该函数类型的返回值类型 680 | Type *get_return_type() const; 681 | } 682 | ``` 683 | 684 | #### User 685 | 686 | - 概念:使用者。维护了 use-def 信息,表示该使用者用了哪些值。 687 | 688 | ??? info "User 常用接口" 689 | 690 | ```cpp 691 | class User : public Value { 692 | public: 693 | // 从该使用者的操作数链表中取出第 i 个操作数 694 | Value *get_operand(unsigned i) const; 695 | // 将该使用者的第 i 个操作数设为值 v 696 | void set_operand(unsigned i, Value *v); 697 | // 将值 v 添加到该使用者的操作数链表上 698 | void add_operand(Value *v); 699 | // 得到操作数链表的大小 700 | unsigned get_num_operand() const; 701 | // 从该使用者的操作数链表中的所有操作数的使用情况中移除该使用者 702 | void remove_use_of_ops(); 703 | // 从该使用者的操作数链表中移除第 index 个 704 | void remove_operands(unsigned index); 705 | }; 706 | ``` 707 | 708 | #### Use 709 | 710 | - 概念:代表了值的使用情况。 711 | 712 | ??? info "Use 常用接口" 713 | 714 | ```cpp 715 | struct Use { 716 | // 使用者 717 | Value *val_; 718 | // 使用者中值的序数 719 | // 对于 func(a, b),a 为 0,b 为 1 720 | unsigned arg_no_; 721 | }; 722 | ``` 723 | 724 | #### Value 725 | 726 | - 概念:值。代表一个可能用于指令操作数的带类型数据,是最基础的类,维护了 def-use 信息,即该值被哪些使用者使用。 727 | 728 | ??? info "Value 常用接口" 729 | 730 | ```cpp 731 | class Value 732 | { 733 | public: 734 | // 获取该值的类型 735 | Type *get_type() const; 736 | 737 | // 取得该值的使用情况 738 | const std::list &get_use_list() const { return use_list_; } 739 | // 添加加该值的使用情况 740 | void add_use(Value *val, unsigned arg_no = 0); 741 | // 在所有地方将该值用新的值 new_val 替换,并维护好 use_def 和 def_use 链表 742 | void replace_all_use_with(Value *new_val); 743 | // 将值 val 从使用链表中移除 744 | void remove_use(Value *val); 745 | }; 746 | ``` 747 | -------------------------------------------------------------------------------- /docs/common/asm_intro.md: -------------------------------------------------------------------------------- 1 | # 龙芯汇编介绍 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | > 本介绍参考自[《龙芯架构参考手册 - 卷一:基础架构》](https://github.com/loongson/LoongArch-Documentation/releases/download/2023.04.20/LoongArch-Vol1-v1.10-CN.pdf)。 10 | 11 | 龙芯架构 LoongArch 是一种精简指令集计算机(RISC)风格的指令系统架构,分为 32 位和 64 位两个版本,分别称为 LA32 架构和 LA64 架构。我们主要关注 LA64 架构中的非向量整数指令和非向量浮点指令中的一部分,如果你想了解更多关于龙芯汇编的内容,可以参考[《龙芯架构参考手册 - 卷一:基础架构》](https://github.com/loongson/LoongArch-Documentation/releases/download/2023.04.20/LoongArch-Vol1-v1.10-CN.pdf)。 12 | 13 | ## 汇编文件的组成 14 | 15 | 以[示例 2](#示例-2) 中的汇编文件为例,下图展示了一个汇编文件的整体结构与组成: 16 | 17 | ![汇编文件结构](./figs/asm_structure.png) 18 | 19 | 其中 20 | 21 | - 每一行 `#` 之后的是注释; 22 | - 以 `.` 开头的语句是 [GNU 汇编伪指令](#gnu-汇编伪指令),比如 `.text`、`.globl a` 和 `.space 4` 等等; 23 | - 以 `:` 结尾的是标签,用于标记一条语句的位置,比如 `main`、`a` 和 `.main_label_entry` 等等; 24 | - 形如 `$sp`、`$fp`、`$zero`、`$ra` 和 `$t0` 等以 `$` 开头的是[寄存器](#寄存器介绍)。 25 | 26 | ## 寄存器介绍 27 | 28 | 寄存器通过不同的首字母表明其属于哪个寄存器文件。对于非向量寄存器,以 `$rN` 标记通用寄存器,`$fN` 标记浮点寄存器。其中 `N` 是数字,表示操作的时该寄存器文件中的第 N 号寄存器。 29 | 30 | ### 通用寄存器 31 | 32 | 整数指令涉及的寄存器包括**通用寄存器** GR 和**程序计数器** PC。 33 | 34 | 其中通用寄存器有 32 个,记为 `$r0` ~ `$r31`,其中第 0 号寄存器 `$r0` 的值恒为 `0`。在 LA64 架构下,通用寄存器的位宽为 64 比特。 35 | 36 | 龙芯架构 ELF psABI 规范对通用寄存器遵循的使用作出了规定,其中,我们将用到以下寄存器: 37 | 38 | | 名称 | 别名 | 用途 | 在调用中是否保留 | 39 | | :-------------: | :-----------: | :----------------------: | :--------------: | 40 | | `$r0` | `$zero` | 常数 `0` | (常数) | 41 | | `$r1` | `$ra` | 返回地址 | 否 | 42 | | `$r3` | `$sp` | 堆栈指针 | 是 | 43 | | `$r4` - `$r5` | `$a0` - `$a1` | 传参寄存器、返回值寄存器 | 否 | 44 | | `$r6` - `$r11` | `$a2` - `$a7` | 传参寄存器 | 否 | 45 | | `$r12` - `$r20` | `$t0` - `$t8` | 临时寄存器 | 否 | 46 | | `$r22` | `$fp` / `$s9` | 栈帧指针 / 静态寄存器 | 是 | 47 | 48 | - 便于理解和记忆,寄存器一般都有一个别名。在汇编程序中可使用寄存器名、也可使用对应的别名来指代相应寄存器 49 | - ”在调用中是否保留“这一栏表示在函数调用时,被调用函数是否需要在返回前将该寄存器的值恢复到调用前的状态 50 | 51 | 对于这些寄存器的用途,更详细的解释如下 52 | 53 | - `$ra` (`$r1`):用于存储函数的返回地址,在调用一个函数时(详见 [`bl`](#bl) 指令),函数的返回地址(即调用指令的下一条指令地址)会自动写入 `$ra` 中,用于在返回时恢复 `PC` 的值; 54 | 55 | - `$sp` (`$r3`)、`$fp` (`$r22`):分别存储当前函数的堆栈指针和栈帧指针,你可以在[栈帧布局](#栈帧布局)中了解到关于它们的更多信息 56 | 57 | - `$a0` - `$a7` (`$r4` - `$r11`):用于存储函数参数中的整数值 58 | 59 | 比如在调用函数 `int func(int a, int b, int c[], float d)` 时,参数 `a`、 `b` 和 `c` 的值就分别存储在 `$a0`,`$a1` 和 `$a2` 中 60 | 61 | - `$a0` (`$r4`) 和 `$a1` (`$r5`) 还用于存储返回值,即函数的整数返回值会存储在这两个寄存器中,被调用者获取 62 | 63 | 比如在调用函数 `int const_ret() { return 0; }` 后,`$a0` 的值就是 `0` 64 | 65 | - `$t0` - `$t8` (`$r12` - `$r20`):用作临时寄存器,存储各个指令产生的中间值 66 | 67 | ### 浮点寄存器 68 | 69 | 浮点数指令涉及到的寄存器包括**浮点寄存器** FR、**条件标志寄存器** CFR 和**浮点控制状态寄存器** FCSR。我们将着重介绍前两种寄存器。 70 | 71 | #### 浮点寄存器 72 | 73 | 浮点寄存器共有 32 个,记为 `$f0` ~ `$f31`,每一个都可以读写。 74 | 75 | 龙芯架构 ELF psABI 规范对浮点寄存器遵循使用作出了规定,其中,我们将用到以下浮点寄存器: 76 | 77 | | 名称 | 别名 | 用途 | 在调用中是否保留 | 78 | | :------------: | :--------------: | :----------------------: | :--------------: | 79 | | `$f0` - `$f1` | `$fa0` - `$fa1` | 传参寄存器、返回值寄存器 | 否 | 80 | | `$f2` - `$f7` | `$fa2` - `$fa7` | 传参寄存器 | 否 | 81 | | `$f8` - `$f23` | `$ft0` - `$ft15` | 临时寄存器 | 否 | 82 | 83 | 这些寄存器的用途与对应的浮点寄存器类似,这里不再赘述。 84 | 85 | #### 条件标志寄存器 86 | 87 | 条件标志寄存器共有 8 个,记为 `$fcc0` ~ `$fcc7`,每一个都可以读写,位宽为 1 比特。它们主要在以下两个场景中被用到: 88 | 89 | - **浮点比较**的结果将写到条件标志寄存器中,比较结果为真时置 `1`,否则置 `0` 90 | - **浮点分支指令的判断条件**来自条件标志寄存器 91 | 92 | 比如对于如下的 C 语言代码: 93 | 94 | ```c 95 | int func_fcmp(float a, float b) 96 | { 97 | if (a < b) { 98 | return 1; 99 | } else { 100 | return 0; 101 | } 102 | } 103 | ``` 104 | 105 | 可以手写 LA64 汇编如下(为了便于理解,这里的汇编代码与编译器生成的代码有所不同): 106 | 107 | ```asm 108 | # $fcc0 是条件标志寄存器, $fa0 和 $fa1 是浮点寄存器, $zero $t0 和 $a0 是通用寄存器 109 | func_fcmp: 110 | entry: 111 | # 这里省略保存各个保留寄存器 (即调用者信息) 的过程 112 | # 在调用函数时, $fa0 = a, $fa1 = b 113 | fcmp.slt.s $fcc0, $fa0, $fa1 # 浮点比较指令: $fcc0 = $fa0 < $fa1 ? 1 : 0 114 | bcnez $fcc0, true_label # 浮点分支指令: 如果 $fcc0 != 0, 则跳转到 true_label 115 | b false_label # 无条件跳转指令, 跳转到 false_label 116 | true_label: 117 | addi.w $t0, $zero, 1 # $t0 = 0 + 1 = 1 118 | b exit # 无条件跳转指令, 跳转到 exit 119 | false_label: 120 | addi.w $t0, $zero, 0 # $t0 = 0 + 0 = 0 121 | b exit # 无条件跳转指令, 跳转到 exit 122 | exit: 123 | ori $a0, $t0, 0 # $a0 = $t0, $a0 是存储返回值的通用寄存器 124 | # 这里省略恢复各个保留寄存器 (即调用者信息) 的过程 125 | jr $ra # return 126 | ``` 127 | 128 | ## 基础指令介绍 129 | 130 | ### 指令编码格式 131 | 132 | 在龙芯架构中,所有指令均采用 32 位固定长度,且指令的地址都要求 4 字节边界对齐。如下,为某个汇编程序进入函数体时,首条汇编指令及后 2 条指令的地址,可见指令的地址按照 4 字节边界对齐: 133 | 134 | ``` 135 | ==> 0x120000660 : addi.d $r3,$r3,-32 136 | 0x120000664 : st.d $r22,$r3,24 137 | 0x120000668 : addi.d $r22,$r3,32 138 | ``` 139 | 140 | ### 汇编助记格式 141 | 142 | 龙芯架构对指令名的前、后缀进行了统一考虑,以方便汇编编程人员和编译器开发人员使用,例如 `fadd.s` 指令为“单精度” “浮点”加法指令。 143 | 144 | #### 指令名前缀 145 | 146 | 龙芯架构指令前缀和指令类型的对应关系为: 147 | 148 | | 指令前缀 | 指令类型 | 149 | | :------: | :--------------: | 150 | | 无前缀 | 非向量整数指令 | 151 | | `f` | 非向量浮点数指令 | 152 | | …… | …… | 153 | 154 | #### 指令名后缀 155 | 156 | 其次,绝大多数指令通过指令名中 `.XX` 形式的后缀来指示指令的操作对象类型: 157 | 158 | - 对于整数指令,后缀和操作对象类型的对应关系为 159 | 160 | | 指令后缀 | 操作对象类型 | 操作对象长度 | 161 | | :------: | :----------: | :------------: | 162 | | `.b` | 字节 | 1 字节 / 8 位 | 163 | | `.h` | 半字 | 2 字节 / 16 位 | 164 | | `.w` | 字 | 4 字节 / 32 位 | 165 | | `.d` | 双字 | 8 字节 / 64 位 | 166 | 167 | - 对于浮点数指令,后缀和指令类型的对应关系为 168 | 169 | | 指令后缀 | 操作对象类型 | 操作对象长度 | 170 | | :------: | :----------: | :------------: | 171 | | `.h` | 半精度浮点数 | 2 字节 / 16 位 | 172 | | `.s` | 单精度浮点数 | 4 字节 / 32 位 | 173 | | `.d` | 双精度浮点数 | 8 字节 / 64 位 | 174 | | `.w` | 字 | 4 字节 / 32 位 | 175 | | `.l` | 双字 | 8 字节 / 64 位 | 176 | 177 | 当源操作数和目的操作数的数据位宽/类型一致时,指令名只有一个后缀。但是如果源操作数的数据位宽/类型情况一致,但和目的操作数不一致时,那么指令名将有两个后缀: 178 | 179 | - 左边的后缀表示目的操作数的情况 180 | - 右边的操作数表示源操作数的情况 181 | 182 | 比如,将长整数型定点数(双字)转化为单精度浮点数的指令名为 `ffint.s.l`(`.s` 表示转化的目标为单精度浮点数,`.l` 表示转化的源头为有双字)。 183 | 184 | 更复杂的数据位宽情况这里不做讨论,感兴趣的同学可以自行查阅[《龙芯架构参考手册 - 卷一:基础架构》](https://github.com/loongson/LoongArch-Documentation/releases/download/2023.04.20/LoongArch-Vol1-v1.10-CN.pdf)的 1.3 章节。 185 | 186 | ### 整数指令 187 | 188 | 在龙芯架构中,基础整数指令操作的数据类型有 5 种: 189 | 190 | | 数据类型 | 英文 | 简记 | 长度 | 191 | | :------: | :--------: | :--: | :--: | 192 | | 比特 | bit | b | 1b | 193 | | 字节 | Byte | B | 8b | 194 | | 半字 | Halfword | H | 16b | 195 | | 字 | Word | W | 32b | 196 | | 双字 | Doubleword | D | 64b | 197 | 198 | 字节、半字、字和双字数据类型均采用二进制补码的编码方式。 199 | 200 | #### 算数/逻辑运算指令 201 | 202 | ##### add.w, add.d, sub.w, sub.d 203 | 204 | - 指令格式: 205 | 206 | ```asm 207 | add.w $rd, $rj, $rk 208 | sub.w $rd, $rj, $rk 209 | add.d $rd, $rj, $rk 210 | sub.d $rd, $rj, $rk 211 | ``` 212 | 213 | - 行为: 214 | 215 | - `add.w`:将通用寄存器 `$rj` 中的 `[31:0]` 位数据加上通用寄存器 `$rk` 中的 `[31:0]` 位数据,所得结果的 `[31:0]` 位符号扩展后写入通用寄存器 `$rd` 中 216 | - `sub.w`:将通用寄存器 `$rj` 中的 `[31:0]` 位数据减去通用寄存器 `$rk` 中的 `[31:0]` 位数据,所得结果的 `[31:0]` 位符号扩展后写入通用寄存器 `$rd` 中 217 | - `add.d`:将通用寄存器 `$rj` 中的 `[63:0]` 位数据加上通用寄存器 `$rk` 中的 `[63:0]` 位数据,所得结果写入通用寄存器 `$rd` 中 218 | - `sub.d`:将通用寄存器 `$rj` 中的 `[63:0]` 位数据减去通用寄存器 `$rk` 中的 `[63:0]` 位数据,所得结果写入通用寄存器 `$rd` 中 219 | 220 | - 注意事项: 221 | 222 | - 上述指令执行时不对溢出作特殊处理 223 | 224 | - 示例: 225 | 226 | - 在执行下面的指令前,`$t0` 寄存器的值为 `0x0000_0000_0000_0003`,`$t1` 寄存器的值为 `0x0000_0000_0000_0005` 227 | 228 | ```asm 229 | sub.w $t2, $t0, $t1 230 | ``` 231 | 232 | 执行指令后,`$t2` 寄存器的值为 `0xffff_ffff_ffff_fffe`,即其 `[31:0]` 位数据等于 -2 233 | 234 | ##### mul.w, mul.d, div.w, div.d 235 | 236 | - 指令格式: 237 | 238 | ```asm 239 | mul.w $rd, $rj, $rk 240 | div.w $rd, $rj, $rk 241 | mul.d $rd, $rj, $rk 242 | div.d $rd, $rj, $rk 243 | ``` 244 | 245 | - 行为: 246 | 247 | - `mul.w`:将通用寄存器 `$rj` 中的 `[31:0]` 位数据乘以通用寄存器 `$rk` 中的 `[31:0]` 位数据,所得乘积/商的 `[31:0]` 位数据符号扩展后写入通用寄存器 `$rd` 中 248 | - `div.w`:将通用寄存器 `$rj` 中的 `[31:0]` 位数据除去通用寄存器 `$rk` 中的 `[31:0]` 位数据,所得乘积/商的 `[31:0]` 位数据符号扩展后写入通用寄存器 `$rd` 中 249 | - `mul.d`:将通用寄存器 `$rj` 中的 `[63:0]` 位数据乘以通用寄存器 `$rk` 中的 `[63:0]` 位数据,所得乘积/商的 `[63:0]` 位数据写入通用寄存器 `$rd` 中 250 | - `div.d`:将通用寄存器 `$rj` 中的 `[63:0]` 位数据除去通用寄存器 `$rk` 中的 `[63:0]` 位数据,所得乘积/商的 `[63:0]` 位数据写入通用寄存器 `$rd` 中 251 | 252 | - 注意事项: 253 | 254 | - 在 LA64 兼容的机器上,执行 `div.w` 指令时,如果通用寄存器 `$rj` 和 `$rk` 中的数值超过了 32 位有符号数的数值范围,则执行结果可以是无意义的任意值 255 | - `div.w` 进行除法操作时,操作数均视作有符号数 256 | - 对 `div.w` 指令,当除数是 0 时,结果可以是任意值 257 | 258 | - 示例: 259 | 260 | - 在执行下面的指令前,`$t0` 寄存器的值为 `0x0000_0000_0000_0009`,`$t1` 寄存器的值为 `0x0000_0000_0000_0002` 261 | 262 | ```asm 263 | mul.w $t2, $t0, $t1 264 | div.w $t3, $t0, $t1 265 | ``` 266 | 267 | 执行指令后,`$t2` 寄存器的值为 `0x0000_0000_0000_0012`,`$t3` 寄存器的值为 `0x0000_0000_0000_0004`,即其 `[31:0]` 位数据分别等于 18 和 4 268 | 269 | ##### addi.w, addi.d 270 | 271 | - 指令格式: 272 | 273 | ```asm 274 | addi.w $rd, $rj, si12 275 | addi.d $rd, $rj, si12 276 | ``` 277 | 278 | - 行为: 279 | 280 | - `addi.w`:将通用寄存器 `$rj` 中的 `[31:0]` 位数据加上 12 比特立即数 `si12` 符号扩展后的 32 位数据,所得结果的 `[31:0]` 位符号扩展后写入通用寄存器 `$rd` 中 281 | 282 | - `addi.d`:将通用寄存器 `$rj` 中的 `[63:0]` 位数据加上 12 比特立即数 `si12` 符号扩展后的 64 位数据,所得结果写入通用寄存器 `$rd` 中 283 | 284 | - 注意事项: 285 | 286 | - 上述指令执行时不对溢出作特殊处理 287 | 288 | - 示例: 289 | 290 | - 在执行下面的指令前,`$sp` 寄存器的值为 `0x0000_00ff_ffff_33f0` 291 | 292 | ```asm 293 | addi.d $fp, $sp, -32 294 | ``` 295 | 296 | 执行后,`$fp` 寄存器的值为 `0x0000_00ff_ffff_33d0` 297 | 298 | ##### lu12i.w, lu32i.d, lu52i.d 299 | 300 | - 指令格式: 301 | 302 | ```asm 303 | lu12i.w $rd, si20 304 | lu32i.d $rd, si20 305 | lu52i.d $rd, $rj, si12 306 | ``` 307 | 308 | - 行为: 309 | 310 | - `lu12i.w`:将 20 比特立即数 `si20` 后的数据最低位连接上 12 比特 0(即 `si20` 逻辑左移 12 位),然后符号扩展后写入通用寄存器 `$rd` 中 311 | - `lu32i.d`:将 20 比特立即数 `si20` 符号扩展后的数据最低位连接上通用寄存器 `$rd` 中 `[31:0]` 位数据,结果写入到通用寄存器 `$rd` 中 312 | - `lu52i.d`:将 12 比特立即数 `si12`最低位连接上通用寄存器 `$rj` 中 `[51:0]` 位数据,结果写入到通用寄存器 `$rd` 中 313 | 314 | - 注意事项: 315 | 316 | - 上述指令搭配 `ori` 指令,可以将超过 12 位的立即数装入通用寄存器中 317 | 318 | - 示例: 319 | 320 | - 下面的指令将 `0x1234_5678_90ab_cdef` 装入了 `$t0` 寄存器 321 | 322 | ```asm 323 | lu12i.w $t0, -0x6f543 # $t0 = 0xffff_ffff_90ab_c000 324 | ori $t0, $t0, 0xdef # $t0 = 0xffff_ffff_90ab_cdef 325 | lu32i.d $t0, 0x45678 # $t0 = 0xfff4_5678_90ab_cdef 326 | lu52i.d $t0, $t0, 0x123 # $t0 = 0x1234_5678_90ab_cdef 327 | ``` 328 | 329 | 其中 `(int32_t)(-0x6f544)` = `(int32_t)(0xfff90abc)` 330 | 331 | #### 整数比较指令 332 | 333 | ##### slt, sltu 334 | 335 | - 指令格式: 336 | 337 | ```asm 338 | slt $rd, $rj, $rk 339 | sltu $rd, $rj, $rk 340 | ``` 341 | 342 | - 行为: 343 | 344 | - `slt`:将通用寄存器 `$rj` 中的数据与通用寄存器 `$rk` 中的数据视**作有符号整数**进行大小比较,如果前者小于后者,则将通用寄存器 `$rd` 的值置为 `1`,否则置为 `0` 345 | 346 | - `sltu`:将通用寄存器 `$rj` 中的数据与通用寄存器 `$rk` 中的数据**视作无符号整数**进行大小比较,如果前者小于后者,则将通用寄存器 `$rd` 的值置为 `1`,否则置为 `0` 347 | 348 | - 示例: 349 | 350 | - 在执行下面的指令前,`$t0` 寄存器的值为 `0x0000_0000_0000_0001`,`$t1` 寄存器的值为 `0x0000_0000_0000_0002` 351 | 352 | ```asm 353 | slt $t2, $t0, $t1 354 | ``` 355 | 356 | 执行指令后,`$t2` 寄存器的值为 `0x0000_0000_0000_0001` 357 | 358 | ##### slti, sltui 359 | 360 | - 指令格式: 361 | 362 | ```asm 363 | slti $rd, $rj, si12 364 | sltui $rd, $rj, si12 365 | ``` 366 | 367 | - 行为: 368 | 369 | - `slti`:将通用寄存器 `$rj` 中数据与 12 比特立即数 `si12` 符号扩展后所得的数据**视作有符号整数**进行大小比较,如果前者小于后者,则将通用寄存器 `$rd` 的值置为 `1`,否则置为 `0` 370 | 371 | - `sltui`:将通用寄存器 `$rj` 中数据与 12 比特立即数 `si12` 符号扩展后所得的数据**视作无符号整数**进行大小比较,如果前者小于后者,则将通用寄存器 `$rd` 的值置为 `1`,否则置为 `0` 372 | 373 | - 示例: 374 | 375 | - 在执行下面的指令前,`$t0` 寄存器的值为 `0x0000_0000_0000_0008` 376 | 377 | ```asm 378 | slti $t1, $t0, 9 379 | ``` 380 | 381 | 执行指令后,`$t1` 寄存器的值为 `0x0000_0000_0000_0001` 382 | 383 | #### 位运算指令 384 | 385 | ##### and, or, nor, xor 386 | 387 | - 指令格式: 388 | 389 | ```asm 390 | and $rd, $rj, $rk 391 | or $rd, $rj, $rk 392 | nor $rd, $rj, $rk 393 | xor $rd, $rj, $rk 394 | ``` 395 | 396 | - 行为: 397 | 398 | - `and`:将通用寄存器 `$rj` 中的数据与通用寄存器 `$rk` 中的数据进行按位逻辑与运算,结果写入通用寄存器 `$rd` 中 399 | - `or`:将通用寄存器 `$rj` 中的数据与通用寄存器 `$rk` 中的数据进行按位逻辑或运算,结果写入通用寄存器 `$rd` 中 400 | - `nor`:将通用寄存器 `$rj` 中的数据与通用寄存器 `$rk` 中的数据进行按位逻辑或非运算,结果写入通用寄存器 `$rd` 中 401 | - `xor`:将通用寄存器 `$rj` 中的数据与通用寄存器 `$rk` 中的数据进行按位逻辑异或运算,结果写入通用寄存器 `$rd` 中 402 | 403 | ##### andn, orn 404 | 405 | - 指令格式: 406 | 407 | ```asm 408 | andn $rd, $rj, $rk 409 | orn $rd, $rj, $rk 410 | ``` 411 | 412 | - 行为: 413 | 414 | - `andn`:将通用寄存器 `$rj` 中的数据与通用寄存器 `$rk` 中的数据**按位取反后**的数据进行按位逻辑与运算,结果写入通用寄存器 `$rd` 中 415 | - `orn`:将通用寄存器 `$rj` 中的数据与通用寄存器 `$rk` 中的数据**按位取反后**的数据进行按位逻辑或运算,结果写入通用寄存器 `$rd` 中 416 | 417 | ##### andi, ori, xori 418 | 419 | - 指令格式: 420 | 421 | ```asm 422 | andi $rd, $rj, ui12 423 | ori $rd, $rj, ui12 424 | xori $rd, $rj, ui12 425 | ``` 426 | 427 | - 行为: 428 | 429 | - `andi`:将通用寄存器 `$rj` 中数据与 12 比特立即数零扩展后的数据进行按位逻辑与运算,结果写入通用寄存器 `$rd` 中 430 | - `ori`:将通用寄存器 `$rj` 中数据与 12 比特立即数零扩展后的数据进行按位逻辑或运算,结果写入通用寄存器 `$rd` 中 431 | - `xori`:将通用寄存器 `$rj` 中数据与 12 比特立即数零扩展后的数据进行按位逻辑异或运算,结果写入通用寄存器 `$rd` 中 432 | 433 | #### 位操作指令 434 | 435 | ##### bstrpick.w, bstrpick.d 436 | 437 | - 指令格式: 438 | 439 | ```asm 440 | bstrpick.w $rd, $rj, msbw, lsbw 441 | bstrpick.d $rd, $rj, msbd, lsbd 442 | ``` 443 | 444 | - 行为: 445 | 446 | - `bstrpick.w`:提取通用寄存器 `$rj` 中 `[msbw:lsbw]` 位零扩展至 32 位,所形成 32 位中间结果符号扩展后写入通用寄存器 `$rd` 中 447 | 448 | - `bstrpick.d`:提取通用寄存器 `$rj` 中 `[msbd:lsbd]` 位零扩展至 64 位写入通用寄存器 `$rd` 中 449 | 450 | - 示例: 451 | 452 | - 在执行下面的指令前,`$t0` 寄存器的值为 `0x0000_0000_0000_00FF` 453 | 454 | ```asm 455 | bstrpick.w $t1, $t0, 2, 0 456 | ``` 457 | 458 | 执行指令后,`$t1` 寄存器的值为 `0x0000_0000_0000_0007` 459 | 460 | #### 分支指令 461 | 462 | [示例 2](#示例-2) 给出了分支指令 `B` 的使用参考,[示例 3](#示例-3) 给出了过程调用中 `BL` 和 `JIRL` 的使用参考。 463 | 464 | !!! info "跳转目标的表示" 465 | 466 | 在汇编代码中,分支指令的跳转目标地址可以通过相应标签或使用字节为单位的偏移量表示。如果选择使用偏移量表示目标地址,那么在指令格式中的立即数字段应该填入以字节为单位的偏移值,即指令格式中的 `offs << 2`。 467 | 468 | 比如,对于下面的汇编指令 469 | ```asm 470 | label_1: 471 | b label_2 472 | label_2: 473 | add $zero, $zero, $zero 474 | ``` 475 | 它等价于 476 | ```asm 477 | label_1: 478 | b 0x4 479 | label_2: 480 | add $zero, $zero, $zero 481 | ``` 482 | 483 | ##### beq, bne, blt, bge, bltu, bgeu 484 | 485 | - 指令格式: 486 | 487 | ```asm 488 | beq $rj, $rd, offs16 489 | bne $rj, $rd, offs16 490 | blt $rj, $rd, offs16 491 | bge $rj, $rd, offs16 492 | bltu $rj, $rd, offs16 493 | bgeu $rj, $rd, offs16 494 | ``` 495 | 496 | - 目标地址的计算方式:将指令码中的 16 比特立即数 `offs16` 逻辑左移 2 位后再符号扩展,所得偏移值加上该分支指令的 `PC` 497 | 498 | - 行为: 499 | 500 | - `beq`:将通用寄存器 `$rj` 和通用寄存器 `$rd` 的值进行比较,如果两者相等则跳转到目标地址,否则不跳转 501 | 502 | - `bne`:将通用寄存器 `$rj` 和通用寄存器 `$rd` 的值进行比较,如果两者不等则跳转到目标地址,否则不跳转 503 | 504 | - `blt`:将通用寄存器 `$rj` 和通用寄存器 `$rd` 的值视作**有符号数**进行比较,如果前者小于后者则跳转到目标地址,否则不跳转 505 | 506 | - `bge`:将通用寄存器 `$rj` 和通用寄存器 `$rd` 的值视作**有符号数**进行比较,如果前者大于等于后者则跳转到目标地址,否则不跳转 507 | 508 | - `bltu`:将通用寄存器 `$rj` 和通用寄存器 `$rd` 的值视作**无符号数**进行比较,如果前者小于后者则跳转到目标地址,否则不跳转 509 | - `bgeu`:将通用寄存器 `$rj` 和通用寄存器 `$rd` 的值视作**无符号数**进行比较,如果前者大于等于后者则跳转到目标地址,否则不跳转 510 | 511 | - 示例: 512 | 513 | - 在执行下面的指令前,`$t0` 寄存器的值为 `0x0000_0000_0000_0001`,`$t1` 寄存器的值为 `0x0000_0000_0000_0002` 514 | 515 | ```asm 516 | blt $t0, $t1, true_label 517 | addi.w $t2, $zero, 0 518 | b exit # 无条件跳转 519 | true_label: 520 | addi.w $t2, $zero, 1 521 | b exit # 无条件跳转 522 | exit: 523 | # ... 524 | ``` 525 | 526 | 执行后,`$t2` 寄存器的值为 `0x0000_0000_0000_0001` 527 | 528 | ##### beqz, bnez 529 | 530 | - 指令格式: 531 | 532 | ```asm 533 | beqz $rj, offs21 534 | bnez $rj, offs21 535 | ``` 536 | 537 | - 目标地址计算方式:将指令码中的 21 比特立即数 `offs21` 逻辑左移 2 位后再符号扩展,所得偏移值加上该分支指令的 `PC` 538 | 539 | - 行为: 540 | 541 | - `beqz`:对通用寄存器 `$rj` 的值进行判断,如果等于 0 则跳转到目标地址,否则不跳转 542 | - `bnez`:对通用寄存器 `$rj` 的值进行判断,如果不等于 0 则跳转到目标地址,否则不跳转 543 | 544 | - 示例: 545 | 546 | - 在执行下面的指令前,`$t0` 寄存器的值为 `0x0000_0000_0000_0001` 547 | 548 | ```asm 549 | bnez $t0, true_label 550 | addi $t2, $zero, 0 551 | b exit # 无条件跳转 552 | true_label: 553 | addi $t2, $zero, 1 554 | b exit # 无条件跳转 555 | exit: 556 | # ... 557 | ``` 558 | 559 | 执行后,`$t2` 寄存器的值为 `0x0000_0000_0000_0001` 560 | 561 | ##### b 562 | 563 | - 指令格式:`b offs26` 564 | 565 | - 目标地址计算方式:将指令码中的 26 比特立即数 `offs26` 逻辑左移 2 位后再符号扩展,所得偏移值加上该分支指令的 `PC` 566 | 567 | - 行为:无条件跳转到目标地址处 568 | 569 | ##### bl 570 | 571 | - 指令格式:`bl offs26` 572 | - 目标地址计算方式:将指令码中的 26 比特立即数 `offs26` 逻辑左移 2 位后再符号扩展,所得偏移值加上该分支指令的 `PC` 573 | - 行为:无条件跳转到目标地址处,同时将该指令的 `PC` 值加 4 的结果写入到 1 号通用寄存器 `$r1`(`$ra`)中 574 | - 注意事项: 575 | - 1 号通用寄存器 `$r1` 作为返回地址寄存器 `$ra` 576 | - 该指令通常配合 `jirl` 指令,完成函数调用 577 | 578 | ##### jirl 579 | 580 | - 指令格式:`jirl $rd, $rj, offs16` 581 | - 目标地址计算方式:将指令码中的 16 比特立即数 `offs16` 逻辑左移 2 位后再符号扩展,所得偏移值加上通用寄存器 `$rj` 中的值 582 | - 行为:无条件跳转到目标地址处,同时将该指令的 `PC` 值加 4 的结果写入到通用寄存器 `$rd` 中。 583 | 584 | - 注意事项: 585 | - 宏指令 `jr $rj` 等价于 `jirl $zero, $rj, 0` 586 | - 当 `$rd` 为 `$r0` (`$zero`) 时,`jirl` 的功能即是一条普通的非调用间接跳转指令。 587 | - `$rd` 为 `$r0` (`$zero`),`$rj` 为 `$r1` (`$ra`) 且 `offs16` 等于 0 的 `jirl`(即 `jr $ra`)常作为调用返回间接跳转使用 588 | 589 | #### 普通访存指令 590 | 591 | !!! info "小端序" 592 | 593 | 龙芯架构只采用小端序(little endian)的储存方式,即一个字 `0x0123_4567` 在内存中的存储形式如下: 594 | 595 | | 地址 | 字节 | 596 | |:----:|:----:| 597 | | `base + 3` | `0x01` | 598 | | `base + 2` | `0x23` | 599 | | `base + 1` | `0x45` | 600 | | `base` | `0x67` | 601 | 602 | 为了简化实验,可以假设运算环境支持并允许非对齐访存,即 `ld` 和 `st` 指令不需要考虑地址对齐的问题。 603 | 604 | ##### ld.b, ld.h, ld.w, ld.d 605 | 606 | 后缀 `.b`、`.h`、`.w` 和 `.d` 分别代表字节/半字/字/双字数据类型,长度分别为 1 / 2 / 4 / 8 字节。 607 | 608 | - `ld.b` / `ld.h` / `ld.w` 609 | 610 | - 指令格式: 611 | 612 | ```asm 613 | ld.b $rd, $rj, si12 614 | ld.h $rd, $rj, si12 615 | ld.w $rd, $rj, si12 616 | ld.d $rd, $rj, si12 617 | ``` 618 | 619 | - 访存地址:通用寄存器 `$rj` 中的值与符号扩展后的 12 比特立即数 `si12` 相加求和的结果 620 | 621 | - 行为: 622 | 623 | - `ld.b`:从内存取回一个字节的数据符号扩展后写入通用寄存器 `$rd` 624 | - `ld.h`:从内存取回一个半字的数据符号扩展后写入通用寄存器 `$rd` 625 | - `ld.w`:从内存取回一个字的数据符号扩展后写入通用寄存器 `$rd` 626 | - `ld.d`:从内存取回一个双字的数据写入通用寄存器 `$rd` 627 | 628 | ##### st.b, st.h, st.w, st.d 629 | 630 | 后缀 `.b`、`.h`、`.w` 和 `.d` 分别代表字节/半字/字/双字数据类型,长度分别为 1 / 2 / 4 / 8 字节。 631 | 632 | - 指令格式: 633 | 634 | ```asm 635 | st.b $rd, $rj, si12 636 | st.h $rd, $rj, si12 637 | st.w $rd, $rj, si12 638 | st.d $rd, $rj, si12 639 | ``` 640 | 641 | - 访存地址:通用寄存器 `$rj` 中的值与零扩展后的 12 比特立即数 `si12` 相加求和的结果 642 | 643 | - 行为: 644 | 645 | - `st.b`:将通用寄存器 `$rd` 中的 `[7:0]` 位数据写入到内存中 646 | - `st.h`:将通用寄存器 `$rd` 中的 `[15:0]` 位数据写入到内存中 647 | - `st.w`:将通用寄存器 `$rd` 中的 `[31:0]` 位数据写入到内存中 648 | - `st.d`:将通用寄存器 `$rd` 中的 `[63:0]` 位数据写入到内存中 649 | 650 | #### 宏指令 651 | 652 | ##### jr 653 | 654 | - 指令格式:`jr $rj` 655 | - 行为:等价于 `jirl $zero, $rj, 0` 656 | - 注意事项: 657 | - `jr $ra` 常作为调用返回间接跳转使用 658 | 659 | ##### la.local 660 | 661 | - 指令格式:`la.local $rd, label` 662 | 663 | - 行为:将定义在模块内的符号/标签(比如全局变量)的地址加载到通用寄存器 rd 中 664 | 665 | - 示例: 666 | 667 | ```asm 668 | a: # 假设 a 的地址为 0x10000000 669 | .space 4 670 | # ... 671 | la.local $t0, a # 将 a 对应的地址加载到 $t0 中,即 $t0 = 0x10000000 672 | ``` 673 | 674 | ### 浮点数指令 675 | 676 | 在龙芯架构中,浮点数据类型包括单精度浮点数和双精度浮点数,二者均遵循 IEEE 754-2008 标准规范中的定义。部分浮点指令(如浮点转换指令)也会涉及定点(整数)数据,包括字(Word,简记 W,长度 32b)和长字(Longword,简记 L,长度 64b),它们均采用二进制补码的编码方式。 677 | 678 | #### 浮点运算指令 679 | 680 | ##### fadd.s, fsub.s, fmul.s, fdiv.s 681 | 682 | - 指令格式: 683 | 684 | ```asm 685 | fadd.s $fd, $fj, $fk 686 | fsub.s $fd, $fj, $fk 687 | fmul.s $fd, $fj, $fk 688 | fdiv.s $fd, $fj, $fk 689 | ``` 690 | 691 | - 行为: 692 | 693 | - `fadd.s`:将浮点寄存器 `$fj` 中的单精度浮点数加上浮点寄存器 `$fk` 中的单精度浮点数,得到的单精度浮点数结果写入到浮点寄存器 `$fd` 中 694 | - `fsub.s`:将浮点寄存器 `$fj` 中的单精度浮点数减去浮点寄存器 `$fk` 中的单精度浮点数,得到的单精度浮点数结果写入到浮点寄存器 `$fd` 中 695 | - `fmul.s`:将浮点寄存器 `$fj` 中的单精度浮点数乘以浮点寄存器 `$fk` 中的单精度浮点数,得到的单精度浮点数结果写入到浮点寄存器 `$fd` 中 696 | - `fmul.d`:将浮点寄存器 `$fj` 中的单精度浮点数除以浮点寄存器 `$fk` 中的单精度浮点数,得到的单精度浮点数结果写入到浮点寄存器 `$fd` 中 697 | 698 | - 注意事项: 699 | 700 | - 当操作数是单精度浮点数时,结果浮点寄存器的高 32 位可以是任意值 701 | 702 | - 示例: 703 | 704 | - 在执行下面的指令前,`$ft0` 寄存器的值为 `0x0000_0000_4060_0000`(`3.5` 的 FP32 表示),`$ft1` 寄存器的值为 `0x0000_0000_4090_0000`(`4.5` 的 FP32 表示)` 705 | 706 | ```asm 707 | fadd.s $ft2, $ft0, $ft1 708 | fsub.s $ft3, $ft0, $ft1 709 | ``` 710 | 711 | 执行后,`$ft2` 寄存器的值为 `0x0000_0000_4100_0000`(`8.0` 的 FP32 表示),`$ft3` 寄存器的值为 `0x0000_0000_3f80_0000`(`4.5` 的 FP32 表示)` 712 | 713 | #### 浮点转换指令 714 | 715 | ##### ffint.s.w 716 | 717 | `ffint` 指令有两个后缀,其中第一个后缀 `.s` 表示转换的目标为单精度浮点数,第二个后缀 `.w` 表示转换的来源是整数型(字,4 字节)定点数。 718 | 719 | - 指令格式: 720 | 721 | ```asm 722 | ffint.s.w $fd, $fj 723 | ``` 724 | 725 | - 行为:选择**浮点寄存器** `$fj` 中的整数型定点数转换为单精度浮点数,得到的单精度浮点数写入到浮点寄存器 `$fd` 中 726 | 727 | - 示例: 728 | 729 | 在执行下面的指令前,`$ft0` 寄存器的值为 `0x0000_0000_0000_0008` 730 | 731 | ```asm 732 | ffint.s.w $ft1, $ft0 733 | ``` 734 | 735 | 执行后,`$ft1` 寄存器的值为 `0x0000_0000_4100_0000`(`8.0` 的 FP32 表示) 736 | 737 | ##### ftintrz.w.s 738 | 739 | ??? info "舍入模式" 740 | 741 | 为了和 LLVM IR/Light IR 中 `fptosi` 的行为一致,我们选择 “向零方向舍入”(rounding towards zero)作为舍入模式,即选择 `ftintrz` 作为转换浮点数为定点数的指令。 742 | 龙芯架构中也有支持其他的舍入模式的 `ftint*` 指令,感兴趣的同学可以参考[《龙芯架构参考手册 - 卷一:基础架构》](https://github.com/loongson/LoongArch-Documentation/releases/download/2023.04.20/LoongArch-Vol1-v1.10-CN.pdf)的第 3.2.3.2 节。 743 | 744 | `ftintrz` 指令有两个后缀,其中第一个后缀 `.w` 表示转换的目标是整数型(字,4 字节)定点数,第二个后缀 `.s` 表示转换的来源为单精度浮点数。 745 | 746 | - 指令格式: 747 | 748 | ```asm 749 | ftintrz.w.s $fd, $fj 750 | ``` 751 | 752 | - 行为:选择浮点寄存器 `$fj` 中的单精度浮点数转换为整数型定点数,得到的整数型定点数写入到**浮点寄存器** `$fd` 中 753 | 754 | - 示例: 755 | 756 | - 在执行下面的指令前,`$ft0` 寄存器的值为 `0x0000_0000_4108_0000`(`8.5` 的 FP32 表示) 757 | 758 | ```asm 759 | ftintrz.w.s $ft1, $ft0 760 | ``` 761 | 762 | 执行后,`$ft1` 寄存器的值为 `0x0000_0000_0000_0008` 763 | 764 | #### 浮点搬运指令 765 | 766 | ##### movgr2fr.w 767 | 768 | - 指令格式: 769 | 770 | ```asm 771 | movgr2fr.w $fd, $rj 772 | ``` 773 | 774 | - 行为:将通用寄存器 `$rj` 中的 `[31:0]` 位数据写入浮点寄存器 `$fd` 中的 `[31:0]` 位中 775 | 776 | - 注意事项: 777 | 778 | - 使用 `movgr2fr.w` 指令后,浮点寄存器 `$fd` 的 `[63:32]` 位值不确定 779 | 780 | - 示例: 781 | 782 | - 下面的指令将 `0x4108_0000` 装入 `$ft0` 寄存器的 `[31:0]` 位 783 | 784 | ```asm 785 | lu12i.w $t0, 0x41080 786 | movgr2fr.w $ft0, $t0 787 | ``` 788 | 789 | ##### movfr2gr.s 790 | 791 | - `movfr2gr.s` 792 | 793 | - 指令格式: 794 | 795 | ```asm 796 | movfr2gr.s $rd, $fj 797 | ``` 798 | 799 | - 行为:将浮点寄存器中的 `[31:0]` 位数据符号扩展后写入通用寄存器 `$rd` 800 | 801 | - 示例: 802 | 803 | - 下面的指令将全局浮点数变量 `a` 转换为整型载入 `$t0` 中 804 | 805 | ```asm 806 | # 这里省略了一系列伪指令 807 | a: 808 | .space 4 # 申请 4 字节空间存储 a 的值 809 | # ... 810 | la.local $t1, a # 将 a 的地址载入 $t1 811 | fld.s $ft0, $t1, 0 # 将 a 的值载入 $ft0 812 | ftintrz.w.s $ft1, $ft0 # $ft1 = (int)a 813 | movfr2gr.s $t0, $ft1 # 将 $ft1 的数据载入 $t0 中 814 | ``` 815 | 816 | #### 浮点普通访存指令 817 | 818 | ##### fld.s, fld.d 819 | 820 | - 指令格式:`fld.s $fd, $rj, si12` 821 | 822 | ```asm 823 | fld.s $fd, $rj, si12 824 | fld.d $fd, $rj, si12 825 | ``` 826 | 827 | - 访存地址:通用寄存器 `$rj` 中的值与符号扩展后的 12 比特立即数 `si12` 相加求和的结果 828 | 829 | - 行为: 830 | 831 | - `fld.s`:从内存取回一个字的数据写入浮点寄存器 `$fd` 的 `[31:0]` 位 832 | - `fld.d`:从内存取回一个双字的数据写入浮点寄存器 `$fd` 833 | 834 | - 注意事项: 835 | 836 | - 使用 `fld.s` 指令后,浮点寄存器 `$fd` 的 `[63:32]` 位值不确定 837 | 838 | ##### fst.s, fst.d 839 | 840 | - 指令格式: 841 | 842 | ```asm 843 | fst.s $fd, $rj, si12 844 | fst.d $fd, $rj, si12 845 | ``` 846 | 847 | - 访存地址:通用寄存器 `$rj` 中的值与符号扩展后的 12 比特立即数 `si12` 相加求和的结果 848 | 849 | - 行为: 850 | 851 | - `fst.s`:将浮点寄存器 `$fd` 中的 `[31:0]` 位数据写入到内存中 852 | - `fst.d`:将浮点寄存器 `$fd` 中的数据写入到内存中 853 | 854 | #### 浮点比较指令 855 | 856 | ##### fcmp.cond.s, fcmp.cond.d 857 | 858 | - 指令格式: 859 | 860 | ```asm 861 | fcmp.cond.s $fccd, $fj, $fk 862 | fcmp.cond.d $fccd, $fj, $fk 863 | ``` 864 | 865 | - 行为:根据 `cond` 对 `$fj` 和 `$fk` 进行比较,将比较的结果存入[条件标志寄存器](#条件标志寄存器) `$fccd` 866 | 867 | - `cond`:下面是一部分实验中可能会使用到的 `cond` 的信息, 868 | 869 | | 助记符 | `cond` | 含义 | 为真的条件 | 870 | | :----: | :----: | :------: | :------------------------: | 871 | | `seq` | `0x5` | 相等 | `$fj == $fk` | 872 | | `sne` | `0x11` | 不等 | `$fj > $fk` 或 `$fj < $fk` | 873 | | `slt` | `0x3` | 小于 | `$fj < $fk` | 874 | | `sle` | `0x7` | 小于等于 | `$fj <= $fk` | 875 | 876 | 如果你想了解全部的 22 种 `cond`,可以参考[《龙芯架构参考手册 - 卷一:基础架构》](https://github.com/loongson/LoongArch-Documentation/releases/download/2023.04.20/LoongArch-Vol1-v1.10-CN.pdf)的第 3.2.2 节 877 | 878 | - 示例: 879 | 880 | - 在执行下面的指令前,`$ft0` 寄存器的值为 `0x0000_0000_4060_0000`(`3.5` 的 FP32 表示),`$ft1` 寄存器的值为 `0x0000_0000_4090_0000`(`4.5` 的 FP32 表示)` 881 | 882 | ```asm 883 | fcmp.slt.s $fcc0, $ft0, $ft1 884 | ``` 885 | 886 | 执行后,条件标志寄存器 `$fcc0` 的值为 1 887 | 888 | #### 浮点分支指令 889 | 890 | !!! info "跳转目标的表示" 891 | 892 | 在汇编代码中,分支指令的跳转目标地址可以通过相应标签或使用字节为单位的偏移量表示。如果选择使用偏移量表示目标地址,那么在指令格式中的立即数字段应该填入以字节为单位的偏移值,即指令格式中的 `offs << 2`。 893 | 894 | 比如,对于下面的汇编指令 895 | ```asm 896 | label_1: 897 | bceqz $fcc0, label_2 898 | label_2: 899 | add $zero, $zero, $zero 900 | ``` 901 | 它等价于 902 | ```asm 903 | label_1: 904 | bceqz $fcc0, 0x4 905 | label_2: 906 | add $zero, $zero, $zero 907 | ``` 908 | 909 | ##### bceqz, bcnez 910 | 911 | - 指令格式: 912 | 913 | ```asm 914 | bceqz $fccj, offs21 915 | bcnez $fccj, offs21 916 | ``` 917 | 918 | - 跳转地址:将指令码中的 21 比特立即数 `offs21` 逻辑左移 2 位后再符号扩展,所得偏移值加上该分支指令的 `PC` 的和 919 | 920 | - 行为: 921 | 922 | - `bceqz`:对[条件标志寄存器](#条件标志寄存器) `$fccj` 的值进行判断,如果等于 0 则跳转到目标地址,否则不跳转 923 | - `bcnez`:对[条件标志寄存器](#条件标志寄存器) `$fccj` 的值进行判断,如果不等于 0 则跳转到目标地址,否则不跳转 924 | 925 | - 示例: 926 | 927 | - 在执行下面的指令前,条件标志寄存器 `$fcc0` 的值为 1 928 | 929 | ```asm 930 | bcnez $fcc0, true_label 931 | b false_label # 无条件跳转 932 | true_label: 933 | addi.w $t0, $zero, 1 934 | b exit # 无条件跳转 935 | false_label: 936 | addi.w $t0, $zero, 0 937 | b exit # 无条件跳转 938 | exit: 939 | # ... 940 | ``` 941 | 942 | 执行后,`$t0` 寄存器的值为 `0x0000_0000_0000_0001` 943 | 944 | ### GNU 汇编伪指令 945 | 946 | 由于生成的汇编代码最终会被 GNU 工具链中的 Assembler 翻译为机器语言,所以汇编代码中会包含一些 **GNU 汇编伪指令**。这里将简要介绍可能会用到的伪指令,如果想要更进一步了解 GNU 汇编伪指令,可以参考 [Assembler Directives](https://sourceware.org/binutils/docs-2.37/as/Pseudo-Ops.html#Pseudo-Ops)。 947 | 948 | | 伪指令 | 简介 | 949 | | :----------------------------------: | :----------------------------------------------------------------------------------------------------: | 950 | | `.text` | 告诉汇编器将下面的语句汇编到目标程序的代码段 | 951 | | `.globl `/`.global ` | 声明一个符号(通常是标签) `symbol` 为全局符号
这意味着该符号可被其他源文件或目标文件访问 | 952 | | `.type , ` | 将符号指定为特定的类型,我们将主要用到两种类型:
`@function`(函数)和 `@object`(数据对象/变量) | 953 | | `.section ...` | 指定汇编代码或数据应该放在哪个段中,并可以控制段的属性 | 954 | | `.size , ` | 告诉汇编器 `symbol` 的大小为 `expr` 字节 | 955 | | `.space ` | 分配 `size` 字节的空间 | 956 | 957 | 其中,对于 `.section` 伪指令,我们只在给全局变量分配空间时会用到它,具体的语句为: 958 | 959 | ```asm 960 | .section .bss, "aw", @nobits 961 | ``` 962 | 963 | 这三个参数的含义分别是: 964 | 965 | - `.bss`:将数据放在 BSS 段中 966 | - `"aw"`:指定这段空间为可分配(allocatable)且可写(writable)的 967 | - `@nobits`:这段空间不占用实际的文件空间,而是在加载可执行文件时被分配 968 | 969 | #### 示例 970 | 971 | 下面是一段简单的 C 代码: 972 | 973 | ```c 974 | int globalInt; 975 | float globalFloat; 976 | int globalIntArray[10]; 977 | 978 | int main(void) { 979 | return 0; 980 | } 981 | ``` 982 | 983 | 编译器生成的汇编代码如下,其中有大量的伪指令,注释中有对各条伪指令的说明: 984 | 985 | ```asm 986 | # Global variables 987 | .text # 后面的内容将放在代码段 (但是由于 .section, 全局变量实际被放到了 BSS 段) 988 | .section .bss, "aw", @nobits # 将后面的全局变量放到 BSS 段, 并设置属性 989 | 990 | .globl globalInt # 标记 globalInt 为全局符号 991 | .type globalInt, @object # 标记 globalInt 为数据对象/变量 992 | .size globalInt, 4 # 标记 globalInt 的大小为 4 字节 993 | globalInt: 994 | .space 4 # 为 globalInt 分配 4 字节空间 995 | 996 | .globl globalFloat # 标记 globalFloat 为全局符号 997 | .type globalFloat, @object # 标记 globalFloat 为数据对象/变量 998 | .size globalFloat, 4 # 标记 globalFloat 的大小为 4 字节 999 | globalFloat: 1000 | .space 4 # 为 globalFloat 分配 4 字节空间 1001 | 1002 | .globl globalIntArray # 标记 globalIntArray 为全局符号 1003 | .type globalIntArray, @object # 标记 globalIntArray 为数据对象/变量 1004 | .size globalIntArray, 40 # 标记 globalIntArray 的大小为 40 字节 (4 * 10) 1005 | globalIntArray: 1006 | .space 40 # 为 globalIntArray 分配 40 字节空间 1007 | 1008 | # Functions 1009 | .text # 后面的内容将放在代码段 1010 | .globl main # 标记 main 为全局符号, main 函数是程序的入口, 因此这个标记是必须的 1011 | .type main, @function # 标记 main 为函数 1012 | main: 1013 | st.d $ra, $sp, -8 1014 | st.d $fp, $sp, -16 1015 | addi.d $fp, $sp, 0 1016 | addi.d $sp, $sp, -16 1017 | .main_label_entry: 1018 | # ret i32 0 1019 | addi.w $a0, $zero, 0 1020 | b main_exit 1021 | main_exit: 1022 | addi.d $sp, $sp, 16 1023 | ld.d $ra, $sp, -8 1024 | ld.d $fp, $sp, -16 1025 | jr $ra 1026 | ``` 1027 | 1028 | ## 栈帧与函数调用 1029 | 1030 | ### 栈帧布局 1031 | 1032 | 栈帧是程序执行中的一个重要概念,主要用于支持函数调用和返回。因为函数的调用常常是嵌套的,所以堆栈中可能有多个函数的上下文信息。每个未完成运行的函数占用堆栈中一段独立的连续区域,这段区域即为栈帧(Stack Frame)。在调用函数时,栈帧被压入堆栈;在函数返回时,栈帧从堆栈中弹出。 1033 | 1034 | 龙芯架构 ELF psABI 规范中使用两个寄存器来访问堆栈: 1035 | 1036 | - 栈帧指针 `$fp` 指向栈帧的底部 1037 | - 堆栈指针 `$sp` 指向栈帧的顶部 1038 | 1039 | 这两个寄存器的值应该对齐到 16 字节。维护好这两个寄存器的值后,就可以比较方便地访问栈帧中的函数上下文信息,比如返回地址和局部变量。下面是调用堆栈的一个示意图: 1040 | 1041 | ![栈帧示意图](./figs/stack_frame.png) 1042 | 1043 | ### 函数调用过程 1044 | 1045 | 函数调用的过程由调用者和被调用函数共同完成: 1046 | 1047 | 1. 调用方调用函数 1048 | 1049 | 调用方将参数加载到适当的寄存器和堆栈位置,然后执行跳转指令: 1050 | 1051 | ```asm 1052 | bl function # 执行跳转,并将 PC + 4 写入 $ra 1053 | ``` 1054 | 1055 | 2. 被调用方进入 1056 | 1057 | 被调用方需要初始其堆栈并保存上下文信息,假设堆栈大小为 `N`,则对应的指令为: 1058 | 1059 | ```asm 1060 | function: 1061 | st.d $ra, $sp, -8 # 保存返回地址 1062 | st.d $fp, $sp, -16 # 保存栈帧指针的值 1063 | addi.d $fp, $sp, 0 # 将新的栈底值写入 $fp 1064 | addi.d $sp, $sp, -N # 分配栈帧 1065 | .function_label_entry: 1066 | ... 1067 | ``` 1068 | 1069 | 3. 被调用方退出 1070 | 1071 | 在被调用方执行完指令后,被调用方将执行退出。被调用方将返回值放入对应寄存器,释放栈帧并恢复各个保留寄存器,最后返回调用方 1072 | 1073 | ```asm 1074 | .function_label_exit: 1075 | ori $a0, $t0, 0 # 将返回结果保存至 $ra 1076 | b function_exit 1077 | function_exit: 1078 | addi.d $sp, $sp, N # 恢复 $sp 1079 | ld.d $ra, $sp, -8 # 恢复 $ra 1080 | ld.d $fp, $sp, -16 # 恢复 $fp 1081 | jr $ra # 返回到调用方 1082 | ``` 1083 | 1084 | 4. 调用方还原 1085 | 1086 | 如果调用方在调用时分配了堆栈空间,则它需要将这些空间释放 1087 | 1088 | ### 参数传递 1089 | 1090 | 为了简化实验,编译器将遵循下面的参数传递规则: 1091 | 1092 | - 与龙芯架构 ELF psABI 规范一致,编译器将使用 8 个通用寄存器 `$a0` - `$a7` (`$r4` - `$r11`) 和 8 个浮点寄存器 `$fa0` - `$fa1` (`$f0` - `$f7`) 用于参数传递,其中 1093 | 1094 | - 整数类型(指针或者整型)的参数用通用寄存器`$a0` - `$a7` (`$r4` - `$r11`) 传递 1095 | - 浮点数类型的参数使用浮点寄存器 `$fa0` - `$fa1` (`$f0` - `$f7`) 传递 1096 | - 按照顺序,参数依次放入对应的寄存器中 1097 | 1098 | - 示例:下面的函数在注释中提供了各个参数对应的寄存器分配情况 1099 | 1100 | ```c 1101 | int function( 1102 | int a, // 整型,放入 $a0 1103 | int b[], // 指针,放入 $a1 1104 | float c, // 浮点数,放入 $fa0 1105 | int d, // 整型,放入 $a2 1106 | float e, // 浮点数,放入 $fa1 1107 | float f // 浮点数,放入 $fa2 1108 | ) 1109 | ``` 1110 | 1111 | 这个规则只使用通用/浮点参数寄存器来传递参数,不会将参数传递到栈上。具体而言,编译器假定函数的整数类型参数和浮点类型参数均不超过 8 个,对于参数个数超过 8 个的情况,暂不做考虑。 1112 | 1113 | 你可以在这个规则上进行拓展,以支持更多参数的情况。 1114 | 1115 | !!! note 1116 | 1117 | 上面说的只用寄存器传递参数,指的是函数调用时只使用寄存器传参,这是我们对函数传参的约定;在之后的栈式分配过程中,我们依然会将参数备份到栈上,这并不矛盾。 1118 | 1119 | 你也可以选择其他的策略来节省栈空间的使用。 1120 | 1121 | ### 函数的返回 1122 | 1123 | - 如果返回值类型时整数类型(指针或者整型),则返回值放入 `$a0` 中 1124 | - 如果返回值类型时浮点数类型,则返回值放入 `$fa0` 中 1125 | 1126 | ## LA64 汇编示例 1127 | 1128 | 下面的汇编代码可能与实验需要实现的编译器的生成结果有所差异。 1129 | 1130 | ### 示例 1 1131 | 1132 | 源程序: 1133 | 1134 | ```c 1135 | int main(void) { 1136 | return 0; 1137 | } 1138 | ``` 1139 | 1140 | 汇编代码: 1141 | 1142 | ```asm 1143 | .text # 标记代码段 1144 | .globl main # 标记 main 为全局符号 1145 | .type main, @function # 标记 main 为函数 1146 | main: 1147 | st.d $ra, $sp, -8 # 保存返回地址 1148 | st.d $fp, $sp, -16 # 保存调用者的栈帧指针 1149 | addi.d $fp, $sp, 0 # 设置新的栈帧指针 1150 | addi.d $sp, $sp, -16 # 入栈, 为栈帧分配 16 字节的空间 1151 | 1152 | addi.w $a0, $zero, 0 # 将返回值设置成 1 1153 | 1154 | addi.d $sp, $sp, 16 # 出栈, 恢复原来的栈指针 1155 | ld.d $ra, $sp, -8 # 恢复返回地址 1156 | ld.d $fp, $sp, -16 # 恢复栈帧指针 1157 | jr $ra # 返回至调用者 1158 | ``` 1159 | 1160 | 在这个例子中,`main` 函数的栈帧大小为 16 字节,其中存储了返回地址和调用者的栈帧指针 1161 | 1162 | ### 示例 2 1163 | 1164 | 源程序: 1165 | 1166 | ```c 1167 | int a; 1168 | int main(void) { 1169 | a = 4; 1170 | if (a > 3) { 1171 | return 1; 1172 | } 1173 | return 0; 1174 | } 1175 | ``` 1176 | 1177 | 汇编代码: 1178 | 1179 | ```asm 1180 | .text # 标记代码段 1181 | .section .bss, "aw", @nobits # 将后面的全局变量放到 BSS 段, 并设置属性 1182 | .globl a # 标记 a 为全局符号 1183 | .type a, @object # 标记 a 为数据对象/变量 1184 | .size a, 4 # 标记 a 的大小为 4 字节 1185 | a: 1186 | .space 4 # 为 a 分配 4 字节的空间 1187 | 1188 | .text # 标记代码段 1189 | .globl main # 标记 main 为全局符号 1190 | .type main, @function # 标记 main 为函数 1191 | main: # 进入 main 函数 1192 | st.d $ra, $sp, -8 # 保存返回地址 1193 | st.d $fp, $sp, -16 # 保存调用者的栈帧指针 1194 | addi.d $fp, $sp, 0 # 设置新的栈帧指针 1195 | addi.d $sp, $sp, -16 # 入栈, 为栈帧分配 16 字节空间 1196 | .main_label_entry: # 分支判断入口 1197 | addi.w $t4, $zero, 4 # t4 = 4 1198 | addi.w $t2, $zero, 0 # t2 = 0 1199 | la.local $t0, a # 将 a 所处的内存地址加载入 t0 1200 | st.w $t4, $t0, 0 # 将 t4 的数据保存入 t0 指向的地址中 1201 | blt $t2, $t4, .main_then # 将 t2 和 t4 比较,如果 t2 < t4 则跳转到 main_then 1202 | b .main_else # 否则跳转到 .main_else 1203 | .main_else: 1204 | addi.w $a0, $zero, 0 # 设置返回值为 0 1205 | b .main_label_exit # 跳转到 .main_label_exit 1206 | .main_then: 1207 | addi.w $a0, $zero, 1 # 设置返回值为 1 1208 | b .main_label_exit # 跳转到 .main_label_exit 1209 | .main_label_return: # 退出 main 函数 1210 | addi.d $sp, $sp, 16 # 出栈, 恢复原来的栈指针 1211 | ld.d $ra, $sp, -8 # 恢复返回地址 1212 | ld.d $fp, $sp, -16 # 恢复栈帧指针 1213 | jr $ra # 返回至调用者 1214 | ``` 1215 | 1216 | ### 示例 3 1217 | 1218 | 源程序: 1219 | 1220 | ```c 1221 | int add(int a, int b) { 1222 | return a + b; 1223 | } 1224 | 1225 | int main(void) { 1226 | int a; 1227 | int b; 1228 | int c; 1229 | a = 1; 1230 | b = 2; 1231 | c = add(a, b); 1232 | output(c); 1233 | return 0; 1234 | } 1235 | ``` 1236 | 1237 | 汇编程序: 1238 | 1239 | ```asm 1240 | .text # 标记代码段 1241 | .globl add # 标记 add 全局可见(必需) 1242 | .type add, @function # 标记 main 是一个函数 1243 | add: 1244 | st.d $ra, $sp, -8 # 保存返回地址, 在这里即为 bl add 指令的下一条指令地址 1245 | st.d $fp, $sp, -16 # 保存调用者的栈帧指针 1246 | addi.d $fp, $sp, 0 # 设置新的栈帧指针 1247 | addi.d $sp, $sp, -16 # 入栈, 为栈帧分配 16 字节空间 1248 | 1249 | add.d $a0, $a0, $a1 # 计算 a0 + a1, 函数返回值存储到 a0 中 1250 | 1251 | addi.d $sp, $sp, 16 # 出栈, 恢复原来的栈指针 1252 | ld.d $ra, $sp, -8 # 恢复返回地址 1253 | ld.d $fp, $sp, -16 # 恢复栈帧指针 1254 | jr $ra # 返回至调用者 main 函数 1255 | 1256 | .globl main # 标记 main 为全局符号 1257 | .type main, @function # 标记 main 为函数 1258 | main: 1259 | st.d $ra, $sp, -8 # 保存返回地址 1260 | st.d $fp, $sp, -16 # 保存调用者的栈帧指针 1261 | addi.d $fp, $sp, 0 # 设置新的栈帧指针 1262 | addi.d $sp, $sp, -16 # 入栈, 为栈帧分配 16 字节空间 1263 | 1264 | addi.w $a0, $zero, 1 # 设置第一个参数 1265 | addi.w $a1, $zero, 2 # 设置第二个参数 1266 | bl add # 调用 add 函数 1267 | bl output # 输出结果 1268 | 1269 | addi.d $sp, $sp, 16 # 出栈, 恢复原来的栈指针 1270 | ld.d $ra, $sp, -8 # 恢复返回地址 1271 | ld.d $fp, $sp, -16 # 恢复栈帧指针 1272 | jr $ra # 返回至调用者 1273 | ``` 1274 | 1275 | ### 示例 4 1276 | 1277 | 源程序: 1278 | 1279 | ```c 1280 | int main (void) { 1281 | int a; 1282 | float b; 1283 | float c; 1284 | a = 8; 1285 | b = a; 1286 | c = 3.5; 1287 | if (b < c) 1288 | { 1289 | return 1; 1290 | } 1291 | return 0; 1292 | } 1293 | ``` 1294 | 1295 | 汇编程序如下: 1296 | 1297 | ```asm 1298 | .text 1299 | .globl main 1300 | .type main, @function 1301 | main: 1302 | st.d $ra, $sp, -8 # 保存返回地址 1303 | st.d $fp, $sp, -16 # 保存调用者的栈帧指针 1304 | addi.d $fp, $sp, 0 # 设置新的栈帧指针 1305 | addi.d $sp, $sp, -16 # 入栈, 为栈帧分配 16 字节空间 1306 | 1307 | addi.w $t0, $zero, 8 # t0 = 8 1308 | movgr2fr.w $ft0, $t0 # 将 0x8 搬运到 $ft0 中 1309 | ffint.s.w $ft0, $ft0 # 将浮点寄存器 $ft0 中存放的定点数转换为浮点格式 1310 | lu12i.w $t1, 0x40600 # $t1[31:0] = 0x40600000, 即浮点数 3.5 的单精度表示 1311 | movgr2fr.w $ft1, $t1 # 将 0x40600000 搬运到 $ft1 中 1312 | fcmp.slt.s $fcc0, $ft0, $ft1 # $fcc0 = ($ft0 < $ft1) ? 1 : 0 1313 | bceqz $fcc0, .L1 # 如果 $fcc0 等于 0, 则跳转到 .L1 处 1314 | addi.w $t2, $zero, 1 # $t2 = 1 1315 | b .L2 1316 | .L1: 1317 | addi.w $t2, $zero, 0 # $t2 = 0 1318 | .L2: 1319 | ori $a0, $t2, 0 # 设置返回值 $a0 = $t2 1320 | addi.d $sp, $sp, 16 # 出栈, 恢复原来的栈指针 1321 | ld.d $ra, $sp, -8 # 恢复返回地址 1322 | ld.d $fp, $sp, -16 # 恢复栈帧指针 1323 | jr $ra # 返回至调用者 1324 | ``` 1325 | 1326 | 这里使用了两种方式来设置浮点寄存器的值: 1327 | 1328 | - 将通用寄存器 `$t0` 中的定点值 `0x8` 通过 `movgr2fr.w` 指令搬运到 `$ft0` 后,通过 `ffint.s.w` 指令转化为浮点值 1329 | - 通过 `lu12i.w` 指令将 `$t1` 的 `[31:0]` 位设置为 `3.5` 的单精度表示 `0x40600000`,然后通过 `movgr2fr.w` 指令搬运到 `$ft1` 1330 | -------------------------------------------------------------------------------- /docs/common/cminusf.md: -------------------------------------------------------------------------------- 1 | # Cminusf 的语法与语义 2 | 3 | ## Cminusf 的语法 4 | 5 | 1. $\text{program} \rightarrow \text{declaration-list}$ 6 | 2. $\text{declaration-list} \rightarrow \text{declaration-list}\ \text{declaration}\ |\ \text{declaration}$ 7 | 3. $\text{declaration} \rightarrow \text{var-declaration}\ |\ \text{fun-declaration}$ 8 | 4. $\text{var-declaration}\ \rightarrow \text{type-specifier}\ \underline{\textbf{ID}}\ \underline{\textbf{;}}\ |\ \text{type-specifier}\ \underline{\textbf{ID}}\ \underline{\textbf{[}}\ \underline{\textbf{INTEGER}}\ \underline{\textbf{]}}\ \underline{\textbf{;}}$ 9 | 5. $\text{type-specifier} \rightarrow \underline{\textbf{int}}\ |\ \underline{\textbf{float}}\ |\ \underline{\textbf{void}}$ 10 | 6. $\text{fun-declaration} \rightarrow \text{type-specifier}\ \underline{\textbf{ID}}\ \underline{\textbf{(}}\ \text{params}\ \underline{\textbf{)}}\ \text{compound-stmt}$ 11 | 7. $\text{params} \rightarrow \text{param-list}\ |\ \underline{\textbf{void}}$ 12 | 8. $\text{param-list} \rightarrow \text{param-list}\ \underline{\textbf{,}}\ \text{param}\ |\ \text{param}$ 13 | 9. $\text{param} \rightarrow \text{type-specifier}\ \underline{\textbf{ID}}\ |\ \text{type-specifier}\ \underline{\textbf{ID}}\ \underline{\textbf{[}}\ \underline{\textbf{]}}$ 14 | 10. $\text{compound-stmt} \rightarrow \underline{\textbf{\\{}}\ \text{local-declarations}\ \text{statement-list}\ \underline{\textbf{\\}}}$ 15 | 11. $\text{local-declarations} \rightarrow \text{local-declarations var-declaration}\ |\ \text{empty}$ 16 | 12. $\text{statement-list} \rightarrow \text{statement-list}\ \text{statement}\ |\ \text{empty}$ 17 | 13. $\begin{aligned}\text{statement} \rightarrow\ &\text{expression-stmt}\\\ &|\ \text{compound-stmt}\\\ &|\ \text{selection-stmt}\\\ &|\ \text{iteration-stmt}\\\ &|\ \text{return-stmt}\end{aligned}$ 18 | 14. $\text{expression-stmt} \rightarrow \text{expression}\ \underline{\textbf{;}}\ |\ \underline{\textbf{;}}$ 19 | 15. $\begin{aligned}\text{selection-stmt} \rightarrow\ &\underline{\textbf{if}}\ \underline{\textbf{(}}\ \text{expression}\ \underline{\textbf{)}}\ \text{statement}\\\ &|\ \underline{\textbf{if}}\ \underline{\textbf{(}}\ \text{expression}\ \underline{\textbf{)}}\ \text{statement}\ \underline{\textbf{else}}\ \text{statement}\end{aligned}$ 20 | 16. $\text{iteration-stmt} \rightarrow \underline{\textbf{while}}\ \underline{\textbf{(}}\ \text{expression}\ \underline{\textbf{)}}\ \text{statement}$ 21 | 17. $\text{return-stmt} \rightarrow \underline{\textbf{return}}\ \underline{\textbf{;}}\ |\ \underline{\textbf{return}}\ \text{expression}\ \underline{\textbf{;}}$ 22 | 18. $\text{expression} \rightarrow \text{var}\ \underline{\textbf{=}}\ \text{expression}\ |\ \text{simple-expression}$ 23 | 19. $\text{var} \rightarrow \underline{\textbf{ID}}\ |\ \underline{\textbf{ID}}\ \underline{\textbf{[}}\ \text{expression} \underline{\textbf{]}}$ 24 | 20. $\text{simple-expression} \rightarrow \text{additive-expression}\ \text{relop}\ \text{additive-expression}\ |\ \text{additive-expression}$ 25 | 21. $\text{relop}\ \rightarrow \underline{\textbf{<=}}\ |\ \underline{\textbf{<}}\ |\ \underline{\textbf{>}}\ |\ \underline{\textbf{>=}}\ |\ \underline{\textbf{==}}\ |\ \underline{\textbf{!=}}$ 26 | 22. $\text{additive-expression} \rightarrow \text{additive-expression}\ \text{addop}\ \text{term}\ |\ \text{term}$ 27 | 23. $\text{addop} \rightarrow \underline{\textbf{+}}\ |\ \underline{\textbf{-}}$ 28 | 24. $\text{term} \rightarrow \text{term}\ \text{mulop}\ \text{factor}\ |\ \text{factor}$ 29 | 25. $\text{mulop} \rightarrow \underline{\textbf{*}}\ |\ \underline{\textbf{/}}$ 30 | 26. $\text{factor} \rightarrow \underline{\textbf{(}}\ \text{expression}\ \underline{\textbf{)}}\ |\ \text{var}\ |\ \text{call}\ |\ \text{integer}\ |\ \text{float}$ 31 | 27. $\text{integer} \rightarrow \underline{\textbf{INTEGER}}$ 32 | 28. $\text{float} \rightarrow \underline{\textbf{FLOATPOINT}}$ 33 | 29. $\text{call} \rightarrow \underline{\textbf{ID}}\ \underline{\textbf{(}}\ \text{args} \underline{\textbf{)}}$ 34 | 30. $\text{args} \rightarrow \text{arg-list}\ |\ \text{empty}$ 35 | 31. $\text{arg-list} \rightarrow \text{arg-list}\ \underline{\textbf{,}}\ \text{expression}\ |\ \text{expression}$ 36 | 37 | ## Cminusf 的语义 38 | 39 | 在上述语法规则中,我们定义了 Cminusf 语言的语法,接着,我们对照语法规则,给出相关的语义和解释。 40 | 在阅读前,需要理解 Cminusf 主要源自于 C 语言,因此它的行为都会接近 C 语言。 41 | 42 | - $\text{program} \rightarrow \text{declaration-list}$ 43 | - $\text{declaration-list} \rightarrow \text{declaration-list}\ \text{declaration}\ |\ \text{declaration}$ 44 | - $\text{declaration} \rightarrow \text{var-declaration}\ |\ \text{fun-declaration}$ 45 | 46 | 一个程序由一系列声明组成,声明包括了变量声明和函数声明,它们可以以任意顺序排列。 47 | 48 | 全局变量需要初始化为全 0。 49 | 50 | 所有的变量必须在使用前先进行声明,所有的函数必须在使用前先进行定义。 51 | 52 | 一个程序中至少要有一个 `main` 函数的声明。 53 | 54 | 因为没有原型这个概念,Cminusf 不区分函数的声明和定义。 55 | 56 | - $\text{var-declaration}\ \rightarrow \text{type-specifier}\ \underline{\textbf{ID}}\ \underline{\textbf{;}}\ |\ \text{type-specifier}\ \underline{\textbf{ID}}\ \underline{\textbf{[}}\ \underline{\textbf{INTEGER}}\ \underline{\textbf{]}}\ \underline{\textbf{;}}$ 57 | - $\text{type-specifier} \rightarrow \underline{\textbf{int}}\ |\ \underline{\textbf{float}}\ |\ \underline{\textbf{void}}$ 58 | 59 | Cminusf 的基础类型只有整型 (`int`)、浮点型 (`float`) 和 `void`。而在变量声明中,只有整型和浮点型可以使用,`void` 仅用于函数声明。 60 | 61 | 一个变量声明定义一个整型或者浮点型的变量,或者一个整型或浮点型的数组变量(这里整型指的是 32 位有符号整型,浮点数是指 32 位浮点数)。 62 | 63 | 数组变量在声明时,$\textbf{INTEGER}$ 应当大于 0。 64 | 65 | 一次只能声明一个变量。 66 | 67 | - $\text{fun-declaration} \rightarrow \text{type-specifier}\ \underline{\textbf{ID}}\ \underline{\textbf{(}}\ \text{params}\ \underline{\textbf{)}}\ \text{compound-stmt}$ 68 | - $\text{params} \rightarrow \text{param-list}\ |\ \underline{\textbf{void}}$ 69 | - $\text{param-list} \rightarrow \text{param-list}\ \underline{\textbf{,}}\ \text{param}\ |\ \text{param}$ 70 | - $\text{param} \rightarrow \text{type-specifier}\ \underline{\textbf{ID}}\ |\ \text{type-specifier}\ \underline{\textbf{ID}}\ \underline{\textbf{[}}\ \underline{\textbf{]}}$ 71 | 72 | 函数声明包含了返回类型,标识符,由逗号分隔的形参列表,还有一个复合语句。 73 | 74 | 当函数的返回类型是 `void` 时,函数不返回任何值。 75 | 76 | 函数的参数可以是 `void` ,也可以是一个列表。当函数的形参是 `void` 时,调用该函数时不用传入任何参数。 77 | 78 | 形参中跟着中括号代表数组参数,它们可以有不同长度。 79 | 80 | 整型参数通过值来传入函数(pass by value),而数组参数通过引用来传入函数(pass by reference,即指针)。 81 | 82 | 函数的形参拥有和函数声明的复合语句相同的作用域,并且每次函数调用都会产生一组独立内存的参数(和 C 语言一致)。 83 | 84 | 函数可以递归调用。 85 | 86 | - $\text{compound-stmt} \rightarrow \underline{\textbf{\\{}}\ \text{local-declarations}\ \text{statement-list}\ \underline{\textbf{\\}}}$ 87 | 88 | 一个复合语句由一对大括号和其中的局部声明与语句列表组成。 89 | 90 | 复合语句的执行时,对包含着的语句按照语句列表中的顺序执行。 91 | 92 | 局部声明拥有和复合语句中的语句列表一样的作用域,且其优先级高于任何同名的全局声明(常见的静态作用域)。 93 | 94 | - $\text{local-declarations} \rightarrow \text{local-declarations var-declaration}\ |\ \text{empty}$ 95 | - $\text{statement-list} \rightarrow \text{statement-list}\ \text{statement}\ |\ \text{empty}$ 96 | 97 | 局部声明和语句列表都可以为空(empty 表示空字符串,即 $\varepsilon$)。 98 | 99 | - $\begin{aligned}\text{statement} \rightarrow\ &\text{expression-stmt}\\\ &|\ \text{compound-stmt}\\\ &|\ \text{selection-stmt}\\\ &|\ \text{iteration-stmt}\\\ &|\ \text{return-stmt}\end{aligned}$ 100 | - $\text{expression-stmt} \rightarrow \text{expression}\ \underline{\textbf{;}}\ |\ \underline{\textbf{;}}$ 101 | 102 | 表达式语句由一个可选的表达式(即可以没有表达式)和一个分号组成。 103 | 104 | 我们通常使用表达式语句中的表达式计算时产生的副作用,所以这种语句用于赋值和函数调用。 105 | 106 | - $\begin{aligned}\text{selection-stmt} \rightarrow\ &\underline{\textbf{if}}\ \underline{\textbf{(}}\ \text{expression}\ \underline{\textbf{)}}\ \text{statement}\\\ &|\ \underline{\textbf{if}}\ \underline{\textbf{(}}\ \text{expression}\ \underline{\textbf{)}}\ \text{statement}\ \underline{\textbf{else}}\ \text{statement}\end{aligned}$ 107 | 108 | `if` 语句中的表达式将被求值,若结果的值等于 0,则第二个语句执行(如果存在的话),否则第一个语句会执行。 109 | 110 | 为了避免歧义,$\textbf{else}$ 将会匹配最近的 $\textbf{if}$。 111 | 112 | - $\text{iteration-stmt} \rightarrow \underline{\textbf{while}}\ \underline{\textbf{(}}\ \text{expression}\ \underline{\textbf{)}}\ \text{statement}$ 113 | 114 | `while` 语句是 Cminusf 中唯一的迭代语句。它执行时,会不断对表达式进行求值,并且在对表达式的求值结果等于 0 前,循环执行下面的语句。 115 | 116 | - $\text{return-stmt} \rightarrow \underline{\textbf{return}}\ \underline{\textbf{;}}\ |\ \underline{\textbf{return}}\ \text{expression}\ \underline{\textbf{;}}$ 117 | 118 | `return` 语句可以返回值,也可以不返回值。 119 | 120 | 未声明为 $\textbf{void}$ 类型的函数必须返回和函数返回类型相同的值。 121 | 122 | `return` 会将程序的控制转移给当前函数的调用者,而 $\textbf{main}$ 函数的 `return` 会使得程序终止。 123 | 124 | - $\text{expression} \rightarrow \text{var}\ \underline{\textbf{=}}\ \text{expression}\ |\ \text{simple-expression}$ 125 | - $\text{var} \rightarrow \underline{\textbf{ID}}\ |\ \underline{\textbf{ID}}\ \underline{\textbf{[}}\ \text{expression} \underline{\textbf{]}}$ 126 | 127 | 一个表达式可以是一个变量引用(即 `var`)接着一个赋值符号(=)以及一个表达式,也可以是一个简单表达式。 128 | 129 | `var` 可以是一个整型变量、浮点变量,或者一个取了下标的数组变量。 130 | 131 | 数组的下标值为整型,作为数组下标值的表达式计算结果可能需要类型转换变成整型值。 132 | 133 | 一个负的下标会导致程序终止,需要调用框架中的内置函数 `neg_idx_except`(该内部函数会主动退出程序,只需要调用该函数即可),但是对于上界并不做检查。 134 | 135 | 赋值语义为:先找到 `var` 代表的变量地址(如果是数组,需要先对下标表达式求值),然后对右侧的表达式进行求值,求值结果将在转换成变量类型后存储在先前找到的地址中。同时,存储在 `var` 中的值将作为赋值表达式的求值结果。 136 | 137 | 在 C 中,赋值对象(即 `var` )必须是左值,而左值可以通过多种方式获得。Cminusf 中,唯一的左值就是通过 `var` 的语法得到的,因此 Cminusf 通过语法限制了 `var` 为左值,而不是像 C 中一样通过类型检查,这也是为什么 Cminusf 中不允许进行指针算数。 138 | 139 | - $\text{simple-expression} \rightarrow \text{additive-expression}\ \text{relop}\ \text{additive-expression}\ |\ \text{additive-expression}$ 140 | - $\text{relop}\ \rightarrow \underline{\textbf{<=}}\ |\ \underline{\textbf{<}}\ |\ \underline{\textbf{>}}\ |\ \underline{\textbf{>=}}\ |\ \underline{\textbf{==}}\ |\ \underline{\textbf{!=}}$ 141 | - $\text{additive-expression} \rightarrow \text{additive-expression}\ \text{addop}\ \text{term}\ |\ \text{term}$ 142 | - $\text{addop} \rightarrow \underline{\textbf{+}}\ |\ \underline{\textbf{-}}$ 143 | - $\text{term} \rightarrow \text{term}\ \text{mulop}\ \text{factor}\ |\ \text{factor}$ 144 | - $\text{mulop} \rightarrow \underline{\textbf{*}}\ |\ \underline{\textbf{/}}$ 145 | 146 | 一个简单表达式是一个加法表达式或者两个加法表达式的关系运算。当它是加法表达式时,它的值就是加法表达式的值。而当它是关系运算时,如果关系运算结果为真则值为整型值 1,反之则值为整型值 0。 147 | 148 | 加法表达式表现出了四则运算的结合性质与优先级顺序,四则运算的含义和 C 中的整型运算一致。 149 | 150 | 浮点数和整型一起运算时,整型值需要进行类型提升,转换成浮点数类型,且运算结果也是浮点数类型。 151 | 152 | - $\text{factor} \rightarrow \underline{\textbf{(}}\ \text{expression}\ \underline{\textbf{)}}\ |\ \text{var}\ |\ \text{call}\ |\ \text{integer}\ |\ \text{float}$ 153 | 154 | 因数可以是一个括号包围的表达式(此时它的值是表达式的值),或者是一个变量(此时它的值是变量的值),或者是一个函数调用(此时它的值是函数调用的返回值),或者是一个数字字面量(此时它的值为该字面量的值)。当因数是数组变量时,除非此时它被用作一个函数调用中的数组参数,否则它必须要带有下标。 155 | 156 | - $\text{integer} \rightarrow \underline{\textbf{INTEGER}}$ 157 | - $\text{float} \rightarrow \underline{\textbf{FLOATPOINT}}$ 158 | - $\text{call} \rightarrow \underline{\textbf{ID}}\ \underline{\textbf{(}}\ \text{args} \underline{\textbf{)}}$ 159 | - $\text{args} \rightarrow \text{arg-list}\ |\ \text{empty}$ 160 | - $\text{arg-list} \rightarrow \text{arg-list}\ \underline{\textbf{,}}\ \text{expression}\ |\ \text{expression}$ 161 | 162 | 函数调用由一个函数的标识符与一组括号包围的实参组成。实参可以为空,也可以是由逗号分隔的的表达式组成的列表,这些表达式代表着函数调用时,传给形参的值。函数调用时的实参数量和类型必须与函数声明中的形参一致,必要时需要进行类型转换。 163 | 164 | Cminusf 中包含四个预定义的函数 `input` 、 `output`、 `outputFloat` 和 `neg_idx_except`,它们的声明为: 165 | 166 | ```c 167 | int input(void) {...} 168 | void output(int x) {...} 169 | void outputFloat(float x) {...} 170 | void neg_idx_except(void) {...} 171 | ``` 172 | 173 | - `input` 函数没有形参,且返回一个从标准输入中读到的整型值。 174 | - `output` 函数接受一个整型参数,然后将它的值打印到标准输出,并输出换行符。 175 | - `outputFloat` 函数接受一个浮点参数,然后将它的值打印到标准输出,并输出换行符。 176 | - `neg_idx_except` 函数没有形参,执行后报错并退出。 177 | 178 | 除此之外,其它规则和 C 中类似,比如同一个作用域下不允许定义重名变量或函数。 179 | -------------------------------------------------------------------------------- /docs/common/figs/asm_structure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustc-compiler-principles/2023/3413d0831014139229c3469a81fe541aad7cab81/docs/common/figs/asm_structure.png -------------------------------------------------------------------------------- /docs/common/figs/lightir.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustc-compiler-principles/2023/3413d0831014139229c3469a81fe541aad7cab81/docs/common/figs/lightir.png -------------------------------------------------------------------------------- /docs/common/figs/module_relation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustc-compiler-principles/2023/3413d0831014139229c3469a81fe541aad7cab81/docs/common/figs/module_relation.png -------------------------------------------------------------------------------- /docs/common/figs/stack_frame.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustc-compiler-principles/2023/3413d0831014139229c3469a81fe541aad7cab81/docs/common/figs/stack_frame.png -------------------------------------------------------------------------------- /docs/common/figs/type_inherit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustc-compiler-principles/2023/3413d0831014139229c3469a81fe541aad7cab81/docs/common/figs/type_inherit.png -------------------------------------------------------------------------------- /docs/common/figs/user_inherit.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustc-compiler-principles/2023/3413d0831014139229c3469a81fe541aad7cab81/docs/common/figs/user_inherit.jpg -------------------------------------------------------------------------------- /docs/common/figs/value_inherit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustc-compiler-principles/2023/3413d0831014139229c3469a81fe541aad7cab81/docs/common/figs/value_inherit.png -------------------------------------------------------------------------------- /docs/common/logging.md: -------------------------------------------------------------------------------- 1 | # logging 工具使用 2 | 3 | ## 介绍 4 | 5 | 为了方便同学们在之后的实验中 debug,为大家设计了一个 C++ 简单实用的分级日志工具。该工具将日志输出信息从低到高分成四种等级:`DEBUG`,`INFO`,`WARNING`,`ERROR`。通过设定环境变量 `LOGV` 的值,来选择输出哪些等级的日志。`LOGV` 的取值是 **0 到 3**,分别对应到上述的 4 种级别 (`0:DEBUG`,`1:INFO`,`2:WARNING`,`3:ERROR`)。此外输出中还会包含打印该日志的代码所在位置。 6 | 7 | ## 使用 8 | 9 | 项目编译好之后,可以在 `build` 目录下运行 test_logging 可执行文件,该文件的源代码在 `tests/test_logging.cpp`,用法如下: 10 | 11 | ```cpp 12 | #include "logging.hpp" 13 | // 引入头文件 14 | int main(){ 15 | LOG(DEBUG) << "This is DEBUG log item."; 16 | // 使用关键字 LOG,括号中填入要输出的日志等级 17 | // 紧接着就是<<以及日志的具体信息,就跟使用 std::cout 一样 18 | LOG(INFO) << "This is INFO log item"; 19 | LOG(WARNING) << "This is WARNING log item"; 20 | LOG(ERROR) << "This is ERROR log item"; 21 | return 0; 22 | } 23 | ``` 24 | 25 | 接着在运行该程序的时候,设定环境变量 `LOGV=0`,那么程序就会输出级别**大于等于 0** 日志信息: 26 | 27 | ```bash 28 | user@user:${ProjectDir}/build$ LOGV=0 ./test_logging 29 | [DEBUG] (test_logging.cpp:5L main)This is DEBUG log item. 30 | [INFO] (test_logging.cpp:6L main)This is INFO log item 31 | [WARNING] (test_logging.cpp:7L main)This is WARNING log item 32 | [ERROR] (test_logging.cpp:8L main)This is ERROR log item 33 | ``` 34 | 35 | 输出中除了包含日志级别和用户想打印的信息,在圆括号中还包含了打印该信息代码的具体位置(包括文件名称、所在行、所在函数名称),可以很方便地定位到出问题的地方。 36 | 37 | 假如我们觉得程序已经没有问题了,不想看那么多的 DEBUG 信息,那么我们就可以设定环境变量 `LOGV=1`,选择只看**级别大于等于 1** 的日志信息: 38 | 39 | ```bash 40 | user@user:${ProjectDir}/build$ LOGV=0 ./test_logging 41 | [INFO] (test_logging.cpp:6L main)This is INFO log item 42 | [WARNING] (test_logging.cpp:7L main)This is WARNING log item 43 | [ERROR] (test_logging.cpp:8L main)This is ERROR log item 44 | ``` 45 | 46 | 当然 `LOGV` 值越大,日志的信息将更加简略。如果没有设定 `LOGV` 的环境变量,将默认不输出任何信息。 47 | 48 | 这里再附带一个小技巧,如果日志内容多,在终端观看体验较差,可以输入以下命令将日志输出到文件中: 49 | 50 | ``` 51 | user@user:${ProjectDir}/build$ LOGV=0 ./test_logging > log 52 | ``` 53 | 54 | 然后就可以输出到文件名为 log 的文件中啦~ 55 | -------------------------------------------------------------------------------- /docs/common/simple_cpp.md: -------------------------------------------------------------------------------- 1 | # CPP 简介 2 | 3 | C++ 是一门面向对象的语言,在 C 语言之上添加了许多新特性。我们的实验并不需要你们使用所有高级的 C++ 特性,在此简单介绍一下部分特性以便更好地完成实验。 4 | 5 | ## STL 6 | 7 | STL (Standard Template Library),意为标准模板库,包含了许多常用的数据结构,在我们的实验中你可能会用到: 8 | 9 | - `std::vector`: 可变长数组 10 | - `std::map`: 由红黑树实现的有序关联容器 11 | - `std::set`: 由红黑树实现的有序集合 12 | - `std::unordered_map`: 由哈希表实现的无序关联容器 13 | - `std::list`: 链表 14 | 15 | 这里的 `std` 是 C++ 中的命名空间,可以防止标识符的重复,详见 [维基百科](https://en.wikipedia.org/wiki/Namespace) 16 | 17 | 同时,这些容器都是模板 [Template](),需要实例化,例如一个可变长的整形数组应该实例化为 `std::vector`。 18 | 19 | 更多信息可以参考:[https://en.cppreference.com/w/cpp/standard_library](https://en.cppreference.com/w/cpp/standard_library) 20 | 21 | ## string 22 | 23 | C++ 提供了更易用的 `std::string` 以处理字符串,可以支持通过 `+` 拼接,还提供了许多方法: 24 | 25 | - `length`: 返回字符串长度。 26 | - `push_back(c)`: 在字符串末尾添加一个字符 `c`。 27 | - `append(str)`: 在字符串末尾添加字符串 `str`。 28 | - `substr`: 取子串。 29 | 30 | 更多方法请查阅:[https://en.cppreference.com/w/cpp/string](https://en.cppreference.com/w/cpp/string) 31 | 32 | ## auto 33 | 34 | `auto` 关键字可以用于当类型已知时自动推断类型,例如: 35 | 36 | ```cpp 37 | std::vector v; 38 | v.push_back("compile"); 39 | auto s = v.front(); // 这里 s 就是 std::string 类型 40 | ``` 41 | 42 | ## class 43 | 44 | class 是 C++ 面向对象的基础,它相当于对 C 中的结构体的扩展。除了保留了原来的结构体成员(即成员对象),它增加了成员函数、访问控制、继承和多态等。 45 | 46 | 假设某类为 `Animal`,一个它的实例为 `animal`,我们可以在 `Animal` 的定义中增加函数声明 `void eat();`,这样声明的函数即是成员函数。成员函数的作用域中自带一个 `Animal*` 类型的指针 `this`,指向调用该成员函数的实例。我们可以通过 `animal.eat()` 一样,用类似访问成员的方法访问该函数。 47 | 48 | 在面向对象的术语中,我们通常称成员函数为“方法”([method]())。 49 | 50 | ```cpp 51 | // 注:C++ 中 struct 可以看作默认所有成员都是 public 的 class 52 | struct Animal { 53 | void eat(); 54 | }; 55 | ``` 56 | 57 | 类的访问控制指的是在定义 class 时,可以用 `public` 与 `private` 标签,指定接下来的成员是私有或是公开成员。公开成员可以在外部函数使用该类的实例时访问,而内部成员只有该类的成员函数能访问。访问控制的作用是对使用者隐藏实现的细节,而关注于设计者想要公开的接口,从而让使用者能更容易理解如何使用该类。详细介绍在 [access specifiers](https://en.cppreference.com/w/cpp/language/access)。 58 | 59 | 我们通常将类的声明放到头文件中,同时为了区别 C 的头文件,用后缀 `.hpp` 表示 C++ 的头文件。 60 | 61 | ## 类的继承 62 | 63 | 类的继承是一种面向对象语言常用的代码复用方法,也是一种非常直观的抽象方式。我们可以定义 `struct Cat : Animal` 来声明 `Cat` 类是 `Animal` 类的子类,也就是 `Cat` 继承了 `Animal` 类。此时,新的 `Cat` 类从 `Animal` 类中继承了 `void eat();` 成员函数,并且可以在此之上定义额外的成员函数 `void nyan()`。同理,我们也可以定义 `struct Dog : Animal` 来定义 `Dog` 类。 64 | 65 | ```cpp 66 | struct Cat : Animal { 67 | // 从 Animal 中继承了 void eat(); 68 | void nyan(); 69 | }; 70 | 71 | struct Dog : Animal { 72 | // 从 Animal 中继承了 void eat(); 73 | void wang() 74 | }; 75 | ``` 76 | 77 | 我们可以通过合理的继承结构来将函数定义在合适的位置,使得大部分通用函数可以共享。 78 | 79 | 同学们可能会想到同是 `Animal`,`Cat` 和 `Dog` 可能会有相同名称与参数的函数,但是却有着不同的实现,这时我们就要用到虚函数了。子类中可以定义虚函数的实现,从而使得不同子类对于同一个名字的成员函数有不同实现。 80 | 81 | 我们可以用子类的指针给基类指针赋值,在基类指针上调用虚函数时,会通过虚函数表查找到对应的函数实现,而不是和普通类一样查找对应类型的函数实现。 82 | 83 | ```cpp 84 | struct Animal { 85 | // = 0 表示该虚函数在 Animal 类中没有实现 86 | virtual void say() = 0; 87 | }; 88 | 89 | struct Cat : Animal { 90 | // override 表示覆盖父函数中的实现,下同 91 | void say() override { 92 | std::cout << "I'm a cat" << std::endl; 93 | } 94 | }; 95 | 96 | struct Dog : Animal { 97 | void say() override{ 98 | std::cout << "I'm a dog" << std::endl; 99 | } 100 | }; 101 | 102 | // 试一试 103 | int main() { 104 | Cat c; 105 | Dog d; 106 | Animal* a; 107 | c.say(); 108 | d.say(); 109 | a = &c; 110 | // 这里基类指针指向 Cat 类实例,调用 say 时实际会调用 Cat 类的实现,下同 111 | a->say(); 112 | a = &d; 113 | a->say(); 114 | return 0; 115 | } 116 | ``` 117 | 118 | 同时,既然子类的指针可以赋值给基类,例如上面我们让 `Animal *a = &c`,这时 `a` 指向的是 `Cat` 类的实例,为了得到 `Cat` 类的对应指针,需要进行类型转换,可以使用 `dynamic_cast`: 119 | 120 | ```cpp 121 | auto cc = dynamic_cast(a); 122 | ``` 123 | 124 | 这样我们就可以把基类指针又转回对应的子类指针。当然,如果指针不是 `Cat *` 类型的,`dynamic_cast` 将会返回 `nullptr`。(与 C 中统一使用 `NULL` 不同,在 C++ 中,我们用 `nullptr` 表示空指针) 125 | 126 | 在确信类型转换一定成功时,也可以使用 `static_cast` 来完成转换,可以省去运行时的类型检查。此外,`static_cast` 还用于基本类型的转换,例如: 127 | 128 | ```cpp 129 | float f = 3.14; 130 | int n = static_cast(f); 131 | ``` 132 | 133 | !!! warning 134 | 135 | 你应该避免使用 C 风格的强制类型转换,例如 `int n = (int)f`,因为 C++ 风格的类型转换会被编译器检查,而 C 风格的不会。 136 | 137 | ## 函数重载 138 | 139 | C++ 中的函数可以重载,即可以有同名函数,但是要求它们的形参必须不同。如果想进一步了解,可以阅读[详细规则](https://en.cppreference.com/w/cpp/language/overload_resolution)。下面是函数重载的示例: 140 | 141 | ```cpp 142 | struct Point { 143 | int x; 144 | int y; 145 | }; 146 | 147 | struct Line { 148 | Point first; 149 | Point second; 150 | }; 151 | 152 | void print(Point p) { 153 | printf("(%d, %d)", p.x, p.y); 154 | } 155 | 156 | void print(Line s) { 157 | print(s.first); // s.first == Point { ... } 158 | printf("->"); 159 | print(s.second); // s.second == Point { ... } 160 | } 161 | ``` 162 | 163 | 我们可以看到上面的示例定义了两个 `print` 函数,并且它们的参数列表的类型不同。它们实际上是两个不同的函数(并且拥有不同的内部名字),但是 C++ 能够正确的识别函数调用时使用了哪一个定义(前提是你正确使用了这一特性),并且在编译时就会链接上正确的实现。我们可以看到,这种特性非常符合人的直觉,并且没有带来额外开销。 164 | 165 | ## 内存管理 166 | 167 | C++11 引入了许多智能指针类型来帮助自动内存管理,本实验中用到的有两种,分别是: 168 | 169 | 1. `std::shared_ptr`: 引用计数智能指针,使用一个共享变量来记录指针管理的对象被引用了几次。当对象引用计数为 0 时,说明当前该对象不再有引用,并且进程也无法再通过其它方式来引用它,也就意味着可以回收内存,这相当于低级的垃圾回收策略。可以用 `std::make_shared` 来创建。 170 | 2. `std::unique_ptr`: 表示所有权的智能指针,该指针要求它所管理的对象只能有一次引用,主要用于语义上不允许共享的对象(比如 `llvm::Module`)。当引用计数为 0 时,它也会回收内存。可以用 `std::make_unique` 来创建。 171 | 172 | 在涉及到内存管理时,应该尽量使用智能指针。 173 | 174 | ## 调试 175 | 176 | ### Segmentation Fault 177 | 178 | 在之后的实验中,你可能会各种各样的段错误(Segmentation Fault),不要害怕!clang 提供了一个工具来帮助我们解决内存泄漏。 179 | 180 | ```bash 181 | $ cd build 182 | $ cmake .. -DCMAKE_BUILD_TYPE=Asan 183 | $ make 184 | ``` 185 | 186 | 然后再次运行你的错误代码,Asan 会提供更详细的报错信息。 187 | 188 | 注:要更换到别的 build type(如 Debug 或 Release)时需要显式指定,否则 cmake 会使用 cached 的版本。 189 | -------------------------------------------------------------------------------- /docs/css/extra.css: -------------------------------------------------------------------------------- 1 | .red,.red-important{color:red!important}.darkred,.darkred-important{color:darkred!important}.orangered,.orangered-important{color:#ff4500!important}.green,.green-important{color:green!important}.limegreen,.limegreen-important{color:#32cd32!important}.cyan,.cyan-important{color:cyan!important}.darkcyan,.darkcyan-important{color:#008b8b!important}:root{--md-text-font: Roboto;--md-code-font: Roboto Mono}.md-typeset #references,.md-typeset .no-underline{margin-bottom:0;padding-bottom:0;border:none}.md-typeset h2{padding-bottom:.2em;border-bottom:1px solid #d3d3d3}.md-typeset h3{padding-bottom:.2em;border-bottom:1px dashed #d3d3d3}.md-typeset h4{font-size:1.17em}.md-typeset h5{font-size:1.08em}.md-typeset h6{font-size:1em}.md-typeset dt{font-weight:700}.md-typeset img{display:block;margin-left:auto;margin-right:auto}.md-typeset .img-inline{display:inline-block;vertical-align:text-bottom}.md-typeset .img-border{border:1px solid #000}.md-typeset kbd{box-shadow:0 0 0 .05rem var(--md-default-fg-color--lighter),0 .1rem 0 var(--md-default-fg-color--lighter),inset 0 -.1rem .2rem var(--md-default-bg-color)}.md-typeset .admonition,.md-typeset details{font-size:.9em}.md-typeset .md-typeset__table{display:block;margin-left:auto;margin-right:auto}.md-typeset .md-typeset__table>table{display:table;margin:0 auto}.md-typeset p.caption{text-align:center;font-size:.94em;color:grey;margin:-.7em 0 0}.md-typeset .footnote hr{margin-top:.2em} -------------------------------------------------------------------------------- /docs/exp_platform_intro/README.md: -------------------------------------------------------------------------------- 1 | # 课程平台介绍 2 | 3 | 本课程使用希冀实验平台进行作业提交和在线评测,同时使用 git 和 GitLab 进行代码开发和项目管理。 4 | 5 | ## 希冀实验平台 6 | 7 | 网址:**[http://cscourse.ustc.edu.cn/](http://cscourse.ustc.edu.cn/)** 8 | 9 | 进入希冀平台后,使用学生入口填写学号(大写字母,如 PB20110001)和默认密码(与学号相同)登录。如果您已注册过希冀平台账号,请使用原密码登录。 10 | 11 | 在网页右上角选择“编译原理和技术”课程实验平台。 12 | 13 | ![Untitled](Untitled.png) 14 | 15 | 首次登录希冀平台时,请记得修改默认密码并完善个人信息。 16 | 17 | 在希冀平台,您可以查看最新公告、作业和截止时间。**请注意,所有作业和实验都需要在平台上提交和批改。** 18 | 19 | ## 作业及实验提交 20 | 21 | 在课程平台左上角选择当前作业,在线提交如下: 22 | 23 | ![Untitled](Untitled1.png) 24 | 25 | 1. 请提交作业和实验报告的 PDF 文件,确保照片部分清晰可读。如果因图片清晰度问题而导致扣分,则不接受相关的申诉。 26 | 2. 提交作业不接受迟交或补交。 27 | 3. 实验提交在该平台进行,详细说明将在实验发布时提供。 28 | 29 | 作业和实验报告撰写推荐使用 Markdown,简单易学易上手。 30 | 31 | Markdown 相关阅读材料:[https://markdown.com.cn/basic-syntax/](https://markdown.com.cn/basic-syntax/) 32 | 33 | > Markdown 是一种轻量级标记语言,排版语法简洁,让人们更多地关注内容本身而非排版。它使用易读易写的纯文本格式编写文档,可与 HTML 混编,可导出 HTML、PDF 以及本身的 .md 格式的文件。因简洁、高效、易读、易写,Markdown 被大量使用,如 GitHub、Wikipedia、简书等。 34 | 35 | ## Git & Gitlab 36 | 37 | 本课程使用 Git 进行代码版本管理,结合 GitLab 进行远程代码托管。 38 | 39 | > Git is a [free and open source](https://git-scm.com/about/free-and-open-source) distributed version control system designed to handle everything from small to very large projects with speed and efficiency. 40 | > 41 | > Git is [easy to learn](https://git-scm.com/doc) and has a [tiny footprint with lightning fast performance](https://git-scm.com/about/small-and-fast). It outclasses SCM tools like Subversion, CVS, Perforce, and ClearCase with features like [cheap local branching](https://git-scm.com/about/branching-and-merging), convenient [staging areas](https://git-scm.com/about/staging-area), and [multiple workflows](https://git-scm.com/about/distributed). 42 | 43 | > **GitLab 是一个用于[仓库管理系统](https://www.zhihu.com/search?q=%E4%BB%93%E5%BA%93%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F&search_source=Entity&hybrid_search_source=Entity&hybrid_search_extra=%7B%22sourceType%22%3A%22answer%22%2C%22sourceId%22%3A884336808%7D)的[开源项目](https://www.zhihu.com/search?q=%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE&search_source=Entity&hybrid_search_source=Entity&hybrid_search_extra=%7B%22sourceType%22%3A%22answer%22%2C%22sourceId%22%3A884336808%7D)**,使用 Git 作为代码[管理工具](https://www.zhihu.com/search?q=%E7%AE%A1%E7%90%86%E5%B7%A5%E5%85%B7&search_source=Entity&hybrid_search_source=Entity&hybrid_search_extra=%7B%22sourceType%22%3A%22answer%22%2C%22sourceId%22%3A884336808%7D),并在此基础上搭建起来的 Web 服务,可通过 Web 界面进行访问公开的或者私人项目。它拥有与**GitHub**类似的功能,能够浏览[源代码](https://www.zhihu.com/search?q=%E6%BA%90%E4%BB%A3%E7%A0%81&search_source=Entity&hybrid_search_source=Entity&hybrid_search_extra=%7B%22sourceType%22%3A%22answer%22%2C%22sourceId%22%3A884336808%7D),管理缺陷和注释。 44 | 45 | 在本课程实验中,你将使用 git 进行版本管理,需要至少掌握以下指令: 46 | 47 | - git init 48 | - git add 49 | - git commit 50 | - git clone 51 | - git merge 52 | - git branch 53 | - git pull 54 | - git push 55 | 56 | 参考阅读材料:[https://www.liaoxuefeng.com/wiki/896043488029600](https://www.liaoxuefeng.com/wiki/896043488029600) 57 | 58 | GitLab 是一个代码管理平台。关于 GitLab,你至少需要掌握以下概念: 59 | 60 | - ssh key 61 | - project private and public 62 | - watch, star and fork 63 | - …… 64 | 65 | 参考阅读材料:[https://www.liaoxuefeng.com/wiki/896043488029600](https://www.tutorialspoint.com/gitlab/gitlab_create_project.htm) 66 | 67 | 要访问 GitLab,请点击实验平台右上角的导航栏。 68 | 69 | ![Untitled](Untitled2.png) 70 | 71 | 若尚未设置电子邮箱账号,请进行注册。注册成功后 GitLab 用户名和密码和希冀平台相同。 72 | 73 | **请注意,不要更改 GitLab 账户用户名,因用户名导致的错判漏判,一律不接受申诉。** 74 | 75 | ![Untitled](Untitled3.png) 76 | -------------------------------------------------------------------------------- /docs/exp_platform_intro/Untitled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustc-compiler-principles/2023/3413d0831014139229c3469a81fe541aad7cab81/docs/exp_platform_intro/Untitled.png -------------------------------------------------------------------------------- /docs/exp_platform_intro/Untitled1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustc-compiler-principles/2023/3413d0831014139229c3469a81fe541aad7cab81/docs/exp_platform_intro/Untitled1.png -------------------------------------------------------------------------------- /docs/exp_platform_intro/Untitled2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustc-compiler-principles/2023/3413d0831014139229c3469a81fe541aad7cab81/docs/exp_platform_intro/Untitled2.png -------------------------------------------------------------------------------- /docs/exp_platform_intro/Untitled3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustc-compiler-principles/2023/3413d0831014139229c3469a81fe541aad7cab81/docs/exp_platform_intro/Untitled3.png -------------------------------------------------------------------------------- /docs/faq/README.md: -------------------------------------------------------------------------------- 1 | # FAQ 2 | 3 | [TBD] 4 | -------------------------------------------------------------------------------- /docs/innovative-lab/assets/编译创新实验群二维码.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustc-compiler-principles/2023/3413d0831014139229c3469a81fe541aad7cab81/docs/innovative-lab/assets/编译创新实验群二维码.png -------------------------------------------------------------------------------- /docs/innovative-lab/index.md: -------------------------------------------------------------------------------- 1 | # 建木杯–编译原理创新实验 2 | 3 | 本比赛是编译原理的创新实验比赛,鼓励同学们积极参加,并有丰厚的现金奖励。 4 | 5 | 参加方式:组队参加,1-3 人每组 6 | 7 | - 参赛人员不限于选了本课程的同学,其他年级、其他专业的学生也鼓励参加(同学们也可以帮忙传播该比赛) 8 | 9 | 实验时间:现在 - 2024 三月底(暂定) 10 | 11 | 比赛内容:根据课堂所学知识和[实验框架](https://cscourse.ustc.edu.cn/vdir/Gitlab/compiler_staff/2023ustc-jianmu-compiler/-/tree/master),开发一个针对 SysY 语言的龙芯后端编译器,并通过相关测试样例 12 | 13 | 考核标准:测试样例通过情况 + 测试样例性能分数 + 答辩分数 + bonus(暂定) 14 | 15 | ## SysY 语言 16 | 17 | SysY 语言是编译系统设计赛要实现的编程语言。由 C 语言的一个子集扩展而成。每个 SysY 程序的源码存储在一个扩展名为 sy 的文件中。该文件中有且仅有一个名为 main 的主函数定义,还可以包含若干全局变量声明、常量声明和其他函数定义。SysY 语言支持 int/float 类型和元素为 int/float 类型且按行优先存储的多维数组类型,其中 int 型整数为 32 位有符号数;float 为 32 位单精度浮点数;const 修饰符用于声明常量。SysY 支持 int 和 float 之间的隐式类型,但是无显式的强制类型转化支持。 18 | 19 | SysY 语言本身没有提供输入/输出 (I/O) 的语言构造,IO 是以运行时库方式提供,库函数可以在 SysY 程序中的函数内调用。部分 SysY 运行时库函数的参数类型会超出 SysY 支持的数据类型,如可以为字符串。SysY 编译器需要能处理这种情况,将 SysY 程序中这样的参数正确地传递给 SysY 运行时库。有关在 SysY 程序中可以使用哪些库函数,请参见 SysY 运行时库文档。 20 | 21 | 相关文档: 22 | 23 | - 语言定义: 24 | - 运行时库: 25 | - I/O 相关实现: 26 | 27 | ## 比赛内容 28 | 29 | 根据课堂所学知识和实验框架,开发一个针对 SysY 语言的龙芯后端编译器,并通过相关测试样例并汇报性能分数。 30 | 31 | 开发该编译器至少涉及以下内容: 32 | 33 | - 设计和实现语言解析器得到具体语法树 34 | - 设计抽象语法树(AST),并将具体语法树转化为 AST 35 | - 基于 AST,进行中间代码生成 36 | - 基于中间代码生成龙芯汇编 37 | 38 | 注意:我们上课使用的实验框架可能不能完全支持全功能的 SysY 语言,可以视情况进行修改。 39 | 40 | 我们将视情况提供原生的龙芯环境,方便参赛队伍进行调试和开发。 41 | 42 | ### 测试样式 43 | 44 | 测试样例分为 func 和 perf 45 | 46 | - 对于 func 的测试样例,其得分要点为通过率,正确通过数目越高得分越高。 47 | - 对于 perf 的测试样例,其得分要点为性能(执行时间),性能越高则得分越高。 48 | - 提高性能涉及各种优化,对性能提升显著的优化主要有以下:**Mem2Reg**,**寄存器分配**,**循环相关的优化** 49 | 50 | 优先级上:func 的通过率 > perf 的分数 51 | 52 | 测试样例链接: 53 | 54 | ### 考核标准 55 | 56 | - 测试样例通过情况:汇报通过率 57 | - 测试样例性能分数:汇报性能分数 58 | - 答辩分数:评委打分 59 | - Bonus:评委打分 60 | 61 | ## 奖项 62 | 63 | - 一等奖:3000¥ 64 | - 二等奖:1500¥ 65 | - 三等价:800¥ 66 | - 积极参与奖若干(小礼品或其他) 67 | 68 | ## Bonus 69 | 70 | Bonus 分为两种类型: 71 | 72 | - 指令类型以及优化 pass 丰富程度 73 | - SysY 语言支持完整程度 74 | 75 | ### 指令类型以及优化 pass 丰富程度 76 | 77 | 针对中间代码、后端生成的指令类型以及优化 pass 丰富程度会提供额外的加分。 78 | 79 | ### SysY 语言支持完整程度 80 | 81 | 针对 SysY 语言,在 上还有多达上百个测试样例,有以下四个目录: 82 | 83 | - final_performance 84 | - functional 85 | - hidden_functional 86 | - performance 87 | 88 | 对于这些测试样例,可以视情况进行测试,并在答辩时提供通过情况(如下),针对高通过率的队伍,会提供额外的加分。 89 | 90 | ```shell 91 | * final_performance (通过率: 10/37,总执行时间:60 sec) 92 | * functional (通过率: 100/100) 93 | * hidden_functional (通过率: 30/40) 94 | * performance (通过率: 10/59,总执行时间:102 sec) 95 | ``` 96 | 97 | ## 报名方式 98 | 99 | 组队参加,至多三人一组,将报名信息发送至 gpzlx1@mail.ustc.edu.cn。主题为"编译创新实验报名",内容为参赛同学学号、姓名、队长等相关信息。 100 | 101 | 相关 QQ 群:648929105 102 | 103 | ![编译创新实验群二维码](assets/编译创新实验群二维码.png) 104 | 105 | ## 声明 106 | 107 | 针对 SysY 语言,网上已经有很多非常优秀的开源实现。如果**借鉴**或**参考**了这些开源实现,须在答辩时主动说明参考对象以及在其上的相关修改。 108 | -------------------------------------------------------------------------------- /docs/javascripts/katex.js: -------------------------------------------------------------------------------- 1 | document$.subscribe(({ body }) => { 2 | renderMathInElement(body, { 3 | delimiters: [ 4 | { left: '$$', right: '$$', display: true }, 5 | { left: '$', right: '$', display: false }, 6 | { left: '\\(', right: '\\)', display: false }, 7 | { left: '\\[', right: '\\]', display: true } 8 | ] 9 | }) 10 | }) 11 | -------------------------------------------------------------------------------- /docs/lab0/git.md: -------------------------------------------------------------------------------- 1 | # Git 的使用 2 | 3 | 本课程使用 Git 作为代码管理工具,GitLab 作为代码管理仓库。为了后续实验的便捷进行,需要掌握 Git 的基本用法。 4 | 5 | ## Git 配置 6 | 7 | 在 Linux 环境中,我们已经通过 apt 安装了 Git。然而,还需要进行一些配置以确保每次代码提交都能正确标识提交者。在 Shell 中运行以下命令,并将 "Your Name" 和 "Your Email" 替换为你自己的信息: 8 | 9 | ```bash 10 | git config --global user.name "Your Name" 11 | git config --global user.email "Your Email" 12 | ``` 13 | 14 | 这些配置信息会记录每次代码更新的提交者和联系方式,有助于代码的追踪。 15 | 16 | ## 创建你的仓库 17 | 18 | 我们将使用 GitLab 进行远程代码管理。虽然你没有权限在 GitLab 上创建仓库,但你可以通过 fork 的方式拥有一个属于自己的仓库。 19 | 20 | 1. 进入以下公开仓库链接:**[https://cscourse.ustc.edu.cn/vdir/Gitlab/compiler_staff/2023_warm_up_b](https://cscourse.ustc.edu.cn/vdir/Gitlab/compiler_staff/2023_warm_up_b)** 21 | 2. 点击右上角的 "Fork" 按钮。这个操作会创建一个与原仓库一模一样的新仓库,唯一的区别是,这个新仓库是你自己的。你可以在新仓库上执行任何你需要的操作。 22 | 23 | ![Untitled](photos/git1.png) 24 | 25 | 3. 点击 "Fork" 后,页面将自动跳转到新的仓库页面,你将成为这个仓库的拥有者,这也会显示在仓库名称旁边。 26 | 27 | ![Untitled](photos/git2.png) 28 | 29 | 现在,你已经成功拥有了自己的第一个仓库。接下来,我们可以开始对这个仓库进行修改。 30 | 31 | ## 进行一次提交 32 | 33 | 由于仓库目前在服务器上,我们首先得到仓库的 URL,并通过 git clone 将其下载到本地。 34 | 35 | 1. 得到仓库的 URL 36 | 37 | ![Untitled](photos/git3.png) 38 | 39 | 2. 在 Ubuntu 环境中,利用得到的 URL,克隆仓库到本地 40 | 41 | ```bash 42 | # 使用 URL 进行 git clone XXX 为你的用户名 43 | git clone https://cscourse.ustc.edu.cn/vdir/Gitlab/XXX/2023_warm_up_b.git 44 | ``` 45 | 46 | 3. 输入 GitLab 用户名和密码进行身份验证,验证完毕后就将服务器上的仓库克隆到了本地 47 | 48 | ```bash 49 | # 检查是否成功 clone 50 | $ cd 2023_warm_up_b 51 | $ ls 52 | warm_up.txt 53 | ``` 54 | 55 | 此时,我们可以创建并提交第一个文件 56 | 57 | 3. 创建一个 readme.md 文件 58 | 59 | ```bash 60 | touch readme.md 61 | ``` 62 | 63 | 4. 在 readme.md 中添加 hello world 信息 64 | 65 | ```bash 66 | $ echo "# Hello World" >> readme.md 67 | $ cat readme.md 68 | # Hello World 69 | ``` 70 | 71 | 5. 将 readme.md 添加到 git 的暂存区 72 | 73 | ```bash 74 | # git add 指令可以将文件添加到 git 的暂存区 75 | # git 的暂存区是用于存放本次的所有修改 76 | $ git add readme.md 77 | 78 | # 我们可以通过 git status 查看 git 的暂存区 79 | $ git status 80 | On branch master 81 | Your branch is up to date with 'origin/master'. 82 | 83 | Changes to be committed: 84 | (use "git restore --staged ..." to unstage) 85 | new file: readme.md 86 | ``` 87 | 88 | 6. 提交本次修改并添加提交信息 89 | 90 | ```bash 91 | # 使用 git commit 提交本次修改到 Git 本地仓库 92 | # -m 后面的内容用于帮助记录本次提交的相关信息,要求每次提交最好都记录信息 93 | $ git commit -m 'add readme' 94 | [master bc20c0b] add readme 95 | 1 file changed, 1 insertion(+) 96 | create mode 100644 readme.md 97 | 98 | # 我们可以通过 git log 查看历史提交记录 99 | $ git log 100 | commit bc20c0b170a829948439961745cf1a2dc7817e86 (HEAD -> master) 101 | Author: gpzlx1 102 | Date: Wed Sep 6 19:01:15 2023 +0800 103 | 104 | add readme 105 | 106 | commit 6d08e6d4a1be64dea41798086b54cb0acc4377e2 (origin/master, origin/HEAD) 107 | Author: 123 108 | Date: Tue Sep 5 22:39:37 2023 +0800 109 | 110 | copy b 111 | 112 | commit 1abfe16c1d28becaea62aa0971b1e7afd1f4672e 113 | Author: 123 114 | Date: Tue Sep 5 22:02:53 2023 +0800 115 | 116 | 欢迎加入编译原理 117 | ``` 118 | 119 | 通过以上操作,我们完成了一次本地修改和提交。然而,请注意这些更改仅存储在本地,需要使用 "git push" 命令将它们上传到服务器,以实现本地和服务器的同步。 120 | 121 | ```bash 122 | $ git push origin master # 推送本地仓库的内容到 Git 远程仓库 123 | Enumerating objects: 4, done. 124 | Counting objects: 100% (4/4), done. 125 | Delta compression using up to 8 threads 126 | Compressing objects: 100% (2/2), done. 127 | Writing objects: 100% (3/3), 290 bytes | 290.00 KiB/s, done. 128 | Total 3 (delta 0), reused 0 (delta 0), pack-reused 0 129 | To https://cscourse.ustc.edu.cn/vdir/Gitlab/ustc_gongping/2023_warm_up_b.git 130 | 6d08e6d..bc20c0b master -> master 131 | ``` 132 | 133 | 现在,刷新 GitLab 页面,刚刚提交的内容已经显示在网页上。 134 | 135 | ![Untitled](photos/git4.png) 136 | 137 | ## 上下游同步和冲突处理 138 | 139 | 通过 fork 操作,你已经拥有了一个自己的仓库,在此我们可以引入上下游的概念。假设通过 fork 仓库 A 得到仓库 B,那么 A 就是 B 的上游。尽管 A 和 B 可能有大部分相同的内容,但对 A 和 B 的修改是彼此独立的。在这个课程实验中,我们将以类似的方式发布实验代码,助教维护上游仓库 A,而学生修改下游仓库 B。由于 A 和 B 的修改是相互独立的,接下来我们将介绍如何使 B 同步 A 的相关修改。 140 | 141 | 接下来,以**[https://cscourse.ustc.edu.cn/vdir/Gitlab/compiler_staff/2023_warm_up](https://cscourse.ustc.edu.cn/vdir/Gitlab/compiler_staff/2023_warm_up)** 作为上游仓库为例: 142 | 143 | - 添加上游仓库 144 | ```bash 145 | # 可以通过 git remote add 添加上游仓库 146 | # 用 upstream 标识该上游仓库 147 | $ git remote add upstream https://cscourse.ustc.edu.cn/vdir/Gitlab/compiler_staff/2023_warm_up.git 148 | ``` 149 | - 抓取上游仓库 150 | 151 | ```bash 152 | # 使用以下命令从上游仓库获取最新代码 153 | $ git fetch upstream master 154 | remote: Enumerating objects: 5, done. 155 | remote: Counting objects: 100% (5/5), done. 156 | remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0 157 | Unpacking objects: 100% (3/3), 258 bytes | 51.00 KiB/s, done. 158 | From https://cscourse.ustc.edu.cn/vdir/Gitlab/compiler_staff/2023_warm_up 159 | * branch master -> FETCH_HEAD 160 | * [new branch] master -> upstream/master 161 | hint: You have divergent branches and need to specify how to reconcile them. 162 | hint: You can do so by running one of the following commands sometime before 163 | hint: your next pull: 164 | hint: 165 | hint: git config pull.rebase false # merge (the default strategy) 166 | hint: git config pull.rebase true # rebase 167 | hint: git config pull.ff only # fast-forward only 168 | hint: 169 | hint: You can replace "git config" with "git config --global" to set a default 170 | hint: preference for all repositories. You can also pass --rebase, --no-rebase, 171 | hint: or --ff-only on the command line to override the configured default per 172 | hint: invocation. 173 | fatal: Need to specify how to reconcile divergent branches. 174 | 175 | # 此时所有的 upstream 的内容都在 upstream/master 分支上 176 | # 分支是 git 的一个重要概念,在本质上是一条独立的开发线。 177 | # 在处理新功能或 bug 修复时,您可以使用分支来将您的工作与其他团队成员的工作隔离开来。 178 | ``` 179 | 180 | - 合并分支并处理冲突 181 | 182 | ```bash 183 | # 此时我们的仓库中存在两条分支 184 | # master: 我们修改内容 185 | # upstream/mater: 上游的内存 186 | # 我们可以通过 merge 操作将 upstream/master 的内容合并到 master 中 187 | $ git merge upstream/master 188 | Auto-merging warm_up.txt 189 | CONFLICT (content): Merge conflict in warm_up.txt 190 | Automatic merge failed; fix conflicts and then commit the result. 191 | 192 | # 此时输出信息,告诉我们在 warm_up.txt 中存在冲突,这是由于 master 分支 193 | # 和 upstream/master 分支都对改文件相同位置进行了修改,此时需要我们手动处理冲突 194 | # 然后才能完成分支的合并和代码的同步 195 | 196 | # 我们先查看当前 warm_up.txt 的内容 197 | $ cat warm_up.txt 198 | <<<<<<< HEAD 199 | 欢迎你同学加入编译原理课程学习! 200 | ======= 201 | 欢迎您加入编译原理课程学习! 202 | >>>>>>> upstream/master 203 | 204 | # 这里出现了 <<< ==== >>>>等标识符 205 | # <<< 和 ===之间的内容是当前这个分支的内容 206 | # === 和 >>>之间的内容是计划 merge 进来的分支的内容 207 | # 你现在可以根据自己的需要选择某一个分支的内容,又或者综合两个分支的内容进行修改 208 | # 比如本教程选择保留进来的内容,删除当前的内容 209 | # 那更改 warm_up.text 的内容为以下,并删除<<< ==== >>>>等标识符 210 | $ cat warm_up.txt 211 | 欢迎您加入编译原理课程学习! 212 | 213 | # 此时,我们完成该文件的冲突处理,我们可以添加和提交本次修改,完成 merge 操作 214 | $ git add warm_up.txt 215 | $ git commit 216 | [master 126d5c9] Merge remote-tracking branch 'upstream/master' 217 | 218 | # 最后,将所有修改同步到远程服务器,方便助教进行批改 219 | $ git push origin master 220 | Enumerating objects: 10, done. 221 | Counting objects: 100% (10/10), done. 222 | Delta compression using up to 8 threads 223 | Compressing objects: 100% (3/3), done. 224 | Writing objects: 100% (6/6), 626 bytes | 626.00 KiB/s, done. 225 | Total 6 (delta 0), reused 0 (delta 0), pack-reused 0 226 | To https://cscourse.ustc.edu.cn/vdir/Gitlab/ustc_gongping/2023_warm_up_b.git 227 | bc20c0b..126d5c9 master -> master 228 | ``` 229 | 230 | 注:在完成冲突处理后 `git commit` 时,终端弹出的默认编辑器应该是 GNU nano,并带有默认的 commit message `Merge remote-tracking branch 'upstream/master'`,这时用 `Ctrl + X` 再选择 `Y` 即可完成 commit。感兴趣的同学可以阅读 [这篇文章](https://zhuanlan.zhihu.com/p/341705638) 进一步学习 GNU nano 的使用。 231 | 232 | ## 扩展材料 233 | 234 | **Git 教程** [https://www.liaoxuefeng.com/wiki/896043488029600](https://www.liaoxuefeng.com/wiki/896043488029600) 235 | 236 | **Git 文档** [https://git-scm.com/book/zh/v2](https://git-scm.com/book/zh/v2) 237 | 238 | ## 实验任务 239 | 240 | 1. 根据上述操作,完成 readme.md 添加、上游仓库添加和冲突处理等操作。 241 | 2. 阅读扩展材料,回答以下问题,将答案添加到 `answer.pdf`。 242 | 1. 如何创建一个新的 git 分支?如何进行分支切换?如何删除一个分支?什么时候可以安全的删除一个分支? 243 | 2. 如何撤销保存在暂存区的修改?如何撤销已经提交的修改? 244 | 3. 如何从远程仓库抓取更新? 245 | 4. 解释 git init 的作用? 246 | 5. 解释 git fetch 和 git pull 的区别。 247 | 3. 将 [LLVM 等软件环境配置与测试](software.md) 中生成的 Test.ll 文件添加到仓库中,并上传到远程仓库中去。 248 | -------------------------------------------------------------------------------- /docs/lab0/index.md: -------------------------------------------------------------------------------- 1 | # Lab0 实验准备 2 | 3 | !!! warning "Deadline" 4 | 5 | **2023 年 9 月 15 日 23:59** 6 | 7 | 本次实验用于指导同学们进行环境的搭建以及带领同学们熟悉 git 的操作,主要有以下内容: 8 | 9 | - 搭建 Ubuntu 22.04 虚拟机环境并下载本课程所需软件包 10 | - 测试环境配置是否成功 11 | - 使用 Git 维护代码版本并处理冲突 12 | 13 | ## 文档 14 | 15 | - [Linux 环境配置](linux.md) 16 | - [LLVM 等软件环境配置](software.md) 17 | - [Git 的使用](git.md) 18 | 19 | ## 实验内容 20 | 21 | ### 实验一:LLVM 环境测试 22 | 23 | 阅读文档 [Linux 环境配置](linux.md) 和 [LLVM 等软件环境配置](software.md),生成 Test.ll 文件。 24 | 25 | ### 实验二:Git 26 | 27 | 阅读文档 [Git 的使用](git.md),完成实验任务 1、2、3。 28 | 29 | ## 实验要求 30 | 31 | ### 提交内容 32 | 33 | Lab0 完成后,除了 answer.pdf 文件,你还在 GitLab 上拥有一个名为 2023_warm_up_b 的远程仓库,该仓库有以下结构: 34 | 35 | ```shell 36 | 2023_warm_up_b: 37 | - warm_up.txt 38 | - readme.md 39 | - Test.ll 40 | ``` 41 | 42 | 在希冀平台上传提交你的 answer.pdf 文件,并提交 GitLab 仓库链接。 43 | 44 | #### 文件上传提交方式 45 | 46 | 文件上传助教会进行文本相似性检测,请同学们认真学习并完成要求。 47 | 48 | ![Alt text](photos/image.png) 49 | 50 | #### GitLab 仓库链接提交方式 51 | 52 | ![Alt text](photos/image-1.png) 53 | 54 | ![Alt text](photos/image-2.png) 55 | 56 | #### 答疑 57 | 58 | 同学们如果对实验有什么疑问,请先登录希冀平台后在[在线答疑模块](https://cscourse.ustc.edu.cn/forum/forum.jsp?forum=121)发帖询问,助教看到帖子会及时回复。 59 | ![Alt text](photos/image_3.png) 60 | -------------------------------------------------------------------------------- /docs/lab0/linux.md: -------------------------------------------------------------------------------- 1 | # Linux 环境配置 2 | 3 | 本页文档用于对本学期实验所使用的实验环境进行说明并提供相应的搭建建议。 4 | 5 | 本学期实验使用的**核心软件**版本为: 6 | 7 | - Ubuntu 22.04 8 | - LLVM 14.0.0 9 | - Git 2.34.1 10 | - CMake 3.22.1 11 | - Flex 2.6.4 12 | - Bison 3.8.2 13 | 14 | !!! info "说明" 15 | 16 | 文档主要针对非 Linux 系统用户,已搭建好 Linux 环境并且**符合版本要求**的同学可选择性跳过。 17 | 18 | 下面以虚拟机软件 VirtualBox 为例介绍搭建环境的方法。 19 | 20 | ## 1.1 下载 21 | 22 | 下载虚拟机软件: 23 | 24 | - VirtualBox:[Downloads – Oracle VM VirtualBox](https://www.virtualbox.org/wiki/Downloads) 25 | 26 | 虚拟机软件的版本没有规定的要求,但必须满足基本需求。 27 | 28 | 下载 Ubuntu: 29 | 30 | - 官网链接:[Ubuntu Releases](https://releases.ubuntu.com/) 31 | - 科大镜像:[Index of /ubuntu-releases/ (ustc.edu.cn)](https://mirrors.ustc.edu.cn/ubuntu-releases/) 32 | - 清华镜像:[Index of /ubuntu-releases/ | 清华大学开源软件镜像站 | Tsinghua Open Source Mirror](https://mirrors.tuna.tsinghua.edu.cn/ubuntu-releases/) 33 | 34 | !!! warning "重要" 35 | 36 | 本学期实验规定了相应的 Ubuntu 版本以方便后续软件的下载与使用,请各位同学**务必**下载使用推荐版本 **22.04**。 37 | 38 | 镜像文件请下载 **server** 版本,后续指导基于 server 版本展开。请下载命名为 ubuntu-22.04.3-live-server-amd64 的镜像文件。 39 | 40 | 下载完成之后无需打开镜像文件,只需在后续导入虚拟机软件即可。 41 | 42 | ## 1.2 创建、安装虚拟机(VirtualBox) 43 | 44 | 下面介绍 VirtualBox 创建、安装虚拟机的过程。 45 | 46 | 1. 点击“新建”创建虚拟机。设置虚拟机的名称,指定文件夹,设置类型与版本,虚拟光盘可以先不用指定: 47 | ![V1](photos/V1.png) 48 | 2. 下一步后给虚拟机分配内存,请至少分配 2GB 以上的内存给虚拟机。对于处理器的分配可结合个人情况进行分配: 49 | ![V2](photos/V2.png) 50 | 3. 下一步后给硬盘分配空间: 51 | ![V3](photos/V3.png) 52 | 如果各位同学只将该虚拟机用于本学期的实验那么设置最大磁盘大小为 25~40GB 即可,本学期的实验不会占用过多磁盘空间。但**不建议将磁盘大小设置过小**,因为磁盘空间不够引发的相关问题将会非常棘手。 53 | 4. 下一步后点击完成,虚拟机已经创建完毕,点击启动进入虚拟机: 54 | ![V4](photos/V4.png) 55 | 5. 启动虚拟机后其会提示加载虚拟光盘,在这里选择我们先前已经按照的 Ubuntu 镜像文件: 56 | ![V5](photos/V5.png) 57 | 6. 选择完毕后点击挂载并尝试启动,等待其出现如下的界面: 58 | ![Vs6](photos/V6.png) 59 | 按图示选择第一项后点击回车即可,以下**选择确认时均需回车**。 60 | 7. 以后出现的界面选择较简单,选择默认选择即可; 61 | 8. 选择至出现下述界面,按自己喜好进行填写即可: 62 | ![V8](photos/V7.png) 63 | > 务必记住 username 以及 password 64 | 9. 填写完后一路按照默认选择至如下界面并且下方出现 Reboot 时选择 Reboot 即可;注意当出现下述界面时需要**保持等待**直到其出现 Reboot 按键: 65 | ![Vs17](photos/V8.png) 66 | 10. 等待重启过程中如果界面卡住不发生变化时可以尝试按回车,例如出现如下界面时可按回车尝试解决: 67 | ![Vs20](photos/V9.png) 68 | 11. 等到出现如下界面时输入你的用户名以及密码即可成功登录: 69 | ![Vs18](photos/V10.png) 70 | 12. 出现此界面后即表示配置成功: 71 | ![Vs19](photos/V11.png) 72 | 73 | ## 1.3 使用 VSCode 74 | 75 | ???+ info "VSCode" 76 | 77 | VSCode 由微软开发,是一款强大的跨平台代码编辑器。它提供美观的界面和可定制的主题,同时支持众多插件,如代码高亮、智能代码补全、括号匹配和代码比较,显著提升工作效率。此外,VSCode 还支持便捷的远程服务器连接,例如虚拟机,让你享受代码编写环境和预配置的 Linux 环境,大幅提高实验效率。熟练使用 VSCode 将为你后续的工作带来极大便利。 78 | 79 | ### 1.3.1 下载 VSCode 80 | 81 | 前往[官网](https://code.visualstudio.com/download)进行下载即可。 82 | 83 | ### 1.3.2 配置 VSCode[可选] 84 | 85 | 对 VSCode 进行基础配置可以参考[链接](https://zhuanlan.zhihu.com/p/87864677)。 86 | 87 | 对 VSCode 进行插件安装可以参考[链接](https://zhuanlan.zhihu.com/p/487406915)。 88 | 89 | ## 1.4 通过 VSCode 连接虚拟机 90 | 91 | 在开始指导连接虚拟机之前,我们需要明确一个重要事实:VSCode 并非用于直接连接虚拟机的工具,而是一款专注于代码(文本)编辑的应用程序。我们实际上是通过一种特殊的方式来实现虚拟机连接,而这种方式允许我们在 VSCode 中享受代码编写的环境。这个特殊的方式就是 SSH。 92 | 93 | ### 1.4.1 SSH 94 | 95 | SSH(Secure Shell)是一种网络协议,用于在计算机之间建立安全的远程连接和数据传输。它提供了一种加密的通信方式,可用于远程管理和安全数据传输。SSH 协议通常涉及两个主要组件:SSH 服务器(server)和 SSH 客户端(client)。 96 | 97 | SSH 服务器(SSH Server): 98 | 99 | - SSH 服务器是运行在远程计算机上的软件或服务,它监听着 SSH 协议的连接请求,并允许远程用户通过 SSH 协议进行安全访问。 100 | - 主要任务包括验证用户身份、管理连接请求、启动远程会话、执行命令和文件传输。 101 | 102 | SSH 客户端(SSH Client): 103 | 104 | - SSH 客户端是运行在本地计算机上的软件或工具,用于连接到远程 SSH 服务器并与之通信。 105 | - 客户端用于提供连接参数、身份验证凭据(通常是用户名和密码或 SSH 密钥)以及执行远程操作,例如运行远程命令、传输文件等。 106 | 107 | #### 1.4.1.1 在 Linux 系统上安装 ssh server 108 | 109 | 在虚拟机中执行以下命令: 110 | 111 | ```shell 112 | # 安装 ssh server 113 | sudo apt install openssh-server 114 | # 启动 ssh server 115 | sudo systemctl start ssh 116 | # 设置系统开机自启动 117 | sudo systemctl enable ssh 118 | ``` 119 | 120 | 检验是否启动成功,输入下述命令: 121 | 122 | ```shell 123 | # 出现 /usr/sbin/sshd 类似信息则已启动成功 124 | $ ps aux | grep sshd 125 | root 765 0.0 0.0 15420 8784 ? Ss 03:50 0:00 sshd: /usr/sbin/sshd -D [listener] 0 of 10-100 startups 126 | ``` 127 | 128 | #### 1.4.1.2 虚拟机网络设置 129 | 130 | 接下来,我们需要配置虚拟机的网络设置,以确保我们可以从主机访问虚拟机。 131 | 132 | 1. 打开虚拟机启动界面,在设置中选择网络: 133 | 134 | ![V20](photos/V13.png) 135 | 136 | 2. 这里连接方式选择 **NAT**,在高级中选择端口转发: 137 | 138 | ![V21](photos/V14.png) 139 | 140 | 3. 在出现的界面中添加一个转发规则: 141 | 142 | ![V22](photos/V15.png) 143 | 144 | 规则的名称可以自行命名,主机 IP 与子系统 IP 可以不填,子系统端口**必须是 22**(为什么?);主机端口可以随意,不产生端口冲突即可 145 | 146 | 4. 添加完后点击确认即可。 147 | 148 | #### 1.4.1.3 在 Windows 系统上使用 ssh client 149 | 150 | 在 Windows 系统上,我们使用 OpenSSH client 来进行 SSH 连接。OpenSSH 是目前最流行的 SSH 协议实现,而且是一个开源项目,微软已经提供了对在 Windows 上使用 OpenSSH 的支持。 151 | 152 | 从 Windows10 开始系统自带 OpenSSH client,在 "设置"->"应用"->"可选功能"可以检查是否已经安装 OpenSSH client: 153 | 154 | ![V35](photos/V12.png) 155 | 156 | 若没有安装,请参考[这里](https://blog.csdn.net/fjw044586/article/details/110940729#前言)。 157 | 158 | #### 1.4.1.4 使用 ssh 连接虚拟机 159 | 160 | 打开 Windows PowerShell,在命令行输入以下内容进行 ssh 连接: 161 | 162 | ```shell 163 | # -p 表示端口 后面数字为端口号 164 | ssh -p 1145 虚拟机用户名@127.0.0.1 165 | ``` 166 | 167 | ![Alt text](photos/ssh_image.png) 168 | 169 | 输入虚拟机用户对应的密码即可连接到虚拟机当中。 170 | 171 | ### 1.4.2 使用 VSCode 连接虚拟机 172 | 173 | 由于在 VirtualBox 上配置的 Ubuntu server 没有图形化界面,不够美观的同时也无法可视化操作。这里我们推荐使用 VSCode 连接我们刚刚配置好的虚拟机,使用 VSCode 进行开发可以大大提高我们的开发效率。 174 | 175 | 在验证 ssh 连接虚拟机成功后,我们即可以通过 VSCode 来连接虚拟机。 176 | 177 | #### 1.4.2.1 VSCode 设置 178 | 179 | 打开 VSCode 执行以下操作: 180 | 181 | 1. 下载并安装 Remote-SSH 插件: 182 | 183 | ![image-20230907232746795](photos/image-20230907232746795.png) 184 | 185 | > remote-ssh 是一个专门用于通过 SSH 来远程连接服务器的辅助工具。 186 | 187 | 2. 下载完毕后在 VSCode 的左边栏找到远程资源管理器,在上方选择栏中选择**远程(隧道/SSH)**: 188 | 189 | ![V24](photos/V17.png) 190 | 191 | 3. 在 SSH 中点击设置,后选择 SSH 配置文件: 192 | 193 | ![V25](photos/V18.png) 194 | 195 | 4. 在配置文件中加入如下代码段,保存: 196 | 197 | ```sshconfig 198 | # Host 名字可以随意 199 | Host vbox 200 | HostName 127.0.0.1 201 | # User 填入你自己的虚拟机 username 202 | User test 203 | # Port 填入在设置虚拟机网络时你填入的主机端口 204 | Port 4514 205 | ``` 206 | 207 | 随后刷新左侧列表可以在 SSH 下看到如下项: 208 | 209 | ![V26](photos/V19.png) 210 | 211 | 5. 在自己设置的项(助教这里是 vbox)右侧点击右箭头开始连接: 212 | 213 | ![V27](photos/V20.png) 214 | 215 | 6. 这里出现的 platform 选择 Linux: 216 | 217 | ![V28](photos/V21.png) 218 | 219 | 7. 随后点击 Continue; 220 | 221 | 8. 输入你的虚拟机的密码; 222 | 223 | 9. 耐心等待连接,等到界面左下方出现如下情况即表示连接成功: 224 | 225 | ![V31](photos/V22.png) 226 | 227 | 10. 随后即可在 VSCode 上愉快地使用虚拟机。VSCode 拥有一系列针对各种语言的插件,同学们按需安装,这里不再做过多展开。 228 | 229 | #### 1.4.2.2 VSCode 连接虚拟机使用示例 230 | 231 | 我们已经成功通过 VSCode 连接了虚拟机,下面是一些简单的使用指导。 232 | 233 | 首先在 VSCode 中,我们可以使用快捷键 ``CTRL + ` `` 打开终端: 234 | 235 | ![image-20230907233217641](photos/image-20230907233217641.png) 236 | 237 | 注意这个终端里的所有指令都是运行在你配置的虚拟机/服务器上,尝试如下指令: 238 | 239 | ```shell 240 | # 查看虚拟机的相关信息 241 | $ uname -a 242 | Linux gpu12 5.4.0-156-generic #173-Ubuntu SMP Tue Jul 11 07:25:22 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux 243 | 244 | # mkdir 创建文件夹 245 | # code 指令使用 VSCode 打开一个特定的工作区 246 | $ mkdir Test 247 | $ code Test 248 | ``` 249 | 250 | 此时,会弹出一个新的 VSCode 窗口,该新窗口以 Test 文件夹作为 workspace。 251 | 252 | 下面,我们进行简单的文件创建和添加。选择 VSCode 的 explorer,并点击新建文件操作,新建文件 `testfile.txt`。 253 | 254 | ![image-20230907233515388](photos/image-20230907233515388.png) 255 | 256 | 随后打开该文件,并输入相关信息。此时,我们已经通过 VSCode 在虚拟机上创建一个 `testfile.txt` 文件,且该文件位于 `Test` 文件夹下。 257 | 258 | ![V36](photos/V23.png) 259 | 260 | 完成上述操作后,你可以在 VSCode 终端中或 virtualbox 的界面,在虚拟机中查看相关文件内容。 261 | 262 | ```shell 263 | $ cd Test 264 | $ cat testfile.txt 265 | Hello VirtualBox! 266 | ``` 267 | 268 | 通过这个例子,你应该已经掌握了使用 VSCode 连接远程服务器和修改相关文件等操作。 269 | 270 | ???+ tip "VSCode 远程服务器文件上传和下载" 271 | 272 | 文件上传:直接拖拽桌面文件进入 VSCode 的 explorer 273 | 274 | 文件下载:在 VSCode 的 explorer 中对相关文件进行右键并选择下载。 275 | 276 | 更多关于 VSCode 的操作,欢迎同学自行探索。 277 | -------------------------------------------------------------------------------- /docs/lab0/photos/V1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustc-compiler-principles/2023/3413d0831014139229c3469a81fe541aad7cab81/docs/lab0/photos/V1.png -------------------------------------------------------------------------------- /docs/lab0/photos/V10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustc-compiler-principles/2023/3413d0831014139229c3469a81fe541aad7cab81/docs/lab0/photos/V10.png -------------------------------------------------------------------------------- /docs/lab0/photos/V11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustc-compiler-principles/2023/3413d0831014139229c3469a81fe541aad7cab81/docs/lab0/photos/V11.png -------------------------------------------------------------------------------- /docs/lab0/photos/V12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustc-compiler-principles/2023/3413d0831014139229c3469a81fe541aad7cab81/docs/lab0/photos/V12.png -------------------------------------------------------------------------------- /docs/lab0/photos/V13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustc-compiler-principles/2023/3413d0831014139229c3469a81fe541aad7cab81/docs/lab0/photos/V13.png -------------------------------------------------------------------------------- /docs/lab0/photos/V14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustc-compiler-principles/2023/3413d0831014139229c3469a81fe541aad7cab81/docs/lab0/photos/V14.png -------------------------------------------------------------------------------- /docs/lab0/photos/V15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustc-compiler-principles/2023/3413d0831014139229c3469a81fe541aad7cab81/docs/lab0/photos/V15.png -------------------------------------------------------------------------------- /docs/lab0/photos/V16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustc-compiler-principles/2023/3413d0831014139229c3469a81fe541aad7cab81/docs/lab0/photos/V16.png -------------------------------------------------------------------------------- /docs/lab0/photos/V17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustc-compiler-principles/2023/3413d0831014139229c3469a81fe541aad7cab81/docs/lab0/photos/V17.png -------------------------------------------------------------------------------- /docs/lab0/photos/V18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustc-compiler-principles/2023/3413d0831014139229c3469a81fe541aad7cab81/docs/lab0/photos/V18.png -------------------------------------------------------------------------------- /docs/lab0/photos/V19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustc-compiler-principles/2023/3413d0831014139229c3469a81fe541aad7cab81/docs/lab0/photos/V19.png -------------------------------------------------------------------------------- /docs/lab0/photos/V2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustc-compiler-principles/2023/3413d0831014139229c3469a81fe541aad7cab81/docs/lab0/photos/V2.png -------------------------------------------------------------------------------- /docs/lab0/photos/V20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustc-compiler-principles/2023/3413d0831014139229c3469a81fe541aad7cab81/docs/lab0/photos/V20.png -------------------------------------------------------------------------------- /docs/lab0/photos/V21.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustc-compiler-principles/2023/3413d0831014139229c3469a81fe541aad7cab81/docs/lab0/photos/V21.png -------------------------------------------------------------------------------- /docs/lab0/photos/V22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustc-compiler-principles/2023/3413d0831014139229c3469a81fe541aad7cab81/docs/lab0/photos/V22.png -------------------------------------------------------------------------------- /docs/lab0/photos/V23.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustc-compiler-principles/2023/3413d0831014139229c3469a81fe541aad7cab81/docs/lab0/photos/V23.png -------------------------------------------------------------------------------- /docs/lab0/photos/V24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustc-compiler-principles/2023/3413d0831014139229c3469a81fe541aad7cab81/docs/lab0/photos/V24.png -------------------------------------------------------------------------------- /docs/lab0/photos/V25.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustc-compiler-principles/2023/3413d0831014139229c3469a81fe541aad7cab81/docs/lab0/photos/V25.png -------------------------------------------------------------------------------- /docs/lab0/photos/V3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustc-compiler-principles/2023/3413d0831014139229c3469a81fe541aad7cab81/docs/lab0/photos/V3.png -------------------------------------------------------------------------------- /docs/lab0/photos/V4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustc-compiler-principles/2023/3413d0831014139229c3469a81fe541aad7cab81/docs/lab0/photos/V4.png -------------------------------------------------------------------------------- /docs/lab0/photos/V5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustc-compiler-principles/2023/3413d0831014139229c3469a81fe541aad7cab81/docs/lab0/photos/V5.png -------------------------------------------------------------------------------- /docs/lab0/photos/V6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustc-compiler-principles/2023/3413d0831014139229c3469a81fe541aad7cab81/docs/lab0/photos/V6.png -------------------------------------------------------------------------------- /docs/lab0/photos/V7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustc-compiler-principles/2023/3413d0831014139229c3469a81fe541aad7cab81/docs/lab0/photos/V7.png -------------------------------------------------------------------------------- /docs/lab0/photos/V8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustc-compiler-principles/2023/3413d0831014139229c3469a81fe541aad7cab81/docs/lab0/photos/V8.png -------------------------------------------------------------------------------- /docs/lab0/photos/V9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustc-compiler-principles/2023/3413d0831014139229c3469a81fe541aad7cab81/docs/lab0/photos/V9.png -------------------------------------------------------------------------------- /docs/lab0/photos/git1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustc-compiler-principles/2023/3413d0831014139229c3469a81fe541aad7cab81/docs/lab0/photos/git1.png -------------------------------------------------------------------------------- /docs/lab0/photos/git2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustc-compiler-principles/2023/3413d0831014139229c3469a81fe541aad7cab81/docs/lab0/photos/git2.png -------------------------------------------------------------------------------- /docs/lab0/photos/git3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustc-compiler-principles/2023/3413d0831014139229c3469a81fe541aad7cab81/docs/lab0/photos/git3.png -------------------------------------------------------------------------------- /docs/lab0/photos/git4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustc-compiler-principles/2023/3413d0831014139229c3469a81fe541aad7cab81/docs/lab0/photos/git4.png -------------------------------------------------------------------------------- /docs/lab0/photos/image-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustc-compiler-principles/2023/3413d0831014139229c3469a81fe541aad7cab81/docs/lab0/photos/image-1.png -------------------------------------------------------------------------------- /docs/lab0/photos/image-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustc-compiler-principles/2023/3413d0831014139229c3469a81fe541aad7cab81/docs/lab0/photos/image-2.png -------------------------------------------------------------------------------- /docs/lab0/photos/image-20230907232746795.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustc-compiler-principles/2023/3413d0831014139229c3469a81fe541aad7cab81/docs/lab0/photos/image-20230907232746795.png -------------------------------------------------------------------------------- /docs/lab0/photos/image-20230907233217641.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustc-compiler-principles/2023/3413d0831014139229c3469a81fe541aad7cab81/docs/lab0/photos/image-20230907233217641.png -------------------------------------------------------------------------------- /docs/lab0/photos/image-20230907233515388.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustc-compiler-principles/2023/3413d0831014139229c3469a81fe541aad7cab81/docs/lab0/photos/image-20230907233515388.png -------------------------------------------------------------------------------- /docs/lab0/photos/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustc-compiler-principles/2023/3413d0831014139229c3469a81fe541aad7cab81/docs/lab0/photos/image.png -------------------------------------------------------------------------------- /docs/lab0/photos/image_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustc-compiler-principles/2023/3413d0831014139229c3469a81fe541aad7cab81/docs/lab0/photos/image_3.png -------------------------------------------------------------------------------- /docs/lab0/photos/ssh_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustc-compiler-principles/2023/3413d0831014139229c3469a81fe541aad7cab81/docs/lab0/photos/ssh_image.png -------------------------------------------------------------------------------- /docs/lab0/software.md: -------------------------------------------------------------------------------- 1 | # LLVM 等软件环境配置 2 | 3 | 这部分将会带领同学们初步了解 LLVM、Clang、Git、cmake、Flex、Bison、build-essential,并在我们已经配置好的 Linux 环境下安装相关软件包。 4 | 5 | ## LLVM、Clang 安装 6 | 7 | LLVM 是一个用 C++ 编写的开源编译器基础设施项目,包括模块化编译器组件和工具链。它提供了一种称为 LLVM IR 的中间代码,介于高级语言和汇编语言之间,本课程的实验核心围绕 LLVM 展开。 8 | 9 | Clang 是 LLVM 编译器工具集的前端,用于编译 C、C++ 和 Objective-C。在配置 LLVM 环境时,需要安装 Clang。 10 | 11 | 想要了解更多 LLVM 相关知识可参考:[LLVM Discussion Forums - Our community includes both users and developers of various compiler technologies within the LLVM project.](https://discourse.llvm.org/) 12 | 13 | LLVM 和 Clang 都可通过 Ubuntu 自带的软件包管理器下载,因此我们在配置好的环境下执行以下指令即可下载 LLVM 与 Clang: 14 | 15 | ```shell 16 | # 更新软件源 17 | sudo apt update 18 | # 更新软件(可选) 19 | sudo apt upgrade 20 | # 安装 LLVM 和 Clang 21 | sudo apt install clang llvm 22 | ``` 23 | 24 | ## Git 安装 25 | 26 | Git 是一个分布式版本控制软件,目前已经成为最流行的版本管理工具。我们将会经常利用到它的分支合并与分支追踪的功能。 27 | 28 | 在搭建好的环境下使用如下指令安装 Git: 29 | 30 | ```shell 31 | sudo apt install git 32 | ``` 33 | 34 | ## build-essential 安装 35 | 36 | 在 Ubuntu 中,为了简化安装过程,你可以使用 build-essential 软件包。它集成了编译 C、C++ 等语言所需的关键工具和库,包括编译器、链接器以及其他常用构建工具和库。这样,你就不需要单独安装 gcc、g++、make 等包,让编译和构建软件更加便捷。 37 | 38 | ```shell 39 | sudo apt install build-essential 40 | 41 | # 查看是否成功安装 42 | gcc --version 43 | ``` 44 | 45 | ## CMake 安装 46 | 47 | CMake 是一个用于管理和生成跨平台构建系统的开源工具。它可以帮助开发人员轻松地配置、构建和测试他们的项目,而无需关心目标操作系统或编译器的细节。CMake 的主要目标是使跨平台开发更加容易和可维护,因此它被广泛用于许多开源和商业项目中。 48 | 49 | 想要了解更多 CMake 可参考:[Modern CMake 简体中文版](https://modern-cmake-cn.github.io/Modern-CMake-zh_CN/)。 50 | 51 | 扩展阅读: 52 | 53 | - make 参考:[make 官方文档英文版](https://www.gnu.org/software/make/manual/make.html)。 54 | - make 和 CMake 的区别:[https://zhuanlan.zhihu.com/p/431118510](https://zhuanlan.zhihu.com/p/431118510)。 55 | 56 | ```shell 57 | # 安装 cmake 58 | sudo apt install cmake 59 | # 检查 cmake 是否正确安装 60 | cmake --version 61 | ``` 62 | 63 | ## Flex 和 Bison 安装 64 | 65 | Flex 是一个用于生成词法分析器的工具。它简化了词法分析器的创建过程,只需提供正则表达式描述词法规则,Flex 就能自动生成相应的 C 代码。 66 | 67 | Bison 是一款解析器生成器,用于将 LALR 文法转换为可编译的 C 代码,从而显著降低了手动设计解析器的工作量。Bison 是对早期 Unix 上的 Yacc 工具的重新实现,因此通常使用 `.y` 文件扩展名来表示它的语法规则文件(Yacc 是 Yet Another Compiler Compiler 的缩写)。 68 | 69 | **我们将在 Lab1 对 Flex 和 Bison 作更为详细的介绍。** 70 | 71 | ```shell 72 | sudo apt-get install flex bison 73 | 74 | # 查看是否成功安装 75 | flex --version 76 | bison --version 77 | ``` 78 | 79 | ## GDB 安装 80 | 81 | GDB 是一款强大的开发工具,可用于调试各种编程语言的应用程序,帮助开发者诊断和解决代码中的问题。它在开发和调试过程中起着重要作用,有助于提高软件质量和开发效率。 82 | 83 | ```shell 84 | sudo apt install gdb 85 | # 查看是否成功安装 86 | gdb --version 87 | 88 | ``` 89 | 90 | ## 测试 91 | 92 | 为了测试配置是否成功,我们提供了一个简单的测试程序 Test.c,其内容如下: 93 | 94 | ```c 95 | #include 96 | 97 | int Grade[1]; 98 | int Degree[1]; 99 | int Number[1]; 100 | int grade_mul; 101 | int degree_mul; 102 | 103 | void StudentNumber(int u[], int v[], int k[]) { 104 | int grade; 105 | int degree; 106 | int number; 107 | int result; 108 | grade = u[0]; 109 | degree = v[0]; 110 | number = k[0]; 111 | result = grade * grade_mul + degree * degree_mul + number; 112 | // 若学号前缀不是 PB 可于此修改 113 | printf("PB%d\n",result); 114 | return; 115 | } 116 | 117 | /** 118 | * @brief 修改 main 函数中的信息输出你的学号 119 | * 120 | * @param Grade[0]: 你的年级,如 20,21,22 等 121 | * @param Degree[0]: 你的专业代号,如计科是 11 122 | * @param Number[0]: 你的学生序号,如 0011,4514 等 123 | * @return int 124 | */ 125 | int main(void) { 126 | Grade[0] = 20; 127 | Degree[0] = 11; 128 | Number[0] = 4514; 129 | grade_mul = 1000000; 130 | degree_mul = 10000; 131 | StudentNumber(Grade,Degree,Number); 132 | return 0; 133 | } 134 | ``` 135 | 136 | 它的作用是输出你的学号,请你填写相关信息后,在虚拟机/服务器中执行如下指令运行: 137 | 138 | ```shell 139 | # 该指令对 Text.c 进行编译,并生成其对应的 LLVM IR 代码,记为 Test.ll。 140 | $ clang -S -emit-llvm Test.c -o Test.ll 141 | ``` 142 | 143 | 随后,我们使用 `lli` 执行该 IR 文件: 144 | 145 | ```shell 146 | $ lli Test.ll && echo $? 147 | PB20114514 # 第一行结果应为你的学号,助教会评测该结果 [重要] 148 | 0 # main 函数返回值,应该为 0 149 | ``` 150 | 151 | 请将你修改的 Test.ll 保存下来,它将在 [Git 的使用](git.md)中被使用。 152 | 153 | 感兴趣的同学们也可自行阅读生成的 Test.ll 文件,注意观察高级语言中的常量、变量、全局变量、数组和函数等在 LLVM IR 中是如何表示的,在后续的实验中我们会经常和它们打交道。 154 | 155 | ```shell 156 | cat Test.ll 157 | ``` 158 | -------------------------------------------------------------------------------- /docs/lab1/Bison.md: -------------------------------------------------------------------------------- 1 | # Bison 2 | 3 | ## 简介 4 | 5 | Bison 是一款 LALR 文法解析器生成器,可转换为可编译的 C 代码,减轻手动设计解析器的工作。它重新实现了早期 Unix 上的 Yacc 工具,文件扩展名为 `.y`(Yacc 意为 Yet Another Compiler Compiler)。 6 | 7 | Flex 和 Bison 是 Linux 下生成词法分析器和语法分析器的工具,用于处理结构化输入,协同工作解析复杂文件。Flex 将文本文件拆分为有意义的词法记号(token),而 Bison 根据语法规则生成抽象语法树(AST),Bison 在协同工作中担任主导角色,而 Flex 辅助生成 yylex 函数。 8 | 9 | 以计算器程序(该程序即为下文的[一个复杂的 Bison 程序](#一个复杂的-bison-程序))为例,用户在界面输入 `2 + 2 * 2`,Flex 将输入识别为 token 流,其中 2 被识别为 `number`,`+` 被识别为 `ADD`,`*` 被识别为 `MUL`。接下来,Bison 负责根据语法规则将这些 token 组织成 AST 树,流程如下图所示: 10 | 11 | image-20230913142328935 12 | 13 | ## Bison 程序结构 14 | 15 | Bison 源程序的一般结构如下所示。Bison 源程序后缀一般为 `.y`。 16 | 17 | ``` 18 | 定义部分 19 | 20 | 语法规则部分 21 | 22 | C 代码部分 23 | ``` 24 | 25 | **定义部分**可以分为以下两个部分: 26 | 27 | 1. 包括 C 语言代码、头文件引用、宏定义、全局变量定义和函数声明等内容,位于 `%{` 和 `%}` 之间。 28 | 29 | 2. 终结符和非终结符声明:用于定义语法中使用的终结符(也称为记号)和非终结符,常见声明包括 `%token`、`%union`、`%start`、`%type`、`%left`、`%right`、`%nonassoc` 等。 30 | 31 | - `%token` 定义终结符。定义形式:`%token TOKEN1 TOKEN2`。一行可定义多个终结符,空格分隔。一般约定终结符都是大写,非终结符的名字是小写。 32 | 33 | - `%type` 定义非终结符。 34 | 35 | - `%left`、`%right`、`%nonassoc` 定义终结符的结合性和优先级关系。定义形式与 `%token` 类似。先定义的优先级低,最后定义的优先级最高,同时定义的优先级相同。`%left` 表示左结 合(如“+”、“-”、“\*”、“/”);`%right` 表示右结合(例如“=”);`%nonassoc` 表示不可结合(即它定义的终结符不能连续出现。例如“-”负号。如下定义中,优先级关系为:AA = BB < CC < DD;表示结合性为:AA、BB 左结合,DD 右结合,CC 不可结合。 36 | 37 | ```c 38 | %left AA BB 39 | %nonassoc CC 40 | %right DD 41 | ``` 42 | 43 | - `%union` 定义了语法符号的语义值类型的集合。在 Bison 中,每个符号,包括记号和非终结符,都有一个不同数据类型的语义值,并且这些值通过 yylval 变量在移进和归约操作中传递。默认情况下,YYSTYPE(宏定义)为 yylval 的类型,通常为 int。但通过使用 `%union`,你可以重新定义符号的类型。使用 union 是因为不同节点可能需要不同类型的语义值,比如下面的定义,希望 ADDOP 的 值是 char 类型,而 NUMBER 应该是 double 类型。 44 | 45 | ```shell 46 | %token NUMBER 47 | %token ADDOP MULOP LPAREN RPAREN 48 | %union { 49 | char op; 50 | double num; 51 | } 52 | 53 | # 注意:一旦 %union 被定义,需要指定 Bison 每种符号使用的值类型,值类型通过放在尖括号中的 union 类型对应的成员名称确定,如 %token 。 54 | ``` 55 | 56 | 3. 使用 `%start` 指定文法的开始符号,表示最终需要规约成的符号,例如 `%start program`。如果不使用 `%start` 定义文法开始符 号,则默认在第二部分规则段中定义的第一条生产式规则的左部非终结符为开始符号。 57 | 58 | 语法规则部分: 59 | 60 | - 语法规则部分由归约规则和动作组成。规则基本按照巴科斯范式(BNF)描述。规则中目标或非终端符放在左边,后跟一个冒号:然后是 产生式的右边,之后是对应的动作(用 `{}` 包含)。**Bison 的语法树是按自下而上的归约方式进行构建的**。如下所示: 61 | 62 | ```c 63 | %% 64 | program: program expr '\n' { printf("%d\n", $2); } 65 | ; 66 | expr: expr '+' expr { $$ = $1 + $3; } 67 | | expr '-' expr { $$ = $1 - $3} 68 | ; 69 | %% 70 | /* 动作中“$1”表示右边的第一个标记的值,“$2”表示右边的第二个标记的值,依 71 | 次类推。“$$”表示归约后的值。以“expr: expr '+' expr { $$ = $1 + $3; }”,说明“$$”表示从左 72 | 向右第一个 expr,即规约的结果,“$1”表示从左向右第二个 expr,“$2”表示“+”加号,“$3” 73 | 表示从左向右第三个 expr。“|”符号表示其他的规约规则。 */ 74 | ``` 75 | 76 | C 代码部分: 77 | 78 | - C 代码部分为 C 代码,会被原样复制到 c 文件中,这里一般自定义一些函数。主要包括调用 Bison 的语法分析程序 `yyparse()`。其中 `yyparse` 函数由 Bison 根据语法规则自动生成,用于语法分析。 79 | 80 | ## 一个简单 Bison 程序 81 | 82 | 我们首先展示一个简单的 Bison 程序 `bison_demo.y`,帮助你理解 Bison,请仔细阅读文件内容。该程序没有联动 Flex,直接由用户提供 `yylex` 函数进行 token 分析,`yylex` 函数将在 Bison 生产的函数 `yyparse` 中被调用。 83 | 84 | ```c 85 | /* file name : bison_demo.y */ 86 | %{ 87 | #include 88 | /* 定义部分 */ 89 | /* 这部分代码会被原样拷贝到生成的 .c 文件的开头 */ 90 | int yylex(void); 91 | void yyerror(const char *s); 92 | %} 93 | 94 | /* 定义部分 */ 95 | /* 对语法的终结符和非终结符进行声明 */ 96 | %start reimu 97 | %token REIMU 98 | 99 | /* 从这里开始,下面是解析规则 */ 100 | %% 101 | reimu : marisa { /* 这里写与该规则对应的处理代码 */ puts("rule1"); } 102 | | REIMU { /* 这里写与该规则对应的处理代码 */ puts("rule2"); } 103 | ; /* 规则最后不要忘了用分号结束哦~ */ 104 | 105 | /* 这种写法表示 ε —— 空输入 */ 106 | marisa : { puts("Hello!"); } 107 | %% 108 | 109 | 110 | /* 以下是 C 代码部分 */ 111 | /* 在这个 Bison 程序中,我们没有联动 Flex,所以手写一个 yylex 函数 */ 112 | int yylex(void) 113 | { 114 | int c = getchar(); // 从 stdin 获取下一个字符 115 | switch (c) { 116 | case EOF: return YYEOF; 117 | case 'R': return REIMU; 118 | default: return YYUNDEF; // 报告 token 未定义,迫使 bison 报错。 119 | // 由于 bison 不同版本有不同的定义。如果这里 YYUNDEF 未定义,请尝试 YYUNDEFTOK 或使用一个随意的整数。 120 | } 121 | } 122 | 123 | void yyerror(const char *s) 124 | { 125 | fprintf(stderr, "%s\n", s); 126 | } 127 | 128 | int main(void) 129 | { 130 | yyparse(); // 启动解析 131 | return 0; 132 | } 133 | ``` 134 | 135 | 编译和运行: 136 | 137 | ```shell 138 | $ bison bison_demo.y 139 | 140 | # 查看生产的 c 程序 141 | $ ls bison_demo.tab.c 142 | bison_demo.tab.c 143 | 144 | $ gcc bison_demo.tab.c 145 | $ ./a.out 146 | R # <-- 不要回车,在这里按 Ctrl-D 147 | rule2 148 | 149 | $ ./a.out 150 | # <-- 不要回车,在这里按 Ctrl-D 151 | Hello! 152 | rule1 153 | 154 | $ ./a.out 155 | blablabla # <-- 不要回车,在这里按 Ctrl-D 156 | Hello! 157 | rule1 158 | syntax error <-- 发现了错误 159 | ``` 160 | 161 | ## 一个复杂的 Bison 程序 162 | 163 | 我们接下来展示一个复杂的 Bison 程序,该程序同时使用 Flex 和 Bison,使用 Flex 生产的 yylex 函数进行字符串分析,Bison 生成的 `yyparse` 进行语法树构建。共涉及 2 个文件,`calc.y` 和 `calc.l`。其功能是实现一个数值计算器。 164 | 165 | ```c 166 | /* calc.y */ 167 | %{ 168 | #include 169 | int yylex(void); 170 | void yyerror(const char *s); 171 | %} 172 | 173 | %token RET 174 | %token NUMBER 175 | %token ADDOP MULOP LPAREN RPAREN 176 | %type top line expr term factor 177 | 178 | %start top 179 | 180 | %union { 181 | char op; 182 | double num; 183 | } 184 | 185 | %% 186 | 187 | top 188 | : top line {} 189 | | {} 190 | 191 | line 192 | : expr RET 193 | { 194 | printf(" = %f\n", $1); 195 | } 196 | 197 | expr 198 | : term 199 | { 200 | $$ = $1; 201 | } 202 | | expr ADDOP term 203 | { 204 | switch ($2) { 205 | case '+': $$ = $1 + $3; break; 206 | case '-': $$ = $1 - $3; break; 207 | } 208 | } 209 | 210 | term 211 | : factor 212 | { 213 | $$ = $1; 214 | } 215 | | term MULOP factor 216 | { 217 | switch ($2) { 218 | case '*': $$ = $1 * $3; break; 219 | case '/': $$ = $1 / $3; break; // 这里会出什么问题? 220 | } 221 | } 222 | 223 | factor 224 | : LPAREN expr RPAREN 225 | { 226 | $$ = $2; 227 | } 228 | | NUMBER 229 | { 230 | $$ = $1; 231 | } 232 | 233 | %% 234 | 235 | void yyerror(const char *s) 236 | { 237 | fprintf(stderr, "%s\n", s); 238 | } 239 | 240 | int main() 241 | { 242 | yyparse(); 243 | return 0; 244 | } 245 | ``` 246 | 247 | ```c 248 | /* calc.l */ 249 | %option noyywrap 250 | 251 | %{ 252 | /* 引入 calc.y 定义的 token */ 253 | /* calc.tab.h 文件由 Bison 生成 */ 254 | /* 当我们在.y 文件中使用 %token 声明一个 token 时,这个 token 就会导出到 .h 中, 255 | 可以在 C 代码中直接使用,供 Flex 使用。就如 .l 文件中的\( { return LPAREN; }, 256 | 其中 LPAREN 定义来自 calc.tab.h,由对应的 .y 文件生成 */ 257 | #include "calc.tab.h" 258 | %} 259 | 260 | 261 | /* 规则部分 yylval 同样来自 calc.tab.h 文件,其类型为 YYSTYPE,用于 token 的相关属性,比如 NUMBER 对应的实际数值 */ 262 | /* 在这个例子中,YYSTYPE 定义如下 263 | 264 | typedef union YYSTYPE { 265 | char op; 266 | double num; 267 | } YYSTYPE; 268 | 269 | 其同样由 .y 文件根据 %union 生成,在文件中我们的 %union 定义如下 270 | 271 | %union { 272 | char op; 273 | double num; 274 | } 275 | */ 276 | 277 | %% 278 | \( { return LPAREN; } 279 | \) { return RPAREN; } 280 | "+"|"-" { yylval.op = yytext[0]; return ADDOP; } 281 | "*"|"/" { yylval.op = yytext[0]; return MULOP; } 282 | [0-9]+|[0-9]+\.[0-9]*|[0-9]*\.[0-9]+ { yylval.num = atof(yytext); return NUMBER; } 283 | " "|\t { } 284 | \r\n|\n|\r { return RET; } 285 | %% 286 | ``` 287 | 288 | 使用如下命令构建并测试程序: 289 | 290 | ```shell 291 | # 生成 calc.tab.c 和 calc.tab.h。如果不给出 -d 参数,则不会生成 .h 文件。 292 | $ bison -d calc.y 293 | # 生成 lex.yy.c 294 | $ flex calc.l 295 | 296 | $ ls calc.tab.c calc.tab.h lex.yy.c 297 | calc.tab.c calc.tab.h lex.yy.c 298 | 299 | # 编译 300 | $ gcc lex.yy.c calc.tab.c -o calc 301 | $ ./calc 302 | 1+1 303 | = 2.000000 304 | 2*(1+1) 305 | = 4.000000 306 | 2*1+1 307 | = 3.000000 308 | ``` 309 | 310 | 其大致工作流程如下: 311 | 312 | ![image-20230913164805071](./assets/image-20230913164805071-1694594895535-1-1694594931543-3.png) 313 | 314 | ### 思考题 315 | 316 | - 上述计算器例子的文法中存在左递归,为什么 `bison` 可以处理? 317 | - 能否修改计算器例子的文法,使得它支持除数 0 规避功能? 318 | -------------------------------------------------------------------------------- /docs/lab1/Flex.md: -------------------------------------------------------------------------------- 1 | # Flex 2 | 3 | ## 简介 4 | 5 | 最初的词法分析器生成程序 Lex 由 Mike Lesk 和当时在 AT&T 实习的 Eric Schmidt 于 1975 年合作完成。Lex 可单独使用或与 Johnson 的 yacc 协同工作。然而,Lex 以低效率和 bug 而闻名。在 20 世纪 80 年代,Lawrence Berkeley 实验室的 Vern Paxson 以 C 重新编写了 Lex,命名为 Flex(Fast Lexical Analyzer Generator)。Flex 现在是 SourceForge 的一个项目,用于 C/C++ 的词法扫描生成器。Flex 支持使用正则表达式描述词法模式。 6 | 7 | Flex 执行过程如下: 8 | 9 | ![img](assets/flex.jpg) 10 | 11 | 1. 首先,Flex 源程序中的规则被转换成状态转换图,生成对应的代码,包括核心的 `yylex()` 函数,保存在 `lex.yy.c` 文件中。Flex 源程序通常以 `.l` 为后缀,按照 Flex 语法编写,用于描述词法分析器。 12 | 13 | 2. 生成的 `lex.yy.c` 文件可以通过 C 编译为可执行文件。 14 | 15 | 3. 最终,可执行文件将输入流解析成一系列的标记(tokens)。 16 | 17 | ## Flex 语言 18 | 19 | Flex 源程序的结构如下: 20 | 21 | ```c 22 | 声明部分 23 | 24 | 规则部分 25 | 26 | C 代码部分 27 | ``` 28 | 29 | - **声明部分**包含名称声明和选项设置,`%{` 和 `%}` 之间的内容会被原样复制到生成的 C 文件头部,可用于编写 C 代码,如头文件声明和变量定义等。 30 | - **规则部分**位于两个 `%%` 之间,包括多条规则,每个规则由正则表达式定义的模式和与之匹配的 C 代码动作组成。当词法分析程序识别出某模式时,执行相应的 C 代码。 31 | - **C 代码部分**可包括 `main()` 函数,用于调用 `yylex()` 执行词法分析。`yylex()` 是由 Flex 生成的词法分析例程,默认从 stdin 读取输入文本。 32 | 33 | 下面我们结合具体的例子,来看 Flex 的源程序的三部分结构: 34 | 35 | ```c 36 | /* %option noyywrap 功能较为复杂,同学们自行了解 */ 37 | %option noyywrap 38 | %{ 39 | /* Flex 源程序采样类 C 的语法和规则 */ 40 | /* 以下是声明部分,`%{` 和 `%}` 之间的内容会被原样复制到生成的 C 文件头部 41 | 包括该条注释内容 */ 42 | #include 43 | int chars = 0; 44 | int words = 0; 45 | %} 46 | 47 | /* 以下是规则部分,在规则部分写注释不能顶格写 */ 48 | /* 每条规则由正则表达式和动作组成 */ 49 | /* 第一条规则匹配纯字母的字符串,并统计字母个数和字符串个数 50 | 其中 yytext 为匹配到的 token */ 51 | /* 第二条规则匹配其他字符或字符串并执行空动作 */ 52 | %% 53 | /* 在规则部分,不要顶格写注释 */ 54 | [a-zA-Z]+ { chars += strlen(yytext); words++; } 55 | . {} 56 | %% 57 | 58 | /* 以下为 C 代码部分 */ 59 | int main() 60 | { 61 | /* yylex() 是由 Flex 自行生成的,用于执行 */ 62 | yylex(); 63 | /* 对于 stdin 输入匹配结束,执行其他操作 */ 64 | printf("look, I find %d words of %d chars\n", words, chars); 65 | return 0; 66 | } 67 | ``` 68 | 69 | ## 编译和执行 70 | 71 | 下面,我们将演示如何在 Ubuntu 系统上编译和运行 Flex 源程序,假设以上的 Flex 源程序被保存为 `wc.l`。 72 | 73 | 首先,我们执行以下命令将 Flex 源程序转化为 c 程序: 74 | 75 | ```shell 76 | $ flex wc.l 77 | ``` 78 | 79 | 接着,我们查看生成的 `lex.yy.c` 文件: 80 | 81 | ```shell 82 | $ ls lex.yy.c 83 | lex.yy.c 84 | ``` 85 | 86 | `lex.yy.c` 文件比较复杂,你可以简要浏览一下: 87 | 88 | ```shell 89 | $ cat lex.yy.c 90 | ...... 91 | ``` 92 | 93 | 接下来,我们编译 `lex.yy.c` 生成可执行文件: 94 | 95 | ```shell 96 | $ gcc lex.yy.c -o lexer 97 | ``` 98 | 99 | 此时,将生成一个可执行文件 `lexer`: 100 | 101 | ```shell 102 | $ ls lexer 103 | lexer 104 | ``` 105 | 106 | 最后,执行可执行文件并输入 `hello world!`: 107 | 108 | ```shell 109 | $ ./lexer 110 | hello world! 111 | ^D # 如果使用 stdin 作为输入,按 Ctrl+D 退出 112 | look, I find 2 words of 10 chars 113 | ``` 114 | 115 | 恭喜,你已成功使用 Flex 创建了一个简单的分析器! 116 | 117 | ## 扩展阅读 118 | 119 | Flex 自带详细手册,通过以下终端命令打开: 120 | 121 | ```shell 122 | $ info flex 123 | # 按 q 键退出 124 | ``` 125 | 126 | 特别推荐仔细阅读章节 `Pattern` 和 `Matching`。 127 | 128 | ## 思考题 129 | 130 | 1. 如果存在同时以下规则和动作,对于字符串 `+=`,哪条规则会被触发,并尝试解释理由。 131 | 132 | ```c 133 | %% 134 | \+ { return ADD; } 135 | = { return ASSIGN; } 136 | \+= { return ASSIGNADD; } 137 | %% 138 | ``` 139 | 140 | 2. 如果存在同时以下规则和动作,对于字符串 `ABC`,哪条规则会被触发,并尝试解释理由。 141 | 142 | ```c 143 | %% 144 | ABC { return 1; } 145 | [a-zA-Z]+ {return 2; } 146 | %% 147 | ``` 148 | 149 | 3. 如果存在同时以下规则和动作,对于字符串 `ABC`,哪条规则会被触发,并尝试解释理由。 150 | 151 | ```c 152 | %% 153 | [a-zA-Z]+ {return 2; } 154 | ABC { return 1; } 155 | %% 156 | ``` 157 | 158 | 提示:你可以编写相关程序,实际进行执行。具体原因在 Flex 自带手册的 `Matching` 章节中有详细说明。 159 | -------------------------------------------------------------------------------- /docs/lab1/assets/flex.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustc-compiler-principles/2023/3413d0831014139229c3469a81fe541aad7cab81/docs/lab1/assets/flex.jpg -------------------------------------------------------------------------------- /docs/lab1/assets/image-20230913142328935.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustc-compiler-principles/2023/3413d0831014139229c3469a81fe541aad7cab81/docs/lab1/assets/image-20230913142328935.png -------------------------------------------------------------------------------- /docs/lab1/assets/image-20230913164805071-1694594895535-1-1694594931543-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustc-compiler-principles/2023/3413d0831014139229c3469a81fe541aad7cab81/docs/lab1/assets/image-20230913164805071-1694594895535-1-1694594931543-3.png -------------------------------------------------------------------------------- /docs/lab1/index.md: -------------------------------------------------------------------------------- 1 | # Lab1 词法语法解析 2 | 3 | !!! warning "Deadline" 4 | 5 | **2023 年 9 月 28 日 23:59** 6 | 7 | 本次实验需要同学们从无到有完成一个完整的 Cminusf 解析器,包括基于 Flex 的词法分析器和基于 Bison 的语法分析器。 8 | 9 | ## 文档 10 | 11 | 本次实验提供了若干文档,请根据实验进度和个人需要认真阅读。 12 | 13 | - [正则表达式](./正则表达式.md) 14 | - [Flex](./Flex.md) 15 | - [Bison](./Bison.md) 16 | - [实验细节与要求](实验细节与要求.md) 17 | 18 | ## 实验内容 19 | 20 | 内容一:阅读[正则表达式](./正则表达式.md)、[Flex](./Flex.md)、[Bison](./Bison.md) 掌握基础知识,并**回答每个文档内的思考题**,回答内容保存为 `answer.pdf` 21 | 22 | 内容二:阅读[实验细节与要求](实验细节与要求.md),完成词法分析器 23 | 24 | 内容三:阅读[实验细节与要求](实验细节与要求.md),完成语法分析器 25 | 26 | ## 实验要求 27 | 28 | 请根据 Lab0 的内容,将[实验仓库](https://cscourse.ustc.edu.cn/vdir/Gitlab/compiler_staff/2023ustc-jianmu-compiler) fork 并 clone 到本地虚拟机中。 29 | 30 | ## 提交内容 31 | 32 | - 在希冀平台提交你实验仓库的 url(如 `https://cscourse.ustc.edu.cn/vdir/Gitlab/xxx/2023ustc-jianmu-compiler.git`)。 33 | - 在希冀平台提交你的 `answer.pdf` 文件。 34 | -------------------------------------------------------------------------------- /docs/lab1/实验细节与要求.md: -------------------------------------------------------------------------------- 1 | # 实验细节与要求 2 | 3 | ## 词法分析器 4 | 5 | 现在我们开始编写 Cminusf 的词法解析器。首先,让我们了解一下 Cminusf 的词法规则,随后将介绍实验要求和细节。 6 | 7 | ### Cminusf 词法 8 | 9 | Cminus 是 C 语言的一个子集,该语言的语法在《编译原理与实践》第九章附录中有详细的介绍。而 Cminusf 则是在 Cminus 上追加了浮点操作。 10 | 11 | 1. 关键字 12 | 13 | ```c 14 | else if int return void while float 15 | ``` 16 | 17 | 2. 专用符号 18 | 19 | ```c 20 | + - * / < <= > >= == != = ; , ( ) [ ] { } /* */ 21 | ``` 22 | 23 | 3. 标识符 ID 和数值,通过下列正则表达式定义: 24 | 25 | ```c 26 | letter = a|...|z|A|...|Z 27 | digit = 0|...|9 28 | ID = letter+ 29 | INTEGER = digit+ 30 | FLOAT = (digit+. | digit*.digit+) 31 | ``` 32 | 33 | 4. 注释用 `/*...*/` 表示,可以超过一行。注释不能嵌套。 34 | 35 | ```c 36 | /*...*/ 37 | ``` 38 | 39 | 请认真阅读 Cminusf 的词法。其词法特性相比 C 语言做了大量简化,比如标识符 `student_id` 在 C 语言中是合法的,但是在 Cminusf 中是不合法的。 40 | 41 | ### 实验内容 42 | 43 | 本部分需要各位同学在 `src/parser/lexical_analyzer.l` 文件中根据 Cminusf 的词法规则完成词法分析器。在 `lexical_analyzer.l` 文件中,你只需在规则部分补全模式和动作即可,能够输出识别出的 `token`,`text`,`line(刚出现的行数)`,`pos_start(该行开始位置)`,`pos_end(结束的位置,不包含)`。比如: 44 | 45 | 文本输入: 46 | 47 | ```c 48 | void main(void) { return; } 49 | ``` 50 | 51 | 则识别结果应该如下: 52 | 53 | ```shell 54 | Token Text Line Column (Start,End) 55 | 282 void 1 (1,5) 56 | 284 main 1 (6,10) 57 | 272 ( 1 (10,11) 58 | 282 void 1 (11,15) 59 | 273 ) 1 (15,16) 60 | 276 { 1 (17,18) 61 | 281 return 1 (19,25) 62 | 270 ; 1 (25,26) 63 | 277 } 1 (27,28) 64 | ``` 65 | 66 | 必须维护正确的:`token`、`text` 67 | 68 | 选择维护的(方便 debug,测试):`line`、`pos_start`、`pos_end` 69 | 70 | 请注意以下几点: 71 | 72 | 1. 在补全 `lexical_analyzer.l` 前,你需要在 `src/parser/syntax_analyzer.y` 中补全 `%union` 的相关内容; 73 | 2. 在补全 `lexical_analyzer.l` 前,需要首先修改 `src/parser/syntax_analyzer.y` 文件对 token 进行定义,定义方式形如 `%token ERROR`; 74 | 3. token 编号是程序自动生成的,根据 token 定义顺序不同,输出的 token 编号也可能不同,是正常现象; 75 | 4. 对于部分 token,只需被识别,但不应该被输出到分析结果中,因为这些 token 对程序运行没有作用。 76 | 77 | ## 语法分析器 78 | 79 | ### Cminusf 语法 80 | 81 | 本小节将给出 Cminusf 的语法,我们将 Cminusf 的所有规则分为五类。 82 | 83 | 1. 字面量、关键字、运算符与标识符 84 | - `type-specifier` 85 | - `relop` 86 | - `addop` 87 | - `mulop` 88 | 2. 声明 89 | - `declaration-list` 90 | - `declaration` 91 | - `var-declaration` 92 | - `fun-declaration` 93 | - `local-declarations` 94 | 3. 语句 95 | - `compound-stmt` 96 | - `statement-list` 97 | - `statement` 98 | - `expression-stmt` 99 | - `iteration-stmt` 100 | - `selection-stmt` 101 | - `return-stmt` 102 | 4. 表达式 103 | - `expression` 104 | - `simple-expression` 105 | - `var` 106 | - `additive-expression` 107 | - `term` 108 | - `factor` 109 | - `integer` 110 | - `float` 111 | - `call` 112 | 5. 其他 113 | - `params` 114 | - `param-list` 115 | - `param` 116 | - `args` 117 | - `arg-list` 118 | 119 | 起始符号是 `program`。文法中用到的 token 均以下划线和粗体标出。 120 | 121 | 1. $\text{program} \rightarrow \text{declaration-list}$ 122 | 2. $\text{declaration-list} \rightarrow \text{declaration-list}\ \text{declaration}\ |\ \text{declaration}$ 123 | 3. $\text{declaration} \rightarrow \text{var-declaration}\ |\ \text{fun-declaration}$ 124 | 4. $\text{var-declaration}\ \rightarrow \text{type-specifier}\ \underline{\textbf{ID}}\ \underline{\textbf{;}}\ |\ \text{type-specifier}\ \underline{\textbf{ID}}\ \underline{\textbf{[}}\ \underline{\textbf{INTEGER}}\ \underline{\textbf{]}}\ \underline{\textbf{;}}$ 125 | 5. $\text{type-specifier} \rightarrow \underline{\textbf{int}}\ |\ \underline{\textbf{float}}\ |\ \underline{\textbf{void}}$ 126 | 6. $\text{fun-declaration} \rightarrow \text{type-specifier}\ \underline{\textbf{ID}}\ \underline{\textbf{(}}\ \text{params}\ \underline{\textbf{)}}\ \text{compound-stmt}$ 127 | 7. $\text{params} \rightarrow \text{param-list}\ |\ \underline{\textbf{void}}$ 128 | 8. $\text{param-list} \rightarrow \text{param-list}\ \underline{\textbf{,}}\ \text{param}\ |\ \text{param}$ 129 | 9. $\text{param} \rightarrow \text{type-specifier}\ \underline{\textbf{ID}}\ |\ \text{type-specifier}\ \underline{\textbf{ID}}\ \underline{\textbf{[}}\ \underline{\textbf{]}}$ 130 | 10. $\text{compound-stmt} \rightarrow \underline{\textbf{\\{}}\ \text{local-declarations}\ \text{statement-list}\ \underline{\textbf{\\}}}$ 131 | 11. $\text{local-declarations} \rightarrow \text{local-declarations var-declaration}\ |\ \text{empty}$ 132 | 12. $\text{statement-list} \rightarrow \text{statement-list}\ \text{statement}\ |\ \text{empty}$ 133 | 13. $\begin{aligned}\text{statement} \rightarrow\ &\text{expression-stmt}\\\ &|\ \text{compound-stmt}\\\ &|\ \text{selection-stmt}\\\ &|\ \text{iteration-stmt}\\\ &|\ \text{return-stmt}\end{aligned}$ 134 | 14. $\text{expression-stmt} \rightarrow \text{expression}\ \underline{\textbf{;}}\ |\ \underline{\textbf{;}}$ 135 | 15. $\begin{aligned}\text{selection-stmt} \rightarrow\ &\underline{\textbf{if}}\ \underline{\textbf{(}}\ \text{expression}\ \underline{\textbf{)}}\ \text{statement}\\\ &|\ \underline{\textbf{if}}\ \underline{\textbf{(}}\ \text{expression}\ \underline{\textbf{)}}\ \text{statement}\ \underline{\textbf{else}}\ \text{statement}\end{aligned}$ 136 | 16. $\text{iteration-stmt} \rightarrow \underline{\textbf{while}}\ \underline{\textbf{(}}\ \text{expression}\ \underline{\textbf{)}}\ \text{statement}$ 137 | 17. $\text{return-stmt} \rightarrow \underline{\textbf{return}}\ \underline{\textbf{;}}\ |\ \underline{\textbf{return}}\ \text{expression}\ \underline{\textbf{;}}$ 138 | 18. $\text{expression} \rightarrow \text{var}\ \underline{\textbf{=}}\ \text{expression}\ |\ \text{simple-expression}$ 139 | 19. $\text{var} \rightarrow \underline{\textbf{ID}}\ |\ \underline{\textbf{ID}}\ \underline{\textbf{[}}\ \text{expression} \underline{\textbf{]}}$ 140 | 20. $\text{simple-expression} \rightarrow \text{additive-expression}\ \text{relop}\ \text{additive-expression}\ |\ \text{additive-expression}$ 141 | 21. $\text{relop}\ \rightarrow \underline{\textbf{<=}}\ |\ \underline{\textbf{<}}\ |\ \underline{\textbf{>}}\ |\ \underline{\textbf{>=}}\ |\ \underline{\textbf{==}}\ |\ \underline{\textbf{!=}}$ 142 | 22. $\text{additive-expression} \rightarrow \text{additive-expression}\ \text{addop}\ \text{term}\ |\ \text{term}$ 143 | 23. $\text{addop} \rightarrow \underline{\textbf{+}}\ |\ \underline{\textbf{-}}$ 144 | 24. $\text{term} \rightarrow \text{term}\ \text{mulop}\ \text{factor}\ |\ \text{factor}$ 145 | 25. $\text{mulop} \rightarrow \underline{\textbf{*}}\ |\ \underline{\textbf{/}}$ 146 | 26. $\text{factor} \rightarrow \underline{\textbf{(}}\ \text{expression}\ \underline{\textbf{)}}\ |\ \text{var}\ |\ \text{call}\ |\ \text{integer}\ |\ \text{float}$ 147 | 27. $\text{integer} \rightarrow \underline{\textbf{INTEGER}}$ 148 | 28. $\text{float} \rightarrow \underline{\textbf{FLOATPOINT}}$ 149 | 29. $\text{call} \rightarrow \underline{\textbf{ID}}\ \underline{\textbf{(}}\ \text{args} \underline{\textbf{)}}$ 150 | 30. $\text{args} \rightarrow \text{arg-list}\ |\ \text{empty}$ 151 | 31. $\text{arg-list} \rightarrow \text{arg-list}\ \underline{\textbf{,}}\ \text{expression}\ |\ \text{expression}$ 152 | 153 | ### 实验内容 154 | 155 | 本部分需要同学们完成 `src/parser/syntax_analyzer.y`。与词法分析器相同,你只需要补全代码中规则部分即可。 156 | 157 | 如果实现正确,该语法分析器可以从 Cminusf 代码分析得到一颗语法树。例如输入 158 | 159 | ```c 160 | int main(void) { return 0; } 161 | ``` 162 | 163 | 可以得到如下语法树 164 | 165 | ```plaintext 166 | >--+ program 167 | | >--+ declaration-list 168 | | | >--+ declaration 169 | | | | >--+ fun-declaration 170 | | | | | >--+ type-specifier 171 | | | | | | >--* int 172 | | | | | >--* main 173 | | | | | >--* ( 174 | | | | | >--+ params 175 | | | | | | >--* void 176 | | | | | >--* ) 177 | | | | | >--+ compound-stmt 178 | | | | | | >--* { 179 | | | | | | >--+ local-declarations 180 | | | | | | | >--* epsilon 181 | | | | | | >--+ statement-list 182 | | | | | | | >--+ statement-list 183 | | | | | | | | >--* epsilon 184 | | | | | | | >--+ statement 185 | | | | | | | | >--+ return-stmt 186 | | | | | | | | | >--* return 187 | | | | | | | | | >--+ expression 188 | | | | | | | | | | >--+ simple-expression 189 | | | | | | | | | | | >--+ additive-expression 190 | | | | | | | | | | | | >--+ term 191 | | | | | | | | | | | | | >--+ factor 192 | | | | | | | | | | | | | | >--+ integer 193 | | | | | | | | | | | | | | | >--* 0 194 | | | | | | | | | >--* ; 195 | | | | | | >--* } 196 | ``` 197 | 198 | **这一部分必须严格遵守我们给出的语法,输出必须与标准程序输出完全一致。** 199 | 200 | ## 实验要求 201 | 202 | ### 仓库目录结构 203 | 204 | 与本次实验有关的文件如下。 205 | 206 | ```sh 207 | |-- CMakeLists.txt 208 | |-- build # 在编译过程中产生,不需要通过 git add 添加到仓库中 209 | |-- src 210 | | |-- CMakeLists.txt 211 | | |-- common 212 | | `-- parser 213 | | |-- CMakeLists.txt 214 | | |-- lexer.c 215 | | |-- lexical_analyzer.l # 你需要修改本文件 216 | | |-- parser.c 217 | | `-- syntax_analyzer.y # 你需要修改本文件 218 | `-- tests 219 | |-- 1-parser 220 | | |-- input # 针对 Lab1 的测试样例 221 | | |-- output_standard # 助教提供的标准参考结果 222 | | |-- output_student # 测试脚本产生的你解析后的结果 223 | | |-- cleanup.sh 224 | | `-- eval_lab1.sh # 测试用的脚本 225 | `-- testcases_general # 整个课程所用到的测试样例 226 | ``` 227 | 228 | ### 编译、运行和评测 229 | 230 | 首先将你的实验仓库克隆的本地虚拟机中。要编译和运行词法分析器,请按照以下步骤在本地虚拟机中进行操作: 231 | 232 | **编译** 233 | 234 | ```shell 235 | $ cd 2023ustc-jianmu-compiler 236 | $ mkdir build 237 | $ cd build 238 | # 使用 cmake 生成 makefile 等文件 239 | $ cmake .. 240 | # 使用 make 进行编译 241 | $ make 242 | ``` 243 | 244 | 如果构建成功,你会在 `build` 文件夹下找到 `lexer` 和 `parser` 可执行文件,用于对 Cminusf 文件进行词法和语法分析。 245 | 246 | ```shell 247 | $ ls lexer parser 248 | lexer parser 249 | 250 | $ ./lexer 251 | usage: lexer input_file 252 | ``` 253 | 254 | **运行** 255 | 256 | 我们在 `tests/testcases_general` 文件夹中准备了一些通用案例。 257 | 258 | ```shell 259 | # 返回 2023ustc-jianmu-compiler 的根目录 260 | $ cd ${WORKSPACE} 261 | 262 | # 运行 lexer,进行词法分析 263 | $ ./build/lexer tests/testcases_general/1-return.cminus 264 | Token Text Line Column (Start,End) 265 | 282 void 1 (1,5) 266 | 284 main 1 (6,10) 267 | 272 ( 1 (10,11) 268 | 282 void 1 (11,15) 269 | 273 ) 1 (15,16) 270 | 276 { 1 (17,18) 271 | 281 return 1 (19,25) 272 | 270 ; 1 (25,26) 273 | 277 } 1 (27,28) 274 | 275 | # 运行 parser,解析 1-return.cminus 输出语法树 276 | $ ./build/parser ./tests/testcases_general/1-return.cminus 277 | >--+ program 278 | | >--+ declaration-list 279 | | | >--+ declaration 280 | | | | >--+ fun-declaration 281 | | | | | >--+ type-specifier 282 | | | | | | >--* void 283 | | | | | >--* main 284 | | | | | >--* ( 285 | | | | | >--+ params 286 | | | | | | >--* void 287 | | | | | >--* ) 288 | | | | | >--+ compound-stmt 289 | | | | | | >--* { 290 | | | | | | >--+ local-declarations 291 | | | | | | | >--* epsilon 292 | | | | | | >--+ statement-list 293 | | | | | | | >--+ statement-list 294 | | | | | | | | >--* epsilon 295 | | | | | | | >--+ statement 296 | | | | | | | | >--+ return-stmt 297 | | | | | | | | | >--* return 298 | | | | | | | | | >--* ; 299 | | | | | | >--* } 300 | ``` 301 | 302 | **测试** 303 | 304 | 测试样例分为两个部分,分别是 `tests/testcases_general` 和 lab1 限定的 `tests/1-parser`。 305 | 306 | 我们重点使用 `tests/1-parser` 考察语法分析器的正确性。其结构如下: 307 | 308 | ```shell 309 | . 310 | |-- input # 输入目录,包含 xxx.cminus 文件 311 | | |-- easy 312 | | |-- hard 313 | | `-- normal 314 | |-- output_standard # 助教标准输出目录,包含 xxx.syntax_tree 文件 315 | | |-- easy 316 | | |-- normal 317 | | |-- hard 318 | | `-- testcases_general 319 | |-- output_student # 学生输出目录,测试过程中产生 xxx.syntax_tree 文件 320 | | |-- easy 321 | | |-- hard 322 | | |-- normal 323 | | `-- testcases_general 324 | |-- eval_lab1.sh # 测试脚本 325 | `-- cleanup.sh 326 | ``` 327 | 328 | 我们使用 `diff` 命令进行结果比较: 329 | 330 | ```shell 331 | $ cd 2023ustc-jianmu-compiler 332 | $ export PATH="$(realpath ./build):$PATH" 333 | $ cd tests/1-parser 334 | $ parser input/normal/local-decl.cminus > output_student/normal/local-decl.syntax_tree 335 | $ diff output_student/normal/local-decl.syntax_tree output_standard/normal/local-decl.syntax_tree 336 | [输出为空,代表没有区别,该测试通过] 337 | ``` 338 | 339 | 我们提供了 `eval_lab1.sh` 脚本进行快速批量测试。该脚本的第一个参数可以是 `easy`、 `normal`、 `hard` 以及 `testcases_general`,并且有第二个可选参数,用于批量 `diff` 和助教提供的标准参考结果进行比较。脚本运行后会将生成结果放在 `tests/1-parser/output_student` 文件夹里,而助教的参考输出则在 `tests/1-parser/output_standard` 中。 340 | 341 | ```shell 342 | $ ./eval_lab1.sh easy 343 | [info] Analyzing FAIL_comment.cminus 344 | error at line 1 column 4: syntax error 345 | ... 346 | [info] Analyzing id.cminus 347 | 348 | $ ./eval_lab1.sh easy yes 349 | ... 350 | [info] Analyzing FAIL_comment.cminus 351 | error at line 1 column 4: syntax error 352 | ... 353 | [info] Comparing... 354 | [info] No difference! Congratulations! 355 | ``` 356 | -------------------------------------------------------------------------------- /docs/lab1/正则表达式.md: -------------------------------------------------------------------------------- 1 | # 正则表达式 2 | 3 | 正则表达式(Regex)是描述文本模式的工具,用于字符串匹配、搜索和替换。它由字符和特殊符号组成,形成匹配文本的模式。正则表达式在编程和文本处理中非常强大。 4 | 5 | 下面是计算机程序中一些常见的正则表达式符号及其含义: 6 | 7 | 8 | 9 | | 符号 | 含义 | 10 | | --- | --- | 11 | | `.` | 匹配除换行符以外的任何字符 | 12 | | `*` | 匹配前一个字符的零次或多次出现 | 13 | | `+` | 匹配前一个字符的一次或多次出现 | 14 | | `?` | 匹配前一个字符的零次或一次出现 | 15 | | `[]` | 定义一个字符类,匹配括号内的任何一个字符 | 16 | | `[^]` | 否定字符类,匹配不在括号内的任何一个字符 | 17 | | `()` | 创建一个子表达式,并捕获匹配的部分 | 18 | | `|` | 或操作,匹配两边任一表达式 | 19 | | `{n}` | 匹配前一个字符恰好 n 次 | 20 | | `{n,}` | 匹配前一个字符至少 n 次 | 21 | | `{n,m}` | 匹配前一个字符至少 n 次,但不超过 m 次 | 22 | | `\` | 转义字符,用于匹配特殊字符 | 23 | | `^` | 锚点,匹配字符串的开头 | 24 | | `$` | 锚点,匹配字符串的结尾 | 25 | | `\d` | 匹配任何数字字符 | 26 | | `\D` | 匹配任何非数字字符 | 27 | | `\w` | 匹配任何字母、数字或下划线字符 | 28 | | `\W` | 匹配任何非字母、非数字、非下划线字符 | 29 | | `\s` | 匹配任何空白字符(空格、制表符、换行等) | 30 | | `\S` | 匹配任何非空白字符 | 31 | 32 | 33 | 34 | 这些符号允许你创建复杂的 pattern,用于匹配和操作文本中的字符串。不同的编程语言和工具可能会略有不同,但基本的正则表达式符号通常是通用的。 35 | 36 | 这是一个正则表达式在线匹配网站(),你可以在该网站创建相关正则 pattern 进行匹配,以熟悉计算机程序中的正则表达式符号。 37 | 38 | ## 思考题 39 | 40 | - `\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*` 正则表达式匹配的字符串的含义是什么? 41 | - 匹配 HTML 注释:编写一个正则表达式,可以匹配 HTML 中的注释,例如 ``。 42 | -------------------------------------------------------------------------------- /docs/lab2/autogen.md: -------------------------------------------------------------------------------- 1 | # IR 自动化生成 2 | 3 | 学生将在本阶段提供的实验框架下,利用访问者模式遍历抽象语法树,调用 Light IR C++ 库,实现 IR 自动化生成。 4 | 5 | ## 实验框架介绍 6 | 7 | ### 抽象语法树 8 | 9 | !!! notes "分析树(Parse Tree)与抽象语法树(Abstract Syntax Tree)" 10 | 11 | 分析树在语法分析的过程中被构造;抽象语法树则是分析树的浓缩表示,使用运算符作为根节点和内部节点,并使用操作数作为子节点。进一步了解可以阅读 [分析树和抽象语法树的比较](https://stackoverflow.com/questions/5026517/whats-the-difference-between-parse-trees-and-abstract-syntax-trees-asts)。 12 | 13 | 实验框架实现了 Lab1 生成的分析树到 C++ 上的抽象语法树的转换。可以使用[访问者模式](./visitor_pattern.md)来实现对抽象语法树的遍历,[ast.hpp](https://cscourse.ustc.edu.cn/vdir/Gitlab/compiler_staff/2023ustc-jianmu-compiler/-/blob/master/include/common/ast.hpp) 文件中包含抽象语法树节点定义。 14 | 15 | ### 符号表 Scope 16 | 17 | 在 `include/cminusf_builder.hpp` 中,我们定义了一个用于存储作用域的类 `Scope`。它的作用是辅助我们在遍历语法树时,管理不同作用域中的变量。它提供了以下接口: 18 | 19 | ```cpp 20 | // 进入一个新的作用域 21 | void enter(); 22 | // 退出一个作用域 23 | void exit(); 24 | // 往当前作用域插入新的名字->值映射 25 | bool push(std::string name, Value *val); 26 | // 根据名字,寻找到值 27 | Value* find(std::string name); 28 | // 判断当前作用域是否是全局作用域 29 | bool in_global(); 30 | ``` 31 | 32 | 你需要根据语义合理调用 `enter` 与 `exit`,并且在变量声明和使用时正确调用 `push` 与 `find`。 33 | 34 | ### `CminusfBuilder` 类 35 | 36 | `CminusfBuilder` 类定义在 [cminusf_builder.hpp](https://cscourse.ustc.edu.cn/vdir/Gitlab/compiler_staff/2023ustc-jianmu-compiler/-/blob/master/include/cminusfc/cminusf_builder.hpp)文件中,`CminusfBuilder` 类中定义了对抽象语法树不同语法节点的 `visit` 函数,实验已给出了一些语法树节点的访问规则,其余的需要学生补充。 37 | 38 | 在 `CminusfBuilder` 构造函数函数中,下列代码片段是对 [Cminusf 语义](../common/cminusf.md#cminusf-的语义)中的 4 个预定义函数进行声明并加入全局符号表中,在生成 IR 时可从符号表中查找。我们的测试样例会使用这些函数,从而实现 IO。 39 | 40 | ```cpp 41 | scope.enter(); 42 | scope.push("input", input_fun); 43 | scope.push("output", output_fun); 44 | scope.push("outputFloat", output_float_fun); 45 | scope.push("neg_idx_except", neg_idx_except_fun); 46 | ``` 47 | 48 | `CminusfBuilder` 类使用成员 `context` 存储翻译时状态,下列代码片段是 `context` 的定义,学生需要为该结构体添加更多域来存储翻译时的状态。 49 | 50 | ```cpp 51 | struct { 52 | // function that is being built 53 | Function *func = nullptr; 54 | } context; 55 | ``` 56 | 57 | ## 实验内容 58 | 59 | 阅读 [Cminusf 语义](../common/cminusf.md#cminusf-的语义),并根据语义补全 `include/cminusfc/cminusf_builder.hpp` 与 `src/cminusfc/cminusf_builder.cpp` 文件,实现 IR 自动产生的算法,使得它能正确编译任何合法的 Cminusf 程序,生成符合 [Cminusf 语义](../common/cminusf.md#cminusf-的语义)的 IR。 60 | 61 | **友情提示**: 62 | 63 | 1. 请比较通过 cminusfc 产生的 IR 和通过 clang 产生的 IR 来找出可能的问题或发现新的思路。 64 | 2. 使用 GDB 进行调试来检查错误的原因。 65 | 3. 我们为 `Function`、`Type` 等类都实现了 `print` 接口,可以使用我们提供的 [logging 工具](../common/logging.md) 进行打印调试。 66 | 4. 对于 C++ 不熟悉的学生可以参考 [C++ 简介](../common/simple_cpp.md)。 67 | 68 | ## 实验要求 69 | 70 | ### 仓库目录结构 71 | 72 | 与该阶段有关的文件如下。 73 | 74 | ``` 75 | . 76 | ├── CMakeLists.txt 77 | ├── include <- 实验所需的头文件 78 | │   ├── ... 79 | | ├── cminusfc 80 | | | └── cminusf_builder.hpp <- 该阶段需要修改的文件 81 | │   ├── lightir/* 82 | │   └── common 83 | │ ├── ast.hpp 84 | │ ├── logging.hpp 85 | │ └── syntax_tree.h 86 | ├── src 87 | │   ├── ... 88 | │   └── cminusfc 89 | │   ├── cminusfc.cpp <- cminusfc 的主程序文件 90 | │   └── cminusf_builder.cpp <- 该阶段需要修改的文件 91 | └── tests 92 | ├── ... 93 | └── 2-ir-gen 94 | └── autogen 95 |    ├── testcases <- 助教提供的测试样例 96 | ├── answers <- 助教提供的测试样例 97 |    └── eval_lab2.py <- 助教提供的测试脚本 98 | ``` 99 | 100 | ### 编译、运行和评测 101 | 102 | **编译** 103 | 104 | ```shell 105 | $ cd 2023ustc-jianmu-compiler 106 | $ mkdir build 107 | $ cd build 108 | # 使用 cmake 生成 makefile 等文件 109 | $ cmake .. 110 | # 使用 make 进行编译 111 | $ make 112 | # 安装以链接 libcminus_io.a 113 | $ sudo make install 114 | ``` 115 | 116 | 如果构建成功,你会在 `build` 文件夹下找到 cminusfc 可执行文件,它能将 cminus 文件输出为 IR 文件,编译成二进制可执行文件。 117 | 118 | **运行** 119 | 120 | 我们在 `tests/testcases_general` 文件夹中准备了一些通用案例。当需要对 `.cminus` 单个文件测试时,可以这样使用: 121 | 122 | #### 情况一:生成 IR 文件 123 | 124 | !!! note 125 | 126 | 为了让 cminusfc 在 `$PATH` 中,一定要 `sudo make install`。 127 | 128 | ```shell 129 | # 假设 cminusfc 的路径在你的 $PATH 中,并且你现在在 test.cminus 文件所在目录中 130 | $ cminusfc test.cminus -emit-llvm 131 | ``` 132 | 133 | 此时会在同目录下生成同名的 .ll 文件,在这里即为 `test.ll`。 134 | 135 | #### 情况二:生成可执行文件 136 | 137 | 上面生成的 .ll 文件用于阅读,如果需要运行,需要调用 clang 编译链接生成二进制文件 `test` 138 | 139 | ```shell 140 | $ clang -O0 -w -no-pie test.ll -o test -lcminus_io 141 | ``` 142 | 143 | !!! note 144 | 145 | 上面的命令编译了 `test.ll`,并链接了 `cminus_io` 库。 146 | 147 | **测试** 148 | 149 | 150 | 151 | 自动测试脚本和所有测试样例都是公开的,它在 `tests/2-ir-gen/autogen` 目录下,使用方法如下: 152 | 153 | ```sh 154 | # 在 tests/2-ir-gen/autogen 目录下运行: 155 | $ python3 ./eval_lab2.py 156 | $ cat eval_result 157 | ``` 158 | 159 | 测试结果会输出到 `tests/2-ir-gen/autogen/eval_result`。 160 | -------------------------------------------------------------------------------- /docs/lab2/index.md: -------------------------------------------------------------------------------- 1 | # Lab2 中间代码生成 2 | 3 | 本次实验需要同学们在 Lab1 实现的 Cminusf 解析器基础上,完成从语法树向中间代码的自动化翻译过程。 4 | 5 | ## 实验内容 6 | 7 | 本次实验需要分阶段完成及验收。 8 | 9 | ### 阶段一 10 | 11 | - 内容一: 12 | 阅读 [Light IR 预热](./warmup.md)并参考 [Light IR 简介](../common/LightIR.md),掌握手写 Light IR,使用 Light IR C++ 库生成 IR 的方法。 13 | - 内容二: 14 | 阅读[访问者模式](./visitor_pattern.md),理解 C++ 访问者模式的工作原理及遍历顺序。 15 | 16 | 阶段一需要**回答 [Light IR 预热](./warmup.md)与[访问者模式](./visitor_pattern.md)文档中的思考题**,回答内容保存为 `answer.pdf`。并完成 `tests/2-ir-gen/warmup/stu_ll` 与 `tests/2-ir-gen/warmup/stu_cpp` 目录下代码的编写。 17 | 18 | !!! warning "Deadline" 19 | 20 | **2023 年 10 月 14 日 23:59** 21 | 22 | ### 阶段二 23 | 24 | 25 | 26 | 阅读 [IR 自动化生成](./autogen.md),[Cminusf 语义](../common/cminusf.md#cminusf-的语义),补充 `include/cminusfc/cminusf_builder.hpp` 与 `src/cminusfc/cminusf_builder.cpp` 文件,并通过 `tests/2-ir-gen/autogen/testcases/`目录下 `lv0_1`, `lv0_2`, `lv1` 级别的测试样例。 27 | 28 | !!! warning "Deadline" 29 | 30 | **2023 年 10 月 21 日 23:59** 31 | 32 | ### 阶段三 33 | 34 | 在阶段二的基础上,继续补充 `include/cminusfc/cminusf_builder.hpp` 与 `src/cminusfc/cminusf_builder.cpp` 文件,并通过 `tests/2-ir-gen/autogen/testcases/` 目录下所有提供的测试样例。 35 | 36 | !!! warning "Deadline" 37 | 38 | **2023 年 10 月 28 日 23:59** 39 | 40 | ## 实验要求 41 | 42 | 43 | 44 | 请将[实验仓库](https://cscourse.ustc.edu.cn/vdir/Gitlab/compiler_staff/2023ustc-jianmu-compiler)设置为上游仓库,并获取本次实验更新的内容,具体步骤如下: 45 | 46 | 将上游仓库设置一个别名(alias),在这里我们用 `upstream`,你也可以改成其他你喜欢的名字。在你 fork 后的本地仓库中: 47 | 48 | ```shell 49 | $ git remote add upstream https://cscourse.ustc.edu.cn/vdir/Gitlab/compiler_staff/2023ustc-jianmu-compiler.git 50 | ``` 51 | 52 | 尝试将远程的更新拉取到本地并进行 merge 操作: 53 | 54 | ```shell 55 | $ git pull upstream master 56 | ``` 57 | 58 | 当 merge 过程出现冲突时,请参考 [Lab0](../lab0/git.md#上下游同步和冲突处理) 来合理的解决冲突。 59 | 60 | 最后你需要将更改同步到你 fork 得到的远程仓库中: 61 | 62 | ```shell 63 | $ git push origin master 64 | ``` 65 | 66 | ## 提交内容 67 | 68 | - 阶段一:在希冀平台提交你的 `answer.pdf` 文件,在希冀平台提交你实验仓库的 url(如 `https://cscourse.ustc.edu.cn/vdir/Gitlab/xxx/2023ustc-jianmu-compiler.git`)。 69 | - 阶段二、三:在希冀平台提交你实验仓库的 url(如 `https://cscourse.ustc.edu.cn/vdir/Gitlab/xxx/2023ustc-jianmu-compiler.git`)。 70 | -------------------------------------------------------------------------------- /docs/lab2/visitor_pattern.md: -------------------------------------------------------------------------------- 1 | # 访问者模式 2 | 3 | ### 简介 4 | 5 | Visitor Pattern(访问者模式)是一种在 LLVM 项目源码中被广泛使用的设计模式。在本实验中,指的是**语法树**类有一个方法接受**访问者**,将自身引用传入**访问者**,而**访问者**类中集成了根据语法树节点内容生成 IR 的规则,下面我们将通过例子来帮助大家理解 Visitor Pattern 的调用流程。 6 | 7 | !!! info 8 | 9 | 有关 Visitor Pattern 的含义、模式和特点,可参考 [维基百科](https://en.wikipedia.org/wiki/Visitor_pattern#C++_example)。 10 | 11 | ### 实验内容 12 | 13 | 14 | 15 | 在 `tests/2-ir-gen/warmup/calculator` 目录下提供了一个接受算术表达式,利用访问者模式,产生计算算数表达式的中间代码的程序,其中 `calc_ast.hpp` 定义了语法树的不同节点类型,`calc_builder.cpp` 实现了访问不同语法树节点 `visit` 函数。**阅读这两个文件和目录下的其它相关代码**,理解语法树是如何通过访问者模式被遍历的,并回答相应[思考题](./visitor_pattern.md#思考题)。 16 | 17 | ### 编译、运行 18 | 19 | **编译** 20 | 21 | ```shell 22 | $ cd 2023ustc-jianmu-compiler 23 | $ mkdir build 24 | $ cd build 25 | # 使用 cmake 生成 makefile 等文件 26 | $ cmake .. 27 | # 使用 make 进行编译 28 | $ make 29 | ``` 30 | 31 | 如果构建成功,你会在 `build` 文件夹下找到 calc 可执行文件 32 | 33 | **运行与测试** 34 | 35 | ```shell 36 | # 在 build 目录下操作 37 | $ ./calc 38 | Input an arithmatic expression (press Ctrl+D in a new line after you finish the expression): 39 | 4 * (8 + 4 - 1) / 2 40 | result and result.ll have been generated. 41 | $ ./result 42 | 22 43 | ``` 44 | 45 | 其中,`result.ll` 是 `calc` 产生的中间代码,`result` 是中间代码编译产生的二进制可执行文件,运行它就可以输出算数表达式的结果。 46 | 47 | ## 思考题 48 | 49 | 1. 分析 `calc` 程序在输入为 `4 * (8 + 4 - 1) / 2` 时的行为: 50 | 51 | 1. 请画出该表达式对应的抽象语法树(使用 `calc_ast.hpp` 定义的语法树节点来表示,并给出节点成员存储的值),并给节点使用数字编号。 52 | 2. 请给出示例代码在用访问者模式遍历该语法树时,访问者到达语法树节点的顺序。序列请按如下格式指明(序号为问题 1.a 中的编号):3->2->5->1->1 53 | -------------------------------------------------------------------------------- /docs/lab2/warmup.md: -------------------------------------------------------------------------------- 1 | # Light IR 预热 2 | 3 | 本阶段实验需要完成以下两部分 4 | 5 | 1. 阅读 [Light IR 简介](../common/LightIR.md) 了解 LLVM IR 与 Light IR 关系,认识 Light IR 的结构及指令语法。在 clang 翻译出的 IR 作为参考下,手动编写 IR 文件。 6 | 7 | 2. 了解并掌握 Light IR C++ 库的使用方法,通过调用 Light IR C++ 库提供的接口生成 IR 文件。 8 | 9 | ## 手工编写 IR 文件 10 | 11 | ### LLVM IR 介绍 12 | 13 | 根据[维基百科](https://zh.wikipedia.org/zh-cn/LLVM)的介绍,LLVM 是一个自由软件项目,它是一种编译器基础设施,以 C++ 写成,包含一系列模块化的编译器组件和工具链,用来开发编译器前端和后端。IR 的全称是 Intermediate Representation,即中间表示。LLVM IR 是 LLVM 项目定义的编译器中间代码。 14 | 15 | LLVM IR 指令参考手册:[Reference Manual](https://llvm.org/docs/LangRef.html) 16 | 17 | ### Light IR 介绍 18 | 19 | LLVM IR 的目标是成为一种通用 IR(支持包括动态与静态语言),因此 IR 指令种类较为复杂繁多。本课程从 LLVM IR 中裁剪出了适用于教学的精简的 IR 子集,并将其命名为 Light IR。 20 | 21 | Light IR 指令参考手册:[Light IR 手册](../common/LightIR.md#ir-%E6%A0%BC%E5%BC%8F) 22 | 23 | ### clang 生成 LLVM IR 24 | 25 | 26 | 27 | LLVM IR 文件以 `.ll` 为文件后缀,clang 是 LLVM 工具链中的前端可实现从 C 语言向 LLVM IR 的翻译,操作流程如下 28 | 29 | ```shell 30 | # 可用 clang 生成 C 代码对应的 .ll 文件 31 | $ clang -S -emit-llvm gcd_array.c 32 | # lli 可用来执行 .ll 文件 33 | $ lli gcd_array.ll 34 | # `$?` 的内容是上一条命令所返回的结果,而 `echo $?` 可以将其输出到终端中 35 | $ echo $? 36 | ``` 37 | 38 | [gcd_array.c](https://cscourse.ustc.edu.cn/vdir/Gitlab/compiler_staff/2023ustc-jianmu-compiler/-/blob/master/tests/2-ir-gen/warmup/ta_gcd/gcd_array.c)是实验提供的例子,学生可以通过使用 clang 翻译示例,并查阅 [Light IR 手册](../common/LightIR.md#lightir-指令)来理解每条 LLVM IR 指令含义。 39 | 40 | ### 实验内容 41 | 42 | 43 | 44 | 实验在 `tests/2-ir-gen/warmup/c_cases/` 目录下提供了四个 C 程序: `assign.c`、 `fun.c`、 `if.c` 和 `while.c`。学生需要在 `test/2-ir-gen/warmup/stu_ll` 目录中,手动使用 LLVM IR 将这四个 C 程序翻译成 IR 代码,得到 `assign_hand.ll`、`func_hand.ll`、`if_handf.ll` 和 `while_hand.ll`,可参考 `clang -S -emit-llvm` 的输出。 45 | 46 | ### 运行、测试 47 | 48 | **运行**:.ll 文件的运行与测试参考[运行 IR 文件](./warmup.md#clang 生成 LLVM IR) 49 | 50 | **测试**:lli 运行 `tests/2-ir-gen/warmup/stu_ll` 目录下四个.ll 文件,并通过检查返回值来判断 .ll 文件的正确性。 51 | 52 | ## 使用 Light IR C++ 库生成 IR 文件 53 | 54 | ### Light IR C++ 库介绍 55 | 56 | LLVM 项目提供了辅助 IR 生成的 C++ 库,但其类继承关系过于复杂,并且存在很多为了编译性能的额外设计,不利于学生理解 IR 抽象。因此实验依据 LLVM 的设计,为 Light IR 提供了配套简化的 C++ 库。与 LLVM IR C++ 库相比,Light IR C++ 库仅保留必要的核心类,简化了核心类的继承关系与成员设计,且给学生提供与 LLVM 相同的生成 IR 的接口。 57 | 58 | Light IR IR C++ 库参考手册:[Light IR cpp APIs](../common/LightIR.md#c-apis) 59 | 60 | ### 使用 Light IR C++ 库生成 IR 示例 61 | 62 | 阅读样例 [gcd_array.c](https://cscourse.ustc.edu.cn/vdir/Gitlab/compiler_staff/2023ustc-jianmu-compiler/-/blob/master/tests/2-ir-gen/warmup/ta_gcd/gcd_array.c), [gcd_array_generator.cpp](https://cscourse.ustc.edu.cn/vdir/Gitlab/compiler_staff/2023ustc-jianmu-compiler/-/blob/master/tests/2-ir-gen/warmup/ta_gcd/gcd_array_generator.cpp)。结合该样例的注释与 [Light IR C++ 库](../common/LightIR.md#)章节,掌握使用 Light IR C++ 库生成 IR 的方法 63 | 64 | ### 实验内容 65 | 66 | 实验在 `tests/2-ir-gen/warmup/c_cases/` 目录下提供了四个 C 程序。学生需要在 `tests/2-ir-gen/warmup/stu_cpp/` 目录中,参考上面提供的 `gcd_array_generator.cpp` 样例,使用 Light IR C++ 库,编写 `assign_generator.cpp`、`fun_generator.cpp`、`if_generator.cpp` 和 `while_generator.cpp` 四个 cpp 程序。这四个程序运行后应该能够生成 `tests/2-ir-gen/warmup/c_cases/` 目录下四个 C 程序对应的 .ll 文件。 67 | 68 | ### 编译、运行、测试 69 | 70 | !!! note 71 | 72 | 如果你在 `cmake ..` 一步遇到如下报错`Target "IR_lib" links to target "ZLIB::ZLIB" but the target was not found.`,请使用 `sudo apt install zlib1g-dev` 安装 zlib 库,然后重新 `cmake .. && make`。 73 | 74 | **编译** 75 | 76 | ```shell 77 | $ cd 2023ustc-jianmu-compiler 78 | $ mkdir build 79 | $ cd build 80 | # 使用 cmake 生成 makefile 等文件 81 | $ cmake .. 82 | # 使用 make 进行编译 83 | $ make 84 | ``` 85 | 86 | 如果构建成功,你会在 `build` 文件夹下找到 `gcd_array_generator`, `stu_assign_generator` 等可执行文件。 87 | 88 | **运行与测试** 89 | 90 | ```shell 91 | # 在 build 目录下操作 92 | $ ./gcd_array_generator > gcd_array_generator.ll 93 | $ lli gcd_array_generator.ll 94 | $ echo $? 95 | ``` 96 | 97 | 你可以通过观察原来的 C++ 代码来推断 `echo $?` 应该返回的正确结果,也可以通过 `clang -S -emit-llvm` 编译 `tests/2-ir-gen/warmup/c_cases` 目录下 4 个 CPP 文件获得 4 个相应的 .ll 文件,再用 `lli` 执行 .ll 文件来获取正确结果。 98 | 99 | ### 仓库目录结构 100 | 101 | 与 Light IR 预热实验相关文件如下 102 | 103 | ``` 104 | . 105 | ├── ... 106 | ├── include 107 | │ ├── common 108 | │ └── lightir/* 109 | └── tests 110 | ├── ... 111 | └── 2-ir-gen 112 | └── warmup 113 | ├── CMakeLists.txt 114 | ├── c_cases <- 需要翻译的 c 代码 115 | ├── calculator <- 助教编写的计算器示例 116 | ├── stu_cpp <- 学生需要编写的 .ll 代码手动生成器 117 | ├── stu_ll <- 学生需要手动编写的 .ll 代码 118 | └── ta_gcd <- 助教编写的 .ll 代码手动生成器示例 119 | ``` 120 | 121 | !!! warning 122 | 123 | 我们已经创建了 `stu_ll` 目录下的四个空文件,请不要修改文件名!否则希冀平台的测试将会失败。 124 | 125 | ## 思考题 126 | 127 | 1. 在 [Light IR 简介](../common/LightIR.md)里,你已经了解了 IR 代码的基本结构,请尝试编写一个有全局变量的 cminus 程序,并用 `clang` 编译生成中间代码,解释全局变量在其中的位置。 128 | 2. Light IR 中基本类型 label 在 Light IR C++ 库中是如何用类表示的? 129 | 3. Light IR C++ 库中 `Module` 类中对基本类型与组合类型存储的方式是一样的吗?请尝试解释组合类型使用其存储方式的原因。 130 | -------------------------------------------------------------------------------- /docs/lab3/environment.md: -------------------------------------------------------------------------------- 1 | # 后端环境配置 2 | 3 | 本次实验需要安装龙芯的交叉编译工具链。这篇文档将帮助你安装好相关工具。 4 | 5 | ## 交叉编译 6 | 7 | 我们的后端面向的是龙芯架构,而虚拟机和宿主机都是 x86 架构,不能直接运行龙芯程序。 8 | 9 | 所谓的交叉编译,就是在一个平台上生成另一个平台上的可执行代码。 10 | 11 | 具体到我们的实验,我们将在虚拟机上安装龙芯 gcc、qemu 和 gdb,使得我们可以在虚拟机中生成龙芯的可执行文件、模拟运行并且调试。 12 | 13 | ## 安装 14 | 15 | 助教准备了[龙芯交叉编译的相关工具](https://rec.ustc.edu.cn/share/d8c57580-669d-11ee-8794-d542ef642531),以便同学们在本地完成测试与调试。 16 | 17 | 具体来说有以下四项: 18 | 19 | - **loongarch64-clfs-3.0-cross-tools-gcc-glibc.tar.xz**:生成龙芯汇编、可执行文件等 20 | - **qemu-6.2.50.loongarch64.tar.gz**:模拟运行龙芯二进制文件 21 | - **gdb.tar.gz**:调试生成的龙芯程序 22 | 23 | - **test-env.tar.gz**:用于测试上述工具是否安装成功 24 | 25 | 点击上方链接,下载压缩包至虚拟机中。你可以根据自己的喜好选择存放的位置,比如 `~/Downloads`。 26 | 27 | ??? info "如何在宿主机和虚拟机之间传送文件?" 28 | 29 | 如果你在 Windows 系统上阅读本文档,你大概将网盘中的文件下载到了 Windows 系统(宿主机)中。 30 | 31 | 如何将文件传递进虚拟机中呢?在 Lab0 中你已经配置了宿主机与虚拟机之间的[ SSH 连接](../lab0/linux.md/#141-ssh),现在你可以通过 scp 命令进行文件传输: 32 | 33 | ```shell 34 | $ scp -P 端口号 源文件 目标文件 35 | ``` 36 | 例如将 `gdb.tar.gz` 传送到虚拟机的 `~/Downloads` 目录,具体步骤如下: 37 | 38 | 1. 在 Windows 文件资源管理器中找到下载好的文件 39 | 2. 在文件列表的空白处按住 Shift 点击鼠标右键,选择打开 PowerShell 40 | 3. 在弹出的命令行窗口中键入:`scp -P 主机端口 gdb.tar.gz 虚拟机用户名@127.0.0.1:~/Downloads` 41 | 42 | 现在进入虚拟机中你选择的目录,通过 `ls` 可以看到下载好的压缩包,执行以下解压命令,将交叉编译工具解压到 `/opt` 目录下: 43 | 44 | ```shell 45 | $ sudo tar xaf loongarch64-clfs-3.0-cross-tools-gcc-glibc.tar.xz -C /opt 46 | $ sudo tar xaf qemu-6.2.50.loongarch64.tar.gz -C /opt 47 | $ sudo tar xaf gdb.tar.gz -C /opt 48 | ``` 49 | 50 | 此时解压好的工具分别位于: 51 | 52 | - `/opt/cross-tools.gcc_glibc/bin/loongarch64-unknown-linux-gnu-gcc` 53 | - `/opt/qemu/bin/qemu-loongarch64` 54 | - `/opt/gdb/bin/loongarch64-unknown-linux-gnu-gdb` 55 | 56 | 需要使用绝对路径访问。为了使用方便和 **Lab3 的顺利测评**,我们将上述路径加入到`PATH`中: 57 | 58 | ```shell 59 | $ echo "export PATH=\$PATH:/opt/cross-tools.gcc_glibc/bin:/opt/gdb/bin:/opt/qemu/bin" >> ~/.bashrc && source ~/.bashrc 60 | ``` 61 | 62 | ## 测试 63 | 64 | 经过上述步骤,你可以使用以下三个命令简单检查是否安装成功: 65 | 66 | ??? info "检查龙芯 gcc 版本" 67 | 68 | ```shell 69 | $ loongarch64-unknown-linux-gnu-gcc -v 70 | Using built-in specs. 71 | COLLECT_GCC=loongarch64-unknown-linux-gnu-gcc 72 | COLLECT_LTO_WRAPPER=/opt/cross-tools.gcc_glibc/bin/../libexec/gcc/loongarch64-unknown-linux-gnu/12.0.1/lto-wrapper 73 | Target: loongarch64-unknown-linux-gnu 74 | Configured with: ../configure --prefix=/opt/cross-tools --build=x86_64-cross-linux-gnu --host=x86_64-cross-linux-gnu --target=loongarch64-unknown-linux-gnu --enable-__cxa_atexit --enable-threads=posix --enable-libstdcxx-time --enable-checking=release --with-sysroot=/opt/cross-tools/target --enable-default-pie --enable-languages=c,c++,fortran,objc,obj-c++,lto 75 | Thread model: posix 76 | Supported LTO compression algorithms: zlib zstd 77 | gcc version 12.0.1 20220210 (experimental) (GCC) 78 | ``` 79 | 80 | ??? info "检查龙芯 qemu 版本" 81 | 82 | ```shell 83 | $ qemu-loongarch64 -version 84 | qemu-loongarch64 version 6.2.50 (v6.0.0-7567-gac069a8ffb) 85 | Copyright (c) 2003-2022 Fabrice Bellard and the QEMU Project developers 86 | ``` 87 | 88 | ??? info "检查龙芯 gdb 版本" 89 | 90 | ```shell 91 | $ loongarch64-unknown-linux-gnu-gdb -v 92 | GNU gdb (GDB) 12.0.50.20210713-git 93 | Copyright (C) 2021 Free Software Foundation, Inc. 94 | License GPLv3+: GNU GPL version 3 or later 95 | This is free software: you are free to change and redistribute it. 96 | There is NO WARRANTY, to the extent permitted by law.``` 97 | ``` 98 | 99 | 更进一步,我们提供了环境测试程序。 100 | 101 | 进入虚拟机中存放压缩包的目录,解压压缩包 `test-env.tar.gz`: 102 | 103 | ```shell 104 | $ tar xaf test-env.tar.gz 105 | ``` 106 | 107 | 解压得到文件夹 `test-env`,其结构及说明如下: 108 | 109 | ```shell 110 | test-env 111 | ├── bubble-sort.S # 冒泡排序的汇编实现 112 | ├── hello-world.S # 输出 hello world 的汇编实现 113 | ├── inline-assembly.c # 内嵌汇编的 c 程序 114 | └── Makefile # 使用 make 自动测试 gcc 和 qemu 是否安装成功 115 | ``` 116 | 117 | ### 测试 gcc 与 qemu 118 | 119 | 进入上述 `test-env` 文件夹中,`make`,三段源程序将被编译成面向龙芯的可执行文件,并用 qemu 模拟运行。 120 | 121 |
122 | 期望输出 123 | loongarch64-unknown-linux-gnu-gcc -static hello-world.S -o hello-world
124 | loongarch64-unknown-linux-gnu-gcc -static bubble-sort.S -o bubble-sort
125 | loongarch64-unknown-linux-gnu-gcc -static inline-assembly.c -o inline-assembly
126 | loongarch64-unknown-linux-gnu-gcc -O2 -static inline-assembly.c -o inline-assembly-opt
127 | qemu-loongarch64 ./hello-world
128 | Hello World!
129 | qemu-loongarch64 ./bubble-sort
130 | 53461
131 | 13456
132 | qemu-loongarch64 ./inline-assembly
133 | ret_1 ret 123
134 | myadd_1 = 8
135 | myadd_2 = 8
136 | myadd_3 = 8
137 | a = 2, b = 3, c = 2
138 | a = 2, b = 3, c = 2
139 | test5 ok
140 | test6 expected error
141 | qemu-loongarch64 ./inline-assembly-opt
142 | ret_1 ret 123
143 | myadd_1 = 8
144 | myadd_2 = 8
145 | myadd_3 = 8
146 | a = 2, b = 2, c = 1
147 | a = 2, b = 3, c = 2
148 | test5 ok
149 | test6 expected error
150 |
151 | 如果得到以上输出,说明 gcc 和 qemu 被正确安装,你已经可以正常进行 Lab3 的评测。 152 | 153 | ### 测试 / 使用 gdb 154 | 155 | !!! warning "Lazy Reading" 156 | 157 | 这一小节是为了调试生成的汇编,你可以在需要调试时再回来查看。 158 | 159 | 我们接下来调试程序 `test-env/bubble-sort.S`,你现在应该去扫一眼这个文件,注意文件最后,`main` 函数如下: 160 | 161 | ```asm 162 | .global main 163 | main: 164 | bl display # 这是第一条汇编指令,率先被执行,表示将跳转进入 display 函数中 165 | bl sort 166 | bl display 167 | 168 | li.w $a7, 93 # exit 169 | li.w $a0, 0 170 | syscall 0x0 171 | ``` 172 | 173 | 在 [测试 gcc 与 qemu](#测试-gcc-与-qemu) 环节,已经利用龙芯 gcc 生成了可执行文件 `bubble-sort`,我们将读取这个文件进行调试。 174 | 175 | 龙芯程序运行在 qemu 上,首先需要启动 qemu,再用 gdb 连接。在 `test-env` 目录下: 176 | 177 | 1. 使用 qemu 模拟运行 `bubble-sort` 178 | 179 | ```shell 180 | $ qemu-loongarch64 -g 1234 bubble-sort & 181 | ``` 182 | 183 | 参数 `-g 1234` 表示不立即执行而是等待 gdb 连接端口 1234,结尾的 `&` 符号令 qemu 程序在后台运行。 184 | 185 | 2. 启动 gdb 并连接 qemu 186 | 187 | ```shell 188 | $ loongarch64-unknown-linux-gnu-gdb --quiet 189 | 190 | (gdb) 191 | ``` 192 | 193 | 我们使用 `--quiet` 参数避免 gdb 输出冗长的版本信息。出现提示符 `(gdb)`,表示我们处于 gdb 的命令行环境中。 194 | 195 | 指定可执行文件: 196 | 197 | ```shell 198 | (gdb) file bubble-sort 199 | Reading symbols from bubble-sort... 200 | ``` 201 | 202 | 连接 qemu: 203 | 204 | ```shell 205 | (gdb) target remote localhost:1234 206 | Remote debugging using localhost:1234 207 | warning: Can not parse XML target description; XML support was disabled at compile time 208 | _start () at ../sysdeps/loongarch/start.S:45 209 | 45 ../sysdeps/loongarch/start.S: No such file or directory. 210 | ``` 211 | 212 | 出现了一些 warning,对我们的调试没有影响,可以忽略之。 213 | 214 | 3. 现在程序停止在 `_start()` 处,我们让程序停在 `main` 函数的入口处: 215 | 216 | ```shell 217 | (gdb) break main 218 | Breakpoint 1 at 0x1200008f4 219 | (gdb) continue 220 | Continuing. 221 | 222 | Breakpoint 1, 0x00000001200008f4 in main () 223 | ``` 224 | 225 | 现在,程序停止在 `main` 函数入口处。 226 | 227 | 4. 将内存中的机器码程序反汇编出来: 228 | 229 | ```shell 230 | (gdb) disassemble 231 | Dump of assembler code for function main: 232 | => 0x00000001200008f4 <+0>: bl -204(0xfffff34) # 0x120000828 233 | 0x00000001200008f8 <+4>: bl -100(0xfffff9c) # 0x120000894 234 | 0x00000001200008fc <+8>: bl -212(0xfffff2c) # 0x120000828 235 | 0x0000000120000900 <+12>: ori $a7, $zero, 0x5d 236 | 0x0000000120000904 <+16>: move $a0, $zero 237 | 0x0000000120000908 <+20>: syscall 0x0 238 | End of assembler dump. 239 | ``` 240 | 241 | 在这里有很多有用的信息,比如你看到了 `main` 函数入口处的全部汇编指令,还有当前正在执行的指令地址是 `0x00000001200008f4`,这也是 `main` 函数的地址…… 242 | 243 | 通过对比,你会发现反汇编得到的指令与 `bubble-sort.S` 中的手写指令基本一致。 244 | 245 | 5. 查看当前寄存器状态: 246 | 247 | ```shell 248 | (gdb) info registers 249 | zero ra tp sp 250 | R0 0000000000000000 00000001200009c4 00000001200947e0 00000040007ffb00 251 | a0 a1 a2 a3 252 | R4 0000000000000001 00000040007ffc58 00000040007ffc68 000000012008c030 253 | a4 a5 a6 a7 254 | R8 0000000000000001 000000012008d060 00000040007ffc50 fffffffffffff000 255 | t0 t1 t2 t3 256 | R12 00000001200008f4 00000040007ffb20 0000000000000000 00000001200932b8 257 | t4 t5 t6 t7 258 | R16 000000012008ee00 000000012008ee00 636a6b6c7c6a2f5b ffffffffffffffff 259 | t8 x fp s0 260 | R20 fffffffffefef000 0000000000000000 0000000000000000 0000000120088da8 261 | s1 s2 s3 s4 262 | R24 0000000000000001 00000040007ffc58 0000000000000001 00000001200008f4 263 | s5 s6 s7 s8 264 | R28 00000040007ffc68 0000000000000001 0000000120000468 0000000000000000 265 | pc 0x1200008f4 0x1200008f4
266 | badvaddr 0x0 0x0 267 | ``` 268 | 269 | 你可以重点关注一下 pc 寄存器:其中的值与反汇编得到的 `main` 函数地址一致。 270 | 271 | 6. 当前程序停止在 `main` 函数入口处,即将执行的是指令 `bl display`,我们执行这条指令,将进入 `display` 函数: 272 | 273 | ```shell 274 | (gdb) stepi 275 | 0x0000000120000828 in display () 276 | ``` 277 | 278 | 更多的实用指令,请善用搜索引擎自助查询,我们也欢迎同学们在群内或者论坛中进行讨论。 279 | -------------------------------------------------------------------------------- /docs/lab3/figs/stack-frame.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustc-compiler-principles/2023/3413d0831014139229c3469a81fe541aad7cab81/docs/lab3/figs/stack-frame.png -------------------------------------------------------------------------------- /docs/lab3/framework.md: -------------------------------------------------------------------------------- 1 | # 后端框架介绍 2 | 3 | 本篇文档将介绍后端的代码框架。阅读前,你应该浏览下[这里的目录结构](./index.md#同步实验仓库),对框架的文件布局有总体的印象。 4 | 5 | ## 极简框架 6 | 7 | 本次实验的框架相对简单,你只需要少量的代码阅读就能快速进入代码撰写的环节。 8 | 9 | 顶层的 `Codegen` 类只维护了如下成员: 10 | 11 | ```cpp 12 | class CodeGen { 13 | // ... 14 | private: 15 | struct { ... } context; // 类似 lab2 的 context,用于保存翻译过程中的上下文信息,如当前所在函数 16 | Module *m; // 输入的 IR 模块 17 | std::list output; // 生成的汇编指令 18 | }; 19 | ``` 20 | 21 | 几个上层函数如下: 22 | 23 | ```cpp 24 | class CodeGen { 25 | // ... 26 | public: 27 | explicit CodeGen(Module *module) : m(module) {} // 构造函数 28 | std::string print() const; // 将汇编指令格式化输出 29 | void run(); // 后端代码生成的入口函数 30 | } 31 | ``` 32 | 33 | 你需要了解或者实现的是下面一系列函数: 34 | 35 | ```cpp 36 | class CodeGen { 37 | // ... 38 | private: 39 | // 栈式分配的变量分配环节,将在函数翻译开始时调用 40 | void allocate(); 41 | 42 | /*=== 以下为助教准备的辅助函数 ===*/ 43 | // 将数据在寄存器和栈帧间搬移。下边的章节将详细介绍 44 | void load_xxx(...); 45 | void store_xxx(...); 46 | // 添加汇编指令 47 | void append_inst(...); 48 | // 基本块在汇编程序中的名字 49 | static std::string label_name(BasicBlock *bb); 50 | /*=== 以上为助教准备的辅助函数 ===*/ 51 | 52 | // 需要补全的部分,进行代码生成的各个环节 53 | void gen_xxx(...); 54 | }; 55 | ``` 56 | 57 | 初始代码已经为你处理好了一些繁琐的细节,如全局变量的定义及初始化、汇编中 `section` `type` 的定义等,所以你可以把重点放在栈式分配的实现中。 58 | 59 | ## 基本类描述 60 | 61 | 框架对后端中的指令和寄存器进行了抽象,下面将依次介绍这两个基本类。 62 | 63 | ### 指令类 64 | 65 | 指令类 `ASMInstruction` 是用来描述一行汇编指令的,在 `CodeGen` 中以 `std::list` 形式组织。指令类的代码定义如下: 66 | 67 | ```cpp 68 | struct ASMInstruction { 69 | enum InstType { 70 | Instruction, // 汇编指令 71 | Atrribute, // 汇编伪指令、描述符等非常规指令 72 | Label, // 汇编中的 label 73 | Comment // 注释 74 | } type; // 用来描述指令的用途,会被下面的 format 函数使用 75 | 76 | std::string content; // 汇编代码,不包含换行符等格式化的信息 77 | 78 | explicit ASMInstruction(std::string s, InstType ty = Instruction); // 构造函数 79 | std::string format() const; // 根据 type 对 content 进行格式化(如添加缩进、换行符等) 80 | }; 81 | ``` 82 | 83 | 举个例子,`ASMInstruction("some debug info", ASMInstruction::Comment)` 定义了一个指令类实例,其用途是注释, `format()` 的返回结果是如下字符串:`"#some debug info\n"`。 84 | 85 | ### 寄存器类 86 | 87 | 寄存器分为通用寄存器 `Reg` 、浮点寄存器 `FReg` 和条件标志寄存器 `CFReg`。 88 | 89 | 以下是 `FReg` 的代码定义,`Reg` 与 `CFReg` 的定义与之类似: 90 | 91 | ```cpp 92 | struct FReg { 93 | unsigned id; // 0 <= id <= 31 94 | 95 | explicit FReg(unsigned i); 96 | bool operator==(const FReg &other); 97 | 98 | std::string print() const; // 根据 id 返回寄存器别名,如 "$fa0" 而不是 "$f0" 99 | 100 | static FReg fa(unsigned i); // 得到寄存器 $faN 101 | static FReg ft(unsigned i); // 得到寄存器 $ftN 102 | static FReg fs(unsigned i); // 得到寄存器 $fsN 103 | }; 104 | ``` 105 | 106 | 举例: 107 | 108 | - `FReg(0)` 定义了寄存器 `$f0` 的实例,`print()` 的结果是 `"$fa0"` 109 | - 为了获得 `$ft0` 的实例,你可以使用 `FReg(8)`,也可以使用更方便的`FReg::ft(0)` 110 | 111 | ## 辅助函数 112 | 113 | 在顶层的 `CodeGen` 类中,助教提供了很多辅助函数,主要是进行数据装载/写回的 load/store 相关和追加指令相关两大类,来帮助你更舒适地完成本次实验。 114 | 115 | ### load/store 116 | 117 | 阅读过[栈式分配介绍](stack_allocation.md)后,你将认同:我们生成的汇编中,load、store 的使用会非常频繁。 118 | 119 | 针对此,我们提供了如下函数,用于方便地提取数据至寄存器和将寄存器数据保存至栈上。 120 | 121 | ```cpp 122 | class CodeGen { 123 | //... 124 | private: 125 | // 向寄存器中装载数据 126 | void load_to_greg(Value *, const Reg &); // 将 IR 中的 Value 加载到整形寄存器中 127 | void load_to_freg(Value *, const FReg &); // 将 IR 中的 Value 加载到浮点寄存器中 128 | 129 | // 将寄存器中的数据保存回栈上 130 | void store_from_greg(Value *, const Reg &); // 将整形寄存器中的数据保存至 IR 中 Value 对应的栈帧位置 131 | void store_from_freg(Value *, const FReg &);// 将浮点寄存器中的数据保存至 IR 中 Value 对应的栈帧位置 132 | }; 133 | ``` 134 | 135 | 你只需要提供 IR 中的 `Value` 指针,同时指定目标寄存器,即可方便地完成数据的加载或备份。 136 | 137 | 事实上,寄存器的另一大数据来源,还有立即数的提取。对于 12bit 能够表示的整形立即数,你可以直接使用 `$dest = $zero + imm` 的形式,对于比较复杂的大立即数提取及浮点立即数提取,我们提供了一些辅助函数: 138 | 139 | ```cpp 140 | class CodeGen{ 141 | // ... 142 | private: 143 | // 向寄存器中加载立即数 144 | void load_large_int32(int32_t, const Reg &); // 提取 32 bit 的整数 145 | void load_large_int64(int64_t, const Reg &); // 提取 64 bit 的整数 146 | void load_float_imm(float, const FReg &); // 提取单精度浮点数(32bit) 147 | }; 148 | ``` 149 | 150 | 关于这些 API,虽然在这里给出了一些注释,它们看起来很好用,但是仍然不建议你开箱即用,希望你能简单阅读一下源码,理解其中在做什么后,再拿来使用。 151 | 152 | ### append_inst() 153 | 154 | `CodeGen` 中以 `std::list` 的形式组织 `ASMInstruction`,`append_inst()` 接口就是用来添加新的 `ASMInstruction`。 155 | 156 | 这里有两个版本的`append_inst()`: 157 | 158 | - 你可以直接按照 `ASMInstruction` 的构造函数添加指令,如: 159 | 160 | ```cpp 161 | append_inst("st.d $ra, $sp, -8", ASMInstruction::Instruction); 162 | // 第二个参数的默认值即为 ASMInstruction::Instruction,所以下边的代码等价 163 | append_inst("st.d $ra, $sp, -8"); 164 | ``` 165 | 166 | - 也可以使用二次封装后的版本: 167 | 168 | ```cpp 169 | append_inst("st.d", {"$ra", "$sp", "-8"}, ASMInstruction::Instruction); 170 | // 最后一个参数的默认值即为 ASMInstruction::Instruction,所以下边的代码等价 171 | append_inst("st.d", {"$ra", "$sp", "-8"}); 172 | ``` 173 | 174 | 这么看还没有什么区别,在 `include/codegen/CodeGenUtil.hpp` 中,我们定义了一系列宏定义,你可以去看看,使用这些宏定义,结合其他 API,上述调用会变得不太一样: 175 | 176 | ```cpp 177 | append_inst(STORE DOUBLE, {Reg::ra().print(), Reg::sp().print(), "-8"}); 178 | ``` 179 | 180 | ## 变量分配 181 | 182 | 关于变量分配的实验方案,我们已经在[栈式分配介绍](stack_allocation.md/#实验方案)中有过提及,初始代码已经为你实现,在 `src/codegen/CodeGen.cpp` 中的 `CodeGen::allocate()`。 183 | 184 | 你可以设计自己喜欢的 `allocate()`,但是这不被推荐,因为我们不能保证框架的其他部分(主要是辅助函数)和你的设计兼容。 185 | -------------------------------------------------------------------------------- /docs/lab3/guidance.md: -------------------------------------------------------------------------------- 1 | # Lab3 实验指导 2 | 3 | ## 阶段一:预热实验 4 | 5 | ### 仓库目录结构 6 | 7 | 与预热实验相关的文件如下: 8 | 9 | ``` 10 | . 11 | ├── ... 12 | ├── include 13 | │ ├── common 14 | │ └── codegen/* 15 | └── tests 16 | ├── ... 17 | └── 3-codegen 18 | └── warmup 19 | ├── CMakeLists.txt 20 | ├── ll_cases <- 需要翻译的 ll 代码 21 | └── stu_cpp <- 学生需要编写的汇编代码手动生成器 22 | ``` 23 | 24 | ### 实验内容 25 | 26 | 实验在 `tests/3-codegen/warmup/ll_cases/` 目录下提供了六个 `.ll` 文件。学生需要在 `tests/3-codegen/warmup/stu_cpp/` 目录中,依次完成 `assign_codegen.cpp`、`float_codegen.cpp`、`global_codegen.cpp`、`function_codegen.cpp`、`icmp_codegen.cpp` 和 `fcmp_codegen.cpp` 六个 C++ 程序中的 TODO。这六个程序运行后应该能够生成 `tests/3-codegen/warmup/ll_cases/` 目录下六个 `.ll` 文件对应的汇编程序。 27 | 28 | ### 编译 29 | 30 | ```shell 31 | $ cd 2023ustc-jianmu-compiler 32 | $ mkdir build 33 | $ cd build 34 | # 使用 cmake 生成 makefile 等文件 35 | $ cmake .. 36 | # 使用 make 进行编译 37 | $ make 38 | ``` 39 | 40 | 如果构建成功,你会在 `build` 文件夹下找到 `stu_assign_codegen`, `stu_float_codegen` 等可执行文件。 41 | 42 | ### 运行与测试 43 | 44 | !!! note 45 | 46 | 在运行和测试之前,你应该确保你完成了[后端环境配置](./environment.md)。 47 | 48 | ```shell 49 | # 在 build 目录下操作 50 | $ ./stu_assign_codegen > assign.s 51 | $ loongarch64-unknown-linux-gnu-gcc -static assign.s -o assign 52 | $ qemu-loongarch64 ./assign 53 | $ echo $? 54 | ``` 55 | 56 | 你可以通过观察原来的 `.ll` 代码来推断 `echo $?` 应该返回的正确结果,也可以直接使用 `lli` 执行 `.ll` 文件来获取正确结果。 57 | 58 | !!! note 59 | 60 | 请注意,`echo $?` 返回的值是 qemu 执行完毕后的退出状态,也就是你的程序的返回值。由于 Linux 系统的限制,这个返回值只包含低 8 位,即范围在 0 到 255 之间。如果你的程序返回值超过了这个范围,那么 `echo $?` 将只显示该返回值的低 8 位。 61 | 62 | ## 阶段二:编译器后端 63 | 64 | ### 仓库目录结构 65 | 66 | 与实验第二阶段相关的文件如下: 67 | 68 | ``` 69 | . 70 | ├── include 71 | │ └── codegen/* # 相关头文件 72 | ├── src 73 | │ └── codegen 74 | │ └── CodeGen.cpp <-- 学生需要补全的文件 75 | └── tests 76 | ├── 3-codegen 77 | │   └── autogen 78 | │      ├── eval_lab3.sh <-- 测评脚本 79 | │ └── testcases <-- lab3 第二阶段的测例目录一 80 | └── testcases_general <-- lab3 第二阶段的测例目录二 81 | ``` 82 | 83 | ### 实验内容 84 | 85 | 补全 `src/codegen/CodeGen.cpp` 中的 TODO,并按需修改 `include/codegen/CodeGen.hpp` 等文件,使编译器能够生成正确的汇编代码。 86 | 87 | !!! info "实现约定" 88 | 89 | 出于简化实验的目的,我们只考核最核心的功能点,对如下情况不做要求: 90 | 91 | - phi 指令:本实验不考核对于 phi 指令的处理 92 | 93 | - 函数调用传参:本次实验只考核使用寄存器传参的情况,即对于超多参数的栈上传参不做要求 94 | 95 | 此外,存在部分 `void main() {...}` 的样例,其返回值是未定义的,我们要求这样的主函数通过寄存器 `$a0` 返回 0,以顺利进行测评。 96 | 97 | ### 编译 98 | 99 | ```shell 100 | $ cd 2023ustc-jianmu-compiler 101 | $ mkdir build 102 | $ cd build 103 | # 使用 cmake 生成 makefile 等文件 104 | $ cmake .. 105 | # 使用 make 进行编译 106 | $ make 107 | # 安装以链接 libcminus_io.a 108 | $ sudo make install 109 | ``` 110 | 111 | 如果构建成功,你会在 `build` 文件夹下找到 cminusfc 可执行文件,它能将 `.cminus` 文件输出为龙芯汇编文件。 112 | 113 | !!! note 114 | 115 | 为了让 cminusfc 在 `$PATH` 中,一定要 `sudo make install`。 116 | 117 | ### 运行 118 | 119 | 完成第二阶段,编译器能够将 `.cminus` 文件翻译成正确的龙芯汇编 `.s` 文件: 120 | 121 | ```shell 122 | # 假设 cminusfc 的路径在你的 $PATH 中,并且你现在在 test.cminus 文件所在目录中 123 | $ cminusfc test.cminus -S 124 | ``` 125 | 126 | 此时会在同目录下生成同名的汇编文件,在这里即为 `test.s`。 127 | 128 | 为了运行得到的汇编文件,需要调用龙芯交叉编译器编译链接生成二进制文件 `test`: 129 | 130 | ```shell 131 | # 假设你位于 2023ustc-jianmu-compiler 目录, 否则你应该修改下面 src/io/io.c 到具体的路径 132 | $ loongarch64-unknown-linux-gnu-gcc -static test.s src/io/io.c -o test 133 | ``` 134 | 135 | !!! note 136 | 137 | 上面的命令编译了 `test.s`,并链接了 `io.c` 中的函数。 138 | 139 | 然后你可以使用 `qemu-loongarch64` 运行二进制文件 `test` 140 | 141 | ```shell 142 | $ qemu-loongarch64 ./test 143 | ``` 144 | 145 | ### 测试 146 | 147 | 在 `tests/3-codegen/autogen` 目录下,使用我们提供的 `eval_lab3.sh` 进行本地评测,其接受两个**参数**: 148 | 149 | 1. 测试样例目录:考核的两个测例目录分别是 `./testcases` 和 `../../testcases_general` 150 | 1. 评测模式:`test` 和 `debug` 两种模式都能正常测评,后者会额外生成 `.ll` 文件 151 | 152 | 脚本运行后,会留下如下文件/目录来辅助你 **debug**: 153 | 154 | - `output/` 目录:保存了评测中编译器产生的文件,如汇编文件、IR 文件和二进制文件等 155 | - `log.txt` 文件:该文件收录了编译器报错信息及评测的比较信息 (`diff` 的结果) 156 | 157 | 出现错误时请查阅相关文件。 158 | 159 | 在结束测评后,你可以使用 `./cleanup.sh` **清空**上述文件/目录,以保持仓库的清爽。 160 | 161 | 以下是评测脚本的**使用示例**: 162 | 163 | 以 `./testcases` 为测例目录,使用评测脚本 `test` 模式测试(我们使用正确的实现,但是故意在 `13-complex.cminus` 中制造了语法错误): 164 | 165 | ```shell 166 | $ ./eval_lab3.sh ./testcases test 167 | [info] Start testing, using testcase dir: ./testcases 168 | 0-io...OK 169 | 1-return...OK 170 | 2-calculate...OK 171 | 3-output...OK 172 | 4-if...OK 173 | 5-while...OK 174 | 6-array...OK 175 | 7-function...OK 176 | 8-store...OK 177 | 9-fibonacci...OK 178 | 10-float...OK 179 | 11-floatcall...OK 180 | 12-global...OK 181 | 13-complex..../eval_lab3.sh: line 65: 40958 Segmentation fault (core dumped) bash -c "cminusfc -S $case -o $asm_file" >> $LOG 2>&1 182 | CE: cminusfc compiler error 183 | ``` 184 | 185 | 从脚本输出中观察到样例 `13-complex` 没有通过,原因为 `CE: cminusfc compiler error`,表示 `cminusfc` 编译过程报错,打开 `log.txt` 检查报错信息: 186 | 187 | ``` 188 | ==========./testcases/0-io.cminus========== 189 | 1234 1234 190 | 0 0 191 | ...省略多行中间输出 192 | ==========./testcases/13-complex.cminus========== 193 | error at line 7 column 5: syntax error 194 | ``` 195 | 196 | 根据输出信息,我们了解到 `cminusfc` 检测到语法错误而退出,具体为 `13-complex.cminus` 中第 7 行第 5 列。 197 | 198 | 直接使用初始代码尝试评测,每个样例都会报错 `CE: cminusfc compiler error`。 199 | 200 | ??? detail "使用初始代码评测后的 log 文件" 201 | 202 | ``` 203 | ==========./testcases/0-io.cminus========== 204 | terminate called after throwing an instance of 'not_implemented_error' 205 | what(): gen_call 206 | ==========./testcases/1-return.cminus========== 207 | terminate called after throwing an instance of 'not_implemented_error' 208 | what(): gen_call 209 | ==========./testcases/2-calculate.cminus========== 210 | terminate called after throwing an instance of 'not_implemented_error' 211 | what(): gen_alloca 212 | ==========./testcases/3-output.cminus========== 213 | terminate called after throwing an instance of 'not_implemented_error' 214 | what(): gen_call 215 | ==========./testcases/4-if.cminus========== 216 | terminate called after throwing an instance of 'not_implemented_error' 217 | what(): gen_alloca 218 | ==========./testcases/5-while.cminus========== 219 | terminate called after throwing an instance of 'not_implemented_error' 220 | what(): gen_alloca 221 | ==========./testcases/6-array.cminus========== 222 | terminate called after throwing an instance of 'not_implemented_error' 223 | what(): gen_alloca 224 | ==========./testcases/7-function.cminus========== 225 | terminate called after throwing an instance of 'not_implemented_error' 226 | what(): gen_alloca 227 | ==========./testcases/8-store.cminus========== 228 | terminate called after throwing an instance of 'not_implemented_error' 229 | what(): gen_alloca 230 | ==========./testcases/9-fibonacci.cminus========== 231 | terminate called after throwing an instance of 'not_implemented_error' 232 | what(): gen_alloca 233 | ==========./testcases/10-float.cminus========== 234 | terminate called after throwing an instance of 'not_implemented_error' 235 | what(): gen_alloca 236 | ==========./testcases/11-floatcall.cminus========== 237 | terminate called after throwing an instance of 'not_implemented_error' 238 | what(): gen_alloca 239 | ==========./testcases/12-global.cminus========== 240 | terminate called after throwing an instance of 'not_implemented_error' 241 | what(): gen_load 242 | ==========./testcases/13-complex.cminus========== 243 | terminate called after throwing an instance of 'not_implemented_error' 244 | what(): gen_alloca 245 | ``` 246 | -------------------------------------------------------------------------------- /docs/lab3/index.md: -------------------------------------------------------------------------------- 1 | # Lab3 后端代码生成 2 | 3 | 经过 Lab1 和 Lab2,我们的编译器能够将 Cminusf 源代码翻译成 Light IR。本次实验要求同学们将 IR 翻译成龙芯汇编指令。 4 | 5 | ## 同步实验仓库 6 | 7 | 在进行实验之前,首先拉取[实验仓库](https://cscourse.ustc.edu.cn/vdir/Gitlab/compiler_staff/2023ustc-jianmu-compiler)的最新代码,具体步骤可以参考 [Lab2 中的指导](../lab2/index.md#实验要求)。 8 | 9 | 本次实验仓库更新的内容如下,每个阶段的文件将在对应文档详细说明: 10 | 11 | ``` 12 | . 13 | ├── ... 14 | ├── include 15 | │ ├── ... 16 | │ └── codegen 17 | │ ├── ASMInstruction.hpp # 描述汇编指令 18 | │ ├── CodeGen.hpp # 后端框架顶层设计 19 | │ ├── CodeGenUtil.hpp # 一些辅助函数及宏的定义 20 | │ └── Register.hpp # 描述寄存器 21 | ├── src 22 | │ ├── ... 23 | │ └── codegen 24 | │ ├── CMakeLists.txt 25 | │ ├── CodeGen.cpp <-- lab3 第二阶段需要修改的文件 26 | │ └── Register.cpp 27 | └── tests 28 | ├── ... 29 | └── 3-codegen 30 |    ├── warmup <-- lab3 第一阶段(代码撰写) 31 |    └── autogen <-- lab3 第二阶段的测试 32 | ``` 33 | 34 | ## 实验内容 35 | 36 | 37 | 38 | ### 阶段一 39 | 40 | 此为预热实验,主要引导同学完成环境的配置并学习相关知识。 41 | 42 | 1. 阅读[后端环境配置](./environment.md)章节,完成龙芯交叉编译工具链的安装。 43 | 2. 阅读[龙芯汇编介绍](../common/asm_intro.md)章节,了解龙芯汇编的基础知识。 44 | 3. 阅读[后端框架介绍](framework.md)中的[指令类介绍](framework.md/#指令类)和[辅助函数(添加指令)介绍](framework.md/#append_inst),了解如何使用提供的 C++ 接口生成汇编指令。 45 | 4. 阅读 [Lab3 实验指导的预热实验](./guidance.md#阶段一预热实验)章节,了解实验内容,进行补全汇编的实验并测试。 46 | 47 | !!! warning "Deadline" 48 | 49 | **2023 年 11 月 10 日 23:59** 50 | 51 | ### 阶段二 52 | 53 | 来到本次实验的核心:实现编译器后端,即根据 IR 翻译得到龙芯汇编指令。 54 | 55 | 一个典型的编译器后端从中间代码获取信息,进行活跃变量分析、寄存器分配、指令选择、指令优化等一系列流程,最终生成高质量的后端代码。 56 | 57 | 本次实验,我们将这些复杂的流程简化,仅追求实现的完整性,要求同学们采用栈式分配的策略,完成后端代码生成。 58 | 59 | 1. 阅读[栈式分配介绍](stack_allocation.md)章节,了解其思想。 60 | 2. 阅读[后端框架介绍](framework.md)章节,了解实验代码框架设计。 61 | 3. 阅读 [Lab3 实验指导中的阶段二](./guidance.md/#阶段二编译器后端)章节,了解实验细节,完成本次实验。 62 | 63 | !!! warning "Deadline" 64 | 65 | **2023 年 11 月 24 日 23:59** 66 | 67 | ## 提交内容 68 | 69 | 阶段一、二:在希冀平台提交你实验仓库的 url(如 `https://cscourse.ustc.edu.cn/vdir/Gitlab/xxx/2023ustc-jianmu-compiler.git`)。 70 | 71 | 在提交之前,请确保你 fork 得到的远程仓库与本地同步: 72 | 73 | ```shell 74 | $ git push origin master 75 | ``` 76 | -------------------------------------------------------------------------------- /docs/lab3/stack_allocation.md: -------------------------------------------------------------------------------- 1 | # 栈式分配介绍 2 | 3 | 本篇文档介绍后端代码生成中栈式分配的核心思想及实验方案,我们假设你已经阅读了[龙芯汇编介绍](../common/asm_intro.md)章节,并基本了解了龙芯的寄存器、汇编指令、调用规范等。 4 | 5 | ## 核心思想 6 | 7 | 栈式分配,是指**程序的所有变量都保存在栈上**,只在参与计算时提取到寄存器中。 8 | 9 | 在栈式分配策略下,编译器后端主要分为两个步骤: 10 | 11 | 1. **变量分配**:为程序中的每个变量分配一个栈帧位置,即相对栈指针的一个偏移量 12 | 13 | 2. **指令选择**:对于指令 $u= v_1 \ op\ v_2$ 14 | 1. 将两个操作数($v_1$ 和 $v_2$)对应的数据分别从栈上提取到寄存器中(load) 15 | 2. 根据指令类型选择合适的汇编指令(可能有多条) 16 | 3. 将计算结果存储到栈上(store) 17 | 18 | 而在寄存器分配策略下,变量活跃期间数据都保留在各自的寄存器中,不需要 2.a 的 load 和 2.c 的 store。所以性能上,栈式分配不如寄存器分配,而在实现难度上,栈式分配要简单许多。 19 | 20 | ## 实验方案 21 | 22 | 关于变量分配,我们需要为函数中每个变量分配一段栈空间,这个并没有固定的标准,我们提供并已经为你实现好的方案如下: 23 | 24 | - 记录每个变量相对于栈底 `$fp` 的偏移,由于栈从高向低生长,所以这个偏移量为负数 25 | - 固定备份两个寄存器:`$ra` 和 `$fp` 26 | - 备份函数参数 27 | - 为每个存在定值的指令分配相应的空间 28 | - 对于 `alloca` 指令,`alloca` 本身的定值为指针类型,`alloca` 的空间紧挨着这个这个指针,在更靠近栈顶的位置 29 | 30 | 即我们的栈帧结构如下: 31 | 32 | ![](figs/stack-frame.png) 33 | 34 | !!! note "注意" 35 | 36 | 为变量分配空间,实际上就是移动栈指针,这里注意 `$sp` 和 `$fp` 需要**对齐到 16 字节**,详见[汇编介绍中的栈帧布局章节](../common/asm_intro.md/#栈帧布局)。 37 | 38 | !!! note "代码" 39 | 40 | 查阅框架中的代码实现:`src/codegen/CodeGen.cpp` 中 `CodeGen::allocate()` 41 | 42 | ## 举个例子 43 | 44 | 例如如下的 Cminusf 程序: 45 | 46 | ```c 47 | int main(void) { 48 | int a; 49 | a = 1; 50 | return a; 51 | } 52 | ``` 53 | 54 | 生成的 IR 文件(部分)如下: 55 | 56 | ```c 57 | define i32 @main() { 58 | label_entry: 59 | %op0 = alloca i32 60 | store i32 1, i32* %op0 61 | %op1 = load i32, i32* %op0 62 | ret i32 %op1 63 | } 64 | ``` 65 | 66 | 在栈式分配策略下: 67 | 68 | 1. 变量分配:为 `alloca` 指令分配一个 `i32` 类型的空间,为 `%op0` 和 `%op1` 分配栈帧空间。算上备份的寄存器,汇编代码中 `main` 函数的栈帧如下: 69 | 70 | | 栈帧内容 | 宽度 (byte) | 栈帧位置(相对 `$fp` 的偏移) | 71 | | :----------------------: | :---------: | :---------------------------: | 72 | | `$ra` | 8 | -8 | 73 | | `$fp` | 8 | -16 | 74 | | `i32* %op0` | 8 | -24 | 75 | | `alloca` 出的 `i32` 空间 | 4 | -28 | 76 | | `i32 %op1` | 4 | -32 | 77 | 78 | 2. 指令选择:以 IR 中第 5 行的 `%op1 = load i32, i32* %op0` 为例,首先需要将 `%op0` 的值从栈帧加载到寄存器中,然后使用汇编指令 `ld.w` 读取内存,结果即为 `%op1` 的值,最后将其保存回 `%op1` 所在的栈帧位置。 79 | 80 | 得到的汇编指令如下(建议你读懂数字的来由): 81 | 82 | ```asm 83 | .text 84 | .globl main 85 | .type main, @function 86 | main: 87 | # prologue 88 | st.d $ra, $sp, -8 89 | st.d $fp, $sp, -16 90 | addi.d $fp, $sp, 0 91 | addi.d $sp, $sp, -32 92 | .main_label_entry: 93 | # %op0 = alloca i32 94 | addi.d $t0, $fp, -28 95 | st.d $t0, $fp, -24 96 | # store i32 1, i32* %op0 97 | ld.d $t0, $fp, -24 98 | addi.w $t1, $zero, 1 99 | st.w $t1, $t0, 0 100 | # %op1 = load i32, i32* %op0 <-- 刚刚详细介绍过的指令 101 | ld.d $t0, $fp, -24 102 | ld.w $t0, $t0, 0 103 | st.w $t0, $fp, -32 104 | # ret i32 %op1 105 | ld.w $a0, $fp, -32 106 | b main_exit 107 | main_exit: 108 | # epilogue 109 | addi.d $sp, $sp, 32 110 | ld.d $ra, $sp, -8 111 | ld.d $fp, $sp, -16 112 | jr $ra 113 | ``` 114 | -------------------------------------------------------------------------------- /docs/lab4/Dom.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustc-compiler-principles/2023/3413d0831014139229c3469a81fe541aad7cab81/docs/lab4/Dom.pdf -------------------------------------------------------------------------------- /docs/lab4/Mem2Reg介绍.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustc-compiler-principles/2023/3413d0831014139229c3469a81fe541aad7cab81/docs/lab4/Mem2Reg介绍.pdf -------------------------------------------------------------------------------- /docs/lab4/figs/cmp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustc-compiler-principles/2023/3413d0831014139229c3469a81fe541aad7cab81/docs/lab4/figs/cmp.png -------------------------------------------------------------------------------- /docs/lab4/figs/copy-stmt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustc-compiler-principles/2023/3413d0831014139229c3469a81fe541aad7cab81/docs/lab4/figs/copy-stmt.png -------------------------------------------------------------------------------- /docs/lab4/index.md: -------------------------------------------------------------------------------- 1 | # Lab4 Mem2Reg 2 | 3 | 经过前序实验,同学们已经基本完成了一个贯穿从前端到后端的简单编译器,祝贺大家!然而,该编译器生成的代码只能保证语义正确、能在目标机器上运行。接下来,我们将让大家体验如何通过增加优化 pass 让生成的代码快起来。正如课上所讲,优化的方法有很多,由于时间关系,我们不能一一尝试。因此,我们为大家准备了 Mem2Reg 优化实验,在该实验中,同学们将实现这一优化 pass。完成代码后,同学们可以在测试样例中,看到优化前后的性能差距。 4 | 5 | !!! warning "Deadline" 6 | 7 | **2023 年 12 月 18 日 23:59** 8 | 9 | ## 同步实验仓库 10 | 11 | 在进行实验之前,首先拉取[实验仓库](https://cscourse.ustc.edu.cn/vdir/Gitlab/compiler_staff/2023ustc-jianmu-compiler)的最新代码,具体步骤可以参考 [Lab2 中的指导](../lab2/index.md#实验要求)。 12 | 13 | 本次实验仓库更新的内容如下,每个阶段的文件将在对应文档详细说明: 14 | 15 | ``` 16 | . 17 | ├── ... 18 | ├── include 19 | │ ├── ... 20 | │   └── passes 21 | | ├── PassManager.hpp # PassManager:管理 pass 的运行 22 | │   ├── FuncInfo.hpp # pass 1:纯函数分析 23 | │   ├── DeadCode.hpp # pass 2:死代码删除 24 | │   ├── Dominators.hpp # pass 3:支配树分析(需要阅读,根据需要修改) 25 | │   └── Mem2Reg.hpp # pass 4:Mem2Reg 分析(需要阅读,根据需要修改) 26 | ├── src 27 | │ ├── ... 28 | │   └── passes 29 | │      ├── ... 30 | │      ├── Dominators.cpp <-- 支配树分析实现,需要补全 31 | │      └── Mem2Reg.cpp <-- Mem2Reg 实现,需要补全 32 | └── tests 33 | ├── ... 34 | └── 4-mem2reg # Lab4 的本地测试 35 | ``` 36 | 37 | ## 实验内容 38 | 39 | ### 阅读与学习 40 | 41 | - 回顾课上关于支配树的介绍,并阅读 [Mem2Reg 介绍](./Mem2Reg介绍.pdf),了解 Mem2Reg 的基本原理 42 | 43 | > 支配树的相关算法伪代码可以参考如下文章:[Dom.pdf](Dom.pdf)。注意助教在其中的的标柱。 44 | 45 | - 阅读 PassManager、FuncInfo 和 DeadCode 的实现,了解如何编写 pass 46 | 47 | ### 代码撰写 48 | 49 | 1. 补全 `src/passes/Dominators.cpp` 文件,使编译器能够进行正确的支配树分析 50 | 2. 补全 `src/passes/Mem2Reg.cpp` 文件,使编译器能够正确执行 Mem2Reg 51 | 3. 将 phi 指令转化为 copy statement,令后端可以正确处理 phi 指令 52 | 53 | !!! info "关于 copy statement" 54 | 55 | **什么是 copy statement?** 56 | 57 | 在进行后端翻译时,我们根据 phi 节点的语义,将其转化为前驱块的拷贝操作,如下图所示。 58 | 59 | ![](figs/copy-stmt.png) 60 | 61 | **这样做正确吗?** 62 | 63 | 这种 naive 的方案并不完全正确,在个别极端情况下,它会带来 Lost Of Copy 等问题,但是在本次实验中不会出现,所以你可以放心采用这个方案。 64 | 65 | ## 本地测试 66 | 67 | ### 测试脚本 68 | 69 | `tests/4-mem2reg` 目录的结构如下: 70 | 71 | ``` 72 | . 73 | ├── functional-cases # 功能测试样例 74 | ├── performance-cases # 性能测试样例 75 | ├── cleanup.sh 76 | ├── eval_lab4.sh # lab4 评测脚本 77 | └── test_perf.sh # 性能比较脚本 78 | ``` 79 | 80 | 其中本地测评脚本 `eval_lab4.sh` 与 Lab3 一致,使用方法可以回顾 [Lab3 测试](../lab3/guidance.md#测试),要求通过的测例目录: 81 | 82 | - `tests/testcases_general` 83 | - `tests/4-mem2reg/functional-cases` 84 | 85 | 此外,为了让你能够体会 Mem2Reg 的效果,我们还提供了 3 个性能测试样例,在 `performance-cases` 中。你可以使用脚本 `test_perf.sh` 来进行性能比较,使用示例如下所示。 86 | 87 | ??? info "`test_perf.sh` 使用示例" 88 | 89 | ```shell 90 | $ ./test_perf.sh 91 | [info] Start testing, using testcase dir: ./performance-cases 92 | ==========./performance-cases/const-prop.cminus========== 93 | ==========mem2reg off 94 | 95 | real 0m13.052s 96 | user 0m13.014s 97 | sys 0m0.009s 98 | ==========mem2reg on 99 | 100 | real 0m11.929s 101 | user 0m11.905s 102 | sys 0m0.007s 103 | ==========./performance-cases/loop.cminus========== 104 | ==========mem2reg off 105 | 106 | real 0m7.129s 107 | user 0m7.117s 108 | sys 0m0.007s 109 | ==========mem2reg on 110 | 111 | real 0m5.112s 112 | user 0m5.110s 113 | sys 0m0.000s 114 | ==========./performance-cases/transpose.cminus========== 115 | ==========mem2reg off 116 | 117 | real 0m15.186s 118 | user 0m15.171s 119 | sys 0m0.003s 120 | ==========mem2reg on 121 | 122 | real 0m10.473s 123 | user 0m10.440s 124 | sys 0m0.007s 125 | ``` 126 | 127 | ### IR CFG 128 | 129 | 在实现支配树时,为了方便同学们测试支配树的正确性,本节将向你介绍两个工具:[opt](https://llvm.org/docs/CommandGuide/opt.html) 和 [dot](https://manpages.ubuntu.com/manpages/trusty/man1/dot.1.html)。opt 和 dot 配合使用可以将 IR 文件转换为 CFG 图片,将基本块之间的关系可视化,利用可视化的 CFG,可以判断生成的支配树是否正确。 130 | 131 | 在你的机器上,opt 已经随 llvm 一起安装,使用以下命令安装 dot: 132 | 133 | ``` 134 | $ sudo apt install graphviz 135 | ``` 136 | 137 | 以如下 `test.ll` 文件为例: 138 | 139 | ??? info "test.ll" 140 | 141 | ```c 142 | define i32 @cmp(i32 %arg0, i32 %arg1) { 143 | label_entry: 144 | %op2 = icmp slt i32 %arg0, %arg1 145 | %op3 = zext i1 %op2 to i32 146 | %op4 = icmp ne i32 %op3, 0 147 | br i1 %op4, label %label5, label %label7 148 | label5: ; preds = %label_entry 149 | ret i32 1 150 | label6: 151 | ret i32 0 152 | label7: ; preds = %label_entry 153 | ret i32 0 154 | } 155 | 156 | define i32 @main() { 157 | label_entry: 158 | %op0 = call i32 @cmp(i32 1, i32 2) 159 | ret i32 %op0 160 | } 161 | ``` 162 | 163 | 在 `test.ll` 的同级目录下: 164 | 165 | ```shell 166 | $ opt -passes=dot-cfg test.ll >/dev/null 167 | Writing '.cmp.dot'... 168 | Writing '.main.dot'... 169 | ``` 170 | 171 | 可以看到 opt 输出了两个 dot 文件,分别与 ll 中的两个函数对应。然后我们使用 dot 工具将其转化为图片: 172 | 173 | ```shel 174 | $ dot .main.dot -Tpng > main.png 175 | $ dot .cmp.dot -Tpng > cmp.png 176 | ``` 177 | 178 | 比如得到的 `cmp.png` 如下: 179 | 180 | ![](figs/cmp.png) 181 | 182 | ## 编译与运行 183 | 184 | 按照如下示例进行项目编译: 185 | 186 | ```shell 187 | $ cd 2023ustc-jianmu-compiler 188 | $ mkdir build 189 | $ cd build 190 | # 使用 cmake 生成 makefile 等文件 191 | $ cmake .. 192 | # 使用 make 进行编译,指定 install 以正确测试 193 | $ sudo make install 194 | ``` 195 | 196 | 现在你可以 `-mem2reg` 使用来指定开启 Mem2Reg 优化: 197 | 198 | - 将 `test.cminus` 编译到 IR:`cminusfc -emit-llvm -mem2reg test.cminus` 199 | - 将 `test.cminus` 编译到汇编:`cminusfc -S -mem2reg test.cminus` 200 | 201 | ## 提交方式 202 | 203 | - 在希冀平台提交实验仓库的 URL 204 | 205 | > 在提交之前,请确保你 fork 得到的远程仓库与本地同步:`git push origin master` 206 | 207 | - 在希冀平台提交实验报告(实现方法、正确性验证、性能验证等) 208 | -------------------------------------------------------------------------------- /docs/建木杯-编译原理创新实验/建木杯-扩展实验-v2.md: -------------------------------------------------------------------------------- 1 | # 重定向中,请稍后... 2 | 3 | 7 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: USTC 编译原理和技术 2023 2 | repo_name: ustc-compiler-principles/2023 3 | repo_url: https://github.com/ustc-compiler-principles/2023 4 | theme: 5 | name: 'material' 6 | palette: 7 | primary: 'black' 8 | language: 'zh' 9 | features: 10 | - content.code.copy 11 | - navigation.footer 12 | markdown_extensions: 13 | - admonition 14 | - attr_list 15 | - codehilite 16 | - footnotes 17 | - pymdownx.details 18 | - pymdownx.superfences 19 | - markdown.extensions.def_list 20 | - mdx_truly_sane_lists 21 | - toc: 22 | permalink: true 23 | slugify: !!python/name:markdown.extensions.toc.slugify_unicode 24 | extra_javascript: 25 | - javascripts/katex.js 26 | - https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.7/katex.min.js 27 | - https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.7/contrib/auto-render.min.js 28 | extra_css: 29 | - css/extra.css 30 | - https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.7/katex.min.css 31 | nav: 32 | - 首页: README.md 33 | - 课程平台介绍: exp_platform_intro/README.md 34 | - 公共文档: 35 | - Cminusf 语法语义介绍: common/cminusf.md 36 | - Light IR 简介: common/LightIR.md 37 | - C++ 介绍: common/simple_cpp.md 38 | - Logging 工具使用: common/logging.md 39 | - 龙芯汇编介绍: common/asm_intro.md 40 | - Lab0: 41 | - Lab0 简介: lab0/index.md 42 | - Linux 环境配置: lab0/linux.md 43 | - LLVM 等软件环境配置: lab0/software.md 44 | - Git 的使用: lab0/git.md 45 | - Lab1: 46 | - Lab1 简介: lab1/index.md 47 | - 正则表达式: lab1/正则表达式.md 48 | - Flex: lab1/Flex.md 49 | - Bison: lab1/Bison.md 50 | - 实验细节与要求: lab1/实验细节与要求.md 51 | - Lab2: 52 | - Lab2 简介: lab2/index.md 53 | - Light IR 预热: lab2/warmup.md 54 | - 访问者模式: lab2/visitor_pattern.md 55 | - IR 自动化生成: lab2/autogen.md 56 | - Lab3: 57 | - Lab3 简介: lab3/index.md 58 | - Lab3 实验指导: lab3/guidance.md 59 | - 后端环境配置: lab3/environment.md 60 | - 龙芯汇编介绍: common/asm_intro.md 61 | - 栈式分配介绍: lab3/stack_allocation.md 62 | - 后端框架介绍: lab3/framework.md 63 | - Lab4: lab4/index.md 64 | - 建木杯–编译原理创新实验: innovative-lab/index.md 65 | - FAQ: faq/README.md 66 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "2023", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "2023", 9 | "version": "1.0.0", 10 | "dependencies": { 11 | "autocorrect-node": "^2.8.4", 12 | "prettier": "^3.1.0" 13 | } 14 | }, 15 | "node_modules/autocorrect-node": { 16 | "version": "2.8.4", 17 | "resolved": "https://registry.npmjs.org/autocorrect-node/-/autocorrect-node-2.8.4.tgz", 18 | "integrity": "sha512-65DUHJhIUwAlEtyDjYxE94sTT2anQtGXrPB7W9fdrk/Bm7ro4b7mjxQAP14lQlISXXtbNEJO1WABMqNpNbTjxQ==", 19 | "bin": { 20 | "autocorrect": "cli.js" 21 | }, 22 | "engines": { 23 | "node": ">= 10" 24 | }, 25 | "optionalDependencies": { 26 | "autocorrect-node-darwin-arm64": "2.8.4", 27 | "autocorrect-node-darwin-x64": "2.8.4", 28 | "autocorrect-node-linux-x64-gnu": "2.8.4", 29 | "autocorrect-node-linux-x64-musl": "2.8.4", 30 | "autocorrect-node-win32-x64-msvc": "2.8.4" 31 | } 32 | }, 33 | "node_modules/autocorrect-node-darwin-arm64": { 34 | "version": "2.8.4", 35 | "resolved": "https://registry.npmjs.org/autocorrect-node-darwin-arm64/-/autocorrect-node-darwin-arm64-2.8.4.tgz", 36 | "integrity": "sha512-eJFsrZZZUJVPTbdA6hAmVUbTBaznIfkIwstcfn02CXLVKq15j00CXkNytQIdmKpBraTmxjub9kQvYY0Nz2h4DQ==", 37 | "cpu": [ 38 | "arm64" 39 | ], 40 | "optional": true, 41 | "os": [ 42 | "darwin" 43 | ], 44 | "engines": { 45 | "node": ">= 10" 46 | } 47 | }, 48 | "node_modules/autocorrect-node-darwin-x64": { 49 | "version": "2.8.4", 50 | "resolved": "https://registry.npmjs.org/autocorrect-node-darwin-x64/-/autocorrect-node-darwin-x64-2.8.4.tgz", 51 | "integrity": "sha512-3QheEz2VgZnJcId7zgzbyC88CSmyHg+F93Bw5wsf7dTSwhVdnXeIIlYtXzFabp+c2aCi9lRAQkHFC9Om1CYkuQ==", 52 | "cpu": [ 53 | "x64" 54 | ], 55 | "optional": true, 56 | "os": [ 57 | "darwin" 58 | ], 59 | "engines": { 60 | "node": ">= 10" 61 | } 62 | }, 63 | "node_modules/autocorrect-node-linux-x64-gnu": { 64 | "version": "2.8.4", 65 | "resolved": "https://registry.npmjs.org/autocorrect-node-linux-x64-gnu/-/autocorrect-node-linux-x64-gnu-2.8.4.tgz", 66 | "integrity": "sha512-0nR3VnxpfU6HINQiZjC2cz1YVxOTCn529VPqHsWz2H03BPbS9YskGzbRpujnLvwLCyyDlYXJJneFYD1G+9VPaA==", 67 | "cpu": [ 68 | "x64" 69 | ], 70 | "optional": true, 71 | "os": [ 72 | "linux" 73 | ], 74 | "engines": { 75 | "node": ">= 10" 76 | } 77 | }, 78 | "node_modules/autocorrect-node-linux-x64-musl": { 79 | "version": "2.8.4", 80 | "resolved": "https://registry.npmjs.org/autocorrect-node-linux-x64-musl/-/autocorrect-node-linux-x64-musl-2.8.4.tgz", 81 | "integrity": "sha512-2LtKtaN6YWUnDtBpo2w5c7FSulozbt2kv9UigrckCX8M/AOVw0MA6uB5snglmKMx1f7fYC06/V4A7Ny1weUOGA==", 82 | "cpu": [ 83 | "x64" 84 | ], 85 | "optional": true, 86 | "os": [ 87 | "linux" 88 | ], 89 | "engines": { 90 | "node": ">= 10" 91 | } 92 | }, 93 | "node_modules/autocorrect-node-win32-x64-msvc": { 94 | "version": "2.8.4", 95 | "resolved": "https://registry.npmjs.org/autocorrect-node-win32-x64-msvc/-/autocorrect-node-win32-x64-msvc-2.8.4.tgz", 96 | "integrity": "sha512-ExcoBqRjEVnK9n2AVy/dmkiUSnvs5P/VdzI909bCjej1XgOpXonsZ4vGPEPicRfiHb8dc7whl6sh5P/TunDB7A==", 97 | "cpu": [ 98 | "x64" 99 | ], 100 | "optional": true, 101 | "os": [ 102 | "win32" 103 | ], 104 | "engines": { 105 | "node": ">= 10" 106 | } 107 | }, 108 | "node_modules/prettier": { 109 | "version": "3.1.0", 110 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.0.tgz", 111 | "integrity": "sha512-TQLvXjq5IAibjh8EpBIkNKxO749UEWABoiIZehEPiY4GNpVdhaFKqSTu+QrlU6D2dPAfubRmtJTi4K4YkQ5eXw==", 112 | "bin": { 113 | "prettier": "bin/prettier.cjs" 114 | }, 115 | "engines": { 116 | "node": ">=14" 117 | }, 118 | "funding": { 119 | "url": "https://github.com/prettier/prettier?sponsor=1" 120 | } 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "2023", 3 | "version": "1.0.0", 4 | "description": "课程主页", 5 | "private": true, 6 | "scripts": { 7 | "test": "prettier --check docs && autocorrect --lint docs", 8 | "format": "prettier --write docs && autocorrect --fix docs", 9 | "dev": "mkdocs serve", 10 | "build": "mkdocs build" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/ustc-compiler-principles/2023" 15 | }, 16 | "keywords": [ 17 | "ustc", 18 | "course" 19 | ], 20 | "author": "2023", 21 | "bugs": { 22 | "url": "https://github.com/ustc-compiler-principles/2023/issues" 23 | }, 24 | "homepage": "https://github.com/ustc-compiler-principles/2023#readme", 25 | "dependencies": { 26 | "autocorrect-node": "^2.8.4", 27 | "prettier": "^3.1.0" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | mkdocs 2 | mkdocs-material 3 | mdx_truly_sane_lists 4 | jinja2~=3.0.3 5 | markupsafe~=2.0.1 6 | --------------------------------------------------------------------------------