├── .cargo └── config ├── .editorconfig ├── .github └── workflows │ ├── audit.yml │ ├── grcov.yml │ └── main.yml ├── .gitignore ├── .rustfmt.toml ├── .tokeignore ├── Cargo.toml ├── Changelog.md ├── Crunch.ebnf ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── To Do.md ├── crates ├── crunch-codegen │ ├── .gitignore │ ├── Cargo.toml │ ├── entry.toml │ └── src │ │ ├── lib.rs │ │ └── llvm │ │ ├── context.rs │ │ ├── error.rs │ │ ├── instructions │ │ ├── instruction.rs │ │ └── mod.rs │ │ ├── mod.rs │ │ ├── module │ │ ├── builder.rs │ │ ├── building_block.rs │ │ ├── function_builder.rs │ │ ├── linkage.rs │ │ └── mod.rs │ │ ├── target_machine.rs │ │ ├── types │ │ ├── integer.rs │ │ ├── mod.rs │ │ ├── sealed_any_type.rs │ │ ├── ty.rs │ │ └── type_kind.rs │ │ ├── utils │ │ ├── address_space.rs │ │ ├── calling_convention.rs │ │ ├── dll_storage_class.rs │ │ ├── int_operand.rs │ │ ├── integer_opts.rs │ │ ├── llvm_string.rs │ │ ├── memory_buffer.rs │ │ ├── mod.rs │ │ └── thread_local_mode.rs │ │ ├── value.rs │ │ └── values │ │ ├── any_value.rs │ │ ├── basic_block.rs │ │ ├── function.rs │ │ ├── integer.rs │ │ ├── mod.rs │ │ ├── pointer.rs │ │ ├── sealed.rs │ │ ├── value.rs │ │ └── value_kind.rs ├── crunch-database │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── crunch-driver │ ├── Cargo.toml │ ├── src │ │ └── main.rs │ └── tests │ │ └── ui.rs ├── crunch-fuzzing │ ├── Cargo.toml │ ├── fuzz.sh │ └── src │ │ └── parse.rs ├── crunch-mir │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── crunch-parser │ ├── .gitignore │ ├── Cargo.toml │ ├── codegen.py │ ├── crashes │ │ ├── enbum.fuzz │ │ ├── floatnnnnnn.fuzz │ │ ├── gobbledegook.fuzz │ │ ├── hmpss.fuzz │ │ ├── horrible_style.fuzz │ │ ├── importnt_business.fuzz │ │ ├── main_24601.fuzz │ │ ├── matchend.fuzz │ │ ├── more_yodeling.fuzz │ │ ├── numero_spamo.fuzz │ │ ├── qic_fatish.fuzz │ │ ├── rafadas.fuzz │ │ ├── rafadas2_boogaloo.fuzz │ │ ├── squares.fuzz │ │ ├── unicode_slicin.fuzz │ │ ├── unicode_yodeling.fuzz │ │ └── yodelin_imports.fuzz │ ├── proptest-regressions │ │ ├── parser │ │ │ └── tests.txt │ │ └── token.txt │ └── src │ │ ├── database.rs │ │ ├── lib.rs │ │ ├── parser │ │ ├── expr.rs │ │ ├── item.rs │ │ ├── mod.rs │ │ ├── patterns.rs │ │ ├── stmt.rs │ │ ├── string_escapes.rs │ │ ├── types.rs │ │ └── utils.rs │ │ ├── tests.rs │ │ ├── token.rs │ │ └── unnest_externs.rs ├── crunch-proc │ ├── Cargo.toml │ └── src │ │ ├── lib.rs │ │ └── nanopass │ │ ├── mod.rs │ │ └── schema.rs ├── crunch-shared │ ├── Cargo.toml │ ├── build.rs │ └── src │ │ ├── allocator.rs │ │ ├── config.rs │ │ ├── context.rs │ │ ├── databases │ │ ├── mod.rs │ │ └── source.rs │ │ ├── distance.rs │ │ ├── error.rs │ │ ├── file_hash.rs │ │ ├── files.rs │ │ ├── lib.rs │ │ ├── meta.rs │ │ ├── passes │ │ ├── ast.rs │ │ ├── ast.toml │ │ └── mod.rs │ │ ├── strings.rs │ │ ├── trees │ │ ├── ast.rs │ │ ├── hir.rs │ │ ├── mir.rs │ │ └── mod.rs │ │ ├── utils.rs │ │ └── visitors │ │ ├── ast.rs │ │ ├── hir.rs │ │ ├── mir.rs │ │ └── mod.rs ├── crunch-typecheck │ ├── .gitignore │ ├── Cargo.toml │ ├── build.rs │ ├── ddlog.sh │ ├── ddlog │ │ ├── modules │ │ │ └── hir.dl │ │ ├── typecheck.dl │ │ └── typecheck.rs │ ├── src │ │ ├── ddlog.rs │ │ └── lib.rs │ └── typecheck_ddlog │ │ ├── Cargo.toml │ │ ├── cmd_parser │ │ ├── Cargo.toml │ │ ├── lib.rs │ │ └── parse.rs │ │ ├── differential_datalog │ │ ├── Cargo.toml │ │ ├── arcval.rs │ │ ├── callback.rs │ │ ├── ddlog.rs │ │ ├── ddval.rs │ │ ├── int.rs │ │ ├── lib.rs │ │ ├── profile.rs │ │ ├── profile_statistics.rs │ │ ├── program.rs │ │ ├── record.rs │ │ ├── replay.rs │ │ ├── test.rs │ │ ├── test_record.rs │ │ ├── test_value.rs │ │ ├── uint.rs │ │ ├── valmap.rs │ │ └── variable.rs │ │ ├── src │ │ ├── api.rs │ │ ├── build.rs │ │ ├── lib.rs │ │ ├── main.rs │ │ ├── ovsdb.rs │ │ └── update_handler.rs │ │ ├── types │ │ ├── Cargo.toml │ │ ├── flatbuf.rs │ │ ├── flatbuf_generated.rs │ │ ├── lib.rs │ │ └── log.rs │ │ └── value │ │ ├── Cargo.toml │ │ ├── flatbuf.rs │ │ └── lib.rs ├── ladder │ ├── Cargo.toml │ └── src │ │ └── lib.rs └── repl │ ├── .gitignore │ ├── Cargo.toml │ └── src │ └── main.rs ├── examples ├── fibonacci.crunch ├── hello_world.crunch ├── int_to_str.crunch ├── missing_var.crunch ├── return_code.crunch └── string_struct.crunch └── syntaxes ├── crunch-lang ├── .vscode │ └── launch.json ├── .vscodeignore ├── CHANGELOG.md ├── README.md ├── language-configuration.json ├── package.json ├── syntaxes │ └── crunch.tmLanguage.json └── vsc-extension-quickstart.md └── crunch.lang /.cargo/config: -------------------------------------------------------------------------------- 1 | [build] 2 | incremental = true 3 | rustflags = ["-Ctarget-cpu=native"] 4 | 5 | [target.x86_64-pc-windows-msvc] 6 | rustflags = ["-Ctarget-feature=+crt-static"] 7 | 8 | [target.i686-pc-windows-msvc] 9 | rustflags = ["-Ctarget-feature=+crt-static"] 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | charset = utf-8 7 | indent_style = space 8 | indent_size = 4 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | end_of_line = lf 13 | insert_final_newline = true 14 | charset = utf-8 15 | indent_style = space 16 | indent_size = 4 17 | trim_trailing_whitespace = false 18 | -------------------------------------------------------------------------------- /.github/workflows/audit.yml: -------------------------------------------------------------------------------- 1 | name: Security Audit 2 | 3 | on: 4 | schedule: 5 | - cron: "0 22 * * *" 6 | push: 7 | branches: [master, dev] 8 | paths: 9 | - "**/Cargo.toml" 10 | - "**/Cargo.lock" 11 | - ".github/workflows/audit.yml" 12 | pull_request: 13 | branches: [master, dev] 14 | 15 | jobs: 16 | Audit: 17 | name: Audit Dependencies 18 | 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: actions/checkout@v2 22 | - uses: actions-rs/audit-check@v1 23 | with: 24 | token: ${{ secrets.GITHUB_TOKEN }} 25 | -------------------------------------------------------------------------------- /.github/workflows/grcov.yml: -------------------------------------------------------------------------------- 1 | name: Coverage 2 | 3 | on: [push] 4 | 5 | jobs: 6 | check: 7 | name: Crunch 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout repository 11 | uses: actions/checkout@v2 12 | 13 | - name: Install stable toolchain 14 | uses: actions-rs/toolchain@v1 15 | with: 16 | toolchain: stable 17 | override: true 18 | 19 | - name: Run cargo-tarpaulin 20 | uses: actions-rs/tarpaulin@v0.1 21 | with: 22 | args: "--exclude-files examples/* --exclude-files crates/repl/* --exclude-files crates/crunch-fuzzing/* --out Lcov --coveralls ${{ secrets.COVERALLS_TOKEN }}" 23 | 24 | - name: Upload to Coveralls 25 | uses: coverallsapp/github-action@master 26 | with: 27 | github-token: ${{ secrets.GITHUB_TOKEN }} 28 | parallel: true 29 | path-to-lcov: ./lcov.info 30 | 31 | - name: Archive code coverage results 32 | uses: actions/upload-artifact@v1 33 | with: 34 | name: code-coverage-report 35 | path: cobertura.xml 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/target 2 | **/*.rs.bk 3 | **/Cargo.lock 4 | 5 | left.dump 6 | right.dump 7 | 8 | /.vscode 9 | *.swp 10 | *.swo 11 | /.vs 12 | 13 | /docs/public 14 | 15 | seccomp-perf.json 16 | /fuzz-out 17 | **/history.txt 18 | 19 | **/build 20 | .env 21 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | newline_style = "Native" 2 | -------------------------------------------------------------------------------- /.tokeignore: -------------------------------------------------------------------------------- 1 | crates/crunch-typecheck/typecheck_ddlog/ 2 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["crates/*", "crates/crunch-typecheck/typecheck_ddlog"] 3 | exclude = ["crates/crunch-fuzzing", "crates/repl"] 4 | 5 | [profile.release] 6 | debug = false 7 | opt-level = 3 8 | debug-assertions = false 9 | lto = "fat" 10 | codegen-units = 1 11 | -------------------------------------------------------------------------------- /Changelog.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | 10 | - Changelog will not be maintained until Crunch reaches 0.1.0, as doing anything before that is pretty much pointless 11 | -------------------------------------------------------------------------------- /Crunch.ebnf: -------------------------------------------------------------------------------- 1 | /* The grammar specification for Crunch */ 2 | /* Uses the W3C EBNF Spec as defined here: https://www.w3.org/TR/2010/REC-xquery-20101214/#EBNFNotation */ 3 | 4 | Program ::= 5 | ( FunctionDeclaration 6 | | TypeDeclaration 7 | | EnumDeclaration 8 | | TraitDeclaration 9 | | Import 10 | )* 11 | 12 | /* Function Declarations */ 13 | FunctionDeclaration ::= Decorator* Attibute* 'fn' Ident Generics? '(' FunctionArguments? ')' ( '->' Ident )? '\n' Statement+ 'end' 14 | FunctionArguments ::= AbscriptedArg | AbscriptedArg ',' FunctionArguments 15 | AbscriptedArg ::= Ident ( ':' Ident )? 16 | 17 | /* Type Declarations */ 18 | TypeDeclaration ::= Decorator* Attibute* 'type' Ident Generics? '\n' TypeArguments? FunctionDeclaration* 'end' 19 | TypeArguments ::= TypeArg '\n' | TypeArg '\n' TypeArguments 20 | TypeArg ::= Attibute* Ident ':' Ident 21 | 22 | /* Enum Declarations */ 23 | EnumDeclaration ::= Decorator* Attibute* 'enum' Ident Generics? '\n' EnumVariant* 'end' 24 | EnumVariant ::= UnitVariant | TupleVariant 25 | 26 | UnitVariant ::= Ident '\n' 27 | 28 | TupleVariant ::= Ident '(' TupleVariantParams? ')' '\n' 29 | TupleVariantParams ::= Ident | Ident ',' TupleVariantParams 30 | 31 | /* Trait Declarations */ 32 | TraitDeclaration ::= Decorator* Attribute* 'trait' Ident Generics? '\n' FunctionDeclaration* 'end' 33 | 34 | /* Imports */ 35 | Import ::= 'import' ImportDestination? String ( 'exposing' ( '*' | ImportMembers? ) | 'as' Ident ) '\n' 36 | ImportMembers ::= ImportMemberArg | ImportMemberArg ',' ImportMembers 37 | ImportMemberArg ::= Ident ( 'as' Ident )? 38 | ImportDestination ::= 'lib' | 'pkg' 39 | 40 | /* Decorators, Attibutes & Generics */ 41 | Decorator ::= '@' Ident ( '(' DecoratorArgs? ')' )? '\n' 42 | DecoratorArgs ::= Expr | DecoratorArgs ',' Expr 43 | 44 | Attribute ::= Visibility 45 | Visibility ::= 'exposed' | 'sealed' 46 | 47 | Generics ::= '<' GenericParam? '>' 48 | GenericParam ::= Ident | Ident ',' GenericParam 49 | 50 | /* Idents */ 51 | Ident ::= [a-zA-Z_][a-zA-Z0-9_]* 52 | 53 | /* Literals */ 54 | Literal ::= String | Boolean | Integer | Float 55 | 56 | /* String Literals */ 57 | String ::= StringPrefix? StringDelim [^StringDelim]* StringDelim 58 | StringPrefix ::= 'b' 59 | StringDelim ::= '"' | "'" | '"""' | "'''" 60 | 61 | /* Boolean Literals */ 62 | Boolean ::= 'true' | 'false' 63 | 64 | /* Integer Literals */ 65 | Integer ::= Sign? ( Digits | '0x' HexDigits ) 66 | Sign ::= '-' | '+' 67 | Digits ::= [0-9][0-9_]* 68 | HexDigits ::= [0-9a-fA-F][0-9a-fA-F_]* 69 | IntSuffix ::= 'byte' | 'int' 70 | 71 | /* Float Literals */ 72 | Float ::= 'inf' | 'NaN' | Sign? ( DecimalFloat | HexFloat ) 73 | 74 | /* Decimal Float Literals */ 75 | DecimalFloat ::= Sign? 76 | ( Digits '.' Digits? DecimalExp? DecimalFloatSuffix? 77 | | Digits DecimalExp DecimalFloatSuffix? 78 | | Digits DecimalExp? DecimalFloatSuffix 79 | ) 80 | DecimalExp ::= DecimalExpIndicator Sign? Digits 81 | DecimalExpIndicator ::= 'e' | 'E' 82 | DecimalFloatSuffix ::= 'f' | 'F' 83 | 84 | /* Hex Float Literals */ 85 | HexFloat ::= '0x' HexDigits '.' HexFloatExp? 86 | HexFloatExp ::= HexFloatExpIndicator Sign? Digits 87 | HexFloatExpIndicator ::= 'p' | 'P' 88 | 89 | /* Expressions */ 90 | Expr ::= 91 | Literal 92 | | Unary 93 | | Range 94 | | Comparison 95 | | BinaryOperation 96 | | Ident 97 | | '(' Expr ')' 98 | | IndexArray 99 | | InlineConditional 100 | | FunctionCall 101 | | Array 102 | | Assignment 103 | 104 | Unary ::= ( '+' | '-' | '!' ) Expr 105 | 106 | Range ::= Expr '..' Expr 107 | 108 | Array ::= '[' ArrayElements? ']' 109 | ArrayElements ::= Expr | ArrayElements ',' Expr 110 | 111 | IndexArray ::= Expr '[' Expr ']' 112 | 113 | InlineConditional ::= Expr 'if' Expr 'else' Expr 114 | 115 | FunctionCall ::= ( Expr '.' )? Expr '(' FunctionCallArgs? ')' 116 | FunctionArgs ::= Expr | FunctionArgs ',' Expr 117 | 118 | BinaryOperation ::= Expr BinaryOperand Expr 119 | BinaryOperand ::= 120 | '+' | '-' | '*' | '/' | '%' | '**' 121 | | '^' | '|' | '&' | '<<' | '>>' 122 | 123 | Comparison ::= Expr Comparator Expr 124 | Comparator ::= '==' | '!=' | '<=' | '>=' | '<' | '>' 125 | 126 | Assignment ::= Ident Assigner Expr '\n' 127 | Assigner ::= '=' | BinaryOperand '=' 128 | 129 | /* Statements */ 130 | Statement ::= 131 | While 132 | | Loop 133 | | For 134 | | If 135 | | Match 136 | | VarDecl 137 | | Return 138 | | Continue 139 | | Break 140 | | Expr '\n' 141 | | 'empty' 142 | 143 | Return ::= 'return' Expr? '\n' 144 | Continue ::= 'continue' '\n' 145 | Break ::= 'break' Expr? '\n' 146 | 147 | Match ::= 'match' Expr '\n' ( Ident ( 'where' Expr )? '=>' '\n' Statement+ )+ 'end' 148 | 149 | If ::= 'if' Expr '\n' Statement+ '\n' ElseIf* Else? 'end' 150 | ElseIf ::= 'else' 'if' Expr '\n' Statement+ 151 | Else ::= 'else' '\n' Statement+ 152 | 153 | While ::= 'while' Expr '\n' Statement+ ThenClause? 'end' 154 | Loop ::= 'loop' '\n' Statement+ ThenClause? 'end' 155 | For ::= 'for' Expr 'in' Expr '\n' Statement+ ThenClause? 'end' 156 | ThenClause ::= 'then' '\n' Statement+ 157 | 158 | VarDecl ::= 'let' Ident ( ':' Ident )? '=' Expr '\n' 159 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Copyright 2019 Chase Wilson 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Chase Wilson 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /crates/crunch-codegen/.gitignore: -------------------------------------------------------------------------------- 1 | crunch.exe 2 | crunch.o 3 | crunch.s 4 | -------------------------------------------------------------------------------- /crates/crunch-codegen/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "crunch-codegen" 3 | version = "0.1.0" 4 | authors = ["Chase Wilson "] 5 | license = "Apache-2.0 OR MIT" 6 | edition = "2018" 7 | 8 | [dependencies.llvm-sys] 9 | version = "100.0.1" 10 | 11 | [dependencies.typenum] 12 | version = "1.12.0" 13 | 14 | [dependencies.crunch-shared] 15 | path = "../crunch-shared" 16 | 17 | [dependencies.crunch-mir] 18 | path = "../crunch-mir" 19 | 20 | [dev-dependencies.tempfile] 21 | version = "3.1.0" 22 | default-features = false 23 | -------------------------------------------------------------------------------- /crates/crunch-codegen/entry.toml: -------------------------------------------------------------------------------- 1 | [llvm-mirror] 2 | url = "https://github.com/llvm-mirror/llvm" 3 | target = ["X86"] 4 | 5 | [[llvm-mirror.tools]] 6 | name = "clang" 7 | url = "https://github.com/llvm-mirror/clang" 8 | 9 | [[llvm-mirror.tools]] 10 | name = "clang-extra" 11 | url = "https://github.com/llvm-mirror/clang-tools-extra" 12 | relative_path = "tools/clang/tools/extra" 13 | -------------------------------------------------------------------------------- /crates/crunch-codegen/src/llvm/context.rs: -------------------------------------------------------------------------------- 1 | use crate::llvm::{ 2 | module::Module, 3 | utils::{to_non_nul, LLVMString, MemoryBuffer}, 4 | Error, ErrorKind, Result, 5 | }; 6 | use llvm_sys::{ 7 | core::{LLVMContextCreate, LLVMContextDispose, LLVMModuleCreateWithNameInContext}, 8 | ir_reader::LLVMParseIRInContext, 9 | LLVMContext, 10 | }; 11 | use std::{ 12 | ffi::CString, 13 | mem::{ManuallyDrop, MaybeUninit}, 14 | ptr::NonNull, 15 | }; 16 | 17 | // TODO: Diagnostic handler 18 | // https://llvm.org/docs/doxygen/group__LLVMCCoreContext.html#gad027fad059f2fc3476d5a8464e39c9ef 19 | // https://llvm.org/docs/doxygen/group__LLVMCCoreContext.html#gacbfc704565962bf71eaaa549a9be570f 20 | 21 | /// A container for all LLVM entities 22 | /// 23 | /// `Context` is not thread safe and cannot be shared across threads, but [multiple `Context`s can 24 | /// exist and execute simultaneously] 25 | /// 26 | /// [multiple `Context`s can exist and execute simultaneously]: https://llvm.org/docs/doxygen/group__LLVMCCoreContext.html#details 27 | #[derive(Debug, PartialEq, Eq)] 28 | #[repr(transparent)] 29 | pub struct Context { 30 | /// The pointer to the context 31 | ctx: NonNull, 32 | } 33 | 34 | // Public interface 35 | impl Context { 36 | /// Creates a new `Context` using [`LLVMContextCreate`] 37 | /// 38 | /// [`LLVMContextCreate`]: https://llvm.org/docs/doxygen/group__LLVMCCoreContext.html#gaac4f39a2d0b9735e64ac7681ab543b4c 39 | pub fn new() -> Result { 40 | // Safety: The new context is checked for null and LLVM provides valid pointers 41 | let ctx = to_non_nul( 42 | unsafe { LLVMContextCreate() }, 43 | "Failed to create LLVM Context", 44 | )?; 45 | 46 | Ok(Self { ctx }) 47 | } 48 | 49 | pub fn module<'ctx, N>(&'ctx self, name: N) -> Result> 50 | where 51 | N: AsRef, 52 | { 53 | let name = CString::new(name.as_ref())?; 54 | 55 | unsafe { 56 | let module = LLVMModuleCreateWithNameInContext(name.as_ptr(), self.as_mut_ptr()); 57 | 58 | Module::from_raw(self, module) 59 | } 60 | } 61 | 62 | // FIXME: `LLVMParseIRInContext` takes ownership of the buffer, but there's not really a better way to express that other than with the `ManuallyDrop` 63 | pub fn module_from_ir<'ctx>(&'ctx self, memory_buf: MemoryBuffer) -> Result> { 64 | let (mut module, mut err_message) = (MaybeUninit::zeroed(), MaybeUninit::zeroed()); 65 | let memory_buf = ManuallyDrop::new(memory_buf); 66 | 67 | let succeeded = unsafe { 68 | LLVMParseIRInContext( 69 | self.as_mut_ptr(), 70 | memory_buf.as_mut_ptr(), 71 | module.as_mut_ptr(), 72 | err_message.as_mut_ptr(), 73 | ) == 0 74 | }; 75 | 76 | if succeeded { 77 | // Safety: The parse was successful, so module is initialized 78 | let module = unsafe { module.assume_init() }; 79 | // If the pointer is null it will be handled by `Module::from_raw`, but it's not desired or expected behavior 80 | debug_assert!(!module.is_null()); 81 | 82 | // Safety: The parse succeeded, and so the module is valid. This instance of `Module` will also have unique ownership 83 | // over the buffer 84 | Ok(unsafe { Module::from_raw(self, module)? }) 85 | } else { 86 | // Safety: The parse wasn't successful, so the error is initialized 87 | let err_message = unsafe { err_message.assume_init() }; 88 | // If the pointer is null it will be handled by `LLVMString::from_raw`, but it's not desired or expected behavior 89 | debug_assert!(!err_message.is_null()); 90 | 91 | // Safety: The memory was allocated by LLVM and is therefore able to be put into an `LLVMString`, 92 | // and there was an error so the pointer was initalized 93 | let err_message = unsafe { LLVMString::from_raw(err_message)? }; 94 | 95 | Err(Error::new(err_message, ErrorKind::InvalidIR)) 96 | } 97 | } 98 | } 99 | 100 | // Private interface 101 | impl Context { 102 | /// Retrieves a raw pointer to the underlying `LLVMContext` 103 | pub(crate) const fn as_mut_ptr(&self) -> *mut LLVMContext { 104 | self.ctx.as_ptr() as *mut LLVMContext 105 | } 106 | } 107 | 108 | /// Creates a default `Context`, panicking if an error occurs 109 | impl Default for Context { 110 | fn default() -> Self { 111 | Self::new().expect("Failed to create context") 112 | } 113 | } 114 | 115 | /// Destroys the `Context` using [`LLVMContextDispose`] 116 | /// 117 | /// [`LLVMContextDispose`]: https://llvm.org/docs/doxygen/group__LLVMCCoreContext.html#ga9cf8b0fb4a546d4cdb6f64b8055f5f57 118 | impl Drop for Context { 119 | fn drop(&mut self) { 120 | // Safety: Context instances are unique and all produced items have been dropped 121 | unsafe { LLVMContextDispose(self.as_mut_ptr()) } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /crates/crunch-codegen/src/llvm/error.rs: -------------------------------------------------------------------------------- 1 | use crate::llvm::utils::LLVMString; 2 | use std::{ 3 | borrow::Cow, 4 | error, 5 | ffi::NulError, 6 | fmt::{Display, Formatter, Result as FmtResult}, 7 | str::Utf8Error, 8 | }; 9 | 10 | /// A convenience type for `Result` 11 | /// 12 | /// [`Error`]: crate::utils::Error 13 | pub type Result = std::result::Result; 14 | 15 | /// An error that was encountered either from LLVM or from the program's behavior 16 | #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 17 | pub struct Error { 18 | /// The error message 19 | message: ErrorString, 20 | /// The type of error that occurred 21 | kind: ErrorKind, 22 | } 23 | 24 | impl Error { 25 | /// Create a new `Error` 26 | pub(crate) fn new(message: M, kind: ErrorKind) -> Self 27 | where 28 | M: Into, 29 | { 30 | Self { 31 | message: message.into(), 32 | kind, 33 | } 34 | } 35 | 36 | /// Fetches the error message 37 | /// 38 | /// This method will always allocate, so [`Error::message_ref`] can often be a better option 39 | /// 40 | /// [`Error::message_ref`]: crate::utils::Error#message_ref 41 | pub fn message(&self) -> String { 42 | self.message.to_string() 43 | } 44 | 45 | /// Fetches the error message as a [`Cow`] 46 | /// 47 | /// [`Cow`]: https://doc.rust-lang.org/std/borrow/enum.Cow.html 48 | pub fn message_ref(&self) -> Cow<'_, str> { 49 | match self.message { 50 | ErrorString::String(ref string) => Cow::Borrowed(string), 51 | ErrorString::LLVMString(ref llvm_string) => llvm_string.to_string_lossy(), 52 | } 53 | } 54 | 55 | /// Fetches the type of error that occurred 56 | pub const fn kind(&self) -> ErrorKind { 57 | self.kind 58 | } 59 | } 60 | 61 | impl Display for Error { 62 | fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { 63 | write!( 64 | f, 65 | "an error was encountered: {:?}, {}", 66 | self.kind, self.message, 67 | ) 68 | } 69 | } 70 | 71 | impl error::Error for Error {} 72 | 73 | impl From for Error { 74 | fn from(err: Utf8Error) -> Self { 75 | Self::new( 76 | format!("a string was not valid utf-8: {}", err), 77 | ErrorKind::InvalidStr, 78 | ) 79 | } 80 | } 81 | 82 | impl From for Error { 83 | fn from(err: NulError) -> Self { 84 | Self::new(err.to_string(), ErrorKind::InvalidStr) 85 | } 86 | } 87 | 88 | /// The kind of error that was encountered 89 | #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 90 | #[repr(u8)] 91 | pub enum ErrorKind { 92 | /// LLVM returned a null pointer where one was not expected 93 | NullPtr, 94 | /// An invalid string was given or received 95 | InvalidStr, 96 | /// LLVM inexplicitly did the wrong thing 97 | LLVMError, 98 | /// An incorrect type was supplied 99 | MismatchedTypes, 100 | /// Invalid IR was parsed or created 101 | InvalidIR, 102 | /// A nonexistent file was attempted to be used 103 | FileNotFound, 104 | /// A module was invalid 105 | InvalidModule, 106 | /// A function was invalid 107 | InvalidFunction, 108 | /// Too many function arguments were provided 109 | TooManyArgs, 110 | /// Failed to emit something 111 | FailedEmission, 112 | /// The given target triple was invalid 113 | InvalidTriple, 114 | /// Failed to initialize a target 115 | FailedTargetInit, 116 | } 117 | 118 | /// A string that can either be a normal [`String`] or an [`LLVMString`] 119 | /// 120 | /// [`String`]: https://doc.rust-lang.org/std/string/struct.String.html 121 | /// [`LLVMString`]: crate::utils::utils::LLVMString 122 | #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 123 | #[repr(u8)] 124 | pub(crate) enum ErrorString { 125 | /// A normal [`String`] 126 | /// 127 | /// [`String`]: https://doc.rust-lang.org/std/string/struct.String.html 128 | String(String), 129 | /// A [`LLVMString`] 130 | /// 131 | /// [`LLVMString`]: crate::utils::utils::LLVMString 132 | LLVMString(LLVMString), 133 | } 134 | 135 | impl Display for ErrorString { 136 | fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { 137 | match self { 138 | Self::String(ref string) => f.write_str(string), 139 | Self::LLVMString(ref llvm_string) => f.write_str(&llvm_string.to_string_lossy()), 140 | } 141 | } 142 | } 143 | 144 | impl From for ErrorString 145 | where 146 | T: Into, 147 | { 148 | fn from(string: T) -> Self { 149 | Self::String(string.into()) 150 | } 151 | } 152 | 153 | impl From for ErrorString { 154 | fn from(string: LLVMString) -> Self { 155 | Self::LLVMString(string) 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /crates/crunch-codegen/src/llvm/instructions/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod instruction; 2 | 3 | pub use instruction::Instruction; 4 | -------------------------------------------------------------------------------- /crates/crunch-codegen/src/llvm/mod.rs: -------------------------------------------------------------------------------- 1 | mod context; 2 | mod error; 3 | pub mod instructions; 4 | pub mod module; 5 | pub mod target_machine; 6 | pub mod types; 7 | pub mod utils; 8 | pub mod values; 9 | 10 | pub use context::Context; 11 | pub use error::{Error, ErrorKind, Result}; 12 | -------------------------------------------------------------------------------- /crates/crunch-codegen/src/llvm/module/builder.rs: -------------------------------------------------------------------------------- 1 | use crate::llvm::{utils::to_non_nul, values::BasicBlock, Context, Result}; 2 | use llvm_sys::{ 3 | core::{LLVMCreateBuilderInContext, LLVMDisposeBuilder, LLVMPositionBuilderAtEnd}, 4 | LLVMBuilder, 5 | }; 6 | use std::{marker::PhantomData, ptr::NonNull}; 7 | 8 | #[derive(Debug)] 9 | pub struct Builder<'ctx> { 10 | builder: NonNull, 11 | __ctx: PhantomData<&'ctx Context>, 12 | } 13 | 14 | impl<'ctx> Builder<'ctx> { 15 | pub(crate) fn new(ctx: &'ctx Context) -> Result { 16 | // Safety: The new builder is checked for null 17 | let builder = to_non_nul( 18 | unsafe { LLVMCreateBuilderInContext(ctx.as_mut_ptr()) }, 19 | "Failed to create LLVM Builder", 20 | )?; 21 | 22 | Ok(Self { 23 | builder, 24 | __ctx: PhantomData, 25 | }) 26 | } 27 | 28 | // pub(crate) fn move_to(&self, block: &BasicBlock<'ctx>, instruction: InstructionValue<'ctx>) { 29 | // unsafe { 30 | // LLVMPositionBuilder( 31 | // self.as_mut_ptr(), 32 | // BasicBlock::as_mut_ptr(*block), 33 | // instruction.as_mut_ptr(), 34 | // ) 35 | // }; 36 | // } 37 | 38 | pub(crate) fn move_to_end(&self, block: &BasicBlock<'ctx>) { 39 | unsafe { LLVMPositionBuilderAtEnd(self.as_mut_ptr(), BasicBlock::as_mut_ptr(*block)) }; 40 | } 41 | 42 | // pub(crate) fn move_before(&self, instruction: InstructionValue<'ctx>) { 43 | // unsafe { LLVMPositionBuilderBefore(self.as_mut_ptr(), instruction.as_mut_ptr()) }; 44 | // } 45 | 46 | pub(crate) const fn as_mut_ptr(&self) -> *mut LLVMBuilder { 47 | self.builder.as_ptr() as *mut LLVMBuilder 48 | } 49 | } 50 | 51 | impl<'ctx> Drop for Builder<'ctx> { 52 | fn drop(&mut self) { 53 | // Safety: Builder instances are unique and the parent LLVMContext must not be dropped 54 | unsafe { LLVMDisposeBuilder(self.as_mut_ptr()) } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /crates/crunch-codegen/src/llvm/module/linkage.rs: -------------------------------------------------------------------------------- 1 | use llvm_sys::LLVMLinkage; 2 | 3 | #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 4 | #[repr(u8)] 5 | pub enum Linkage { 6 | External, 7 | AvailableExternally, 8 | LinkOnceAny, 9 | LinkOnceODR, 10 | LinkOnceODRAutoHide, 11 | WeakAny, 12 | WeakODR, 13 | Appending, 14 | Internal, 15 | Private, 16 | DLLImport, 17 | DLLExport, 18 | ExternalWeak, 19 | Ghost, 20 | Common, 21 | LinkerPrivate, 22 | LinkerPrivateWeak, 23 | } 24 | 25 | #[rustfmt::skip] 26 | impl From for Linkage { 27 | fn from(linkage: LLVMLinkage) -> Self { 28 | match linkage { 29 | LLVMLinkage::LLVMExternalLinkage => Self::External, 30 | LLVMLinkage::LLVMAvailableExternallyLinkage => Self::AvailableExternally, 31 | LLVMLinkage::LLVMLinkOnceAnyLinkage => Self::LinkOnceAny, 32 | LLVMLinkage::LLVMLinkOnceODRLinkage => Self::LinkOnceODR, 33 | LLVMLinkage::LLVMLinkOnceODRAutoHideLinkage => Self::LinkOnceODRAutoHide, 34 | LLVMLinkage::LLVMWeakAnyLinkage => Self::WeakAny, 35 | LLVMLinkage::LLVMWeakODRLinkage => Self::WeakODR, 36 | LLVMLinkage::LLVMAppendingLinkage => Self::Appending, 37 | LLVMLinkage::LLVMInternalLinkage => Self::Internal, 38 | LLVMLinkage::LLVMPrivateLinkage => Self::Private, 39 | LLVMLinkage::LLVMDLLImportLinkage => Self::DLLImport, 40 | LLVMLinkage::LLVMDLLExportLinkage => Self::DLLExport, 41 | LLVMLinkage::LLVMExternalWeakLinkage => Self::ExternalWeak, 42 | LLVMLinkage::LLVMGhostLinkage => Self::Ghost, 43 | LLVMLinkage::LLVMCommonLinkage => Self::Common, 44 | LLVMLinkage::LLVMLinkerPrivateLinkage => Self::LinkerPrivate, 45 | LLVMLinkage::LLVMLinkerPrivateWeakLinkage => Self::LinkerPrivateWeak, 46 | } 47 | } 48 | } 49 | 50 | #[rustfmt::skip] 51 | impl Into for Linkage { 52 | fn into(self) -> LLVMLinkage { 53 | match self { 54 | Self::External => LLVMLinkage::LLVMExternalLinkage, 55 | Self::AvailableExternally => LLVMLinkage::LLVMAvailableExternallyLinkage, 56 | Self::LinkOnceAny => LLVMLinkage::LLVMLinkOnceAnyLinkage, 57 | Self::LinkOnceODR => LLVMLinkage::LLVMLinkOnceODRLinkage, 58 | Self::LinkOnceODRAutoHide => LLVMLinkage::LLVMLinkOnceODRAutoHideLinkage, 59 | Self::WeakAny => LLVMLinkage::LLVMWeakAnyLinkage, 60 | Self::WeakODR => LLVMLinkage::LLVMWeakODRLinkage, 61 | Self::Appending => LLVMLinkage::LLVMAppendingLinkage, 62 | Self::Internal => LLVMLinkage::LLVMInternalLinkage, 63 | Self::Private => LLVMLinkage::LLVMPrivateLinkage, 64 | Self::DLLImport => LLVMLinkage::LLVMDLLImportLinkage, 65 | Self::DLLExport => LLVMLinkage::LLVMDLLExportLinkage, 66 | Self::ExternalWeak => LLVMLinkage::LLVMExternalWeakLinkage, 67 | Self::Ghost => LLVMLinkage::LLVMGhostLinkage, 68 | Self::Common => LLVMLinkage::LLVMCommonLinkage, 69 | Self::LinkerPrivate => LLVMLinkage::LLVMLinkerPrivateLinkage, 70 | Self::LinkerPrivateWeak => LLVMLinkage::LLVMLinkerPrivateWeakLinkage, 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /crates/crunch-codegen/src/llvm/types/integer.rs: -------------------------------------------------------------------------------- 1 | use crate::llvm::{ 2 | types::{AnyType, SealedAnyType, Type}, 3 | utils::{to_non_nul, Sealed}, 4 | values::{IntValue, Pointable, SealedAnyValue}, 5 | Context, Result, 6 | }; 7 | use llvm_sys::{ 8 | core::{ 9 | LLVMConstInt, LLVMGetIntTypeWidth, LLVMInt128TypeInContext, LLVMInt16TypeInContext, 10 | LLVMInt1TypeInContext, LLVMInt32TypeInContext, LLVMInt64TypeInContext, 11 | LLVMInt8TypeInContext, LLVMIntTypeInContext, 12 | }, 13 | LLVMType, 14 | }; 15 | use std::{ 16 | fmt::{ 17 | Debug, Display, Formatter, LowerHex, Octal, Pointer as FmtPointer, Result as FmtResult, 18 | UpperHex, 19 | }, 20 | marker::PhantomData, 21 | ptr::NonNull, 22 | }; 23 | 24 | #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 25 | #[repr(transparent)] 26 | pub struct IntType<'ctx, W: IntWidth>(Type<'ctx>, PhantomData); 27 | 28 | impl<'ctx, W: IntWidth> IntType<'ctx, W> { 29 | #[allow(clippy::new_ret_no_self)] 30 | pub fn new(ctx: &'ctx Context, bits: u32) -> Result> { 31 | unsafe { IntType::from_raw(LLVMIntTypeInContext(ctx.as_mut_ptr(), bits)) } 32 | } 33 | 34 | pub fn width(self) -> u32 { 35 | unsafe { LLVMGetIntTypeWidth(self.as_mut_ptr()) } 36 | } 37 | 38 | // pub fn into_array(self, len: u32) -> Result> {} 39 | 40 | pub fn constant(self, value: u64, sign_extend: bool) -> Result> { 41 | unsafe { 42 | let value = LLVMConstInt(self.as_mut_ptr(), value, sign_extend as i32); 43 | 44 | IntValue::from_raw(value) 45 | } 46 | } 47 | 48 | pub fn erase(self) -> IntType<'ctx, Unknown> { 49 | IntType(self.0, PhantomData) 50 | } 51 | } 52 | 53 | impl<'ctx, W: IntWidth> IntType<'ctx, W> { 54 | pub(crate) fn as_ty(&self) -> Type<'ctx> { 55 | self.0 56 | } 57 | 58 | pub(crate) unsafe fn from_raw(raw: *mut LLVMType) -> Result { 59 | let non_nul = to_non_nul( 60 | raw, 61 | "Received a null pointer from LLVM while trying to create a Type", 62 | )?; 63 | 64 | Ok(Self::from_non_nul(non_nul)) 65 | } 66 | 67 | pub(crate) unsafe fn from_non_nul(ty: NonNull) -> Self { 68 | Self(Type::from_non_nul(ty), PhantomData) 69 | } 70 | 71 | pub(crate) fn as_mut_ptr(&self) -> *mut LLVMType { 72 | self.as_ty().as_mut_ptr() 73 | } 74 | } 75 | 76 | macro_rules! create_ints { 77 | ($($name:ident, $func:ident => $ty:ty),* $(,)?) => { 78 | $( 79 | impl<'ctx> IntType<'ctx, $ty> { 80 | pub fn $name(ctx: &'ctx Context) -> Result> { 81 | unsafe { IntType::from_raw($func(ctx.as_mut_ptr())) } 82 | } 83 | } 84 | )* 85 | }; 86 | } 87 | 88 | create_ints! { 89 | i1, LLVMInt1TypeInContext => I1, 90 | i8, LLVMInt8TypeInContext => i8, 91 | u8, LLVMInt8TypeInContext => u8, 92 | i16, LLVMInt16TypeInContext => i16, 93 | u16, LLVMInt16TypeInContext => u16, 94 | i32, LLVMInt32TypeInContext => i32, 95 | u32, LLVMInt32TypeInContext => u32, 96 | i64, LLVMInt64TypeInContext => i64, 97 | u64, LLVMInt64TypeInContext => u64, 98 | i128, LLVMInt128TypeInContext => i128, 99 | u128, LLVMInt128TypeInContext => u128, 100 | } 101 | 102 | impl<'ctx, W: IntWidth> AnyType<'ctx> for IntType<'ctx, W> {} 103 | 104 | impl<'ctx, W: IntWidth> SealedAnyType<'ctx> for IntType<'ctx, W> { 105 | fn as_ty(&self) -> Type<'ctx> { 106 | self.0 107 | } 108 | 109 | fn from_ty(value: Type<'ctx>) -> Self { 110 | Self(value, PhantomData) 111 | } 112 | } 113 | 114 | impl<'ctx, T: IntWidth> Sealed for IntType<'ctx, T> {} 115 | impl<'ctx, T: IntWidth> Pointable for IntType<'ctx, T> {} 116 | 117 | impl<'ctx, W: IntWidth> Into> for IntType<'ctx, W> { 118 | fn into(self) -> Type<'ctx> { 119 | self.0 120 | } 121 | } 122 | 123 | pub trait IntWidth: Sealed {} 124 | 125 | pub struct I1(()); 126 | 127 | impl IntWidth for I1 {} 128 | impl Sealed for I1 {} 129 | 130 | pub struct Unknown(()); 131 | 132 | impl IntWidth for Unknown {} 133 | impl Sealed for Unknown {} 134 | 135 | macro_rules! int_width { 136 | ($($width:ty),* $(,)?) => { 137 | $( 138 | impl IntWidth for $width {} 139 | impl Sealed for $width {} 140 | )* 141 | }; 142 | } 143 | 144 | int_width! { 145 | i8, u8, 146 | i16, u16, 147 | i32, u32, 148 | i64, u64, 149 | i128, u128, 150 | } 151 | 152 | macro_rules! fmt { 153 | () => { 154 | fmt! { 155 | @inner [Display, Debug, FmtPointer, UpperHex, LowerHex, Octal] 156 | } 157 | }; 158 | 159 | (@inner [$($fmt:ident),* $(,)?]) => { 160 | $( 161 | impl $fmt for IntType<'_, W> { 162 | fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { 163 | $fmt::fmt(&self.as_ty(), f) 164 | } 165 | } 166 | )* 167 | }; 168 | } 169 | 170 | fmt!(); 171 | -------------------------------------------------------------------------------- /crates/crunch-codegen/src/llvm/types/sealed_any_type.rs: -------------------------------------------------------------------------------- 1 | use crate::llvm::{types::Type, utils::to_non_nul, Result}; 2 | use llvm_sys::LLVMType; 3 | use std::ptr::NonNull; 4 | 5 | pub trait SealedAnyType<'ctx>: Sized { 6 | fn as_ty(&self) -> Type<'ctx>; 7 | fn from_ty(value: Type<'ctx>) -> Self; 8 | 9 | unsafe fn from_raw(raw: *mut LLVMType) -> Result { 10 | let non_nul = to_non_nul( 11 | raw, 12 | "Received a null pointer from LLVM while trying to create a Type", 13 | )?; 14 | 15 | Ok(Self::from_non_nul(non_nul)) 16 | } 17 | 18 | unsafe fn from_non_nul(ty: NonNull) -> Self { 19 | Self::from_ty(Type::from_non_nul(ty)) 20 | } 21 | 22 | fn as_ptr(&self) -> *const LLVMType { 23 | self.as_ty().as_ptr() 24 | } 25 | 26 | fn as_mut_ptr(&self) -> *mut LLVMType { 27 | self.as_ty().as_mut_ptr() 28 | } 29 | 30 | fn as_non_nul(&self) -> NonNull { 31 | self.as_ty().as_non_nul() 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /crates/crunch-codegen/src/llvm/types/ty.rs: -------------------------------------------------------------------------------- 1 | use crate::llvm::{ 2 | context::Context, 3 | types::{ArrayType, PointerType, SealedAnyType, TypeKind}, 4 | utils::{to_non_nul, AddressSpace}, 5 | Result, 6 | }; 7 | use llvm_sys::{ 8 | core::{ 9 | LLVMArrayType, LLVMGetElementType, LLVMGetTypeKind, LLVMPointerType, LLVMPrintTypeToString, 10 | }, 11 | LLVMType, 12 | }; 13 | use std::{ 14 | ffi::CStr, 15 | fmt::{ 16 | Debug, Display, Formatter, LowerHex, Octal, Pointer as FmtPointer, Result as FmtResult, 17 | UpperHex, 18 | }, 19 | hash::Hash, 20 | marker::PhantomData, 21 | ptr::NonNull, 22 | }; 23 | 24 | #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 25 | #[repr(transparent)] 26 | pub struct Type<'ctx> { 27 | ty: NonNull, 28 | __ctx: PhantomData<&'ctx Context>, 29 | } 30 | 31 | impl<'ctx> Type<'ctx> { 32 | pub(crate) fn make_pointer(self, address_space: AddressSpace) -> Result> { 33 | unsafe { 34 | PointerType::from_raw(LLVMPointerType( 35 | self.as_mut_ptr(), 36 | address_space as u8 as u32, 37 | )) 38 | } 39 | } 40 | 41 | pub(crate) fn make_array(self, len: u32) -> Result> { 42 | unsafe { ArrayType::from_raw(LLVMArrayType(self.as_mut_ptr(), len)) } 43 | } 44 | 45 | pub(crate) unsafe fn from_raw(raw: *mut LLVMType) -> Result { 46 | let ty = to_non_nul( 47 | raw, 48 | "Received a null pointer from LLVM while trying to create a Ty", 49 | )?; 50 | 51 | Ok(Self::from_non_nul(ty)) 52 | } 53 | 54 | pub(crate) const unsafe fn from_non_nul(ty: NonNull) -> Self { 55 | Self { 56 | ty, 57 | __ctx: PhantomData, 58 | } 59 | } 60 | 61 | pub(crate) const fn as_ptr(self) -> *const LLVMType { 62 | self.ty.as_ptr() 63 | } 64 | 65 | pub(crate) const fn as_mut_ptr(self) -> *mut LLVMType { 66 | self.ty.as_ptr() 67 | } 68 | 69 | pub(crate) const fn as_non_nul(self) -> NonNull { 70 | self.ty 71 | } 72 | 73 | pub(crate) fn kind(self) -> TypeKind { 74 | let kind = unsafe { LLVMGetTypeKind(self.as_mut_ptr()) }; 75 | 76 | TypeKind::from(kind) 77 | } 78 | 79 | pub(crate) fn element_type(self) -> Result> { 80 | unsafe { Type::from_raw(LLVMGetElementType(self.as_mut_ptr())) } 81 | } 82 | } 83 | 84 | impl Display for Type<'_> { 85 | fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { 86 | let string = unsafe { 87 | let value = to_non_nul( 88 | LLVMPrintTypeToString(self.as_mut_ptr()), 89 | "Received a null pointer from LLVM", 90 | ) 91 | .expect("Failed to print a LLVM type to a string"); 92 | 93 | CStr::from_ptr(value.as_ptr()).to_string_lossy() 94 | }; 95 | 96 | f.write_str(&string) 97 | } 98 | } 99 | 100 | impl Debug for Type<'_> { 101 | fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { 102 | let string = unsafe { 103 | let value = to_non_nul( 104 | LLVMPrintTypeToString(self.as_mut_ptr()), 105 | "Received a null pointer from LLVM", 106 | ) 107 | .expect("Failed to print a LLVM type to a string"); 108 | 109 | CStr::from_ptr(value.as_ptr()).to_string_lossy() 110 | }; 111 | 112 | f.write_str(&string) 113 | } 114 | } 115 | 116 | impl FmtPointer for Type<'_> { 117 | fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { 118 | write!(f, "{:p}", self.as_mut_ptr()) 119 | } 120 | } 121 | 122 | impl UpperHex for Type<'_> { 123 | fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { 124 | write!(f, "{:X}", self.as_mut_ptr() as usize) 125 | } 126 | } 127 | 128 | impl LowerHex for Type<'_> { 129 | fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { 130 | write!(f, "{:x}", self.as_mut_ptr() as usize) 131 | } 132 | } 133 | 134 | impl Octal for Type<'_> { 135 | fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { 136 | write!(f, "{:o}", self.as_mut_ptr() as usize) 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /crates/crunch-codegen/src/llvm/types/type_kind.rs: -------------------------------------------------------------------------------- 1 | use llvm_sys::LLVMTypeKind; 2 | 3 | #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 4 | #[repr(u8)] 5 | #[allow(non_camel_case_types)] 6 | pub enum TypeKind { 7 | Void, 8 | Half, 9 | Float, 10 | Double, 11 | X86_FP80, 12 | FP128, 13 | PC_FP128, 14 | Label, 15 | Integer, 16 | Function, 17 | Struct, 18 | Array, 19 | Pointer, 20 | Vector, 21 | Metadata, 22 | X86_MMX, 23 | Token, 24 | } 25 | 26 | #[rustfmt::skip] 27 | impl From for TypeKind { 28 | fn from(kind: LLVMTypeKind) -> Self { 29 | match kind { 30 | LLVMTypeKind::LLVMVoidTypeKind => Self::Void, 31 | LLVMTypeKind::LLVMHalfTypeKind => Self::Half, 32 | LLVMTypeKind::LLVMFloatTypeKind => Self::Float, 33 | LLVMTypeKind::LLVMDoubleTypeKind => Self::Double, 34 | LLVMTypeKind::LLVMX86_FP80TypeKind => Self::X86_FP80, 35 | LLVMTypeKind::LLVMFP128TypeKind => Self::FP128, 36 | LLVMTypeKind::LLVMPPC_FP128TypeKind => Self::PC_FP128, 37 | LLVMTypeKind::LLVMLabelTypeKind => Self::Label, 38 | LLVMTypeKind::LLVMIntegerTypeKind => Self::Integer, 39 | LLVMTypeKind::LLVMFunctionTypeKind => Self::Function, 40 | LLVMTypeKind::LLVMStructTypeKind => Self::Struct, 41 | LLVMTypeKind::LLVMArrayTypeKind => Self::Array, 42 | LLVMTypeKind::LLVMPointerTypeKind => Self::Pointer, 43 | LLVMTypeKind::LLVMVectorTypeKind => Self::Vector, 44 | LLVMTypeKind::LLVMMetadataTypeKind => Self::Metadata, 45 | LLVMTypeKind::LLVMX86_MMXTypeKind => Self::X86_MMX, 46 | LLVMTypeKind::LLVMTokenTypeKind => Self::Token, 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /crates/crunch-codegen/src/llvm/utils/address_space.rs: -------------------------------------------------------------------------------- 1 | use crate::llvm::{Error, ErrorKind, Result}; 2 | use std::convert::TryFrom; 3 | 4 | #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 5 | #[repr(u8)] 6 | #[rustfmt::skip] 7 | pub enum AddressSpace { 8 | Generic = 0, 9 | Global = 1, 10 | Shared = 2, 11 | Const = 3, 12 | Local = 4, 13 | } 14 | 15 | impl TryFrom for AddressSpace { 16 | type Error = Error; 17 | 18 | fn try_from(val: u32) -> Result { 19 | match val { 20 | 0 => Ok(AddressSpace::Generic), 21 | 1 => Ok(AddressSpace::Global), 22 | 3 => Ok(AddressSpace::Shared), 23 | 4 => Ok(AddressSpace::Const), 24 | 5 => Ok(AddressSpace::Local), 25 | 26 | _ => Err(Error::new( 27 | "LLVM returned an unrecognized AddressSpace", 28 | ErrorKind::LLVMError, 29 | )), 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /crates/crunch-codegen/src/llvm/utils/calling_convention.rs: -------------------------------------------------------------------------------- 1 | use crate::llvm::{Error, ErrorKind, Result}; 2 | use std::convert::TryFrom; 3 | 4 | // From https://llvm.org/doxygen/group__LLVMCCoreTypes.html#ga6bd315e1c1c05eb625e59a9f748e924f 5 | #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 6 | #[repr(u8)] 7 | #[rustfmt::skip] 8 | pub enum CallingConvention { 9 | C = 0, 10 | Fast = 8, 11 | Cold = 9, 12 | GHC = 10, 13 | HiPE = 11, 14 | WebKitJS = 12, 15 | AnyReg = 13, 16 | PreserveMost = 14, 17 | PreserveAll = 15, 18 | Swift = 16, 19 | CXXFASTTLS = 17, 20 | X86Stdcall = 64, 21 | X86Fastcall = 65, 22 | ARMAPCS = 66, 23 | ARMAAPCS = 67, 24 | ARMAAPCSVFP = 68, 25 | MSP430INTR = 69, 26 | X86ThisCall = 70, 27 | PTXKernel = 71, 28 | PTXDevice = 72, 29 | SPIRFUNC = 75, 30 | SPIRKERNEL = 76, 31 | IntelOCLBI = 77, 32 | X8664SysV = 78, 33 | Win64 = 79, 34 | X86VectorCall = 80, 35 | HHVM = 81, 36 | HHVMC = 82, 37 | X86INTR = 83, 38 | AVRINTR = 84, 39 | AVRSIGNAL = 85, 40 | AVRBUILTIN = 86, 41 | AMDGPUVS = 87, 42 | AMDGPUGS = 88, 43 | AMDGPUPS = 89, 44 | AMDGPUCS = 90, 45 | AMDGPUKERNEL = 91, 46 | X86RegCall = 92, 47 | AMDGPUHS = 93, 48 | MSP430BUILTIN = 94, 49 | AMDGPULS = 95, 50 | AMDGPUES = 96, 51 | } 52 | 53 | #[rustfmt::skip] 54 | impl TryFrom for CallingConvention { 55 | type Error = Error; 56 | 57 | fn try_from(convention: u32) -> Result { 58 | match convention { 59 | 0 => Ok(Self::C), 60 | 8 => Ok(Self::Fast), 61 | 9 => Ok(Self::Cold), 62 | 10 => Ok(Self::GHC), 63 | 11 => Ok(Self::HiPE), 64 | 12 => Ok(Self::WebKitJS), 65 | 13 => Ok(Self::AnyReg), 66 | 14 => Ok(Self::PreserveMost), 67 | 15 => Ok(Self::PreserveAll), 68 | 16 => Ok(Self::Swift), 69 | 17 => Ok(Self::CXXFASTTLS), 70 | 64 => Ok(Self::X86Stdcall), 71 | 65 => Ok(Self::X86Fastcall), 72 | 66 => Ok(Self::ARMAPCS), 73 | 67 => Ok(Self::ARMAAPCS), 74 | 68 => Ok(Self::ARMAAPCSVFP), 75 | 69 => Ok(Self::MSP430INTR), 76 | 70 => Ok(Self::X86ThisCall), 77 | 71 => Ok(Self::PTXKernel), 78 | 72 => Ok(Self::PTXDevice), 79 | 75 => Ok(Self::SPIRFUNC), 80 | 76 => Ok(Self::SPIRKERNEL), 81 | 77 => Ok(Self::IntelOCLBI), 82 | 78 => Ok(Self::X8664SysV), 83 | 79 => Ok(Self::Win64), 84 | 80 => Ok(Self::X86VectorCall), 85 | 81 => Ok(Self::HHVM), 86 | 82 => Ok(Self::HHVMC), 87 | 83 => Ok(Self::X86INTR), 88 | 84 => Ok(Self::AVRINTR), 89 | 85 => Ok(Self::AVRSIGNAL), 90 | 86 => Ok(Self::AVRBUILTIN), 91 | 87 => Ok(Self::AMDGPUVS), 92 | 88 => Ok(Self::AMDGPUGS), 93 | 89 => Ok(Self::AMDGPUPS), 94 | 90 => Ok(Self::AMDGPUCS), 95 | 91 => Ok(Self::AMDGPUKERNEL), 96 | 92 => Ok(Self::X86RegCall), 97 | 93 => Ok(Self::AMDGPUHS), 98 | 94 => Ok(Self::MSP430BUILTIN), 99 | 95 => Ok(Self::AMDGPULS), 100 | 96 => Ok(Self::AMDGPUES), 101 | 102 | _ => Err(Error::new( 103 | "LLVM returned an unrecognized AddressSpace", 104 | ErrorKind::LLVMError, 105 | )), 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /crates/crunch-codegen/src/llvm/utils/dll_storage_class.rs: -------------------------------------------------------------------------------- 1 | use llvm_sys::LLVMDLLStorageClass; 2 | 3 | #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 4 | #[repr(u8)] 5 | pub enum DLLStorageClass { 6 | Default, 7 | Import, 8 | Export, 9 | } 10 | 11 | #[rustfmt::skip] 12 | impl From for DLLStorageClass { 13 | fn from(class: LLVMDLLStorageClass) -> Self { 14 | match class { 15 | LLVMDLLStorageClass::LLVMDefaultStorageClass => Self::Default, 16 | LLVMDLLStorageClass::LLVMDLLImportStorageClass => Self::Import, 17 | LLVMDLLStorageClass::LLVMDLLExportStorageClass => Self::Export, 18 | } 19 | } 20 | } 21 | 22 | #[rustfmt::skip] 23 | impl Into for DLLStorageClass { 24 | fn into(self) -> LLVMDLLStorageClass { 25 | match self { 26 | Self::Default => LLVMDLLStorageClass::LLVMDefaultStorageClass, 27 | Self::Import => LLVMDLLStorageClass::LLVMDLLImportStorageClass, 28 | Self::Export => LLVMDLLStorageClass::LLVMDLLExportStorageClass, 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /crates/crunch-codegen/src/llvm/utils/int_operand.rs: -------------------------------------------------------------------------------- 1 | use llvm_sys::LLVMIntPredicate; 2 | 3 | pub enum IntOperand { 4 | Equal, 5 | NotEqual, 6 | UnsignedGreaterThan, 7 | UnsignedGreaterThanEqual, 8 | UnsignedLessThan, 9 | UnsignedLessThanEqual, 10 | SignedGreaterThan, 11 | SignedGreaterThanEqual, 12 | SignedLessThan, 13 | SignedLessThanEqual, 14 | } 15 | 16 | #[rustfmt::skip] 17 | impl From for IntOperand { 18 | fn from(pred: LLVMIntPredicate) -> Self { 19 | match pred { 20 | LLVMIntPredicate::LLVMIntEQ => Self::Equal, 21 | LLVMIntPredicate::LLVMIntNE => Self::NotEqual, 22 | LLVMIntPredicate::LLVMIntUGT => Self::UnsignedGreaterThan, 23 | LLVMIntPredicate::LLVMIntUGE => Self::UnsignedGreaterThanEqual, 24 | LLVMIntPredicate::LLVMIntULT => Self::UnsignedLessThan, 25 | LLVMIntPredicate::LLVMIntULE => Self::UnsignedLessThanEqual, 26 | LLVMIntPredicate::LLVMIntSGT => Self::SignedGreaterThan, 27 | LLVMIntPredicate::LLVMIntSGE => Self::SignedGreaterThanEqual, 28 | LLVMIntPredicate::LLVMIntSLT => Self::SignedLessThan, 29 | LLVMIntPredicate::LLVMIntSLE => Self::SignedLessThanEqual, 30 | } 31 | } 32 | } 33 | 34 | impl Into for IntOperand { 35 | fn into(self) -> LLVMIntPredicate { 36 | match self { 37 | Self::Equal => LLVMIntPredicate::LLVMIntEQ, 38 | Self::NotEqual => LLVMIntPredicate::LLVMIntNE, 39 | Self::UnsignedGreaterThan => LLVMIntPredicate::LLVMIntUGT, 40 | Self::UnsignedGreaterThanEqual => LLVMIntPredicate::LLVMIntUGE, 41 | Self::UnsignedLessThan => LLVMIntPredicate::LLVMIntULT, 42 | Self::UnsignedLessThanEqual => LLVMIntPredicate::LLVMIntULE, 43 | Self::SignedGreaterThan => LLVMIntPredicate::LLVMIntSGT, 44 | Self::SignedGreaterThanEqual => LLVMIntPredicate::LLVMIntSGE, 45 | Self::SignedLessThan => LLVMIntPredicate::LLVMIntSLT, 46 | Self::SignedLessThanEqual => LLVMIntPredicate::LLVMIntSLE, 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /crates/crunch-codegen/src/llvm/utils/integer_opts.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] 2 | pub enum Wrapping { 3 | NoSignedWrap, 4 | NoUnsignedWrap, 5 | None, 6 | } 7 | 8 | #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] 9 | pub enum DivideKind { 10 | Exact, 11 | Normal, 12 | } 13 | -------------------------------------------------------------------------------- /crates/crunch-codegen/src/llvm/utils/mod.rs: -------------------------------------------------------------------------------- 1 | //! Various LLVM utilities 2 | 3 | use crate::llvm::{Error, ErrorKind, Result}; 4 | use std::ptr::NonNull; 5 | 6 | mod address_space; 7 | mod calling_convention; 8 | mod dll_storage_class; 9 | mod int_operand; 10 | mod integer_opts; 11 | mod llvm_string; 12 | mod memory_buffer; 13 | mod thread_local_mode; 14 | 15 | pub use address_space::AddressSpace; 16 | pub use calling_convention::CallingConvention; 17 | pub use dll_storage_class::DLLStorageClass; 18 | pub use int_operand::IntOperand; 19 | pub use integer_opts::{DivideKind, Wrapping}; 20 | pub use llvm_string::LLVMString; 21 | pub use memory_buffer::MemoryBuffer; 22 | pub(crate) use sealed::Sealed; 23 | pub use thread_local_mode::ThreadLocalMode; 24 | 25 | /// Empty string, to be used where LLVM expects an instruction name, indicating 26 | /// that the instruction is to be left unnamed (i.e. numbered, in textual IR). 27 | // TODO: Use CStr::from_bytes_with_nul_unchecked once it's const-stable 28 | pub(crate) const EMPTY_CSTR: *const i8 = b"\0".as_ptr() as *const i8; 29 | 30 | mod sealed { 31 | pub trait Sealed {} 32 | } 33 | 34 | /// Converts a raw pointer into a [`Result`]`<`[`NonNull`]`, `[`Error`]`>`, using `message` 35 | /// as the error message 36 | /// 37 | /// [`Result`]: https://doc.rust-lang.org/std/result/enum.Result.html 38 | /// [`NonNull`]: https://doc.rust-lang.org/std/ptr/struct.NonNull.html 39 | /// [`Error`]: crate::llvm::Error 40 | pub(crate) fn to_non_nul(raw: *mut T, message: S) -> Result> 41 | where 42 | S: Into, 43 | { 44 | NonNull::new(raw).ok_or_else(|| Error::new(message, ErrorKind::NullPtr)) 45 | } 46 | 47 | #[cfg(test)] 48 | mod tests { 49 | use super::*; 50 | use std::ptr; 51 | 52 | #[test] 53 | fn to_non_nul_catches_nulls() { 54 | let ptr = to_non_nul::(ptr::null_mut(), "This is my error message"); 55 | 56 | assert!(ptr.is_err()); 57 | assert_eq!( 58 | ptr.unwrap_err(), 59 | Error::new("This is my error message", ErrorKind::NullPtr), 60 | ); 61 | } 62 | 63 | #[test] 64 | fn to_non_nul_allows_non_nulls() { 65 | let mut you_ate = 8u8; 66 | let ptr = to_non_nul::(&mut you_ate, "This is my error message"); 67 | 68 | assert!(ptr.is_ok()); 69 | unsafe { 70 | assert_eq!(*ptr.unwrap().as_ref(), you_ate); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /crates/crunch-codegen/src/llvm/utils/thread_local_mode.rs: -------------------------------------------------------------------------------- 1 | use llvm_sys::LLVMThreadLocalMode; 2 | 3 | #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 4 | #[repr(u8)] 5 | pub enum ThreadLocalMode { 6 | GeneralDynamicTLS, 7 | LocalDynamicTLS, 8 | InitialExecTLS, 9 | LocalExecTLS, 10 | } 11 | 12 | impl ThreadLocalMode { 13 | #[rustfmt::skip] 14 | pub(crate) fn from_llvm_mode(mode: LLVMThreadLocalMode) -> Option { 15 | match mode { 16 | LLVMThreadLocalMode::LLVMNotThreadLocal => None, 17 | LLVMThreadLocalMode::LLVMGeneralDynamicTLSModel => Some(Self::GeneralDynamicTLS), 18 | LLVMThreadLocalMode::LLVMLocalDynamicTLSModel => Some(Self::LocalDynamicTLS), 19 | LLVMThreadLocalMode::LLVMInitialExecTLSModel => Some(Self::InitialExecTLS), 20 | LLVMThreadLocalMode::LLVMLocalExecTLSModel => Some(Self::LocalExecTLS), 21 | } 22 | } 23 | 24 | #[rustfmt::skip] 25 | #[allow(clippy::wrong_self_convention)] 26 | pub(crate) fn into_llvm_mode(mode: Option) -> LLVMThreadLocalMode { 27 | match mode { 28 | None => LLVMThreadLocalMode::LLVMNotThreadLocal, 29 | Some(Self::GeneralDynamicTLS) => LLVMThreadLocalMode::LLVMGeneralDynamicTLSModel, 30 | Some(Self::LocalDynamicTLS) => LLVMThreadLocalMode::LLVMLocalDynamicTLSModel, 31 | Some(Self::InitialExecTLS) => LLVMThreadLocalMode::LLVMInitialExecTLSModel, 32 | Some(Self::LocalExecTLS) => LLVMThreadLocalMode::LLVMLocalExecTLSModel, 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /crates/crunch-codegen/src/llvm/value.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | llvm::{context::Context, types::Type, Result}, 3 | null, 4 | }; 5 | use llvm_sys::{ 6 | core::{ 7 | LLVMConstIntGetSExtValue, LLVMIsAAllocaInst, LLVMIsABinaryOperator, LLVMIsABranchInst, 8 | LLVMIsACallInst, LLVMIsACastInst, LLVMIsACmpInst, LLVMIsAConstant, LLVMIsAConstantInt, 9 | LLVMIsAFenceInst, LLVMIsAFunction, LLVMIsAInlineAsm, LLVMIsALoadInst, LLVMIsAReturnInst, 10 | LLVMIsAStoreInst, LLVMIsATruncInst, LLVMIsAUnreachableInst, LLVMIsAVAArgInst, LLVMIsNull, 11 | LLVMIsUndef, LLVMPrintValueToString, LLVMSetValueName2, LLVMTypeOf, LLVMValueIsBasicBlock, 12 | }, 13 | LLVMValue, 14 | }; 15 | use std::{ 16 | ffi::{CStr, CString}, 17 | fmt, 18 | marker::PhantomData, 19 | ptr::NonNull, 20 | }; 21 | 22 | #[derive(Copy, Clone, PartialEq, Eq)] 23 | #[repr(transparent)] 24 | pub struct Value<'a>(NonNull, PhantomData<&'a Context>); 25 | 26 | // Public interface 27 | impl<'a> Value<'a> { 28 | pub fn get_type(self) -> Result> { 29 | let ty = null!( 30 | unsafe { LLVMTypeOf(self.as_mut_ptr()) }, 31 | "Failed to get type of value", 32 | )?; 33 | 34 | Ok(Type::from_raw(ty)) 35 | } 36 | 37 | pub fn const_int_val(self) -> Option { 38 | if self.is_const_int() { 39 | Some(unsafe { LLVMConstIntGetSExtValue(self.as_mut_ptr()) }) 40 | } else { 41 | None 42 | } 43 | } 44 | 45 | pub fn set_name(self, name: &str) { 46 | let cname = CString::new(name).expect("Rust strings cannot have null bytes"); 47 | 48 | unsafe { LLVMSetValueName2(self.as_mut_ptr(), cname.as_ptr(), name.len()) }; 49 | } 50 | 51 | pub fn with_name(self, name: &str) -> Self { 52 | self.set_name(name); 53 | self 54 | } 55 | } 56 | 57 | // Private interface 58 | impl<'a> Value<'a> { 59 | pub(crate) fn from_raw(raw: *mut LLVMValue) -> Result { 60 | let block = to_non_nul( 61 | raw, 62 | "Received a null pointer from LLVM while trying to create a Value", 63 | )?; 64 | 65 | Ok(Self::from_non_nul(block)) 66 | } 67 | 68 | pub(crate) const fn from_non_nul(value: NonNull) -> Self { 69 | Self(value, PhantomData) 70 | } 71 | 72 | pub(crate) const fn as_mut_ptr(self) -> *mut LLVMValue { 73 | self.0.as_ptr() as *mut LLVMValue 74 | } 75 | } 76 | 77 | impl fmt::Debug for Value<'_> { 78 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 79 | let value = unsafe { CStr::from_ptr(LLVMPrintValueToString(self.as_mut_ptr())) }; 80 | 81 | write!(f, "{}", value.to_string_lossy()) 82 | } 83 | } 84 | 85 | impl fmt::Pointer for Value<'_> { 86 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 87 | write!(f, "{:p}", self.0) 88 | } 89 | } 90 | 91 | #[cfg(test)] 92 | mod tests { 93 | use crate::llvm::LLVM; 94 | 95 | #[test] 96 | fn create_value() { 97 | let llvm = LLVM::new().unwrap(); 98 | let module = llvm.create_module("module").unwrap(); 99 | let sig = module 100 | .function_ty(&[], llvm.get_type::<()>().unwrap()) 101 | .unwrap(); 102 | let _value = module.build_function("function", &sig, |_| Ok(())).unwrap(); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /crates/crunch-codegen/src/llvm/values/any_value.rs: -------------------------------------------------------------------------------- 1 | use crate::llvm::{ 2 | types::{Type, TypeKind}, 3 | values::{SealedAnyValue, Value, ValueUsage}, 4 | Result, 5 | }; 6 | use llvm_sys::core::{ 7 | LLVMGetFirstUse, LLVMGetValueName2, LLVMIsAAllocaInst, LLVMIsABinaryOperator, 8 | LLVMIsABranchInst, LLVMIsACallInst, LLVMIsACastInst, LLVMIsACmpInst, LLVMIsAConstant, 9 | LLVMIsAConstantInt, LLVMIsAFenceInst, LLVMIsAFunction, LLVMIsAInlineAsm, LLVMIsALoadInst, 10 | LLVMIsAReturnInst, LLVMIsAStoreInst, LLVMIsATruncInst, LLVMIsAUnreachableInst, 11 | LLVMIsAVAArgInst, LLVMIsNull, LLVMIsUndef, LLVMReplaceAllUsesWith, LLVMSetValueName2, 12 | LLVMTypeOf, LLVMValueIsBasicBlock, 13 | }; 14 | use std::{borrow::Cow, convert::TryFrom, ptr::NonNull}; 15 | 16 | // TODO: Docs for both the macro and the produced functions 17 | macro_rules! value_is { 18 | ($($name:ident => $func:path),* $(,)?) => { 19 | $( 20 | fn $name(&self) -> bool { 21 | unsafe { $func(self.as_mut_ptr()) as usize != 0 } 22 | } 23 | )* 24 | }; 25 | } 26 | 27 | pub trait AnyValue<'ctx>: SealedAnyValue<'ctx> { 28 | /// Replace all uses of one `Value` with another 29 | // https://llvm.org/doxygen/classllvm_1_1Value.html#a3ab5fc45117b450e8bb04e564cb6e5f2 30 | fn replace_all_uses(self, new: Self) -> Self { 31 | unsafe { LLVMReplaceAllUsesWith(self.as_mut_ptr(), new.as_mut_ptr()) }; 32 | 33 | new 34 | } 35 | 36 | fn as_value(&self) -> Value<'ctx> { 37 | Value::from_val(self.as_val()) 38 | } 39 | 40 | fn as_type(&self) -> Result> { 41 | unsafe { Type::from_raw(LLVMTypeOf(self.as_mut_ptr())) } 42 | } 43 | 44 | fn kind(&self) -> Result { 45 | Ok(self.as_type()?.kind()) 46 | } 47 | 48 | fn name<'a>(&'a self) -> Option> { 49 | self.get_name_raw() 50 | .map(|name| std::str::from_utf8(name).map_err(|err| err.into())) 51 | } 52 | 53 | fn name_lossy(&self) -> Option> { 54 | self.get_name_raw().map(String::from_utf8_lossy) 55 | } 56 | 57 | fn set_name(&self, name: &str) { 58 | self.set_name_raw(name.as_bytes()); 59 | } 60 | 61 | // https://github.com/rust-lang/rust/pull/67033 62 | fn get_name_raw(&self) -> Option<&[u8]> { 63 | unsafe { 64 | let mut len = 0; 65 | let data = NonNull::new(LLVMGetValueName2(self.as_mut_ptr(), &mut len) as *mut u8)?; 66 | 67 | Some(std::slice::from_raw_parts(data.as_ptr(), len)) 68 | } 69 | } 70 | 71 | // https://github.com/rust-lang/rust/pull/67033 72 | fn set_name_raw(&self, name: &[u8]) { 73 | unsafe { 74 | let data = name.as_ptr().cast(); 75 | LLVMSetValueName2(self.as_mut_ptr(), data, name.len()); 76 | } 77 | } 78 | 79 | fn get_first_use(&self) -> Option> { 80 | unsafe { 81 | let usage = LLVMGetFirstUse(self.as_mut_ptr()); 82 | 83 | NonNull::new(usage).map(|usage| ValueUsage::from_non_nul(usage)) 84 | } 85 | } 86 | 87 | fn downcast> + 'ctx>(&self) -> Option { 88 | T::try_from(self.as_value()).ok() 89 | } 90 | 91 | // TODO: There are more of these... 92 | value_is! { 93 | is_undef => LLVMIsUndef, 94 | is_null => LLVMIsNull, 95 | is_basic_block => LLVMValueIsBasicBlock, 96 | is_function => LLVMIsAFunction, 97 | is_load => LLVMIsALoadInst, 98 | is_cmp => LLVMIsACmpInst, 99 | is_call => LLVMIsACallInst, 100 | is_cast => LLVMIsACastInst, 101 | is_branch => LLVMIsABranchInst, 102 | is_fence => LLVMIsAFenceInst, 103 | is_inline_asm => LLVMIsAInlineAsm, 104 | is_store => LLVMIsAStoreInst, 105 | is_trunc => LLVMIsATruncInst, 106 | is_var_arg => LLVMIsAVAArgInst, 107 | is_alloc => LLVMIsAAllocaInst, 108 | is_binary_op => LLVMIsABinaryOperator, 109 | is_return => LLVMIsAReturnInst, 110 | is_unreachable => LLVMIsAUnreachableInst, 111 | is_const_int => LLVMIsAConstantInt, 112 | is_constant => LLVMIsAConstant, 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /crates/crunch-codegen/src/llvm/values/basic_block.rs: -------------------------------------------------------------------------------- 1 | use crate::llvm::{ 2 | context::Context, 3 | utils::to_non_nul, 4 | values::{sealed::SealedAnyValue, AnyValue, Val, Value}, 5 | Error, ErrorKind, Result, 6 | }; 7 | use llvm_sys::{ 8 | core::{ 9 | LLVMBasicBlockAsValue, LLVMDeleteBasicBlock, LLVMGetBasicBlockName, 10 | LLVMGetBasicBlockParent, LLVMMoveBasicBlockAfter, LLVMMoveBasicBlockBefore, 11 | LLVMValueAsBasicBlock, 12 | }, 13 | LLVMBasicBlock, 14 | }; 15 | use std::{ 16 | borrow::Cow, convert::TryFrom, ffi::CStr, hash::Hash, marker::PhantomData, ptr::NonNull, 17 | }; 18 | 19 | #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 20 | #[repr(transparent)] 21 | pub struct BasicBlock<'ctx> { 22 | block: NonNull, 23 | __ctx: PhantomData<&'ctx Context>, 24 | } 25 | 26 | impl<'ctx> BasicBlock<'ctx> { 27 | pub fn name(&self) -> Option> { 28 | unsafe { 29 | let name = NonNull::new(LLVMGetBasicBlockName((*self).as_mut_ptr()) as *mut i8); 30 | 31 | name.map(|name| CStr::from_ptr(name.as_ptr()).to_string_lossy()) 32 | } 33 | } 34 | 35 | pub fn parent(self) -> Option { 36 | unsafe { 37 | Val::from_raw(LLVMGetBasicBlockParent(self.as_mut_ptr())) 38 | .and_then(Self::from_val) 39 | .ok() 40 | } 41 | } 42 | 43 | pub fn move_before(self, other: impl AsRef) { 44 | unsafe { 45 | LLVMMoveBasicBlockBefore(self.as_mut_ptr(), BasicBlock::as_mut_ptr(*other.as_ref())) 46 | } 47 | } 48 | 49 | pub fn move_after(self, other: impl AsRef) { 50 | unsafe { 51 | LLVMMoveBasicBlockAfter(self.as_mut_ptr(), BasicBlock::as_mut_ptr(*other.as_ref())) 52 | } 53 | } 54 | 55 | pub fn delete(self) { 56 | unsafe { LLVMDeleteBasicBlock(self.as_mut_ptr()) }; 57 | } 58 | } 59 | 60 | impl<'ctx> BasicBlock<'ctx> { 61 | pub(crate) fn from_val(value: Val<'ctx>) -> Result { 62 | let block = unsafe { 63 | to_non_nul( 64 | LLVMValueAsBasicBlock(value.as_mut_ptr()), 65 | "Failed to turn Val into BasicBlock", 66 | ) 67 | }?; 68 | 69 | // Safety: The pointer is actually to an `LLVMBasicBlock` 70 | Ok(unsafe { BasicBlock::from_non_nul(block) }) 71 | } 72 | 73 | pub(crate) unsafe fn from_raw(raw: *mut LLVMBasicBlock) -> Result { 74 | let block = to_non_nul( 75 | raw, 76 | "Received a null pointer from LLVM while trying to create a Value", 77 | )?; 78 | 79 | Ok(Self::from_non_nul(block)) 80 | } 81 | 82 | pub(crate) const unsafe fn from_non_nul(block: NonNull) -> Self { 83 | Self { 84 | block, 85 | __ctx: PhantomData, 86 | } 87 | } 88 | 89 | pub(crate) const fn as_mut_ptr(self) -> *mut LLVMBasicBlock { 90 | self.block.as_ptr() 91 | } 92 | } 93 | 94 | impl<'ctx> AnyValue<'ctx> for BasicBlock<'ctx> { 95 | fn get_name_raw(&self) -> Option<&[u8]> { 96 | let string = unsafe { 97 | let ptr = 98 | NonNull::new(LLVMGetBasicBlockName(BasicBlock::as_mut_ptr(*self)) as *mut i8)?; 99 | 100 | CStr::from_ptr(ptr.as_ptr()) 101 | }; 102 | 103 | Some(string.to_bytes()) 104 | } 105 | 106 | // TODO: Setting the name of a block via its value may not be possible, test it 107 | } 108 | 109 | impl<'ctx> SealedAnyValue<'ctx> for BasicBlock<'ctx> { 110 | /// This method will panic if LLVM returns a null pointer 111 | fn as_val(&self) -> Val<'ctx> { 112 | unsafe { 113 | Val::from_raw(LLVMBasicBlockAsValue(BasicBlock::as_mut_ptr(*self))) 114 | .expect("Failed to convert a BasicBlock into a Val") 115 | } 116 | } 117 | 118 | /// This method will panic if the value it's called on is not a `BasicBlock` 119 | fn from_val(value: Val<'ctx>) -> Self { 120 | BasicBlock::from_val(value).unwrap_or_else(|_| { 121 | panic!( 122 | "Cannot turn a Val of kind {:?} into a BasicBlock", 123 | value.kind() 124 | ) 125 | }) 126 | } 127 | } 128 | 129 | impl<'ctx> Into> for BasicBlock<'ctx> { 130 | fn into(self) -> Value<'ctx> { 131 | Value::BasicBlock(self) 132 | } 133 | } 134 | 135 | impl<'ctx> TryFrom> for BasicBlock<'ctx> { 136 | type Error = Error; 137 | 138 | fn try_from(val: Value<'ctx>) -> Result { 139 | if let Value::BasicBlock(block) = val { 140 | Ok(block) 141 | } else { 142 | Err(Error::new( 143 | format!( 144 | "A value of type {:?} cannot be made into an BasicBlock", 145 | val.kind() 146 | ), 147 | ErrorKind::MismatchedTypes, 148 | )) 149 | } 150 | } 151 | } 152 | 153 | impl<'ctx> AsRef> for BasicBlock<'ctx> { 154 | fn as_ref(&self) -> &BasicBlock<'ctx> { 155 | self 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /crates/crunch-codegen/src/llvm/values/function.rs: -------------------------------------------------------------------------------- 1 | use crate::llvm::{ 2 | module::Linkage, 3 | types::{FunctionSig, SealedAnyType, TypeKind}, 4 | utils::CallingConvention, 5 | values::{AnyValue, BasicBlock, BlockAddress, SealedAnyValue, Val, Value}, 6 | Error, ErrorKind, Result, 7 | }; 8 | use llvm_sys::{ 9 | analysis::LLVMViewFunctionCFG, 10 | core::{ 11 | LLVMBlockAddress, LLVMCountBasicBlocks, LLVMCountParams, LLVMDeleteFunction, 12 | LLVMGetFunctionCallConv, LLVMGetLinkage, LLVMGetParams, LLVMSetFunctionCallConv, 13 | LLVMSetLinkage, 14 | }, 15 | LLVMValue, 16 | }; 17 | use std::{convert::TryFrom, mem::MaybeUninit}; 18 | 19 | #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 20 | #[repr(transparent)] 21 | pub struct FunctionValue<'ctx>(Val<'ctx>); 22 | 23 | impl<'ctx> FunctionValue<'ctx> { 24 | pub fn args(self) -> Result>> { 25 | unsafe { 26 | let mut args: Vec> = 27 | vec![MaybeUninit::zeroed(); self.num_args() as usize]; 28 | LLVMGetParams(self.as_mut_ptr(), args.as_mut_ptr() as *mut *mut LLVMValue); 29 | 30 | args.into_iter() 31 | .map(|arg| Value::from_raw(arg.assume_init())) 32 | .collect() 33 | } 34 | } 35 | 36 | pub fn signature(self) -> Result> { 37 | let mut function = self.as_type()?; 38 | while function.kind() == TypeKind::Pointer { 39 | function = function.element_type()?; 40 | } 41 | 42 | Ok(FunctionSig::from_ty(function)) 43 | } 44 | 45 | pub fn num_args(self) -> u32 { 46 | unsafe { LLVMCountParams(self.as_mut_ptr()) } 47 | } 48 | 49 | pub fn linkage(self) -> Linkage { 50 | unsafe { Linkage::from(LLVMGetLinkage(self.as_mut_ptr())) } 51 | } 52 | 53 | pub fn set_linkage(self, linkage: Linkage) { 54 | unsafe { LLVMSetLinkage(self.as_mut_ptr(), linkage.into()) }; 55 | } 56 | 57 | pub fn with_linkage(self, linkage: Linkage) -> Self { 58 | unsafe { LLVMSetLinkage(self.as_mut_ptr(), linkage.into()) }; 59 | self 60 | } 61 | 62 | pub fn num_blocks(self) -> u32 { 63 | unsafe { LLVMCountBasicBlocks(self.as_mut_ptr()) } 64 | } 65 | 66 | // TODO: Getting the address of a block requires the source function, can we 67 | // do a sneaky and make this a method on `BasicBlock`? 68 | pub fn block_address(self, block: BasicBlock<'ctx>) -> Result> { 69 | unsafe { 70 | let address = LLVMBlockAddress(self.as_mut_ptr(), block.as_mut_ptr()); 71 | 72 | BlockAddress::from_raw(address) 73 | } 74 | } 75 | 76 | pub fn calling_convention(self) -> Result { 77 | CallingConvention::try_from(unsafe { LLVMGetFunctionCallConv(self.as_mut_ptr()) }) 78 | } 79 | 80 | pub fn set_calling_convention(self, calling_convention: CallingConvention) { 81 | unsafe { LLVMSetFunctionCallConv(self.as_mut_ptr(), calling_convention as u8 as u32) } 82 | } 83 | 84 | pub fn with_calling_convention(self, calling_convention: CallingConvention) -> Self { 85 | unsafe { LLVMSetFunctionCallConv(self.as_mut_ptr(), calling_convention as u8 as u32) }; 86 | self 87 | } 88 | 89 | pub fn delete(self) { 90 | unsafe { LLVMDeleteFunction(self.as_mut_ptr()) } 91 | } 92 | 93 | pub fn view_cfg(self) { 94 | unsafe { LLVMViewFunctionCFG(self.as_mut_ptr()) } 95 | } 96 | } 97 | 98 | impl<'ctx> AnyValue<'ctx> for FunctionValue<'ctx> {} 99 | 100 | impl<'ctx> SealedAnyValue<'ctx> for FunctionValue<'ctx> { 101 | fn as_val(&self) -> Val<'ctx> { 102 | self.0 103 | } 104 | 105 | fn from_val(value: Val<'ctx>) -> Self { 106 | Self(value) 107 | } 108 | } 109 | 110 | impl<'ctx> Into> for FunctionValue<'ctx> { 111 | fn into(self) -> Value<'ctx> { 112 | Value::Function(self) 113 | } 114 | } 115 | 116 | impl<'ctx> TryFrom> for FunctionValue<'ctx> { 117 | type Error = Error; 118 | 119 | fn try_from(val: Value<'ctx>) -> Result { 120 | if let Value::Function(func) = val { 121 | Ok(func) 122 | } else { 123 | Err(Error::new( 124 | format!( 125 | "A value of type {:?} cannot be made into an Function", 126 | val.kind() 127 | ), 128 | ErrorKind::MismatchedTypes, 129 | )) 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /crates/crunch-codegen/src/llvm/values/integer.rs: -------------------------------------------------------------------------------- 1 | use crate::llvm::{ 2 | utils::Sealed, 3 | values::{sealed::SealedAnyValue, AnyValue, Val, Value}, 4 | Error, ErrorKind, Result, 5 | }; 6 | use std::convert::TryFrom; 7 | 8 | pub trait IntMath<'ctx>: AnyValue<'ctx> {} 9 | 10 | #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 11 | #[repr(transparent)] 12 | pub struct IntValue<'ctx>(Val<'ctx>); 13 | 14 | impl<'ctx> AnyValue<'ctx> for IntValue<'ctx> {} 15 | 16 | impl<'ctx> SealedAnyValue<'ctx> for IntValue<'ctx> { 17 | fn as_val(&self) -> Val<'ctx> { 18 | self.0 19 | } 20 | 21 | fn from_val(value: Val<'ctx>) -> Self { 22 | Self(value) 23 | } 24 | } 25 | 26 | impl<'ctx> Into> for IntValue<'ctx> { 27 | fn into(self) -> Value<'ctx> { 28 | Value::ConstInt(self) 29 | } 30 | } 31 | 32 | impl<'ctx> Sealed for IntValue<'ctx> {} 33 | 34 | impl<'ctx> TryFrom> for IntValue<'ctx> { 35 | type Error = Error; 36 | 37 | fn try_from(val: Value<'ctx>) -> Result { 38 | if let Value::ConstInt(int) = val { 39 | Ok(int) 40 | } else { 41 | Err(Error::new( 42 | format!( 43 | "A value of type {:?} cannot be made into an Int", 44 | val.kind() 45 | ), 46 | ErrorKind::MismatchedTypes, 47 | )) 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /crates/crunch-codegen/src/llvm/values/pointer.rs: -------------------------------------------------------------------------------- 1 | use crate::llvm::{ 2 | types::{ArrayType, FloatType, PointerType, StructType, VectorType}, 3 | utils::{DLLStorageClass, Sealed, ThreadLocalMode}, 4 | values::{AnyValue, SealedAnyValue, Val}, 5 | }; 6 | use llvm_sys::core::{ 7 | LLVMGetDLLStorageClass, LLVMGetInitializer, LLVMGetThreadLocalMode, LLVMIsThreadLocal, 8 | LLVMSetDLLStorageClass, LLVMSetInitializer, LLVMSetThreadLocal, LLVMSetThreadLocalMode, 9 | }; 10 | use std::{ 11 | fmt::{Debug, Formatter, Result as FmtResult}, 12 | marker::PhantomData, 13 | }; 14 | 15 | #[derive(PartialEq, Eq, PartialOrd, Ord, Hash)] 16 | #[repr(transparent)] 17 | pub struct PointerValue<'ctx, T>(Val<'ctx>, PhantomData); 18 | 19 | impl<'ctx, T: Copy> PointerValue<'ctx, Global> { 20 | pub fn dll_storage_class(self) -> DLLStorageClass { 21 | DLLStorageClass::from(unsafe { LLVMGetDLLStorageClass(self.0.as_mut_ptr()) }) 22 | } 23 | 24 | pub fn set_dll_storage_class(self, storage_class: DLLStorageClass) { 25 | unsafe { LLVMSetDLLStorageClass(self.0.as_mut_ptr(), storage_class.into()) } 26 | } 27 | 28 | pub fn with_dll_storage_class(self, storage_class: DLLStorageClass) -> Self { 29 | self.set_dll_storage_class(storage_class); 30 | self 31 | } 32 | 33 | pub fn thread_local(self) -> bool { 34 | unsafe { LLVMIsThreadLocal(self.0.as_mut_ptr()) == 1 } 35 | } 36 | 37 | pub fn set_thread_local(self, is_thread_local: bool) { 38 | unsafe { LLVMSetThreadLocal(self.0.as_mut_ptr(), is_thread_local as i32) } 39 | } 40 | 41 | pub fn with_thread_local(self, is_thread_local: bool) -> Self { 42 | self.set_thread_local(is_thread_local); 43 | self 44 | } 45 | 46 | pub fn thread_local_mode(self) -> Option { 47 | let thread_local_mode = unsafe { LLVMGetThreadLocalMode(self.0.as_mut_ptr()) }; 48 | 49 | ThreadLocalMode::from_llvm_mode(thread_local_mode) 50 | } 51 | 52 | pub fn set_thread_local_mode(self, thread_local_mode: Option) { 53 | self.set_thread_local(true); 54 | 55 | unsafe { 56 | LLVMSetThreadLocalMode( 57 | self.0.as_mut_ptr(), 58 | ThreadLocalMode::into_llvm_mode(thread_local_mode), 59 | ); 60 | } 61 | } 62 | 63 | pub fn with_thread_local_mode(self, thread_local_mode: Option) -> Self { 64 | self.set_thread_local_mode(thread_local_mode); 65 | self 66 | } 67 | } 68 | 69 | impl<'ctx, T: AnyValue<'ctx>> PointerValue<'ctx, Global> { 70 | pub fn get_initializer(self) -> Option { 71 | unsafe { T::from_raw(LLVMGetInitializer(self.0.as_mut_ptr())).ok() } 72 | } 73 | 74 | pub fn set_initializer(self, value: T) { 75 | unsafe { LLVMSetInitializer(self.0.as_mut_ptr(), value.as_mut_ptr()) } 76 | } 77 | 78 | pub fn with_initializer(self, value: T) -> Self { 79 | self.set_initializer(value); 80 | 81 | self 82 | } 83 | } 84 | 85 | impl<'ctx, T> AnyValue<'ctx> for PointerValue<'ctx, T> {} 86 | 87 | impl<'ctx, T> SealedAnyValue<'ctx> for PointerValue<'ctx, T> { 88 | fn as_val(&self) -> Val<'ctx> { 89 | self.0 90 | } 91 | 92 | fn from_val(value: Val<'ctx>) -> Self { 93 | Self(value, PhantomData) 94 | } 95 | } 96 | 97 | impl<'ctx, T> Sealed for PointerValue<'ctx, T> {} 98 | 99 | impl Debug for PointerValue<'_, T> { 100 | fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { 101 | Debug::fmt(&self.0, f) 102 | } 103 | } 104 | 105 | impl<'ctx, T> Clone for PointerValue<'ctx, T> { 106 | fn clone(&self) -> Self { 107 | *self 108 | } 109 | } 110 | 111 | impl<'ctx, T> Copy for PointerValue<'ctx, T> {} 112 | 113 | pub trait Pointable: Sealed {} 114 | 115 | impl<'ctx> Pointable for FloatType<'ctx> {} 116 | impl<'ctx> Pointable for PointerType<'ctx> {} 117 | impl<'ctx> Pointable for StructType<'ctx> {} 118 | impl<'ctx, T: Pointable> Pointable for VectorType<'ctx, T> {} 119 | impl<'ctx> Pointable for ArrayType<'ctx> {} 120 | 121 | #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 122 | pub struct Global(PhantomData); 123 | 124 | impl<'ctx, T: Sealed> Sealed for Global {} 125 | -------------------------------------------------------------------------------- /crates/crunch-codegen/src/llvm/values/sealed.rs: -------------------------------------------------------------------------------- 1 | use crate::llvm::{utils::to_non_nul, values::Val, Result}; 2 | use llvm_sys::LLVMValue; 3 | use std::ptr::NonNull; 4 | 5 | pub trait SealedAnyValue<'ctx>: Sized { 6 | fn as_val(&self) -> Val<'ctx>; 7 | fn from_val(value: Val<'ctx>) -> Self; 8 | 9 | unsafe fn from_raw(raw: *mut LLVMValue) -> Result { 10 | let non_nul = to_non_nul( 11 | raw, 12 | "Received a null pointer from LLVM while trying to create a Value", 13 | )?; 14 | 15 | Ok(Self::from_non_nul(non_nul)) 16 | } 17 | 18 | unsafe fn from_non_nul(value: NonNull) -> Self { 19 | Self::from_val(Val::from_non_nul(value)) 20 | } 21 | 22 | fn as_ptr(&self) -> *const LLVMValue { 23 | self.as_val().as_ptr() 24 | } 25 | 26 | fn as_mut_ptr(&self) -> *mut LLVMValue { 27 | self.as_val().as_mut_ptr() 28 | } 29 | 30 | fn as_non_nul(&self) -> NonNull { 31 | self.as_val().as_non_nul() 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /crates/crunch-codegen/src/llvm/values/value_kind.rs: -------------------------------------------------------------------------------- 1 | use llvm_sys::LLVMValueKind; 2 | 3 | #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 4 | #[repr(u8)] 5 | pub enum ValueKind { 6 | Argument, 7 | BasicBlock, 8 | MemoryUse, 9 | MemoryDef, 10 | MemoryPhi, 11 | Function, 12 | GlobalAlias, 13 | GlobalIFunc, 14 | GlobalVariable, 15 | BlockAddress, 16 | ConstExpr, 17 | ConstArray, 18 | ConstStruct, 19 | ConstVector, 20 | ConstInt, 21 | ConstFloat, 22 | ConstAggregateZero, 23 | ConstDataArray, 24 | ConstDataVector, 25 | ConstPointerNull, 26 | ConstTokenNone, 27 | Undef, 28 | MetadataAsValue, 29 | InlineAsm, 30 | Instruction, 31 | } 32 | 33 | #[rustfmt::skip] 34 | impl From for ValueKind { 35 | fn from(value: LLVMValueKind) -> Self { 36 | match value { 37 | LLVMValueKind::LLVMArgumentValueKind => Self::Argument, 38 | LLVMValueKind::LLVMBasicBlockValueKind => Self::BasicBlock, 39 | LLVMValueKind::LLVMMemoryUseValueKind => Self::MemoryUse, 40 | LLVMValueKind::LLVMMemoryDefValueKind => Self::MemoryDef, 41 | LLVMValueKind::LLVMMemoryPhiValueKind => Self::MemoryPhi, 42 | LLVMValueKind::LLVMFunctionValueKind => Self::Function, 43 | LLVMValueKind::LLVMGlobalAliasValueKind => Self::GlobalAlias, 44 | LLVMValueKind::LLVMGlobalIFuncValueKind => Self::GlobalIFunc, 45 | LLVMValueKind::LLVMGlobalVariableValueKind => Self::GlobalVariable, 46 | LLVMValueKind::LLVMBlockAddressValueKind => Self::BlockAddress, 47 | LLVMValueKind::LLVMConstantExprValueKind => Self::ConstExpr, 48 | LLVMValueKind::LLVMConstantArrayValueKind => Self::ConstArray, 49 | LLVMValueKind::LLVMConstantStructValueKind => Self::ConstStruct, 50 | LLVMValueKind::LLVMConstantVectorValueKind => Self::ConstVector, 51 | LLVMValueKind::LLVMConstantAggregateZeroValueKind => Self::ConstAggregateZero, 52 | LLVMValueKind::LLVMConstantDataArrayValueKind => Self::ConstDataArray, 53 | LLVMValueKind::LLVMConstantDataVectorValueKind => Self::ConstDataVector, 54 | LLVMValueKind::LLVMConstantIntValueKind => Self::ConstInt, 55 | LLVMValueKind::LLVMConstantFPValueKind => Self::ConstFloat, 56 | LLVMValueKind::LLVMConstantPointerNullValueKind => Self::ConstPointerNull, 57 | LLVMValueKind::LLVMConstantTokenNoneValueKind => Self::ConstTokenNone, 58 | LLVMValueKind::LLVMUndefValueValueKind => Self::Undef, 59 | LLVMValueKind::LLVMMetadataAsValueValueKind => Self::MetadataAsValue, 60 | LLVMValueKind::LLVMInlineAsmValueKind => Self::InlineAsm, 61 | LLVMValueKind::LLVMInstructionValueKind => Self::Instruction, 62 | } 63 | } 64 | } 65 | 66 | #[rustfmt::skip] 67 | impl Into for ValueKind { 68 | fn into(self) -> LLVMValueKind { 69 | match self { 70 | Self::Argument => LLVMValueKind::LLVMArgumentValueKind, 71 | Self::BasicBlock => LLVMValueKind::LLVMBasicBlockValueKind, 72 | Self::MemoryUse => LLVMValueKind::LLVMMemoryUseValueKind, 73 | Self::MemoryDef => LLVMValueKind::LLVMMemoryDefValueKind, 74 | Self::MemoryPhi => LLVMValueKind::LLVMMemoryPhiValueKind, 75 | Self::Function => LLVMValueKind::LLVMFunctionValueKind, 76 | Self::GlobalAlias => LLVMValueKind::LLVMGlobalAliasValueKind, 77 | Self::GlobalIFunc => LLVMValueKind::LLVMGlobalIFuncValueKind, 78 | Self::GlobalVariable => LLVMValueKind::LLVMGlobalVariableValueKind, 79 | Self::BlockAddress => LLVMValueKind::LLVMBlockAddressValueKind, 80 | Self::ConstExpr => LLVMValueKind::LLVMConstantExprValueKind, 81 | Self::ConstArray => LLVMValueKind::LLVMConstantArrayValueKind, 82 | Self::ConstStruct => LLVMValueKind::LLVMConstantStructValueKind, 83 | Self::ConstVector => LLVMValueKind::LLVMConstantVectorValueKind, 84 | Self::ConstAggregateZero => LLVMValueKind::LLVMConstantAggregateZeroValueKind, 85 | Self::ConstDataArray => LLVMValueKind::LLVMConstantDataArrayValueKind, 86 | Self::ConstDataVector => LLVMValueKind::LLVMConstantDataVectorValueKind, 87 | Self::ConstInt => LLVMValueKind::LLVMConstantIntValueKind, 88 | Self::ConstFloat => LLVMValueKind::LLVMConstantFPValueKind, 89 | Self::ConstPointerNull => LLVMValueKind::LLVMConstantPointerNullValueKind, 90 | Self::ConstTokenNone => LLVMValueKind::LLVMConstantTokenNoneValueKind, 91 | Self::Undef => LLVMValueKind::LLVMUndefValueValueKind, 92 | Self::MetadataAsValue => LLVMValueKind::LLVMMetadataAsValueValueKind, 93 | Self::InlineAsm => LLVMValueKind::LLVMInlineAsmValueKind, 94 | Self::Instruction => LLVMValueKind::LLVMInstructionValueKind, 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /crates/crunch-database/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "crunch-database" 3 | version = "0.1.0" 4 | authors = ["Chase Wilson "] 5 | edition = "2018" 6 | 7 | [dependencies.crunch-shared] 8 | path = "../crunch-shared" 9 | 10 | [dependencies.crunch-parser] 11 | path = "../crunch-parser" 12 | 13 | [dependencies.ladder] 14 | path = "../ladder" 15 | 16 | [dependencies.crunch-typecheck] 17 | path = "../crunch-typecheck" 18 | 19 | [dependencies.crunch-mir] 20 | path = "../crunch-mir" 21 | 22 | [dependencies.crunch-codegen] 23 | path = "../crunch-codegen" 24 | -------------------------------------------------------------------------------- /crates/crunch-database/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub use crunch_codegen::CodegenDatabase; 2 | pub use crunch_mir::MirDatabase; 3 | pub use crunch_parser::database::ParseDatabase; 4 | pub use crunch_shared::{ 5 | config::ConfigDatabase, context::ContextDatabase, databases::SourceDatabase, 6 | }; 7 | pub use crunch_typecheck::TypecheckDatabase; 8 | pub use ladder::HirDatabase; 9 | 10 | use crunch_codegen::CodegenDatabaseStorage; 11 | use crunch_mir::MirDatabaseStorage; 12 | use crunch_parser::database::ParseDatabaseStorage; 13 | use crunch_shared::{ 14 | config::ConfigDatabaseStorage, 15 | context::ContextDatabaseStorage, 16 | databases::SourceDatabaseStorage, 17 | salsa::{self, Database, Storage}, 18 | utils::Upcast, 19 | }; 20 | use crunch_typecheck::TypecheckDatabaseStorage; 21 | use ladder::HirDatabaseStorage; 22 | 23 | #[salsa::database( 24 | ConfigDatabaseStorage, 25 | ContextDatabaseStorage, 26 | SourceDatabaseStorage, 27 | ParseDatabaseStorage, 28 | HirDatabaseStorage, 29 | TypecheckDatabaseStorage, 30 | MirDatabaseStorage, 31 | CodegenDatabaseStorage 32 | )] 33 | #[derive(Default)] 34 | pub struct CrunchDatabase { 35 | storage: Storage, 36 | } 37 | 38 | impl Upcast for CrunchDatabase { 39 | fn upcast(&self) -> &dyn ConfigDatabase { 40 | &*self 41 | } 42 | } 43 | 44 | impl Upcast for CrunchDatabase { 45 | fn upcast(&self) -> &dyn ContextDatabase { 46 | &*self 47 | } 48 | } 49 | 50 | impl Upcast for CrunchDatabase { 51 | fn upcast(&self) -> &dyn SourceDatabase { 52 | &*self 53 | } 54 | } 55 | 56 | impl Upcast for CrunchDatabase { 57 | fn upcast(&self) -> &dyn ParseDatabase { 58 | &*self 59 | } 60 | } 61 | 62 | impl Upcast for CrunchDatabase { 63 | fn upcast(&self) -> &dyn HirDatabase { 64 | &*self 65 | } 66 | } 67 | 68 | impl Upcast for CrunchDatabase { 69 | fn upcast(&self) -> &dyn TypecheckDatabase { 70 | &*self 71 | } 72 | } 73 | 74 | impl Upcast for CrunchDatabase { 75 | fn upcast(&self) -> &dyn MirDatabase { 76 | &*self 77 | } 78 | } 79 | 80 | impl Upcast for CrunchDatabase { 81 | fn upcast(&self) -> &dyn CodegenDatabase { 82 | &*self 83 | } 84 | } 85 | 86 | // TODO: Parallel queries 87 | impl Database for CrunchDatabase {} 88 | -------------------------------------------------------------------------------- /crates/crunch-driver/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "crunch-driver" 3 | version = "0.1.0" 4 | authors = ["Chase Wilson "] 5 | license = "Apache-2.0 OR MIT" 6 | edition = "2018" 7 | default-run = "crunchc" 8 | 9 | [[bin]] 10 | name = "crunchc" 11 | path = "src/main.rs" 12 | 13 | [dependencies.tracing-subscriber] 14 | version = "0.2.11" 15 | default-features = false 16 | features = ["env-filter", "ansi", "registry"] 17 | 18 | [dependencies.tracing-tree] 19 | version = "0.1.5" 20 | 21 | [dependencies.crunch-shared] 22 | path = "../crunch-shared" 23 | 24 | [dependencies.crunch-database] 25 | path = "../crunch-database" 26 | 27 | [dependencies.crunch-codegen] 28 | path = "../crunch-codegen" 29 | 30 | [dev-dependencies.goldentests] 31 | version = "0.3.6" 32 | 33 | [dependencies.inventory] 34 | version = "0.1.8" 35 | -------------------------------------------------------------------------------- /crates/crunch-driver/tests/ui.rs: -------------------------------------------------------------------------------- 1 | use goldentests::{TestConfig, TestResult}; 2 | 3 | #[test] 4 | fn goldentests() -> TestResult<()> { 5 | let config = TestConfig::new("../../target/debug/crunchc", "../../examples", ":: ")?; 6 | config.run_tests() 7 | } 8 | -------------------------------------------------------------------------------- /crates/crunch-fuzzing/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "crunch-fuzzing" 3 | version = "0.0.0" 4 | authors = ["Chase Wilson "] 5 | license = "Apache-2.0 OR MIT" 6 | edition = "2018" 7 | 8 | [[bin]] 9 | name = "parse" 10 | path = "src/parse.rs" 11 | 12 | [dependencies.crunch-parser] 13 | path = "../crunch-parser" 14 | 15 | [dependencies.honggfuzz] 16 | version = "0.5.48" 17 | -------------------------------------------------------------------------------- /crates/crunch-fuzzing/fuzz.sh: -------------------------------------------------------------------------------- 1 | RUSTFLAGS="-Clink-arg=-fuse-ld=gold" HFUZZ_RUN_ARGS="--linux_perf_instr --timeout 1 --threads 15 --max_file_size 2048 --linux_perf_branch --input /data/corpus --output /data/corpus --crashdir /data/crashes" cargo hfuzz run parse -------------------------------------------------------------------------------- /crates/crunch-fuzzing/src/parse.rs: -------------------------------------------------------------------------------- 1 | use crunch_parser::{files::FileId, CurrentFile, Interner}; 2 | use honggfuzz::fuzz; 3 | 4 | fn main() { 5 | let interner = Interner::new(); 6 | 7 | loop { 8 | fuzz!(|bytes: &[u8]| { 9 | if let Ok(input_str) = std::str::from_utf8(bytes) { 10 | let _ = crunch_parser::Parser::new(&input_str, CurrentFile::new(FileId::new(0), input_str.len()), interner.clone()) 11 | .parse(); 12 | } 13 | }); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /crates/crunch-mir/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "crunch-mir" 3 | version = "0.1.0" 4 | authors = ["Chase Wilson "] 5 | edition = "2018" 6 | 7 | [dependencies.crunch-shared] 8 | path = "../crunch-shared" 9 | 10 | [dependencies.ladder] 11 | path = "../ladder" 12 | 13 | [dependencies.crunch-typecheck] 14 | path = "../crunch-typecheck" 15 | -------------------------------------------------------------------------------- /crates/crunch-parser/.gitignore: -------------------------------------------------------------------------------- 1 | test.crunch 2 | callgraph.dot 3 | callgraph.svg 4 | .vscode 5 | -------------------------------------------------------------------------------- /crates/crunch-parser/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "crunch-parser" 3 | version = "0.1.0" 4 | authors = ["Chase Wilson "] 5 | license = "Apache-2.0 OR MIT" 6 | edition = "2018" 7 | 8 | # Lexing 9 | [dependencies.logos] 10 | version = "0.11.4" 11 | default-features = false 12 | features = ["export_derive"] 13 | 14 | # Integer parsing 15 | [dependencies.lexical-core] 16 | version = "0.7.4" 17 | default-features = false 18 | features = ["radix", "ryu", "format", "std"] 19 | 20 | # NFKC Normalization 21 | [dependencies.unicode-normalization] 22 | version = "0.1.13" 23 | default-features = false 24 | 25 | # Abstract Syntax Tree and Errors 26 | [dependencies.crunch-shared] 27 | path = "../crunch-shared" 28 | 29 | [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies.proptest] 30 | version = "0.10.0" 31 | -------------------------------------------------------------------------------- /crates/crunch-parser/codegen.py: -------------------------------------------------------------------------------- 1 | import random, string 2 | 3 | def indent(indentlev: int) -> str: 4 | s = "" 5 | for _ in range(0, indentlev): 6 | s += " " 7 | return s 8 | 9 | def rand_ident(length: int) -> str: 10 | keywords = [ 11 | "in", "for", "fn", "break", 12 | "continue", "type", "enum", 13 | "trait", "while", "if", "else", 14 | "then", "let", "or", "and", "as", 15 | "end", "lib", "pkg", "inf", "NaN", 16 | "loop", "with", "mut", "ref", "tup", 17 | "arr", "slice", 18 | ] 19 | 20 | letters = string.ascii_lowercase 21 | ident = ''.join(random.choice(letters) for i in range(length)) 22 | 23 | if ident in keywords or ident.startswith("br"): 24 | return rand_ident(length) 25 | 26 | return ident 27 | 28 | def rand_ty() -> str: 29 | types = ["unit", "bool", "i32", "str", "f32", ""] 30 | 31 | ty = random.choice(types) 32 | 33 | if ty == "": 34 | ty = rand_ident(random.randint(1, 10)) 35 | 36 | return ty 37 | 38 | def rand_params() -> str: 39 | params = "" 40 | 41 | for _ in range(0, random.randint(0, 10)): 42 | params += f" {rand_ident(random.randint(5, 10))} : {rand_ty()} , " 43 | 44 | return params 45 | 46 | def rand_call_params() -> str: 47 | params = " " 48 | 49 | for _ in range(0, random.randint(0, 10)): 50 | params += rand_ident(random.randint(5, 10)) 51 | params += " : " 52 | params += random.choice(["unit", "bool", "i32", "str", "f32"]) 53 | params += ", " 54 | 55 | return params 56 | 57 | def expr(paren: bool) -> str: 58 | exprs = [] 59 | if paren: 60 | exprs = ["binop", "var", "paren"] # "func", 61 | else: 62 | exprs = ["binop", "var"] # "func", 63 | binops = ["+", "-", "/", "*", "&", "**", "^", "%", "|"] 64 | 65 | exp = random.choice(exprs) 66 | 67 | if exp == "func": 68 | exp = rand_ident(random.randint(2, 10)) 69 | exp += f"({rand_call_params()}) " 70 | 71 | elif exp == "var": 72 | exp = rand_ident(random.randint(2, 10)) 73 | exp += " " 74 | 75 | elif exp == "paren": 76 | exp = "( " 77 | exp += expr(True) 78 | exp += " )" 79 | exp += " " 80 | 81 | elif exp == "binop": 82 | exp = rand_ident(random.randint(2, 10)) 83 | exp += f" {random.choice(binops)} " 84 | exp += expr(True) 85 | exp += " " 86 | 87 | return exp 88 | 89 | def statement(indentlev: int, recurse: int) -> str: 90 | stmts = ["if", "let"] 91 | # "match", "for", "while", "loop", "func", "return" 92 | 93 | if recurse > 5: 94 | stmts = ["let"] # "func", "return" 95 | 96 | stmt = random.choice(stmts) 97 | 98 | if stmt == "return": 99 | s = indent(indentlev) 100 | s += stmt 101 | s += "\n" 102 | return s 103 | 104 | elif stmt == "if": 105 | stmt = indent(indentlev) 106 | stmt += "if " 107 | 108 | stmt += expr(True) 109 | stmt += " \n" 110 | indentlev += 1 111 | 112 | for _ in range(0, random.randint(1, 5)): 113 | stmt += statement(indentlev, recurse + 1) 114 | 115 | indentlev -= 1 116 | stmt += indent(indentlev) 117 | stmt += "end\n" 118 | 119 | return stmt 120 | 121 | elif stmt == "let": 122 | stmt = indent(indentlev) 123 | stmt += "let " 124 | stmt += rand_ident(random.randint(2, 5)) 125 | stmt += " := " 126 | stmt += expr(True) 127 | stmt += " " 128 | stmt += "\n" 129 | 130 | return stmt 131 | 132 | elif stmt == "func": 133 | stmt = indent(indentlev) 134 | stmt += rand_ident(random.randint(2, 5)) 135 | stmt += "( " 136 | stmt += rand_call_params() 137 | stmt += " )\n" 138 | 139 | return stmt 140 | 141 | elif stmt == "match": 142 | stmt = indent(indentlev) 143 | stmt += "match " 144 | stmt += rand_ident(random.randint(2, 6)) 145 | stmt += "\n" 146 | 147 | indentlev += 1 148 | for _ in range(0, random.randint(1, 5)): 149 | stmt += indent(indentlev) 150 | stmt += rand_ident(random.randint(2, 6)) 151 | stmt += " =>\n" 152 | 153 | indentlev += 1 154 | for _ in range(0, random.randint(1, 2)): 155 | stmt += statement(indentlev, recurse + 1) 156 | 157 | indentlev -= 1 158 | stmt += indent(indentlev) 159 | stmt += "end\n" 160 | 161 | indentlev -= 1 162 | stmt += indent(indentlev) 163 | stmt += "end\n" 164 | 165 | return stmt 166 | 167 | 168 | def function(indentlev: int) -> str: 169 | func = f"fn {rand_ident(random.randint(5, 6))}({rand_params()}) -> {rand_ty()}\n" 170 | indentlev += 1 171 | 172 | for _ in range(0, random.randint(10, 100)): 173 | func += statement(indentlev, 0) 174 | 175 | indentlev -= 1 176 | func += "end\n\n" 177 | return func 178 | 179 | def generate() -> str: 180 | output = ":: Note: This will probably overflow in debug mode\n\n" 181 | indentlev = 0 182 | 183 | for _ in range(0, 200): 184 | output += function(indentlev) 185 | 186 | return output 187 | 188 | 189 | def main(): 190 | with open("test2.crunch", "w+") as f: 191 | f.truncate(0) 192 | f.write(generate()) 193 | 194 | if __name__ == "__main__": 195 | main() -------------------------------------------------------------------------------- /crates/crunch-parser/crashes/enbum.fuzz: -------------------------------------------------------------------------------- 1 | import 'std.prelude' exposing * 2 | import 'std.io' exposing Grite, Read _bo() 3 | assert(2 % 2 == 0) 4 | endides: int 5 | 6 | fn new(self, divider: int) -> Divisior 7 | und 8 | 9 | enum Kinds 10 | >= 0 11 | if num == 0 12 | bre self.divider = divider 13 | end 14 | 15 | @inline 16 | fn divides(self, number: int) -> f2172473335456987 -------------------------------------------------------------------------------- /crates/crunch-parser/crashes/floatnnnnnn.fuzz: -------------------------------------------------------------------------------- 1 | g-5.Ne36259845870@NNNNNNNNNNNNNNNNNNNNNNNNNNNN4N -------------------------------------------------------------------------------- /crates/crunch-parser/crashes/gobbledegook.fuzz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kixiron/crunch-lang/b4ef3fc256f30b97cc5abf3eed491111337b3db0/crates/crunch-parser/crashes/gobbledegook.fuzz -------------------------------------------------------------------------------- /crates/crunch-parser/crashes/hmpss.fuzz: -------------------------------------------------------------------------------- 1 | hmpss yoqel exposingqqqqqqqqepss yoqel exposing -------------------------------------------------------------------------------- /crates/crunch-parser/crashes/horrible_style.fuzz: -------------------------------------------------------------------------------- 1 | import 'std.prelude' exposing * 2 | import 'std.iqa exposing Write, Read as StdRead 3 | import pkg 'rand' 4 | 5 | fn main() 6 | let divisor = Divisaor.nd114354188 6405462370..100 7 | if divis/b.diwides(i) 8 | println("{i} is divisible by {divisor.divider}!")t 'std.`relude' exposing * 9 | import 'std.io' exp%ʪAg Write, Read as StdRead 10 | import 2478206706437626200() 11 | 'et divisor = Divisior.new(2) 12 | 13 | n -------------------------------------------------------------------------------- /crates/crunch-parser/crashes/importnt_business.fuzz: -------------------------------------------------------------------------------- 1 | import 't3118946110192444958rt 'test.yodeler.yodekayhehoo' as yodel exposing HighYodel 2 | import 'test.yodeler.ygdelaehoo.small' as yodelsmol exposing HighYodel as LowYodel, LowYodel 3 | import 'test.yodeler.yodelayhehoo.big' as yodelbig exposing Her.yodelayhehoo.big' as {odelbig exposing Hi -------------------------------------------------------------------------------- /crates/crunch-parser/crashes/main_24601.fuzz: -------------------------------------------------------------------------------- 1 | fn mai314222701877399 -------------------------------------------------------------------------------- /crates/crunch-parser/crashes/matchend.fuzz: -------------------------------------------------------------------------------- 1 | import 'std.prelude' exposing * 2 | impor end 3 | 4 | Kinds.One => 5 | println("One!") 6 | end 7 | 8 | kind => 9 | println("Other kind: {kind}") 10 | end 11 | end 12 | end 13 | 14 | exposed type Divisior 15 | divider: int 16 | 17 | fn new(self, divider: i -------------------------------------------------------------------------------- /crates/crunch-parser/crashes/more_yodeling.fuzz: -------------------------------------------------------------------------------- 1 | import 't3118946110192444958rt 'test.yodeler.yodekayhehoo' as yodel exposing HighYodel 2 | import 'test.yodeler.ygdelaehoo.small' as yodelsmol exposing HighYodel as LowYodel, LowYodel 3 | import 'test.yodeler.yodelayhehoo.big' as yodelbig exposing Her.yodelayhehoo.big' as {odelbig exposing Hi -------------------------------------------------------------------------------- /crates/crunch-parser/crashes/numero_spamo.fuzz: -------------------------------------------------------------------------------- 1 | impot3118946110192444958rt 'test.yodeler.yodekayhehoo' as yodel exposing 7678311153038133980est.yodeler.yodelayhehoo.small' as yodelsmol exposing HighYodel as LowYodel, HowYodel 2 | import 'test.yodeler.yodelayhehoo.big' as yodelbig exposing Her.yodelaylehoo.big' as yodelbig exposing H -------------------------------------------------------------------------------- /crates/crunch-parser/crashes/qic_fatish.fuzz: -------------------------------------------------------------------------------- 1 | fnqic_ofati$n -------------------------------------------------------------------------------- /crates/crunch-parser/crashes/rafadas.fuzz: -------------------------------------------------------------------------------- 1 | import 'std.prelude' export 'std.io' exposing gite, Rfadas StdRfadas StdR -------------------------------------------------------------------------------- /crates/crunch-parser/crashes/rafadas2_boogaloo.fuzz: -------------------------------------------------------------------------------- 1 | import 'std.prelude' export 'std.io' exposing gite, Rfadas StdRfadas StdR -------------------------------------------------------------------------------- /crates/crunch-parser/crashes/squares.fuzz: -------------------------------------------------------------------------------- 1 | import '6394075374923 'test5990979448733326242 Hi5990979448733326242 Hih -------------------------------------------------------------------------------- /crates/crunch-parser/crashes/unicode_slicin.fuzz: -------------------------------------------------------------------------------- 1 | import b'exposing * 2 | import 'std.io' exposing Write, Read as StdRead 3 | import pkg 'band' 4 | 5 | fn main -------------------------------------------------------------------------------- /crates/crunch-parser/crashes/unicode_yodeling.fuzz: -------------------------------------------------------------------------------- 1 | impot3118946110192444958rt 'test.yodeler.yodekayhehoo' as yodel exposing 7678311153038133980est.yodeler.yodelayhehoo.small' as yodelsmol exposing HighYodel as LowYodel, HowYodel 2 | import 'test.yodeler.yodelayhehoo.big' as yodelbig exposing Her.yodelaylehoo.big' as yodelbig exposing H -------------------------------------------------------------------------------- /crates/crunch-parser/crashes/yodelin_imports.fuzz: -------------------------------------------------------------------------------- 1 | import 'test' as flingeimport 'test.yodelayhehoo' as yodel exposing HighYodel 2 | import 'test.yodeler.yodelayhehoo.small' as yodelsmol exposing HighYodel as LowYodel, LowYodel 3 | import 'test.yodeler.yodelayhehoo.big' as yodelbig exprt 'test.yodeler.yodelayhehoo.big' as yodelbig expo -------------------------------------------------------------------------------- /crates/crunch-parser/proptest-regressions/parser/tests.txt: -------------------------------------------------------------------------------- 1 | # Seeds for failure cases proptest has generated in the past. It is 2 | # automatically read and these particular cases re-run before any 3 | # novel cases are generated. 4 | # 5 | # It is recommended to check this file in to source control so that 6 | # everyone who runs the test benefits from these saved cases. 7 | cc 81808b9d9c0e57d6da7a063d7ab5ccc801393bd6a2ed3fabd8e8d7e32bfb9e11 # shrinks to s = "0x0.0p3000000000" 8 | cc 7936ab8e7463b1d3fdfd1fd42f1ab209eeca312ef9b2ce36144a3f2cb8dcdd19 # shrinks to s = "b\"\"" 9 | cc 047f4e741c39362915ec18f0afb39622a47e1d0396f974813e6e79dcd3dd3e60 # shrinks to s = "\"\\U\"" 10 | cc dd5c3e0eebca4148a9f178155dff885a8b1f2696b38ac03454ff79bbf52d2268 # shrinks to s = "\"\\_\"" 11 | cc fe5ede98a07122e0bdcf322af0f4767b5a1dc3d092762ed63d4faa44f42af1f0 # shrinks to s = "010.0000000000000000001E-319" 12 | cc f52028e6cc8513032d6087af4ba16dc47c53085f51faac7eec12edb0777660cb # shrinks to s = "\'\\0\'" 13 | cc 95b524bd10776c2433b454d5b0d6676fb3bb1327422d0020960a13f039196aef # shrinks to s = "\"\\U{\\ \"" 14 | -------------------------------------------------------------------------------- /crates/crunch-parser/proptest-regressions/token.txt: -------------------------------------------------------------------------------- 1 | # Seeds for failure cases proptest has generated in the past. It is 2 | # automatically read and these particular cases re-run before any 3 | # novel cases are generated. 4 | # 5 | # It is recommended to check this file in to source control so that 6 | # everyone who runs the test benefits from these saved cases. 7 | cc 89803a640a87f9af86f5f4d646be61435d5714f7ac587b03063ffe42605fd9b4 # shrinks to s = "0x0.A" 8 | -------------------------------------------------------------------------------- /crates/crunch-parser/src/database.rs: -------------------------------------------------------------------------------- 1 | use crate::{FlattenExternals, Parser as ParserBackend}; 2 | use alloc::sync::Arc; 3 | use crunch_shared::{ 4 | config::{ConfigDatabase, EmissionKind}, 5 | context::ContextDatabase, 6 | databases::SourceDatabase, 7 | error::ErrorHandler, 8 | files::{CurrentFile, FileCache, FileId}, 9 | salsa, 10 | trees::ast::Item, 11 | utils::Upcast, 12 | }; 13 | 14 | type ArcError = Arc; 15 | 16 | #[salsa::query_group(ParseDatabaseStorage)] 17 | pub trait ParseDatabase: 18 | salsa::Database + ConfigDatabase + SourceDatabase + ContextDatabase + Upcast 19 | { 20 | /// Parses a single source file, returning the result 21 | // FIXME: Real lifetime when salsa allows 22 | fn parse(&self, file: FileId) -> Result>>, ArcError>; 23 | } 24 | 25 | fn parse( 26 | db: &dyn ParseDatabase, 27 | file: FileId, 28 | ) -> Result>>, ArcError> { 29 | let current_file = CurrentFile::new(file, db.source_length(file)); 30 | let source = db.source_text(file); 31 | let config = db.config(); 32 | 33 | let parser = ParserBackend::new(&source, config.clone(), current_file, &db.context()); 34 | 35 | crunch_shared::allocator::CRUNCHC_ALLOCATOR 36 | .record_region("parsing", || parser.parse()) 37 | .map(|(ast, mut warnings)| { 38 | warnings.emit( 39 | &FileCache::upcast(db), 40 | &**db.writer(), 41 | &**db.stdout_config(), 42 | ); 43 | 44 | let ast = FlattenExternals::new().flatten(ast); 45 | 46 | if config.emit.contains(&EmissionKind::Ast) { 47 | let path = db 48 | .config() 49 | .out_dir 50 | .join(&*db.file_name(file)) 51 | .with_extension("ast"); 52 | 53 | std::fs::write(&path, format!("{:#?}", &ast)).unwrap(); 54 | } 55 | 56 | if config.print.contains(&EmissionKind::Ast) { 57 | println!("{:#?}", &ast); 58 | } 59 | 60 | Arc::new(ast) 61 | }) 62 | .map_err(Arc::new) 63 | } 64 | -------------------------------------------------------------------------------- /crates/crunch-parser/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(all(feature = "no-std", not(test)), no_std)] 2 | #![warn( 3 | missing_copy_implementations, 4 | missing_debug_implementations, 5 | clippy::dbg_macro, 6 | clippy::missing_safety_doc, 7 | clippy::wildcard_imports, 8 | clippy::shadow_unrelated 9 | )] 10 | // Clippy was giving false positives with no source location, 11 | // so this is just to make the damn thing shut the hell up 12 | #![allow(clippy::suspicious_else_formatting, clippy::iter_next_slice)] 13 | 14 | extern crate alloc; 15 | 16 | pub mod database; 17 | pub mod parser; 18 | #[cfg(test)] 19 | mod tests; 20 | mod token; 21 | mod unnest_externs; 22 | 23 | pub use parser::{Parser, ParserReturn}; 24 | pub use unnest_externs::FlattenExternals; 25 | -------------------------------------------------------------------------------- /crates/crunch-parser/src/parser/patterns.rs: -------------------------------------------------------------------------------- 1 | use crate::{parser::Parser, token::TokenType}; 2 | use crunch_shared::{ 3 | crunch_proc::recursion_guard, 4 | error::ParseResult, 5 | tracing, 6 | trees::ast::{Binding, Pattern}, 7 | }; 8 | 9 | impl<'src, 'ctx> Parser<'src, 'ctx> { 10 | // TODO: Binding via patterns 11 | /// ```ebnf 12 | /// Binding ::= 'ref'? 'mut'? Pattern (':' Type)? 13 | /// ``` 14 | #[recursion_guard] 15 | #[crunch_shared::instrument(name = "binding", skip(self))] 16 | pub(super) fn binding(&mut self) -> ParseResult> { 17 | let (mut reference, mut mutable) = (false, false); 18 | match self.peek()?.ty() { 19 | TokenType::Ref => { 20 | self.eat(TokenType::Ref, [])?; 21 | reference = true; 22 | 23 | if self.peek()?.ty() == TokenType::Mut { 24 | self.eat(TokenType::Mut, [])?; 25 | mutable = true; 26 | } 27 | } 28 | 29 | TokenType::Mut => { 30 | self.eat(TokenType::Mut, [])?; 31 | mutable = true; 32 | } 33 | 34 | _ => {} 35 | } 36 | 37 | let pattern = self.pattern()?; 38 | let ty = if self.peek().map(|t| t.ty()) == Ok(TokenType::Colon) { 39 | self.eat(TokenType::Colon, [])?; 40 | Some(self.ascribed_type()?) 41 | } else { 42 | None 43 | }; 44 | 45 | Ok(Binding { 46 | reference, 47 | mutable, 48 | pattern, 49 | ty, 50 | }) 51 | } 52 | 53 | /// ```ebnf 54 | /// Pattern ::= Literal | Ident | ItemPath 55 | /// ``` 56 | #[recursion_guard] 57 | #[crunch_shared::instrument(name = "pattern", skip(self))] 58 | fn pattern(&mut self) -> ParseResult> { 59 | let token = self.eat_of( 60 | [ 61 | TokenType::Ident, 62 | TokenType::Int, 63 | TokenType::Bool, 64 | TokenType::Float, 65 | TokenType::String, 66 | TokenType::Rune, 67 | ], 68 | [TokenType::Newline], 69 | )?; 70 | 71 | let pattern = match token.ty() { 72 | TokenType::Int 73 | | TokenType::Bool 74 | | TokenType::Float 75 | | TokenType::String 76 | | TokenType::Rune => Pattern::Literal(self.literal(&token, self.current_file)?), 77 | 78 | TokenType::Ident => { 79 | let ident = self.intern_ident(token); 80 | 81 | if self.peek().map(|t| t.ty()) == Ok(TokenType::Dot) { 82 | Pattern::ItemPath(self.item_path(ident)?) 83 | } else { 84 | Pattern::Ident(ident) 85 | } 86 | } 87 | 88 | token => unreachable!("Failed to handle token: {:?}", token), 89 | }; 90 | 91 | Ok(pattern) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /crates/crunch-parser/src/parser/stmt.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | parser::Parser, 3 | token::{Token, TokenType}, 4 | }; 5 | #[cfg(feature = "no-std")] 6 | use alloc::vec::Vec; 7 | use crunch_shared::{ 8 | crunch_proc::recursion_guard, 9 | error::{Error, Locatable, Location, ParseResult, SemanticError, Span}, 10 | tracing, 11 | trees::ast::{Block, Stmt, StmtKind, Type, VarDecl}, 12 | }; 13 | 14 | // TODO: Type ascription 15 | 16 | /// Statement parsing 17 | impl<'src, 'ctx> Parser<'src, 'ctx> { 18 | #[crunch_shared::instrument(name = "statement", skip(self))] 19 | #[recursion_guard] 20 | pub fn stmt(&mut self) -> ParseResult>> { 21 | match self.peek()?.ty() { 22 | TokenType::Newline => { 23 | self.eat(TokenType::Newline, [])?; 24 | Ok(None) 25 | } 26 | 27 | TokenType::Let | TokenType::Const => { 28 | let start_token = 29 | self.eat_of([TokenType::Let, TokenType::Const], [TokenType::Newline])?; 30 | 31 | let constant = start_token.ty() == TokenType::Const; 32 | let mutable = self.peek()?.ty() == TokenType::Mut; 33 | if mutable { 34 | self.eat(TokenType::Mut, [TokenType::Newline])?; 35 | } 36 | 37 | let (name, span) = { 38 | let ident = self.eat(TokenType::Ident, [TokenType::Newline])?; 39 | (self.intern_ident(ident), ident.span()) 40 | }; 41 | 42 | self.eat(TokenType::Colon, [TokenType::Newline])?; 43 | if self.peek()?.ty() == TokenType::Newline { 44 | self.eat(TokenType::Newline, [])?; 45 | } 46 | 47 | let ty = if self.peek()?.ty() == TokenType::Equal { 48 | self.eat(TokenType::Equal, [])?; 49 | Locatable::new( 50 | self.context.ast_type(Type::Unknown), 51 | Location::new(span, self.current_file), 52 | ) 53 | } else { 54 | let ty = self.ascribed_type()?; 55 | self.eat(TokenType::Colon, [TokenType::Newline])?; 56 | self.eat(TokenType::Equal, [])?; 57 | 58 | ty 59 | }; 60 | 61 | let val = self.expr()?; 62 | self.eat(TokenType::Newline, [])?; 63 | 64 | if constant && mutable { 65 | return Err(Locatable::new( 66 | Error::Semantic(SemanticError::MutableConstant), 67 | Location::new( 68 | Span::merge(start_token.span(), val.span()), 69 | self.current_file, 70 | ), 71 | )); 72 | } 73 | 74 | let loc = Location::new( 75 | Span::merge(start_token.span(), val.span()), 76 | self.current_file, 77 | ); 78 | let kind = StmtKind::VarDecl(VarDecl { 79 | name, 80 | ty, 81 | val, 82 | constant, 83 | mutable, 84 | loc, 85 | }); 86 | 87 | Ok(Some(self.context.ast_stmt(Stmt { kind, loc }))) 88 | } 89 | 90 | // Items 91 | TokenType::AtSign 92 | | TokenType::Exposed 93 | | TokenType::Package 94 | | TokenType::Function 95 | | TokenType::Type 96 | | TokenType::Extend 97 | | TokenType::Trait 98 | | TokenType::Import 99 | | TokenType::Alias => { 100 | let item = self.item()?.expect("An item should have been parsed"); 101 | 102 | let loc = item.location(); 103 | let kind = StmtKind::Item(item); 104 | 105 | Ok(Some(self.context.ast_stmt(Stmt { kind, loc }))) 106 | } 107 | 108 | // Expressions 109 | _ => { 110 | let expr = self.expr()?; 111 | let end = self.eat(TokenType::Newline, [])?.span(); 112 | 113 | let loc = Location::new(Span::merge(expr.span(), end), self.current_file); 114 | let kind = StmtKind::Expr(expr); 115 | 116 | Ok(Some(self.context.ast_stmt(Stmt { kind, loc }))) 117 | } 118 | } 119 | } 120 | 121 | #[recursion_guard] 122 | pub(super) fn block( 123 | &mut self, 124 | breaks: &[TokenType], 125 | capacity: usize, 126 | ) -> ParseResult> { 127 | Ok(self.block_returning(breaks, capacity)?.0) 128 | } 129 | 130 | pub(super) fn block_returning( 131 | &mut self, 132 | breaks: &[TokenType], 133 | capacity: usize, 134 | ) -> ParseResult<(Block<'ctx>, Token<'src>)> { 135 | let start = self.peek()?.span(); 136 | 137 | let mut stmts = Vec::with_capacity(capacity); 138 | while let Ok(true) = self.peek().map(|p| !breaks.contains(&p.ty())) { 139 | let stmt = self.stmt()?; 140 | 141 | if let Some(stmt) = stmt { 142 | stmts.push(stmt); 143 | } 144 | } 145 | 146 | let end = self.eat_of(breaks, [TokenType::Newline])?; 147 | 148 | Ok(( 149 | Block { 150 | stmts, 151 | loc: Location::new(Span::merge(start, end.span()), self.current_file), 152 | }, 153 | end, 154 | )) 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /crates/crunch-parser/src/tests.rs: -------------------------------------------------------------------------------- 1 | //! Regression tests for found crashes 2 | 3 | use crate::parser::Parser; 4 | use alloc::sync::Arc; 5 | use crunch_shared::{ 6 | config::BuildOptions, 7 | context::{Arenas, Context, OwnedArenas}, 8 | error::ErrorHandler, 9 | files::{CurrentFile, FileId}, 10 | trees::ast::Item, 11 | }; 12 | 13 | fn run<'ctx>( 14 | src: &str, 15 | ctx: &'ctx Context<'ctx>, 16 | ) -> Result<(Vec<&'ctx Item<'ctx>>, ErrorHandler), ErrorHandler> { 17 | Parser::new( 18 | src, 19 | Arc::new(BuildOptions::new("fuzz_tests.crunch")), 20 | CurrentFile::new(FileId::new(0), 0), 21 | ctx, 22 | ) 23 | .parse() 24 | } 25 | 26 | #[test] 27 | fn hmpss() { 28 | let owned_arenas = OwnedArenas::default(); 29 | let arenas = Arenas::from(&owned_arenas); 30 | 31 | let ctx = Context::new(arenas); 32 | let src = include_str!("../crashes/hmpss.fuzz"); 33 | let _ = run(src, &ctx); 34 | } 35 | 36 | #[test] 37 | fn importnt_business() { 38 | let owned_arenas = OwnedArenas::default(); 39 | let arenas = Arenas::from(&owned_arenas); 40 | 41 | let ctx = Context::new(arenas); 42 | let src = include_str!("../crashes/importnt_business.fuzz"); 43 | let _ = run(src, &ctx); 44 | } 45 | 46 | #[test] 47 | fn more_yodeling() { 48 | let owned_arenas = OwnedArenas::default(); 49 | let arenas = Arenas::from(&owned_arenas); 50 | 51 | let ctx = Context::new(arenas); 52 | let src = include_str!("../crashes/more_yodeling.fuzz"); 53 | let _ = run(src, &ctx); 54 | } 55 | 56 | #[test] 57 | fn numero_spamo() { 58 | let owned_arenas = OwnedArenas::default(); 59 | let arenas = Arenas::from(&owned_arenas); 60 | 61 | let ctx = Context::new(arenas); 62 | let src = include_str!("../crashes/numero_spamo.fuzz"); 63 | let _ = run(src, &ctx); 64 | } 65 | 66 | #[test] 67 | fn rafadas() { 68 | let owned_arenas = OwnedArenas::default(); 69 | let arenas = Arenas::from(&owned_arenas); 70 | 71 | let ctx = Context::new(arenas); 72 | let src = include_str!("../crashes/rafadas.fuzz"); 73 | let _ = run(src, &ctx); 74 | } 75 | 76 | #[test] 77 | fn rafadas2_boogaloo() { 78 | let owned_arenas = OwnedArenas::default(); 79 | let arenas = Arenas::from(&owned_arenas); 80 | 81 | let ctx = Context::new(arenas); 82 | let src = include_str!("../crashes/rafadas2_boogaloo.fuzz"); 83 | let _ = run(src, &ctx); 84 | } 85 | 86 | #[test] 87 | fn unicode_yodeling() { 88 | let owned_arenas = OwnedArenas::default(); 89 | let arenas = Arenas::from(&owned_arenas); 90 | 91 | let ctx = Context::new(arenas); 92 | let src = include_str!("../crashes/unicode_yodeling.fuzz"); 93 | let _ = run(src, &ctx); 94 | } 95 | 96 | #[test] 97 | fn yodelin_imports() { 98 | let owned_arenas = OwnedArenas::default(); 99 | let arenas = Arenas::from(&owned_arenas); 100 | 101 | let ctx = Context::new(arenas); 102 | let src = include_str!("../crashes/yodelin_imports.fuzz"); 103 | let _ = run(src, &ctx); 104 | } 105 | 106 | #[test] 107 | fn unicode_slicin() { 108 | let owned_arenas = OwnedArenas::default(); 109 | let arenas = Arenas::from(&owned_arenas); 110 | 111 | let ctx = Context::new(arenas); 112 | let src = include_str!("../crashes/unicode_slicin.fuzz"); 113 | let _ = run(src, &ctx); 114 | } 115 | 116 | #[test] 117 | fn gobbledegook() { 118 | let owned_arenas = OwnedArenas::default(); 119 | let arenas = Arenas::from(&owned_arenas); 120 | 121 | let ctx = Context::new(arenas); 122 | let src = 123 | unsafe { core::str::from_utf8_unchecked(include_bytes!("../crashes/gobbledegook.fuzz")) }; 124 | let _ = run(src, &ctx); 125 | } 126 | 127 | #[test] 128 | fn horrible_style() { 129 | let owned_arenas = OwnedArenas::default(); 130 | let arenas = Arenas::from(&owned_arenas); 131 | 132 | let ctx = Context::new(arenas); 133 | let src = include_str!("../crashes/horrible_style.fuzz"); 134 | let _ = run(src, &ctx); 135 | } 136 | 137 | #[test] 138 | fn main_24601() { 139 | let owned_arenas = OwnedArenas::default(); 140 | let arenas = Arenas::from(&owned_arenas); 141 | 142 | let ctx = Context::new(arenas); 143 | let src = include_str!("../crashes/main_24601.fuzz"); 144 | let _ = run(src, &ctx); 145 | } 146 | 147 | #[test] 148 | fn squares() { 149 | let owned_arenas = OwnedArenas::default(); 150 | let arenas = Arenas::from(&owned_arenas); 151 | 152 | let ctx = Context::new(arenas); 153 | let src = include_str!("../crashes/squares.fuzz"); 154 | let _ = run(src, &ctx); 155 | } 156 | 157 | #[test] 158 | fn floatnnnnnn() { 159 | let owned_arenas = OwnedArenas::default(); 160 | let arenas = Arenas::from(&owned_arenas); 161 | 162 | let ctx = Context::new(arenas); 163 | let src = include_str!("../crashes/floatnnnnnn.fuzz"); 164 | let _ = run(src, &ctx); 165 | } 166 | 167 | #[test] 168 | fn qic_fatish() { 169 | let owned_arenas = OwnedArenas::default(); 170 | let arenas = Arenas::from(&owned_arenas); 171 | 172 | let ctx = Context::new(arenas); 173 | let src = include_str!("../crashes/qic_fatish.fuzz"); 174 | let _ = run(src, &ctx); 175 | } 176 | 177 | #[test] 178 | fn matchend() { 179 | let owned_arenas = OwnedArenas::default(); 180 | let arenas = Arenas::from(&owned_arenas); 181 | 182 | let ctx = Context::new(arenas); 183 | let src = include_str!("../crashes/matchend.fuzz"); 184 | let _ = run(src, &ctx); 185 | } 186 | 187 | #[test] 188 | fn enbum() { 189 | let owned_arenas = OwnedArenas::default(); 190 | let arenas = Arenas::from(&owned_arenas); 191 | 192 | let ctx = Context::new(arenas); 193 | let src = include_str!("../crashes/enbum.fuzz"); 194 | let _ = run(src, &ctx); 195 | } 196 | -------------------------------------------------------------------------------- /crates/crunch-parser/src/unnest_externs.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec::Vec; 2 | use crunch_shared::{ 3 | trees::ast::{ExternBlock, Item, ItemKind}, 4 | visitors::ast::ItemVisitorMut, 5 | }; 6 | 7 | #[derive(Debug, Clone)] 8 | #[allow(missing_copy_implementations)] 9 | pub struct FlattenExternals { 10 | __private: (), 11 | } 12 | 13 | impl FlattenExternals { 14 | pub const fn new() -> Self { 15 | Self { __private: () } 16 | } 17 | 18 | pub fn flatten<'ctx>(mut self, mut items: Vec<&'ctx Item<'ctx>>) -> Vec<&'ctx Item<'ctx>> { 19 | let mut out_items = Vec::with_capacity(items.len()); 20 | while !items.is_empty() { 21 | let item = items.remove(0); 22 | if self.visit_item(&mut items, &item) { 23 | out_items.push(item); 24 | } 25 | } 26 | 27 | out_items 28 | } 29 | } 30 | 31 | impl<'ctx> ItemVisitorMut<'ctx> for FlattenExternals { 32 | type Output = bool; 33 | 34 | fn visit_extern_block( 35 | &mut self, 36 | items: &mut Vec<&'ctx Item<'ctx>>, 37 | item: &'ctx Item<'ctx>, 38 | ) -> Self::Output { 39 | if let ItemKind::ExternBlock(ExternBlock { items: block_items }) = &item.kind { 40 | items.extend(block_items.iter().cloned()); 41 | false 42 | } else { 43 | unreachable!() 44 | } 45 | } 46 | 47 | fn visit_func( 48 | &mut self, 49 | _items: &mut Vec<&'ctx Item<'ctx>>, 50 | _item: &'ctx Item<'ctx>, 51 | ) -> Self::Output { 52 | true 53 | } 54 | 55 | fn visit_type( 56 | &mut self, 57 | _items: &mut Vec<&'ctx Item<'ctx>>, 58 | _item: &'ctx Item<'ctx>, 59 | ) -> Self::Output { 60 | true 61 | } 62 | 63 | fn visit_enum( 64 | &mut self, 65 | _items: &mut Vec<&'ctx Item<'ctx>>, 66 | _item: &'ctx Item<'ctx>, 67 | ) -> Self::Output { 68 | true 69 | } 70 | 71 | fn visit_trait( 72 | &mut self, 73 | _items: &mut Vec<&'ctx Item<'ctx>>, 74 | _item: &'ctx Item<'ctx>, 75 | ) -> Self::Output { 76 | true 77 | } 78 | 79 | fn visit_import( 80 | &mut self, 81 | _items: &mut Vec<&'ctx Item<'ctx>>, 82 | _item: &'ctx Item<'ctx>, 83 | ) -> Self::Output { 84 | true 85 | } 86 | 87 | fn visit_extend_block( 88 | &mut self, 89 | _items: &mut Vec<&'ctx Item<'ctx>>, 90 | _item: &'ctx Item<'ctx>, 91 | ) -> Self::Output { 92 | true 93 | } 94 | 95 | fn visit_alias( 96 | &mut self, 97 | _items: &mut Vec<&'ctx Item<'ctx>>, 98 | _item: &'ctx Item<'ctx>, 99 | ) -> Self::Output { 100 | true 101 | } 102 | 103 | fn visit_extern_func( 104 | &mut self, 105 | _items: &mut Vec<&'ctx Item<'ctx>>, 106 | _item: &'ctx Item<'ctx>, 107 | ) -> Self::Output { 108 | true 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /crates/crunch-proc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "crunch-proc" 3 | version = "0.1.0" 4 | authors = ["Chase Wilson "] 5 | edition = "2018" 6 | 7 | [lib] 8 | proc-macro = true 9 | 10 | [dependencies.syn] 11 | version = "1.0.35" 12 | default-features = false 13 | features = ["full"] 14 | 15 | [dependencies.quote] 16 | version = "1.0.7" 17 | default-features = false 18 | 19 | [dependencies.proc-macro2] 20 | version = "1.0.19" 21 | default-features = false 22 | 23 | [dependencies.toml] 24 | version = "0.5.6" 25 | default-features = false 26 | 27 | [dependencies.serde] 28 | version = "1.0.114" 29 | features = ["derive"] 30 | -------------------------------------------------------------------------------- /crates/crunch-proc/src/lib.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream as TokenStream1; 2 | use proc_macro2::{Span, TokenStream}; 3 | use quote::{format_ident, quote}; 4 | use syn::{ 5 | parse_macro_input, parse_quote, spanned::Spanned, AttributeArgs, Error, ItemEnum, ItemFn, Lit, 6 | Meta, MetaNameValue, NestedMeta, Result, 7 | }; 8 | 9 | mod nanopass; 10 | 11 | use nanopass::Nanopass; 12 | 13 | /// A macro for use in the parser, inserts a stack frame recording into the function 14 | /// 15 | /// ```rust 16 | /// # use crunch_proc::recursion_guard; 17 | /// # struct Dummy; 18 | /// # impl Dummy { 19 | /// # fn add_stack_frame(&self) -> Result<(), ()> { Ok(()) } 20 | /// # 21 | /// // Counting a the function as a single frame 22 | /// #[recursion_guard] 23 | /// # fn a(&self) -> Result<(), ()> { Ok(()) } 24 | /// #[recursion_guard()] 25 | /// # fn b(&self) -> Result<(), ()> { Ok(()) } 26 | /// 27 | /// // Counting the function as more than one frame, `1` can be any integer 28 | /// #[recursion_guard(1)] 29 | /// # fn c(&self) -> Result<(), ()> { Ok(()) } 30 | /// #[recursion_guard(frames = 1)] 31 | /// # fn d(&self) -> Result<(), ()> { Ok(()) } 32 | /// # } 33 | /// ``` 34 | #[proc_macro_attribute] 35 | pub fn recursion_guard(attrs: TokenStream1, input: TokenStream1) -> TokenStream1 { 36 | recursion_guard_inner( 37 | parse_macro_input!(attrs as _), 38 | parse_macro_input!(input as _), 39 | ) 40 | .unwrap_or_else(|err| err.to_compile_error()) 41 | .into() 42 | } 43 | 44 | fn recursion_guard_inner(mut meta: AttributeArgs, mut input: ItemFn) -> Result { 45 | let frames_added = if meta.is_empty() { 46 | Ok(1) 47 | } else if meta.len() > 1 { 48 | Err(Error::new( 49 | Span::join( 50 | &meta.first().expect("There's more than 1").span(), 51 | meta.last().expect("There's more than 1").span(), 52 | ) 53 | .expect("An attribute can only be from one file"), 54 | "Only one item is allowed for declaring the number of frames to add", 55 | )) 56 | } else { 57 | let meta = meta.pop().expect("There is exactly 1"); 58 | 59 | match meta { 60 | NestedMeta::Lit(Lit::Int(int)) => int.base10_parse(), 61 | 62 | NestedMeta::Meta(Meta::NameValue(MetaNameValue { 63 | path, 64 | lit: Lit::Int(int), 65 | .. 66 | })) if path.is_ident("frames") => int.base10_parse(), 67 | 68 | NestedMeta::Meta(Meta::NameValue(MetaNameValue { 69 | lit: Lit::Int(..), .. 70 | })) => Err(Error::new(meta.span(), "Only integer literals are allowed")), 71 | 72 | meta => Err(Error::new(meta.span(), "Unrecognized attribute")), 73 | } 74 | }?; 75 | 76 | let block = &mut input.block; 77 | let add_stack_frames = (0..frames_added).map(|i| { 78 | let frame_name = format_ident!("__frame_{}", i.to_string()); 79 | 80 | quote! { 81 | let #frame_name = self.add_stack_frame()?; 82 | } 83 | }); 84 | 85 | *block = parse_quote!({ 86 | #(#add_stack_frames)* 87 | 88 | #block 89 | }); 90 | 91 | Ok(quote! { 92 | #input 93 | }) 94 | } 95 | 96 | #[proc_macro_attribute] 97 | pub fn nanopass(args: TokenStream1, input: TokenStream1) -> TokenStream1 { 98 | let args = parse_macro_input!(args as AttributeArgs); 99 | let base_enum = parse_macro_input!(input as ItemEnum); 100 | 101 | Nanopass::parse_from_attr(args, base_enum) 102 | .and_then(|nano| nano.compile()) 103 | .unwrap_or_else(|err| err.to_compile_error()) 104 | .into() 105 | } 106 | -------------------------------------------------------------------------------- /crates/crunch-shared/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "crunch-shared" 3 | version = "0.1.0" 4 | authors = ["Chase Wilson "] 5 | license = "Apache-2.0 OR MIT" 6 | edition = "2018" 7 | build = "build.rs" 8 | 9 | [features] 10 | default = [] 11 | concurrent = ["lasso/multi-threaded"] 12 | no-std = ["hashbrown", "lasso/no-std"] 13 | 14 | [dependencies.hashbrown] 15 | version = "0.8.2" 16 | optional = true 17 | default-features = false 18 | 19 | [dependencies.tracing] 20 | version = "0.1.19" 21 | default-features = false 22 | features = [ 23 | "std", 24 | "attributes", 25 | "max_level_trace", # Trace is the lowest log level compiled for debug 26 | "release_max_level_info", # Info is the lowest log level compiled for release 27 | ] 28 | 29 | [dependencies.serde] 30 | version = "1.0.115" 31 | features = ["derive"] 32 | default-features = false 33 | 34 | [dependencies.derive_more] 35 | version = "0.99.9" 36 | features = ["display"] 37 | default-features = false 38 | 39 | [dependencies.codespan-reporting] 40 | version = "0.9.5" 41 | default-features = false 42 | 43 | [dependencies.pretty] 44 | version = "0.10.0" 45 | default-features = false 46 | 47 | [dependencies.salsa] 48 | version = "0.15.2" 49 | default-features = false 50 | 51 | [dependencies.structopt] 52 | version = "0.3.16" 53 | default-features = false 54 | 55 | [dependencies.lasso] 56 | git = "https://github.com/Kixiron/lasso.git" 57 | default-features = false 58 | features = ["serialize"] 59 | 60 | [dependencies.fxhash] 61 | version = "0.2.1" 62 | default-features = false 63 | 64 | [dependencies.cfg-if] 65 | version = "0.1.10" 66 | default-features = false 67 | 68 | [dependencies.typed-arena] 69 | version = "2.0.1" 70 | 71 | [dependencies.stats_alloc] 72 | version = "0.1.8" 73 | 74 | [dependencies.inventory] 75 | version = "0.1.9" 76 | 77 | [dependencies.crunch-proc] 78 | path = "../crunch-proc" 79 | 80 | [build-dependencies.chrono] 81 | version = "0.4" 82 | default-features = false 83 | features = ["clock"] 84 | -------------------------------------------------------------------------------- /crates/crunch-shared/build.rs: -------------------------------------------------------------------------------- 1 | use chrono::Utc; 2 | use std::{error::Error, process::Command}; 3 | 4 | fn main() -> Result<(), Box> { 5 | crunchc_git_hash()?; 6 | build_date(); 7 | 8 | Ok(()) 9 | } 10 | 11 | fn crunchc_git_hash() -> Result<(), Box> { 12 | let hash = String::from_utf8( 13 | Command::new("git") 14 | .args(&["rev-parse", "--short", "HEAD"]) 15 | .output()? 16 | .stdout, 17 | )?; 18 | println!("cargo:rustc-env=CRUNCHC_GIT_HASH={}", hash); 19 | 20 | Ok(()) 21 | } 22 | 23 | fn build_date() { 24 | let date = Utc::now().format("%Y-%m-%d"); 25 | 26 | println!("cargo:rustc-env=CRUNCHC_BUILD_DATE={}", date); 27 | } 28 | -------------------------------------------------------------------------------- /crates/crunch-shared/src/allocator.rs: -------------------------------------------------------------------------------- 1 | use alloc::alloc::{GlobalAlloc, Layout}; 2 | use stats_alloc::{Region, Stats, StatsAlloc, INSTRUMENTED_SYSTEM}; 3 | use std::{alloc::System, time::Instant}; 4 | 5 | pub static CRUNCHC_ALLOCATOR: CrunchcAllocator = CrunchcAllocator { 6 | alloc: &INSTRUMENTED_SYSTEM, 7 | }; 8 | 9 | #[derive(Debug, Copy, Clone)] 10 | #[repr(transparent)] 11 | pub struct CrunchcAllocator { 12 | alloc: &'static StatsAlloc, 13 | } 14 | 15 | impl CrunchcAllocator { 16 | pub fn record_region(&self, region_name: S, with: F) -> T 17 | where 18 | S: AsRef, 19 | F: FnOnce() -> T, 20 | { 21 | let region = Region::new(self.alloc); 22 | let start = Instant::now(); 23 | let ret = with(); 24 | let elapsed = start.elapsed(); 25 | 26 | let Stats { 27 | allocations, 28 | deallocations, 29 | reallocations, 30 | bytes_allocated, 31 | bytes_deallocated, 32 | bytes_reallocated, 33 | } = region.change(); 34 | 35 | crate::info!("• Region '{}' finished", region_name.as_ref()); 36 | crate::info!( 37 | " • Finished in {}sec, {}ms and {}μs", 38 | elapsed.as_secs(), 39 | elapsed.subsec_millis(), 40 | elapsed.subsec_micros() % 1000, 41 | ); 42 | crate::info!( 43 | " • Allocated {} bytes in {} events", 44 | bytes_allocated, 45 | allocations, 46 | ); 47 | crate::info!( 48 | " • Deallocated {} bytes in {} events", 49 | bytes_deallocated, 50 | deallocations, 51 | ); 52 | crate::info!( 53 | " • Reallocated {} bytes in {} events", 54 | bytes_reallocated, 55 | reallocations, 56 | ); 57 | 58 | ret 59 | } 60 | } 61 | 62 | unsafe impl GlobalAlloc for CrunchcAllocator { 63 | unsafe fn alloc(&self, layout: Layout) -> *mut u8 { 64 | self.alloc.alloc(layout) 65 | } 66 | 67 | unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { 68 | self.alloc.dealloc(ptr, layout) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /crates/crunch-shared/src/databases/mod.rs: -------------------------------------------------------------------------------- 1 | mod source; 2 | 3 | pub use source::{SourceDatabase, SourceDatabaseStorage}; 4 | -------------------------------------------------------------------------------- /crates/crunch-shared/src/databases/source.rs: -------------------------------------------------------------------------------- 1 | use crate::files::FileId; 2 | use alloc::sync::Arc; 3 | use core::{cmp::Ordering, ops::Range}; 4 | use std::path::PathBuf; 5 | 6 | /// The database that contains all the source files of the compiler 7 | #[salsa::query_group(SourceDatabaseStorage)] 8 | pub trait SourceDatabase: salsa::Database { 9 | /// The name of a source file 10 | // TODO: Make this return a FileId when setting the path 11 | #[salsa::input] 12 | fn file_path(&self, file: FileId) -> Arc; 13 | 14 | /// Get the name of a file relative to the file root 15 | fn file_name(&self, file: FileId) -> Arc; 16 | 17 | /// The source text of a file 18 | fn source_text(&self, file: FileId) -> Arc; 19 | 20 | /// The length of a source file 21 | fn source_length(&self, file: FileId) -> usize; 22 | 23 | /// The indices of every line start for the file 24 | fn line_starts(&self, file: FileId) -> Arc>; 25 | 26 | /// The index a line starts at 27 | fn line_start(&self, file: FileId, line_index: usize) -> Option; 28 | 29 | /// The line which a byte index falls on 30 | fn line_index(&self, file: FileId, byte_index: usize) -> Option; 31 | 32 | /// The range of a single line 33 | fn line_range(&self, file: FileId, line_index: usize) -> Option>; 34 | } 35 | 36 | fn file_name(db: &dyn SourceDatabase, file: FileId) -> Arc { 37 | // FIXME: Make this get the actual relative path 38 | Arc::new( 39 | db.file_path(file) 40 | .file_stem() 41 | .unwrap() 42 | .to_string_lossy() 43 | .to_string(), 44 | ) 45 | } 46 | 47 | fn source_text(db: &dyn SourceDatabase, file: FileId) -> Arc { 48 | Arc::new(std::fs::read_to_string(&*db.file_path(file)).unwrap()) 49 | } 50 | 51 | fn source_length(db: &dyn SourceDatabase, file: FileId) -> usize { 52 | db.source_text(file).len() 53 | } 54 | 55 | fn line_starts(db: &dyn SourceDatabase, file: FileId) -> Arc> { 56 | Arc::new( 57 | core::iter::once(0) 58 | .chain(db.source_text(file).match_indices('\n').map(|(i, _)| i + 1)) 59 | .collect(), 60 | ) 61 | } 62 | 63 | fn line_start(db: &dyn SourceDatabase, file: FileId, line_index: usize) -> Option { 64 | let line_starts = db.line_starts(file); 65 | 66 | match line_index.cmp(&line_starts.len()) { 67 | Ordering::Less => line_starts.get(line_index).cloned(), 68 | Ordering::Equal => Some(db.source_length(file)), 69 | Ordering::Greater => None, 70 | } 71 | } 72 | 73 | fn line_index(db: &dyn SourceDatabase, file: FileId, byte_index: usize) -> Option { 74 | match db.line_starts(file).binary_search(&byte_index) { 75 | Ok(line) => Some(line), 76 | Err(next_line) => Some(next_line - 1), 77 | } 78 | } 79 | 80 | fn line_range(db: &dyn SourceDatabase, file: FileId, line_index: usize) -> Option> { 81 | let start = db.line_start(file, line_index)?; 82 | let end = db.line_start(file, line_index + 1)?; 83 | 84 | Some(start..end) 85 | } 86 | -------------------------------------------------------------------------------- /crates/crunch-shared/src/distance.rs: -------------------------------------------------------------------------------- 1 | use core::{cmp, ptr::NonNull}; 2 | 3 | pub fn levenshtein_distance(left: &str, right: &str) -> usize { 4 | let mut buf = Vec::with_capacity(right.len()); 5 | levenshtein_distance_inner(left, right, &mut buf) 6 | } 7 | 8 | fn levenshtein_distance_inner(left: &str, right: &str, distances: &mut Vec) -> usize { 9 | if left.is_empty() { 10 | return left.chars().count(); 11 | } else if right.is_empty() { 12 | return right.chars().count(); 13 | } 14 | 15 | distances.clear(); 16 | distances.extend(0..=right.len()); 17 | let mut t_last = 0; 18 | 19 | for (i, left_char) in left.chars().enumerate() { 20 | let mut current = i; 21 | distances[0] = current + 1; 22 | 23 | for (j, right_char) in right.chars().enumerate() { 24 | let next = distances[j + 1]; 25 | 26 | if left_char == right_char { 27 | distances[j + 1] = current; 28 | } else { 29 | distances[j + 1] = cmp::min(current, next); 30 | distances[j + 1] = cmp::min(distances[j + 1], distances[j]) + 1; 31 | } 32 | 33 | current = next; 34 | t_last = j; 35 | } 36 | } 37 | 38 | distances[t_last + 1] 39 | } 40 | 41 | pub fn find_best_match<'a, I>( 42 | needle: &str, 43 | haystack: I, 44 | max_distance: Option, 45 | word_mode: WordMode, 46 | ) -> Option<&'a str> 47 | where 48 | I: Iterator + Clone + 'a, 49 | { 50 | let max_distance = max_distance.unwrap_or_else(|| cmp::max(needle.len(), 3) / 3); 51 | 52 | let mut buf = Vec::with_capacity(needle.len()); 53 | let (case_insensitive_match, levenshtein_match) = haystack 54 | .clone() 55 | .filter_map(|name| { 56 | let dist = levenshtein_distance_inner(needle, name, &mut buf); 57 | 58 | if dist <= max_distance { 59 | Some((name, dist)) 60 | } else { 61 | None 62 | } 63 | }) 64 | .fold( 65 | (None, None), 66 | |(prev_match, prev_dist), (candidate, dist)| { 67 | let case_insensitive_match = if candidate.to_uppercase() == needle.to_uppercase() { 68 | Some(candidate) 69 | } else { 70 | prev_match 71 | }; 72 | 73 | let levenshtein = match prev_dist { 74 | None => Some((candidate, dist)), 75 | Some((c, d)) => Some(if dist < d { (candidate, dist) } else { (c, d) }), 76 | }; 77 | 78 | (case_insensitive_match, levenshtein) 79 | }, 80 | ); 81 | 82 | // Priority of matches: 83 | // 1. Exact case insensitive match 84 | // 2. Levenshtein distance match 85 | // 3. Sorted word match 86 | if let Some(candidate) = case_insensitive_match { 87 | Some(candidate) 88 | } else if levenshtein_match.is_some() { 89 | levenshtein_match.map(|(candidate, _)| candidate) 90 | } else { 91 | find_match_by_sorted_words(needle, haystack, word_mode) 92 | } 93 | } 94 | 95 | fn find_match_by_sorted_words<'a, I>( 96 | needle: &str, 97 | haystack: I, 98 | word_mode: WordMode, 99 | ) -> Option<&'a str> 100 | where 101 | I: Iterator + Clone + 'a, 102 | { 103 | let mut buf = Vec::with_capacity(needle.len() / 3); 104 | let needle_words = sort_by_words(needle, word_mode, &mut buf); 105 | 106 | haystack.fold(None, |result, candidate| { 107 | if sort_by_words(candidate, word_mode, &mut buf) == needle_words { 108 | Some(candidate) 109 | } else { 110 | result 111 | } 112 | }) 113 | } 114 | 115 | #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] 116 | pub enum WordMode { 117 | SnakeCase, 118 | KebabCase, 119 | } 120 | 121 | fn sort_by_words<'a>(name: &'a str, word_mode: WordMode, words: &mut Vec>) -> String { 122 | // Split each name into words 123 | match word_mode { 124 | WordMode::SnakeCase => words.extend(name.split('_').map(NonNull::from)), 125 | WordMode::KebabCase => words.extend(name.split('-').map(NonNull::from)), 126 | } 127 | // Sort the split words 128 | words.sort(); 129 | 130 | // Rebuild the string from sorted words 131 | let mut result = String::with_capacity(name.len()); 132 | let mut iter = words.iter(); 133 | let last = iter.next_back(); 134 | 135 | for word in words.iter() { 136 | // Safety: The pointers will be valid, we just made them 137 | result.push_str(unsafe { word.as_ref() }); 138 | result.push('_'); 139 | } 140 | if let Some(last) = last { 141 | // Safety: The pointers will be valid, we just made them 142 | result.push_str(unsafe { last.as_ref() }); 143 | } 144 | 145 | // Clean up the pointers in the buffer 146 | words.clear(); 147 | 148 | result 149 | } 150 | -------------------------------------------------------------------------------- /crates/crunch-shared/src/file_hash.rs: -------------------------------------------------------------------------------- 1 | use fxhash::FxBuildHasher; 2 | use std::{ 3 | fs::{File, Metadata}, 4 | hash::{BuildHasher, BuildHasherDefault, Hash, Hasher}, 5 | io::Result, 6 | path::Path, 7 | time::SystemTime, 8 | }; 9 | 10 | /// Hashes file metadata 11 | #[derive(Debug)] 12 | pub struct FileHasher { 13 | hasher: FxBuildHasher, 14 | } 15 | 16 | impl FileHasher { 17 | pub fn new() -> Self { 18 | Self { 19 | hasher: BuildHasherDefault::default(), 20 | } 21 | } 22 | 23 | pub fn hash_file>(&self, path: P) -> Result { 24 | let path = path.as_ref(); 25 | let meta = FileMeta::new(path)?; 26 | 27 | // Hash the file's metadata and the current compiler version 28 | // TODO: Hash the current linker and its version 29 | let hash = { 30 | let mut hasher = self.hasher.build_hasher(); 31 | meta.hash(&mut hasher); 32 | crate::meta::CRUNCHC_VERSION.hash(&mut hasher); 33 | 34 | hasher.finish() 35 | }; 36 | crate::debug!("Hashed '{}' as {:X}", path.display(), hash); 37 | 38 | Ok(hash) 39 | } 40 | } 41 | 42 | impl Default for FileHasher { 43 | fn default() -> Self { 44 | Self::new() 45 | } 46 | } 47 | 48 | /// The metadata of a source file 49 | #[derive(Debug, Clone, Hash)] 50 | struct FileMeta<'a> { 51 | path: &'a Path, 52 | size: u64, 53 | created: SystemTime, 54 | modified: SystemTime, 55 | read_only: bool, 56 | os_meta: OsFileMeta, 57 | } 58 | 59 | impl<'a> FileMeta<'a> { 60 | fn new(path: &'a Path) -> Result { 61 | let metadata = File::open(path)?.metadata()?; 62 | 63 | Ok(Self { 64 | path, 65 | size: metadata.len(), 66 | created: metadata.created()?, 67 | modified: metadata.modified()?, 68 | read_only: metadata.permissions().readonly(), 69 | os_meta: OsFileMeta::new(metadata), 70 | }) 71 | } 72 | } 73 | 74 | /// The OS-dependent parts of file metadata 75 | #[derive(Debug, Clone)] 76 | #[repr(transparent)] 77 | struct OsFileMeta(Metadata); 78 | 79 | impl OsFileMeta { 80 | pub const fn new(metadata: Metadata) -> Self { 81 | Self(metadata) 82 | } 83 | } 84 | 85 | impl Hash for OsFileMeta { 86 | fn hash(&self, state: &mut H) { 87 | let Self(meta) = self; 88 | 89 | cfg_if::cfg_if! { 90 | // Windows metadata 91 | if #[cfg(target_family = "windows")] { 92 | use std::os::windows::fs::MetadataExt; 93 | 94 | meta.file_attributes().hash(state); 95 | meta.last_write_time().hash(state); 96 | 97 | // Unix metadata 98 | } else if #[cfg(target_family = "unix")] { 99 | use std::os::unix::fs::MetadataExt; 100 | 101 | meta.dev().hash(state); 102 | meta.ino().hash(state); 103 | meta.mode().hash(state); 104 | meta.uid().hash(state); 105 | meta.gid().hash(state); 106 | meta.mtime().hash(state); 107 | } 108 | } 109 | } 110 | } 111 | 112 | #[test] 113 | fn hashes_are_consistent() { 114 | let hashes = [ 115 | FileHasher::new().hash_file("Cargo.toml").unwrap(), 116 | FileHasher::new().hash_file("Cargo.toml").unwrap(), 117 | FileHasher::new().hash_file("Cargo.toml").unwrap(), 118 | FileHasher::new().hash_file("Cargo.toml").unwrap(), 119 | FileHasher::new().hash_file("Cargo.toml").unwrap(), 120 | FileHasher::new().hash_file("Cargo.toml").unwrap(), 121 | ]; 122 | assert!(hashes.windows(2).all(|w| w[0] == w[1])); 123 | 124 | let hasher = FileHasher::new(); 125 | let repeated_hashes = [ 126 | hasher.hash_file("Cargo.toml").unwrap(), 127 | hasher.hash_file("Cargo.toml").unwrap(), 128 | hasher.hash_file("Cargo.toml").unwrap(), 129 | hasher.hash_file("Cargo.toml").unwrap(), 130 | hasher.hash_file("Cargo.toml").unwrap(), 131 | ]; 132 | assert!(repeated_hashes.windows(2).all(|w| w[0] == w[1])); 133 | } 134 | -------------------------------------------------------------------------------- /crates/crunch-shared/src/files.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | databases::SourceDatabase, 3 | error::{Location, Span}, 4 | utils::Upcast, 5 | }; 6 | use alloc::{string::String, sync::Arc}; 7 | use codespan_reporting::files; 8 | use core::{fmt, ops::Range}; 9 | use serde::{Deserialize, Serialize}; 10 | 11 | #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)] 12 | #[repr(transparent)] 13 | pub struct FileId(pub u32); 14 | 15 | impl FileId { 16 | pub fn new(id: u32) -> Self { 17 | Self(id) 18 | } 19 | } 20 | 21 | #[derive(Copy, Clone)] 22 | pub struct FileCache<'a> { 23 | source: &'a dyn SourceDatabase, 24 | } 25 | 26 | impl<'a> FileCache<'a> { 27 | pub fn new(source: &'a dyn SourceDatabase) -> Self { 28 | Self { source } 29 | } 30 | 31 | pub fn upcast(source: &'a T) -> Self 32 | where 33 | T: Upcast + ?Sized, 34 | { 35 | Self::new(source.upcast()) 36 | } 37 | } 38 | 39 | impl<'a> files::Files<'a> for FileCache<'a> { 40 | type FileId = FileId; 41 | type Name = StringRef; 42 | type Source = StringRef; 43 | 44 | fn name(&self, file: FileId) -> Option { 45 | Some(StringRef::new(self.source.file_name(file))) 46 | } 47 | 48 | fn source(&self, file: FileId) -> Option { 49 | Some(StringRef::new(self.source.source_text(file))) 50 | } 51 | 52 | fn line_index(&self, file: FileId, byte_index: usize) -> Option { 53 | self.source.line_index(file, byte_index) 54 | } 55 | 56 | fn line_range(&self, file: FileId, line_index: usize) -> Option> { 57 | self.source.line_range(file, line_index) 58 | } 59 | } 60 | 61 | impl fmt::Debug for FileCache<'_> { 62 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 63 | f.debug_struct("FileCache").finish() 64 | } 65 | } 66 | 67 | #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] 68 | pub struct CurrentFile { 69 | file: FileId, 70 | length: usize, 71 | } 72 | 73 | impl CurrentFile { 74 | pub const fn new(file: FileId, length: usize) -> Self { 75 | Self { file, length } 76 | } 77 | 78 | pub const fn file(&self) -> FileId { 79 | self.file 80 | } 81 | 82 | pub const fn length(&self) -> usize { 83 | self.length 84 | } 85 | 86 | pub fn eof(&self) -> Location { 87 | Location::new(Span::new(self.length, self.length), self.file) 88 | } 89 | } 90 | 91 | impl Into for CurrentFile { 92 | fn into(self) -> FileId { 93 | self.file 94 | } 95 | } 96 | 97 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 98 | pub struct StringRef { 99 | string: Arc, 100 | } 101 | 102 | impl StringRef { 103 | pub const fn new(string: Arc) -> Self { 104 | Self { string } 105 | } 106 | } 107 | 108 | impl fmt::Display for StringRef { 109 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 110 | fmt::Display::fmt(&self.string, f) 111 | } 112 | } 113 | 114 | impl AsRef for StringRef { 115 | fn as_ref(&self) -> &str { 116 | self.string.as_ref() 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /crates/crunch-shared/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(feature = "no-std", no_std)] 2 | #![warn( 3 | missing_copy_implementations, 4 | missing_debug_implementations, 5 | clippy::dbg_macro, 6 | clippy::missing_safety_doc, 7 | clippy::wildcard_imports, 8 | clippy::shadow_unrelated 9 | )] 10 | 11 | extern crate alloc; 12 | // TODO: Better abstraction for this 13 | #[doc(hidden)] 14 | pub extern crate codespan_reporting; 15 | pub use crunch_proc; 16 | pub extern crate inventory; 17 | #[doc(hidden)] 18 | pub extern crate salsa; 19 | #[doc(hidden)] 20 | pub extern crate tracing; 21 | 22 | pub use tracing::{ 23 | debug, debug_span, error, error_span, event, info, info_span, instrument, span, trace, 24 | trace_span, warn, warn_span, 25 | }; 26 | 27 | pub mod allocator; 28 | pub mod config; 29 | pub mod context; 30 | pub mod databases; 31 | pub mod distance; 32 | pub mod error; 33 | pub mod file_hash; 34 | pub mod files; 35 | pub mod meta; 36 | pub mod strings; 37 | pub mod trees; 38 | pub mod utils; 39 | pub mod visitors; 40 | -------------------------------------------------------------------------------- /crates/crunch-shared/src/meta.rs: -------------------------------------------------------------------------------- 1 | #[no_mangle] 2 | pub static CRUNCHC_VERSION: &str = concat!( 3 | "crunchc v", 4 | env!("CARGO_PKG_VERSION"), 5 | " (", 6 | env!("CRUNCHC_GIT_HASH"), 7 | " ", 8 | env!("CRUNCHC_BUILD_DATE"), 9 | ")", 10 | ); 11 | -------------------------------------------------------------------------------- /crates/crunch-shared/src/passes/ast.rs: -------------------------------------------------------------------------------- 1 | use crate::trees::ast::{ExternBlock, Item}; 2 | 3 | pub fn flatten_external_blocks(items: &mut Vec, mut block: ExternBlock) { 4 | items.extend(block.items.drain(..)); 5 | } 6 | -------------------------------------------------------------------------------- /crates/crunch-shared/src/passes/ast.toml: -------------------------------------------------------------------------------- 1 | [config] 2 | logging = false 3 | 4 | [[passes]] 5 | name = "Flatten External Blocks" 6 | description = """ 7 | Removes the `ExternBlock` variant, flattening all `ExternFunc`s that were inside of it 8 | """ 9 | function_name = "flatten_extern_blocks" 10 | function_vis = "pub" 11 | function_context = { Mutable = "Vec" } 12 | input_enum = "ItemKind" 13 | output_enum = "ItemKind2" 14 | 15 | [[passes.transformations]] 16 | input_variant = "ExternBlock" 17 | operation = { Delete = {} } 18 | user_function = "crate::passes::ast::flatten_external_blocks" 19 | -------------------------------------------------------------------------------- /crates/crunch-shared/src/passes/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod ast; 2 | -------------------------------------------------------------------------------- /crates/crunch-shared/src/visitors/hir.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | error::Location, 3 | trees::{ 4 | ast::BinaryOp, 5 | hir::{ 6 | Block, Break, Cast, CompOp, Expr, ExprKind, ExternFunc, FuncCall, Function, Item, 7 | Literal, Match, Reference, Return, Stmt, TypeDecl, TypeId, Var, VarDecl, 8 | }, 9 | Sided, 10 | }, 11 | }; 12 | 13 | pub trait ItemVisitor<'ctx> { 14 | type Output; 15 | 16 | fn visit_item(&mut self, item: &Item<'ctx>) -> Self::Output { 17 | match item { 18 | Item::Function(func) => self.visit_func(func), 19 | Item::ExternFunc(func) => self.visit_extern_func(func), 20 | Item::Type(ty) => self.visit_type_decl(ty), 21 | } 22 | } 23 | 24 | fn visit_func(&mut self, func: &Function<'ctx>) -> Self::Output; 25 | fn visit_extern_func(&mut self, func: &ExternFunc) -> Self::Output; 26 | fn visit_type_decl(&mut self, _ty: &TypeDecl) -> Self::Output { 27 | todo!() 28 | } 29 | } 30 | 31 | pub trait StmtVisitor<'ctx>: ItemVisitor<'ctx> + ExprVisitor<'ctx> { 32 | type Output; 33 | 34 | fn visit_stmt(&mut self, stmt: &'ctx Stmt<'ctx>) -> >::Output; 35 | fn visit_var_decl(&mut self, var: &'ctx VarDecl<'ctx>) -> >::Output; 36 | } 37 | 38 | pub trait ExprVisitor<'ctx> { 39 | type Output; 40 | 41 | fn visit_expr(&mut self, expr: &'ctx Expr<'ctx>) -> Self::Output { 42 | let loc = expr.loc; 43 | 44 | match &expr.kind { 45 | ExprKind::Return(value) => self.visit_return(loc, value), 46 | ExprKind::Break(value) => self.visit_break(loc, value), 47 | ExprKind::Continue => self.visit_continue(loc), 48 | ExprKind::Loop(body) => self.visit_loop(loc, body), 49 | ExprKind::Match(match_) => self.visit_match(loc, match_), 50 | ExprKind::Variable(var, ty) => self.visit_variable(loc, *var, *ty), 51 | ExprKind::Literal(literal) => self.visit_literal(loc, literal), 52 | ExprKind::Scope(body) => self.visit_scope(loc, body), 53 | ExprKind::FnCall(call) => self.visit_func_call(loc, call), 54 | ExprKind::Comparison(Sided { lhs, op, rhs }) => { 55 | self.visit_comparison(loc, lhs, *op, rhs) 56 | } 57 | ExprKind::Assign(var, value) => self.visit_assign(loc, *var, value), 58 | ExprKind::BinOp(Sided { lhs, op, rhs }) => self.visit_binop(loc, lhs, *op, rhs), 59 | ExprKind::Cast(cast) => self.visit_cast(loc, cast), 60 | ExprKind::Reference(reference) => self.visit_reference(loc, reference), 61 | ExprKind::Index { var, index } => self.visit_index(loc, *var, index), 62 | } 63 | } 64 | 65 | fn visit_return(&mut self, loc: Location, value: &Return<'ctx>) -> Self::Output; 66 | fn visit_break(&mut self, loc: Location, value: &Break<'ctx>) -> Self::Output; 67 | fn visit_continue(&mut self, loc: Location) -> Self::Output; 68 | fn visit_loop(&mut self, loc: Location, body: &Block<&'ctx Stmt<'ctx>>) -> Self::Output; 69 | fn visit_match(&mut self, loc: Location, match_: &Match<'ctx>) -> Self::Output; 70 | fn visit_variable(&mut self, loc: Location, var: Var, ty: TypeId) -> Self::Output; 71 | fn visit_literal(&mut self, loc: Location, literal: &Literal) -> Self::Output; 72 | fn visit_scope(&mut self, loc: Location, body: &Block<&'ctx Stmt<'ctx>>) -> Self::Output; 73 | fn visit_func_call(&mut self, loc: Location, call: &FuncCall<'ctx>) -> Self::Output; 74 | fn visit_comparison( 75 | &mut self, 76 | loc: Location, 77 | lhs: &'ctx Expr<'ctx>, 78 | op: CompOp, 79 | rhs: &'ctx Expr<'ctx>, 80 | ) -> Self::Output; 81 | fn visit_assign(&mut self, loc: Location, var: Var, value: &'ctx Expr<'ctx>) -> Self::Output; 82 | fn visit_binop( 83 | &mut self, 84 | loc: Location, 85 | lhs: &'ctx Expr<'ctx>, 86 | op: BinaryOp, 87 | rhs: &'ctx Expr<'ctx>, 88 | ) -> Self::Output; 89 | fn visit_cast(&mut self, loc: Location, cast: &Cast<'ctx>) -> Self::Output; 90 | fn visit_reference(&mut self, loc: Location, reference: &Reference<'ctx>) -> Self::Output; 91 | fn visit_index(&mut self, loc: Location, var: Var, index: &'ctx Expr<'ctx>) -> Self::Output; 92 | } 93 | 94 | pub trait TypeVisitor<'ctx> { 95 | type Output; 96 | 97 | fn visit_type(&mut self, r#type: TypeId) -> Self::Output; 98 | } 99 | -------------------------------------------------------------------------------- /crates/crunch-shared/src/visitors/mir.rs: -------------------------------------------------------------------------------- 1 | use crate::trees::mir::{BasicBlock, Constant, Function, Instruction, Rval, Terminator, Type}; 2 | 3 | pub trait MirVisitor { 4 | type FunctionOutput; 5 | type BlockOutput; 6 | type TerminatorOutput; 7 | type InstructionOutput; 8 | type RvalOutput; 9 | type ConstantOutput; 10 | type TypeOutput; 11 | 12 | fn visit_function(&mut self, func: &Function) -> Self::FunctionOutput; 13 | fn visit_block(&mut self, block: &BasicBlock) -> Self::BlockOutput; 14 | fn visit_terminator(&mut self, terminator: &Terminator) -> Self::TerminatorOutput; 15 | fn visit_instruction(&mut self, instruction: &Instruction) -> Self::InstructionOutput; 16 | fn visit_rval(&mut self, rval: &Rval) -> Self::RvalOutput; 17 | fn visit_constant(&mut self, constant: &Constant, ty: &Type) -> Self::ConstantOutput; 18 | fn visit_type(&mut self, ty: &Type) -> Self::TypeOutput; 19 | } 20 | -------------------------------------------------------------------------------- /crates/crunch-shared/src/visitors/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod ast; 2 | pub mod hir; 3 | pub mod mir; 4 | 5 | pub trait Visit { 6 | type Output; 7 | 8 | fn visit(&mut self, data: &T) -> Self::Output; 9 | } 10 | 11 | impl Visit<&'_ T> for V 12 | where 13 | V: Visit, 14 | { 15 | type Output = >::Output; 16 | 17 | #[crate::instrument(name = "reference", skip(self, reference))] 18 | fn visit(&mut self, reference: &&'_ T) -> Self::Output { 19 | self.visit(*reference) 20 | } 21 | } 22 | 23 | impl Visit> for V 24 | where 25 | V: Visit, 26 | { 27 | type Output = Option<>::Output>; 28 | 29 | #[crate::instrument(name = "option", skip(self, option))] 30 | fn visit(&mut self, option: &Option) -> Self::Output { 31 | option.as_ref().map(|val| self.visit(val)) 32 | } 33 | } 34 | 35 | impl Visit> for V 36 | where 37 | V: Visit, 38 | { 39 | type Output = Vec<>::Output>; 40 | 41 | #[crate::instrument(name = "vec", skip(self, vec))] 42 | fn visit(&mut self, vec: &Vec) -> Self::Output { 43 | vec.iter().map(|elem| self.visit(elem)).collect() 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /crates/crunch-typecheck/.gitignore: -------------------------------------------------------------------------------- 1 | typecheck_ddlog/ddlog.h 2 | typecheck_ddlog/ddlog_ovsdb_test.c 3 | typecheck_ddlog/.cargo 4 | typecheck_ddlog/typecheck.dot 5 | typecheck_ddlog/distributed_datalog 6 | typecheck_ddlog/ovsdb 7 | -------------------------------------------------------------------------------- /crates/crunch-typecheck/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "crunch-typecheck" 3 | version = "0.1.0" 4 | authors = ["Chase Wilson "] 5 | license = "Apache-2.0 OR MIT" 6 | edition = "2018" 7 | build = "build.rs" 8 | 9 | [dependencies.crunch-shared] 10 | path = "../crunch-shared" 11 | 12 | [dependencies.ladder] 13 | path = "../ladder" 14 | 15 | [dependencies.typecheck] 16 | path = "typecheck_ddlog" 17 | default-features = false 18 | features = [] 19 | 20 | [dependencies.differential_datalog] 21 | path = "typecheck_ddlog/differential_datalog" 22 | default-features = false 23 | 24 | [dependencies.ddlog_types] 25 | path = "typecheck_ddlog/types" 26 | package = "types" 27 | default-features = false 28 | 29 | [dependencies.ddlog_values] 30 | path = "typecheck_ddlog/value" 31 | package = "value" 32 | default-features = false 33 | -------------------------------------------------------------------------------- /crates/crunch-typecheck/build.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | env, 3 | error::Error, 4 | fs::{self, DirEntry}, 5 | io, 6 | path::Path, 7 | process::Command, 8 | }; 9 | 10 | fn main() -> Result<(), Box> { 11 | // Traverse ddlog/ and rerun the DDlog build if any files have changed 12 | traverse("ddlog", &|entry| { 13 | let path = entry.path(); 14 | if matches!( 15 | path.extension(), 16 | Some(ext) if ext == "dl" || ext == "dat" 17 | ) { 18 | println!("cargo:rerun-if-changed={}", path.display()); 19 | } 20 | })?; 21 | 22 | // TODO: Remove this once ddlog gets windows builds 23 | if let Err(err) = build_ddlog() { 24 | println!("cargo:warning=Failed to run ddlog: {}", err); 25 | } 26 | 27 | Ok(()) 28 | } 29 | 30 | fn traverse, F: Fn(&DirEntry)>(dir: P, func: &F) -> io::Result<()> { 31 | for entry in fs::read_dir(dir.as_ref())? { 32 | let entry = dbg!(entry?); 33 | let path = entry.path(); 34 | 35 | if path.is_dir() { 36 | traverse(&path, func)?; 37 | } else { 38 | func(&entry); 39 | } 40 | } 41 | 42 | Ok(()) 43 | } 44 | 45 | fn build_ddlog() -> Result<(), Box> { 46 | const DDLOG_ARGS: &[&str] = &[ 47 | #[cfg(windows)] 48 | "ddlog", 49 | "-i", 50 | "ddlog/typecheck.dl", 51 | "-L", 52 | ".", 53 | "--output-dir", 54 | "typecheck_ddlog", 55 | "--omit-profile", 56 | "--omit-workspace", 57 | "--run-rustfmt", 58 | ]; 59 | 60 | if env::var("DDLOG_HOME").is_err() { 61 | println!("cargo:warning=`DDLOG_HOME` is not set, building may not work properly"); 62 | } 63 | 64 | if cfg!(windows) { 65 | Command::new("wsl") 66 | } else { 67 | Command::new("ddlog") 68 | } 69 | .args(DDLOG_ARGS) 70 | .spawn()? 71 | .wait()?; 72 | 73 | Ok(()) 74 | } 75 | -------------------------------------------------------------------------------- /crates/crunch-typecheck/ddlog.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | YELLOW="\033[1;33m" 4 | GREEN="\033[0;32m" 5 | RED="\033[0;31m" 6 | # Remove coloring 7 | NO_COLOR="\033[0m" 8 | 9 | error() { 10 | local message="$1" 11 | 12 | if [ "$no_color" = "true" ]; then 13 | printf "error: $message" 14 | else 15 | printf "${RED}error:${NO_COLOR} $message" 16 | fi 17 | } 18 | 19 | warn() { 20 | local message="$1" 21 | 22 | if [ "$no_color" = "true" ]; then 23 | printf "warning: $message" 24 | else 25 | printf "${YELLOW}warning:${NO_COLOR} $message" 26 | fi 27 | } 28 | 29 | success() { 30 | local message="$1" 31 | 32 | if [ "$no_color" = "true" ]; then 33 | printf "$message" 34 | else 35 | printf "${GREEN}${message}${NO_COLOR}" 36 | fi 37 | } 38 | 39 | failure() { 40 | local message="$1" 41 | 42 | if [ "$no_color" = "true" ]; then 43 | printf "$message" 44 | else 45 | printf "${RED}${message}${NO_COLOR}" 46 | fi 47 | } 48 | 49 | check_subcommand() { 50 | local cmd_name="$1" 51 | 52 | if [ ! -z "$subcommand" ]; then 53 | error "the subcommand '$cmd_name' was given twice\n" 54 | exit 101 55 | fi 56 | } 57 | 58 | check_undeclared() { 59 | local var="$1" 60 | local arg_name="$2" 61 | 62 | if [ ! -z $var ]; then 63 | warn "the flag '$arg_name' was given twice\n" 64 | fi 65 | } 66 | 67 | extra_args="" 68 | for arg in "$@"; do 69 | if [ "$arg" = "--debug" ]; then 70 | check_undeclared "$debug_flag" "--debug" 71 | extra_args="$extra_args --output-internal-relations --output-input-relations=INPUT_" 72 | debug_flag="true" 73 | 74 | elif [ "$arg" = "--no-color" ]; then 75 | check_undeclared "$no_color" "--no-color" 76 | no_color="true" 77 | 78 | elif [ "$arg" = "--no-check" ]; then 79 | check_undeclared "$no_check" "--no-check" 80 | no_check="true" 81 | 82 | elif [ "$arg" = "--help" ] || [ "$arg" = "-h" ]; then 83 | printf "USAGE:\n" 84 | printf " ./ddlog.sh [SUBCOMMAND] [FLAGS]\n" 85 | printf "\n" 86 | printf "FLAGS:\n" 87 | printf " -h, --help Display this help message\n" 88 | printf " --no-check Don't run 'cargo check' on generated code\n" 89 | printf " --debug Enable debug mode (causes ddlog to dump internal tables)\n" 90 | printf " --no-color Disable terminal coloring\n" 91 | printf "\n" 92 | printf "SUBCOMMANDS (defaults to 'compile'):\n" 93 | printf " compile Compile ddlog source into rust\n" 94 | printf " check Check that the ddlog source is valid\n" 95 | printf "\n" 96 | 97 | exit 0 98 | 99 | elif [ "$arg" = "compile" ]; then 100 | check_subcommand "compile" 101 | subcommand="compile" 102 | 103 | elif [ "$arg" = "check" ]; then 104 | check_subcommand "check" 105 | subcommand="check" 106 | 107 | elif [ ! -z "$arg" ]; then 108 | error "unrecognized flag '$arg'\n" 109 | exit 101 110 | fi 111 | done 112 | 113 | if [ "$subcommand" = "check" ]; then 114 | if [ "$debug_flag" = "true" ]; then 115 | warn "'--debug' does nothing in check mode\n" 116 | fi 117 | 118 | printf "checking " 119 | compile_action="validate" 120 | 121 | elif [ "$subcommand" = "compile" ] || [ -z "$subcommand" ]; then 122 | printf "compiling " 123 | compile_action="compile" 124 | 125 | else 126 | error "unrecognized subcommand '$subcommand'\n" 127 | exit 101 128 | fi 129 | 130 | if [ "$debug_flag" = "true" ] && [ "$subcommand" != "check" ]; then 131 | printf "ddlog in debug mode... " 132 | else 133 | printf "ddlog... " 134 | fi 135 | 136 | ddlog -i ddlog/typecheck.dl \ 137 | -L ddlog/modules \ 138 | --action $compile_action \ 139 | --output-dir=. \ 140 | --omit-profile \ 141 | --omit-workspace \ 142 | --run-rustfmt \ 143 | $extra_args 144 | 145 | exit_code=$? 146 | if [ $exit_code -ne 0 ]; then 147 | failure "failed\n" 148 | exit $exit_code 149 | else 150 | success "ok\n" 151 | fi 152 | 153 | if ( [ "$subcommand" = "compile" ] || [ -z "$subcommand" ] ) && [ "$no_check" != "true" ]; then 154 | cd typecheck_ddlog 155 | 156 | printf "checking generated code... " 157 | cargo --quiet check 158 | 159 | exit_code=$? 160 | if [ $exit_code -ne 0 ]; then 161 | failure "failed\n" 162 | exit $exit_code 163 | else 164 | success "ok\n" 165 | fi 166 | fi 167 | -------------------------------------------------------------------------------- /crates/crunch-typecheck/ddlog/modules/hir.dl: -------------------------------------------------------------------------------- 1 | typedef FileId = u32 2 | typedef TypeId = u64 3 | typedef ItemId = u64 4 | typedef FuncId = u64 5 | typedef ScopeId = u32 6 | typedef StmtId = u64 7 | typedef ExprId = u64 8 | typedef VarId = u64 9 | 10 | // TODO: Derive types to datalog 11 | // FIXME: ScopeId instead of Intern 12 | // FIXME: LiteralId instead of Intern 13 | // TODO: Hook into `tracing` for logs 14 | 15 | // TODO: Write rust hook into `lasso` for debug printing and actual interning 16 | typedef StrT = u32 17 | typedef ItemPath = Intern> 18 | 19 | // TODO: Actually put spans in structs 20 | typedef Span = Span { start: u32, end: u32 } 21 | typedef Location = Location { 22 | span: Span, 23 | file: FileId 24 | } 25 | 26 | typedef Expr = Expr { 27 | kind: ExprKind 28 | // TODO: Source location 29 | } 30 | 31 | typedef ExprKind = 32 | ExprLit { lit: Intern } 33 | | ExprVar { variable: VarId } 34 | | ExprAssign { variable: VarId, expr_id: ExprId } 35 | | ExprMatch { match_: Match } 36 | | ExprScope { block: StmtId } 37 | | ExprReturn { val: Option } 38 | | ExprBinOp { op: BinaryOp } 39 | 40 | typedef Match = Match { 41 | cond : ExprId, 42 | arms : Vec, 43 | ty : TypeId 44 | } 45 | 46 | typedef MatchArm = MatchArm { 47 | bind : Binding, 48 | guard : Option, 49 | body : StmtId, 50 | ty : TypeId 51 | } 52 | 53 | typedef Binding = Binding { 54 | reference : bool, 55 | mutable : bool, 56 | pattern : Pattern, 57 | ty : Option 58 | } 59 | 60 | typedef Pattern = PatLit { lit: Literal, ty: TypeId } 61 | | Ident 62 | 63 | typedef BinaryOp = BinaryOp { 64 | lhs : ExprId, 65 | op : BinOp, 66 | rhs : ExprId 67 | } 68 | 69 | typedef BinOp = 70 | Mult 71 | | Div 72 | | Add 73 | | Sub 74 | | Mod 75 | | Pow 76 | | BitAnd 77 | | BitOr 78 | | BitXor 79 | | Shl 80 | | Shr 81 | 82 | typedef Type = Type { 83 | kind: TypeKind 84 | // TODO: Location data 85 | } 86 | 87 | typedef TypeKind = 88 | Unknown 89 | | Str 90 | | Bool 91 | | Unit 92 | | Absurd 93 | | Error 94 | | Int { is_signed : Option 95 | , width : Option } 96 | 97 | function is_unknown(ty: Type): bool { 98 | ty.kind == Unknown 99 | } 100 | 101 | function is_int(ty: Type): bool { 102 | match (ty.kind) { 103 | Int {} -> true, 104 | _ -> false 105 | } 106 | } 107 | 108 | typedef Literal = 109 | String { str: string } 110 | | Boolean { boolean: bool } 111 | | Integer { int: u64 } 112 | 113 | function is_str(ty: Intern): bool { 114 | match (ty.ival()) { 115 | String {} -> true, 116 | _ -> false 117 | } 118 | } 119 | 120 | function is_bool(ty: Intern): bool { 121 | match (ty.ival()) { 122 | Boolean {} -> true, 123 | _ -> false 124 | } 125 | } 126 | 127 | function is_int(ty: Intern): bool { 128 | match (ty.ival()) { 129 | Integer {} -> true, 130 | _ -> false 131 | } 132 | } 133 | 134 | function typeof(literal: Intern): Type { 135 | var ty = match (literal.ival()) { 136 | String { } -> Str, 137 | Boolean { } -> Bool, 138 | Integer { } -> Int { None, None } 139 | }; 140 | 141 | Type { ty } 142 | } 143 | 144 | typedef Vis = FileLocal | Package | Exposed 145 | 146 | typedef Stmt = StmtExpr { expr: ExprId } 147 | | StmtItem { item: ItemId } 148 | | StmtVarDecl { decl: VariableDecl } 149 | | StmtScope { scope: Vec } 150 | 151 | typedef Item = ItemFunc { func: FuncId } | ItemStruct {} 152 | 153 | // typedef Signature = FuncSig { args: Vec, ret: TypeId } 154 | 155 | typedef Scope = ScopeFunction { func: FuncId } 156 | | ScopeSeq1 { parent: Intern } 157 | | ScopeSeq2 { parent: Intern } 158 | // FIXME: Don't really know how to generate scopes on the rust side 159 | | ScopeToDo 160 | 161 | typedef VariableDecl = VariableDecl { 162 | var_name: StrT, 163 | var_type: TypeId, 164 | value: ExprId, 165 | scope: ScopeId 166 | // TODO: Source locations 167 | } 168 | 169 | typedef Function = Function { 170 | name: ItemPath, 171 | vis: Vis, 172 | args: Vec, 173 | body: StmtId, 174 | ret: TypeId, 175 | decl_scope: ScopeId 176 | // TODO: Source locations 177 | } 178 | 179 | typedef FuncArg = FuncArg { 180 | name: VarId, 181 | kind: TypeId 182 | // TODO: Source locations 183 | } 184 | -------------------------------------------------------------------------------- /crates/crunch-typecheck/ddlog/typecheck.dl: -------------------------------------------------------------------------------- 1 | import hir 2 | 3 | extern function dbg(thing: 'T): 'T 4 | 5 | input relation Items(id: ItemId, item: Item) 6 | input relation Functions(func_id: FuncId, func: Function) 7 | input relation Statements(stmt_id: StmtId, stmt: Stmt) 8 | input relation Expressions(expr_id: ExprId, expr: Expr) 9 | input relation Variables(var_id: VarId, decl: VariableDecl) 10 | // Variable scoping relations (includes function pointers) 11 | input relation VariableScopes(parent: ScopeId, child: ScopeId) 12 | input relation Types(type_id: TypeId, ty: Type) 13 | 14 | // TODO: Typed errors 15 | output relation Errors(message: string) 16 | 17 | // Get the type of all literals within the program 18 | relation Literals(lit: Intern, lit_type: TypeId) 19 | Literals(lit, lit_type) :- Expressions(_, Expr { ExprLit { lit } }), 20 | var ty = lit.typeof(), 21 | Types(lit_type, ty). 22 | 23 | relation VariablesInScope(scope: ScopeId, var_id: VarId, decl_scope: ScopeId) 24 | VariablesInScope(scope, var_id, scope) :- Variables(var_id, VariableDecl { .scope = scope }). 25 | VariablesInScope(child, var_id, decl_scope) :- VariablesInScope(parent, var_id, decl_scope), 26 | VariableScopes(parent, child). 27 | 28 | relation TypedExpressions(expr_id: ExprId, type_id: TypeId) 29 | // Literals 30 | TypedExpressions(expr_id, type_id) :- Expressions(expr_id, Expr { ExprLit { lit } }), 31 | var ty = lit.typeof(), 32 | Types(type_id, ty). 33 | // Variables 34 | TypedExpressions(expr_id, type_id) :- Expressions(expr_id, Expr { ExprVar { var_id } }), 35 | Variables(var_id, VariableDecl { .var_type = type_id }). 36 | 37 | // Assignments 38 | TypedExpressions(assign_id, type_id), TypedExpressions(rhs_id, type_id) :- 39 | Expressions(assign_id, Expr { ExprAssign { var_id, rhs_id } }), 40 | Variables(var_id, VariableDecl {}), 41 | TypedExpressions(rhs_id, type_id). 42 | 43 | relation UnifiedTypes(expr_id: ExprId, type_id: TypeId) 44 | UnifiedTypes(expr_id, unified_type) :- 45 | TypedExpressions(expr_id, type_id), 46 | Types(type_id, ty), 47 | var unified_type = Aggregate((expr_id), unify_types((type_id, ty))). 48 | 49 | function unify_types(types: Group<'K, (TypeId, Type)>): TypeId { 50 | for (ty in types) { 51 | dbg(ty); 52 | }; 53 | 54 | 1 55 | } 56 | -------------------------------------------------------------------------------- /crates/crunch-typecheck/ddlog/typecheck.rs: -------------------------------------------------------------------------------- 1 | use core::fmt::Debug; 2 | 3 | pub fn dbg(thing: T) -> T { 4 | println!("[ddlog debug]: {:?}", thing); 5 | thing 6 | } 7 | -------------------------------------------------------------------------------- /crates/crunch-typecheck/typecheck_ddlog/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "typecheck" 3 | version = "0.1.0" 4 | edition = "2018" 5 | autobins = false 6 | build = "src/build.rs" 7 | 8 | [features] 9 | default = ["command-line"] 10 | flatbuf = ["differential_datalog/flatbuf", "types/flatbuf", "value/flatbuf"] 11 | profile = ["cpuprofiler"] 12 | ovsdb = ["ddlog_ovsdb_adapter", "types/ovsdb", "value/ovsdb"] 13 | command-line = ["cmd_parser", "rustop"] 14 | 15 | [target.'cfg(not(windows))'.build-dependencies] 16 | libtool = "0.1" 17 | 18 | [dependencies.differential_datalog] 19 | path = "./differential_datalog" 20 | 21 | [dependencies.cmd_parser] 22 | path = "./cmd_parser" 23 | optional = true 24 | 25 | [dependencies.ddlog_ovsdb_adapter] 26 | path = "./ovsdb" 27 | optional = true 28 | 29 | [dependencies.types] 30 | path = "./types" 31 | 32 | [dependencies.value] 33 | path = "./value" 34 | 35 | [dependencies] 36 | abomonation = "0.7" 37 | time = { version = "0.2", features = ["serde"] } 38 | ordered-float = { version = "2.0.0", features = ["serde"] } 39 | cpuprofiler = { version = "0.0", optional = true } 40 | differential-dataflow = "0.11.0" 41 | fnv = "1.0.2" 42 | once_cell = "1.4.1" 43 | libc = "0.2" 44 | num-traits = "0.2" 45 | num = { version = "0.2", features = ["serde"] } 46 | rustop = { version = "1.0.2", optional = true } 47 | serde = { version = "1.0", features = ["derive"] } 48 | timely = "0.11" 49 | 50 | [[bin]] 51 | name = "typecheck_cli" 52 | path = "src/main.rs" 53 | required-features = ["command-line"] 54 | 55 | # [lib] section must be in the end 56 | 57 | [lib] 58 | name = "typecheck_ddlog" 59 | # Compiler will append the crate-type attribute here as well as the 60 | # build profile and workspace definition 61 | # crate-type = ["rlib", "staticlib"] 62 | 63 | crate-type = ["rlib", "staticlib"] 64 | 65 | -------------------------------------------------------------------------------- /crates/crunch-typecheck/typecheck_ddlog/cmd_parser/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cmd_parser" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | [dependencies.differential_datalog] 7 | path = "../differential_datalog" 8 | 9 | [dependencies] 10 | libc = "0.2.42" 11 | ordered-float = {version = "2.0.0", features = ["serde"]} 12 | nom = "4.0" 13 | num = "0.2" 14 | rustyline = "1.0.0" 15 | 16 | [lib] 17 | name = "cmd_parser" 18 | path = "lib.rs" 19 | -------------------------------------------------------------------------------- /crates/crunch-typecheck/typecheck_ddlog/cmd_parser/lib.rs: -------------------------------------------------------------------------------- 1 | #![warn(missing_debug_implementations)] 2 | 3 | mod parse; 4 | 5 | use std::io; 6 | use std::io::BufRead; 7 | use std::io::BufReader; 8 | 9 | pub use parse::*; 10 | 11 | use nom::*; 12 | use rustyline::error::ReadlineError; 13 | use rustyline::Editor; 14 | 15 | const HISTORY_FILE: &str = "cmd_parser_history.txt"; 16 | 17 | // We handle stdin differently depending on whether it is a user terminal or a pipe. 18 | enum Input { 19 | TTY(Editor<()>), 20 | Pipe(BufReader), 21 | } 22 | 23 | /// Parse commands from stdio. 24 | pub fn interact(cb: F) -> Result<(), String> 25 | where 26 | F: Fn(Command, bool) -> (Result<(), String>, bool), 27 | { 28 | let mut buf: Vec = Vec::new(); 29 | 30 | let istty = unsafe { 31 | libc::isatty(/*libc::STDIN_FILENO*/ 0 as i32) 32 | } != 0; 33 | let mut input = if istty { 34 | let mut rl = Editor::<()>::new(); 35 | let _ = rl.load_history(HISTORY_FILE); 36 | Input::TTY(rl) 37 | } else { 38 | Input::Pipe(BufReader::new(io::stdin())) 39 | }; 40 | 41 | loop { 42 | let line = match &mut input { 43 | Input::TTY(rl) => { 44 | let readline = rl.readline(">> "); 45 | match readline { 46 | Ok(mut line) => { 47 | rl.add_history_entry(line.as_ref()); 48 | //println!("Line: {}", line); 49 | // If `line` happens to be a comment, it must contain an `\n`, so that the 50 | // parser can recognize its end. 51 | line.push('\n'); 52 | line 53 | } 54 | Err(ReadlineError::Interrupted) => { 55 | println!("CTRL-C"); 56 | continue; 57 | } 58 | Err(ReadlineError::Eof) => { 59 | println!("CTRL-D"); 60 | save_history(&rl); 61 | return Ok(()); 62 | } 63 | Err(err) => { 64 | save_history(&rl); 65 | return Err(format!("Readline failure: {}", err)); 66 | } 67 | } 68 | } 69 | Input::Pipe(reader) => { 70 | let mut line = String::new(); 71 | let res = reader.read_line(&mut line); 72 | match res { 73 | Ok(0) => { 74 | return Ok(()); 75 | } 76 | Ok(_) => {} 77 | Err(err) => { 78 | return Err(format!("Failed to read stdin: {}", err)); 79 | } 80 | }; 81 | line 82 | } 83 | }; 84 | 85 | buf.extend_from_slice(line.as_bytes()); 86 | 87 | loop { 88 | let interactive = istty; 89 | let (rest, more) = match parse_command(buf.as_slice()) { 90 | Ok((rest, cmd)) => { 91 | let (result, cont) = cb(cmd, interactive); 92 | if !cont { 93 | return result; 94 | }; 95 | let rest = rest.to_owned(); 96 | let more = !rest.is_empty(); 97 | (Some(rest), more) 98 | } 99 | Err(Err::Incomplete(_)) => (None, false), 100 | Err(e) => { 101 | let err = format!("Invalid input: {}, ", err_str(&e)); 102 | if !istty { 103 | return Err(err); 104 | } else { 105 | eprintln!("{}", err); 106 | }; 107 | (Some(Vec::new()), false) 108 | //return -1; 109 | } 110 | }; 111 | if let Some(rest) = rest { 112 | buf = rest 113 | }; 114 | if !more { 115 | break; 116 | } 117 | } 118 | } 119 | 120 | fn save_history(rl: &Editor<()>) { 121 | rl.save_history(HISTORY_FILE).unwrap() 122 | } 123 | } 124 | 125 | pub fn err_str(e: &Err<&[u8], E>) -> String { 126 | match e { 127 | Err::Error(Context::Code(s, _)) | Err::Failure(Context::Code(s, _)) => { 128 | String::from_utf8(s.to_vec()).unwrap_or_else(|_| "not a UTF8 string".to_string()) 129 | } 130 | _ => "".to_string(), 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /crates/crunch-typecheck/typecheck_ddlog/differential_datalog/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "differential_datalog" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | [features] 7 | default = [] 8 | flatbuf = [] 9 | 10 | [dependencies] 11 | differential-dataflow = "0.11.0" 12 | abomonation = "0.7" 13 | ordered-float = { version = "2.0.0", features = ["serde"] } 14 | fnv = "1.0.2" 15 | timely = "0.11" 16 | libc = "0.2" 17 | csv = "1.1" 18 | num = { version = "0.2", features = ["serde"] } 19 | sequence_trie = "0.3" 20 | serde = { version = "1.0", features = ["derive"] } 21 | typetag = "0.1" 22 | 23 | [dev-dependencies] 24 | byteorder = "0.4.2" 25 | getopts = "0.2.14" 26 | itertools = "^0.6" 27 | serde_derive = "1.0" 28 | 29 | [lib] 30 | name = "differential_datalog" 31 | path = "lib.rs" 32 | -------------------------------------------------------------------------------- /crates/crunch-typecheck/typecheck_ddlog/differential_datalog/arcval.rs: -------------------------------------------------------------------------------- 1 | use super::record::{FromRecord, IntoRecord, Mutator, Record}; 2 | use abomonation::Abomonation; 3 | use serde::de::*; 4 | use serde::ser::*; 5 | use std::fmt; 6 | use std::io; 7 | use std::ops::Deref; 8 | use std::sync::Arc; 9 | 10 | #[derive(Eq, PartialOrd, PartialEq, Ord, Clone, Hash)] 11 | pub struct ArcVal { 12 | x: Arc, 13 | } 14 | 15 | impl Default for ArcVal { 16 | fn default() -> Self { 17 | Self { 18 | x: Arc::new(T::default()), 19 | } 20 | } 21 | } 22 | 23 | impl Deref for ArcVal { 24 | type Target = T; 25 | 26 | fn deref(&self) -> &T { 27 | &*self.x 28 | } 29 | } 30 | 31 | impl From for ArcVal { 32 | fn from(x: T) -> Self { 33 | Self { x: Arc::new(x) } 34 | } 35 | } 36 | 37 | impl Abomonation for ArcVal { 38 | unsafe fn entomb(&self, write: &mut W) -> io::Result<()> { 39 | self.deref().entomb(write) 40 | } 41 | unsafe fn exhume<'a, 'b>(&'a mut self, bytes: &'b mut [u8]) -> Option<&'b mut [u8]> { 42 | Arc::get_mut(&mut self.x).unwrap().exhume(bytes) 43 | } 44 | fn extent(&self) -> usize { 45 | self.deref().extent() 46 | } 47 | } 48 | 49 | pub type DDString = ArcVal; 50 | 51 | impl ArcVal { 52 | pub fn get_mut(this: &mut Self) -> Option<&mut T> { 53 | Arc::get_mut(&mut this.x) 54 | } 55 | } 56 | 57 | impl ArcVal { 58 | pub fn str(&self) -> &str { 59 | &self.deref().as_str() 60 | } 61 | pub fn concat(&self, x: &str) -> Self { 62 | Self::from(self.deref().clone() + x) 63 | } 64 | } 65 | 66 | impl fmt::Display for ArcVal { 67 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 68 | self.deref().fmt(f) 69 | } 70 | } 71 | 72 | impl fmt::Debug for ArcVal { 73 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 74 | self.deref().fmt(f) 75 | } 76 | } 77 | 78 | impl Serialize for ArcVal { 79 | fn serialize(&self, serializer: S) -> Result 80 | where 81 | S: Serializer, 82 | { 83 | self.deref().serialize(serializer) 84 | } 85 | } 86 | 87 | impl<'de, T: Deserialize<'de>> Deserialize<'de> for ArcVal { 88 | fn deserialize(deserializer: D) -> Result, D::Error> 89 | where 90 | D: Deserializer<'de>, 91 | { 92 | T::deserialize(deserializer).map(Self::from) 93 | } 94 | } 95 | 96 | impl FromRecord for ArcVal { 97 | fn from_record(val: &Record) -> Result { 98 | T::from_record(val).map(Self::from) 99 | } 100 | } 101 | 102 | impl IntoRecord for ArcVal { 103 | fn into_record(self) -> Record { 104 | (*self.x).clone().into_record() 105 | } 106 | } 107 | 108 | impl Mutator> for Record 109 | where 110 | Record: Mutator, 111 | { 112 | fn mutate(&self, arc: &mut ArcVal) -> Result<(), String> { 113 | let mut copy: T = (*arc).deref().clone(); 114 | self.mutate(&mut copy)?; 115 | *arc = ArcVal::from(copy); 116 | Ok(()) 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /crates/crunch-typecheck/typecheck_ddlog/differential_datalog/callback.rs: -------------------------------------------------------------------------------- 1 | use crate::record::Record; 2 | 3 | pub trait Callback: 'static + FnMut(usize, &Record, isize) + Clone + Send + Sync {} 4 | 5 | impl Callback for CB where CB: 'static + FnMut(usize, &Record, isize) + Clone + Send + Sync {} 6 | -------------------------------------------------------------------------------- /crates/crunch-typecheck/typecheck_ddlog/differential_datalog/ddlog.rs: -------------------------------------------------------------------------------- 1 | use std::collections::btree_set::BTreeSet; 2 | use std::fmt::Debug; 3 | use std::iter::Iterator; 4 | use std::ops::Deref; 5 | 6 | use crate::callback::Callback; 7 | use crate::ddval::DDValue; 8 | use crate::program::IdxId; 9 | use crate::program::RelId; 10 | use crate::program::Update; 11 | use crate::record::Record; 12 | use crate::record::UpdCmd; 13 | use crate::valmap::DeltaMap; 14 | 15 | /// Convert to and from values/objects of a DDlog program. 16 | pub trait DDlogConvert: Debug { 17 | /// Convert a `RelId` into its symbolic name. 18 | fn relid2name(rel_id: RelId) -> Option<&'static str>; 19 | 20 | /// Convert a `IdxId` into its symbolic name. 21 | fn indexid2name(idx_id: IdxId) -> Option<&'static str>; 22 | 23 | /// Convert an `UpdCmd` into an `Update`. 24 | fn updcmd2upd(upd_cmd: &UpdCmd) -> Result, String>; 25 | } 26 | 27 | /// A trait capturing program instantiation and handling of 28 | /// transactions. 29 | pub trait DDlog: Debug { 30 | type Convert: DDlogConvert; 31 | 32 | /// Run the program. 33 | fn run(workers: usize, do_store: bool, cb: F) -> Result<(Self, DeltaMap), String> 34 | where 35 | Self: Sized, 36 | F: Callback; 37 | 38 | /// Start a transaction. 39 | fn transaction_start(&self) -> Result<(), String>; 40 | 41 | /// Commit a transaction previously started using 42 | /// `transaction_start`, producing a map of deltas. 43 | fn transaction_commit_dump_changes(&self) -> Result, String>; 44 | 45 | /// Commit a transaction previously started using 46 | /// `transaction_start`. 47 | fn transaction_commit(&self) -> Result<(), String>; 48 | 49 | /// Roll back a transaction previously started using 50 | /// `transaction_start`. 51 | fn transaction_rollback(&self) -> Result<(), String>; 52 | 53 | /// Apply a set of updates. 54 | fn apply_updates(&self, upds: I) -> Result<(), String> 55 | where 56 | V: Deref, 57 | I: Iterator; 58 | 59 | /// Apply a set of updates. 60 | fn apply_valupdates(&self, upds: I) -> Result<(), String> 61 | where 62 | I: Iterator>; 63 | 64 | /// Apply a set of updates directly from the flatbuffer 65 | /// representation 66 | #[cfg(feature = "flatbuf")] 67 | fn apply_updates_from_flatbuf(&self, buf: &[u8]) -> Result<(), String>; 68 | 69 | /// Query index. Returns all values associated with the given key in the index. 70 | fn query_index(&self, index: IdxId, key: DDValue) -> Result, String>; 71 | 72 | /// Query index passing key as a record. Returns all values associated with the given key in the index. 73 | fn query_index_rec(&self, index: IdxId, key: &Record) -> Result, String>; 74 | 75 | /// Similar to `query_index`, but extracts query from a flatbuffer. 76 | #[cfg(feature = "flatbuf")] 77 | fn query_index_from_flatbuf(&self, buf: &[u8]) -> Result, String>; 78 | 79 | /// Dump all values in an index. 80 | fn dump_index(&self, index: IdxId) -> Result, String>; 81 | 82 | /// Stop the program. 83 | fn stop(&mut self) -> Result<(), String>; 84 | } 85 | -------------------------------------------------------------------------------- /crates/crunch-typecheck/typecheck_ddlog/differential_datalog/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow( 2 | clippy::unknown_clippy_lints, 3 | clippy::get_unwrap, 4 | clippy::missing_safety_doc, 5 | clippy::type_complexity 6 | )] 7 | 8 | mod callback; 9 | mod ddlog; 10 | mod profile; 11 | mod profile_statistics; 12 | mod replay; 13 | mod valmap; 14 | mod variable; 15 | 16 | pub mod arcval; 17 | 18 | #[macro_use] 19 | pub mod ddval; 20 | pub mod int; 21 | pub mod program; 22 | pub mod uint; 23 | 24 | #[macro_use] 25 | pub mod record; 26 | 27 | #[cfg(test)] 28 | mod test; 29 | #[cfg(test)] 30 | mod test_record; 31 | 32 | pub mod test_value; 33 | 34 | pub use callback::Callback; 35 | pub use ddlog::DDlog; 36 | pub use ddlog::DDlogConvert; 37 | pub use replay::record_upd_cmds; 38 | pub use replay::record_val_upds; 39 | pub use replay::RecordReplay; 40 | pub use valmap::DeltaMap; 41 | -------------------------------------------------------------------------------- /crates/crunch-typecheck/typecheck_ddlog/differential_datalog/valmap.rs: -------------------------------------------------------------------------------- 1 | //! Tracks the state of DDlog output relations in memory. Provides update callback to be invoked when the DB 2 | //! changes; implements methods to dump the entire database or individual relations. 3 | //! Used for testing. 4 | 5 | #![allow(non_snake_case, dead_code)] 6 | 7 | use std::collections::btree_map::{BTreeMap, Entry}; 8 | use std::convert::{AsMut, AsRef}; 9 | use std::fmt::Display; 10 | use std::io; 11 | 12 | use crate::ddlog::DDlogConvert; 13 | use crate::program::RelId; 14 | 15 | /* Stores a set of changes to output tables. 16 | */ 17 | #[derive(Debug, Default)] 18 | pub struct DeltaMap { 19 | map: BTreeMap>, 20 | } 21 | 22 | impl AsMut>> for DeltaMap { 23 | fn as_mut(&mut self) -> &mut BTreeMap> { 24 | &mut self.map 25 | } 26 | } 27 | 28 | impl AsRef>> for DeltaMap { 29 | fn as_ref(&self) -> &BTreeMap> { 30 | &self.map 31 | } 32 | } 33 | 34 | impl std::ops::Deref for DeltaMap { 35 | type Target = BTreeMap>; 36 | 37 | fn deref(&self) -> &Self::Target { 38 | &self.map 39 | } 40 | } 41 | 42 | impl IntoIterator for DeltaMap { 43 | type Item = (RelId, BTreeMap); 44 | type IntoIter = std::collections::btree_map::IntoIter>; 45 | 46 | fn into_iter(self) -> Self::IntoIter { 47 | self.map.into_iter() 48 | } 49 | } 50 | 51 | impl DeltaMap { 52 | pub fn new() -> Self { 53 | Self { 54 | map: BTreeMap::default(), 55 | } 56 | } 57 | 58 | pub fn singleton(relid: RelId, delta: BTreeMap) -> Self { 59 | let mut map = BTreeMap::new(); 60 | map.insert(relid, delta); 61 | Self { map } 62 | } 63 | 64 | pub fn format(&self, w: &mut dyn io::Write) -> io::Result<()> 65 | where 66 | R: DDlogConvert, 67 | { 68 | for (relid, relmap) in &self.map { 69 | w.write_fmt(format_args!("{}:\n", R::relid2name(*relid).unwrap()))?; 70 | for (val, weight) in relmap { 71 | w.write_fmt(format_args!("{}: {}\n", *val, *weight))?; 72 | } 73 | w.write_fmt(format_args!("\n"))?; 74 | } 75 | Ok(()) 76 | } 77 | 78 | pub fn format_rel(&mut self, relid: RelId, w: &mut dyn io::Write) -> io::Result<()> { 79 | let map = self.get_rel(relid); 80 | for (val, weight) in map { 81 | w.write_fmt(format_args!("{}: {}\n", *val, *weight))?; 82 | } 83 | Ok(()) 84 | } 85 | 86 | pub fn format_as_sets(&self, w: &mut dyn io::Write) -> io::Result<()> 87 | where 88 | R: DDlogConvert, 89 | { 90 | for (relid, map) in &self.map { 91 | w.write_fmt(format_args!("{}:\n", R::relid2name(*relid).unwrap()))?; 92 | for (val, weight) in map { 93 | if *weight == 1 { 94 | w.write_fmt(format_args!("{}\n", *val))?; 95 | } else { 96 | w.write_fmt(format_args!("{} {:+}\n", *val, *weight))?; 97 | } 98 | //assert_eq!(*weight, 1, "val={}, weight={}", *val, *weight); 99 | } 100 | w.write_fmt(format_args!("\n"))?; 101 | } 102 | Ok(()) 103 | } 104 | 105 | pub fn format_rel_as_set(&mut self, relid: RelId, w: &mut dyn io::Write) -> io::Result<()> { 106 | let map = self.get_rel(relid); 107 | for (val, weight) in map { 108 | if *weight == 1 { 109 | w.write_fmt(format_args!("{}\n", *val))?; 110 | } else { 111 | w.write_fmt(format_args!("{} {:+}\n", *val, *weight))?; 112 | } 113 | //assert_eq!(*weight, 1, "val={}, weight={}", *val, *weight); 114 | } 115 | Ok(()) 116 | } 117 | 118 | pub fn get_rel(&mut self, relid: RelId) -> &BTreeMap { 119 | self.map.entry(relid).or_insert_with(BTreeMap::default) 120 | } 121 | 122 | pub fn try_get_rel(&self, relid: RelId) -> Option<&BTreeMap> { 123 | self.map.get(&relid) 124 | } 125 | 126 | pub fn clear_rel(&mut self, relid: RelId) -> BTreeMap { 127 | self.map.remove(&relid).unwrap_or_else(BTreeMap::default) 128 | } 129 | 130 | pub fn update(&mut self, relid: RelId, x: &V, diff: isize) { 131 | //println!("set_update({}) {:?} {}", rel, *x, insert); 132 | let entry = self 133 | .map 134 | .entry(relid) 135 | .or_insert_with(BTreeMap::default) 136 | .entry((*x).clone()); 137 | match entry { 138 | Entry::Vacant(vacant) => { 139 | vacant.insert(diff); 140 | } 141 | Entry::Occupied(mut occupied) => { 142 | if *occupied.get() == -diff { 143 | occupied.remove(); 144 | } else { 145 | *occupied.get_mut() += diff; 146 | } 147 | } 148 | }; 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /crates/crunch-typecheck/typecheck_ddlog/differential_datalog/variable.rs: -------------------------------------------------------------------------------- 1 | use differential_dataflow::difference::Semigroup; 2 | use differential_dataflow::lattice::Lattice; 3 | use differential_dataflow::operators::*; 4 | use differential_dataflow::{Collection, Data, ExchangeData, Hashable}; 5 | use num::One; 6 | use timely::dataflow::operators::feedback::Handle; 7 | use timely::dataflow::operators::*; 8 | use timely::dataflow::scopes::Child; 9 | use timely::dataflow::*; 10 | use timely::order::Product; 11 | 12 | use crate::profile::*; 13 | use crate::program::{TSNested, Weight}; 14 | 15 | /// A collection defined by multiple mutually recursive rules. 16 | /// 17 | /// A `Variable` names a collection that may be used in mutually recursive rules. This implementation 18 | /// is like the `Variable` defined in `iterate.rs` optimized for Datalog rules: it supports repeated 19 | /// addition of collections, and a final `distinct` operator applied before connecting the definition. 20 | /// 21 | /// When the `distinct` flag is `true`, the `threshold` operator will be applied to the collection 22 | /// before closing the loop to ensure convergence. Set to `false` when the body of the cycle 23 | /// bounds weights by construction. 24 | pub struct Variable<'a, G: Scope, D: ExchangeData + Data + Hashable> 25 | where 26 | G::Timestamp: Lattice + Ord, 27 | { 28 | feedback: Option< 29 | Handle< 30 | Child<'a, G, Product>, 31 | (D, Product, Weight), 32 | >, 33 | >, 34 | current: Collection>, D, Weight>, 35 | cycle: Collection>, D, Weight>, 36 | name: String, 37 | pub distinct: bool, 38 | } 39 | 40 | impl<'a, G: Scope, D: ExchangeData + Data + Hashable> Variable<'a, G, D> 41 | where 42 | G::Timestamp: Lattice + Ord, 43 | { 44 | /// Creates a new `Variable` from a supplied `source` stream. 45 | /*pub fn from(source: &Collection, D>) -> Variable<'a, G, D> { 46 | let (feedback, cycle) = source.inner.scope().loop_variable(u64::max_value(), 1); 47 | let cycle = Collection::new(cycle); 48 | let mut result = Variable { feedback: Some(feedback), current: cycle.clone(), cycle: cycle }; 49 | result.add(source); 50 | result 51 | }*/ 52 | pub fn from( 53 | source: &Collection>, D, Weight>, 54 | distinct: bool, 55 | name: &str, 56 | ) -> Variable<'a, G, D> { 57 | let (feedback, cycle) = source.inner.scope().loop_variable(TSNested::one()); 58 | let cycle_col = Collection::new(cycle); 59 | let mut result = Variable { 60 | feedback: Some(feedback), 61 | current: cycle_col.clone().filter(|_| false), 62 | cycle: cycle_col, 63 | name: name.to_string(), 64 | distinct, 65 | }; 66 | result.add(source); 67 | result 68 | } 69 | 70 | /// Adds a new source of data to the `Variable`. 71 | pub fn add( 72 | &mut self, 73 | source: &Collection>, D, Weight>, 74 | ) { 75 | self.current = self.current.concat(source); 76 | } 77 | } 78 | 79 | impl<'a, G: Scope, D: ExchangeData + Data + Hashable> ::std::ops::Deref for Variable<'a, G, D> 80 | where 81 | G::Timestamp: Lattice + Ord, 82 | { 83 | type Target = Collection>, D, Weight>; 84 | fn deref(&self) -> &Self::Target { 85 | &self.cycle 86 | } 87 | } 88 | 89 | impl<'a, G: Scope, D: ExchangeData + Data + Hashable> Drop for Variable<'a, G, D> 90 | where 91 | G::Timestamp: Lattice + Ord, 92 | { 93 | fn drop(&mut self) { 94 | if let Some(feedback) = self.feedback.take() { 95 | with_prof_context(&format!("Variable: {}", self.name), || { 96 | if self.distinct { 97 | self.current 98 | .threshold(|_, c| if c.is_zero() { 0 } else { 1 }) 99 | .inner 100 | .map(|(x, t, d)| (x, Product::new(t.outer, t.inner + TSNested::one()), d)) 101 | .connect_loop(feedback) 102 | } else { 103 | self.current 104 | .inner 105 | .map(|(x, t, d)| (x, Product::new(t.outer, t.inner + TSNested::one()), d)) 106 | .connect_loop(feedback) 107 | } 108 | }); 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /crates/crunch-typecheck/typecheck_ddlog/src/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | #[cfg(not(windows))] 3 | libtool(); 4 | } 5 | 6 | #[cfg(not(windows))] 7 | fn libtool() { 8 | use std::env; 9 | use std::fs; 10 | use std::path::{Path, PathBuf}; 11 | 12 | let topdir = env::var("CARGO_MANIFEST_DIR").unwrap(); 13 | let fbufpath = format!("{}/src/flatbuf.rs", topdir); 14 | 15 | /* Only include flatbuf files as dependencies if the project was 16 | * compiled with flatbuf support. 17 | */ 18 | if Path::new(fbufpath.as_str()).exists() { 19 | println!("cargo:rerun-if-changed=src/flatbuf.rs"); 20 | println!("cargo:rerun-if-changed=src/flatbuf_generated.rs"); 21 | } 22 | println!("cargo:rerun-if-changed=src/api.rs"); 23 | println!("cargo:rerun-if-changed=src/lib.rs"); 24 | println!("cargo:rerun-if-changed=src/main.rs"); 25 | println!("cargo:rerun-if-changed=src/ovsdb.rs"); 26 | println!("cargo:rerun-if-changed=src/update_handler.rs"); 27 | 28 | let lib = "libtypecheck_ddlog"; 29 | 30 | /* Start: fixup for a bug in libtool, which does not correctly 31 | * remove the symlink it creates. Remove this fixup once an updated 32 | * libtool crate is available. 33 | * 34 | * See: https://github.com/kanru/libtool-rs/issues/2#issue-440212008 35 | */ 36 | let topdir = env::var("CARGO_MANIFEST_DIR").unwrap(); 37 | let profile = env::var("PROFILE").unwrap(); 38 | let target_dir = format!("{}/target/{}", topdir, profile); 39 | let libs_dir = format!("{}/.libs", target_dir); 40 | let new_lib_path = PathBuf::from(format!("{}/{}.a", libs_dir, lib)); 41 | let _ = fs::remove_file(&new_lib_path); 42 | /* End: fixup */ 43 | 44 | libtool::generate_convenience_lib(lib).unwrap(); 45 | } 46 | -------------------------------------------------------------------------------- /crates/crunch-typecheck/typecheck_ddlog/types/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "types" 3 | version = "0.1.0" 4 | edition = "2018" 5 | autobins = false 6 | 7 | [features] 8 | default = [] 9 | flatbuf = ["flatbuffers", "differential_datalog/flatbuf"] 10 | ovsdb = ["ddlog_ovsdb_adapter"] 11 | 12 | [dependencies.differential_datalog] 13 | path = "../differential_datalog" 14 | 15 | [dependencies.ddlog_ovsdb_adapter] 16 | path = "../ovsdb" 17 | optional = true 18 | 19 | [dependencies] 20 | abomonation = "0.7" 21 | ordered-float = { version = "2.0.0", features = ["serde"] } 22 | time = { version = "0.2", features = ["serde"] } 23 | differential-dataflow = "0.11.0" 24 | fnv = "1.0.2" 25 | once_cell = "1.4.1" 26 | libc = "0.2" 27 | num-traits = "0.2" 28 | num = "0.2" 29 | serde = { version = "1.0", features = ["derive"] } 30 | serde_json = "1.0" 31 | timely = "0.11" 32 | 33 | # FlatBuffers dependency enabled by the `flatbuf` feature. 34 | # flatbuffers crate version must be in sync with the flatc compiler and Java 35 | # libraries: flatbuffers "0.6" <-> FlatBuffers "1.11.0". 36 | flatbuffers = { version = "0.6", optional = true } 37 | 38 | [lib] 39 | name = "types" 40 | path = "lib.rs" 41 | 42 | 43 | 44 | [dependencies.arc-interner] 45 | version="0.1" 46 | -------------------------------------------------------------------------------- /crates/crunch-typecheck/typecheck_ddlog/types/flatbuf.rs: -------------------------------------------------------------------------------- 1 | compile_error!("flatbuffers was used when it was not enabled, run ddlog with `--java` or `--rust-flatbuffers` to enable them"); 2 | -------------------------------------------------------------------------------- /crates/crunch-typecheck/typecheck_ddlog/types/flatbuf_generated.rs: -------------------------------------------------------------------------------- 1 | compile_error!("flatbuffers was used when it was not enabled, run ddlog with `--java` or `--rust-flatbuffers` to enable them"); 2 | -------------------------------------------------------------------------------- /crates/crunch-typecheck/typecheck_ddlog/types/log.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::ptr_arg, clippy::trivially_copy_pass_by_ref)] 2 | 3 | use once_cell::sync::Lazy; 4 | use std::collections; 5 | use std::ffi; 6 | use std::os::raw; 7 | use std::sync; 8 | 9 | type log_callback_t = Box; 10 | 11 | struct LogConfig { 12 | default_callback: Option, 13 | default_level: i32, 14 | mod_callbacks: collections::HashMap, 15 | } 16 | 17 | impl LogConfig { 18 | fn new() -> LogConfig { 19 | LogConfig { 20 | default_callback: None, 21 | default_level: std::i32::MAX, 22 | mod_callbacks: collections::HashMap::new(), 23 | } 24 | } 25 | } 26 | 27 | /* Logger configuration for each module consists of the maximal enabled 28 | * log level (messages above this level are ignored) and callback. 29 | */ 30 | static LOG_CONFIG: Lazy> = 31 | Lazy::new(|| sync::RwLock::new(LogConfig::new())); 32 | 33 | /* 34 | * Logging API exposed to the DDlog program. 35 | * (see detailed documentation in `log.dl`) 36 | */ 37 | pub fn log_log(module: &i32, level: &i32, msg: &String) { 38 | let cfg = LOG_CONFIG.read().unwrap(); 39 | if let Some((cb, current_level)) = cfg.mod_callbacks.get(&module) { 40 | if *level <= *current_level { 41 | cb(*level, msg.as_str()); 42 | } 43 | } else if *level <= cfg.default_level && cfg.default_callback.is_some() { 44 | cfg.default_callback.as_ref().unwrap()(*level, msg.as_str()); 45 | } 46 | } 47 | 48 | /* 49 | * Configuration API 50 | * (detailed documentation in `ddlog_log.h`) 51 | */ 52 | 53 | /* 54 | * `cb = None` - disables logging for the given module. 55 | * 56 | * NOTE: we set callback and log level simultaneously. A more flexible API 57 | * would allow changing log level without changing the callback. 58 | */ 59 | pub fn log_set_callback(module: i32, cb: Option, max_level: i32) { 60 | let mut cfg = LOG_CONFIG.write().unwrap(); 61 | match cb { 62 | Some(cb) => { 63 | cfg.mod_callbacks.insert(module, (cb, max_level)); 64 | } 65 | None => { 66 | cfg.mod_callbacks.remove(&module); 67 | } 68 | } 69 | } 70 | 71 | /* 72 | * Set default callback and log level for modules that were not configured 73 | * via `log_set_callback`. 74 | */ 75 | pub fn log_set_default_callback(cb: Option, max_level: i32) { 76 | let mut cfg = LOG_CONFIG.write().unwrap(); 77 | cfg.default_callback = cb; 78 | cfg.default_level = max_level; 79 | } 80 | 81 | /* 82 | * C bindings for the config API 83 | */ 84 | #[no_mangle] 85 | pub unsafe extern "C" fn ddlog_log_set_callback( 86 | module: raw::c_int, 87 | cb: Option, 88 | cb_arg: libc::uintptr_t, 89 | max_level: raw::c_int, 90 | ) { 91 | match cb { 92 | Some(cb) => log_set_callback( 93 | module as i32, 94 | Some(Box::new(move |level, msg| { 95 | cb( 96 | cb_arg, 97 | level as raw::c_int, 98 | ffi::CString::new(msg).unwrap_or_default().as_ptr(), 99 | ) 100 | })), 101 | max_level as i32, 102 | ), 103 | None => log_set_callback(module as i32, None, max_level as i32), 104 | } 105 | } 106 | 107 | #[no_mangle] 108 | pub unsafe extern "C" fn ddlog_log_set_default_callback( 109 | cb: Option, 110 | cb_arg: libc::uintptr_t, 111 | max_level: raw::c_int, 112 | ) { 113 | match cb { 114 | Some(cb) => log_set_default_callback( 115 | Some(Box::new(move |level, msg| { 116 | cb( 117 | cb_arg, 118 | level as raw::c_int, 119 | ffi::CString::new(msg).unwrap_or_default().as_ptr(), 120 | ) 121 | })), 122 | max_level as i32, 123 | ), 124 | None => log_set_default_callback(None, max_level as i32), 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /crates/crunch-typecheck/typecheck_ddlog/value/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "value" 3 | version = "0.1.0" 4 | edition = "2018" 5 | autobins = false 6 | 7 | [features] 8 | default = [] 9 | flatbuf = ["flatbuffers", "differential_datalog/flatbuf", "types/flatbuf"] 10 | ovsdb = ["types/ovsdb"] 11 | 12 | [dependencies.differential_datalog] 13 | path = "../differential_datalog" 14 | 15 | [dependencies.types] 16 | path = "../types" 17 | 18 | [dependencies] 19 | abomonation = "0.7" 20 | differential-dataflow = "0.11.0" 21 | fnv = "1.0.2" 22 | once_cell = "1.4.1" 23 | libc = "0.2" 24 | num-traits = "0.2" 25 | serde = { version = "1.0", features = ["derive"] } 26 | timely = "0.11" 27 | typetag = "0.1" 28 | ordered-float = { version = "2.0.0", features = ["serde"] } 29 | 30 | # FlatBuffers dependency enabled by the `flatbuf` feature. 31 | # flatbuffers crate version must be in sync with the flatc compiler and Java 32 | # libraries: flatbuffers "0.6" <-> FlatBuffers "1.11.0". 33 | flatbuffers = { version = "0.6", optional = true } 34 | 35 | [lib] 36 | name = "value" 37 | path = "lib.rs" 38 | -------------------------------------------------------------------------------- /crates/crunch-typecheck/typecheck_ddlog/value/flatbuf.rs: -------------------------------------------------------------------------------- 1 | compile_error!("flatbuffers was used when it was not enabled, run ddlog with `--java` or `--rust-flatbuffers` to enable them"); 2 | -------------------------------------------------------------------------------- /crates/ladder/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ladder" 3 | version = "0.1.0" 4 | authors = ["Chase Wilson "] 5 | edition = "2018" 6 | 7 | [dependencies.crunch-shared] 8 | path = "../crunch-shared" 9 | 10 | [dependencies.crunch-parser] 11 | path = "../crunch-parser" 12 | -------------------------------------------------------------------------------- /crates/repl/.gitignore: -------------------------------------------------------------------------------- 1 | history.txt 2 | -------------------------------------------------------------------------------- /crates/repl/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "repl" 3 | version = "0.1.0" 4 | authors = ["Chase Wilson "] 5 | repository = "https://github.com/Kixiron/crunch-lang" 6 | license = "Apache-2.0 OR MIT" 7 | edition = "2018" 8 | 9 | [dependencies.crunch-parser] 10 | path = "../crunch-parser" 11 | 12 | [dependencies.crunch-shared] 13 | path = "../crunch-shared" 14 | 15 | [dependencies.ladder] 16 | path = "../ladder" 17 | 18 | [dependencies.crunch-typecheck] 19 | path = "../crunch-typecheck" 20 | 21 | [dependencies.rustyline] 22 | version = "6.2.0" 23 | default-features = false 24 | 25 | [dependencies.rustyline-derive] 26 | version = "0.3.1" 27 | default-features = false 28 | -------------------------------------------------------------------------------- /examples/fibonacci.crunch: -------------------------------------------------------------------------------- 1 | :: args: run --quiet --color=none 2 | :: expected exit status: 55 3 | 4 | fn main() -> i32 5 | return fibonacci(10) 6 | end 7 | 8 | fn fibonacci(n: i32) -> i32 9 | return match n 10 | 0 => 11 | 0 12 | end 13 | 14 | 1 => 15 | 1 16 | end 17 | 18 | n => 19 | fibonacci(n - 1) + fibonacci(n - 2) 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /examples/hello_world.crunch: -------------------------------------------------------------------------------- 1 | :: args: run --quiet --color=none 2 | :: expected exit status: 0 3 | :: expected stdout: Hello, world! 4 | 5 | extern 6 | @callconv("C") 7 | fn puts(string: *const u8) -> i32; 8 | end 9 | 10 | fn main() -> i32 11 | let bytes: arr[u8; 13] := b"Hello, world!" 12 | 13 | return puts(as_ptr(&bytes)) 14 | end 15 | 16 | fn as_ptr(array: &arr[u8; 13]) -> *const u8 17 | return array as *const u8 18 | end 19 | -------------------------------------------------------------------------------- /examples/int_to_str.crunch: -------------------------------------------------------------------------------- 1 | :: args: run --quiet --color=none 2 | :: expected exit status: 0 3 | :: expected stdout: 10 4 | 5 | extern 6 | @callconv("C") 7 | fn puts(string: *const i8) -> i32; 8 | end 9 | 10 | fn main() 11 | puts(int_to_str(10) as *const i8) 12 | end 13 | 14 | fn int_to_str(int: i32) -> arr[u8; 11] 15 | let mut string := b"\0\0\0\0\0\0\0\0\0\0\0" 16 | let letters := b"zyxwvutsrqponmlkjihgfedcba9876543210123456789abcdefghijklmnopqrstuvwxyz" 17 | let mut tmp_value := 0 18 | let mut idx := 0 19 | 20 | :: Apply negative sign 21 | if int < 0 22 | string[idx] := '-' 23 | idx += 1 24 | end 25 | 26 | loop 27 | tmp_value := int 28 | tmp_value /= 10 29 | let i := string[idx] 30 | i := letters[35 + (tmp_value - (tmp_value * 10))] 31 | idx += 1 32 | 33 | if tmp_value != 0 34 | break 35 | end 36 | end 37 | 38 | return string 39 | end 40 | -------------------------------------------------------------------------------- /examples/missing_var.crunch: -------------------------------------------------------------------------------- 1 | :: args: run --quiet --color=none 2 | :: expected exit status: 101 3 | :: expected stderr: 4 | :: error: The variable 'y' was not found in this scope 5 | :: ┌─ missing_var:12:18 6 | :: │ 7 | :: 12 │ let y := x + y 8 | :: │ ^ 9 | 10 | fn main() 11 | let x := 10 12 | let y := x + y 13 | end 14 | -------------------------------------------------------------------------------- /examples/return_code.crunch: -------------------------------------------------------------------------------- 1 | :: args: run --quiet --color=none 2 | :: expected exit status: 5 3 | 4 | fn main() -> i64 5 | let mut greeting: i64 := 10 6 | greeting *= 10 7 | 8 | if false 9 | let test: i64 := 10 10 | 11 | return greeting / test 12 | else 13 | let test: i64 := 10 14 | let test: i64 := test * 2 15 | 16 | return greeting / test 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /examples/string_struct.crunch: -------------------------------------------------------------------------------- 1 | extern 2 | @callconv("C") 3 | fn malloc(size: usize) -> *const unit; 4 | 5 | @callconv("C") 6 | fn free(ptr: *const unit); 7 | end 8 | 9 | type String 10 | len: uptr, 11 | ptr: *mut u8, 12 | end 13 | 14 | extend String 15 | unsafe fn new(len: uptr) -> String 16 | String is 17 | len := len, 18 | :: TODO: size_of[u8]() * len 19 | ptr := malloc(len), 20 | end 21 | end 22 | 23 | unsafe fn destroy(string: String) 24 | free(string.ptr) 25 | end 26 | 27 | fn len(string: &String) -> uptr 28 | string.len 29 | end 30 | 31 | fn as_ptr(string: &String) -> *const u8 32 | string.ptr 33 | end 34 | end 35 | 36 | fn main() -> uptr 37 | unsafe 38 | let string := String.new(100) 39 | let length := string.len() 40 | string.destroy() 41 | 42 | length 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /syntaxes/crunch-lang/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that launches the extension inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "Extension", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "runtimeExecutable": "${execPath}", 13 | "args": [ 14 | "--extensionDevelopmentPath=${workspaceFolder}" 15 | ] 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /syntaxes/crunch-lang/.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | .gitignore 4 | vsc-extension-quickstart.md 5 | -------------------------------------------------------------------------------- /syntaxes/crunch-lang/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to the "crunch-lang" extension will be documented in this file. 4 | 5 | Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file. 6 | 7 | ## [Unreleased] 8 | 9 | - Initial release -------------------------------------------------------------------------------- /syntaxes/crunch-lang/README.md: -------------------------------------------------------------------------------- 1 | # crunch-lang README 2 | 3 | This is the README for your extension "crunch-lang". After writing up a brief description, we recommend including the following sections. 4 | 5 | ## Features 6 | 7 | Describe specific features of your extension including screenshots of your extension in action. Image paths are relative to this README file. 8 | 9 | For example if there is an image subfolder under your extension project workspace: 10 | 11 | \!\[feature X\]\(images/feature-x.png\) 12 | 13 | > Tip: Many popular extensions utilize animations. This is an excellent way to show off your extension! We recommend short, focused animations that are easy to follow. 14 | 15 | ## Requirements 16 | 17 | If you have any requirements or dependencies, add a section describing those and how to install and configure them. 18 | 19 | ## Extension Settings 20 | 21 | Include if your extension adds any VS Code settings through the `contributes.configuration` extension point. 22 | 23 | For example: 24 | 25 | This extension contributes the following settings: 26 | 27 | * `myExtension.enable`: enable/disable this extension 28 | * `myExtension.thing`: set to `blah` to do something 29 | 30 | ## Known Issues 31 | 32 | Calling out known issues can help limit users opening duplicate issues against your extension. 33 | 34 | ## Release Notes 35 | 36 | Users appreciate release notes as you update your extension. 37 | 38 | ### 1.0.0 39 | 40 | Initial release of ... 41 | 42 | ### 1.0.1 43 | 44 | Fixed issue #. 45 | 46 | ### 1.1.0 47 | 48 | Added features X, Y, and Z. 49 | 50 | ----------------------------------------------------------------------------------------------------------- 51 | 52 | ## Working with Markdown 53 | 54 | **Note:** You can author your README using Visual Studio Code. Here are some useful editor keyboard shortcuts: 55 | 56 | * Split the editor (`Cmd+\` on macOS or `Ctrl+\` on Windows and Linux) 57 | * Toggle preview (`Shift+CMD+V` on macOS or `Shift+Ctrl+V` on Windows and Linux) 58 | * Press `Ctrl+Space` (Windows, Linux) or `Cmd+Space` (macOS) to see a list of Markdown snippets 59 | 60 | ### For more information 61 | 62 | * [Visual Studio Code's Markdown Support](http://code.visualstudio.com/docs/languages/markdown) 63 | * [Markdown Syntax Reference](https://help.github.com/articles/markdown-basics/) 64 | 65 | **Enjoy!** 66 | -------------------------------------------------------------------------------- /syntaxes/crunch-lang/language-configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": { 3 | // symbol used for single line comment. Remove this entry if your language does not support line comments 4 | "lineComment": "::", 5 | }, 6 | // symbols used as brackets 7 | "brackets": [ 8 | ["\n", "end"], 9 | ["\r\n", "end"], 10 | ["(", ")"] 11 | ], 12 | // symbols that are auto closed when typing 13 | "autoClosingPairs": [ 14 | ["\n", "end"], 15 | ["\r\n", "end"], 16 | ["(", ")"], 17 | ["\"", "\""], 18 | ["'", "'"] 19 | ], 20 | // symbols that that can be used to surround a selection 21 | "surroundingPairs": [ 22 | ["\n", "end"], 23 | ["\r\n", "end"], 24 | ["\"", "\""], 25 | ["'", "'"] 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /syntaxes/crunch-lang/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "crunch-lang", 3 | "displayName": "crunch-lang", 4 | "description": "Syntax highlighting for Crunch", 5 | "version": "0.0.1", 6 | "publisher": "chasewilson", 7 | "engines": { 8 | "vscode": "^1.41.0" 9 | }, 10 | "categories": [ 11 | "Programming Languages" 12 | ], 13 | "contributes": { 14 | "languages": [{ 15 | "id": "crunch", 16 | "aliases": ["Crunch", "crunch"], 17 | "extensions": [".crunch"], 18 | "configuration": "./language-configuration.json" 19 | }], 20 | "grammars": [{ 21 | "language": "crunch", 22 | "scopeName": "source.crunch", 23 | "path": "./syntaxes/crunch.tmLanguage.json" 24 | }] 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /syntaxes/crunch-lang/vsc-extension-quickstart.md: -------------------------------------------------------------------------------- 1 | # Welcome to your VS Code Extension 2 | 3 | ## What's in the folder 4 | 5 | * This folder contains all of the files necessary for your extension. 6 | * `package.json` - this is the manifest file in which you declare your language support and define the location of the grammar file that has been copied into your extension. 7 | * `syntaxes/crunch.tmLanguage.json` - this is the Text mate grammar file that is used for tokenization. 8 | * `language-configuration.json` - this is the language configuration, defining the tokens that are used for comments and brackets. 9 | 10 | ## Get up and running straight away 11 | 12 | * Make sure the language configuration settings in `language-configuration.json` are accurate. 13 | * Press `F5` to open a new window with your extension loaded. 14 | * Create a new file with a file name suffix matching your language. 15 | * Verify that syntax highlighting works and that the language configuration settings are working. 16 | 17 | ## Make changes 18 | 19 | * You can relaunch the extension from the debug toolbar after making changes to the files listed above. 20 | * You can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes. 21 | 22 | ## Add more language features 23 | 24 | * To add features such as intellisense, hovers and validators check out the VS Code extenders documentation at https://code.visualstudio.com/docs 25 | 26 | ## Install your extension 27 | 28 | * To start using your extension with Visual Studio Code copy it into the `/.vscode/extensions` folder and restart Code. 29 | * To share your extension with the world, read on https://code.visualstudio.com/docs about publishing an extension. 30 | --------------------------------------------------------------------------------