├── .gitignore ├── ChangeLog.md ├── LICENSE ├── LICENSE.language-rust ├── README.md ├── app ├── floorplan.hs └── semantics.hs ├── bin └── make-pkg ├── build ├── build-genrs ├── build-immix ├── clean-gen ├── docs ├── .gitignore ├── 404.html ├── Gemfile ├── _config.yml ├── _posts │ └── 2020-01-26-docs-site-goes-live.markdown ├── about.markdown ├── floorplan-info.svg ├── index.markdown └── unsupported-operations.markdown ├── examples ├── app.flp ├── arith.flp ├── arith_id.flp ├── arith_power.flp ├── bits.flp ├── bump.flp ├── dyn_choice.flp ├── dyn_seq.flp ├── dynamic_prim.flp ├── empty.flp ├── enum.flp ├── enum_bad0.flp ├── grafting_loop.flp ├── hotspot.flp ├── immix │ ├── .gitignore │ ├── Cargo.toml │ ├── README.md │ ├── build.rs │ ├── c_src │ │ ├── common.h │ │ ├── freelist.c │ │ ├── freelist.h │ │ ├── gc.c │ │ ├── gc.h │ │ ├── gcbench.c │ │ ├── immix.h │ │ ├── immix_mutator.c │ │ ├── immix_space.c │ │ ├── main.c │ │ ├── objectmodel.c │ │ └── objectmodel.h │ ├── dot │ │ ├── .gitignore │ │ └── call-graph.dot │ ├── features │ ├── features.all │ ├── run-mt-trace.py.example │ ├── run.py.example │ ├── runme │ ├── runone │ ├── rust_c_interface │ │ ├── immix_rust.h │ │ └── test.c │ ├── src │ │ ├── exhaust.rs │ │ ├── gcbench.rs │ │ ├── heap │ │ │ ├── .gitignore │ │ │ ├── debug.rs │ │ │ ├── freelist │ │ │ │ └── mod.rs │ │ │ ├── gc │ │ │ │ ├── clib_x64.c │ │ │ │ └── mod.rs │ │ │ ├── immix │ │ │ │ ├── immix_mutator.rs │ │ │ │ ├── immix_space.rs │ │ │ │ └── mod.rs │ │ │ ├── layout.flp │ │ │ └── mod.rs │ │ ├── leakme.rs │ │ ├── lib.rs │ │ ├── main.rs │ │ ├── mark.rs │ │ ├── mt_gcbench.rs │ │ ├── mt_trace.rs │ │ ├── obj_init.rs │ │ ├── objectmodel │ │ │ └── mod.rs │ │ ├── testme.rs │ │ └── trace.rs │ └── test_mac.sh ├── layer.flp ├── map_bits.flp ├── named_ref.flp ├── nested.flp ├── nested_union.flp ├── parens.flp ├── seq.flp ├── union.flp └── uniq_fail.flp ├── flp-compiler ├── .gitignore ├── Cargo.lock ├── Cargo.toml └── src │ └── lib.rs ├── flp-framework ├── .gitignore ├── Cargo.toml └── src │ ├── address.rs │ └── lib.rs ├── genrs ├── .gitignore ├── Cargo.toml ├── build.rs └── src │ ├── layout.flp │ └── main.rs ├── package.yaml ├── proofs ├── .gitignore ├── Binary.v ├── Calculus.v ├── Common.v ├── ListSet.v ├── Map.v ├── README.md ├── Semantics.v └── Set.v ├── pygmentize ├── .gitignore ├── Makefile ├── README.md └── floorplan.py ├── src └── Language │ ├── Floorplan.hs │ ├── Floorplan │ ├── C │ │ └── Compiler.hs │ ├── Core │ │ ├── Compiler.hs │ │ └── Syntax.hs │ ├── Parser.y │ ├── Preproc │ │ ├── Passes.hs │ │ └── Types.hs │ ├── Rust.hs │ ├── Rust │ │ ├── Common.hs │ │ ├── Compiler.hs │ │ ├── Mapping.hs │ │ └── Types.hs │ ├── Semantics.hs │ ├── Syntax.hs │ └── Token.x │ └── Rust │ ├── Data │ ├── Ident.hs │ ├── InputStream.hs │ └── Position.hs │ ├── Parser.hs │ ├── Parser │ ├── Internal.y │ ├── Lexer.x │ ├── Literals.hs │ ├── NonEmpty.hs │ ├── ParseMonad.hs │ └── Reversed.hs │ ├── Pretty.hs │ ├── Pretty │ ├── Internal.hs │ ├── Literals.hs │ ├── Resolve.hs │ └── Util.hs │ ├── Quote.hs │ ├── Syntax.hs │ └── Syntax │ ├── AST.hs │ ├── Token.hs │ └── Token.hs-boot ├── stack.yaml └── test └── parser └── Main.hs /.gitignore: -------------------------------------------------------------------------------- 1 | /*.c 2 | /dist 3 | Cargo.lock 4 | /*.lock 5 | .stack-work/ 6 | flp.cabal 7 | *~ 8 | -------------------------------------------------------------------------------- /ChangeLog.md: -------------------------------------------------------------------------------- 1 | # Changelog for flp 2 | 3 | ## Unreleased changes 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The code in floorplan is released under the license below. 2 | 3 | The MIT License (MIT) 4 | 5 | Copyright (c) 2019,2020 Karl Cronburg. All rights reserved. 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy of 8 | this software and associated documentation files (the "Software"), to deal in 9 | the Software without restriction, including without limitation the rights to 10 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 11 | of the Software, and to permit persons to whom the Software is furnished to do 12 | so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | -------------------------------------------------------------------------------- /LICENSE.language-rust: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Alec Theriault 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above 12 | copyright notice, this list of conditions and the following 13 | disclaimer in the documentation and/or other materials provided 14 | with the distribution. 15 | 16 | * Neither the name of Alec Theriault nor the names of other 17 | contributors may be used to endorse or promote products derived 18 | from this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Floorplan compiler 2 | 3 | A language for specifying the layout of a one-dimensional address space, particularly 4 | for garbage collectors and manual memory managers written in Rust and C/C++. 5 | 6 | ## Building and running 7 | 8 | Floorplan is written in Haskell and must be [built with either 9 | stack](https://docs.haskellstack.org/en/stable/README/) or 10 | [cabal](haskell.org/cabal). If you just want to build and install Floorplan 11 | globally on your system you can do the following: 12 | 13 | ```bash 14 | $ cabal install flp 15 | ``` 16 | 17 | This will pull the latest stable release of Floorplan from 18 | [Hackage](hackage.haskell.org/package/flp) and install the `flp` executable 19 | globally. If instead you wish to make modifications to the compiler or to 20 | reference it directly from another Haskell project, you can pull the 21 | master branch of this repo and build the project like follows: 22 | 23 | ```bash 24 | $ git clone --branch master https://github.com/RedlineResearch/floorplan.git 25 | $ cd floorplan && stack build 26 | ... 27 | Completed 2 action(s). 28 | ``` 29 | 30 | At which point you can compile, for example, the file `examples/immix/layout.flp` with 31 | the `build-immix` script: 32 | 33 | ```bash 34 | $ ./build-immix 35 | ... 36 | Compiling immix_rust v0.0.1 (/home/karl/w/flp/examples/immix) 37 | Finished dev [unoptimized + debuginfo] target(s) in 4.56s 38 | ``` 39 | 40 | This script ensures the Floorplan compiler is built, installs it for your current 41 | user, and then builds the Immix project which itself invokes the Floorplan compiler 42 | to build the file `examples/immix/src/heap/layout.flp`. 43 | 44 | In order to run the compiler against some other `.flp` file, the compiler can 45 | be run directly as follows: 46 | 47 | ```bash 48 | stack exec flp [path/to/layout.flp] [path/to/generated.rs] 49 | ``` 50 | 51 | ### Rust 52 | 53 | Note that to build a Rust file generated in this manner, 54 | you must include the `flp-framework` to your Cargo dependencies, and `flp-compiler` 55 | to your cargo build-dependencies. The latter is simply a wrapper for calling out 56 | to the (already stack-installed) flp compiler, and the framework crate contains 57 | necessary macros and address types that generated Rust code uses. 58 | 59 | The skeleton of a Rust cargo project is given in the `genrs/` directory of this 60 | repo, which can be copied over and modified to support the needs of a memory 61 | manager other than immix-rust. 62 | 63 | ### C/C++ 64 | 65 | C/C++ output is under active development. Regardless of which output type is chosen 66 | (`.c`, `.h`, or `.hpp`), the compiler will output the same C-like code. This is 67 | expected to work with a recent version of the Clang compiler, but will likely work 68 | just as fine under a modern verison of GCC. 69 | 70 | When reporting an issue with the C output mode of the Floorplan compiler, please 71 | check first or have a reasonable belief that the same issue happens with Clang. 72 | Modifications to the compiler which tailor the output to different C compilers are 73 | more than welcome, but we intend to primarily support Clang as a backend toolchain. 74 | 75 | ## Dependencies 76 | 77 | A customized version of the [language-rust](https://github.com/harpocrates/language-rust) 78 | package is included in the `deps/` directory of this repo, which adds support for 79 | directly splicing of host language expressions into quasiquoted Rust code. This is the 80 | mechanism by which Floorplan generates Rust code. 81 | 82 | ## Testing and contributing 83 | 84 | If you want to help maintain or contribute new code to this project, feel free to 85 | make a pull request or better yet start an issue in the the Issues tracker so that 86 | you can get our feedback along the way. A number of avenues for work on the compiler 87 | exist, including but not limited to: 88 | 89 | - More example Rust allocators implemented with a Floorplan layout. 90 | - Rust templates for allocating on alignment boundaries. 91 | - Extensively document the interfaces generated. 92 | - Better error messages. 93 | - Integrating the core semantics (`app/semantics.hs`) directly into the project 94 | `src/` hierarchy. 95 | - Calling out to a SMT library to verify alignment and size constraints. 96 | - Targeting both C and Rust. 97 | - Support for non-64-bit architectures. 98 | - Generating debugging assertions. 99 | - Dynamic tracking of type information for each piece of the heap. 100 | - Cleaning up the dependencies by integrating the Rust splicing support directly 101 | into [the upstream repository](https://github.com/harpocrates/language-rust), 102 | e.g. as a separate quasiquoter. 103 | - Generate cargo-based Rust documentation alongside generated functions, indicating 104 | why a certain function was generated and how it might be used. 105 | - Repairing the Coq proofs in the `proofs/*.v` files. 106 | - Better Rust integration and downstream crates. 107 | 108 | -------------------------------------------------------------------------------- /app/floorplan.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings, ScopedTypeVariables #-} 2 | module Main where 3 | import Prelude hiding (putStrLn) 4 | import qualified Prelude as P 5 | import System.Environment 6 | import System.IO hiding (putStr, putStrLn, hGetLine) 7 | import System.Exit (exitSuccess, exitFailure) 8 | import Control.Exception (assert) 9 | import Data.List (isSuffixOf) 10 | import Data.Either (partitionEithers) 11 | import Text.RE.TDFA.String (RE(..), compileRegex) 12 | 13 | import Language.Rust.Syntax (SourceFile(..)) 14 | import Language.Rust.Data.Position (Span(..)) 15 | import Language.Rust.Pretty (pretty') 16 | 17 | import Language.Floorplan 18 | import qualified Language.Floorplan.Rust.Compiler as RC 19 | 20 | --import qualified Language.Floorplan.Parser as P 21 | import qualified Language.Floorplan.Parser as P 22 | import qualified Language.Floorplan.Token as T 23 | import qualified Language.Floorplan.Syntax as S 24 | import qualified Language.Floorplan.Core.Compiler as CC 25 | import qualified Language.Floorplan.Core.Syntax as CS 26 | 27 | import qualified Language.Floorplan.C.Compiler as CComp 28 | import qualified Language.Floorplan.Preproc.Passes as Preproc 29 | 30 | usage = do 31 | pName <- getProgName 32 | P.putStrLn $ pName ++ " [file.flp] [path/to/dest.{rs,c}]" 33 | exitFailure 34 | 35 | data CompilerOutput = RustOutput | COutput | Unknown 36 | 37 | validateAnalysis [] = return () 38 | validateAnalysis xs = P.putStrLn (show xs) >> exitFailure 39 | 40 | -- | Returns a tuple (for now just the [RE]) of the results of analyses with computed results. 41 | doAnalyses :: [Decl] -> IO ([RE], String, String) 42 | doAnalyses result = do 43 | (errors, regexes) <- partitionEithers <$> Preproc.regexAnalysis result 44 | validateAnalysis errors 45 | validateAnalysis $ Preproc.balancedScopeAnalysis result 46 | validateAnalysis $ Preproc.graftingAnalysis result 47 | let (err, header, footer) = Preproc.headFootAnalysis result 48 | validateAnalysis err 49 | return (regexes, header, footer) 50 | 51 | --print $ pretty' sf 52 | 53 | doResult :: CompilerOutput -> FilePath -> [Decl] -> IO () 54 | doResult Unknown outputFile _ = P.putStrLn ("Error: File type unknown '" ++ outputFile ++ "'") >> usage 55 | doResult out_type outputFile result = do 56 | (filterOutRegexes, header, footer) <- doAnalyses result 57 | P.putStrLn ("No grafting errors. Proceeding to graft.") 58 | let layers :: [S.Demarc] = Preproc.removeNoGlobalPass result 59 | grafted :: [S.Demarc] = map (Preproc.grafting $ onlyLayers result) layers 60 | core_flp :: [CS.BaseExp] = map CC.compile grafted 61 | assert (CC.countGrafting grafted == 0) (return ()) 62 | P.putStrLn ("Grafting completed.") 63 | case out_type of 64 | RustOutput -> do sf :: SourceFile Span <- return $ RC.genRust core_flp 65 | if length filterOutRegexes > 0 66 | then P.putStrLn "Warning: Rust output mode does not support filtering regular expressions. Ignoring." 67 | else return () 68 | if length header > 0 || length footer > 0 69 | then P.putStrLn "Warning: Rust output mode does not support headers or footers. Ignoring." 70 | else return () 71 | (RC.writeModule outputFile) sf 72 | COutput -> do sf <- return $ CComp.genC core_flp filterOutRegexes 73 | (CComp.writeCFile outputFile (header, footer)) sf 74 | exitSuccess 75 | 76 | oops s = P.putStr s >> exitFailure 77 | 78 | checkSuffix f 79 | | ".rs" `isSuffixOf` f = RustOutput 80 | | ".c" `isSuffixOf` f = COutput 81 | | ".h" `isSuffixOf` f = COutput 82 | | ".hpp" `isSuffixOf` f = COutput 83 | | otherwise = Unknown 84 | 85 | main = do 86 | args <- getArgs 87 | case args of 88 | (flpFile : outputFile : rst) -> 89 | do P.putStrLn $ "Loading FLP file from " ++ flpFile ++ "..." 90 | contents <- readFile flpFile 91 | P.putStrLn $ "Parsing top-level declarations..." 92 | --let toks = T.scanTokens contents 93 | --P.putStrLn $ "Tokens: " ++ show toks 94 | let result = P.parseTopLevelDecls contents 95 | let cnt = sum $ map countDeclNodes result 96 | P.putStrLn $ "Parsed contents: " ++ show result 97 | P.putStrLn ("Surface-syntax AST nodes: " ++ show cnt) 98 | doResult (checkSuffix outputFile) outputFile result 99 | _ -> usage 100 | 101 | 102 | -------------------------------------------------------------------------------- /bin/make-pkg: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | SCRIPTDIR="$(cd "`dirname $0`"; pwd)" 3 | GITDIR="$(dirname "$SCRIPTDIR")" 4 | 5 | # ============================================================================= 6 | # MODIFY THESE PARAMETERS EACH TIME YOU PUBLISH A NEW VERSION: 7 | arch=x86_64-linux-tinfo6 #-nopie 8 | cVersion=2.4.0.1 9 | pVersion=0.1.0.0 10 | # ============================================================================= 11 | 12 | gzDir=$GITDIR/.stack-work/dist/$arch/Cabal-$cVersion 13 | gzFile=flp-$pVersion.tar.gz 14 | 15 | echo "Changing to directory $GITDIR" 16 | cd "$GITDIR" 17 | 18 | # Generates .stack-work/dist/[arch]/Cabal-[version]/flp-[version].tar.gz : 19 | echo "Generating $gzFile" 20 | stack sdist --pvp-bounds both 21 | 22 | # Generates .stack-work/dist/[arch]/Cabal-[version]/doc/html/flp/* : 23 | echo "Generating haddock documentation" 24 | stack haddock --haddock-hyperlink-source 25 | 26 | # Copy source gzipped tar file to dist: 27 | mkdir -p dist 28 | cp $gzDir/$gzFile dist/ 29 | 30 | # Copy over the haddock documentation: 31 | destDir=dist/flp-$pVersion/flp-$pVersion-docs 32 | mkdir -p $destDir 33 | cp -r $gzDir/doc/html/flp $destDir 34 | 35 | # Generate tar file to upload to hackage: 36 | echo "Making haddock tar file for *manual* upload to https://hackage.haskell.org/package/flp-$pVersion/maintain/docs" 37 | tarFile=flp-$pVersion-docs.tar 38 | tar --format=ustar -cvf dist/$tarFile $destDir | grep index.html 39 | 40 | -------------------------------------------------------------------------------- /build: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | stack build --ghc-options="-ddump-splices -ddump-to-file"; ret=$? 3 | if ! [ $ret -eq 0 ]; then exit $ret; fi 4 | stack exec flp examples/hotspot.flp out.c && cat out.c 5 | 6 | -------------------------------------------------------------------------------- /build-genrs: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | stack build flp --ghc-options="-ddump-splices" 4 | if [ $? -eq 0 ]; then 5 | # Add the path of the executable built by stack to PATH instead of installing it: 6 | export PATH="$(pwd)/$(dirname $(find .stack-work -path */flp/flp | head -n1)):$PATH" 7 | (cd genrs; cargo build) 8 | else 9 | echo "Failed to build FLP." 10 | fi 11 | #&& stack exec flp ./examples/immix/src/heap/layout.flp ./genrs/src/heap/flp/generated.rs \ 12 | 13 | -------------------------------------------------------------------------------- /build-immix: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | IMMIX="./examples/immix" 4 | 5 | #stack exec flp $IMMIX/layout.flp $IMMIX/src/heap/flp/generated.rs \ 6 | stack build flp --ghc-options="-ddump-splices" 7 | if [ $? -eq 0 ]; then 8 | # Add the path of the executable built by stack to PATH instead of installing it: 9 | export PATH="$(pwd)/$(dirname $(find .stack-work -path */flp/flp | head -n1)):$PATH" 10 | (cd $IMMIX; cargo build --features "gcbench") 11 | else 12 | echo "Failed to build FLP." 13 | fi 14 | -------------------------------------------------------------------------------- /clean-gen: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | (cd genrs; cargo clean; rm -f src/heap/flp/generated.rs) 4 | 5 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | _site 2 | .sass-cache 3 | .jekyll-cache 4 | .jekyll-metadata 5 | vendor 6 | -------------------------------------------------------------------------------- /docs/404.html: -------------------------------------------------------------------------------- 1 | --- 2 | permalink: /404.html 3 | layout: default 4 | --- 5 | 6 | 19 | 20 |
21 |

404

22 | 23 |

Page not found :(

24 |

The requested page could not be found.

25 |
26 | -------------------------------------------------------------------------------- /docs/Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | # Hello! This is where you manage which Jekyll version is used to run. 3 | # When you want to use a different version, change it below, save the 4 | # file and run `bundle install`. Run Jekyll with `bundle exec`, like so: 5 | # 6 | # bundle exec jekyll serve 7 | # 8 | # This will help ensure the proper Jekyll version is running. 9 | # Happy Jekylling! 10 | #gem "jekyll", "~> 4.0.0" 11 | # This is the default theme for new Jekyll sites. You may change this to anything you like. 12 | gem "minima", "~> 2.5" 13 | # If you want to use GitHub Pages, remove the "gem "jekyll"" above and 14 | # uncomment the line below. To upgrade, run `bundle update github-pages`. 15 | gem "github-pages", group: :jekyll_plugins 16 | 17 | # If you have any plugins, put them here! 18 | group :jekyll_plugins do 19 | gem "jekyll-feed", "~> 0.12" 20 | end 21 | 22 | # Windows and JRuby does not include zoneinfo files, so bundle the tzinfo-data gem 23 | # and associated library. 24 | install_if -> { RUBY_PLATFORM =~ %r!mingw|mswin|java! } do 25 | gem "tzinfo", "~> 1.2" 26 | gem "tzinfo-data" 27 | end 28 | 29 | # Performance-booster for watching directories on Windows 30 | gem "wdm", "~> 0.1.1", :install_if => Gem.win_platform? 31 | 32 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | # Welcome to Jekyll! 2 | # 3 | # This config file is meant for settings that affect your whole blog, values 4 | # which you are expected to set up once and rarely edit after that. If you find 5 | # yourself editing this file very often, consider using Jekyll's data files 6 | # feature for the data you need to update frequently. 7 | # 8 | # For technical reasons, this file is *NOT* reloaded automatically when you use 9 | # 'bundle exec jekyll serve'. If you change this file, please restart the server process. 10 | # 11 | # If you need help with YAML syntax, here are some quick references for you: 12 | # https://learn-the-web.algonquindesign.ca/topics/markdown-yaml-cheat-sheet/#yaml 13 | # https://learnxinyminutes.com/docs/yaml/ 14 | # 15 | # Site settings 16 | # These are used to personalize your new site. If you look in the HTML files, 17 | # you will see them accessed via {{ site.title }}, {{ site.email }}, and so on. 18 | # You can create any custom variable you would like, and they will be accessible 19 | # in the templates via {{ site.myvariable }}. 20 | 21 | title: Floorplan for Memory Layouts 22 | email: karl@cs.tufts.edu 23 | description: >- # this means to ignore newlines until "baseurl:" 24 | A language for specifying the layout of a one-dimensional address space, 25 | for garbage collectors and manual memory managers written in Rust. 26 | baseurl: "/" # the subpath of your site, e.g. /blog 27 | url: "redlineresearch.github.io" # the base hostname & protocol for your site, e.g. http://example.com 28 | #twitter_username: jekyllrb 29 | github_username: RedlineResearch 30 | 31 | # Build settings 32 | theme: minima 33 | #theme: jekyll-theme-slate 34 | plugins: 35 | - jekyll-feed 36 | 37 | # Exclude from processing. 38 | # The following items will not be processed, by default. 39 | # Any item listed under the `exclude:` key here will be automatically added to 40 | # the internal "default list". 41 | # 42 | # Excluded items can be processed by explicitly listing the directories or 43 | # their entries' file path in the `include:` list. 44 | # 45 | # exclude: 46 | # - .sass-cache/ 47 | # - .jekyll-cache/ 48 | # - gemfiles/ 49 | # - Gemfile 50 | # - Gemfile.lock 51 | # - node_modules/ 52 | # - vendor/bundle/ 53 | # - vendor/cache/ 54 | # - vendor/gems/ 55 | # - vendor/ruby/ 56 | -------------------------------------------------------------------------------- /docs/_posts/2020-01-26-docs-site-goes-live.markdown: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: "Floorplan Compiler Documentation" 4 | date: 2020-01-26 15:50:00 -0500 5 | categories: floorplan update 6 | --- 7 | 8 | The most extensive documentation of the compiler can at present be 9 | found in the preprint of our accompanying conference paper [as available here][preprint-link]. 10 | For prospective users of the compiler, we recommend reading Section 3 of the paper as that 11 | provides a practical view of what type of code the compiler generates for you. 12 | 13 | # Installation 14 | 15 | The Floorplan compiler itself (Haskell project) must be installed from source 16 | with the `stack` toolchain like so: 17 | 18 | ```bash 19 | git clone https://github.com/RedlineResearch/floorplan.git 20 | cd floorplan 21 | stack build 22 | stack install 23 | ``` 24 | 25 | Once this is done, and the `flp` executable from doing so is on your `$PATH`, you 26 | can write Floorplan-enabled Rust crates by adding the following two crates to 27 | your `Cargo.toml`: 28 | 29 | - [flp-framework-0.1.0](https://crates.io/crates/flp-framework/0.1.0) added to `[dependencies]` 30 | - [flp-compiler-0.1.0](https://crates.io/crates/flp-compiler/0.1.0) added to `[build-dependencies]` 31 | 32 | # The "Hello World" of Floorplan 33 | 34 | The simplest memory layout you can define in Floorplan is an opaque chunk of memory, 35 | such as the ``Hello`` type as follows: 36 | 37 | {% highlight floorplan %} 38 | Hello -> 1 bytes 39 | {% endhighlight %} 40 | 41 | [preprint-link]: https://cronburg.com/papers/floorplan19.pdf 42 | -------------------------------------------------------------------------------- /docs/about.markdown: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: About 4 | permalink: /about/ 5 | --- 6 | 7 | Floorplan is a research project developed by the [RedlineResearch group][redline-research] 8 | at Tufts University, with ancillary technical support as well as encouragement from various 9 | members of the [Tufts Programming Languages][tupl-link] research group. 10 | 11 | ![Floorplan Info](./floorplan-info.svg) 12 | 13 | [redline-research]: http://www.cs.tufts.edu/research/redline/ 14 | [tupl-link]: https://tupl.cs.tufts.edu/ 15 | 16 | -------------------------------------------------------------------------------- /docs/index.markdown: -------------------------------------------------------------------------------- 1 | --- 2 | # Feel free to add content and custom Front Matter to this file. 3 | # To modify the layout, see https://jekyllrb.com/docs/themes/#overriding-theme-defaults 4 | 5 | layout: home 6 | --- 7 | -------------------------------------------------------------------------------- /docs/unsupported-operations.markdown: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: Unsupported Floorplan Operations 4 | permalink: /unsupported-operations/ 5 | --- 6 | 7 | Many public functions and interfaces made available through the `flp-framework` 8 | crate is intrinsically unsafe, and unsupported for direct use in memory manager 9 | implementations. They are made available because: 10 | 11 | 1. Code generated by the `flp-compiler` crate needs common access to basic 12 | address computations. 13 | 2. To provide quick and easy prototyping of Floorplan extensions currently 14 | in development without requiring major modifications to the compiler. 15 | 3. To provide an escape hatch for performance sensitive memory management code 16 | which is not well supported by Floorplan. 17 | 18 | -------------------------------------------------------------------------------- /examples/app.flp: -------------------------------------------------------------------------------- 1 | // Parser test: nested groups (union and seq) with 2 | // fixed-width fields. 3 | 4 | Block -> union { 5 | A -> seq { a : Cell<$ 0 $>, b : 2 words } 6 | | B -> seq { a : 0 words, c : 2 words } 7 | } 8 | 9 | -------------------------------------------------------------------------------- /examples/arith.flp: -------------------------------------------------------------------------------- 1 | // Parser test 2 | 3 | Arith ||7 bytes|| -> (4 / 2 * 3 + 1) bytes 4 | 5 | -------------------------------------------------------------------------------- /examples/arith_id.flp: -------------------------------------------------------------------------------- 1 | // Parser test: arithmetic expressions with identifiers 2 | 3 | Block -> union { 4 | A -> seq { a : 0 words, b : n words } 5 | | B -> seq { a : n words, c : k words } 6 | } 7 | 8 | -------------------------------------------------------------------------------- /examples/arith_power.flp: -------------------------------------------------------------------------------- 1 | // Parser test: exponentiation in arithmetic inside a field. 2 | 3 | Block -> union { 4 | A -> seq { a : 0 words, b : 2^2 words } 5 | | B -> seq { a : 0 words, c : 2^4 words } 6 | } 7 | 8 | -------------------------------------------------------------------------------- /examples/bits.flp: -------------------------------------------------------------------------------- 1 | // Parser test 2 | 3 | RefBits -> bits { 4 | SHORT_ENCODE : 1 bits, 5 | OBJ_START : 1 bits, 6 | REF : 6 bits, 7 | MORE : (2 + 2) bits + 4 bits 8 | } 9 | 10 | -------------------------------------------------------------------------------- /examples/bump.flp: -------------------------------------------------------------------------------- 1 | 2 | Bump ||2^16 bytes|| @(2^16 bytes)@ -> seq { 3 | union { 4 | Partial ||7 * 2^13 bytes|| @(2^13 bytes)@ -> seq { 5 | cells : # Cell, 6 | cursor : # words, 7 | limit : 0 words 8 | } 9 | | # Allocd -> 1 words 10 | } 11 | } 12 | 13 | -------------------------------------------------------------------------------- /examples/dyn_choice.flp: -------------------------------------------------------------------------------- 1 | // Parser test: dynamic union of two fixed-width fields. 2 | 3 | Union ||2 bytes|| @(2 bytes)@ -> # union { 4 | a : 1 bytes 5 | | b : 1 bytes 6 | } 7 | 8 | -------------------------------------------------------------------------------- /examples/dyn_seq.flp: -------------------------------------------------------------------------------- 1 | // Parser test: dynamic sequence with fixed-width fields. 2 | 3 | Seq ||2 bytes|| @(2 bytes)@ -> # seq { 4 | a : 1 bytes, 5 | b : 1 bytes 6 | } 7 | 8 | -------------------------------------------------------------------------------- /examples/dynamic_prim.flp: -------------------------------------------------------------------------------- 1 | // Parser test: dynamic primitive in arithmetic 2 | 3 | Seq -> seq { 4 | a : # bytes, 5 | b : 1 bytes 6 | } 7 | 8 | -------------------------------------------------------------------------------- /examples/empty.flp: -------------------------------------------------------------------------------- 1 | // Mostly empty file 2 | 3 | // Ok. 4 | 5 | Foo -> Bar 6 | 7 | -------------------------------------------------------------------------------- /examples/enum.flp: -------------------------------------------------------------------------------- 1 | // Parser test: basic enum 2 | 3 | LineMark -> enum { 4 | Free | Live | FreshAlloc 5 | | ConservLive | PrevLive 6 | } 7 | 8 | -------------------------------------------------------------------------------- /examples/enum_bad0.flp: -------------------------------------------------------------------------------- 1 | // Parser test: basic enum failure due to lower-case ID (prevLive) 2 | 3 | LineMark -> enum { 4 | Free | Live | FreshAlloc 5 | | ConservLive | prevLive 6 | } 7 | 8 | -------------------------------------------------------------------------------- /examples/grafting_loop.flp: -------------------------------------------------------------------------------- 1 | // Grafting error test. 2 | 3 | A -> B 4 | B -> C 5 | C -> A 6 | -------------------------------------------------------------------------------- /examples/immix/.gitignore: -------------------------------------------------------------------------------- 1 | /out*.txt 2 | /std*.txt 3 | *.log 4 | ^.*\.s$ 5 | *.out 6 | *.dylib 7 | *.so 8 | Cargo.lock 9 | target/* 10 | .project 11 | .setting 12 | -------------------------------------------------------------------------------- /examples/immix/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | 3 | name = "immix_rust" 4 | version = "0.0.1" 5 | 6 | # Original author shown here: 7 | authors = [ "Yi Lin " ] 8 | build = "build.rs" 9 | 10 | [build-dependencies] 11 | cc = "1.0" 12 | flp-compiler = { path = "../../flp-compiler" } 13 | 14 | [lib] 15 | name = "immix_rust" 16 | path = "src/lib.rs" 17 | crate-type = ["dylib"] 18 | 19 | [[bin]] 20 | name = "immix-rust" 21 | path = "src/main.rs" 22 | 23 | [features] 24 | exhaust = [] 25 | initobj = [] 26 | mt-gcbench = ["mt-trace"] 27 | gcbench = ["mt-trace"] 28 | mark = [] 29 | trace = [] 30 | linked-list = [] 31 | leakme = [] 32 | testme = [] 33 | 34 | mt-trace = [] 35 | 36 | [dependencies] 37 | flp-framework = { path = "../../flp-framework" } 38 | time = "0.2.16" 39 | memmap = "0.7.0" 40 | libc = "0.2.71" 41 | lazy_static = "1.4.0" 42 | log = { version = "0.4.8", features = [] } 43 | aligned_alloc = "0.1.3" 44 | crossbeam = "0.7.3" 45 | simple_logger = "1.6.0" 46 | backtrace = "0.3.48" 47 | rand = "0.7.3" 48 | 49 | -------------------------------------------------------------------------------- /examples/immix/README.md: -------------------------------------------------------------------------------- 1 | immix-rust 2 | ========== 3 | 4 | An Immix implementation written in Rust. 5 | 6 | Features 7 | -------- 8 | 9 | * Immix GC 10 | * thread local allocation 11 | * parallel GC 12 | * non generational, non moving, no defragment 13 | * Free list allocation (as large object allocator) 14 | * linked list + malloc 15 | * no proper reclamation yet 16 | * will reimplement this part in near future 17 | * Target platform 18 | * x86_64 (root tracing including stack scanning is target dependent) 19 | 20 | Interface 21 | -------------- 22 | `src/lib.rs` defines interface to use the GC. The interface is also 23 | exposed as a C header `rust_c_interface/immix_rust.h`. 24 | 25 | ### Initialisation and Destroy 26 | 27 | * Rust: `pub extern fn gc_init(immix_size: usize, lo_size: usize, n_gcthreads: usize)` 28 | * C: `extern void gc_init(uint64_t immix_size, uint64_t lo_size, uint64_t n_gcthreads)` 29 | 30 | initialises the GC. First two parameters give sizes of immix space and 31 | large object space (in bytes), the 3rd parameter defines the number of 32 | GC threads (only used if multi-thread tracing is turned on via 33 | `--features mt-trace`) 34 | 35 | * Rust: `pub extern fn new_mutator() -> Box` 36 | * C: `extern struct Mutator* new_mutator()` 37 | 38 | creates a mutator. The user is responsible for storing the mutator at thread 39 | local storage. 40 | 41 | * Rust: `pub extern fn drop_mutator(mutator: Box)` 42 | * C: `extern void drop_mutator(struct Mutator* mutator)` 43 | 44 | destroys the mutator. After the call, `mutator` is no longer valid. This should 45 | only be useful when used in C (since Rust will identify the liveness of the mutator, 46 | and drop it when necessary). 47 | 48 | * Rust: `extern "C" {pub fn set_low_water_mark();}` 49 | * C: `extern void set_low_water_mark()` 50 | 51 | stores current stack pointer as a limit, so that conservative stack scanning 52 | will not traverse stack beyond this limit. Call this function after getting 53 | a new mutator. 54 | 55 | ### Allocation 56 | 57 | * Rust: `pub extern fn alloc(mutator: &mut Box, size: usize, align: usize) -> ObjectReference` 58 | * C: `inline uint64_t alloc(struct Mutator** mutator, uint64_t size, uint64_t align)` 59 | 60 | allocates an object in Immix space (thread local allocation). 61 | The object size should be smaller than one Immix line (256 bytes). 62 | Use large object allocation if the object size is larger than 256 bytes. 63 | 64 | * Rust: `pub extern fn alloc_large(mutator: &mut Box, size: usize) -> ObjectReference` 65 | * C: `extern uint64_t alloc_large(struct Mutator** mutator, uint64_t size)` 66 | 67 | allocates an object in large object space (global synchronisation involved). 68 | May later move the object size check into `alloc()` and deprecate this function. 69 | 70 | * Rust: `pub extern fn yieldpoint(mutator: &mut Box)` 71 | * C: `inline void yieldpoint(bool* take_yield, struct Mutator** m)` 72 | 73 | checks if current mutator should yield. GC won't be able to stop a mutator 74 | unless this function is put into code. 75 | 76 | Note: `alloc` and `yieldpoint` are fast paths. They are provided in Rust, 77 | and Rust compiler is able to inline them into Rust code. And they are 78 | expressed in C code in the header file, so that C compiler is able to inline them. 79 | 80 | Usage 81 | ----- 82 | Install Rust and Cargo from https://www.rust-lang.org/. 83 | Run `cargo build --release` under the repo directory to build the GC 84 | (add `--features mt-trace` to turn on parallel GC), which 85 | will generate a dynamic linked library under `target/release/`. 86 | `rust_c_interface/test.c` gives an example on how to use the GC from 87 | C code. Compile and link `test.c` with the library to test (it is an 88 | infinite allocation loop). 89 | 90 | Near Future Plan 91 | ------ 92 | 93 | * implementing a more efficient free list allocator 94 | * removing `alloc_large()`, allocation in large object space will be triggered 95 | in `alloc()` if the object size exceeds a threshold. 96 | -------------------------------------------------------------------------------- /examples/immix/build.rs: -------------------------------------------------------------------------------- 1 | extern crate cc; 2 | extern crate flp_compiler as flpc; 3 | 4 | fn main() { 5 | //cc::compile_library("libgc_clib_x64.a", &["src/heap/gc/clib_x64.c"]); 6 | cc::Build::new() 7 | .file("src/heap/gc/clib_x64.c") 8 | .compile("libgc_clib_x64.a"); 9 | 10 | flpc::Build::new() 11 | .src("src/heap/layout.flp") 12 | .dest("src/heap/layout.rs") 13 | .compile(); 14 | // 15 | // if cfg!(target_os = "linux") { 16 | // cc::Config::new() 17 | // .flag("-lpfm") 18 | // .flag("-O3") 19 | // .file("src/common/perf.c") 20 | // .compile("libgc_perf.a"); 21 | // } 22 | } 23 | -------------------------------------------------------------------------------- /examples/immix/c_src/common.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #ifndef COMMON_H 4 | #define COMMON_H 5 | typedef uintptr_t Address; 6 | typedef uintptr_t ObjectReference; 7 | 8 | #define LOG_POINTER_SIZE 3 9 | #define POINTER_SIZE (1 << LOG_POINTER_SIZE) 10 | 11 | #define likely(x) __builtin_expect((x),1) 12 | #define unlikely(x) __builtin_expect((x),0) 13 | 14 | #define Address_store(addr, Ty, value) *((Ty*)addr) = value 15 | 16 | #define lower_bits(value, len) (value & ((1 << len) - 1)) 17 | #define test_nth_bit(value, index) ((value & (1 << index)) != 0) 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /examples/immix/c_src/freelist.c: -------------------------------------------------------------------------------- 1 | #include "freelist.h" 2 | #include "immix.h" 3 | #include 4 | #include 5 | #include 6 | 7 | int LO_SPACE_SIZE = DEFAULT_HEAP_SIZE * LO_SPACE_RATIO; 8 | 9 | FreeListSpace* lo_space; 10 | 11 | /* 12 | * freelist space (now using global malloc) 13 | */ 14 | FreeListSpace* FreeListSpace_new(int space_size) { 15 | FreeListSpace* ret = (FreeListSpace*) malloc(sizeof(FreeListSpace)); 16 | 17 | ret->head = NULL; 18 | ret->last = NULL; 19 | ret->total_size = space_size; 20 | ret->used_size = 0; 21 | pthread_mutex_init(&(ret->lock), NULL); 22 | 23 | return ret; 24 | } 25 | 26 | Address FreeListSpace_alloc(FreeListSpace* flSpace, int size, int align) { 27 | pthread_mutex_lock( &(flSpace->lock)); 28 | 29 | if (flSpace->used_size + size > flSpace->total_size) { 30 | pthread_mutex_unlock( &(flSpace->lock)); 31 | return 0; 32 | } 33 | 34 | // actually allocation 35 | Address addr; 36 | int ret = posix_memalign((void*)&addr, align, size); 37 | if (ret != 0) { 38 | printf("trying to alloc from freelist space: size=%d, align=%d\n", size, align); 39 | printf("failed posix_memalign alloc"); 40 | exit(1); 41 | } 42 | 43 | // metadata 44 | FreeListNode* node = (FreeListNode*) malloc(sizeof(FreeListNode)); 45 | node->next = NULL; 46 | node->addr = addr; 47 | node->size = size; 48 | 49 | // update freelist space 50 | if (flSpace->last == NULL) { 51 | // first node 52 | flSpace->head = node; 53 | flSpace->last = node; 54 | } else { 55 | flSpace->last->next = node; 56 | } 57 | 58 | flSpace->used_size = flSpace->used_size + size; 59 | 60 | pthread_mutex_unlock( &(flSpace->lock)); 61 | 62 | return addr; 63 | } 64 | 65 | Address alloc_large(int size, int align, ImmixMutatorLocal* mutator, FreeListSpace* space) { 66 | while (true) { 67 | yieldpoint(mutator); 68 | 69 | Address ret = FreeListSpace_alloc(space, size, align); 70 | 71 | if (ret != 0) { 72 | return ret; 73 | } else { 74 | printf("Space exhausted for large alloc\n"); 75 | printf("gc should happen here\n"); 76 | exit(1); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /examples/immix/c_src/freelist.h: -------------------------------------------------------------------------------- 1 | #include "immix.h" 2 | 3 | #ifndef FREELIST_H 4 | #define FREELIST_H 5 | 6 | #define LO_SPACE_RATIO (1 - IMMIX_SPACE_RATIO) 7 | extern int LO_SPACE_SIZE; 8 | 9 | /* 10 | * FreeList Node, Space 11 | */ 12 | struct FreeListNode; 13 | typedef struct FreeListNode { 14 | struct FreeListNode* next; 15 | Address addr; 16 | int64_t size; 17 | } FreeListNode; 18 | 19 | typedef struct FreeListSpace { 20 | FreeListNode* head; 21 | FreeListNode* last; 22 | 23 | pthread_mutex_t lock; 24 | 25 | int used_size; 26 | int total_size; 27 | } FreeListSpace; 28 | 29 | extern FreeListSpace* lo_space; 30 | 31 | Address alloc_large(int size, int align, ImmixMutatorLocal* mutator, FreeListSpace* space); 32 | 33 | FreeListSpace* FreeListSpace_new(int space_size); 34 | Address FreeListSpace_alloc(FreeListSpace* flSpace, int size, int align); 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /examples/immix/c_src/gc.c: -------------------------------------------------------------------------------- 1 | #include "gc.h" 2 | #include "immix.h" 3 | #include "objectmodel.h" 4 | 5 | #include 6 | #include 7 | 8 | AddressVector* AddressVector_new(int init_capacity) { 9 | Address* data = (Address*) malloc(init_capacity * sizeof(Address)); 10 | 11 | AddressVector* ret = (AddressVector*) malloc(sizeof(AddressVector)); 12 | ret->current_capacity = init_capacity; 13 | ret->length = 0; 14 | ret->data = data; 15 | 16 | return ret; 17 | } 18 | 19 | void AddressVector_grow(AddressVector* list) { 20 | int old_capacity = list->current_capacity; 21 | int new_capacity = old_capacity * 2; 22 | 23 | void* new_data = malloc(new_capacity * sizeof(Address)); 24 | memcpy(new_data, (void*)list->data, old_capacity * sizeof(Address)); 25 | 26 | free((void*)list->data); 27 | 28 | list->data = (Address*) new_data; 29 | list->current_capacity = new_capacity; 30 | } 31 | 32 | void start_trace(AddressVector* roots) { 33 | uint8_t mark_state = MARK_STATE; 34 | 35 | LineMark* line_mark_table = immix_space->line_mark_table; 36 | int line_mark_table_len = immix_space->line_mark_table_len; 37 | Address space_start = immix_space->start; 38 | Address space_end = immix_space->end; 39 | 40 | while (!AddressVector_is_empty(roots)) { 41 | trace_object(AddressVector_pop(roots), roots, mark_state, line_mark_table, line_mark_table_len, space_start, space_end); 42 | } 43 | } 44 | 45 | void trace_object_no_inline( 46 | ObjectReference obj, 47 | AddressVector* work_queue, 48 | uint8_t mark_state, 49 | LineMark* line_mark_table, int line_mark_table_len, 50 | Address space_start, Address space_end 51 | ) { 52 | mark_as_traced(immix_space->trace_map, obj, space_start, mark_state); 53 | 54 | if (obj >= space_start && obj < space_end) { 55 | mark_line_live(line_mark_table, line_mark_table_len, space_start, obj); 56 | } else { 57 | // freelist mark 58 | } 59 | 60 | Address base = obj; 61 | 62 | while (true) { 63 | uint8_t value = get_ref_byte(obj); 64 | uint8_t ref_bits = lower_bits(value, REF_BITS_LEN); 65 | bool short_encode = test_nth_bit(value, SHORT_ENCODE_BIT); 66 | 67 | switch (ref_bits) { 68 | case 0b00000001: 69 | process_edge(base, work_queue, mark_state); break; 70 | case 0b00000011: 71 | process_edge(base, work_queue, mark_state); 72 | process_edge(base + 8, work_queue, mark_state); break; 73 | case 0b00001111: 74 | process_edge(base, work_queue, mark_state); 75 | process_edge(base + 8, work_queue, mark_state); 76 | process_edge(base + 16, work_queue, mark_state); 77 | process_edge(base + 24, work_queue, mark_state); break; 78 | default: 79 | printf("unexpected ref_bits patterns"); exit(1); 80 | } 81 | 82 | if (short_encode) { 83 | return; 84 | } else { 85 | printf("should not use long encode"); exit(1); 86 | base = base + REF_BITS_LEN * 8; 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /examples/immix/c_src/gc.h: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | #include "immix.h" 3 | #include "objectmodel.h" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #ifndef GC_H 10 | #define GC_H 11 | 12 | /* 13 | * A linked list that would be useful when scanning object 14 | */ 15 | typedef struct AddressVector { 16 | int current_capacity; 17 | 18 | int length; 19 | Address* data; 20 | } AddressVector; 21 | 22 | AddressVector* AddressVector_new(int init_capacity); 23 | void AddressVector_grow(AddressVector* list); 24 | 25 | inline void AddressVector_push(AddressVector* list, Address value) __attribute__((always_inline)); 26 | inline void AddressVector_push(AddressVector* list, Address value) { 27 | if (unlikely(list->length == list->current_capacity)) { 28 | // grow 29 | AddressVector_grow(list); 30 | } 31 | 32 | list->data[list->length] = value; 33 | list->length ++; 34 | } 35 | 36 | inline Address AddressVector_pop(AddressVector* list) __attribute__((always_inline)); 37 | inline Address AddressVector_pop(AddressVector* list) { 38 | Address ret = list->data[list->length - 1]; 39 | list->length--; 40 | return ret; 41 | } 42 | 43 | inline bool AddressVector_is_empty(AddressVector* list) __attribute__((always_inline)); 44 | inline bool AddressVector_is_empty(AddressVector* list) { 45 | return list->length == 0; 46 | } 47 | 48 | void start_trace(AddressVector* roots); 49 | 50 | inline void process_edge(Address addr, AddressVector* work_queue, uint8_t mark_state) __attribute__((always_inline)); 51 | inline void process_edge(Address addr, AddressVector* work_queue, uint8_t mark_state) { 52 | ObjectReference obj = *((ObjectReference*) addr); 53 | 54 | if (obj != 0 && !is_traced(obj, mark_state)) { 55 | AddressVector_push(work_queue, obj); 56 | } 57 | } 58 | 59 | void trace_object_no_inline( 60 | ObjectReference obj, 61 | AddressVector* work_queue, 62 | uint8_t mark_state, 63 | LineMark* line_mark_table, int line_mark_table_len, 64 | Address space_start, Address space_end 65 | ); 66 | 67 | inline void trace_object( 68 | ObjectReference obj, 69 | AddressVector* work_queue, 70 | uint8_t mark_state, 71 | LineMark* line_mark_table, int line_mark_table_len, 72 | Address space_start, Address space_end 73 | ) __attribute__((always_inline)); 74 | inline void trace_object( 75 | ObjectReference obj, 76 | AddressVector* work_queue, 77 | uint8_t mark_state, 78 | LineMark* line_mark_table, int line_mark_table_len, 79 | Address space_start, Address space_end 80 | ) { 81 | mark_as_traced(immix_space->trace_map, obj, space_start, mark_state); 82 | 83 | if (obj >= space_start && obj < space_end) { 84 | mark_line_live(line_mark_table, line_mark_table_len, space_start, obj); 85 | } else { 86 | // freelist mark 87 | } 88 | 89 | Address base = obj; 90 | 91 | while (true) { 92 | uint8_t value = get_ref_byte(obj); 93 | uint8_t ref_bits = lower_bits(value, REF_BITS_LEN); 94 | bool short_encode = test_nth_bit(value, SHORT_ENCODE_BIT); 95 | 96 | switch (ref_bits) { 97 | case 0b00000001: 98 | process_edge(base, work_queue, mark_state); break; 99 | case 0b00000011: 100 | process_edge(base, work_queue, mark_state); 101 | process_edge(base + 8, work_queue, mark_state); break; 102 | case 0b00001111: 103 | process_edge(base, work_queue, mark_state); 104 | process_edge(base + 8, work_queue, mark_state); 105 | process_edge(base + 16, work_queue, mark_state); 106 | process_edge(base + 24, work_queue, mark_state); break; 107 | default: 108 | printf("unexpected ref_bits patterns"); exit(1); 109 | } 110 | 111 | if (short_encode) { 112 | return; 113 | } else { 114 | printf("should not use long encode"); exit(1); 115 | base = base + REF_BITS_LEN * 8; 116 | } 117 | } 118 | } 119 | 120 | #endif 121 | -------------------------------------------------------------------------------- /examples/immix/c_src/immix.h: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | #include 3 | #include 4 | #include 5 | 6 | #ifndef IMMIX_H 7 | #define IMMIX_H 8 | 9 | #define DEFAULT_HEAP_SIZE (500 << 20) 10 | 11 | typedef uint8_t LineMark; 12 | typedef uint8_t BlockMark; 13 | 14 | /* 15 | * ImmixBlock 16 | */ 17 | typedef struct ImmixBlock { 18 | struct ImmixBlock* next; 19 | struct ImmixBlock* prev; 20 | 21 | int id; 22 | BlockMark state; 23 | Address start; 24 | 25 | LineMark* line_mark_ptr; 26 | int line_mark_len; 27 | } ImmixBlock; 28 | 29 | /* 30 | * ImmixSpace 31 | */ 32 | typedef struct ImmixSpace { 33 | Address start; 34 | Address end; 35 | 36 | uint8_t* alloc_map; 37 | uint8_t* trace_map; 38 | int map_len; 39 | 40 | LineMark* line_mark_table; 41 | int line_mark_table_len; 42 | 43 | int total_blocks; 44 | 45 | ImmixBlock* usable_blocks; 46 | ImmixBlock* used_blocks; 47 | 48 | pthread_mutex_t lock; 49 | } ImmixSpace; 50 | 51 | #define IMMIX_SPACE_RATIO 0.8 52 | 53 | extern int IMMIX_SPACE_SIZE; 54 | extern ImmixSpace* immix_space; 55 | 56 | /* 57 | * Immix Mutator 58 | */ 59 | typedef struct ImmixMutatorLocal { 60 | uint64_t id; 61 | 62 | uint8_t* alloc_map; 63 | Address space_start; 64 | 65 | Address cursor; 66 | Address limit; 67 | uint64_t line; 68 | 69 | ImmixSpace* space; 70 | 71 | ImmixBlock* block; 72 | 73 | bool take_yield; 74 | } ImmixMutatorLocal; 75 | 76 | #define MAX_MUTATORS 1024 77 | 78 | extern pthread_mutex_t id_lock; 79 | extern int N_MUTTAORS; 80 | extern ImmixMutatorLocal* MUTATORS[MAX_MUTATORS]; 81 | 82 | void gc_init(); 83 | 84 | /* 85 | * Immix constants 86 | */ 87 | #define IMMIX_LOG_BYTES_IN_BLOCK 16 88 | #define IMMIX_BYTES_IN_BLOCK (1 << IMMIX_LOG_BYTES_IN_BLOCK) 89 | 90 | #define IMMIX_LOG_BYTES_IN_LINE 8 91 | #define IMMIX_BYTES_IN_LINE (1 << IMMIX_LOG_BYTES_IN_LINE) 92 | 93 | #define IMMIX_LINES_IN_BLOCK (1 << (IMMIX_LOG_BYTES_IN_BLOCK - IMMIX_LOG_BYTES_IN_LINE)) 94 | 95 | #define IMMIX_LINE_MARK_FREE 0 96 | #define IMMIX_LINE_MARK_LIVE 1 97 | #define IMMIX_LINE_MARK_FRESH_ALLOC 2 98 | #define IMMIX_LINE_MARK_CONSERV_LIVE 3 99 | #define IMMIX_LINE_MARK_PREV_LIVE 4 100 | 101 | #define IMMIX_BLOCK_MARK_USABLE 0 102 | #define IMMIX_BLOCK_MARK_FULL 1 103 | 104 | #define ALIGNMENT_VALUE 1 105 | 106 | inline Address align_up(Address region, int align) __attribute__((always_inline)); 107 | inline Address align_up(Address region, int align) { 108 | return (region + align - 1) & ~ (align - 1); 109 | } 110 | 111 | inline void fill_alignment_gap(Address start, Address end) __attribute__((always_inline)); 112 | inline void fill_alignment_gap(Address start, Address end) { 113 | if (end > start) 114 | memset((void*) start, ALIGNMENT_VALUE, end - start); 115 | } 116 | 117 | ImmixSpace* ImmixSpace_new(int space_size); 118 | ImmixMutatorLocal* ImmixMutatorLocal_new(ImmixSpace* space); 119 | 120 | Address ImmixMutatorLocal_try_alloc_from_local(ImmixMutatorLocal* mutator, int size, int align) __attribute__ ((noinline)); 121 | 122 | inline void init_object(ImmixMutatorLocal* mutator, Address addr, uint8_t encode) __attribute__((always_inline)); 123 | inline void init_object(ImmixMutatorLocal* mutator, Address addr, uint8_t encode){ 124 | uint8_t* map = mutator->alloc_map; 125 | map[(addr - mutator->space_start) >> LOG_POINTER_SIZE] = encode; 126 | } 127 | void init_object_no_inline(ImmixMutatorLocal* mutator, Address addr, uint8_t encode); 128 | 129 | inline Address ImmixMutatorLocal_alloc(ImmixMutatorLocal* mutator, int size, int align) __attribute__((always_inline)); 130 | inline Address ImmixMutatorLocal_alloc(ImmixMutatorLocal* mutator, int size, int align) { 131 | Address start = align_up(mutator->cursor, align); 132 | Address end = start + size; 133 | 134 | if (end > mutator->limit) { 135 | return ImmixMutatorLocal_try_alloc_from_local(mutator, size, align); 136 | } else { 137 | // fill_alignment_gap(mutator->cursor, start); 138 | mutator->cursor = end; 139 | 140 | return start; 141 | } 142 | } 143 | 144 | void ImmixSpace_print(ImmixSpace* space); 145 | 146 | int ImmixBlock_get_next_avaiable_line(ImmixBlock* block, int line); 147 | int ImmixBlock_get_next_unavailable_line(ImmixBlock* block, int line); 148 | ImmixBlock* ImmixSpace_get_next_usable_block(ImmixSpace* space); 149 | 150 | void yieldpoint_slowpath(ImmixMutatorLocal* mutator) __attribute__((__noinline__)); 151 | 152 | inline void yieldpoint(ImmixMutatorLocal* mutator) __attribute__((always_inline)); 153 | inline void yieldpoint(ImmixMutatorLocal* mutator) { 154 | if (mutator->take_yield) 155 | yieldpoint_slowpath(mutator); 156 | } 157 | 158 | inline void mark_line_live(LineMark* line_mark_table, int line_mark_table_len, Address start, Address addr) __attribute__((always_inline)); 159 | inline void mark_line_live(LineMark* line_mark_table, int line_mark_table_len, Address start, Address addr) { 160 | uintptr_t mark_table_index = (addr - start) >> IMMIX_LOG_BYTES_IN_LINE; 161 | line_mark_table[mark_table_index] = IMMIX_LINE_MARK_LIVE; 162 | if (mark_table_index < line_mark_table_len - 1) 163 | line_mark_table[mark_table_index + 1] = IMMIX_LINE_MARK_CONSERV_LIVE; 164 | } 165 | 166 | #endif 167 | -------------------------------------------------------------------------------- /examples/immix/c_src/immix_mutator.c: -------------------------------------------------------------------------------- 1 | #include "immix.h" 2 | #include "freelist.h" 3 | #include "common.h" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | pthread_mutex_t id_lock; 12 | int N_MUTATORS; 13 | ImmixMutatorLocal* MUTATORS[MAX_MUTATORS]; 14 | 15 | void gc_init() { 16 | immix_space = ImmixSpace_new(IMMIX_SPACE_SIZE); 17 | lo_space = FreeListSpace_new(LO_SPACE_SIZE); 18 | 19 | pthread_mutex_init(&id_lock, NULL); 20 | N_MUTATORS = 0; 21 | } 22 | 23 | void yieldpoint_slowpath(ImmixMutatorLocal* mutator) { 24 | printf("yieldpoint slowpath"); 25 | } 26 | 27 | void ImmixMutatorLocal_reset(ImmixMutatorLocal* mutator) { 28 | mutator->cursor = 0; 29 | mutator->limit = 0; 30 | mutator->line = IMMIX_LINES_IN_BLOCK; 31 | 32 | mutator->block = NULL; 33 | } 34 | 35 | ImmixMutatorLocal* ImmixMutatorLocal_new(ImmixSpace* space) { 36 | ImmixMutatorLocal* ret = (ImmixMutatorLocal*) malloc(sizeof(ImmixMutatorLocal)); 37 | 38 | pthread_mutex_lock(&id_lock); 39 | 40 | // set id 41 | ret->id = N_MUTATORS; 42 | // global array 43 | MUTATORS[N_MUTATORS] = ret; 44 | // increase id 45 | N_MUTATORS ++; 46 | 47 | // set other fields 48 | ret->space = space; 49 | ImmixMutatorLocal_reset(ret); 50 | 51 | // alloc map 52 | ret->alloc_map = space->alloc_map; 53 | ret->space_start = space->start; 54 | 55 | pthread_mutex_unlock(&id_lock); 56 | return ret; 57 | } 58 | 59 | Address ImmixMutatorLocal_alloc_from_global(ImmixMutatorLocal* mutator, int size, int align) { 60 | // we dont need to return block (as in Rust) 61 | 62 | while (true) { 63 | yieldpoint(mutator); 64 | 65 | ImmixBlock* new_block = ImmixSpace_get_next_usable_block(mutator->space); 66 | 67 | if (new_block != NULL) { 68 | mutator->block = new_block; 69 | mutator->cursor = new_block->start; 70 | mutator->limit = new_block->start; 71 | mutator->line = 0; 72 | 73 | return ImmixMutatorLocal_alloc(mutator, size, align); 74 | } else { 75 | continue; 76 | } 77 | } 78 | } 79 | 80 | Address ImmixMutatorLocal_try_alloc_from_local(ImmixMutatorLocal* mutator, int size, int align) { 81 | if (mutator->line < IMMIX_LINES_IN_BLOCK) { 82 | int next_available_line = ImmixBlock_get_next_avaiable_line(mutator->block, mutator->line); 83 | 84 | if (next_available_line != -1) { 85 | int end_line = ImmixBlock_get_next_unavailable_line(mutator->block, next_available_line); 86 | 87 | mutator->cursor = mutator->block->start + (next_available_line << IMMIX_LOG_BYTES_IN_LINE); 88 | mutator->limit = mutator->block->start + (end_line << IMMIX_LOG_BYTES_IN_LINE); 89 | mutator->line = end_line; 90 | 91 | memset((void*)mutator->cursor, 0, mutator->limit - mutator->cursor); 92 | 93 | int line = next_available_line; 94 | for (; line < end_line; line++) { 95 | mutator->block->line_mark_ptr[line] = IMMIX_LINE_MARK_FRESH_ALLOC; 96 | } 97 | 98 | return ImmixMutatorLocal_alloc(mutator, size, align); 99 | } else { 100 | return ImmixMutatorLocal_alloc_from_global(mutator, size, align); 101 | } 102 | } else { 103 | return ImmixMutatorLocal_alloc_from_global(mutator, size, align); 104 | } 105 | } 106 | 107 | void init_object_no_inline(ImmixMutatorLocal* mutator, Address addr, uint8_t encode) { 108 | mutator->alloc_map[(addr - mutator->space_start) >> LOG_POINTER_SIZE] = encode; 109 | } 110 | -------------------------------------------------------------------------------- /examples/immix/c_src/immix_space.c: -------------------------------------------------------------------------------- 1 | #include "immix.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | ImmixSpace* immix_space; 9 | 10 | int IMMIX_SPACE_SIZE = DEFAULT_HEAP_SIZE * IMMIX_SPACE_RATIO; 11 | 12 | void init_blocks(ImmixSpace* space) { 13 | int id = 0; 14 | Address cur = space->start; 15 | LineMark* curLineMark = space->line_mark_table; 16 | ImmixBlock* last = NULL; 17 | 18 | int totalBlocks = 0; 19 | 20 | for (; cur < space->end; cur += IMMIX_BYTES_IN_BLOCK, curLineMark = &(curLineMark[IMMIX_LINES_IN_BLOCK]), id += 1) { 21 | ImmixBlock* b = (ImmixBlock*) malloc(sizeof(ImmixBlock)); 22 | totalBlocks ++; 23 | 24 | if (last == NULL) { 25 | // first one 26 | space->usable_blocks = b; 27 | b->prev = NULL; 28 | } else { 29 | last->next = b; 30 | b->prev = last; 31 | } 32 | 33 | b->id = id; 34 | b->state = IMMIX_BLOCK_MARK_USABLE; 35 | b->start = cur; 36 | b->line_mark_ptr = curLineMark; 37 | b->line_mark_len = IMMIX_LINES_IN_BLOCK; 38 | 39 | last = b; 40 | } 41 | 42 | space->total_blocks = id; 43 | } 44 | 45 | ImmixSpace* ImmixSpace_new(int space_size) { 46 | // mmap 47 | void* mmap_ret = mmap(0, space_size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, -1, 0); 48 | if (mmap_ret == MAP_FAILED) { 49 | printf("failed to call mmap\n"); 50 | exit(1); 51 | } 52 | Address mmap_start = (Address) mmap_ret; 53 | 54 | // init immix space 55 | ImmixSpace* ret = (ImmixSpace*) malloc(sizeof(ImmixSpace)); 56 | 57 | ret->start = align_up(mmap_start, IMMIX_BYTES_IN_BLOCK); 58 | ret->end = ret->start + space_size; 59 | 60 | // init line mark table 61 | ret->line_mark_table_len = space_size / IMMIX_BYTES_IN_LINE; 62 | ret->line_mark_table = (LineMark*) malloc(sizeof(LineMark) * ret->line_mark_table_len); 63 | LineMark* cursor = ret->line_mark_table; 64 | for (int i = 0; i < ret->line_mark_table_len; i++) { 65 | *cursor = IMMIX_LINE_MARK_FREE; 66 | cursor += sizeof(LineMark); 67 | } 68 | 69 | // init alloc map and trace map 70 | ret->map_len = space_size >> LOG_POINTER_SIZE; 71 | int size = sizeof(uint8_t) * ret->map_len; 72 | ret->alloc_map = (uint8_t*) malloc(size); 73 | memset((void*)ret->alloc_map, 0, size); 74 | ret->trace_map = (uint8_t*) malloc(size); 75 | memset((void*)ret->trace_map, 0, size); 76 | 77 | printf("malloc alloc_map of size %d bytes\n", size); 78 | 79 | pthread_mutex_init(&(ret->lock), NULL); 80 | 81 | init_blocks(ret); 82 | 83 | return ret; 84 | } 85 | 86 | int ImmixBlock_get_next_avaiable_line(ImmixBlock* block, int line) { 87 | int i = line; 88 | while (i < block->line_mark_len) { 89 | if (block->line_mark_ptr[i] == IMMIX_LINE_MARK_FREE) { 90 | return i; 91 | } else { 92 | i++; 93 | } 94 | } 95 | return -1; 96 | } 97 | 98 | int ImmixBlock_get_next_unavailable_line(ImmixBlock* block, int line) { 99 | int i = line; 100 | while (i < block->line_mark_len) { 101 | if (block->line_mark_ptr[i] == IMMIX_LINE_MARK_FREE) { 102 | i++; 103 | } else { 104 | return i; 105 | } 106 | } 107 | return i; 108 | } 109 | 110 | ImmixBlock* ImmixSpace_get_next_usable_block(ImmixSpace* space) { 111 | // lock 112 | pthread_mutex_lock(&(space->lock)); 113 | 114 | // get a new block 115 | ImmixBlock* new_block = space->usable_blocks; 116 | 117 | if (new_block != NULL) { 118 | // success 119 | 120 | // remove from usable_blocks 121 | space->usable_blocks = new_block->next; 122 | if (new_block->next != NULL) 123 | space->usable_blocks->prev = NULL; 124 | 125 | // add to used block 126 | new_block->prev = NULL; 127 | new_block->next = space->used_blocks; 128 | if (space->used_blocks != NULL) 129 | space->used_blocks->prev = new_block; 130 | space->used_blocks = new_block; 131 | 132 | // unlock 133 | pthread_mutex_unlock(&(space->lock)); 134 | 135 | return new_block; 136 | } else { 137 | pthread_mutex_unlock(&(space->lock)); 138 | 139 | printf("Space exhausted\n"); 140 | printf("gc should happen here\n"); 141 | exit(1); 142 | 143 | return NULL; 144 | } 145 | } 146 | 147 | void ImmixSpace_print(ImmixSpace* space) { 148 | 149 | } 150 | -------------------------------------------------------------------------------- /examples/immix/c_src/objectmodel.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "objectmodel.h" 5 | 6 | uint8_t MARK_STATE = 1; 7 | 8 | //Header Header_new(uint8_t gcbits, uint8_t ref_bits, uint16_t size, uint32_t ty_id) { 9 | // Header header; 10 | // header.gcbits = gcbits; 11 | // header.ref_bits = ref_bits; 12 | // header.size = size; 13 | // header.ty_id = ty_id; 14 | // return header; 15 | //} 16 | 17 | const char *byte_to_binary(int x) { 18 | static char b[9]; 19 | b[0] = '\0'; 20 | 21 | int z; 22 | for (z = 128; z > 0; z >>= 1) 23 | { 24 | strcat(b, ((x & z) == z) ? "1" : "0"); 25 | } 26 | 27 | return b; 28 | } 29 | 30 | //void Header_print(Header* header) { 31 | // uint64_t header_u64 = Header_as_u64(*header); 32 | // printf("0x%" PRIx64 "(0b%s)\n", header_u64, byte_to_binary(header_u64)); 33 | // printf("gcbits: 0b%s\n", byte_to_binary((int) header->gcbits)); 34 | // printf("refbits: 0b%s\n", byte_to_binary((int) header->ref_bits)); 35 | //} 36 | 37 | //void __attribute__ ((noinline)) mark_as_traced(ObjectReference obj) { 38 | // set_gcbits_as(obj, MARK_BIT_INDEX, MARK_STATE); 39 | //} 40 | -------------------------------------------------------------------------------- /examples/immix/c_src/objectmodel.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "common.h" 5 | #include "immix.h" 6 | 7 | #ifndef OBJECTMODEL_H 8 | #define OBJECTMODEL_H 9 | 10 | #define HEADER_SIZE 0 11 | 12 | //#define SCALAR_INLINE 0 13 | //#define SCALAR_ID 1 14 | //#define ARRAY_INLINE 2 15 | //#define ARRAY_ID 3 16 | // 17 | //#define MARK_BIT_INDEX 0 18 | extern uint8_t MARK_STATE; 19 | 20 | //typedef struct Header { 21 | // uint8_t gcbits; 22 | // uint8_t ref_bits; 23 | // uint16_t size; 24 | // uint32_t ty_id; 25 | //} Header; 26 | // 27 | //typedef struct Type { 28 | // int size; 29 | // 30 | // int* ref_offsets; 31 | // int ref_offsets_len; 32 | //} Type; 33 | // 34 | //extern Type* types; 35 | // 36 | //Header Header_new(uint8_t gcbits, uint8_t ref_bits, uint16_t size, uint32_t ty_id); 37 | //void Header_print(Header* header); 38 | // 39 | //inline uint64_t Header_as_u64(Header hdr) __attribute__((always_inline)); 40 | //inline uint64_t Header_as_u64(Header hdr) { 41 | // return *((uint64_t*)&hdr); 42 | //} 43 | 44 | //inline void* as_object(Address addr) __attribute__((always_inline)); 45 | //inline void* as_object(Address addr) { 46 | // return (void*) (addr + HEADER_SIZE); 47 | //} 48 | // 49 | //inline void set_gcbits_as(ObjectReference obj, int index, uint8_t value) __attribute__((always_inline)); 50 | //inline void set_gcbits_as(ObjectReference obj, int index, uint8_t value) { 51 | // Header* hdr = ((Header*)obj); 52 | // hdr->gcbits = hdr->gcbits ^ (( -value ^ hdr->gcbits) & (1 << index)); 53 | //} 54 | // 55 | //inline bool test_gcbits(ObjectReference obj, int index, uint8_t value) __attribute__((always_inline)); 56 | //inline bool test_gcbits(ObjectReference obj, int index, uint8_t value) { 57 | // return (((Header*)obj)->gcbits & value) == value; 58 | //} 59 | 60 | #define REF_BITS_LEN 6 61 | #define OBJ_START_BIT 6 62 | #define SHORT_ENCODE_BIT 7 63 | 64 | inline bool is_traced(ObjectReference obj, uint8_t mark_state) __attribute__((always_inline)); 65 | inline bool is_traced(ObjectReference obj, uint8_t mark_state) { 66 | return immix_space->trace_map[(obj - immix_space->start) >> LOG_POINTER_SIZE] == mark_state; 67 | } 68 | 69 | inline void mark_as_traced(uint8_t* trace_map, ObjectReference obj, Address space_start, uint8_t mark_state) __attribute__((always_inline)); 70 | inline void mark_as_traced(uint8_t* trace_map, ObjectReference obj, Address space_start, uint8_t mark_state) { 71 | trace_map[(obj - space_start) >> LOG_POINTER_SIZE] = mark_state; 72 | } 73 | //void __attribute__ ((noinline)) mark_as_traced(ObjectReference obj); 74 | 75 | inline uint8_t get_ref_byte(ObjectReference obj) __attribute__((always_inline)); 76 | inline uint8_t get_ref_byte(ObjectReference obj) { 77 | return immix_space->alloc_map[(obj - immix_space->start) >> LOG_POINTER_SIZE]; 78 | } 79 | 80 | #endif 81 | -------------------------------------------------------------------------------- /examples/immix/dot/.gitignore: -------------------------------------------------------------------------------- 1 | /*.png 2 | -------------------------------------------------------------------------------- /examples/immix/features: -------------------------------------------------------------------------------- 1 | exhaust gcbench mark 2 | -------------------------------------------------------------------------------- /examples/immix/features.all: -------------------------------------------------------------------------------- 1 | exhaust initobj gcbench mt-gcbench mark trace mt-trace 2 | -------------------------------------------------------------------------------- /examples/immix/run-mt-trace.py.example: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import sys 3 | import subprocess 4 | 5 | def error(msg): 6 | print 'Error: ' + msg 7 | sys.exit 8 | 9 | def monitor_invoke(command): 10 | print command 11 | run = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 12 | while True: 13 | out = run.stdout.read(1) 14 | if out == '' and run.poll() != None: 15 | break; 16 | if out != '': 17 | sys.stdout.write(out) 18 | sys.stdout.flush() 19 | return True 20 | 21 | REMOTE_USER = 'yilin' 22 | RSYNC_DIR = '/home/{}/rust_gc/immix_rust_new/'.format(REMOTE_USER) 23 | MACHINE = 'rat.moma' 24 | 25 | #./run-mt-trace.py 1 8 26 | 27 | if len(sys.argv) != 5: 28 | error("usage: ./run-mt-trace.py 2000M 20 1 8") 29 | 30 | heap = sys.argv[1] 31 | invocations = int(sys.argv[2]) 32 | gc_threads_min = int(sys.argv[3]) 33 | gc_threads_max = int(sys.argv[4]) 34 | 35 | print '###heap = ' + heap 36 | print '###invocations = ' + str(invocations) 37 | print '###gc_threads_min = ' + str(gc_threads_min) 38 | print '###gc_threads_max = ' + str(gc_threads_max) 39 | 40 | print 'rsync...' 41 | remote_path = "{}@{}:{}".format(REMOTE_USER, MACHINE, RSYNC_DIR) 42 | ret = subprocess.call(["rsync", "-rav", "-I", "--delete", "--exclude=target", "--exclude=.hg", ".", remote_path]) 43 | if ret != 0: 44 | error('failed to rsync') 45 | 46 | build = 'ssh -t {}@{} "cd {}; rm -r target; cargo build --release --features \"mt-trace\""'.format(REMOTE_USER, MACHINE, RSYNC_DIR) 47 | succ = monitor_invoke(build) 48 | if not succ: 49 | error('failed to build mt-trace') 50 | print 51 | 52 | for n_gc_threads in range(gc_threads_min, gc_threads_max + 1): 53 | print 'Running with %d gc threads' % (n_gc_threads) 54 | print 55 | 56 | for invocation in range(0, invocations): 57 | print 'Invocation %d' % (invocation) 58 | build = 'ssh -t {}@{} "cd {}; HEAP_SIZE={} N_GCTHREADS={} ./target/release/immix-rust"'.format(REMOTE_USER, MACHINE, RSYNC_DIR, heap, n_gc_threads) 59 | succ = monitor_invoke(build) 60 | if not succ: 61 | error('failed to execute rust code') 62 | print 63 | print 'Invocation Finish' 64 | 65 | print 'Finish for current thread number' 66 | print 67 | -------------------------------------------------------------------------------- /examples/immix/run.py.example: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import sys 3 | import subprocess 4 | 5 | def error(msg): 6 | print 'Error: ' + msg 7 | sys.exit 8 | 9 | def monitor_invoke(command): 10 | print command 11 | run = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 12 | while True: 13 | out = run.stdout.read(1) 14 | if out == '' and run.poll() != None: 15 | break; 16 | if out != '': 17 | sys.stdout.write(out) 18 | sys.stdout.flush() 19 | return True 20 | 21 | REMOTE_USER = 'yilin' 22 | RSYNC_DIR = '/home/{}/rust_gc/immix_rust_new/'.format(REMOTE_USER) 23 | MACHINE = 'rat.moma' 24 | 25 | C_FLAGS = '-O3 -std=gnu99' 26 | RUSTC_FLAGS = '--' 27 | 28 | if len(sys.argv) < 4: 29 | error("e.g. ./run.py exhaust 500M 5"); 30 | 31 | feature = sys.argv[1] 32 | all_features = ["exhaust", "initobj", "gcbench", "mark", "trace", "mt-gcbench"] 33 | if feature not in all_features : 34 | error("unsupported feature") 35 | 36 | heap = sys.argv[2] 37 | invocations = sys.argv[3] 38 | 39 | bm_args = '' 40 | if len(sys.argv) > 4 and sys.argv[4] == '--': 41 | bm_args = ' '.join(sys.argv[5:]) 42 | 43 | print 'benchmark specific args: ' + bm_args 44 | 45 | print '###feature = ' + feature 46 | print '###heap = ' + heap 47 | print '###invocations = ' + invocations 48 | 49 | print 'rsync...' 50 | remote_path = "{}@{}:{}".format(REMOTE_USER, MACHINE, RSYNC_DIR) 51 | ret = subprocess.call(["rsync", "-rav", "-I", "--delete", "--exclude=target", "--exclude=.hg", ".", remote_path]) 52 | if ret != 0: 53 | error('failed to rsync') 54 | 55 | for invocation in range(0, int(invocations)): 56 | print 'Invocation %d' % (invocation) 57 | print 58 | 59 | print '===Rust Version===' 60 | build = '' 61 | if feature == 'gcbench' or feature == 'mt-gcbench': 62 | build = 'ssh -t {}@{} "cd {}; rm -r target; cargo rustc --release --features \"{}\" {}; HEAP_SIZE={} N_GCTHREADS=7 ./target/release/immix-rust 7"'.format(REMOTE_USER, MACHINE, RSYNC_DIR, feature, RUSTC_FLAGS, heap, bm_args) 63 | else: 64 | build = 'ssh -t {}@{} "cd {}; rm -r target; cargo rustc --release --features \"{}\" {}; HEAP_SIZE={} N_GCTHREADS=0 ./target/release/immix-rust"'.format(REMOTE_USER, MACHINE, RSYNC_DIR, feature, RUSTC_FLAGS, heap, bm_args) 65 | succ = monitor_invoke(build) 66 | if not succ: 67 | error('failed to execute rust code') 68 | print 69 | 70 | print '===C Version===' 71 | build = 'ssh -t {}@{} "cd {}c_src; clang-3.7 {} *.c ../src/common/perf.c -lpfm -o bench -D{}; HEAP_SIZE={} ./bench {}"'.format(REMOTE_USER, MACHINE, RSYNC_DIR, C_FLAGS, feature, heap, bm_args) 72 | succ = monitor_invoke(build) 73 | if not succ: 74 | error('failed to execute c code') 75 | print 76 | 77 | print '===Boehm GC===' 78 | if feature == "exhaust": 79 | build = 'ssh -t {}@{} "cd {}../bdwgc/gctests/; clang-3.7 -I ../include exhaust.c ../*.o {} -DGC -DTHREAD_LOCAL_ALLOC -DPARALLEL_MARK -o exhaust -l pthread; GC_INITIAL_HEAP_SIZE={} GC_MAXIMUM_HEAP_SIZE={} ./exhaust"'.format(REMOTE_USER, MACHINE, RSYNC_DIR, C_FLAGS, heap, heap) 80 | elif feature == "gcbench": 81 | build = 'ssh -t {}@{} "cd {}../bdwgc/gctests/; clang-3.7 -I ../include gcbench.c ../*.o {} -DGC -DTHREAD_LOCAL_ALLOC -DPARALLEL_MARK -o gcbench -l pthread; GC_INITIAL_HEAP_SIZE={} GC_MAXIMUM_HEAP_SIZE={} ./gcbench"'.format(REMOTE_USER, MACHINE, RSYNC_DIR, C_FLAGS, heap, heap) 82 | elif feature == "mt-gcbench": 83 | build = 'ssh -t {}@{} "cd {}../bdwgc/gctests/; clang-3.7 -I ../include mt-gcbench.c ../*.o {} -DGC -DTHREAD_LOCAL_ALLOC -DPARALLEL_MARK -DNTHREADS=7 -o mt-gcbench -l pthread; GC_INITIAL_HEAP_SIZE={} GC_MAXIMUM_HEAP_SIZE={} ./mt-gcbench"'.format(REMOTE_USER, MACHINE, RSYNC_DIR, C_FLAGS, heap, heap, bm_args) 84 | else: 85 | build = '' 86 | print '"{}" not applicable to bdwgc'.format(feature); 87 | if build != '': 88 | succ = monitor_invoke(build) 89 | if not succ: 90 | error('failed to execute boehm gc code') 91 | print 92 | 93 | print 'Invocation Finish' 94 | -------------------------------------------------------------------------------- /examples/immix/runme: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | for f in `cat features`; do 4 | echo $f 5 | cargo run --release --features "$f" 6 | done 7 | 8 | -------------------------------------------------------------------------------- /examples/immix/runone: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Add the path of the executable built by stack to PATH instead of installing it: 4 | export PATH="$(pwd)/$(dirname $(find ../../.stack-work -path */flp/flp)):$PATH" 5 | cargo run --release --features "$@" 6 | 7 | -------------------------------------------------------------------------------- /examples/immix/rust_c_interface/immix_rust.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | // the layout of this struct needs to be the same as src/heap/immix/immix_mutator.rs 6 | struct Mutator { 7 | uint64_t id; 8 | void* alloc_map; 9 | uint64_t space_start; 10 | uint64_t cursor; 11 | uint64_t limit; 12 | uint64_t line; 13 | 14 | bool* yield; 15 | // we do not care about the rest 16 | }; 17 | 18 | extern void gc_init(uint64_t immix_size, uint64_t lo_size, uint64_t n_gcthreads); 19 | extern void set_low_water_mark(); 20 | extern struct Mutator* new_mutator(); 21 | extern void drop_mutator(struct Mutator* mutator); 22 | extern void yieldpoint_slow(struct Mutator** mutator); 23 | extern uint64_t alloc_slow(struct Mutator** mutator, uint64_t size, uint64_t align); 24 | extern uint64_t alloc_large(struct Mutator** mutator, uint64_t size); 25 | 26 | inline void yieldpoint(bool* take_yield, struct Mutator** m) __attribute__((always_inline)); 27 | inline void yieldpoint(bool* take_yield, struct Mutator** m) { 28 | if (*take_yield) { 29 | yieldpoint_slow(m); 30 | } 31 | } 32 | 33 | inline uint64_t align_up(uint64_t addr, uint64_t align) __attribute__((always_inline)); 34 | inline uint64_t align_up(uint64_t addr, uint64_t align) { 35 | return (addr + align - 1) & ~(align - 1); 36 | } 37 | 38 | inline uint64_t alloc(struct Mutator** mutator, uint64_t size, uint64_t align) __attribute__((always_inline)); 39 | inline uint64_t alloc(struct Mutator** mutator, uint64_t size, uint64_t align) { 40 | struct Mutator* self = *mutator; 41 | uint64_t start = align_up(self->cursor, align); 42 | uint64_t end = start + size; 43 | 44 | if (end > self->limit) 45 | return alloc_slow(mutator, size, align); 46 | else { 47 | self->cursor = end; 48 | return start; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /examples/immix/rust_c_interface/test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "immix_rust.h" 5 | 6 | #define N_ALLOCATION_THREADS 5 7 | 8 | void* new_allocation_thread(void*); 9 | void allocation_once(struct Mutator* m); 10 | void allocation_loop(struct Mutator* m); 11 | 12 | int main() { 13 | // init gc with size of immix space and large object space, and number of gc threads 14 | gc_init(1 << 20, 1 << 20, 8); 15 | 16 | pthread_t threads[N_ALLOCATION_THREADS]; 17 | int i; 18 | 19 | for (i = 0; i < N_ALLOCATION_THREADS; i++) { 20 | pthread_create(&threads[i], NULL, new_allocation_thread, NULL); 21 | } 22 | 23 | for (i = 0; i < N_ALLOCATION_THREADS; i++) { 24 | pthread_join(threads[i], NULL); 25 | } 26 | } 27 | 28 | void* new_allocation_thread(void* arg) { 29 | // creating a new mutator 30 | struct Mutator* m = new_mutator(); 31 | 32 | // set current stack pointer as a limit for conservative stack scanning 33 | // (stack scanning won't traverse beyond the limit) 34 | set_low_water_mark(); 35 | 36 | // main allocation loop 37 | allocation_loop(m); 38 | 39 | // if the allocation finishes, we destroy the mutator 40 | drop_mutator(m); 41 | 42 | return NULL; 43 | } 44 | 45 | void print_mutator(struct Mutator* m) { 46 | printf("mutator:\n"); 47 | printf("id =%lld\n", m->id); 48 | printf("space =0x%llx\n", m->space_start); 49 | printf("cursor=0x%llx\n", m->cursor); 50 | printf("limit =0x%llx\n", m->limit); 51 | printf("yield?=%d\n", *(m->yield)); 52 | } 53 | 54 | void allocation_once(struct Mutator* m) { 55 | print_mutator(m); 56 | uint64_t addr = alloc(&m, 32, 8); 57 | printf("RETURN1 = %llx\n", addr); 58 | print_mutator(m); 59 | addr = alloc(&m, 32, 8); 60 | printf("RETURN2 = %llx\n", addr); 61 | print_mutator(m); 62 | } 63 | 64 | void allocation_loop(struct Mutator* m) { 65 | bool* yield = m->yield; 66 | struct Mutator** m_ref = &m; 67 | 68 | while(1) { 69 | // yieldpoint 70 | yieldpoint(yield, m_ref); 71 | 72 | uint64_t addr = alloc(&m, 32, 8); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /examples/immix/src/exhaust.rs: -------------------------------------------------------------------------------- 1 | extern crate libc; 2 | extern crate time; 3 | 4 | use heap; 5 | use heap::immix::ImmixMutatorLocal; 6 | use heap::immix::ImmixSpace; 7 | use heap::freelist::FreeListSpace; 8 | 9 | use std::sync::RwLock; 10 | 11 | pub const OBJECT_SIZE : usize = 24; 12 | pub const OBJECT_ALIGN: usize = 8; 13 | 14 | pub const ALLOCATION_TIMES : usize = 50000000; 15 | 16 | #[allow(unused_variables)] 17 | pub fn exhaust_alloc() { 18 | use std::sync::{Arc}; 19 | use std::sync::atomic::Ordering; 20 | 21 | let shared_space : Arc = { 22 | let space : ImmixSpace = ImmixSpace::new(heap::IMMIX_SPACE_SIZE.load(Ordering::SeqCst)); 23 | 24 | Arc::new(space) 25 | }; 26 | let lo_space : Arc> = { 27 | let space : FreeListSpace = FreeListSpace::new(heap::LO_SPACE_SIZE.load(Ordering::SeqCst)); 28 | Arc::new(RwLock::new(space)) 29 | }; 30 | heap::gc::init(shared_space.clone(), lo_space.clone()); 31 | 32 | let mut mutator = ImmixMutatorLocal::new(shared_space.clone()); 33 | 34 | println!("Trying to allocate {} objects of (size {}, align {}). ", ALLOCATION_TIMES, OBJECT_SIZE, OBJECT_ALIGN); 35 | const ACTUAL_OBJECT_SIZE : usize = OBJECT_SIZE; 36 | println!("Considering header size of {}, an object should be {}. ", 0, ACTUAL_OBJECT_SIZE); 37 | println!("This would take {} bytes of {} bytes heap", ALLOCATION_TIMES * ACTUAL_OBJECT_SIZE, heap::IMMIX_SPACE_SIZE.load(Ordering::SeqCst)); 38 | 39 | alloc_loop(&mut mutator); 40 | } 41 | 42 | #[inline(never)] 43 | fn alloc_loop(mutator: &mut ImmixMutatorLocal) { 44 | let t_start = time::Instant::now(); 45 | 46 | for _ in 0..ALLOCATION_TIMES { 47 | // mutator.yieldpoint(); 48 | 49 | let res = mutator.alloc(OBJECT_SIZE, OBJECT_ALIGN); 50 | mutator.init_object(res, 0b000011_11); 51 | } 52 | 53 | let t_end = time::Instant::now(); 54 | 55 | println!("time used: {} msec", (t_end - t_start).whole_milliseconds()); 56 | } 57 | -------------------------------------------------------------------------------- /examples/immix/src/gcbench.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_upper_case_globals)] 2 | #![allow(non_camel_case_types)] 3 | #![allow(non_snake_case)] 4 | 5 | #![allow(unused_variables)] 6 | 7 | use heap; 8 | use heap::layout::*; 9 | use heap::immix::ImmixMutatorLocal; 10 | use heap::immix::ImmixSpace; 11 | use heap::freelist; 12 | use heap::freelist::FreeListSpace; 13 | use std::mem::size_of as size_of; 14 | 15 | extern crate time; 16 | 17 | const kStretchTreeDepth : i32 = 18; 18 | const kLongLivedTreeDepth : i32 = 16; 19 | const kArraySize : i32 = 5000; 20 | const kMinTreeDepth : i32 = 4; 21 | const kMaxTreeDepth : i32 = 16; 22 | 23 | struct Node { 24 | left : *mut Node, 25 | right : *mut Node, 26 | i : i32, 27 | j : i32 28 | } 29 | 30 | struct Array { 31 | value : [f64; kArraySize as usize] 32 | } 33 | 34 | fn init_Node(me: *mut Node, l: *mut Node, r: *mut Node) { 35 | unsafe { 36 | (*me).left = l; 37 | (*me).right = r; 38 | } 39 | } 40 | 41 | fn TreeSize(i: i32) -> i32{ 42 | (1 << (i + 1)) - 1 43 | } 44 | 45 | fn NumIters(i: i32) -> i32 { 46 | 2 * TreeSize(kStretchTreeDepth) / TreeSize(i) 47 | } 48 | 49 | fn Populate(iDepth: i32, thisNode: *mut Node, mutator: &mut ImmixMutatorLocal) { 50 | if iDepth <= 0 { 51 | return; 52 | } else { 53 | unsafe { 54 | (*thisNode).left = alloc(mutator); 55 | (*thisNode).right = alloc(mutator); 56 | Populate(iDepth - 1, (*thisNode).left, mutator); 57 | Populate(iDepth - 1, (*thisNode).right, mutator); 58 | } 59 | } 60 | } 61 | 62 | fn MakeTree(iDepth: i32, mutator: &mut ImmixMutatorLocal) -> *mut Node { 63 | if iDepth <= 0 { 64 | alloc(mutator) 65 | } else { 66 | let left = MakeTree(iDepth - 1, mutator); 67 | let right = MakeTree(iDepth - 1, mutator); 68 | let result = alloc(mutator); 69 | init_Node(result, left, right); 70 | 71 | result 72 | } 73 | } 74 | 75 | fn PrintDiagnostics() { 76 | 77 | } 78 | 79 | fn TimeConstruction(depth: i32, mutator: &mut ImmixMutatorLocal) { 80 | let iNumIters = NumIters(depth); 81 | println!("creating {} trees of depth {}", iNumIters, depth); 82 | 83 | let tStart = time::Instant::now(); 84 | for _ in 0..iNumIters { 85 | let tempTree = alloc(mutator); 86 | Populate(depth, tempTree, mutator); 87 | 88 | // destroy tempTree 89 | } 90 | let tFinish = time::Instant::now(); 91 | println!("\tTop down construction took {} msec", (tFinish - tStart).whole_milliseconds()); 92 | 93 | let tStart = time::Instant::now(); 94 | for _ in 0..iNumIters { 95 | let tempTree = MakeTree(depth, mutator); 96 | } 97 | let tFinish = time::Instant::now(); 98 | println!("\tBottom up construction took {} msec", (tFinish - tStart).whole_milliseconds()); 99 | } 100 | 101 | #[inline(always)] 102 | fn alloc(mutator: &mut ImmixMutatorLocal) -> *mut Node { 103 | let addr = mutator.alloc(size_of::(), 8); 104 | mutator.init_object(addr, 0b000011_11); 105 | // objectmodel::init_header(addr, HEADER_INIT_U64); 106 | addr.to_ptr_mut::() 107 | } 108 | 109 | pub fn start() { 110 | use std::sync::{Arc, RwLock}; 111 | use std::sync::atomic::Ordering; 112 | 113 | unsafe {heap::gc::set_low_water_mark();} 114 | 115 | let immix_space : Arc = { 116 | let space : ImmixSpace = ImmixSpace::new(heap::IMMIX_SPACE_SIZE.load(Ordering::SeqCst)); 117 | Arc::new(space) 118 | }; 119 | let lo_space : Arc> = { 120 | let space : FreeListSpace = FreeListSpace::new(heap::LO_SPACE_SIZE.load(Ordering::SeqCst)); 121 | Arc::new(RwLock::new(space)) 122 | }; 123 | heap::gc::init(immix_space.clone(), lo_space.clone()); 124 | let mut mutator = ImmixMutatorLocal::new(immix_space.clone()); 125 | 126 | println!("Garbage Collector Test"); 127 | println!(" Live storage will peak at {} bytes.\n", 128 | 2 * (size_of::() as i32) * TreeSize(kLongLivedTreeDepth) + 129 | (size_of::() as i32)); 130 | 131 | println!(" Stretching memory with a binary tree or depth {}", kStretchTreeDepth); 132 | PrintDiagnostics(); 133 | 134 | let tStart = time::Instant::now(); 135 | // Stretch the memory space quickly 136 | let tempTree = MakeTree(kStretchTreeDepth, &mut mutator); 137 | // destroy tree 138 | 139 | // Create a long lived object 140 | println!(" Creating a long-lived binary tree of depth {}", kLongLivedTreeDepth); 141 | let longLivedTree = alloc(&mut mutator); 142 | Populate(kLongLivedTreeDepth, longLivedTree, &mut mutator); 143 | 144 | println!(" Creating a long-lived array of {} doubles", kArraySize); 145 | freelist::alloc_large(size_of::(), 8, &mut mutator, lo_space.clone()); 146 | 147 | PrintDiagnostics(); 148 | 149 | let mut d = kMinTreeDepth; 150 | while d <= kMaxTreeDepth { 151 | TimeConstruction(d, &mut mutator); 152 | d += 2; 153 | } 154 | 155 | if longLivedTree.is_null() { 156 | println!("Failed(long lived tree wrong)"); 157 | } 158 | 159 | // if array.array[1000] != 1.0f64 / (1000 as f64) { 160 | // println!("Failed(array element wrong)"); 161 | // } 162 | 163 | let tFinish = time::Instant::now(); 164 | let tElapsed = (tFinish - tStart).whole_milliseconds(); 165 | 166 | PrintDiagnostics(); 167 | println!("Completed in {} msec", tElapsed); 168 | println!("Finished with {} collections", heap::gc::GC_COUNT.load(Ordering::SeqCst)); 169 | } 170 | -------------------------------------------------------------------------------- /examples/immix/src/heap/.gitignore: -------------------------------------------------------------------------------- 1 | /layout.rs 2 | -------------------------------------------------------------------------------- /examples/immix/src/heap/debug.rs: -------------------------------------------------------------------------------- 1 | extern crate backtrace; 2 | 3 | macro_rules! backtraceHere { 4 | ( $val:expr ) => { 5 | { use std::fmt::Write; 6 | let bt = &backtrace::Backtrace::new(); 7 | let frames_count = bt.frames().len(); 8 | let frames = &bt.frames(); 9 | for i in 2..frames_count { 10 | let symb = &frames[i].symbols()[0]; 11 | if let Some(name) = symb.name() { 12 | if let Some(ln) = symb.lineno() { 13 | let mut output = String::new(); 14 | let _ = write!(&mut output, "{}", name); 15 | if output.starts_with("std::rt") { break; } 16 | eprintln!(" {}:{}] = 0x{:X}", name, ln, $val); 17 | } 18 | } 19 | } 20 | } 21 | }; 22 | } 23 | 24 | 25 | -------------------------------------------------------------------------------- /examples/immix/src/heap/freelist/mod.rs: -------------------------------------------------------------------------------- 1 | use heap::layout::*; 2 | use heap::immix; 3 | 4 | extern crate aligned_alloc; 5 | 6 | use std::collections::LinkedList; 7 | use std::sync::Arc; 8 | use std::sync::RwLock; 9 | 10 | pub struct FreeListSpace { 11 | current_nodes : LinkedList>, 12 | 13 | node_id: usize, 14 | 15 | size : usize, 16 | used_bytes : usize 17 | } 18 | 19 | impl FreeListSpace { 20 | pub fn new(size: usize) -> FreeListSpace { 21 | FreeListSpace { 22 | current_nodes: LinkedList::new(), 23 | node_id: 0, 24 | size: size, 25 | used_bytes: 0 26 | } 27 | } 28 | 29 | pub fn mark(&mut self, _obj: CellAddr) { 30 | 31 | } 32 | 33 | pub fn alloc(&mut self, size: usize, align: usize) -> Option { 34 | if self.used_bytes + size > self.size { 35 | None 36 | } else { 37 | let ret = self::aligned_alloc::aligned_alloc(BYTES_IN_WORD + size, align); 38 | 39 | let addr = CellAddr::from_ptr::<()>(ret); 40 | 41 | self.current_nodes.push_front(Box::new(FreeListNode{id: self.node_id, start: addr, size: size, mark: NodeMark::FreshAlloc})); 42 | self.node_id += 1; 43 | self.used_bytes += size; 44 | 45 | addr.cell_size().store_word(size); 46 | Some(addr.object()) 47 | } 48 | } 49 | 50 | pub fn sweep(&mut self) { 51 | let (new_nodes, new_used_bytes) = { 52 | let mut ret = LinkedList::new(); 53 | let nodes = &mut self.current_nodes; 54 | let mut used_bytes = 0; 55 | 56 | while !nodes.is_empty() { 57 | let mut node = nodes.pop_front().unwrap(); 58 | match node.mark { 59 | NodeMark::Live => { 60 | //let refBitsA : RefBitsAddr = 61 | // Word2RefBits::map_getAddr( 62 | // self.start.get_first_word(), 63 | // self.alloc_map.toStart, 64 | // WordAddr::from_usize(line_addr.as_usize() + j)); 65 | //check_expect_pc!(refBitsA.as_usize(), 66 | 67 | node.set_mark(NodeMark::PrevLive); 68 | used_bytes += node.size; 69 | ret.push_back(node); 70 | }, 71 | NodeMark::PrevLive | NodeMark::FreshAlloc => { 72 | let ptr = node.start.to_ptr::<()>() as *mut (); 73 | // free the memory 74 | unsafe {self::aligned_alloc::aligned_free(ptr);} 75 | // do not add this node into new linked list 76 | } 77 | } 78 | } 79 | 80 | (ret, used_bytes) 81 | }; 82 | 83 | self.current_nodes = new_nodes; 84 | self.used_bytes = new_used_bytes; 85 | } 86 | 87 | pub fn current_nodes(&self) -> &LinkedList> { 88 | &self.current_nodes 89 | } 90 | pub fn current_nodes_mut(&mut self) -> &mut LinkedList> { 91 | &mut self.current_nodes 92 | } 93 | } 94 | 95 | pub struct FreeListNode { 96 | id: usize, 97 | start : CellAddr, 98 | size : usize, 99 | mark : NodeMark 100 | } 101 | 102 | impl FreeListNode { 103 | pub fn set_mark(&mut self, mark: NodeMark) { 104 | self.mark = mark; 105 | } 106 | } 107 | 108 | #[derive(PartialEq, Eq, Copy, Clone, Debug)] 109 | pub enum NodeMark { 110 | FreshAlloc, 111 | PrevLive, 112 | Live, 113 | } 114 | unsafe impl Sync for NodeMark {} 115 | 116 | #[inline(never)] 117 | pub fn alloc_large(size: usize, align: usize, mutator: &mut immix::ImmixMutatorLocal, space: Arc>) -> ObjectAddr { 118 | loop { 119 | mutator.yieldpoint(); 120 | 121 | let ret_addr = { 122 | let mut lo_space_lock = space.write().unwrap(); 123 | lo_space_lock.alloc(size, align) 124 | }; 125 | 126 | match ret_addr { 127 | Some(addr) => { 128 | return addr; 129 | }, 130 | None => { 131 | use heap::gc; 132 | gc::trigger_gc(); 133 | } 134 | } 135 | } 136 | } 137 | 138 | use std::fmt; 139 | 140 | impl fmt::Display for FreeListSpace { 141 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 142 | write!(f, "FreeListSpace\n").unwrap(); 143 | write!(f, "{} used, {} total\n", self.used_bytes, self.size).unwrap(); 144 | write!(f, "nodes:\n").unwrap(); 145 | 146 | for node in self.current_nodes() { 147 | write!(f, " {}\n", node).unwrap(); 148 | } 149 | 150 | write!(f, "done\n") 151 | } 152 | } 153 | 154 | impl fmt::Display for FreeListNode { 155 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 156 | write!(f, "FreeListNode#{}(start={:#X}, size={}, state={:?})", self.id, self.start, self.size, self.mark) 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /examples/immix/src/heap/gc/clib_x64.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | void* malloc_zero(size_t size) { 6 | void* ret = malloc(size); 7 | memset(ret, 0, size); 8 | return ret; 9 | } 10 | 11 | 12 | 13 | uintptr_t immmix_get_stack_ptr(); 14 | uintptr_t immmix_get_stack_ptr() { 15 | uintptr_t rsp; 16 | // get current rsp, rbp (this C func frame) 17 | __asm__( 18 | "mov %%rsp, %0 \n" 19 | : "=rm" (rsp) 20 | ); 21 | 22 | return rsp; 23 | } 24 | 25 | int get_registers_count() { 26 | return 16; 27 | } 28 | 29 | uintptr_t* get_registers () { 30 | uintptr_t rax, rbx, rcx, rdx, rbp, rsp, rsi, rdi, r8, r9, r10, r11, r12, r13, r14, r15; 31 | 32 | __asm__( 33 | "mov %%rax, %0 \n" 34 | "mov %%rbx, %1 \n" 35 | "mov %%rcx, %2 \n" 36 | "mov %%rdx, %3 \n" 37 | "mov %%rbp, %4 \n" 38 | "mov %%rsp, %5 \n" 39 | "mov %%rsi, %5 \n" 40 | "mov %%rdi, %6 \n" 41 | "mov %%r8, %7 \n" 42 | "mov %%r9, %8 \n" 43 | "mov %%r10, %10\n" 44 | "mov %%r11, %11\n" 45 | "mov %%r12, %12\n" 46 | "mov %%r13, %13\n" 47 | "mov %%r14, %14\n" 48 | "mov %%r15, %15\n" 49 | : "=m" (rax), 50 | "=m" (rbx), 51 | "=m" (rcx), 52 | "=m" (rdx), 53 | "=m" (rbp), 54 | "=m" (rsp), 55 | "=m" (rsi), 56 | "=m" (rdi), 57 | "=m" (r8), 58 | "=m" (r9), 59 | "=m" (r10), 60 | "=m" (r11), 61 | "=m" (r12), 62 | "=m" (r13), 63 | "=m" (r14), 64 | "=m" (r15) 65 | : 66 | : 67 | ); 68 | 69 | uintptr_t* ret = (uintptr_t*) malloc(sizeof(uintptr_t) * 16); 70 | ret[0] = rax; 71 | ret[1] = rbx; 72 | ret[2] = rcx; 73 | ret[3] = rdx; 74 | ret[4] = rbp; 75 | ret[5] = rsp; 76 | ret[6] = rsi; 77 | ret[7] = rdi; 78 | ret[8] = r8; 79 | ret[9] = r9; 80 | ret[10] = r10; 81 | ret[11] = r11; 82 | ret[12] = r12; 83 | ret[13] = r13; 84 | ret[14] = r14; 85 | ret[15] = r15; 86 | return ret; 87 | } 88 | 89 | __thread uintptr_t low_water_mark; 90 | 91 | void set_low_water_mark () { 92 | uintptr_t rsp; 93 | // get current rsp, rbp (this C func frame) 94 | __asm__( 95 | "mov %%rsp, %0 \n" 96 | : "=rm" (rsp) 97 | ); 98 | 99 | low_water_mark = rsp; 100 | } 101 | 102 | uintptr_t get_low_water_mark() { 103 | return low_water_mark; 104 | } 105 | -------------------------------------------------------------------------------- /examples/immix/src/heap/immix/mod.rs: -------------------------------------------------------------------------------- 1 | mod immix_space; 2 | mod immix_mutator; 3 | 4 | pub use self::immix_space::ImmixSpace; 5 | pub use self::immix_mutator::ImmixMutatorLocal; 6 | pub use self::immix_mutator::ImmixMutatorGlobal; 7 | pub use self::immix_space::LineMarkTable as ImmixLineMarkTable; 8 | pub use self::immix_mutator::MUTATORS; 9 | pub use self::immix_mutator::N_MUTATORS; 10 | 11 | pub use heap::layout::*; 12 | 13 | use std::sync::Arc; 14 | use std::sync::RwLock; 15 | 16 | lazy_static!{ 17 | pub static ref SHARED_SPACE : Option>> = None; 18 | } 19 | 20 | pub const BYTES_IN_LINE : usize = 1 << LOG_BYTES_IN_LINE; 21 | pub const BYTES_IN_BLOCK : usize = 1 << LOG_BYTES_IN_BLOCK; 22 | pub const LINES_IN_BLOCK : usize = 1 << (LOG_BYTES_IN_BLOCK - LOG_BYTES_IN_LINE); 23 | 24 | #[derive(PartialEq, Eq, Debug, Copy, Clone)] 25 | pub enum LineMark { 26 | Free = 4, // Marked upon initialization and during sweep upon finding non-Live / non-ConservLive line. 27 | Live = 3, // Marked as such upon allocation 28 | FreshAlloc = 2, // try_alloc_from_local() found an "available" line and allocated it 29 | ConservLive = 1 // Next line of an allocation marked as such 30 | } 31 | 32 | impl From for LineMark { 33 | fn from(val : u8) -> LineMark { 34 | if val >= 5 || val == 0 { 35 | eprintln!("Invalid LineMark::from({})", val); 36 | LineMark::ConservLive 37 | } 38 | // Duplicate ConservLive below is on purpose: 39 | else { [LineMark::ConservLive, LineMark::ConservLive, LineMark::FreshAlloc, LineMark::Live, LineMark::Free][val as usize] } 40 | } 41 | } 42 | 43 | #[derive(PartialEq, Eq, Debug, Copy, Clone)] 44 | pub enum BlockMark { 45 | Usable, 46 | Full 47 | } 48 | -------------------------------------------------------------------------------- /examples/immix/src/heap/layout.flp: -------------------------------------------------------------------------------- 1 | Region -> seq { 2 | Space @(2^19 bytes)@ -> union { 3 | num_blocks union { 4 | FreeBlock @(2^16 bytes)@ -> seq { 5 | 2^16 bytes } 6 | | Block ||2^16 bytes|| @(2^16 bytes)@ 7 | contains(Line) -> seq { 8 | cells : # union { 9 | FreeCell @(1 words)@ -> # words 10 | | Cell }, 11 | remainder : # words, 12 | limit : 0 words } } 13 | | wrds words 14 | | lines Line @|2^8 bytes|@ 15 | contains(Cell) -> # bytes }, 16 | lms : lines LineMark, 17 | refs : wrds RefBits, 18 | mks : wrds MarkBits } 19 | Cell @(1 words)@ contains(Word) -> union { 20 | seq { cell_size : 1 words, 21 | Object -> seq { 22 | ptr_0 : Object ptr, ptr_1 : Object ptr, 23 | ptr_2 : Object ptr, ptr_3 : Object ptr, 24 | payload : # words } } 25 | | # words } 26 | RefBits ||1 bytes|| -> bits { 27 | SHORT_ENCODE : 1 bits, 28 | OBJ_START : 1 bits, 29 | REF : 6 bits } 30 | LineMark -> enum { Free | Live | FreshAlloc 31 | | ConservLive | PrevLive } 32 | MarkBits ||1 bytes|| -> bits { MARK : 8 bits } 33 | Stk -> seq { stack : # seq { Object ptr }, 34 | lowWater : 0 words } 35 | Registers -> seq { regs : # seq { Object ptr }, 36 | regsEnd : 0 words } 37 | AppObject -> union { 38 | LLNode -> 3 words 39 | | QNode -> 2 words 40 | | Dequeue -> 2 words 41 | | # words } 42 | Garbage -> # words 43 | -------------------------------------------------------------------------------- /examples/immix/src/heap/mod.rs: -------------------------------------------------------------------------------- 1 | use std::sync::atomic::AtomicUsize; 2 | #[macro_use] 3 | pub mod debug; 4 | 5 | #[macro_use] 6 | pub mod layout; 7 | use self::layout::*; 8 | 9 | pub mod immix; 10 | pub mod freelist; 11 | pub mod gc; 12 | 13 | pub const ALIGNMENT_VALUE : u8 = 1; 14 | 15 | pub const IMMIX_SPACE_RATIO : f64 = 1.0 - LO_SPACE_RATIO; 16 | pub const LO_SPACE_RATIO : f64 = 0.2; 17 | pub const DEFAULT_HEAP_SIZE : usize = 500 << 20; 18 | 19 | lazy_static! { 20 | pub static ref IMMIX_SPACE_SIZE : AtomicUsize = AtomicUsize::new( (DEFAULT_HEAP_SIZE as f64 * IMMIX_SPACE_RATIO) as usize ); 21 | pub static ref LO_SPACE_SIZE : AtomicUsize = AtomicUsize::new( (DEFAULT_HEAP_SIZE as f64 * LO_SPACE_RATIO) as usize ); 22 | } 23 | 24 | #[inline(always)] 25 | pub fn fill_alignment_gap(start : RemainderAddr, end : LimitAddr) -> () { 26 | Block::memset_remainder_until_limit(ALIGNMENT_VALUE, start, end); 27 | } 28 | -------------------------------------------------------------------------------- /examples/immix/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(const_fn)] 2 | #![allow(dead_code)] 3 | use std::sync::atomic::Ordering; 4 | 5 | #[macro_use] 6 | extern crate lazy_static; 7 | #[macro_use] 8 | extern crate log; 9 | extern crate simple_logger; 10 | extern crate libc; 11 | 12 | pub mod objectmodel; 13 | 14 | #[macro_use] 15 | pub mod heap; 16 | 17 | mod leakme; 18 | 19 | pub use heap::immix::ImmixMutatorLocal as Mutator; 20 | use std::sync::Arc; 21 | use std::sync::RwLock; 22 | use heap::immix::ImmixSpace; 23 | use heap::immix::ImmixMutatorLocal; 24 | use heap::freelist; 25 | use heap::freelist::FreeListSpace; 26 | use std::boxed::Box; 27 | use heap::layout::*; 28 | 29 | #[repr(C)] 30 | pub struct GC { 31 | immix_space: Arc, 32 | lo_space : Arc> 33 | } 34 | 35 | lazy_static! { 36 | pub static ref MY_GC : RwLock> = RwLock::new(None); 37 | } 38 | 39 | pub extern fn gc_init(immix_size: usize, lo_size: usize, n_gcthreads: usize) { 40 | // set this line to turn on certain level of debugging info 41 | // simple_logger::init_with_level(log::LogLevel::Trace).ok(); 42 | 43 | // init space size 44 | heap::IMMIX_SPACE_SIZE.store(immix_size, Ordering::SeqCst); 45 | heap::LO_SPACE_SIZE.store(lo_size, Ordering::SeqCst); 46 | 47 | let (immix_space, lo_space) = { 48 | let immix_space = Arc::new(ImmixSpace::new(immix_size)); 49 | let lo_space = Arc::new(RwLock::new(FreeListSpace::new(lo_size))); 50 | 51 | heap::gc::init(immix_space.clone(), lo_space.clone()); 52 | 53 | (immix_space, lo_space) 54 | }; 55 | 56 | *MY_GC.write().unwrap() = Some(GC {immix_space: immix_space, lo_space: lo_space}); 57 | println!("heap is {} bytes (immix: {} bytes, lo: {} bytes) . ", immix_size + lo_size, immix_size, lo_size); 58 | 59 | // gc threads 60 | heap::gc::GC_THREADS.store(n_gcthreads, Ordering::SeqCst); 61 | println!("{} gc threads", n_gcthreads); 62 | 63 | // init object model 64 | objectmodel::init(); 65 | } 66 | 67 | pub extern fn new_mutator() -> Box { 68 | Box::new(ImmixMutatorLocal::new(MY_GC.read().unwrap().as_ref().unwrap().immix_space.clone())) 69 | } 70 | 71 | #[allow(unused_variables)] 72 | pub extern fn drop_mutator(mutator: Box) { 73 | // rust will reclaim the boxed mutator 74 | } 75 | 76 | #[cfg(target_arch = "x86_64")] 77 | extern "C" { 78 | pub fn set_low_water_mark(); 79 | } 80 | 81 | #[inline(always)] 82 | pub extern fn yieldpoint(mutator: &mut Box) { 83 | mutator.yieldpoint(); 84 | } 85 | 86 | #[inline(never)] 87 | pub extern fn yieldpoint_slow(mutator: &mut Box) { 88 | mutator.yieldpoint_slow() 89 | } 90 | 91 | #[inline(always)] 92 | pub extern fn alloc(mutator: &mut Box, size: usize, align: usize) -> ObjectAddr { 93 | mutator.alloc(size, align) 94 | } 95 | 96 | pub extern fn alloc_slow(mutator: &mut Box, size: usize, align: usize) -> ObjectAddr { 97 | mutator.try_alloc_from_local(size, align) 98 | } 99 | 100 | pub extern fn alloc_large(mutator: &mut Box, size: usize) -> ObjectAddr { 101 | freelist::alloc_large(size, 8, mutator, MY_GC.read().unwrap().as_ref().unwrap().lo_space.clone()) 102 | } 103 | -------------------------------------------------------------------------------- /examples/immix/src/main.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_snake_case)] 2 | #![allow(dead_code)] 3 | use std::env; 4 | use std::sync::atomic::Ordering; 5 | 6 | #[macro_use] 7 | extern crate lazy_static; 8 | #[macro_use] 9 | extern crate log; 10 | 11 | mod objectmodel; 12 | 13 | #[macro_use] 14 | mod heap; 15 | 16 | mod exhaust; 17 | mod mark; 18 | mod trace; 19 | mod mt_trace; 20 | mod gcbench; 21 | mod mt_gcbench; 22 | mod obj_init; 23 | pub mod leakme; 24 | pub mod testme; 25 | 26 | fn init() { 27 | objectmodel::init(); 28 | } 29 | 30 | fn main() { 31 | init(); 32 | 33 | match env::var("HEAP_SIZE") { 34 | Ok(val) => { 35 | if val.ends_with("M") { 36 | let (num, _) = val.split_at(val.len() - 1); 37 | let heap_size = num.parse::().unwrap() << 20; 38 | 39 | let immix_space_size : usize = (heap_size as f64 * heap::IMMIX_SPACE_RATIO) as usize; 40 | heap::IMMIX_SPACE_SIZE.store(immix_space_size, Ordering::SeqCst); 41 | 42 | let lo_space_size : usize = (heap_size as f64 * heap::LO_SPACE_RATIO) as usize; 43 | heap::LO_SPACE_SIZE.store(lo_space_size, Ordering::SeqCst); 44 | 45 | println!("heap is {} bytes (immix: {} bytes, lo: {} bytes) . ", heap_size, immix_space_size, lo_space_size); 46 | } else { 47 | println!("unknow heap size variable: {}, ignore", val); 48 | println!("using default heap size: {} bytes. ", heap::IMMIX_SPACE_SIZE.load(Ordering::SeqCst)); 49 | } 50 | }, 51 | Err(_) => { 52 | println!("using default heap size: {} bytes. ", heap::IMMIX_SPACE_SIZE.load(Ordering::SeqCst)); 53 | } 54 | } 55 | 56 | match env::var("N_GCTHREADS") { 57 | Ok(val) => { 58 | heap::gc::GC_THREADS.store(val.parse::().unwrap(), Ordering::SeqCst); 59 | }, 60 | Err(_) => { 61 | heap::gc::GC_THREADS.store(8, Ordering::SeqCst); 62 | } 63 | } 64 | 65 | if cfg!(feature = "exhaust") { 66 | println!("exhaust"); exhaust::exhaust_alloc(); 67 | } else if cfg!(feature = "initobj") { 68 | println!("initobj"); obj_init::alloc_init(); 69 | } else if cfg!(feature = "gcbench") { 70 | println!("gcbench"); gcbench::start(); 71 | } else if cfg!(feature = "mt-gcbench") { 72 | println!("mt-gcbench"); mt_gcbench::start(); 73 | } else if cfg!(feature = "mark") { 74 | println!("mark"); mark::alloc_mark(); 75 | } else if cfg!(feature = "trace") { 76 | println!("trace"); trace::alloc_trace(); 77 | } else if cfg!(feature = "mt-trace") { 78 | println!("mt-trace"); mt_trace::alloc_trace(); 79 | } else if cfg!(feature = "leakme") { 80 | println!("leakme"); leakme::start(); 81 | } else if cfg!(feature = "testme") { 82 | println!("testme"); testme::start(); 83 | } else { 84 | println!("unknown features: build with 'cargo build --release --features \"exhaust\""); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /examples/immix/src/mark.rs: -------------------------------------------------------------------------------- 1 | extern crate time; 2 | 3 | use heap; 4 | use heap::layout::*; 5 | use heap::immix::ImmixMutatorLocal; 6 | use heap::immix::ImmixSpace; 7 | use heap::freelist::FreeListSpace; 8 | 9 | use std::sync::RwLock; 10 | use std::sync::{Arc}; 11 | use std::sync::atomic::Ordering; 12 | 13 | use exhaust::OBJECT_SIZE; 14 | use exhaust::OBJECT_ALIGN; 15 | use exhaust::ALLOCATION_TIMES; 16 | 17 | const MARK_TIMES : usize = ALLOCATION_TIMES; 18 | 19 | #[allow(unused_variables)] 20 | pub fn alloc_mark() { 21 | let shared_space : Arc = { 22 | let space : ImmixSpace = ImmixSpace::new(heap::IMMIX_SPACE_SIZE.load(Ordering::SeqCst)); 23 | 24 | Arc::new(space) 25 | }; 26 | let lo_space : Arc> = { 27 | let space : FreeListSpace = FreeListSpace::new(heap::LO_SPACE_SIZE.load(Ordering::SeqCst)); 28 | Arc::new(RwLock::new(space)) 29 | }; 30 | heap::gc::init(shared_space.clone(), lo_space.clone()); 31 | 32 | let mut mutator = ImmixMutatorLocal::new(shared_space.clone()); 33 | 34 | println!("Trying to allocate 1 object of (size {}, align {}). ", OBJECT_SIZE, OBJECT_ALIGN); 35 | const ACTUAL_OBJECT_SIZE : usize = OBJECT_SIZE; 36 | println!("Considering header size of {}, an object should be {}. ", 0, ACTUAL_OBJECT_SIZE); 37 | 38 | println!("Trying to allocate {} objects, which will take roughly {} bytes", MARK_TIMES, MARK_TIMES * ACTUAL_OBJECT_SIZE); 39 | let mut objs : Vec = vec![]; 40 | for _ in 0..MARK_TIMES { 41 | let res = mutator.alloc(ACTUAL_OBJECT_SIZE, OBJECT_ALIGN); 42 | mutator.init_object(res, 0b000011_11); 43 | 44 | objs.push(res); 45 | } 46 | 47 | mark_loop(objs, &shared_space); 48 | } 49 | 50 | #[inline(never)] 51 | fn mark_loop(objs: Vec, shared_space: &Arc) { 52 | use objectmodel; 53 | 54 | println!("Start marking"); 55 | let t_start = time::Instant::now(); 56 | 57 | let mark_state : MarkBits = MarkBits::set_MARK_from_u8(objectmodel::MARK_STATE.load(Ordering::SeqCst) as u8); 58 | 59 | let line_mark_table = shared_space.line_mark_table(); 60 | let (space_start, space_end) = (shared_space.start(), shared_space.end()); 61 | 62 | let trace_map = shared_space.trace_map.toStart; 63 | 64 | for i in 0..objs.len() { 65 | let obj_addr = unsafe {*objs.get_unchecked(i)}; 66 | 67 | // mark the object as traced 68 | objectmodel::mark_as_traced(trace_map, space_start, obj_addr, mark_state); 69 | 70 | // mark meta-data 71 | if space_start.contains_object(obj_addr, space_end) { 72 | line_mark_table.mark_line_live2(space_start.get_first_line(), obj_addr); 73 | } 74 | } 75 | 76 | let t_end = time::Instant::now(); 77 | 78 | println!("time used: {} msec", (t_end - t_start).whole_milliseconds()); 79 | } 80 | -------------------------------------------------------------------------------- /examples/immix/src/mt_trace.rs: -------------------------------------------------------------------------------- 1 | extern crate time; 2 | 3 | use heap; 4 | use heap::layout::*; 5 | use heap::immix::ImmixMutatorLocal; 6 | use heap::immix::ImmixSpace; 7 | use heap::freelist::FreeListSpace; 8 | 9 | pub const K : usize = 4; 10 | pub const TREE_DEPTH : usize = 10; // 10 11 | pub const TREE_COUNT : usize = 50; // 50 12 | 13 | pub const OBJECT_SIZE : usize = K * 8; 14 | pub const ACTUAL_OBJECT_SIZE : usize = K * 8; 15 | pub const OBJECT_ALIGN : usize = 8; 16 | 17 | #[inline(always)] 18 | fn alloc_k_ary_tree(mutator: &mut ImmixMutatorLocal) -> ObjectAddr { 19 | let addr = mutator.alloc(ACTUAL_OBJECT_SIZE, 8); 20 | mutator.init_object(addr, 0b001111_11); 21 | addr 22 | } 23 | 24 | fn make_tree(depth: usize, mutator: &mut ImmixMutatorLocal) -> ObjectAddr { 25 | if depth <= 0 { 26 | alloc_k_ary_tree(mutator) 27 | } else { 28 | let mut children = vec![]; 29 | for _ in 0..K { 30 | children.push(make_tree(depth - 1, mutator)); 31 | } 32 | 33 | let result = alloc_k_ary_tree(mutator); 34 | // println!("parent node: {:X}", result); 35 | 36 | let cursor = result; 37 | cursor.ptr_0().store_ObjectAddr(children.pop().unwrap()); 38 | cursor.ptr_1().store_ObjectAddr(children.pop().unwrap()); 39 | cursor.ptr_2().store_ObjectAddr(children.pop().unwrap()); 40 | cursor.ptr_3().store_ObjectAddr(children.pop().unwrap()); 41 | 42 | result 43 | } 44 | } 45 | 46 | #[allow(unused_variables)] 47 | pub fn alloc_trace() { 48 | use std::sync::{Arc, RwLock}; 49 | use std::sync::atomic::Ordering; 50 | 51 | let shared_space : Arc = { 52 | let space : ImmixSpace = ImmixSpace::new(heap::IMMIX_SPACE_SIZE.load(Ordering::SeqCst)); 53 | 54 | Arc::new(space) 55 | }; 56 | let lo_space : Arc> = { 57 | let space : FreeListSpace = FreeListSpace::new(heap::LO_SPACE_SIZE.load(Ordering::SeqCst)); 58 | Arc::new(RwLock::new(space)) 59 | }; 60 | heap::gc::init(shared_space.clone(), lo_space.clone()); 61 | let mut mutator = ImmixMutatorLocal::new(shared_space.clone()); 62 | 63 | println!("Trying to allocate 1 object of (size {}, align {}). ", K * 8, 8); 64 | println!("Considering header size of {}, an object should be {}. ", 0, ACTUAL_OBJECT_SIZE); 65 | 66 | println!("Trying to allocate {} trees of depth {}, which is {} objects ({} bytes)", 67 | TREE_COUNT, TREE_DEPTH, TREE_COUNT * K.pow(TREE_DEPTH as u32), TREE_COUNT * K.pow(TREE_DEPTH as u32) * ACTUAL_OBJECT_SIZE); 68 | 69 | let mut roots : Vec = vec![]; 70 | 71 | for _ in 0..TREE_COUNT { 72 | roots.push(make_tree(TREE_DEPTH, &mut mutator)); 73 | } 74 | 75 | println!("Start tracing"); 76 | 77 | let t_start = time::Instant::now(); 78 | heap::gc::start_trace(&mut roots, shared_space, lo_space); 79 | let t_end = time::Instant::now(); 80 | 81 | println!("time used: {} msec", (t_end - t_start).whole_milliseconds()); 82 | } 83 | -------------------------------------------------------------------------------- /examples/immix/src/obj_init.rs: -------------------------------------------------------------------------------- 1 | extern crate time; 2 | 3 | use heap; 4 | use heap::immix::ImmixMutatorLocal; 5 | use heap::immix::ImmixSpace; 6 | use heap::freelist::FreeListSpace; 7 | use heap::layout::*; 8 | 9 | use std::sync::RwLock; 10 | use std::sync::{Arc}; 11 | use std::sync::atomic::Ordering; 12 | 13 | use exhaust::OBJECT_SIZE; 14 | use exhaust::OBJECT_ALIGN; 15 | use exhaust::ALLOCATION_TIMES; 16 | 17 | const INIT_TIMES : usize = ALLOCATION_TIMES; 18 | 19 | #[allow(unused_variables)] 20 | pub fn alloc_init() { 21 | let shared_space : Arc = { 22 | let space : ImmixSpace = ImmixSpace::new(heap::IMMIX_SPACE_SIZE.load(Ordering::SeqCst)); 23 | 24 | Arc::new(space) 25 | }; 26 | let lo_space : Arc> = { 27 | let space : FreeListSpace = FreeListSpace::new(heap::LO_SPACE_SIZE.load(Ordering::SeqCst)); 28 | Arc::new(RwLock::new(space)) 29 | }; 30 | heap::gc::init(shared_space.clone(), lo_space.clone()); 31 | 32 | let mut mutator = ImmixMutatorLocal::new(shared_space.clone()); 33 | 34 | println!("Trying to allocate 1 object of (size {}, align {}). ", OBJECT_SIZE, OBJECT_ALIGN); 35 | const ACTUAL_OBJECT_SIZE : usize = OBJECT_SIZE; 36 | println!("Considering header size of {}, an object should be {}. ", 0, ACTUAL_OBJECT_SIZE); 37 | 38 | println!("Trying to allocate {} objects, which will take roughly {} bytes", INIT_TIMES, INIT_TIMES * ACTUAL_OBJECT_SIZE); 39 | let mut objs = vec![]; 40 | for _ in 0..INIT_TIMES { 41 | let res = mutator.alloc(ACTUAL_OBJECT_SIZE, OBJECT_ALIGN); 42 | 43 | objs.push(res); 44 | } 45 | 46 | init_loop(objs, &mut mutator); 47 | } 48 | 49 | #[inline(never)] 50 | fn init_loop(objs: Vec, mutator: &mut ImmixMutatorLocal) { 51 | println!("Start init objects"); 52 | let t_start = time::Instant::now(); 53 | 54 | for obj in objs { 55 | mutator.init_object_no_inline(obj, 0b000011_11); 56 | } 57 | 58 | let t_end = time::Instant::now(); 59 | 60 | println!("time used: {} msec", (t_end - t_start).whole_milliseconds()); 61 | } 62 | -------------------------------------------------------------------------------- /examples/immix/src/objectmodel/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_snake_case)] 2 | use std::sync::atomic; 3 | use std::sync::atomic::Ordering; 4 | 5 | pub static MARK_STATE : atomic::AtomicUsize = atomic::AtomicUsize::new(0); 6 | 7 | use heap::layout::*; 8 | 9 | pub fn init() { 10 | MARK_STATE.store(1, atomic::Ordering::SeqCst); 11 | } 12 | 13 | pub fn get_curr_MARK_STATE() -> MarkBits { 14 | MarkBits::set_MARK_from_u8(MARK_STATE.load(Ordering::SeqCst) as u8) 15 | } 16 | 17 | pub fn get_curr_UNMARKED_STATE() -> MarkBits { 18 | let mark_state = MARK_STATE.load(Ordering::SeqCst) as u8; 19 | if mark_state == 0 { 20 | MarkBits::set_MARK_from_u8(0b00000001 as u8) 21 | } else { 22 | MarkBits::set_MARK_from_u8(0b00000000 as u8) 23 | } 24 | } 25 | 26 | pub fn flip_mark_state() { 27 | let mark_state = MARK_STATE.load(atomic::Ordering::SeqCst); 28 | if mark_state == 0 { 29 | MARK_STATE.store(1, atomic::Ordering::SeqCst); 30 | } else { 31 | MARK_STATE.store(0, atomic::Ordering::SeqCst); 32 | } 33 | } 34 | 35 | #[inline(always)] 36 | pub fn mark_as_traced(trace_map: MarkBitsAddr, space_start: SpaceAddr, obj: ObjectAddr, mark_state: MarkBits) { 37 | let cell = CellAddr::from_object(obj); 38 | Word2MarkBits::map_set(space_start.get_first_word(), trace_map, cell.get_first_word(), mark_state) 39 | } 40 | 41 | #[inline(always)] 42 | pub fn is_traced(trace_map: MarkBitsAddr, space_start: SpaceAddr, obj: ObjectAddr, mark_state: MarkBits) -> bool { 43 | let cell = CellAddr::from_object(obj); 44 | let state = Word2MarkBits::map_get(space_start.get_first_word(), trace_map, cell.get_first_word()); 45 | //println!("state = {}, (state == mark_state) = {}", state.get_MARK_bits(), state == mark_state); 46 | state == mark_state 47 | } 48 | 49 | #[inline(always)] 50 | pub fn get_ref_byte(alloc_map: RefBitsAddr, space_start: SpaceAddr, obj: ObjectAddr) -> RefBits { 51 | let cell = CellAddr::from_object(obj); 52 | Word2RefBits::map_get(space_start.get_first_word(), alloc_map, cell.get_first_word()) 53 | } 54 | 55 | #[inline(always)] 56 | pub fn set_ref_byte(alloc_map: RefBitsAddr, space_start: SpaceAddr, obj: ObjectAddr, value: RefBits) { 57 | let cell_addr = CellAddr::from_object(obj); 58 | Word2RefBits::map_set(space_start.get_first_word(), alloc_map, cell_addr.get_first_word(), value) 59 | } 60 | 61 | -------------------------------------------------------------------------------- /examples/immix/src/testme.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_upper_case_globals)] 2 | #![allow(non_camel_case_types)] 3 | #![allow(non_snake_case)] 4 | #![allow(unused_variables)] 5 | #![allow(unused_assignments)] 6 | 7 | use heap; 8 | use heap::layout::*; 9 | use heap::immix::ImmixMutatorLocal; 10 | use heap::immix::ImmixSpace; 11 | use heap::freelist::FreeListSpace; 12 | use std::mem::size_of as size_of; 13 | use std::boxed::Box; 14 | 15 | pub struct Node { 16 | fwd : *mut Node, 17 | bwd : *mut Node, 18 | val : usize 19 | } 20 | 21 | static mut mutator: *mut ImmixMutatorLocal = std::ptr::null::() as *mut ImmixMutatorLocal; 22 | fn alloc(numP: usize, typ: u8) -> *mut Type { 23 | let sz = size_of::(); 24 | let obj_addr : ObjectAddr = unsafe { (*mutator).alloc(sz, 8) }; 25 | unsafe { (*mutator).init_object(obj_addr, match numP { 26 | 0 => 0b000000_11, 27 | 1 => 0b000001_11, 28 | 2 => 0b000011_11, 29 | 3 => 0b000111_11, 30 | 4 => 0b001111_11, 31 | _ => 0b000000_11 32 | }); } 33 | let ret = obj_addr.to_ptr_mut::(); 34 | //for i in 0..sz { 35 | // check_expect_pc!(obj_addr.as_usize() + i, vec![__FLP_IDX_CELL]); 36 | // unsafe { layout::pc::set_bits(0, (obj_addr.as_usize() + i) as *const libc::c_void, typ); } 37 | //} 38 | ret 39 | } 40 | 41 | fn alloc_node() -> *mut Node { 42 | let ret = alloc::(2, __FLP_IDX_LLNODE); 43 | eprintln!("Allocated {} bytes at 0x{:X}", size_of::(), ret as usize); 44 | ret 45 | } 46 | 47 | pub const NUM_BLOCKS : usize = 1; // 1 Block (65536 bytes) fits 2048 Nodes (3 words + 1 cell_size word per Node) 48 | pub const LO_SIZE : usize = 1 << 20; // Also 1 megabyte (large-object space) 49 | 50 | pub fn fwd(n: *mut Node, val: *mut Node) { unsafe { 51 | (*n).fwd = val; 52 | }} 53 | pub fn bwd(n: *mut Node, val: *mut Node) { unsafe { 54 | (*n).bwd = val; 55 | }} 56 | pub fn get_fwd(n: *mut Node) -> *mut Node { unsafe { 57 | (*n).fwd 58 | }} 59 | pub fn get_bwd(n: *mut Node) -> *mut Node { unsafe { 60 | (*n).bwd 61 | }} 62 | 63 | pub fn trash_it(n: *mut Node) { 64 | //for i in 0 .. size_of::() { 65 | // unsafe { layout::pc::set_bits(0, ((n as usize) + i) as *const libc::c_void, __FLP_IDX_GARBAGE); } 66 | //} 67 | } 68 | 69 | pub fn start() { 70 | use std::sync::{Arc, RwLock}; 71 | unsafe {heap::gc::set_low_water_mark();} 72 | let immix_space : Arc = { 73 | let space : ImmixSpace = ImmixSpace::new(NUM_BLOCKS * (1 << LOG_BYTES_IN_BLOCK)); 74 | Arc::new(space) 75 | }; 76 | let lo_space : Arc> = { 77 | let space : FreeListSpace = FreeListSpace::new(LO_SIZE); 78 | Arc::new(RwLock::new(space)) 79 | }; 80 | heap::gc::init(immix_space.clone(), lo_space.clone()); 81 | unsafe { mutator = Box::into_raw(Box::new(ImmixMutatorLocal::new(immix_space.clone()))); } 82 | 83 | { 84 | let mut tmp = 0x0 as *mut Node; 85 | let orig = alloc_node(); 86 | fwd(orig, orig); 87 | bwd(orig, orig); 88 | let mut curr = orig; 89 | for i in 0 .. (2048 - 1) { 90 | tmp = alloc_node(); 91 | fwd(curr, tmp); 92 | bwd(tmp, curr); 93 | curr = tmp; 94 | } 95 | fwd(curr, curr); 96 | curr = get_fwd(orig); 97 | loop { 98 | tmp = get_fwd(curr); 99 | bwd(curr, 0xdeadbeef as *mut Node); 100 | fwd(curr, 0xdeadbeef as *mut Node); 101 | trash_it(curr); 102 | if tmp == curr { break; } 103 | curr = tmp; 104 | } 105 | fwd(orig, orig); 106 | bwd(orig, orig); 107 | curr = orig; 108 | tmp = orig; 109 | // Everything except the original node is marked as garbage now, and no longer accessible 110 | // via the Rust stack (i.e. orig and curr both point to orig, which is /not/ marked as 111 | // garbage) 112 | tmp = alloc_node(); 113 | 114 | // Now trash that node, and move on to next allocation cycle: 115 | fwd(tmp, 0xdeaddead as *mut Node); 116 | bwd(tmp, 0xdeaddead as *mut Node); 117 | trash_it(tmp); 118 | for i in 0 .. (2024 - 1) { 119 | tmp = alloc_node(); 120 | fwd(curr, tmp); 121 | bwd(tmp, curr); 122 | curr = tmp; 123 | } 124 | fwd(curr, curr); 125 | 126 | // Delete everything again, except the original: 127 | curr = get_fwd(orig); 128 | loop { 129 | tmp = get_fwd(curr); 130 | bwd(curr, 0xdeadeeee as *mut Node); 131 | fwd(curr, 0xdeadeeee as *mut Node); 132 | trash_it(curr); 133 | if tmp == curr { break; } 134 | curr = tmp; 135 | } 136 | fwd(orig, orig); 137 | bwd(orig, orig); 138 | curr = orig; 139 | tmp = orig; 140 | 141 | // Trigger GC?: 142 | tmp = alloc_node(); 143 | 144 | } 145 | println!("Finished!"); 146 | //layout::dump_map() 147 | } 148 | 149 | -------------------------------------------------------------------------------- /examples/immix/src/trace.rs: -------------------------------------------------------------------------------- 1 | extern crate time; 2 | 3 | use heap; 4 | use heap::layout::*; 5 | use heap::immix::ImmixMutatorLocal; 6 | use heap::immix::ImmixSpace; 7 | use heap::freelist::FreeListSpace; 8 | 9 | use std::sync::{Arc}; 10 | use std::sync::atomic::Ordering; 11 | use std::sync::RwLock; 12 | 13 | use exhaust::OBJECT_SIZE; 14 | use exhaust::OBJECT_ALIGN; 15 | use exhaust::ALLOCATION_TIMES; 16 | 17 | const TRACE_TIMES : usize = ALLOCATION_TIMES; 18 | 19 | /* 20 | struct Node<'a> { 21 | hdr : u64, 22 | next : &'a Node<'a>, 23 | unused_ptr : usize, 24 | unused_int : i32, 25 | unused_int2: i32 26 | } 27 | */ 28 | 29 | #[allow(unused_variables)] 30 | pub fn alloc_trace() { 31 | let shared_space : Arc = { 32 | let space : ImmixSpace = ImmixSpace::new(heap::IMMIX_SPACE_SIZE.load(Ordering::SeqCst)); 33 | 34 | Arc::new(space) 35 | }; 36 | let lo_space : Arc> = { 37 | let space : FreeListSpace = FreeListSpace::new(heap::LO_SPACE_SIZE.load(Ordering::SeqCst)); 38 | Arc::new(RwLock::new(space)) 39 | }; 40 | heap::gc::init(shared_space.clone(), lo_space.clone()); 41 | 42 | let mut mutator = ImmixMutatorLocal::new(shared_space.clone()); 43 | 44 | println!("Trying to allocate 1 object of (size {}, align {}). ", OBJECT_SIZE, OBJECT_ALIGN); 45 | const ACTUAL_OBJECT_SIZE : usize = OBJECT_SIZE; 46 | println!("Considering header size of {}, an object should be {}. ", 0, ACTUAL_OBJECT_SIZE); 47 | 48 | println!("Trying to allocate {} objects, which will take roughly {} bytes", TRACE_TIMES, TRACE_TIMES * ACTUAL_OBJECT_SIZE); 49 | let root : ObjectAddr = mutator.alloc(ACTUAL_OBJECT_SIZE, OBJECT_ALIGN); 50 | mutator.init_object(root, 0b00000001_11); 51 | 52 | let mut prev : ObjectAddr = root; 53 | for _ in 0..TRACE_TIMES - 1 { 54 | let res = mutator.alloc(ACTUAL_OBJECT_SIZE, OBJECT_ALIGN); 55 | mutator.init_object(res, 0b000001_11); 56 | 57 | // set prev's 1st field (offset 0) to this object 58 | prev.ptr_0().store_ObjectAddr(res); 59 | 60 | prev = res; 61 | } 62 | 63 | trace_loop(root, shared_space, lo_space); 64 | } 65 | 66 | #[inline(never)] 67 | fn trace_loop(root: ObjectAddr, shared_space: Arc, lo_space: Arc>) { 68 | println!("Start tracing"); 69 | let mut roots = vec![root]; 70 | 71 | let t_start = time::Instant::now(); 72 | 73 | heap::gc::start_trace(&mut roots, shared_space, lo_space); 74 | 75 | let t_end = time::Instant::now(); 76 | 77 | println!("time used: {} msec", (t_end - t_start).whole_milliseconds()); 78 | } 79 | -------------------------------------------------------------------------------- /examples/immix/test_mac.sh: -------------------------------------------------------------------------------- 1 | cargo build --release --features mt-trace 2 | cp ../target/release/libimmix_rust.dylib . 3 | gcc test.c libimmix_rust.dylib 4 | ./a.out 5 | -------------------------------------------------------------------------------- /examples/layer.flp: -------------------------------------------------------------------------------- 1 | // Parser test 2 | 3 | Seq -> seq { 4 | Oops -> Bar, 5 | Foo -> Baz ptr 6 | } 7 | 8 | -------------------------------------------------------------------------------- /examples/map_bits.flp: -------------------------------------------------------------------------------- 1 | // Compiler test: mapping bits to bits code gen 2 | 3 | Foo -> seq { 4 | count bytes, 5 | count Bar ||2 bytes|| -> 2 bytes 6 | } 7 | 8 | -------------------------------------------------------------------------------- /examples/named_ref.flp: -------------------------------------------------------------------------------- 1 | // Parser test: a named reference to a flp type specified elsewhere 2 | 3 | Seq -> seq { 4 | a : # A, 5 | b : B 6 | } 7 | 8 | A -> 1 words 9 | 10 | B -> 2 bytes 11 | 12 | -------------------------------------------------------------------------------- /examples/nested.flp: -------------------------------------------------------------------------------- 1 | // Parser test: nested groups (union and seq) with 2 | // fixed-width fields. 3 | 4 | Block -> union { 5 | A -> seq { a : 0 words, b : 2 words } 6 | | B -> seq { a : 0 words, c : 2 words } 7 | } 8 | 9 | -------------------------------------------------------------------------------- /examples/nested_union.flp: -------------------------------------------------------------------------------- 1 | // Parser test: union in various places (both in a flake and in a field) 2 | 3 | Block -> union { 4 | A -> seq { 5 | a : union { 1 words | 1 words } 6 | , b : 2^2 words 7 | } 8 | | B -> seq { a : 0 words, c : (2^4) words } 9 | } 10 | 11 | -------------------------------------------------------------------------------- /examples/parens.flp: -------------------------------------------------------------------------------- 1 | 2 | Bar -> 1 words - 1 bytes 3 | 4 | Foo -> (1 words - 1 bytes) 5 | 6 | -------------------------------------------------------------------------------- /examples/seq.flp: -------------------------------------------------------------------------------- 1 | // Parser test: basic sequence with fixed-width fields. 2 | 3 | Seq -> seq { 4 | a : Oops, 5 | b : Oops ptr 6 | } 7 | 8 | -------------------------------------------------------------------------------- /examples/union.flp: -------------------------------------------------------------------------------- 1 | // Parser test: basic union test. 2 | 3 | Union -> union { 4 | Oops 5 | | Oops ptr 6 | } 7 | 8 | -------------------------------------------------------------------------------- /examples/uniq_fail.flp: -------------------------------------------------------------------------------- 1 | 2 | Outer -> seq { 3 | foo : 1 words, 4 | bar : 2 words, 5 | Inner -> seq { 6 | Outer -> 2 words 7 | } 8 | } 9 | 10 | Other -> seq { 11 | foo : 2 words 12 | } 13 | 14 | -------------------------------------------------------------------------------- /flp-compiler/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | -------------------------------------------------------------------------------- /flp-compiler/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "flp-compiler" 5 | version = "0.1.0" 6 | 7 | -------------------------------------------------------------------------------- /flp-compiler/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "flp-compiler" 3 | version = "0.1.0" 4 | authors = ["Karl Cronburg "] 5 | license = "MIT" 6 | repository = "https://github.com/RedlineResearch/floorplan" 7 | homepage = "https://redlineresearch.github.io/floorplan/" 8 | description = "Floorplan, a memory layout specification language, compiler binding for Rust." 9 | 10 | [dependencies] 11 | 12 | -------------------------------------------------------------------------------- /flp-compiler/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # The Official Floorplan Compiler Crate 2 | //! 3 | //! This module interfaces with the Haskell implementation of the 4 | //! Floorplan compiler, providing official support for compiling 5 | //! Floorplan specifications into Rust code directly from the 6 | //! cargo toolchain. 7 | //! 8 | //! In order to use this crate, you must have the `flp` executable 9 | //! on your `$PATH` when developing a Floorplan-based memory manager. 10 | //! 11 | //! In order to compile directly from the cargo toolchain, include a 12 | //! `build.rs` in your project root similar to the following: 13 | //! 14 | //! ```rust,ignore 15 | //! extern crate flp_compiler as flpc; 16 | //! 17 | //! fn main() { 18 | //! flpc::Build::new() 19 | //! .src("src/heap/layout.flp") 20 | //! .dest("src/heap/layout.rs") 21 | //! .compile(); 22 | //! } 23 | //! ``` 24 | //! 25 | //! For information on acquiring the Floorplan compiler itself, 26 | //! go see the [GitHub project here][github-project]. 27 | //! 28 | //! [github-project]: https://github.com/RedlineResearch/floorplan 29 | use std::process::*; 30 | use std::io::Write; 31 | 32 | /// A build configuration. 33 | #[derive(Clone, Debug)] 34 | pub struct Build { 35 | src: Option, 36 | dest: Option, 37 | } 38 | 39 | /// A very basic implementation of a binding to an external compiler, supporting 40 | /// specification of input and output files. 41 | impl Build { 42 | 43 | /// Construct a new build configuration with default values, which will fail to compile 44 | /// by default. 45 | pub fn new() -> Build { 46 | Build { 47 | src: None, 48 | dest: None, 49 | } 50 | } 51 | 52 | /// Set the source `.flp` to be compiled. 53 | pub fn src(&mut self, s: &str) -> &mut Build { 54 | self.src = Some(s.to_string()); 55 | self 56 | } 57 | 58 | /// Set the destination `.rs` file to generate the output library into. 59 | pub fn dest(&mut self, d: &str) -> &mut Build { 60 | self.dest = Some(d.to_string()); 61 | self 62 | } 63 | 64 | /// Run the compiler on the current build configuration, failing miserably on error. 65 | pub fn compile(&mut self) { 66 | if let Err(e) = self.try_compile() { 67 | fail(&e); 68 | } 69 | } 70 | 71 | /// Attempt to run the compiler on the current build configuration, politely 72 | /// returning an error message if the compiler fails. 73 | pub fn try_compile(&mut self) -> Result<(), String> { 74 | let src = self.src.clone().unwrap(); 75 | let dest = self.dest.clone().unwrap(); 76 | Command::new("flp") 77 | .args(&[src, dest]) 78 | .output() 79 | .expect("Floorplan failed to run."); 80 | Ok(()) 81 | } 82 | 83 | } 84 | 85 | fn fail(s: &str) -> ! { 86 | let _ = writeln!(std::io::stderr(), "\nError: {}\n\n", s); 87 | std::process::exit(1); 88 | } 89 | 90 | -------------------------------------------------------------------------------- /flp-framework/.gitignore: -------------------------------------------------------------------------------- 1 | Cargo.lock 2 | /target 3 | -------------------------------------------------------------------------------- /flp-framework/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "flp-framework" 3 | version = "0.1.0" 4 | authors = ["Karl Cronburg "] 5 | license = "MIT" 6 | repository = "https://github.com/RedlineResearch/floorplan" 7 | homepage = "https://redlineresearch.github.io/floorplan/" 8 | description = "Floorplan, a memory layout specification language, code-generation necessities." 9 | 10 | [dependencies] 11 | 12 | -------------------------------------------------------------------------------- /genrs/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /src/layout.rs 3 | -------------------------------------------------------------------------------- /genrs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dist" 3 | version = "0.1.0" 4 | authors = ["Karl Cronburg "] 5 | build = "build.rs" 6 | 7 | [build-dependencies] 8 | flp-compiler = { path = "../flp-compiler" } 9 | 10 | [[bin]] 11 | name = "main" 12 | path = "src/main.rs" 13 | 14 | [dependencies] 15 | flp-framework = { path = "../flp-framework" } 16 | libc = "0.2.2" 17 | 18 | -------------------------------------------------------------------------------- /genrs/build.rs: -------------------------------------------------------------------------------- 1 | extern crate flp_compiler as flpc; 2 | 3 | fn main() { 4 | 5 | flpc::Build::new() 6 | .src("src/layout.flp") 7 | .dest("src/layout.rs") 8 | .compile(); 9 | 10 | } 11 | 12 | -------------------------------------------------------------------------------- /genrs/src/layout.flp: -------------------------------------------------------------------------------- 1 | 2 | HelloRust -> 32 bytes 3 | 4 | -------------------------------------------------------------------------------- /genrs/src/main.rs: -------------------------------------------------------------------------------- 1 | mod layout; 2 | use layout::*; 3 | 4 | fn main() { 5 | print!("Hello, world: {}\n", BYTES_IN_HELLORUST); 6 | } 7 | 8 | -------------------------------------------------------------------------------- /package.yaml: -------------------------------------------------------------------------------- 1 | name: flp 2 | version: 0.1.0.0 3 | github: "RedlineResearch/floorplan" 4 | license: MIT 5 | author: "Karl Cronburg" 6 | maintainer: "karl@cs.tufts.edu" 7 | copyright: "2019 Karl Cronburg" 8 | 9 | extra-source-files: 10 | - README.md 11 | - LICENSE 12 | - LICENSE.language-rust 13 | - ChangeLog.md 14 | - examples/app.flp 15 | - examples/arith.flp 16 | - examples/arith_id.flp 17 | - examples/arith_power.flp 18 | - examples/bits.flp 19 | - examples/bump.flp 20 | - examples/dynamic_prim.flp 21 | - examples/dyn_choice.flp 22 | - examples/dyn_seq.flp 23 | - examples/empty.flp 24 | - examples/enum_bad0.flp 25 | - examples/enum.flp 26 | - examples/layer.flp 27 | - examples/map_bits.flp 28 | - examples/named_ref.flp 29 | - examples/nested.flp 30 | - examples/nested_union.flp 31 | - examples/parens.flp 32 | - examples/seq.flp 33 | - examples/union.flp 34 | - examples/uniq_fail.flp 35 | 36 | # Metadata used when publishing your package 37 | synopsis: A layout spec language for memory managers implemented in Rust. 38 | category: Compiler 39 | 40 | # To avoid duplicated efforts in documentation and dealing with the 41 | # complications of embedding Haddock markup inside cabal files, it is 42 | # common to point users to the README.md file. 43 | description: Please see the README on GitHub at 44 | 45 | dependencies: 46 | - base >= 4.7 && < 5 47 | - array >= 0.5.3.0 48 | - template-haskell 49 | - th-lift 50 | - containers 51 | # The following dependencies are required by language-rust code in our src/ tree: 52 | - deepseq 53 | - prettyprinter 54 | - transformers 55 | - haskell-src-meta 56 | - language-c-quote >= 0.12.2.1 57 | - mainland-pretty 58 | - text 59 | - srcloc 60 | - regex >= 1.1.0.0 61 | 62 | library: 63 | source-dirs: src 64 | 65 | build-tools: 66 | - alex 67 | - happy 68 | 69 | executables: 70 | flp: 71 | source-dirs: app 72 | main: floorplan.hs 73 | dependencies: 74 | - flp 75 | sem: 76 | source-dirs: app 77 | main: semantics.hs 78 | dependencies: 79 | - flp 80 | - pretty-simple 81 | 82 | tests: 83 | parser: 84 | main: Main.hs 85 | source-dirs: test/parser 86 | ghc-options: 87 | - -threaded 88 | - -rtsopts 89 | - -with-rtsopts=-N 90 | dependencies: 91 | - flp 92 | - test-framework >= 0.8 93 | - test-framework-hunit >= 0.3 94 | - HUnit >= 1.6 95 | 96 | -------------------------------------------------------------------------------- /proofs/.gitignore: -------------------------------------------------------------------------------- 1 | *.vo 2 | *.glob 3 | .*.aux 4 | -------------------------------------------------------------------------------- /proofs/Common.v: -------------------------------------------------------------------------------- 1 | Require Import Coq.Strings.String. 2 | From Coq Require Import Bool Ascii String. 3 | Require Import Coq.Bool.Bool. 4 | 5 | Theorem if_f_app: forall (X : Type) (Y : Type) (f : X -> Y) (cond : bool) (a : X) (b : X), 6 | f (if cond then a else b) = if cond then f a else f b. 7 | Proof. intros X Y f cond a b. destruct cond. reflexivity. reflexivity. Qed. 8 | 9 | Theorem if_app: forall (A : Type) (B : Type) (cond : bool) (a b : A -> B) (c : A), 10 | (if cond then a else b) c = if cond then a c else b c. 11 | Proof. intros A B cond a b c. destruct cond ; reflexivity. Qed. 12 | 13 | Definition eq_ascii (a1 a2 : ascii) := 14 | match a1, a2 with 15 | | Ascii b1 b2 b3 b4 b5 b6 b7 b8, Ascii c1 c2 c3 c4 c5 c6 c7 c8 => 16 | (eqb b1 c1) && (eqb b2 c2) && (eqb b3 c3) && (eqb b4 c4) && 17 | (eqb b5 c5) && (eqb b6 c6) && (eqb b7 c7) && (eqb b8 c8) 18 | end. 19 | 20 | Theorem eq_ascii_correct: forall (a b : ascii), 21 | eq_ascii a b = eq_ascii b a. 22 | Proof. intros a. destruct a. destruct b. 23 | - destruct b0, b1, b2, b3, b4, b5, b6 ; reflexivity. 24 | - destruct b0, b1, b2, b3, b4, b5, b6 ; reflexivity. Qed. 25 | 26 | Theorem eq_ascii_a_a: forall (a : ascii), 27 | eq_ascii a a = true. 28 | Proof. destruct a. 29 | - destruct b, b0, b1, b2, b3, b4, b5, b6 ; reflexivity. Qed. 30 | 31 | Fixpoint eq_string (s1 s2 : string) := 32 | match s1, s2 with 33 | | EmptyString, EmptyString => true 34 | | String x1 s1, String x2 s2 => eq_ascii x1 x2 && eq_string s1 s2 35 | | _, _ => false 36 | end. 37 | 38 | Theorem eq_string_correct: forall (a b : string), 39 | eq_string a b = eq_string b a. 40 | Proof. intros a. induction a. 41 | - destruct b ; reflexivity. 42 | - induction b. 43 | + simpl. reflexivity. 44 | + simpl. rewrite IHa. rewrite eq_ascii_correct. reflexivity. Qed. 45 | 46 | Theorem eq_string_s_s: forall (s : string), 47 | eq_string s s = true. 48 | Proof. induction s. 49 | - reflexivity. 50 | - simpl. rewrite IHs. rewrite eq_ascii_a_a. reflexivity. Qed. 51 | 52 | Fixpoint ble_nat n m := 53 | match n, m with 54 | | O, _ => true 55 | | S n', O => false 56 | | S n', S m' => ble_nat n' m' 57 | end. 58 | 59 | Lemma eqb_eq': forall (a b : bool), 60 | eqb a b = true -> a = b. 61 | Proof. apply eqb_true_iff. Qed. 62 | 63 | Hint Resolve eqb_eq'. 64 | 65 | (* 66 | Hint Resolve -> eqb_true_iff. 67 | Check eqb_true_iff. *) 68 | 69 | (* == firstorder *) 70 | Ltac break_conjs := repeat match goal with 71 | | H : _ /\ _ |- _ => inversion H; subst; clear H 72 | end. 73 | 74 | Check eqb_true_iff. 75 | Lemma eq_ascii_refl: forall (z y x : ascii), 76 | eq_ascii x y = true -> eq_ascii x z = eq_ascii y z. 77 | Proof with auto. induction z. 78 | - destruct y. 79 | * destruct x. simpl. intros. repeat rewrite andb_true_iff in H. 80 | break_conjs. 81 | replace b7 with b15 ... (* ... == "; auto" *) 82 | replace b8 with b16 ... 83 | replace b9 with b17 ... 84 | replace b10 with b18 ... 85 | replace b11 with b19 ... 86 | replace b12 with b20 ... 87 | replace b13 with b21 ... 88 | replace b14 with b22 ... Qed. 89 | 90 | Lemma eq_ascii_refl_false: forall (a a0 a1 : ascii), 91 | eq_ascii a a0 = false 92 | -> eq_ascii a a1 = true 93 | -> eq_ascii a0 a1 = false. 94 | Proof. intros. rewrite eq_ascii_refl with (y := a1) in H. rewrite eq_ascii_correct. 95 | apply H. apply H0. Qed. 96 | 97 | Theorem eq_string_refl: forall (c b a : string), 98 | eq_string a b = true -> eq_string a c = eq_string b c. 99 | Proof. induction c. 100 | - destruct b. 101 | * destruct a. reflexivity. intros. inversion H. 102 | * intros. destruct a0. inversion H. reflexivity. 103 | - destruct b. 104 | * intros. destruct a0. reflexivity. inversion H. 105 | * intros. destruct a1. inversion H. simpl. simpl in H. rewrite andb_true_iff in H. 106 | inversion H. rewrite IHc with (a := a2) (b := b). 2: { apply H1. } 107 | rewrite eq_ascii_refl with (x := a1) (y := a0). 2: { apply H0. } 108 | reflexivity. Qed. 109 | 110 | Lemma eq_string_refl_false: forall (s s0 s1 : string), 111 | eq_string s s0 = false 112 | -> eq_string s s1 = true 113 | -> eq_string s0 s1 = false. 114 | Proof. induction s. 115 | - destruct s0. 116 | * intros. destruct s1. inversion H. reflexivity. 117 | * intros. destruct s1. reflexivity. inversion H0. 118 | - destruct s0. 119 | * intros. destruct s1. inversion H0. reflexivity. 120 | * intros. destruct s1. 121 | + reflexivity. 122 | + simpl. simpl in H. simpl in H0. rewrite andb_true_iff in H0. inversion H0. 123 | rewrite eq_string_correct. rewrite eq_string_refl with (a := s1) (c := s0) (b := s). 124 | 2: { rewrite eq_string_correct. apply H2. } 125 | destruct (eq_string s s0). 2: { rewrite andb_comm. reflexivity. } 126 | rewrite andb_comm. simpl. rewrite andb_comm in H. simpl in H. 127 | rewrite eq_ascii_refl_false with (a := a). reflexivity. apply H. apply H1. Qed. 128 | -------------------------------------------------------------------------------- /proofs/ListSet.v: -------------------------------------------------------------------------------- 1 | Require Import Common. 2 | Require Import Coq.Bool.Bool. 3 | Require Import Coq.Arith.EqNat. 4 | 5 | Inductive set (A : Type) : Type := 6 | | S0 : set A 7 | | St : A -> set A -> set A. 8 | 9 | (* Is the value v syntactically equivalent to something in the set: *) 10 | Fixpoint set_mem (A : Type) (eq_A : A -> A -> bool) (vs : set A) (v : A) : bool := 11 | match vs with 12 | | S0 _ => false 13 | | St _ v2 vs' => if eq_A v v2 then true else (set_mem A eq_A) vs' v 14 | end. 15 | 16 | Fixpoint set_add (A : Type) (eq_A : A -> A -> bool) (vs : set A) (v : A) : set A := 17 | match vs with 18 | | S0 _ => St _ v (S0 _) 19 | | St _ v2 vs' => if eq_A v v2 then St _ v vs' else St _ v2 ((set_add A eq_A) vs' v) 20 | end. 21 | 22 | (* Adding a value and then checking for its membership always returns true: *) 23 | Theorem set_add_correct: forall (A : Type) (eq_A : A -> A -> bool) (vs : set A) (v : A), 24 | eq_A v v = true -> (set_mem A eq_A) ((set_add A eq_A) vs v) v = true. 25 | Proof. intros A eq_A vs. induction vs. 26 | - intros v H. simpl. rewrite H. reflexivity. 27 | - intros v0. simpl. intros H. destruct (eq_A v0 a). 28 | * simpl. rewrite H. reflexivity. 29 | * simpl. rewrite IHvs. destruct (eq_A v0 a) ; reflexivity. apply H. Qed. 30 | 31 | Fixpoint set_rem (A : Type) (eq_A : A -> A -> bool) (vs : set A) (v : A) : set A := 32 | match vs with 33 | | S0 _ => S0 _ 34 | | St _ v2 vs' => if eq_A v v2 then (set_rem A eq_A) vs' v else St _ v2 ((set_rem A eq_A) vs' v) 35 | end. 36 | 37 | Theorem set_rem_correct: forall (A : Type) (eq_A : A -> A -> bool) (vs : set A) (v : A), 38 | eq_A v v = true -> (set_mem A eq_A) ((set_rem A eq_A) vs v) v = false. 39 | Proof. intros A eq_A vs. induction vs. 40 | - intros v H. reflexivity. 41 | - intros v H. simpl. rewrite if_f_app. rewrite if_app. simpl. 42 | rewrite IHvs. destruct (eq_A v a) ; reflexivity. apply H. Qed. 43 | 44 | (* set_fold_left {A B:Type} : (B -> A -> B) -> set A -> B -> B *) 45 | Fixpoint set_fold_left (A : Type) (B : Type) (fncn : B -> A -> B) (s : set A) (init : B) : B := 46 | match s with 47 | | S0 _ => init 48 | | St _ v s' => set_fold_left A B fncn s' (fncn init v) 49 | end. 50 | 51 | Compute (set_add nat beq_nat). 52 | 53 | Compute (set_fold_left _ _ (set_add _ beq_nat) (St _ 3 (St _ 4 (S0 _))) (St _ 42 (St _ 43 (S0 _)))). 54 | 55 | Theorem set_fold_left_correct: forall (A : Type) (eq_A : A -> A -> bool) (s : set A), 56 | (forall v, eq_A v v = true) -> set_fold_left A (set A) (set_add A eq_A) (S0 _) s = s. 57 | Proof. intros. destruct s ; auto. Qed. 58 | 59 | Fixpoint union2 (A : Type) (eq_A : A -> A -> bool) (a b : set A) : set A := 60 | match a with 61 | | S0 _ => b 62 | | St _ v s' => (union2 A eq_A) s' ((set_add A eq_A) b v) 63 | end. 64 | 65 | Fixpoint set_init (A : Type) (eq_A : A -> A -> bool) (fncn : nat -> A) (n : nat) : set A := 66 | match n with 67 | | O => St _ (fncn O) (S0 _) 68 | | S n' => set_add _ eq_A (set_init _ eq_A fncn n') (fncn n) 69 | end. 70 | 71 | Fixpoint set_map (A : Type) (B : Type) (fncn : A -> B) (input : set A) : set B := 72 | match input with 73 | | S0 _ => S0 _ 74 | | St _ x xs => St _ (fncn x) (set_map _ _ fncn xs) 75 | end. 76 | 77 | Definition singleton (V : Type) (v : V) : set V := St _ v (S0 _). 78 | 79 | Fixpoint filter (V : Type) (pred : V -> bool) (vs : set V) : set V := 80 | match vs with 81 | | S0 _ => S0 _ 82 | | St _ v vs' => if pred v then St _ v (filter _ pred vs') else filter _ pred vs' 83 | end. 84 | 85 | (* Union from [0,m] of the given function: *) 86 | Fixpoint unionAll (A : Type) (eq_A : A -> A -> bool) (m : nat) (fncn : nat -> set A) : set A := 87 | match m with 88 | | O => fncn O 89 | | S m' => union2 _ eq_A (fncn m) (unionAll _ eq_A m' fncn) 90 | end. 91 | 92 | Fixpoint all_satisfy (X : Type) (pred : X -> bool) (xs : set X) : bool := 93 | set_fold_left X bool (fun b x => b && pred x) xs true. 94 | 95 | Compute all_satisfy nat (beq_nat 3) (St _ 3 (St _ 3 (St _ 4 (S0 _)))). 96 | 97 | Lemma set_mem_map_preserve: forall (X : Type) (eq_X : X -> X -> bool) (xs : set X) 98 | (x2 : X) (fncn : X -> X) (prop : X -> Prop), 99 | (forall (x1 : X), set_mem X eq_X xs x1 = true -> prop x1) (* induction hypothesis *) (* YES *) 100 | -> (forall (i : X), eq_X i i = true) (* eq_X is reflexive *) (* YES *) 101 | -> (forall (i j : X), eq_X i j = true -> prop i = prop j) (* eq_X is prop-preserving *) (* YES *) 102 | -> (forall (i : X), prop (fncn i) = prop i) (* fncn is prop-preserving *) (* YES *) 103 | -> (set_mem X eq_X (set_map _ _ fncn xs) x2 = true -> prop x2). (* then map is leaves preserving *) (* OBV *) 104 | Proof. induction xs. 105 | - simpl. intros. inversion H3. 106 | - simpl. intro x2. intro fncn. intro prop. intro IHx1. intro eq_refl. 107 | (* intro eq_symm. *) intro eq_prop_pres. (* intro eq_prop_pres_neg. *) intro fncn_prop_pres. 108 | (* intro eq_X_fncn_pres. *) intro set_mem_prop. destruct (eq_X x2 (fncn a)) eqn:X2_is_fna. 109 | (* x2 = fncn(a) *) 110 | + rewrite eq_prop_pres with (j := fncn a). rewrite -> fncn_prop_pres. apply IHx1. rewrite eq_refl. reflexivity. 111 | apply X2_is_fna. 112 | (* x2 /= fncn(a) *) 113 | + apply IHxs with (fncn := fncn). 114 | * intro x1. intro set_mem_x1. apply IHx1. rewrite -> set_mem_x1. destruct (eq_X x1 a) ; reflexivity. 115 | * apply eq_refl. 116 | * apply eq_prop_pres. 117 | * apply fncn_prop_pres. 118 | * apply set_mem_prop. Qed. 119 | -------------------------------------------------------------------------------- /proofs/README.md: -------------------------------------------------------------------------------- 1 | # Proofs 2 | 3 | Warning: The proofs in here are outdated, they do not seem to compile at present 4 | due to newer libraries / dependencies, and the semantics as implemented in Coq 5 | in this directory likely have a semantic bug: one which does not meaningfully 6 | affect the correctness of the size_correctness proof defined herein. To 7 | view the updated semantics look at `app/semantics.hs` in the root of this 8 | repository, or checkout the [preprint](https://cronburg.com/papers/floorplan19.pdf) 9 | of the associated paper. 10 | 11 | -------------------------------------------------------------------------------- /proofs/Set.v: -------------------------------------------------------------------------------- 1 | Require Import Common. 2 | 3 | Inductive set (A : Type) : Type := 4 | | S0 : set A 5 | | St : A -> set A -> set A. 6 | 7 | (* Is the value v syntactically equivalent to something in the set: *) 8 | Fixpoint set_mem (A : Type) (eq_A : A -> A -> bool) (vs : set A) (v : A) : bool := 9 | match vs with 10 | | S0 _ => false 11 | | St _ v2 vs' => if eq_A v v2 then true else (set_mem A eq_A) vs' v 12 | end. 13 | 14 | Fixpoint set_add (A : Type) (eq_A : A -> A -> bool) (vs : set A) (v : A) : set A := 15 | match vs with 16 | | S0 _ => St _ v (S0 _) 17 | | St _ v2 vs' => if eq_A v v2 then St _ v vs' else St _ v2 ((set_add A eq_A) vs' v) 18 | end. 19 | 20 | (* Adding a value and then checking for its membership always returns true: *) 21 | Theorem set_add_correct: forall (A : Type) (eq_A : A -> A -> bool) (vs : set A) (v : A), 22 | eq_A v v = true -> (set_mem A eq_A) ((set_add A eq_A) vs v) v = true. 23 | Proof. intros A eq_A vs. induction vs. 24 | - intros v H. simpl. rewrite H. reflexivity. 25 | - intros v0. simpl. intros H. destruct (eq_A v0 a). 26 | * simpl. rewrite H. reflexivity. 27 | * simpl. rewrite IHvs. destruct (eq_A v0 a) ; reflexivity. apply H. Qed. 28 | 29 | Fixpoint set_rem (A : Type) (eq_A : A -> A -> bool) (vs : set A) (v : A) : set A := 30 | match vs with 31 | | S0 _ => S0 _ 32 | | St _ v2 vs' => if eq_A v v2 then (set_rem A eq_A) vs' v else St _ v2 ((set_rem A eq_A) vs' v) 33 | end. 34 | 35 | Theorem set_rem_correct: forall (A : Type) (eq_A : A -> A -> bool) (vs : set A) (v : A), 36 | eq_A v v = true -> (set_mem A eq_A) ((set_rem A eq_A) vs v) v = false. 37 | Proof. intros A eq_A vs. induction vs. 38 | - intros v H. reflexivity. 39 | - intros v H. simpl. rewrite if_f_app. rewrite if_app. simpl. 40 | rewrite IHvs. destruct (eq_A v a) ; reflexivity. apply H. Qed. 41 | 42 | Fixpoint set_fold_left (A : Type) (B : Type) (fncn : B -> A -> B) (s : set A) (init : B) : B := 43 | match s with 44 | | S0 _ => init 45 | | St _ v s' => set_fold_left A B fncn s' (fncn init v) 46 | end. 47 | 48 | Fixpoint union2 (A : Type) (eq_A : A -> A -> bool) (a b : set A) : set A := 49 | match a with 50 | | S0 _ => b 51 | | St _ v s' => (union2 A eq_A) s' ((set_add A eq_A) b v) 52 | end. 53 | 54 | Fixpoint set_init (A : Type) (eq_A : A -> A -> bool) (fncn : nat -> A) (n : nat) : set A := 55 | match n with 56 | | O => St _ (fncn O) (S0 _) 57 | | S n' => set_add _ eq_A (set_init _ eq_A fncn n') (fncn n) 58 | end. 59 | 60 | Fixpoint set_map (A : Type) (B : Type) (fncn : A -> B) (input : set A) : set B := 61 | match input with 62 | | S0 _ => S0 _ 63 | | St _ x xs => St _ (fncn x) (set_map _ _ fncn xs) 64 | end. 65 | 66 | Definition singleton (V : Type) (v : V) : set V := St _ v (S0 _). 67 | 68 | Fixpoint filter (V : Type) (pred : V -> bool) (vs : set V) : set V := 69 | match vs with 70 | | S0 _ => S0 _ 71 | | St _ v vs' => if pred v then St _ v (filter _ pred vs') else filter _ pred vs' 72 | end. 73 | -------------------------------------------------------------------------------- /pygmentize/.gitignore: -------------------------------------------------------------------------------- 1 | pygments/ 2 | inst/ 3 | -------------------------------------------------------------------------------- /pygmentize/Makefile: -------------------------------------------------------------------------------- 1 | 2 | PWD := $(shell pwd) 3 | PYG := $(PWD)/pygments 4 | PYGEXE := $(PYG)/inst/bin/pygmentize 5 | export PATH := $(PWD)/inst/bin:$(PATH) 6 | 7 | PYTHON_VERSION := $(shell python --version | cut -f2 -d' ' | cut -f1,2 -d'.') 8 | export PYTHONPATH := $(PWD)/inst/lib/python$(PYTHON_VERSION)/site-packages 9 | 10 | all: $(PYGEXE) 11 | 12 | clean: 13 | rm -rf $(PYG)/{build,dist,inst} 14 | 15 | $(PYG): 16 | git clone --branch 2.2.0 https://github.com/pygments/pygments.git 17 | 18 | $(PYG)/pygments/lexers/floorplan.py: floorplan.py $(PYG) 19 | cp $< $@ 20 | 21 | $(PYGEXE): $(PYG)/pygments/lexers/floorplan.py 22 | mkdir -p inst 23 | cd $(PYG) && make mapfiles 24 | mkdir -p $(PYTHONPATH) 25 | cd $(PYG) && python setup.py install --prefix=$(PWD)/inst 26 | 27 | -------------------------------------------------------------------------------- /pygmentize/README.md: -------------------------------------------------------------------------------- 1 | # Floorplan lexer for pygmentize 2 | 3 | The Makefile here clones the [pygments repo](https://github.com/pygments/pygments) 4 | and sets up the Floorplan lexer for use with the `pygmentize` command. To build 5 | and pygmentize do the following: 6 | 7 | ```bash 8 | $ cd pygmentize && make all 9 | ... 10 | $ export PYTHONPATH=`pwd`/inst/lib/python$(python --version | cut -f2 -d' ' | cut -f1,2 -d'.')/site-packages 11 | $ ./inst/bin/pygmentize -l floorplan ../examples/immix/src/heap/layout.flp 12 | ``` 13 | 14 | -------------------------------------------------------------------------------- /pygmentize/floorplan.py: -------------------------------------------------------------------------------- 1 | from pygments.lexer import RegexLexer 2 | from pygments.token import * 3 | import re 4 | 5 | __all__ = ['FloorplanLexer'] 6 | 7 | class FloorplanLexer(RegexLexer): 8 | name = 'Floorplan' 9 | aliases = ['floorplan'] 10 | filenames = ['*.flp'] 11 | 12 | flags = re.MULTILINE | re.UNICODE 13 | 14 | reserved = ( 'ptr', 'flags', '_', 'Counter', 'bit' 15 | , 'bits', 'byte', 'bytes', 'sizeof', 'List' 16 | , 'word', 'words', 'page', 'pages' 17 | , 'enum', 'union', 'seq', 'choice') 18 | 19 | tokens = { 20 | 'root': [ 21 | # Whitespace: 22 | (r'\s+', Text), 23 | # Comments: 24 | (r'\/\/.*$', Comment.Single), 25 | (r'/\*', Comment.Multiline, 'comment'), 26 | 27 | # Identifiers: 28 | (r'(%s)\b' % '|'.join(reserved), Keyword.Reserved), 29 | (r'[A-Z]\w*', Keyword.Type), 30 | (r'[a-z_]\w*', Name), 31 | 32 | # Operators: 33 | (r'({|}|#|@|\^|=>|\.\.\.|\.|->|:|,|=\||~\||\|)', Operator.Word), 34 | (r'[-+\*\/\[\]\<\>]', Operator), 35 | 36 | # Numeric Literals: 37 | (r'0[xX][\da-fA-F]+', Number.Hex), # hex literals 38 | (r'0[bB][01]+', Number.Bin), # binary literals 39 | (r'\d+', Number.Integer), # decimal literals 40 | 41 | # Special: 42 | (r'[\(\)]', Operator), # parentheses 43 | ], 44 | 'comment': [ 45 | # Multiline Comments 46 | (r'[^\*/]+', Comment.Multiline), 47 | (r'/\*', Comment.Multiline, '#push'), 48 | (r'\*/', Comment.Multiline, '#pop'), 49 | (r'[\*/]', Comment.Multiline), 50 | ], 51 | } 52 | 53 | -------------------------------------------------------------------------------- /src/Language/Floorplan.hs: -------------------------------------------------------------------------------- 1 | module Language.Floorplan 2 | ( module Language.Floorplan.Syntax 3 | , module Language.Floorplan.Parser 4 | , module Language.Floorplan.Token 5 | ) where 6 | import Language.Floorplan.Syntax 7 | import Language.Floorplan.Parser 8 | import Language.Floorplan.Token 9 | 10 | -------------------------------------------------------------------------------- /src/Language/Floorplan/C/Compiler.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE QuasiQuotes, TemplateHaskell, BangPatterns #-} 2 | module Language.Floorplan.C.Compiler 3 | ( genC, writeCFile 4 | ) where 5 | 6 | import System.IO (IOMode(..), openFile, hClose) 7 | import Data.Typeable (Typeable(..)) 8 | import Data.Text.Lazy.IO (hPutStr) 9 | import Data.Text.Lazy (pack) 10 | 11 | import Language.C.Quote 12 | import qualified Language.C.Parser as P 13 | import qualified Language.C.Parser.Tokens as T 14 | import qualified Language.C.Syntax as C 15 | 16 | import qualified Language.C.Pretty as PrettyC 17 | import Text.PrettyPrint.Mainland (prettyLazyText) 18 | import Text.PrettyPrint.Mainland.Class (ppr) 19 | 20 | import Language.Floorplan.Core.Syntax 21 | 22 | import Data.Bifunctor ( bimap ) 23 | import Data.Functor ( ($>) ) 24 | import Data.Ord (comparing) 25 | import Data.List (sortBy, nub, inits, intersperse) 26 | import Data.Char (toUpper, toLower) 27 | import Data.Maybe (isJust, fromMaybe, catMaybes) 28 | import Data.Bits 29 | import qualified Debug.Trace as D 30 | 31 | import Language.C.Quote.C 32 | import qualified Language.C.Syntax as S 33 | import Data.Loc -- (SrcLoc(..), noLoc) 34 | 35 | import Text.RE.TDFA.String (RE(..), (?=~), matched) 36 | 37 | -- TODO: Introduce spans based on position in these Haskell files. 38 | fakeLoc = noLoc --Span (Position 0 0 0) (Position 0 0 0) 39 | fL = fakeLoc 40 | 41 | justName :: [([NameID], BaseExp)] -> BaseExp -> Maybe ([NameID], BaseExp) 42 | justName [] (n ::: e) = Just ([n], e) 43 | justName ((ns,_):_) (n ::: e) = Just (n : ns, e) 44 | justName _ _ = Nothing 45 | 46 | justNameExp :: BaseExp -> Maybe (NameID, BaseExp) 47 | justNameExp (n ::: e) = Just (n, e) 48 | justNameExp _ = Nothing 49 | 50 | -- | Get all of the unique name identifiers in the spec and their 51 | -- corresponding subexpression. 52 | findNames :: [BaseExp] -> [([NameID], BaseExp)] 53 | findNames = nub . concatMap (accumTopDown justName) 54 | 55 | mkInitConst s = (Nothing, ExpInitializer (Const s fL) fL) 56 | 57 | mkStrConst t = S.StringConst ['"' : t ++ "\""] t fakeLoc 58 | 59 | mkPoundDefs' :: Int -> [NameID] -> [Definition] 60 | mkPoundDefs' i (n:ns) = EscDef ("#define " ++ n ++ " ((unsigned int)" ++ show i ++ ")") fL : (mkPoundDefs' (i+1) ns) 61 | mkPoundDefs' _ [] = [] 62 | 63 | mkPoundDefs = mkPoundDefs' 0 64 | 65 | -- TODO: produce a warning if there's a regex which doesn't match 66 | -- on any of the output types. 67 | 68 | doFilterOut :: [String] -> [RE] -> [Maybe String] 69 | doFilterOut types res0 = let 70 | -- | Does the given type match any of the provided regular expressions? 71 | -- If so, remove it. 72 | dFO :: String -> [RE] -> [Maybe String] 73 | dFO t [] = [Just t] -- No matches. Keep it. 74 | dFO t (re:res) 75 | | matched (t ?=~ re) = [Nothing] -- It matched. Remove it. 76 | | otherwise = dFO t res -- Check the rest of the regular expressions. 77 | in concatMap (\f -> f res0) (map dFO types) 78 | 79 | -- | Wholly filtered out types (ones resulting a FLP_ALL_* decl with an empty RHS of the #define) 80 | -- probably imply that there were unused types / dead code in the FLP spec. Either need to 81 | -- report a warning in such a case, or write a preprocessing analysis pass that checks that this 82 | -- won't happen. 83 | mkAllPrefixes :: [BaseExp] -> [RE] -> [S.Definition] 84 | mkAllPrefixes bes res = let 85 | 86 | ns = findNames bes 87 | uniques = nub $ concatMap fst ns 88 | 89 | -- Tuples of the constructed type identifier and the list of names from which it was constructed. 90 | types0 :: [String] 91 | types0 = map mkTypeIdentifier ns 92 | 93 | findFor :: String -> [(([NameID], BaseExp), Maybe String)] -> [String] 94 | findFor u [] = [] 95 | findFor u ((tuple@(n_qual, _), Just _):ns0) -- Not filtered out - make and keep the identifier if it involves the type 'u'. 96 | | u `elem` n_qual = mkTypeIdentifier tuple : findFor u ns0 97 | | otherwise = findFor u ns0 98 | findFor u (((_, _), Nothing):ns0) = findFor u ns0 -- This particular name is filtered out, as indicated by the Nothing 99 | 100 | mkAll :: (String, [String]) -> S.Definition 101 | mkAll (unique_ty, paths) 102 | = EscDef ("#define FLP_ALL_" ++ unique_ty ++ " " ++ (concat . intersperse ", " . map ("FLP_" ++) $ paths)) fL 103 | 104 | in map mkAll (zip uniques (map (\f -> f $ zip ns (doFilterOut types0 res)) (map findFor uniques))) 105 | 106 | -- | (["A", "B", "C"], _) becomes "C_B_A" 107 | mkTypeIdentifier :: ([NameID], BaseExp) -> String 108 | mkTypeIdentifier = concat . intersperse "_" . reverse . fst 109 | 110 | genC :: [BaseExp] -> [RE] -> [S.Definition] 111 | genC bes res = let 112 | types0 = map mkTypeIdentifier (findNames bes) 113 | types = "FLP_UNMAPPED" : (map ("FLP_" ++) $ catMaybes $ doFilterOut types0 res) 114 | num_types = [EscDef ("#define __FLP_NUM_VALID_TYPES ((unsigned int)" ++ show (length types) ++ ")") fL] 115 | xs = map (mkInitConst . mkStrConst) types 116 | pound_defs = mkPoundDefs types 117 | initializer = CompoundInitializer xs fL 118 | all_prefixes = mkAllPrefixes bes res 119 | in pound_defs ++ num_types ++ [cunit| 120 | static const char* const __FLP_TYPES[] = $init:initializer; 121 | |] ++ all_prefixes 122 | --"PC_UNMAPPED", "PC_SOMETHING_ELSE" }; 123 | 124 | -- TODO: work with Text instead of String 125 | 126 | -- writeSourceFile :: (Monoid a, Typeable a) => Handle -> SourceFile a -> IO () 127 | -- | The pair of strings are the header and footer and to append 128 | writeCFile :: FilePath -> (String, String) -> [S.Definition] -> IO () 129 | writeCFile outdir (header, footer) c_code = do 130 | fd <- openFile outdir WriteMode 131 | hPutStr fd $ pack header 132 | hPutStr fd (prettyLazyText 160 $ ppr c_code) -- 160-character wide terminal viewing width of source files (it's the 2020 people) 133 | hPutStr fd $ pack footer 134 | hClose fd 135 | 136 | -------------------------------------------------------------------------------- /src/Language/Floorplan/Core/Compiler.hs: -------------------------------------------------------------------------------- 1 | module Language.Floorplan.Core.Compiler where 2 | import Language.Floorplan.Core.Syntax hiding (accum) 3 | import Language.Floorplan.Syntax 4 | import qualified Data.Map.Strict as M 5 | import Data.List (sort) 6 | import Data.Maybe (fromJust, isJust) 7 | import Debug.Trace as D 8 | 9 | getNames :: Demarc -> [String] 10 | getNames = let 11 | gN (Field n _) = Just n 12 | gN (Layer { name = n }) = Just n 13 | gN _ = Nothing 14 | in accum gN 15 | 16 | -- | This can be done before or after grafting phase. It checks that every @Layer@ and @Field@ 17 | -- has a globally unique name, reporting an error when the name is not unique. 18 | checkUniqNames :: [Demarc] -> [String] 19 | checkUniqNames ds = 20 | let ns = foldr (\a b -> getNames a ++ b) [] ds 21 | 22 | -- Assumes the input list is already sorted (duplicates are adjacent) 23 | getDups [] = [] 24 | getDups (x:[]) = [] 25 | getDups (x:y:ys) 26 | | x == y = y : getDups (dropWhile (== y) ys) 27 | | otherwise = getDups (y:ys) 28 | 29 | in getDups $ sort ns 30 | 31 | countGrafting :: [Demarc] -> Int 32 | countGrafting = let 33 | fn (Graft{}) = True 34 | fn _ = False 35 | in sum . map (countMatches fn) 36 | 37 | -- | How many bytes are required for an Enum containing the given fields. 38 | -- This computes ceiling(log_2(length(flags)) / 8). 39 | enumBytes :: [FlagID] -> Int 40 | enumBytes fs 41 | | len == 1 = 1 42 | | otherwise = (bytes 1) 43 | where len = length fs 44 | bytes acc 45 | | len <= acc = 0 46 | | otherwise = 1 + (bytes (acc * 256)) 47 | 48 | -- | TODO: parametrized delta_prim for architecture-dependent types 49 | delta_prim :: Primitive -> Int 50 | delta_prim Bit = 1 51 | delta_prim Byte = 8 52 | delta_prim Word = 8 * (delta_prim Byte) 53 | delta_prim Page = 4096 * (delta_prim Byte) 54 | 55 | pow :: Int -> Int -> Int 56 | pow b e 57 | | e < 0 = error "exponentiation with negative exponent" 58 | | e == 0 && b == 0 = error "indeterminate exponent" 59 | | e == 0 = 1 60 | | e > 0 = b * (pow b (e - 1)) 61 | 62 | -- | Computes over arithmetic expressions in terms of literals 63 | delta_lit :: LitArith -> Int 64 | delta_lit (Plus l r) = (+) (delta_lit l) (delta_lit r) 65 | delta_lit (Minus l r) = (-) (delta_lit l) (delta_lit r) 66 | delta_lit (Times l r) = (*) (delta_lit l) (delta_lit r) 67 | delta_lit (Div l r) = quot (delta_lit l) (delta_lit r) 68 | delta_lit (Exponent b e) = pow (delta_lit b) (delta_lit e) 69 | delta_lit (Lit l) = l 70 | 71 | -- | Computes number of bits 72 | delta :: SizeArith -> Int 73 | delta (SizePlus l r) = (+) (delta l) (delta r) 74 | delta (SizeMinus l r) = (-) (delta l) (delta r) 75 | delta (SizeLit Nothing p) = delta_prim p 76 | delta (SizeLit (Just lit) p) = (delta_lit lit) * (delta_prim p) 77 | 78 | delta_bits = delta 79 | 80 | bits2bytesUP v 81 | | v == 0 = 0 82 | | v <= 8 = 1 83 | | otherwise = 1 + bits2bytesUP (v - 8) 84 | 85 | -- | Rounds up @SizeArith@ to the nearest whole byte. 86 | delta_bytes :: SizeArith -> Int 87 | delta_bytes sz = bits2bytesUP (delta sz) 88 | 89 | fresh :: Demarc -> FormalID 90 | fresh d = head (dropWhile (flip elem $ free_vars d) $ map (("var_" ++) . show) [0..]) 91 | 92 | compile :: Demarc -> BaseExp 93 | compile (Enum fs) = Attr (BaseType $ EnumBT fs) (Prim $ enumBytes fs) 94 | compile (Bits fs) = let fs' = zip (map fst fs) $ map (delta_bits . snd) fs 95 | in Attr (BaseType $ BitsBT fs') (Prim $ bits2bytesUP $ sum $ map snd fs') 96 | compile (Union []) = Prim 0 97 | compile (Union (d:ds)) = foldl (:||) (compile d) $ map compile ds 98 | compile (Seq []) = Prim 0 99 | compile (Seq (d:ds)) = foldl (:+) (compile d) $ map compile ds 100 | compile (PtrF field) = Attr (BaseType $ PtrBT field) $ compile (Blob $ SizeLit Nothing Word) 101 | compile (PtrL layer) = Attr (BaseType $ PtrBT layer) $ compile (Blob $ SizeLit Nothing Word) 102 | compile (Blob sz) = Attr (BaseType $ SizeBT sz) $ Prim $ delta_bytes sz 103 | compile (Field f d) = (:::) f (compile d) 104 | compile (Pound d) = let f = fresh d 105 | in Exists f $ f :# (compile d) 106 | compile (Repetition f d) = f :# (compile d) 107 | compile l@(Layer 108 | { name = n 109 | , formals = fs 110 | , magnitude = m 111 | , alignment = a 112 | , magAlign = ma 113 | , contains = cs 114 | , rhs = d 115 | }) = n ::: (exists $ mag $ align $ contains $ compile d) 116 | where exists :: BaseExp -> BaseExp 117 | exists e 118 | | null fs = e 119 | | otherwise = foldr Exists (Exists (head fs) e) (tail fs) 120 | mag 121 | | isJust m = Con (delta_bytes $ fromJust m) 122 | | isJust ma = Con (delta_bytes $ fromJust ma) 123 | | otherwise = id 124 | align 125 | | isJust a = (flip (:@)) (delta_bytes $ fromJust a) 126 | | isJust ma = (flip (:@)) (delta_bytes $ fromJust ma) 127 | | otherwise = id 128 | contains :: BaseExp -> BaseExp 129 | contains e 130 | | null cs = e 131 | | otherwise = foldr Attr (Attr (Contains $ head cs) e) (map Contains $ tail cs) 132 | compile (Graft _) = error "Grafting is illegal during the compile phase." 133 | 134 | -------------------------------------------------------------------------------- /src/Language/Floorplan/Core/Syntax.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE ScopedTypeVariables #-} 2 | module Language.Floorplan.Core.Syntax where 3 | 4 | import Data.Maybe (maybeToList) 5 | 6 | import Language.Floorplan.Syntax (LayerID, FormalID, FlagID, FieldID, SizeArith(..)) 7 | 8 | type Nat = Int -- >= 0 9 | type Align = Int -- >= 1 10 | 11 | type ExistsID = String -- ^ Formal identifiers (bound by Exists) 12 | type NameID = String -- ^ Names of bound layers and fields 13 | 14 | data Exp a 15 | = Prim Nat -- ^ Primitive number of bytes 16 | | Con Nat (Exp a) -- ^ Constrained 17 | | (:@) (Exp a) Align -- ^ Alignment 18 | | (:+) (Exp a) (Exp a) -- ^ Sequencing 19 | | (:||) (Exp a) (Exp a) -- ^ Union / alternation 20 | | (:::) NameID (Exp a) -- ^ Layer (and field) name binding 21 | | Exists ExistsID (Exp a) -- ^ Formal name binding 22 | | (:#) ExistsID (Exp a) -- ^ Repetitions 23 | | Attr a (Exp a) -- ^ Extensible attributes 24 | deriving (Eq, Ord, Show) 25 | 26 | data Attribute ty 27 | = Contains NameID 28 | | BaseType ty 29 | deriving (Eq, Ord, Show) 30 | 31 | data BaseType 32 | = EnumBT [FlagID] 33 | | BitsBT [(NameID, Int)] 34 | | PtrBT NameID 35 | | SizeBT SizeArith 36 | deriving (Eq, Ord, Show) 37 | 38 | -- | Default core expression type for FLP compiler targeting Rust library 39 | -- (with contains(...) attibutes): 40 | type BaseExp = Exp (Attribute BaseType) 41 | 42 | mTL :: Maybe a -> [a] 43 | mTL = maybeToList 44 | 45 | accumTopDown' :: [a] -> ([a] -> Exp b -> Maybe a) -> Exp b -> [a] 46 | accumTopDown' as fn e@(Prim{}) = mTL (fn as e) 47 | accumTopDown' as fn e1@(Con _ e2) = mTL (fn as e1) ++ accumTopDown' (mTL (fn as e1) ++ as) fn e2 48 | accumTopDown' as fn e1@(e2 :@ _) = mTL (fn as e1) ++ accumTopDown' (mTL (fn as e1) ++ as) fn e2 49 | accumTopDown' as fn e1@(e2 :+ e3) = mTL (fn as e1) ++ accumTopDown' (mTL (fn as e1) ++ as) fn e2 ++ accumTopDown' (mTL (fn as e1) ++ as) fn e3 50 | accumTopDown' as fn e1@(e2 :|| e3) = mTL (fn as e1) ++ accumTopDown' (mTL (fn as e1) ++ as) fn e2 ++ accumTopDown' (mTL (fn as e1) ++ as) fn e3 51 | accumTopDown' as fn e1@(_ ::: e2) = mTL (fn as e1) ++ accumTopDown' (mTL (fn as e1) ++ as) fn e2 52 | accumTopDown' as fn e1@(Exists _ e2) = mTL (fn as e1) ++ accumTopDown' (mTL (fn as e1) ++ as) fn e2 53 | accumTopDown' as fn e1@(_ :# e2) = mTL (fn as e1) ++ accumTopDown' (mTL (fn as e1) ++ as) fn e2 54 | accumTopDown' as fn e1@(Attr _ e2) = mTL (fn as e1) ++ accumTopDown' (mTL (fn as e1) ++ as) fn e2 55 | 56 | -- | Accumulate the results of a function operating over each Demarc in the Demarc IR tree, 57 | -- where the function also takes a list of the results of the same function applied to 58 | -- each of the ancestors of the current Demarc IR node, with the list in order from least 59 | -- ancestor (closest) to greatest ancestor. 60 | accumTopDown = accumTopDown' [] 61 | 62 | -- | Accumulate the results of applying some function to 63 | -- every node in the Exp AST. 64 | accum :: (Exp a -> Maybe b) -> Exp a -> [b] 65 | accum fn e@(Prim{}) = maybeToList (fn e) 66 | accum fn e1@(Con _ e2) = maybeToList (fn e1) ++ accum fn e2 67 | accum fn e1@(e2 :@ _) = maybeToList (fn e1) ++ accum fn e2 68 | accum fn e1@(e2 :+ e3) = maybeToList (fn e1) ++ accum fn e2 ++ accum fn e3 69 | accum fn e1@(e2 :|| e3) = maybeToList (fn e1) ++ accum fn e2 ++ accum fn e3 70 | accum fn e1@(_ ::: e2) = maybeToList (fn e1) ++ accum fn e2 71 | accum fn e1@(Exists _ e2) = maybeToList (fn e1) ++ accum fn e2 72 | accum fn e1@(_ :# e2) = maybeToList (fn e1) ++ accum fn e2 73 | accum fn e1@(Attr _ e2) = maybeToList (fn e1) ++ accum fn e2 74 | 75 | countExpNodes :: Exp a -> Int 76 | countExpNodes e = length $ accum (const $ Just ()) e 77 | 78 | -- | Call the given function on all subexpressions. Good for fixedpoint 79 | -- functions calling on themselves in recursive case where they don't 80 | -- care which type they see. This is slightly dangerous in the case where 81 | -- something gets added to the core calculus. If this happens, PLEASE 82 | -- check all callers of this function to see if they should handle the 83 | -- new case personally. 84 | callSub :: (Exp a -> [b]) -> Exp a -> [b] 85 | callSub fn (Prim{}) = [] 86 | callSub fn (Con _ e) = fn e 87 | callSub fn (e :@ _) = fn e 88 | callSub fn (e1 :+ e2) = fn e1 ++ fn e2 89 | callSub fn (e1 :|| e2) = fn e1 ++ fn e2 90 | callSub fn (_ ::: e) = fn e 91 | callSub fn (Exists _ e) = fn e 92 | callSub fn (_ :# e) = fn e 93 | callSub fn (Attr _ e) = fn e 94 | 95 | plus :: Maybe Int -> Maybe Int -> Maybe Int 96 | plus a b = do 97 | a' <- a 98 | b' <- b 99 | return $ a' + b' 100 | 101 | -- | Conservatively computes the size of the given expression, 102 | -- returning Nothing when a fixed size isn't easily known. 103 | expSize :: Exp a -> Maybe Int 104 | expSize (Prim n) = return n 105 | expSize (Con n _) = return n 106 | expSize (e :@ _) = expSize e 107 | expSize (e1 :+ e2) = expSize e1 `plus` expSize e2 108 | expSize (e1 :|| e2) = 109 | let s1 = expSize e1 110 | s2 = expSize e2 111 | in if s1 == s2 then s1 else Nothing 112 | expSize (_ ::: e) = expSize e 113 | expSize (Exists _ e) = expSize e 114 | expSize (_ :# _) = Nothing -- Conservative assumption 115 | expSize (Attr _ e) = expSize e 116 | 117 | -- | Just like @accum@, but also tracks the number of bytes of memory 118 | -- that come before the subexpression for which we call the fncn. 119 | l2r :: forall a b. (Maybe Nat -> Exp a -> Maybe b) -> Exp a -> [b] 120 | l2r fn e' = let 121 | 122 | mTL :: Maybe Nat -> Exp a -> [b] 123 | mTL i = maybeToList . fn i 124 | 125 | lr :: Maybe Nat -> Exp a -> [b] 126 | lr i e@(Prim{}) = mTL i e 127 | lr i e1@(Con _ e2) = mTL i e1 ++ lr i e2 128 | lr i e1@(e2 :@ _) = mTL i e1 ++ lr i e2 129 | lr i e1@(e2 :+ e3) = mTL i e1 ++ (lr i e2 ++ lr (i `plus` expSize e2) e3) 130 | lr i e1@(e2 :|| e3) = mTL i e1 ++ (lr i e2 ++ lr i e3) 131 | lr i e1@(_ ::: e2) = mTL i e1 ++ lr i e2 132 | lr i e1@(Exists _ e2) = mTL i e1 ++ lr i e2 133 | lr i e1@(_ :# e2) = mTL i e1 ++ lr Nothing e2 134 | lr i e1@(Attr _ e2) = mTL i e1 ++ lr i e2 135 | 136 | in lr (Just 0) e' 137 | -------------------------------------------------------------------------------- /src/Language/Floorplan/Preproc/Types.hs: -------------------------------------------------------------------------------- 1 | module Language.Floorplan.Preproc.Types where 2 | import Language.Floorplan.Core.Syntax hiding (accum) 3 | import Language.Floorplan.Syntax 4 | import qualified Data.Map.Strict as M 5 | import Data.List (sort) 6 | import Data.Maybe (fromJust, isJust) 7 | import Debug.Trace as D 8 | 9 | data PreprocError = 10 | Recursive [String] -- ^ String is a grafting ID that's part of a recursive path. 11 | | UndefinedSymbol String -- ^ String is the symbol that's undefined. 12 | | DuplicateAttribute [ScopeAttribute] -- ^ The list of attributes contains unnecessary duplicates. 13 | | UnbalancedScope Int -- ^ %begin / %end are unbalanced. 14 | | RegexError String -- ^ A regex was unable to be compiled. 15 | | InvalidAttribute String -- ^ String is an error message. 16 | deriving (Eq, Ord, Show) 17 | -------------------------------------------------------------------------------- /src/Language/Floorplan/Rust.hs: -------------------------------------------------------------------------------- 1 | module Language.Floorplan.Rust 2 | ( module Language.Floorplan.Rust.Compiler 3 | ) where 4 | import Language.Floorplan.Rust.Compiler 5 | 6 | -------------------------------------------------------------------------------- /src/Language/Floorplan/Rust/Common.hs: -------------------------------------------------------------------------------- 1 | module Language.Floorplan.Rust.Common 2 | where 3 | {- Functions and names applicable to all code gen phases / passes 4 | -} 5 | 6 | import Language.Rust.Parser as P 7 | import Language.Rust.Syntax as R 8 | import Language.Rust.Quote 9 | import Language.Rust.Data.Ident 10 | import Language.Rust.Data.Position 11 | 12 | import Language.Floorplan.Core.Syntax 13 | 14 | import Data.Functor ( ($>) ) 15 | import Data.Ord (comparing) 16 | import Data.List (sortBy, nub) 17 | import Data.Char (toUpper, toLower) 18 | 19 | import Language.Floorplan.Rust.Types 20 | import qualified Debug.Trace as D 21 | 22 | bytesInWord :: Int 23 | bytesInWord = 8 24 | 25 | bitsInByte :: Int 26 | bitsInByte = 8 27 | 28 | isPow2 k = elem k $ map (2 ^) [0..k] 29 | 30 | -- | Log base `i` of `n` rounded up. 31 | log' :: Int -> Int -> Int 32 | log' i n = length (takeWhile (< n) (iterate (* 2) 1)) 33 | 34 | log2 :: Int -> Int 35 | log2 = log' 2 36 | 37 | -- TODO: Introduce spans based on position in these Haskell files. 38 | fakeSpan = Span (Position 0 0 0) (Position 0 0 0) 39 | fS = fakeSpan 40 | 41 | -- TODO: put Language.Rust.* accessors in their own module: 42 | -- | Makes a Rust expression of from an integer 43 | mkIntExp :: Int -> R.Expr Span 44 | mkIntExp i = P.parse' $ P.inputStreamFromString $ show i 45 | 46 | mkBin' i 47 | | i == 0 = [] 48 | | i `mod` 2 == 0 = '0' : mkBin' (i `div` 2) 49 | | i `mod` 2 == 1 = '1' : mkBin' (i `div` 2) 50 | 51 | mkBinU8 i 52 | | i >= 256 = error $ "Cannot make u8 from i=" ++ show i 53 | | otherwise = take (8 - (length $ mkBin' i)) (repeat '0') ++ (reverse $ mkBin' i) 54 | 55 | mkBinExp :: Int -> R.Expr Span 56 | mkBinExp i = P.parse' $ P.inputStreamFromString $ "0b" ++ mkBinU8 i 57 | 58 | offsetName :: NameID -> String 59 | offsetName n 60 | = allUpper n ++ "_OFFSET_BYTES" 61 | 62 | containsFnName :: NameID -> String 63 | containsFnName ns 64 | = "contains_" ++ firstLower ns 65 | 66 | firstLower [] = error "empty NameID!" 67 | firstLower (n:ns) = toLower n : ns 68 | 69 | firstUpper [] = error "empty NameID!" 70 | firstUpper (n:ns) = toUpper n : ns 71 | 72 | allUpper = map toUpper 73 | 74 | initSeqName :: [NameID] -> String 75 | initSeqName _ = "init_canonical_sequence" 76 | 77 | bumpAllocName :: NameID -> String 78 | bumpAllocName n 79 | = "bump_new_" ++ n 80 | 81 | bumpValidName :: NameID -> NameID -> String 82 | bumpValidName n1 n2 83 | = firstLower n1 ++ "_is_validly_before_" ++ firstLower n2 84 | 85 | initAfterName :: NameID -> NameID -> String 86 | initAfterName n1 n2 87 | = "init_" ++ firstLower n2 ++ "_after_" ++ firstLower n1 88 | 89 | memsetUntilName :: NameID -> NameID -> String 90 | memsetUntilName n1 n2 91 | = "memset_" ++ firstLower n1 ++ "_until_" ++ firstLower n2 92 | 93 | castFromName :: NameID -> NameID -> String 94 | castFromName from to 95 | = "cast_" ++ firstLower from ++ "_to_" ++ firstLower to 96 | 97 | fromIdxName :: NameID -> String 98 | fromIdxName from 99 | = firstLower from ++ "_from_idx" 100 | 101 | toIdxName :: NameID -> String 102 | toIdxName from 103 | = firstLower from ++ "_to_idx" 104 | 105 | logBytesName :: NameID -> String 106 | logBytesName n 107 | = "LOG_" ++ bytesName n 108 | 109 | bytesName :: NameID -> String 110 | bytesName n 111 | = "BYTES_IN_" ++ allUpper n 112 | 113 | -- | All named entities get an addrName: 114 | addrName xs = firstUpper xs ++ "Addr" 115 | 116 | addrEndName n = firstUpper n ++ "AddrEnd" 117 | 118 | enumName xs = "__FLP_IDX_" ++ map toUpper xs 119 | 120 | -- | Only for (known to be) fixed-width things: 121 | structName xs = firstUpper xs -- ++ "Struct" 122 | 123 | getterName xs = firstLower xs 124 | 125 | reverseGetterName xs = "from_" ++ firstLower xs 126 | 127 | skipperName ns = "skip_bytes_to_" ++ firstUpper ns 128 | 129 | jumperName ns = "jump_to_" ++ firstUpper ns 130 | 131 | nextName ns = "next_" ++ firstUpper ns 132 | 133 | bitsGetterName sz xs 134 | | sz == 1 = "get_" ++ xs ++ "_bit" 135 | | otherwise = "get_" ++ xs ++ "_bits" 136 | 137 | bitsFromerName _ [] = error "empty NameID!" 138 | bitsFromerName sz (x:xs) 139 | | sz == 1 = "set_" ++ x : xs ++ "_from_bool" 140 | | otherwise = "set_" ++ x : xs ++ "_from_u8" 141 | 142 | firstGetterName xs = "get_first_" ++ firstLower xs 143 | 144 | mkTy :: NameID -> Ty Span 145 | mkTy s = P.parse' $ P.inputStreamFromString s 146 | 147 | bytesAlignName n = allUpper n ++ "_BYTES_ALIGN" 148 | 149 | logAlignName n = allUpper n ++ "_LOG_BYTES_ALIGN" 150 | 151 | fieldPtrGetterName fs = "load_" ++ firstUpper fs ++ "Addr" 152 | fieldPtrSetterName fs = "store_" ++ firstUpper fs ++ "Addr" 153 | 154 | fieldStructGetterName fs = "load_" ++ firstUpper fs 155 | fieldStructSetterName fs = "store_" ++ firstUpper fs 156 | 157 | -- | Determine all existential-bound names brought into 158 | -- scope by this expression. Alignment and Constrained 159 | -- can be traversed because only a ":#" can refer to names. 160 | findExists :: BaseExp -> [BaseExp] 161 | findExists (Prim{}) = [] 162 | findExists (Con _ e) = findExists e 163 | findExists (e :@ _) = findExists e 164 | findExists (_ :+ _) = [] 165 | findExists (_ :|| _) = [] 166 | findExists (_ ::: _) = [] 167 | findExists e@(Exists _ e') = e : findExists e' 168 | findExists (_ :# _) = [] 169 | findExists (Attr _ e) = findExists e 170 | 171 | -------------------------------------------------------------------------------- /src/Language/Floorplan/Rust/Types.hs: -------------------------------------------------------------------------------- 1 | module Language.Floorplan.Rust.Types 2 | where 3 | import Language.Rust.Parser as P 4 | import Language.Rust.Syntax as R 5 | import Language.Rust.Quote 6 | import Language.Rust.Data.Ident 7 | import Language.Rust.Data.Position 8 | 9 | import Language.Floorplan.Core.Syntax 10 | 11 | -- | Stripped-down Rust Impl item with just the pieces we 12 | -- care about (name and item entries) so that we can merge lists of impls 13 | -- together by NameID equality. 14 | data RustItem = 15 | RustImpl 16 | NameID -- ^ e.g. "CellAddr" 17 | [ImplItem Span] -- ^ Contents of the Impl 18 | [Item Span] -- ^ Associated Items that go *outside* the `impl` 19 | | TopLevel [Item Span] 20 | deriving (Show) 21 | 22 | rustItemComparator (RustImpl n _ _) = n 23 | rustItemComparator (TopLevel _) = "______________________FLP__" -- TODO 24 | 25 | -------------------------------------------------------------------------------- /src/Language/Floorplan/Semantics.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DeriveAnyClass, DeriveGeneric, DeriveDataTypeable #-} 2 | module Language.Floorplan.Semantics where 3 | 4 | import Data.Maybe (fromJust) 5 | import Language.Floorplan.Core.Syntax 6 | 7 | data Tree = 8 | T Tree Tree 9 | | N String Tree 10 | | B0 | B1 11 | deriving (Eq, Ord, Show) 12 | 13 | leaves (T t1 t2) = leaves t1 + leaves t2 14 | leaves (N _ t) = leaves t 15 | leaves B0 = 0 16 | leaves B1 = 1 17 | 18 | addStep s xs = map (\(r,d) -> (r, s : d)) xs 19 | 20 | g p0 p1 p2 p3 = let 21 | 22 | g' a m theta ((:+) e1 e2) = 23 | let params = \r1 -> (a + leaves r1, m - leaves r1, theta, e2) 24 | in 25 | [ (T r1 r2, ("Plus: i = 0.." ++ show m ++ ", Pause (g " ++ show (params r1) ++ ")") : deriv1 26 | ++ ["Resume (g " ++ show (params r1) ++ ")" ++ ", leaves r1 = " ++ show (leaves r1)] ++ deriv2) 27 | | (r1, deriv1) <- concat [ addStep ("Plus_r1: Choose i=" ++ show i) $ g a i theta e1 | i <- [0..m]] 28 | , (r2, deriv2) <- addStep ("Plus_r2: leaves(" ++ show e1 ++ ")=" ++ show (leaves r1)) $ g (a + leaves r1) (m - leaves r1) theta e2 29 | ] 30 | g' a m theta ((:||) e1 e2) = let 31 | r1 = addStep ("Or_e1") (g a m theta e1) 32 | r2 = addStep ("Or_e2") (g a m theta e2) 33 | in r1 ++ r2 34 | g' a m theta (Prim n) 35 | | m == n = [(foldl T B0 (take n $ repeat B1), ["Prim: Evaluate " ++ show n ++ "-leaf tree"])] 36 | | otherwise = [] 37 | g' a m theta (Con n e) 38 | | m == n = addStep ("Con: " ++ show n) (g a m theta e) 39 | | m /= n = [] 40 | g' a m theta ((:@) e aln) 41 | | a `mod` aln == 0 = g a m theta e 42 | | otherwise = [] 43 | g' a m theta ((:::) l e) = 44 | [ (N l r, ("HasType: " ++ l) : deriv) 45 | | (r, deriv) <- g a m theta e 46 | ] 47 | g' a m theta (Exists f e) = concat 48 | [ addStep ("Exists: Choose " ++ f ++ "=" ++ show i) 49 | $ g a m ((f, i) : theta) e | i <- [0 .. m] ] 50 | g' a m theta ((:#) f e) 51 | | (Just m) == lookup f theta && m == 0 = [(T B0 B0, ["Pound: m = 0"])] 52 | | (Just 0) == lookup f theta = [] 53 | | otherwise = let -- Implies lookup theta f > 0 54 | params = \r1 -> (a + leaves r1, m - leaves r1, (f, (fromJust $ lookup f theta) - 1) : theta, (:#) f e) 55 | in case lookup f theta of 56 | Nothing -> error "Nothing" 57 | Just thetaF -> [ (T r1 r2, ("Pound: theta(" ++ f ++ ")=" ++ show thetaF ++ ", Pause (g " ++ show (params r1)) : deriv1 58 | ++ ["Resume (g " ++ show (params r1) ++ ")"] ++ deriv2) 59 | | (r1, deriv1) <- concat [addStep ("Pound_r1: Choose i=" ++ show i) $ g a i theta e | i <- [0..m]] 60 | , (r2, deriv2) <- addStep ("Pound_r2: leaves(" ++ show e ++ ")=" ++ show (leaves r1)) $ 61 | g (a + leaves r1) (m - leaves r1) ((f, (fromJust $ lookup f theta) - 1) : theta) ((:#) f e) 62 | ] 63 | 64 | addLabel (r, d:ds) = (r, (d ++ "[[g " ++ show (p0, p1, p2, p3) ++ "]] ") : ds) 65 | 66 | in map addLabel (g' p0 p1 p2 p3) 67 | 68 | -------------------------------------------------------------------------------- /src/Language/Floorplan/Token.x: -------------------------------------------------------------------------------- 1 | { 2 | {-# OPTIONS_GHC -w #-} 3 | module Language.Floorplan.Token (Token(..), scanTokens) where 4 | import Language.Floorplan.Syntax 5 | import qualified Debug.Trace as D 6 | } 7 | 8 | %wrapper "basic" 9 | 10 | $digit = 0-9 11 | $alpha = [a-zA-Z] 12 | $eol = [\n] 13 | 14 | tokens :- 15 | $eol ; 16 | $white+ ; 17 | "//".* ; 18 | seq { \s -> TokenSeq } 19 | union { \s -> TokenUnion } 20 | contains { \s -> TokenContains } 21 | bits { \s -> TokenBits } 22 | bytes { \s -> TokenBytes } 23 | words { \s -> TokenWords } 24 | pages { \s -> TokenPages } 25 | ptr { \s -> TokenPtr } 26 | enum { \s -> TokenEnum } 27 | "->" { \s -> TokenArrow } 28 | "@(" { \s -> TokenAtParen } 29 | ")@" { \s -> TokenParenAt } 30 | "@|" { \s -> TokenAtBar } 31 | "|@" { \s -> TokenBarAt } 32 | "<$" { \s -> TokenLessD } 33 | "$>" { \s -> TokenGreaterD } 34 | \( { \s -> TokenLParen } 35 | \) { \s -> TokenRParen } 36 | \< { \s -> TokenLess } 37 | \> { \s -> TokenGreater } 38 | \| { \s -> TokenBar } 39 | \|\| { \s -> TokenBarBar } 40 | \{ { \s -> TokenLCurl } 41 | \} { \s -> TokenRCurl } 42 | \# { \s -> TokenPound } 43 | \: { \s -> TokenColon } 44 | \, { \s -> TokenComma } 45 | [\+] { \s -> TokenPlus } 46 | [\-] { \s -> TokenMinus } 47 | [\*] { \s -> TokenTimes } 48 | [\/] { \s -> TokenDiv } 49 | [\^] { \s -> TokenExponent } 50 | "%begin" { \s -> TokenBeginScope } 51 | "%end" { \s -> TokenEndScope } 52 | "%filterout" { \s -> TokenFilterOut } -- filter out rules 53 | noglobal { \s -> TokenNoGlobal } 54 | \"[^\"]*\" { \s -> TokenStringLiteral $ tail $ init s } 55 | $digit+ { \s -> TokenNum (read s) } 56 | 0b [01]+ { \s -> TokenNum (bin2int s) } 57 | [a-z] [$alpha $digit \_]* { \s -> TokenLowerID s } 58 | [A-Z] [$alpha $digit \_]* { \s -> TokenUpperID s} 59 | 60 | { 61 | data Token = 62 | TokenSeq 63 | | TokenUnion 64 | | TokenContains 65 | | TokenBits 66 | | TokenBytes 67 | | TokenWords 68 | | TokenPages 69 | | TokenPtr 70 | | TokenEnum 71 | | TokenArrow 72 | | TokenAtParen 73 | | TokenParenAt 74 | | TokenAtBar 75 | | TokenBarAt 76 | | TokenLParen 77 | | TokenRParen 78 | | TokenLess 79 | | TokenGreater 80 | | TokenLessD 81 | | TokenGreaterD 82 | | TokenBar 83 | | TokenBarBar 84 | | TokenLCurl 85 | | TokenRCurl 86 | | TokenPound 87 | | TokenColon 88 | | TokenComma 89 | | TokenPlus 90 | | TokenMinus 91 | | TokenTimes 92 | | TokenDiv 93 | | TokenExponent 94 | | TokenNum Int 95 | | TokenUpperID String 96 | | TokenLowerID String 97 | | TokenBeginScope | TokenEndScope | TokenNoGlobal 98 | | TokenStringLiteral String | TokenFilterOut 99 | | TokenHeader String | TokenFooter String 100 | deriving (Eq, Ord, Show) 101 | 102 | -- | Returns a tuple of all contents before the first instance of '%end' along with 103 | -- the rest of the input string *after* the '%end'. 104 | getUntilEnd :: String -> String -> (String, String) 105 | getUntilEnd _ ('%':'e':'n':'d':rest) = ("", rest) 106 | getUntilEnd kind "" = error $ "Reached end of input while parsing " ++ kind 107 | getUntilEnd kind (c:cs) = 108 | let (got, rest) = getUntilEnd kind cs 109 | in (c : got, rest) 110 | 111 | getHeader = getUntilEnd "header" 112 | getFooter = getUntilEnd "footer" 113 | 114 | -- | Special case for parsing C-like or Rust-like headers and footers of output. 115 | scanTokens :: String -> [Token] 116 | scanTokens "" = [] 117 | scanTokens s = 118 | case alexScan ('\n',[],s) 0 of 119 | AlexEOF -> [] 120 | (AlexError e) -> error $ show e -- TODO: bad failure mode 121 | (AlexSkip (_,_,s') _) -> scanTokens s' 122 | (AlexToken (_,_,s') len act) -> 123 | case act (take len s) of 124 | TokenBeginScope -> checkBegin s' 125 | tok -> tok : scanTokens s' -- Fall-through case 126 | 127 | checkBegin :: String -> [Token] 128 | checkBegin s = 129 | case alexScan ('\n', [], s) 0 of 130 | AlexEOF -> [] 131 | (AlexError e) -> error $ show e -- TODO: bad failure mode 132 | (AlexSkip (_,_,s') _) -> checkBegin s' 133 | (AlexToken (_,_,s') len act) -> 134 | case act (take len s) of 135 | TokenLowerID "header" -> 136 | let (header, s'') = getHeader s' 137 | in TokenHeader header : scanTokens s'' 138 | TokenLowerID "footer" -> 139 | let (footer, s'') = getFooter s' 140 | in TokenFooter footer : scanTokens s'' 141 | -- Fall-through case: it wasn't a header or a footer, so we need 142 | -- to include the '%begin' scope token that we already parsed in the input. 143 | tok -> TokenBeginScope : tok : scanTokens s' 144 | 145 | } 146 | -------------------------------------------------------------------------------- /src/Language/Rust/Data/Ident.hs: -------------------------------------------------------------------------------- 1 | {-| 2 | Module : Language.Rust.Data.Ident 3 | Description : Identifiers 4 | Copyright : (c) Alec Theriault, 2017-2018 5 | License : BSD-style 6 | Maintainer : alec.theriault@gmail.com 7 | Stability : experimental 8 | Portability : portable 9 | 10 | Data structure behind identifiers. 11 | -} 12 | {-# LANGUAGE DeriveDataTypeable #-} 13 | {-# LANGUAGE DeriveGeneric #-} 14 | {-# LANGUAGE DeriveAnyClass #-} 15 | 16 | module Language.Rust.Data.Ident (Ident(..), mkIdent, Name, IdentName(..) ) where 17 | 18 | import GHC.Generics ( Generic ) 19 | 20 | import Control.DeepSeq ( NFData ) 21 | import Data.Data ( Data ) 22 | import Data.Typeable ( Typeable ) 23 | 24 | import Data.List ( foldl' ) 25 | import Data.Char ( ord ) 26 | import Data.String ( IsString(..) ) 27 | --import Data.Semigroup as Sem 28 | 29 | -- | An identifier 30 | data Ident 31 | = Ident { name :: IdentName -- ^ payload of the identifier 32 | , raw :: Bool -- ^ whether the identifier is raw 33 | , hash :: {-# UNPACK #-} !Int -- ^ hash for quick comparision 34 | } deriving (Data, Typeable, Generic, NFData) 35 | 36 | -- | Shows the identifier as a string (for use with @-XOverloadedStrings@) 37 | instance Show Ident where 38 | show = show . name 39 | 40 | instance IsString Ident where 41 | fromString = mkIdent 42 | 43 | -- | Uses 'hash' to short-circuit 44 | instance Eq Ident where 45 | i1 == i2 = hash i1 == hash i2 && name i1 == name i2 && raw i1 == raw i2 46 | i1 /= i2 = hash i1 /= hash i2 || name i1 /= name i2 || raw i1 /= raw i2 47 | 48 | -- | Uses 'hash' to short-circuit 49 | instance Ord Ident where 50 | compare i1 i2 = case compare i1 i2 of 51 | EQ -> compare (raw i1, name i1) (raw i2, name i2) 52 | rt -> rt 53 | 54 | -- | "Forgets" about whether either argument was raw 55 | instance Monoid Ident where 56 | mappend = (<>) 57 | mempty = mkIdent "" 58 | 59 | -- | "Forgets" about whether either argument was raw 60 | -- <<<<<<< HEAD 61 | instance Semigroup Ident where 62 | Ident (Name n1) _ _ <> Ident (Name n2) _ _ = mkIdent (n1 <> n2) 63 | Ident (HaskellName n1) _ _ <> Ident (HaskellName n2) _ _ = mkIdent (n1 <> n2) 64 | Ident (Name n1) _ _ <> Ident (HaskellName n2) _ _ = mkIdent (n1 <> n2) 65 | Ident (HaskellName n1) _ _ <> Ident (Name n2) _ _ = mkIdent (n1 <> n2) 66 | -- ======= 67 | -- instance Sem.Semigroup Ident where 68 | -- Ident n1 _ _ <> Ident n2 _ _ = mkIdent (n1 <> n2) 69 | -- >>>>>>> upstream/master 70 | 71 | -- | Smart constructor for making an 'Ident'. 72 | mkIdent :: String -> Ident 73 | mkIdent ('$':'{':'i':'|':ss) = Ident (HaskellName $ init $ init ss) False (hashString $ init $ init ss) 74 | mkIdent s = Ident (Name s) False (hashString s) 75 | 76 | -- | Hash a string into an 'Int' 77 | hashString :: String -> Int 78 | hashString = foldl' f golden 79 | where f m c = fromIntegral (ord c) * magic + m 80 | magic = 0xdeadbeef 81 | golden = 1013904242 82 | 83 | -- | The payload of an identifier 84 | type Name = String 85 | 86 | data IdentName = 87 | Name Name 88 | | HaskellName String 89 | deriving (Eq, Ord, Data, Typeable, Generic, NFData) 90 | 91 | instance Show IdentName where 92 | show (Name n) = show n 93 | show (HaskellName n) = "${i|" ++ n ++ "|}" 94 | 95 | -------------------------------------------------------------------------------- /src/Language/Rust/Data/InputStream.hs: -------------------------------------------------------------------------------- 1 | {-| 2 | Module : Language.Rust.Data.InputStream 3 | Description : Interface to the underlying input of parsing 4 | Copyright : (c) Alec Theriault, 2017-2018 5 | License : BSD-style 6 | Maintainer : alec.theriault@gmail.com 7 | Stability : experimental 8 | Portability : portable 9 | 10 | These are the only functions that need to be implemented in order to use the parser. Whether this 11 | wraps 'BS.ByteString' or 'String' depends on whether the @useByteStrings@ option is on or not (it is 12 | by default). Using 'BS.ByteString' means better handling of weird characters ('takeByte' for plain 13 | 'String' fails badly if you try to take a byte that doesn't fall on a character boundary), but it 14 | means incurring a dependency on the [utf8-string](https://hackage.haskell.org/package/utf8-string) 15 | package. 16 | -} 17 | {-# LANGUAGE CPP #-} 18 | 19 | module Language.Rust.Data.InputStream ( 20 | -- * InputStream type 21 | InputStream, 22 | countLines, 23 | inputStreamEmpty, 24 | 25 | -- * Introduction forms 26 | readInputStream, 27 | hReadInputStream, 28 | inputStreamFromString, 29 | 30 | -- * Elimination forms 31 | inputStreamToString, 32 | takeByte, 33 | takeChar, 34 | peekChars, 35 | ) where 36 | 37 | import Data.Word ( Word8 ) 38 | import Data.Coerce ( coerce ) 39 | import Data.String ( IsString(..) ) 40 | import System.IO 41 | 42 | #ifdef USE_BYTESTRING 43 | import qualified Data.ByteString as BS 44 | import qualified Data.ByteString.UTF8 as BE 45 | #else 46 | import qualified Data.Char as Char 47 | #endif 48 | 49 | -- | Read an encoded file into an 'InputStream' 50 | readInputStream :: FilePath -> IO InputStream 51 | {-# INLINE readInputStream #-} 52 | 53 | -- | Read an 'InputStream' from a 'Handle' 54 | hReadInputStream :: Handle -> IO InputStream 55 | {-# INLINE hReadInputStream #-} 56 | 57 | -- | Convert 'InputStream' to 'String'. 58 | inputStreamToString :: InputStream -> String 59 | {-# INLINE inputStreamToString #-} 60 | 61 | -- | Convert a 'String' to an 'InputStream'. 62 | inputStreamFromString :: String -> InputStream 63 | {-# INLINE inputStreamFromString #-} 64 | 65 | -- | Uses 'inputStreamFromString' 66 | instance IsString InputStream where fromString = inputStreamFromString 67 | 68 | -- | Read the first byte from an 'InputStream' and return that byte with what remains of the 69 | -- 'InputStream'. Behaviour is undefined when 'inputStreamEmpty' returns 'True'. 70 | -- 71 | -- >>> takeByte "foo bar" 72 | -- (102, "oo bar") 73 | -- 74 | -- >>> takeByte "Ĥăƨĸëļļ" 75 | -- (196, "\ETX\168\&8\235<<") 76 | -- 77 | takeByte :: InputStream -> (Word8, InputStream) 78 | {-# INLINE takeByte #-} 79 | 80 | -- | Read the first character from an 'InputStream' and return that 'Char' with what remains of the 81 | -- 'InputStream'. Behaviour is undefined when 'inputStreamEmpty' returns 'True'. 82 | -- 83 | -- >>> takeChar "foo bar" 84 | -- ('f', "oo bar") 85 | -- 86 | -- >>> takeChar "Ĥăƨĸëļļ" 87 | -- ('Ĥ', "ăƨĸëļļ") 88 | -- 89 | takeChar :: InputStream -> (Char, InputStream) 90 | {-# INLINE takeChar #-} 91 | 92 | -- | Return @True@ if the given input stream is empty. 93 | -- 94 | -- >>> inputStreamEmpty "" 95 | -- True 96 | -- 97 | -- >>> inputStreamEmpty "foo" 98 | -- False 99 | -- 100 | inputStreamEmpty :: InputStream -> Bool 101 | {-# INLINE inputStreamEmpty #-} 102 | 103 | -- | Returns the first @n@ characters of the given input stream, without removing them. 104 | -- 105 | -- >>> peekChars 5 "foo bar" 106 | -- "foo ba" 107 | -- 108 | -- >>> peekChars 5 "foo" 109 | -- "foo" 110 | -- 111 | -- >>> peekChars 3 "Ĥăƨĸëļļ" 112 | -- "Ĥăƨ" 113 | -- 114 | peekChars :: Int -> InputStream -> String 115 | {-# INLINE peekChars #-} 116 | 117 | -- | Returns the number of text lines in the given 'InputStream' 118 | -- 119 | -- >>> countLines "" 120 | -- 0 121 | -- 122 | -- >>> countLines "foo" 123 | -- 1 124 | -- 125 | -- >>> countLines "foo\n\nbar" 126 | -- 3 127 | -- 128 | -- >>> countLines "foo\n\nbar\n" 129 | -- 3 130 | -- 131 | countLines :: InputStream -> Int 132 | {-# INLINE countLines #-} 133 | 134 | #ifdef USE_BYTESTRING 135 | 136 | -- | Opaque input type. 137 | newtype InputStream = IS BS.ByteString deriving (Eq, Ord) 138 | takeByte bs = (BS.head (coerce bs), coerce (BS.tail (coerce bs))) 139 | takeChar bs = maybe (error "takeChar: no char left") coerce (BE.uncons (coerce bs)) 140 | inputStreamEmpty = BS.null . coerce 141 | peekChars n = BE.toString . BE.take n . coerce 142 | readInputStream f = coerce <$> BS.readFile f 143 | hReadInputStream h = coerce <$> BS.hGetContents h 144 | inputStreamToString = BE.toString . coerce 145 | inputStreamFromString = IS . BE.fromString 146 | countLines = length . BE.lines . coerce 147 | 148 | instance Show InputStream where 149 | show (IS bs) = show bs 150 | 151 | #else 152 | 153 | -- | Opaque input type. 154 | newtype InputStream = IS String deriving (Eq, Ord) 155 | takeByte (IS ~(c:str)) 156 | | Char.isLatin1 c = let b = fromIntegral (Char.ord c) in b `seq` (b, IS str) 157 | | otherwise = error "takeByte: not a latin-1 character" 158 | takeChar (IS ~(c:str)) = (c, IS str) 159 | inputStreamEmpty (IS str) = null str 160 | peekChars n (IS str) = take n str 161 | readInputStream f = IS <$> readFile f 162 | hReadInputStream h = IS <$> hGetContents h 163 | inputStreamToString = coerce 164 | inputStreamFromString = IS 165 | countLines (IS str) = length . lines $ str 166 | 167 | instance Show InputStream where 168 | show (IS bs) = show bs 169 | 170 | #endif 171 | -------------------------------------------------------------------------------- /src/Language/Rust/Parser.hs: -------------------------------------------------------------------------------- 1 | {-| 2 | Module : Language.Rust.Parser 3 | Description : Parsing and lexing 4 | Copyright : (c) Alec Theriault, 2017-2018 5 | License : BSD-style 6 | Maintainer : alec.theriault@gmail.com 7 | Stability : experimental 8 | Portability : GHC 9 | 10 | Selecting the right parser may require adding an annotation or using @-XTypeApplications@ to avoid 11 | an "Ambiguous type variable" error. 12 | 13 | Using 'Control.Monad.void' (as in the examples below) exploits the fact that most AST nodes are 14 | instances of 'Functor' to discard the 'Span' annotation that is attached to most parsed AST nodes. 15 | Conversely, if you wish to extract the 'Span' annotation, the 'Language.Rust.Syntax.AST.Located' 16 | typeclass provides a 'Language.Rust.Syntax.AST.spanOf' method. 17 | 18 | The examples below assume the following GHCi flags and imports: 19 | 20 | >>> :set -XTypeApplications -XOverloadedStrings 21 | >>> import Language.Rust.Syntax.AST 22 | >>> import Control.Monad ( void ) 23 | >>> import System.IO 24 | 25 | -} 26 | {-# LANGUAGE FlexibleInstances #-} 27 | 28 | module Language.Rust.Parser ( 29 | -- * Parsing 30 | parse, 31 | parse', 32 | readSourceFile, 33 | readTokens, 34 | Parse(..), 35 | P, 36 | execParser, 37 | execParserTokens, 38 | initPos, 39 | Span, 40 | 41 | -- * Lexing 42 | lexToken, 43 | lexNonSpace, 44 | lexTokens, 45 | translateLit, 46 | 47 | -- * Input stream 48 | readInputStream, 49 | hReadInputStream, 50 | inputStreamToString, 51 | inputStreamFromString, 52 | 53 | -- * Error reporting 54 | lexicalError, 55 | parseError, 56 | ParseFail(..), 57 | ) where 58 | 59 | import Language.Rust.Syntax 60 | 61 | import Language.Rust.Data.InputStream 62 | import Language.Rust.Data.Position ( Position, Span, Spanned, initPos ) 63 | 64 | import Language.Rust.Parser.Internal 65 | import Language.Rust.Parser.Lexer ( lexToken, lexNonSpace, lexTokens, lexicalError ) 66 | import Language.Rust.Parser.Literals ( translateLit ) 67 | import Language.Rust.Parser.ParseMonad ( P, execParser, parseError, pushToken, ParseFail(..) ) 68 | 69 | import Control.Exception ( throw ) 70 | import Data.Foldable ( traverse_ ) 71 | import System.IO ( Handle ) 72 | 73 | -- | Parse something from an input stream (it is assumed the initial position is 'initPos'). 74 | -- 75 | -- >>> fmap void $ parse @(Expr Span) "x + 1" 76 | -- Right (Binary [] AddOp (PathExpr [] Nothing (Path False [PathSegment "x" Nothing ()] ()) ()) 77 | -- (Lit [] (Int Dec 1 Unsuffixed ()) ()) 78 | -- ()) 79 | -- 80 | -- >>> fmap void $ parse @(Expr Span) "x + " 81 | -- Left (parse failure at 1:4 (Syntax error: unexpected `' (expected an expression))) 82 | -- 83 | parse :: Parse a => InputStream -> Either ParseFail a 84 | parse is = execParser parser is initPos 85 | 86 | -- | Same as 'parse', but throws a 'ParseFail' exception if it cannot parse. This function is 87 | -- intended for situations in which you are already stuck catching exceptions - otherwise you should 88 | -- prefer 'parse'. 89 | -- 90 | -- >>> void $ parse' @(Expr Span) "x + 1" 91 | -- Binary [] AddOp (PathExpr [] Nothing (Path False [PathSegment "x" Nothing ()] ()) ()) 92 | -- (Lit [] (Int Dec 1 Unsuffixed ()) ()) 93 | -- () 94 | -- 95 | -- >>> void $ parse' @(Expr Span) "x + " 96 | -- *** Exception: parse failure at 1:4 (Syntax error: unexpected `' (expected an expression)) 97 | -- 98 | parse' :: Parse a => InputStream -> a 99 | parse' = either throw id . parse 100 | 101 | -- | Same as 'execParser', but working from a list of tokens instead of an 'InputStream'. 102 | execParserTokens :: P a -> [Spanned Token] -> Position -> Either ParseFail a 103 | execParserTokens p toks = execParser (pushTokens toks *> p) (inputStreamFromString "") 104 | where pushTokens = traverse_ pushToken . reverse 105 | 106 | -- | Given a handle to a Rust source file, read that file and parse it into a 'SourceFile' 107 | -- 108 | -- >>> writeFile "empty_main.rs" "fn main() { }" 109 | -- >>> fmap void $ withFile "empty_main.rs" ReadMode readSourceFile 110 | -- SourceFile Nothing [] [Fn [] InheritedV "main" 111 | -- (FnDecl [] Nothing False ()) 112 | -- Normal NotConst Rust 113 | -- (Generics [] [] (WhereClause [] ()) ()) 114 | -- (Block [] Normal ()) ()] 115 | -- 116 | readSourceFile :: Handle -> IO (SourceFile Span) 117 | readSourceFile hdl = parse' <$> hReadInputStream hdl 118 | 119 | -- | Given a path pointing to a Rust source file, read that file and lex it (ignoring whitespace) 120 | -- 121 | -- >>> writeFile "empty_main.rs" "fn main() { }" 122 | -- >>> withFile "empty_main.rs" ReadMode readTokens 123 | -- [fn,main,(,),{,}] 124 | -- 125 | readTokens :: Handle -> IO [Spanned Token] 126 | readTokens hdl = do 127 | inp <- hReadInputStream hdl 128 | case execParser (lexTokens lexNonSpace) inp initPos of 129 | Left pf -> throw pf 130 | Right x -> pure x 131 | 132 | -- | Describes things that can be parsed 133 | class Parse a where 134 | -- | Complete parser (fails if not all of the input is consumed) 135 | parser :: P a 136 | 137 | instance Parse (Lit Span) where parser = parseLit 138 | instance Parse (Attribute Span) where parser = parseAttr 139 | instance Parse (Ty Span) where parser = parseTy 140 | instance Parse (Pat Span) where parser = parsePat 141 | instance Parse (Expr Span) where parser = parseExpr 142 | instance Parse (Stmt Span) where parser = parseStmt 143 | instance Parse (Item Span) where parser = parseItem 144 | instance Parse (SourceFile Span) where parser = parseSourceFile 145 | instance Parse TokenTree where parser = parseTt 146 | instance Parse TokenStream where parser = parseTokenStream 147 | instance Parse (Block Span) where parser = parseBlock 148 | instance Parse (ImplItem Span) where parser = parseImplItem 149 | instance Parse (TraitItem Span) where parser = parseTraitItem 150 | instance Parse (TyParam Span) where parser = parseTyParam 151 | instance Parse (LifetimeDef Span) where parser = parseLifetimeDef 152 | instance Parse (Generics Span) where parser = parseGenerics 153 | instance Parse (WhereClause Span) where parser = parseWhereClause 154 | -------------------------------------------------------------------------------- /src/Language/Rust/Parser/NonEmpty.hs: -------------------------------------------------------------------------------- 1 | {-| 2 | Module : Language.Rust.Parser.NonEmpty 3 | Description : Non-empty lists 4 | Copyright : (c) Alec Theriault, 2017-2018 5 | License : BSD-style 6 | Maintainer : alec.theriault@gmail.com 7 | Stability : experimental 8 | Portability : GHC 9 | 10 | Datatype wrapping non-empty lists. 11 | -} 12 | {-# LANGUAGE CPP #-} 13 | {-# LANGUAGE FlexibleInstances #-} 14 | {-# LANGUAGE TypeFamilies #-} 15 | #if __GLASGOW_HASKELL__ < 800 16 | {-# LANGUAGE FlexibleContexts #-} 17 | #endif 18 | {-# LANGUAGE StandaloneDeriving #-} 19 | {-# LANGUAGE DeriveDataTypeable #-} 20 | {-# LANGUAGE QuasiQuotes #-} 21 | {-# LANGUAGE TemplateHaskell, DeriveFoldable, DeriveFunctor, GeneralizedNewtypeDeriving, DeriveTraversable #-} 22 | 23 | module Language.Rust.Parser.NonEmpty ( 24 | NonEmpty(..), 25 | listCons, 26 | head, tail, 27 | reverse, last, init, (<|), (|:), (|>) 28 | ) where 29 | import Prelude hiding (head, tail, reverse, init, last) 30 | 31 | import qualified Data.List.NonEmpty as N 32 | import Data.Data ( Data(..) ) 33 | import Control.DeepSeq ( NFData ) 34 | import Data.Semigroup (Semigroup(..)) 35 | 36 | -- Support for OverloadedLists of NonEmpty lists: 37 | import GHC.Exts (IsList(..)) 38 | 39 | newtype NonEmpty a = NonEmpty (N.NonEmpty a) 40 | deriving (Foldable, Eq, Ord, Show, Functor, Data, NFData, Traversable) 41 | 42 | instance IsList (NonEmpty a) where 43 | type Item (NonEmpty a) = a 44 | 45 | fromList (a:as) = NonEmpty $ a N.:| as 46 | fromList [] = errorWithoutStackTrace "NonEmpty.fromList: empty list" 47 | 48 | toList ~(NonEmpty (a N.:| as)) = a : as 49 | 50 | instance Semigroup (NonEmpty a) where 51 | (NonEmpty (a N.:| as)) <> ~(NonEmpty (b N.:| bs)) = NonEmpty $ a N.:| (as ++ b : bs) 52 | 53 | listCons :: a -> [a] -> NonEmpty a 54 | listCons a as = NonEmpty $ a N.:| as 55 | 56 | head :: NonEmpty a -> a 57 | head (NonEmpty as) = N.head as 58 | 59 | tail :: NonEmpty a -> [a] 60 | tail (NonEmpty as) = N.tail as 61 | 62 | reverse :: NonEmpty a -> NonEmpty a 63 | reverse (NonEmpty as) = NonEmpty $ N.reverse as 64 | 65 | init :: NonEmpty a -> [a] 66 | init (NonEmpty as) = N.init as 67 | 68 | last :: NonEmpty a -> a 69 | last (NonEmpty as) = N.last as 70 | 71 | (<|) :: a -> NonEmpty a -> NonEmpty a 72 | (<|) a (NonEmpty as) = NonEmpty $ a N.<| as 73 | 74 | (|:) :: [a] -> a -> NonEmpty a 75 | [] |: y = NonEmpty $ y N.:| [] 76 | (x:xs) |: y = NonEmpty $ x N.:| (xs ++ [y]) 77 | 78 | (|>) :: NonEmpty a -> a -> NonEmpty a 79 | (|>) (NonEmpty (x N.:| xs)) y = NonEmpty $ x N.:| (xs ++ [y]) 80 | 81 | -------------------------------------------------------------------------------- /src/Language/Rust/Parser/Reversed.hs: -------------------------------------------------------------------------------- 1 | {-| 2 | Module : Language.Rust.Parser.Reversed 3 | Description : Parsing literals 4 | Copyright : (c) Alec Theriault, 2017-2018 5 | License : BSD-style 6 | Maintainer : alec.theriault@gmail.com 7 | Stability : experimental 8 | Portability : GHC 9 | 10 | Datatypes wrapping lists and non-empty lists designed for fast append (as opposed to prepend) 11 | along with the usual class instances. 12 | -} 13 | {-# LANGUAGE CPP #-} 14 | {-# LANGUAGE FlexibleInstances #-} 15 | {-# LANGUAGE TypeFamilies #-} 16 | #if __GLASGOW_HASKELL__ < 800 17 | {-# LANGUAGE FlexibleContexts #-} 18 | #endif 19 | {-# LANGUAGE StandaloneDeriving #-} 20 | {-# LANGUAGE DeriveDataTypeable #-} 21 | 22 | module Language.Rust.Parser.Reversed ( 23 | Reversed(..), 24 | toNonEmpty, 25 | unsnoc, 26 | snoc, 27 | NonEmpty(..), 28 | ) where 29 | 30 | import Language.Rust.Data.Position 31 | 32 | import Data.Foldable ( Foldable(toList) ) 33 | import Data.Semigroup as Sem ( Semigroup(..) ) 34 | 35 | import qualified Language.Rust.Parser.NonEmpty as N 36 | import Language.Rust.Parser.NonEmpty (NonEmpty(..), listCons) 37 | import qualified GHC.Exts as G 38 | 39 | import Data.Data (Data(..), Typeable) 40 | 41 | -- | Wrap a data type where all the operations are reversed 42 | newtype Reversed f a = Reversed (f a) 43 | 44 | instance Functor f => Functor (Reversed f) where 45 | fmap f (Reversed xs) = Reversed (fmap f xs) 46 | 47 | instance Foldable (Reversed []) where 48 | foldMap f (Reversed xs) = foldMap f (reverse xs) 49 | toList (Reversed xs) = reverse xs 50 | 51 | instance Foldable (Reversed NonEmpty) where 52 | foldMap f (Reversed xs) = foldMap f (N.reverse xs) 53 | toList (Reversed xs) = reverse (toList xs) 54 | 55 | instance Sem.Semigroup (f a) => Sem.Semigroup (Reversed f a) where 56 | Reversed xs <> Reversed ys = Reversed (ys Sem.<> xs) 57 | 58 | instance Monoid (f a) => Monoid (Reversed f a) where 59 | mempty = Reversed mempty 60 | mappend (Reversed xs) (Reversed ys) = Reversed (mappend ys xs) 61 | 62 | instance G.IsList (f a) => G.IsList (Reversed f a) where 63 | type Item (Reversed f a) = G.Item (f a) 64 | fromList xs = Reversed (G.fromList (reverse xs)) 65 | toList (Reversed xs) = reverse (G.toList xs) 66 | 67 | instance Located (f a) => Located (Reversed f a) where 68 | spanOf (Reversed xs) = spanOf xs 69 | 70 | deriving instance (Typeable f, Typeable a, Data (f a)) => Data (Reversed f a) 71 | 72 | -- | Convert a reversed 'NonEmpty' back into a normal one. 73 | {-# INLINE toNonEmpty #-} 74 | toNonEmpty :: Reversed NonEmpty a -> NonEmpty a 75 | toNonEmpty (Reversed xs) = N.reverse xs 76 | 77 | -- | Remove an element from the end of a non-empty reversed sequence 78 | {-# INLINE unsnoc #-} 79 | unsnoc :: Reversed NonEmpty a -> (Reversed [] a, a) 80 | unsnoc (Reversed xs) = (Reversed $ N.tail xs, N.head xs) 81 | 82 | -- | Add an element to the end of a reversed sequence to produce a non-empty 83 | -- reversed sequence 84 | {-# INLINE snoc #-} 85 | snoc :: Reversed [] a -> a -> Reversed NonEmpty a 86 | snoc (Reversed xs) x = Reversed (x `listCons` xs) 87 | -------------------------------------------------------------------------------- /src/Language/Rust/Pretty/Literals.hs: -------------------------------------------------------------------------------- 1 | {-| 2 | Module : Language.Rust.Pretty.Literals 3 | Description : Parsing literals 4 | Copyright : (c) Alec Theriault, 2017-2018 5 | License : BSD-style 6 | Maintainer : alec.theriault@gmail.com 7 | Stability : experimental 8 | Portability : portable 9 | 10 | Functions for pretty printing literals. 11 | -} 12 | {-# LANGUAGE OverloadedStrings #-} 13 | 14 | module Language.Rust.Pretty.Literals ( 15 | printLit, 16 | printLitSuffix, 17 | ) where 18 | 19 | import Language.Rust.Syntax.AST 20 | import Language.Rust.Pretty.Util 21 | 22 | import Data.Text.Prettyprint.Doc ( hcat, annotate, (<>), Doc, pretty, group, hardline, flatAlt ) 23 | 24 | import Data.Char ( intToDigit, ord, chr ) 25 | import Data.Word ( Word8 ) 26 | 27 | -- | Print a literal (@print_literal@) 28 | printLit :: Lit a -> Doc a 29 | printLit lit = noIndent $ case lit of 30 | Str str Cooked s x -> annotate x (hcat [ "\"", group (foldMap (escapeChar True) str), "\"", suf s ]) 31 | Str str (Raw m) s x -> annotate x (hcat [ "r", pad m, "\"", string hardline str, "\"", pad m, suf s ]) 32 | ByteStr str Cooked s x -> annotate x (hcat [ "b\"", group (foldMap (escapeByte True) str), "\"", suf s ]) 33 | ByteStr str (Raw m) s x -> annotate x (hcat [ "br", pad m, "\"", string hardline (map byte2Char str), "\"", pad m, suf s ]) 34 | Char c s x -> annotate x (hcat [ "'", escapeChar False c, "'", suf s ]) 35 | Byte b s x -> annotate x (hcat [ "b'", escapeByte False b, "'", suf s ]) 36 | Int b i l s x -> annotate x (hcat [ printIntLit i b l, suf s ]) 37 | Float d s x -> annotate x (hcat [ pretty d, suf s ]) 38 | Bool True s x -> annotate x (hcat [ "true", suf s ]) 39 | Bool False s x -> annotate x (hcat [ "false", suf s ]) 40 | where 41 | pad :: Int -> Doc a 42 | pad m = pretty (replicate m '#') 43 | 44 | suf :: Suffix -> Doc a 45 | suf = printLitSuffix 46 | 47 | -- | Print literal suffix 48 | printLitSuffix :: Suffix -> Doc a 49 | printLitSuffix Unsuffixed = mempty 50 | printLitSuffix Is = "isize" 51 | printLitSuffix I8 = "i8" 52 | printLitSuffix I16 = "i16" 53 | printLitSuffix I32 = "i32" 54 | printLitSuffix I64 = "i64" 55 | printLitSuffix I128 = "i128" 56 | printLitSuffix Us = "usize" 57 | printLitSuffix U8 = "u8" 58 | printLitSuffix U16 = "u16" 59 | printLitSuffix U32 = "u32" 60 | printLitSuffix U64 = "u64" 61 | printLitSuffix U128 = "u128" 62 | printLitSuffix F32 = "f32" 63 | printLitSuffix F64 = "f64" 64 | 65 | -- | Print an integer literal 66 | printIntLit :: Integer -> IntRep -> String -> Doc a 67 | printIntLit i r len 68 | | i < 0 = "-" <> baseRep r <> printIntPrefix (show $ toNBase (abs i) (baseVal r)) len <> toNBase (abs i) (baseVal r) 69 | | i == 0 = baseRep r <> printIntPrefix "" len -- <> "0" 70 | | otherwise = baseRep r <> printIntPrefix (show $ toNBase (abs i) (baseVal r)) len <> toNBase (abs i) (baseVal r) 71 | where 72 | baseRep :: IntRep -> Doc a 73 | baseRep Bin = "0b" 74 | baseRep Oct = "0o" 75 | baseRep Dec = mempty 76 | baseRep Hex = "0x" 77 | 78 | baseVal :: IntRep -> Integer 79 | baseVal Bin = 2 80 | baseVal Oct = 8 81 | baseVal Dec = 10 82 | baseVal Hex = 16 83 | 84 | printIntPrefix :: String -> String -> Doc a 85 | printIntPrefix out ('0':'b':rest) = pretty $ replicate ((length rest) - (length out)) '0' 86 | printIntPrefix out ('0':'o':rest) = pretty $ replicate ((length rest) - (length out)) '0' 87 | printIntPrefix out ('0':'x':rest) = pretty $ replicate ((length rest) - (length out)) '0' 88 | printIntPrefix out rest = pretty $ replicate ((length rest) - (length out)) '0' 89 | -- = pretty $ take (fromIntegral $ l - (fromIntegral thing)) (repeat '0') 90 | 91 | toDigit :: Integer -> Char 92 | toDigit l = "0123456789ABCDEF" !! fromIntegral l 93 | 94 | toNBase :: Integer -> Integer -> Doc a 95 | l `toNBase` b | l < b = pretty (toDigit l) 96 | | otherwise = let ~(d,e) = l `quotRem` b in toNBase d b <> pretty (toDigit e) 97 | 98 | 99 | -- | Extend a byte into a unicode character 100 | byte2Char :: Word8 -> Char 101 | byte2Char = chr . fromIntegral 102 | 103 | -- | Constrain a unicode character to a byte 104 | -- This assumes the character is in the right range already 105 | char2Byte :: Char -> Word8 106 | char2Byte = fromIntegral . ord 107 | 108 | -- | Escape a byte. Based on @std::ascii::escape_default@. 109 | -- 110 | -- If the first argument is true, newlines may become a literal newline characters if the string is 111 | -- too long. 112 | escapeByte :: Bool -> Word8 -> Doc a 113 | escapeByte nl w8 = case byte2Char w8 of 114 | '\t' -> "\\t" 115 | '\r' -> "\\r" 116 | '\\' -> "\\\\" 117 | '\'' -> "\\'" 118 | '"' -> "\\\"" 119 | '\n'| nl -> flatAlt hardline "\\n" 120 | | otherwise -> "\\n" 121 | c | 0x20 <= w8 && w8 <= 0x7e -> pretty c 122 | _ -> "\\x" <> padHex 2 w8 123 | 124 | -- | Escape a unicode character. Based on @std::ascii::escape_default@. 125 | -- 126 | -- If the first argument is true, newlines may become a literal newline characters if the string is 127 | -- too long. 128 | escapeChar :: Bool -> Char -> Doc a 129 | escapeChar nl c | c <= '\x7f' = escapeByte nl (char2Byte c) 130 | | c <= '\xffff' = "\\u{" <> padHex 4 (ord c) <> "}" 131 | | otherwise = "\\u{" <> padHex 6 (ord c) <> "}" 132 | 133 | -- | Convert a number to its padded hexadecimal form 134 | padHex :: Integral a => Int -> a -> Doc b 135 | padHex i 0 = pretty (replicate i '0') 136 | padHex i m = let (m',r) = m `divMod` 0x10 137 | in padHex (i-1) m' <> pretty (intToDigit (fromIntegral r)) 138 | 139 | -------------------------------------------------------------------------------- /src/Language/Rust/Pretty/Util.hs: -------------------------------------------------------------------------------- 1 | {-| 2 | Module : Language.Rust.Pretty.Util 3 | Description : pretty printing utilities 4 | Copyright : (c) Alec Theriault, 2017-2018 5 | License : BSD-style 6 | Maintainer : alec.theriault@gmail.com 7 | Stability : experimental 8 | Portability : portable 9 | 10 | This module contains a variety of utility functions for pretty printing. Most of these require 11 | inspecting the internal structure of 'Doc'. 12 | 13 | Wadler's take on pretty printing is super useful because it allows us to print thinks like blocks 14 | much more nicely (see [this](http://stackoverflow.com/a/41424946/3072788)) that Hughes'. 15 | Unfortunately, unlike Hughes', Wadler does not have 'mempty' as the identity of @<+>@ - the 16 | space between the arguments of @<+>@ does not go away even if either argument is empty. The 17 | same problem shows up for @hsep@, @<#>@, @vsep@, @@, etc. 18 | 19 | My solution has been to redefine my versions of these functions which _do_ treat 'mempty' as a 20 | neutral element for @<+>@, @hsep@, @<#>@, @vsep@, and @@. 21 | -} 22 | {-# LANGUAGE OverloadedStrings #-} 23 | 24 | module Language.Rust.Pretty.Util where 25 | 26 | import Data.Monoid as M 27 | 28 | import qualified Data.Text.Prettyprint.Doc as PP 29 | import Data.Text.Prettyprint.Doc.Internal.Type ( Doc(..) ) 30 | 31 | import Language.Rust.Syntax.Token ( Delim(..) ) 32 | 33 | -- | Indentation level 34 | n :: Int 35 | n = 2 36 | 37 | -- | Similar to 'maybe', but where the 'Nothing' case is an empty 'Doc' 38 | emptyElim :: Doc a -- ^ result if scrutinee is empty 39 | -> (Doc a -> Doc a) -- ^ how to process scrutinee if it is not empty 40 | -> Doc a -- ^ scrutinee 'Doc' 41 | -> Doc a 42 | emptyElim a _ Empty = a 43 | emptyElim _ f doc = f doc 44 | 45 | -- | Vertically concatenate two 'Doc's with a collapsible line between them 46 | (<##>) :: Doc a -> Doc a -> Doc a 47 | d1 <##> d2 = d1 M.<> PP.line' M.<> d2 48 | 49 | -- | Flatten a 'Doc' 50 | flatten :: Doc a -> Doc a 51 | flatten d@Fail{} = d 52 | flatten d@Empty{} = d 53 | flatten d@Char{} = d 54 | flatten d@Text{} = d 55 | flatten d@Line{} = d 56 | flatten (FlatAlt _ d) = d 57 | flatten (Cat d1 d2) = Cat (flatten d1) (flatten d2) 58 | flatten (Nest i d) = Nest i (flatten d) 59 | flatten (Union d _) = flatten d 60 | flatten (Column f) = Column (flatten . f) 61 | flatten (WithPageWidth f) = WithPageWidth (flatten . f) 62 | flatten (Nesting f) = Nesting (flatten . f) 63 | flatten (Annotated a d) = Annotated a (flatten d) 64 | 65 | -- | Map the list of items into 'Doc's using the provided function and add comma punctuation 66 | commas :: [a] -> (a -> Doc b) -> Doc b 67 | commas xs f = PP.hsep (PP.punctuate "," (map f xs)) 68 | 69 | -- | Take a binary operation on docs and lift it to one that has (left and right) identity 'mempty' 70 | liftOp :: (Doc a -> Doc a -> Doc a) -> Doc a -> Doc a -> Doc a 71 | liftOp _ Empty d = d 72 | liftOp _ d Empty = d 73 | liftOp (#) d d' = d # d' 74 | 75 | -- | Lifted version of Wadler's @<+>@ 76 | (<+>) :: Doc a -> Doc a -> Doc a 77 | (<+>) = liftOp (PP.<+>) 78 | 79 | -- | Lifted version of Wadler's @hsep@ 80 | hsep :: Foldable f => f (Doc a) -> Doc a 81 | hsep = foldr (<+>) mempty 82 | 83 | -- | Lifted version of Wadler's @<#>@ 84 | (<#>) :: Doc a -> Doc a -> Doc a 85 | (<#>) = liftOp (\x y -> x <> PP.line <> y) 86 | 87 | -- | Lifted version of Wadler's @vsep@ 88 | vsep :: Foldable f => f (Doc a) -> Doc a 89 | vsep = foldr (<#>) mempty 90 | 91 | -- | Lifted version of Wadler's @@ 92 | () :: Doc a -> Doc a -> Doc a 93 | () = liftOp (\x y -> x <> PP.softline <> y) 94 | 95 | -- | Unless the condition holds, print the document 96 | unless :: Bool -> Doc a -> Doc a 97 | unless b = when (not b) 98 | 99 | -- | When the condition holds, print the document 100 | when :: Bool -> Doc a -> Doc a 101 | when cond d = if cond then d else mempty 102 | 103 | -- | Apply a printing function to an optional value. If the value is 'Nothing', 'perhaps' returns 104 | -- the empty 'Doc'. 105 | perhaps :: (a -> Doc b) -> Maybe a -> Doc b 106 | perhaps = maybe mempty 107 | 108 | -- | Indent the given 'Doc', but only if multi-line 109 | indent :: Int -> Doc a -> Doc a 110 | indent m doc = PP.flatAlt (PP.indent m doc) (flatten doc) 111 | 112 | -- | Undo what group does. This function is pretty dangerous... 113 | ungroup :: Doc a -> Doc a 114 | ungroup (Union _ x) = x 115 | ungroup y = y 116 | 117 | -- | Remove all indent 118 | noIndent :: Doc a -> Doc a 119 | noIndent d = PP.nesting (\i -> PP.nest (negate i) d) 120 | 121 | -- | Translate '\n' in a string using the provided 'Doc' instead of 'line' 122 | string :: Doc a -> String -> Doc a 123 | string new = foldMap (\c -> case c of { '\n' -> new; _ -> Char c }) 124 | 125 | -- | This is the most general function for printing blocks. It operates with any delimiter, any 126 | -- seperator, an optional leading attribute doc (which isn't followed by a seperator), and wraps a 127 | -- list of entries. It has been tweaked to look Just Right (TM) for the usual cases. 128 | -- 129 | -- Note that this will try to fit things on one line when possible, so if you want a block that is 130 | -- sure /not/ to be condensed on one line (e.g. for a function), you have to construct it manually. 131 | block :: Delim -- ^ outer delimiters 132 | -> Bool -- ^ prefer to be on one line (as opposed to multiline)? 133 | -> Doc a -- ^ seperator 134 | -> Doc a -- ^ attributes doc, after which no seperator will (use 'mempty' to ignore) 135 | -> [Doc a] -- ^ entries 136 | -> Doc a 137 | block delim p s as xs = group' (lDel # PP.vsep (as' ++ ys) # rDel) 138 | where 139 | group' = if p || null (as' ++ ys) then PP.group else id 140 | 141 | -- left and right delimiter lists 142 | (lDel,rDel) = case delim of 143 | Paren -> ("(", ")") 144 | Bracket -> ("[", "]") 145 | Brace -> ("{", "}") 146 | NoDelim -> (mempty, mempty) 147 | 148 | -- method of contenating delimiters with the rest of the body 149 | (#) = case delim of { Paren -> (<##>); _ -> (<#>) } 150 | 151 | -- attributes 152 | as' = case as of 153 | Empty -> [] 154 | _ -> [ PP.flatAlt (PP.indent n as) (flatten as) ] 155 | 156 | -- list of entries 157 | ys = go xs where go [] = [] 158 | go [z] = [ PP.flatAlt (PP.indent n z <> s) (flatten z) ] 159 | go (z:zs) = PP.flatAlt (PP.indent n z <> s) (flatten z <> s) : go zs 160 | 161 | 162 | -------------------------------------------------------------------------------- /src/Language/Rust/Syntax.hs: -------------------------------------------------------------------------------- 1 | {-| 2 | Module : Language.Rust.Syntax 3 | Description : Syntax data defintions 4 | Copyright : (c) Alec Theriault, 2017-2018 5 | License : BSD-style 6 | Maintainer : alec.theriault@gmail.com 7 | Stability : experimental 8 | Portability : GHC 9 | 10 | This module defines Haskell data types corresponding to the abstract syntax tree(s) of the Rust 11 | language, based on the definitions @rustc@ uses (defined in @libsyntax@) whenever possible. 12 | Unfortunately, since the internals of @rustc@ are not exposed, there are no official 13 | docs. are the 14 | unofficial docs. 15 | -} 16 | 17 | module Language.Rust.Syntax ( 18 | -- * Abstract syntax trees 19 | module Language.Rust.Syntax.AST, 20 | -- * Tokens 21 | module Language.Rust.Syntax.Token, 22 | ) where 23 | 24 | import Language.Rust.Syntax.AST 25 | import Language.Rust.Syntax.Token 26 | 27 | -- Using import/export shortcut screws up Haddock 28 | {-# ANN module "HLint: ignore Use import/export shortcut" #-} 29 | 30 | -------------------------------------------------------------------------------- /src/Language/Rust/Syntax/Token.hs-boot: -------------------------------------------------------------------------------- 1 | -- .hs-boot files play badly with associated type families like 'Rep' 2 | {-# LANGUAGE CPP #-} 3 | #if __GLASGOW_HASKELL__ >= 800 4 | {-# OPTIONS_GHC -Wno-missing-methods #-} 5 | #endif 6 | 7 | module Language.Rust.Syntax.Token where 8 | 9 | import GHC.Generics (Generic) 10 | import Data.Data (Data) 11 | -- import Data.Typeable (Typeable) 12 | import Control.DeepSeq (NFData) 13 | 14 | data LitTok 15 | 16 | data Token 17 | instance Eq Token 18 | instance Ord Token 19 | instance Show Token 20 | instance Data Token 21 | -- instance Typeable Token 22 | instance Generic Token 23 | instance NFData Token 24 | 25 | data Delim 26 | instance Eq Delim 27 | instance Ord Delim 28 | instance Data Delim 29 | -- instance Typeable Delim 30 | instance Generic Delim 31 | instance Show Delim 32 | instance NFData Delim 33 | 34 | data Space 35 | instance NFData Space 36 | instance Eq Space 37 | instance Ord Space 38 | 39 | -------------------------------------------------------------------------------- /stack.yaml: -------------------------------------------------------------------------------- 1 | #resolver: lts-11.14 2 | #resolver: nightly-2018-10-31 3 | 4 | #resolver: lts-13.12 5 | #resolver: lts-13.29 6 | #resolver: lts-14.6 7 | #resolver: lts-11.14 8 | #resolver: lts-14.21 9 | resolver: lts-16.0 10 | 11 | packages: 12 | - . 13 | #- ../../../antlr-haskell 14 | #- location: 15 | # git: git@github.com:cronburg/antlr-haskell.git 16 | # commit: 3e428f7 17 | #extra-dep: true 18 | #- location: 19 | # git: https://github.com/harpocrates/language-rust 20 | # commit: d32e57f 21 | #extra-dep: true 22 | #- ./deps/language-rust 23 | 24 | extra-deps: [] 25 | flags: {} 26 | extra-package-dbs: [] 27 | 28 | -------------------------------------------------------------------------------- /test/parser/Main.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DeriveAnyClass, DeriveGeneric, TypeFamilies, QuasiQuotes 2 | , DataKinds, ScopedTypeVariables, OverloadedStrings, TypeSynonymInstances 3 | , FlexibleInstances, UndecidableInstances, FlexibleContexts, TemplateHaskell 4 | , DeriveDataTypeable, BangPatterns, UnicodeSyntax #-} 5 | module Main where 6 | import Test.Framework 7 | import Test.Framework.Providers.HUnit 8 | import Test.HUnit 9 | --import Test.Framework.Providers.QuickCheck2 10 | --import Test.QuickCheck (Property, quickCheck, (==>)) 11 | 12 | import Language.Floorplan.Syntax 13 | import Language.Floorplan.Token 14 | import qualified Language.Floorplan.Parser as P 15 | import System.IO (openFile, IOMode(..), hGetContents) 16 | import qualified Debug.Trace as D 17 | import Data.List (sort) 18 | 19 | import Language.Rust.Pretty (pretty) 20 | import Language.Floorplan.Core.Compiler 21 | import qualified Language.Floorplan.Rust as R 22 | 23 | {- 24 | testTokenizes fn = testCase fn $ do 25 | fd <- openFile fn ReadMode 26 | contents <- hGetContents fd 27 | let !xs = show $ P.tokenize contents 28 | return () 29 | 30 | testParses fn = testCase fn $ do 31 | fd <- openFile fn ReadMode 32 | contents <- hGetContents fd 33 | let !xs = P.parseFloorplanDemarcsGLR fn 0 0 contents 34 | return () 35 | 36 | testParsesExp fn expected = testCase fn $ do 37 | fd <- openFile fn ReadMode 38 | contents <- hGetContents fd 39 | let xs = P.parseFloorplanDemarcsGLR fn 0 0 contents 40 | assertEqual "Not equal" xs expected 41 | -} 42 | 43 | -- Happy parser generator versions: 44 | 45 | readme fn = do 46 | fd <- openFile fn ReadMode 47 | hGetContents fd 48 | 49 | mkTest fn cont = testCase fn $ readme fn >>= cont 50 | 51 | testTokenizes fn = mkTest fn $ \contents -> do 52 | let !xs = show $ scanTokens contents 53 | return () 54 | 55 | testParses fn = mkTest fn $ \contents -> do 56 | let !xs = P.parseLayers contents 57 | return () 58 | 59 | testGrafts fn = mkTest fn $ \contents -> do 60 | let !xs = P.parseLayers contents 61 | let !ys = map (grafting xs) xs 62 | countGrafting ys @?= 0 63 | return () 64 | 65 | testParsesExp fn expected = mkTest fn $ \contents -> 66 | expected @=? P.parseLayers contents 67 | 68 | test_b2i = 69 | assertEqual "incorrect binary value" 70 | (bin2int "0b1010") 71 | 10 72 | 73 | test_b2i' = 74 | assertEqual "incorrect binary value" 75 | (bin2int "0b000000") 76 | 0 77 | 78 | tokenizeSuite = testGroup "tokenization suite" 79 | [ testTokenizes "examples/empty.flp" 80 | ] 81 | 82 | parseSuite = testGroup "parsing suite" 83 | [ testParses "examples/immix/layout.flp" 84 | , testParses "examples/empty.flp" 85 | , testParses "examples/seq.flp" 86 | , testParses "examples/enum.flp" 87 | , testParses "examples/union.flp" 88 | , testParses "examples/layer.flp" 89 | , testParses "examples/bits.flp" 90 | , testParses "examples/arith_id.flp" 91 | , testParses "examples/arith_power.flp" 92 | , testParses "examples/bump.flp" 93 | , testParses "examples/dynamic_prim.flp" 94 | , testParses "examples/dyn_choice.flp" 95 | , testParses "examples/dyn_seq.flp" 96 | -- , testNotParses "examples/enum_bad0.flp" 97 | , testParses "examples/map_bits.flp" 98 | , testParses "examples/named_ref.flp" 99 | , testParses "examples/nested.flp" 100 | , testParses "examples/nested_union.flp" 101 | , testParses "examples/app.flp" 102 | , testParses "examples/parens.flp" 103 | , testParsesExp "examples/arith.flp" theLayer 104 | ] 105 | 106 | theLayer = 107 | [ Layer 108 | { name = "Arith" 109 | , formals = [] 110 | , magnitude = Just (SizeLit (Just (Lit 7)) Byte) 111 | , alignment = Nothing 112 | , magAlign = Nothing 113 | , contains = [] 114 | , rhs = Blob (SizeLit 115 | (Just (Plus 116 | (Times (Div (Lit 4) (Lit 2)) 117 | (Lit 3)) 118 | (Lit 1))) 119 | Byte 120 | ) 121 | } 122 | ] 123 | 124 | graftSuite = testGroup "grafting suite" 125 | [ testGrafts "examples/immix/layout.flp" 126 | , testCase "empty unique" $ [] @=? (checkUniqNames theLayer) 127 | , mkTest "examples/uniq_fail.flp" $ \c -> (sort ["foo", "Outer"]) @=? (sort $ checkUniqNames (P.parseLayers c)) 128 | ] 129 | 130 | testCompiles fn = mkTest fn $ \contents -> do 131 | let !xs = P.parseLayers contents 132 | let !ys = map (grafting xs) xs 133 | countGrafting ys @?= 0 134 | let !zs = D.traceShowId $ map compile ys 135 | let !rust = D.traceShowId $ R.genRust zs 136 | let !_ = D.traceShowId $ pretty rust 137 | return () 138 | 139 | compileSuite = testGroup "compiling suite" 140 | [ testCase "enumBytes A" $ enumBytes (take 0 $ repeat "") @?= 0 -- Null enum requires no space. 141 | , testCase "enumBytes B" $ enumBytes (take 1 $ repeat "") @?= 1 -- Singletone requires space. 142 | , testCase "enumBytes C" $ enumBytes (take 128 $ repeat "") @?= 1 -- Most enums take 1 bytes. 143 | , testCase "enumBytes D" $ enumBytes (take 256 $ repeat "") @?= 1 -- Including this one. 144 | , testCase "enumBytes E" $ enumBytes (take 257 $ repeat "") @?= 2 -- But not this one. 145 | , testCase "enumBytes F" $ enumBytes (take (256*256) $ repeat "") @?= 2 -- Not this one either. 146 | , testCase "enumBytes G" $ enumBytes (take (256*256 + 1) $ repeat "") @?= 3 -- And definitely not this one. 147 | , testCase "enumBytes H" $ enumBytes (take (256*256*256 + 1) $ repeat "") @?= 4 -- And so on. 148 | , testCompiles "examples/immix/layout.flp" 149 | , testCase "delta_bytes round small" $ delta_bytes (SizeLit (Just $ Lit $ 8*3-1) Bit) @?= 3 150 | , testCase "delta_bytes no rounding" $ delta_bytes (SizeLit (Just $ Lit $ 8*3 ) Bit) @?= 3 151 | , testCase "delta_bytes round large" $ delta_bytes (SizeLit (Just $ Lit $ 8*3+1) Bit) @?= 4 152 | ] 153 | 154 | main :: IO () 155 | main = defaultMainWithOpts 156 | [ testCase "Binary conversion" test_b2i 157 | , testCase "Binary conversion" test_b2i' 158 | , tokenizeSuite 159 | , parseSuite 160 | , graftSuite 161 | , compileSuite 162 | ] mempty 163 | 164 | --------------------------------------------------------------------------------