Repository: luc-tielen/eclair-lang Branch: main Commit: 5254a56ef14e Files: 183 Total size: 857.3 KB Directory structure: gitextract_d5o1d0tz/ ├── .dockerignore ├── .ghci ├── .github/ │ ├── FUNDING.yml │ └── workflows/ │ ├── build.yml │ └── ci.yml ├── .gitignore ├── .hlint.yaml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── cabal.project ├── cbits/ │ └── semantic_analysis.dl ├── docs/ │ ├── architecture_choices.md │ └── getting_started.md ├── eclair-lang.cabal ├── hie.yaml ├── lib/ │ ├── Eclair/ │ │ ├── AST/ │ │ │ ├── Analysis.hs │ │ │ ├── Codegen.hs │ │ │ ├── IR.hs │ │ │ ├── Lower.hs │ │ │ ├── Transforms/ │ │ │ │ ├── ConstantFolding.hs │ │ │ │ ├── DeadCodeElimination.hs │ │ │ │ ├── NormalizeRules.hs │ │ │ │ ├── RemoveAliases.hs │ │ │ │ └── ReplaceStrings.hs │ │ │ └── Transforms.hs │ │ ├── ArgParser.hs │ │ ├── Common/ │ │ │ ├── Config.hs │ │ │ ├── Extern.hs │ │ │ ├── Id.hs │ │ │ ├── Literal.hs │ │ │ ├── Location.hs │ │ │ ├── Operator.hs │ │ │ └── Pretty.hs │ │ ├── Comonads.hs │ │ ├── EIR/ │ │ │ ├── IR.hs │ │ │ ├── Lower/ │ │ │ │ ├── API.hs │ │ │ │ ├── Codegen.hs │ │ │ │ └── Externals.hs │ │ │ └── Lower.hs │ │ ├── Error.hs │ │ ├── JSON.hs │ │ ├── LLVM/ │ │ │ ├── Allocator/ │ │ │ │ ├── Arena.hs │ │ │ │ ├── Common.hs │ │ │ │ ├── Malloc.hs │ │ │ │ └── Page.hs │ │ │ ├── BTree/ │ │ │ │ ├── Bounds.hs │ │ │ │ ├── Compare.hs │ │ │ │ ├── Create.hs │ │ │ │ ├── Destroy.hs │ │ │ │ ├── Find.hs │ │ │ │ ├── Insert.hs │ │ │ │ ├── Iterator.hs │ │ │ │ ├── Size.hs │ │ │ │ └── Types.hs │ │ │ ├── BTree.hs │ │ │ ├── Codegen.hs │ │ │ ├── Config.hs │ │ │ ├── Externals.hs │ │ │ ├── Hash.hs │ │ │ ├── HashMap.hs │ │ │ ├── Metadata.hs │ │ │ ├── Symbol.hs │ │ │ ├── SymbolTable.hs │ │ │ ├── Table.hs │ │ │ ├── Template.hs │ │ │ └── Vector.hs │ │ ├── LSP/ │ │ │ ├── Handlers/ │ │ │ │ ├── Diagnostics.hs │ │ │ │ ├── DocumentHighlight.hs │ │ │ │ └── Hover.hs │ │ │ ├── Handlers.hs │ │ │ ├── JSON.hs │ │ │ ├── Monad.hs │ │ │ ├── Types.hs │ │ │ └── VFS.hs │ │ ├── LSP.hs │ │ ├── Parser.hs │ │ ├── RA/ │ │ │ ├── Codegen.hs │ │ │ ├── IR.hs │ │ │ ├── IndexSelection.hs │ │ │ ├── Lower.hs │ │ │ ├── Transforms/ │ │ │ │ └── HoistConstraints.hs │ │ │ └── Transforms.hs │ │ ├── Souffle/ │ │ │ └── IR.hs │ │ ├── Transform.hs │ │ └── TypeSystem.hs │ ├── Eclair.hs │ └── Prelude.hs ├── src/ │ └── eclair/ │ └── Main.hs └── tests/ ├── .gitignore ├── ast_transforms/ │ ├── constant_folding.eclair │ ├── copy_propagation.eclair │ ├── dead_code_elimination.eclair │ ├── remove_contradictions.eclair │ └── shift_assignments.eclair ├── check.sh ├── eclair/ │ ├── Test/ │ │ └── Eclair/ │ │ ├── ArgParserSpec.hs │ │ ├── JSONSpec.hs │ │ ├── LLVM/ │ │ │ ├── Allocator/ │ │ │ │ ├── MallocSpec.hs │ │ │ │ ├── PageSpec.hs │ │ │ │ └── Utils.hs │ │ │ ├── BTreeSpec.hs │ │ │ ├── HashMapSpec.hs │ │ │ ├── HashSpec.hs │ │ │ ├── SymbolSpec.hs │ │ │ ├── SymbolTableSpec.hs │ │ │ ├── SymbolUtils.hs │ │ │ └── VectorSpec.hs │ │ ├── LSP/ │ │ │ ├── HandlersSpec.hs │ │ │ └── JSONSpec.hs │ │ └── RA/ │ │ └── IndexSelectionSpec.hs │ ├── fixtures/ │ │ └── lsp/ │ │ ├── document_highlight.eclair │ │ ├── hover.eclair │ │ ├── invalid_syntax.eclair │ │ ├── semantic_errors.eclair │ │ ├── type_errors.eclair │ │ └── unparsable.eclair │ └── test.hs ├── end_to_end/ │ ├── compile_and_run_native.eclair │ ├── compile_and_run_wasm.eclair │ └── compile_and_run_with_extern.eclair ├── hello.eclair ├── lit.cfg ├── lowering/ │ ├── arithmetic.eclair │ ├── clause_with_same_vars.eclair │ ├── comparisons.eclair │ ├── different_types.eclair │ ├── extern_definitions.eclair │ ├── multiple_clauses_same_name.eclair │ ├── multiple_rule_clauses.eclair │ ├── mutually_recursive_rules.eclair │ ├── negation.eclair │ ├── negation_with_wildcards.eclair │ ├── no_top_level_facts.eclair │ ├── recursive_mix_of_rules.eclair │ ├── single_non_recursive_rule.eclair │ ├── single_recursive_rule.eclair │ ├── stratification.eclair │ ├── top_level_facts.eclair │ └── wasm_codegen.eclair ├── parser/ │ ├── error_recovery.eclair │ ├── file_not_found.eclair │ └── valid.eclair ├── runtime/ │ ├── hashmap_test.eclair │ ├── symbol_table_test.eclair │ └── vector_test.eclair ├── semantic_analysis/ │ ├── cyclic_negation.eclair │ ├── dead_internal_relation.eclair │ ├── invalid_extern_usage.eclair │ ├── invalid_options_usage.eclair │ ├── invalid_wildcard_usage.eclair │ ├── no_output_relations.eclair │ ├── unconstrained_variables.eclair │ ├── ungrounded_variables.eclair │ ├── ungrounded_variables_arithmetic.eclair │ ├── ungrounded_variables_comparisons.eclair │ └── ungrounded_variables_negations.eclair ├── string_support/ │ ├── encode_decode_string.eclair │ ├── encode_decode_string_native.eclair │ └── encode_decode_string_wasm.eclair ├── transpilation/ │ └── souffle.eclair ├── typesystem/ │ ├── arg_count_mismatch.eclair │ ├── arithmetic.eclair │ ├── comparisons.eclair │ ├── duplicate_type_declarations.eclair │ ├── extern_definitions.eclair │ ├── negation.eclair │ ├── no_rules_for_type.eclair │ ├── type_mismatch_in_rule.eclair │ ├── type_mismatch_in_rule_body.eclair │ ├── type_mismatch_in_rule_head.eclair │ ├── type_mismatch_top_level_atoms.eclair │ ├── typed_holes.eclair │ ├── unification_failure.eclair │ ├── unknown_atom_in_rule_body.eclair │ ├── unknown_atom_in_rule_head.eclair │ ├── unknown_atoms.eclair │ ├── unknown_top_level_atoms.eclair │ └── valid.eclair └── utils/ └── extract_snippet ================================================ FILE CONTENTS ================================================ ================================================ FILE: .dockerignore ================================================ .direnv/ .git/ .github/ dist/ dist-newstyle/ result/ Dockerfile .dockerignore .envrc .ghci .gitignore ./*.ll ./*.o ./*.a ./*.wasm ./*.eclair ./*.dl ./logo* hie.yaml ================================================ FILE: .ghci ================================================ :set prompt > ================================================ FILE: .github/FUNDING.yml ================================================ github: luc-tielen ================================================ FILE: .github/workflows/build.yml ================================================ name: "Build" on: [push, pull_request] jobs: build: strategy: matrix: os: [ubuntu-latest] runs-on: ${{matrix.os}} steps: - uses: actions/checkout@v3 - name: Build and test run: | set -eo pipefail export TIMESTAMP=$(date +%s) docker build -f Dockerfile . -t eclair:$TIMESTAMP | tee eclair-lang-${{matrix.os}}.log docker run --rm eclair:$TIMESTAMP bash -c "make test" | tee -a eclair-lang-${{matrix.os}}.log - name: Check for disabled tests run: | ./tests/check.sh - name: Upload logs if: ${{ always() }} uses: actions/upload-artifact@v3 with: name: eclair-lang-${{matrix.os}}.log path: eclair-lang-${{matrix.os}}.log ================================================ FILE: .github/workflows/ci.yml ================================================ name: lint on: pull_request: push: branches: - main - "releases/*" jobs: hlint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: "Set up HLint" uses: rwe/actions-hlint-setup@v1 with: version: "3.6.1" - name: "Run HLint" uses: rwe/actions-hlint-run@v2 with: path: '["lib/", "src/", "tests/"]' fail-on: warning ================================================ FILE: .gitignore ================================================ dist-newstyle/ dist/ .direnv/ .devcontainer/ cabal.project.local* *.ll *.bc *.o *.a *.s /*.wasm /*.dl /*.eclair /*.js result eclair.prof eclair.svg *.eventlog* eclair.hp perf.data perf.data.old perf.svg TODO* ================================================ FILE: .hlint.yaml ================================================ # HLint configuration file # https://github.com/ndmitchell/hlint ########################## # This file contains a template configuration file, which is typically # placed as .hlint.yaml in the root of your project # Specify additional command line arguments # # - arguments: [--color, --cpp-simple, -XQuasiQuotes] # Control which extensions/flags/modules/functions can be used # # - extensions: # - default: false # all extension are banned by default # - name: [PatternGuards, ViewPatterns] # only these listed extensions can be used # - {name: CPP, within: CrossPlatform} # CPP can only be used in a given module # # - flags: # - {name: -w, within: []} # -w is allowed nowhere # # - modules: # - {name: [Data.Set, Data.HashSet], as: Set} # if you import Data.Set qualified, it must be as 'Set' # - {name: Control.Arrow, within: []} # Certain modules are banned entirely # # - functions: # - {name: unsafePerformIO, within: []} # unsafePerformIO can only appear in no modules - arguments: - "-XRecursiveDo" # Add custom hints for this project # # Will suggest replacing "wibbleMany [myvar]" with "wibbleOne myvar" # - error: {lhs: "wibbleMany [x]", rhs: wibbleOne x} # The hints are named by the string they display in warning messages. # For example, if you see a warning starting like # # Main.hs:116:51: Warning: Redundant == # # You can refer to that hint with `{name: Redundant ==}` (see below). # Turn on hints that are off by default # # Ban "module X(module X) where", to require a real export list # - warn: {name: Use explicit module export list} # # Replace a $ b $ c with a . b $ c # - group: {name: dollar, enabled: true} # # Generalise map to fmap, ++ to <> # - group: {name: generalise, enabled: true} # Ignore some builtin hints # - ignore: {name: Use let} # - ignore: {name: Use const, within: SpecialModule} # Only within certain modules - ignore: {name: Reduce duplication} # Define some custom infix operators # - fixity: infixr 3 ~^#^~ # To generate a suitable file for HLint do: # $ hlint --default > .hlint.yaml # Relude-specific (https://github.com/kowainik/relude/blob/main/.hlint.yaml) - arguments: - "-XConstraintKinds" - "-XDeriveGeneric" - "-XGeneralizedNewtypeDeriving" - "-XLambdaCase" - "-XOverloadedStrings" - "-XRecordWildCards" - "-XScopedTypeVariables" - "-XStandaloneDeriving" - "-XTupleSections" - "-XTypeApplications" - "-XViewPatterns" - ignore: name: Use head - ignore: name: Use Foldable.forM_ - hint: lhs: "pure ()" note: "Use 'pass'" rhs: pass - hint: lhs: "return ()" note: "Use 'pass'" rhs: pass - hint: lhs: "(: [])" note: "Use `one`" rhs: one - hint: lhs: "(:| [])" note: "Use `one`" rhs: one - hint: lhs: Data.Sequence.singleton note: "Use `one`" rhs: one - hint: lhs: Data.Text.singleton note: "Use `one`" rhs: one - hint: lhs: Data.Text.Lazy.singleton note: "Use `one`" rhs: one - hint: lhs: Data.ByteString.singleton note: "Use `one`" rhs: one - hint: lhs: Data.ByteString.Lazy.singleton note: "Use `one`" rhs: one - hint: lhs: Data.Map.singleton note: "Use `one`" rhs: one - hint: lhs: Data.Map.Strict.singleton note: "Use `one`" rhs: one - hint: lhs: Data.HashMap.Strict.singleton note: "Use `one`" rhs: one - hint: lhs: Data.HashMap.Lazy.singleton note: "Use `one`" rhs: one - hint: lhs: Data.IntMap.singleton note: "Use `one`" rhs: one - hint: lhs: Data.IntMap.Strict.singleton note: "Use `one`" rhs: one - hint: lhs: Data.Set.singleton note: "Use `one`" rhs: one - hint: lhs: Data.HashSet.singleton note: "Use `one`" rhs: one - hint: lhs: Data.IntSet.singleton note: "Use `one`" rhs: one - warn: lhs: Control.Exception.evaluate rhs: evaluateWHNF - warn: lhs: "Control.Exception.evaluate (force x)" rhs: evaluateNF x - warn: lhs: "Control.Exception.evaluate (x `deepseq` ())" rhs: evaluateNF_ x - warn: lhs: "void (evaluateWHNF x)" rhs: evaluateWHNF_ x - warn: lhs: "void (evaluateNF x)" rhs: evaluateNF_ x - hint: lhs: Control.Exception.throw note: "Use 'impureThrow'" rhs: impureThrow - warn: lhs: Data.Text.IO.readFile rhs: readFileText - warn: lhs: Data.Text.IO.writeFile rhs: writeFileText - warn: lhs: Data.Text.IO.appendFile rhs: appendFileText - warn: lhs: Data.Text.Lazy.IO.readFile rhs: readFileLText - warn: lhs: Data.Text.Lazy.IO.writeFile rhs: writeFileLText - warn: lhs: Data.Text.Lazy.IO.appendFile rhs: appendFileLText - warn: lhs: Data.ByteString.readFile rhs: readFileBS - warn: lhs: Data.ByteString.writeFile rhs: writeFileBS - warn: lhs: Data.ByteString.appendFile rhs: appendFileBS - warn: lhs: Data.ByteString.Lazy.readFile rhs: readFileLBS - warn: lhs: Data.ByteString.Lazy.writeFile rhs: writeFileLBS - warn: lhs: Data.ByteString.Lazy.appendFile rhs: appendFileLBS - hint: lhs: "foldl' (flip f)" note: "Use 'flipfoldl''" rhs: "flipfoldl' f" - warn: lhs: "foldl' (+) 0" rhs: sum - warn: lhs: "foldl' (*) 1" rhs: product - hint: lhs: "fmap and (sequence s)" note: Applying this hint would mean that some actions that were being executed previously would no longer be executed. rhs: andM s - hint: lhs: "and <$> sequence s" note: Applying this hint would mean that some actions that were being executed previously would no longer be executed. rhs: andM s - hint: lhs: "fmap or (sequence s)" note: Applying this hint would mean that some actions that were being executed previously would no longer be executed. rhs: orM s - hint: lhs: "or <$> sequence s" note: Applying this hint would mean that some actions that were being executed previously would no longer be executed. rhs: orM s - hint: lhs: "fmap and (mapM f s)" note: Applying this hint would mean that some actions that were being executed previously would no longer be executed. rhs: allM f s - hint: lhs: "and <$> mapM f s" note: Applying this hint would mean that some actions that were being executed previously would no longer be executed. rhs: allM f s - hint: lhs: "fmap or (mapM f s)" note: Applying this hint would mean that some actions that were being executed previously would no longer be executed. rhs: anyM f s - hint: lhs: "or <$> mapM f s" note: Applying this hint would mean that some actions that were being executed previously would no longer be executed. rhs: anyM f s - warn: lhs: "getAlt (foldMap (Alt . f) xs)" rhs: asumMap xs - warn: lhs: "getAlt . foldMap (Alt . f)" rhs: asumMap - hint: lhs: "foldr (\\x acc -> f x <|> acc) empty" note: "Use 'asumMap'" rhs: asumMap f - hint: lhs: "asum (map f xs)" note: "Use 'asumMap'" rhs: asumMap f xs - warn: lhs: "map fst &&& map snd" rhs: unzip - hint: lhs: "fmap (fmap f) x" note: "Use '(<<$>>)'" rhs: "f <<$>> x" - hint: lhs: "(\\f -> f x) <$> ff" note: Use flap operator rhs: "ff ?? x" - hint: lhs: "fmap (\\f -> f x) ff" note: Use flap operator rhs: "ff ?? x" - hint: lhs: "fmap ($ x) ff" note: Use flap operator rhs: "ff ?? x" - hint: lhs: "($ x) <$> ff" note: Use flap operator rhs: "ff ?? x" - warn: lhs: "fmap f (nonEmpty x)" rhs: viaNonEmpty f x - warn: lhs: fmap f . nonEmpty rhs: viaNonEmpty f - warn: lhs: "f <$> nonEmpty x" rhs: viaNonEmpty f x - warn: lhs: partitionEithers . map f rhs: partitionWith f - warn: lhs: partitionEithers $ map f x rhs: partitionWith f x - warn: lhs: "f >>= guard" rhs: guardM f - warn: lhs: guard =<< f rhs: guardM f - warn: lhs: forever note: "'forever' is loosely typed and may hide errors" rhs: infinitely - warn: lhs: "whenM (not <$> x)" rhs: unlessM x - warn: lhs: "unlessM (not <$> x)" rhs: whenM x - warn: lhs: "either (const True) (const False)" rhs: isLeft - warn: lhs: "either (const False) (const True)" rhs: isRight - warn: lhs: "either id (const a)" rhs: fromLeft a - warn: lhs: "either (const b) id" rhs: fromRight b - warn: lhs: "either Just (const Nothing)" rhs: leftToMaybe - warn: lhs: "either (const Nothing) Just" rhs: rightToMaybe - warn: lhs: "maybe (Left l) Right" rhs: maybeToRight l - warn: lhs: "maybe (Right r) Left" rhs: maybeToLeft r - warn: lhs: "case m of Just x -> f x; Nothing -> pure ()" rhs: whenJust m f - warn: lhs: "case m of Just x -> f x; Nothing -> return ()" rhs: whenJust m f - warn: lhs: "case m of Just x -> f x; Nothing -> pass" rhs: whenJust m f - warn: lhs: "case m of Nothing -> pure () ; Just x -> f x" rhs: whenJust m f - warn: lhs: "case m of Nothing -> return (); Just x -> f x" rhs: whenJust m f - warn: lhs: "case m of Nothing -> pass ; Just x -> f x" rhs: whenJust m f - warn: lhs: "maybe (pure ()) f m" rhs: whenJust m f - warn: lhs: "maybe (return ()) f m" rhs: whenJust m f - warn: lhs: maybe pass f m rhs: whenJust m f - warn: lhs: "m >>= \\a -> whenJust a f" rhs: whenJustM m f - warn: lhs: "m >>= \\case Just x -> f x; Nothing -> pure ()" rhs: whenJustM m f - warn: lhs: "m >>= \\case Just x -> f x; Nothing -> return ()" rhs: whenJustM m f - warn: lhs: "m >>= \\case Just x -> f x; Nothing -> pass" rhs: whenJustM m f - warn: lhs: "m >>= \\case Nothing -> pure () ; Just x -> f x" rhs: whenJustM m f - warn: lhs: "m >>= \\case Nothing -> return (); Just x -> f x" rhs: whenJustM m f - warn: lhs: "m >>= \\case Nothing -> pass ; Just x -> f x" rhs: whenJustM m f - warn: lhs: "maybe (pure ()) f =<< m" rhs: whenJustM m f - warn: lhs: "maybe (return ()) f =<< m" rhs: whenJustM m f - warn: lhs: maybe pass f =<< m rhs: whenJustM m f - warn: lhs: "m >>= maybe (pure ()) f" rhs: whenJustM m f - warn: lhs: "m >>= maybe (return ()) f" rhs: whenJustM m f - warn: lhs: "m >>= maybe pass f" rhs: whenJustM m f - warn: lhs: "case m of Just _ -> pure () ; Nothing -> x" rhs: whenNothing_ m x - warn: lhs: "case m of Just _ -> return (); Nothing -> x" rhs: whenNothing_ m x - warn: lhs: "case m of Just _ -> pass ; Nothing -> x" rhs: whenNothing_ m x - warn: lhs: "case m of Nothing -> x; Just _ -> pure ()" rhs: whenNothing_ m x - warn: lhs: "case m of Nothing -> x; Just _ -> return ()" rhs: whenNothing_ m x - warn: lhs: "case m of Nothing -> x; Just _ -> pass" rhs: whenNothing_ m x - warn: lhs: "maybe x (\\_ -> pure () ) m" rhs: whenNothing_ m x - warn: lhs: "maybe x (\\_ -> return () ) m" rhs: whenNothing_ m x - warn: lhs: "maybe x (\\_ -> pass ) m" rhs: whenNothing_ m x - warn: lhs: "maybe x (const (pure () )) m" rhs: whenNothing_ m x - warn: lhs: "maybe x (const (return ())) m" rhs: whenNothing_ m x - warn: lhs: "maybe x (const pass) m" rhs: whenNothing_ m x - warn: lhs: "m >>= \\a -> whenNothing_ a x" rhs: whenNothingM_ m x - warn: lhs: "m >>= \\case Just _ -> pure () ; Nothing -> x" rhs: whenNothingM_ m x - warn: lhs: "m >>= \\case Just _ -> return (); Nothing -> x" rhs: whenNothingM_ m x - warn: lhs: "m >>= \\case Just _ -> pass ; Nothing -> x" rhs: whenNothingM_ m x - warn: lhs: "m >>= \\case Nothing -> x; Just _ -> pure ()" rhs: whenNothingM_ m x - warn: lhs: "m >>= \\case Nothing -> x; Just _ -> return ()" rhs: whenNothingM_ m x - warn: lhs: "m >>= \\case Nothing -> x; Just _ -> pass" rhs: whenNothingM_ m x - warn: lhs: "maybe x (\\_ -> pure () ) =<< m" rhs: whenNothingM_ m x - warn: lhs: "maybe x (\\_ -> return () ) =<< m" rhs: whenNothingM_ m x - warn: lhs: "maybe x (\\_ -> pass ) =<< m" rhs: whenNothingM_ m x - warn: lhs: "maybe x (const (pure () )) =<< m" rhs: whenNothingM_ m x - warn: lhs: "maybe x (const (return ())) =<< m" rhs: whenNothingM_ m x - warn: lhs: "maybe x (const pass) =<< m" rhs: whenNothingM_ m x - warn: lhs: "m >>= maybe x (\\_ -> pure ())" rhs: whenNothingM_ m x - warn: lhs: "m >>= maybe x (\\_ -> return ())" rhs: whenNothingM_ m x - warn: lhs: "m >>= maybe x (\\_ -> pass)" rhs: whenNothingM_ m x - warn: lhs: "m >>= maybe x (const (pure ()) )" rhs: whenNothingM_ m x - warn: lhs: "m >>= maybe x (const (return ()))" rhs: whenNothingM_ m x - warn: lhs: "m >>= maybe x (const pass)" rhs: whenNothingM_ m x - warn: lhs: "whenLeft ()" rhs: whenLeft_ - warn: lhs: "case m of Left x -> f x; Right _ -> pure ()" rhs: whenLeft_ m f - warn: lhs: "case m of Left x -> f x; Right _ -> return ()" rhs: whenLeft_ m f - warn: lhs: "case m of Left x -> f x; Right _ -> pass" rhs: whenLeft_ m f - warn: lhs: "case m of Right _ -> pure () ; Left x -> f x" rhs: whenLeft_ m f - warn: lhs: "case m of Right _ -> return (); Left x -> f x" rhs: whenLeft_ m f - warn: lhs: "case m of Right _ -> pass ; Left x -> f x" rhs: whenLeft_ m f - warn: lhs: "either f (\\_ -> pure () ) m" rhs: whenLeft_ m f - warn: lhs: "either f (\\_ -> return () ) m" rhs: whenLeft_ m f - warn: lhs: "either f (\\_ -> pass ) m" rhs: whenLeft_ m f - warn: lhs: "either f (const (pure () )) m" rhs: whenLeft_ m f - warn: lhs: "either f (const (return ())) m" rhs: whenLeft_ m f - warn: lhs: "either f (const pass) m" rhs: whenLeft_ m f - warn: lhs: "m >>= \\a -> whenLeft_ a f" rhs: whenLeftM_ m f - warn: lhs: "m >>= \\case Left x -> f x; Right _ -> pure ()" rhs: whenLeftM_ m f - warn: lhs: "m >>= \\case Left x -> f x; Right _ -> return ()" rhs: whenLeftM_ m f - warn: lhs: "m >>= \\case Left x -> f x; Right _ -> pass" rhs: whenLeftM_ m f - warn: lhs: "m >>= \\case Right _ -> pure () ; Left x -> f x" rhs: whenLeftM_ m f - warn: lhs: "m >>= \\case Right _ -> return (); Left x -> f x" rhs: whenLeftM_ m f - warn: lhs: "m >>= \\case Right _ -> pass ; Left x -> f x" rhs: whenLeftM_ m f - warn: lhs: "either f (\\_ -> pure () ) =<< m" rhs: whenLeftM_ m f - warn: lhs: "either f (\\_ -> return () ) =<< m" rhs: whenLeftM_ m f - warn: lhs: "either f (\\_ -> pass ) =<< m" rhs: whenLeftM_ m f - warn: lhs: "either f (const (pure () )) =<< m" rhs: whenLeftM_ m f - warn: lhs: "either f (const (return ())) =<< m" rhs: whenLeftM_ m f - warn: lhs: "either f (const pass) =<< m" rhs: whenLeftM_ m f - warn: lhs: "m >>= either f (\\_ -> pure ())" rhs: whenLeftM_ m f - warn: lhs: "m >>= either f (\\_ -> return ())" rhs: whenLeftM_ m f - warn: lhs: "m >>= either f (\\_ -> pass)" rhs: whenLeftM_ m f - warn: lhs: "m >>= either f (const (pure ()) )" rhs: whenLeftM_ m f - warn: lhs: "m >>= either f (const (return ()))" rhs: whenLeftM_ m f - warn: lhs: "m >>= either f (const pass)" rhs: whenLeftM_ m f - warn: lhs: "whenRight ()" rhs: whenRight_ - warn: lhs: "case m of Right x -> f x; Left _ -> pure ()" rhs: whenRight_ m f - warn: lhs: "case m of Right x -> f x; Left _ -> return ()" rhs: whenRight_ m f - warn: lhs: "case m of Right x -> f x; Left _ -> pass" rhs: whenRight_ m f - warn: lhs: "case m of Left _ -> pure () ; Right x -> f x" rhs: whenRight_ m f - warn: lhs: "case m of Left _ -> return (); Right x -> f x" rhs: whenRight_ m f - warn: lhs: "case m of Left _ -> pass ; Right x -> f x" rhs: whenRight_ m f - warn: lhs: "either (\\_ -> pure () ) f m" rhs: whenRight_ m f - warn: lhs: "either (\\_ -> return () ) f m" rhs: whenRight_ m f - warn: lhs: "either (\\_ -> pass ) f m" rhs: whenRight_ m f - warn: lhs: "either (const (pure () )) f m" rhs: whenRight_ m f - warn: lhs: "either (const (return ())) f m" rhs: whenRight_ m f - warn: lhs: "either (const pass) f m" rhs: whenRight_ m f - warn: lhs: "m >>= \\a -> whenRight_ a f" rhs: whenRightM_ m f - warn: lhs: "m >>= \\case Right x -> f x; Left _ -> pure () " rhs: whenRightM_ m f - warn: lhs: "m >>= \\case Right x -> f x; Left _ -> return ()" rhs: whenRightM_ m f - warn: lhs: "m >>= \\case Right x -> f x; Left _ -> pass" rhs: whenRightM_ m f - warn: lhs: "m >>= \\case Left _ -> pure () ; Right x -> f x" rhs: whenRightM_ m f - warn: lhs: "m >>= \\case Left _ -> return (); Right x -> f x" rhs: whenRightM_ m f - warn: lhs: "m >>= \\case Left _ -> pass ; Right x -> f x" rhs: whenRightM_ m f - warn: lhs: "either (\\_ -> pure () ) f =<< m" rhs: whenRightM_ m f - warn: lhs: "either (\\_ -> return () ) f =<< m" rhs: whenRightM_ m f - warn: lhs: "either (\\_ -> pass ) f =<< m" rhs: whenRightM_ m f - warn: lhs: "either (const (pure () )) f =<< m" rhs: whenRightM_ m f - warn: lhs: "either (const (return ())) f =<< m" rhs: whenRightM_ m f - warn: lhs: "either (const pass) f =<< m" rhs: whenRightM_ m f - warn: lhs: "m >>= either (\\_ -> pure ()) f" rhs: whenRightM_ m f - warn: lhs: "m >>= either (\\_ -> return ()) f" rhs: whenRightM_ m f - warn: lhs: "m >>= either (\\_ -> pass) f" rhs: whenRightM_ m f - warn: lhs: "m >>= either (const (pure ()) ) f" rhs: whenRightM_ m f - warn: lhs: "m >>= either (const (return ())) f" rhs: whenRightM_ m f - warn: lhs: "m >>= either (const pass) f" rhs: whenRightM_ m f - warn: lhs: "case m of Left x -> f x; Right _ -> pure d " rhs: whenLeft d m f - warn: lhs: "case m of Left x -> f x; Right _ -> return d" rhs: whenLeft d m f - warn: lhs: "case m of Right _ -> pure d ; Left x -> f x" rhs: whenLeft d m f - warn: lhs: "case m of Right _ -> return d; Left x -> f x" rhs: whenLeft d m f - warn: lhs: "either f (\\_ -> pure d ) m" rhs: whenLeft d m f - warn: lhs: "either f (\\_ -> return d ) m" rhs: whenLeft d m f - warn: lhs: "either f (const (pure d )) m" rhs: whenLeft d m f - warn: lhs: "either f (const (return d)) m" rhs: whenLeft d m f - warn: lhs: "m >>= \\a -> whenLeft d a f" rhs: whenLeftM d m f - warn: lhs: "m >>= \\case Left x -> f x; Right _ -> pure d" rhs: whenLeftM d m f - warn: lhs: "m >>= \\case Left x -> f x; Right _ -> return d" rhs: whenLeftM d m f - warn: lhs: "m >>= \\case Right _ -> pure d ; Left x -> f x" rhs: whenLeftM d m f - warn: lhs: "m >>= \\case Right _ -> return d; Left x -> f x" rhs: whenLeftM d m f - warn: lhs: "either f (\\_ -> pure d ) =<< m" rhs: whenLeftM d m f - warn: lhs: "either f (\\_ -> return d ) =<< m" rhs: whenLeftM d m f - warn: lhs: "either f (const (pure d )) =<< m" rhs: whenLeftM d m f - warn: lhs: "either f (const (return d)) =<< m" rhs: whenLeftM d m f - warn: lhs: "m >>= either f (\\_ -> pure d)" rhs: whenLeftM d m f - warn: lhs: "m >>= either f (\\_ -> return d)" rhs: whenLeftM d m f - warn: lhs: "m >>= either f (const (pure d))" rhs: whenLeftM d m f - warn: lhs: "m >>= either f (const (return d))" rhs: whenLeftM d m f - warn: lhs: "case m of Right x -> f x; Left _ -> pure d" rhs: whenRight d m f - warn: lhs: "case m of Right x -> f x; Left _ -> return d" rhs: whenRight d m f - warn: lhs: "case m of Left _ -> pure d ; Right x -> f x" rhs: whenRight d m f - warn: lhs: "case m of Left _ -> return d; Right x -> f x" rhs: whenRight d m f - warn: lhs: "either (\\_ -> pure d ) f m" rhs: whenRight d m f - warn: lhs: "either (\\_ -> return d ) f m" rhs: whenRight d m f - warn: lhs: "either (const (pure d )) f m" rhs: whenRight d m f - warn: lhs: "either (const (return d)) f m" rhs: whenRight d m f - warn: lhs: "m >>= \\a -> whenRight d a f" rhs: whenRightM d m f - warn: lhs: "m >>= \\case Right x -> f x; Left _ -> pure d" rhs: whenRightM d m f - warn: lhs: "m >>= \\case Right x -> f x; Left _ -> return d" rhs: whenRightM d m f - warn: lhs: "m >>= \\case Left _ -> pure d ; Right x -> f x" rhs: whenRightM d m f - warn: lhs: "m >>= \\case Left _ -> return d; Right x -> f x" rhs: whenRightM d m f - warn: lhs: "either (\\_ -> pure d ) f =<< m" rhs: whenRightM d m f - warn: lhs: "either (\\_ -> return d ) f =<< m" rhs: whenRightM d m f - warn: lhs: "either (const (pure d )) f =<< m" rhs: whenRightM d m f - warn: lhs: "either (const (return d)) f =<< m" rhs: whenRightM d m f - warn: lhs: "m >>= either (\\_ -> pure d) f" rhs: whenRightM d m f - warn: lhs: "m >>= either (\\_ -> return d) f" rhs: whenRightM d m f - warn: lhs: "m >>= either (const (pure d) ) f" rhs: whenRightM d m f - warn: lhs: "m >>= either (const (return d)) f" rhs: whenRightM d m f - warn: lhs: "case m of [] -> return (); (x:xs) -> f (x :| xs)" rhs: whenNotNull m f - warn: lhs: "case m of [] -> pure () ; (x:xs) -> f (x :| xs)" rhs: whenNotNull m f - warn: lhs: "case m of [] -> pass ; (x:xs) -> f (x :| xs)" rhs: whenNotNull m f - warn: lhs: "case m of (x:xs) -> f (x :| xs); [] -> return ()" rhs: whenNotNull m f - warn: lhs: "case m of (x:xs) -> f (x :| xs); [] -> pure () " rhs: whenNotNull m f - warn: lhs: "case m of (x:xs) -> f (x :| xs); [] -> pass " rhs: whenNotNull m f - warn: lhs: "m >>= \\case [] -> pass ; (x:xs) -> f (x :| xs)" rhs: whenNotNullM m f - warn: lhs: "m >>= \\case [] -> pure () ; (x:xs) -> f (x :| xs)" rhs: whenNotNullM m f - warn: lhs: "m >>= \\case [] -> return (); (x:xs) -> f (x :| xs)" rhs: whenNotNullM m f - warn: lhs: "m >>= \\case (x:xs) -> f (x :| xs); [] -> pass " rhs: whenNotNullM m f - warn: lhs: "m >>= \\case (x:xs) -> f (x :| xs); [] -> pure () " rhs: whenNotNullM m f - warn: lhs: "m >>= \\case (x:xs) -> f (x :| xs); [] -> return ()" rhs: whenNotNullM m f - warn: lhs: mapMaybe leftToMaybe rhs: lefts - warn: lhs: mapMaybe rightToMaybe rhs: rights - warn: lhs: flip runReaderT rhs: usingReaderT - warn: lhs: flip runReader rhs: usingReader - warn: lhs: flip runStateT rhs: usingStateT - warn: lhs: flip runState rhs: usingState - warn: lhs: "fst <$> usingStateT s st" rhs: evaluatingStateT s st - warn: lhs: "fst (usingState s st)" rhs: evaluatingState s st - warn: lhs: "snd <$> usingStateT s st" rhs: executingStateT s st - warn: lhs: "snd (usingState s st)" rhs: executingState s st - warn: lhs: "MaybeT (pure m)" rhs: hoistMaybe m - warn: lhs: "MaybeT (return m)" rhs: hoistMaybe m - warn: lhs: MaybeT . pure rhs: hoistMaybe - warn: lhs: MaybeT . return rhs: hoistMaybe - warn: lhs: "ExceptT (pure m)" rhs: hoistEither m - warn: lhs: "ExceptT (return m)" rhs: hoistEither m - warn: lhs: ExceptT . pure rhs: hoistEither - warn: lhs: ExceptT . return rhs: hoistEither - warn: lhs: fromMaybe mempty rhs: maybeToMonoid - warn: lhs: "m ?: mempty" rhs: maybeToMonoid m - warn: lhs: "Data.Map.toAscList (Data.Map.fromList x)" rhs: sortWith fst x - warn: lhs: "Data.Map.toDescList (Data.Map.fromList x)" rhs: "sortWith (Down . fst) x" - warn: lhs: "Data.Set.toList (Data.Set.fromList l)" rhs: sortNub l - warn: lhs: "Data.Set.assocs (Data.Set.fromList l)" rhs: sortNub l - warn: lhs: "Data.Set.toAscList (Data.Set.fromList l)" rhs: sortNub l - warn: lhs: "Data.HashSet.toList (Data.HashSet.fromList l)" rhs: unstableNub l - warn: lhs: nub note: "'nub' is O(n^2), 'ordNub' is O(n log n)" rhs: ordNub - warn: lhs: "sortBy (comparing f)" note: "If the function you are using for 'comparing' is slow, use 'sortOn' instead of 'sortWith', because 'sortOn' caches applications the function and 'sortWith' doesn't." rhs: sortWith f - warn: lhs: sortOn fst note: "'sortWith' will be faster here because it doesn't do caching" rhs: sortWith fst - warn: lhs: sortOn snd note: "'sortWith' will be faster here because it doesn't do caching" rhs: sortWith snd - warn: lhs: "sortOn (Down . fst)" note: "'sortWith' will be faster here because it doesn't do caching" rhs: "sortWith (Down . fst)" - warn: lhs: "sortOn (Down . snd)" note: "'sortWith' will be faster here because it doesn't do caching" rhs: "sortWith (Down . snd)" - warn: lhs: Data.Text.IO.putStr rhs: putText - warn: lhs: Data.Text.IO.putStrLn rhs: putTextLn - warn: lhs: Data.Text.Lazy.IO.putStr rhs: putLText - warn: lhs: Data.Text.Lazy.IO.putStrLn rhs: putLTextLn - warn: lhs: Data.ByteString.Char8.putStr rhs: putBS - warn: lhs: Data.ByteString.Char8.putStrLn rhs: putBSLn - warn: lhs: Data.ByteString.Lazy.Char8.putStr rhs: putLBS - warn: lhs: Data.ByteString.Lazy.Char8.putStrLn rhs: putLBSLn - warn: lhs: Data.Text.Lazy.Text rhs: LText - warn: lhs: Data.ByteString.Lazy.ByteString rhs: LByteString - warn: lhs: Data.ByteString.UTF8.fromString rhs: encodeUtf8 - warn: lhs: Data.ByteString.UTF8.toString rhs: decodeUtf8 - warn: lhs: Data.Text.Encoding.encodeUtf8 rhs: encodeUtf8 - warn: lhs: Data.Text.Encoding.decodeUtf8 rhs: decodeUtf8 - warn: lhs: "Data.ByteString.Lazy.toStrict (encodeUtf8 x)" rhs: encodeUtf8 x - warn: lhs: "toStrict (encodeUtf8 x)" rhs: encodeUtf8 x - warn: lhs: "decodeUtf8 (Data.ByteString.Lazy.fromStrict x)" rhs: decodeUtf8 x - warn: lhs: "decodeUtf8 (fromStrict x)" rhs: decodeUtf8 x - warn: lhs: Data.ByteString.Lazy.UTF8.fromString rhs: encodeUtf8 - warn: lhs: Data.ByteString.Lazy.UTF8.toString rhs: decodeUtf8 - warn: lhs: "Data.ByteString.Lazy.fromStrict (Data.Text.Encoding.encodeUtf8 x)" rhs: encodeUtf8 x - warn: lhs: "Data.ByteString.Lazy.fromStrict (encodeUtf8 x)" rhs: encodeUtf8 x - warn: lhs: "Data.Text.Encoding.decodeUtf8 (Data.ByteString.Lazy.toStrict x)" rhs: decodeUtf8 x - warn: lhs: "Data.Text.Encoding.decodeUtf8 (toStrict x)" rhs: decodeUtf8 x - warn: lhs: "decodeUtf8 (Data.ByteString.Lazy.toStrict x)" rhs: decodeUtf8 x - warn: lhs: "decodeUtf8 (toStrict x)" rhs: decodeUtf8 x - warn: lhs: Data.Text.pack rhs: toText - warn: lhs: Data.Text.unpack rhs: toString - warn: lhs: Data.Text.Lazy.pack rhs: toLText - warn: lhs: Data.Text.Lazy.unpack rhs: toString - warn: lhs: Data.Text.Lazy.toStrict rhs: toText - warn: lhs: Data.Text.Lazy.fromStrict rhs: toLText - warn: lhs: "Data.Text.pack (show x)" rhs: show x - warn: lhs: "Data.Text.Lazy.pack (show x)" rhs: show x - warn: lhs: Data.ByteString.Lazy.fromStrict rhs: fromStrict - warn: lhs: Data.ByteString.Lazy.toStrict rhs: toStrict - warn: lhs: Data.Text.Lazy.fromStrict rhs: fromStrict - warn: lhs: Data.Text.Lazy.toStrict rhs: toStrict - warn: lhs: Control.Applicative.Alternative name: "Use 'Alternative' from Relude" note: "'Alternative' is already exported from Relude" rhs: Alternative - warn: lhs: Control.Applicative.empty name: "Use 'empty' from Relude" note: "'empty' is already exported from Relude" rhs: empty - warn: lhs: "(Control.Applicative.<|>)" name: "Use '<|>' from Relude" note: "Operator '(<|>)' is already exported from Relude" rhs: "(<|>)" - warn: lhs: Control.Applicative.some name: "Use 'some' from Relude" note: "'some' is already exported from Relude" rhs: some - warn: lhs: Control.Applicative.many name: "Use 'many' from Relude" note: "'many' is already exported from Relude" rhs: many - warn: lhs: Control.Applicative.Const name: "Use 'Const' from Relude" note: "'Const' is already exported from Relude" rhs: Const - warn: lhs: Control.Applicative.getConst name: "Use 'getConst' from Relude" note: "'getConst' is already exported from Relude" rhs: getConst - warn: lhs: Control.Applicative.ZipList name: "Use 'ZipList' from Relude" note: "'ZipList' is already exported from Relude" rhs: ZipList - warn: lhs: Control.Applicative.getZipList name: "Use 'getZipList' from Relude" note: "'getZipList' is already exported from Relude" rhs: getZipList - warn: lhs: Control.Applicative.liftA2 name: "Use 'liftA2' from Relude" note: "'liftA2' is already exported from Relude" rhs: liftA2 - warn: lhs: Control.Applicative.liftA3 name: "Use 'liftA3' from Relude" note: "'liftA3' is already exported from Relude" rhs: liftA3 - warn: lhs: Control.Applicative.optional name: "Use 'optional' from Relude" note: "'optional' is already exported from Relude" rhs: optional - warn: lhs: "(Control.Applicative.<**>)" name: "Use '<**>' from Relude" note: "Operator '(<**>)' is already exported from Relude" rhs: "(<**>)" - warn: lhs: Data.Bits.xor name: "Use 'xor' from Relude" note: "'xor' is already exported from Relude" rhs: xor - warn: lhs: Data.Char.chr name: "Use 'chr' from Relude" note: "'chr' is already exported from Relude" rhs: chr - warn: lhs: Data.Int.Int8 name: "Use 'Int8' from Relude" note: "'Int8' is already exported from Relude" rhs: Int8 - warn: lhs: Data.Int.Int16 name: "Use 'Int16' from Relude" note: "'Int16' is already exported from Relude" rhs: Int16 - warn: lhs: Data.Int.Int32 name: "Use 'Int32' from Relude" note: "'Int32' is already exported from Relude" rhs: Int32 - warn: lhs: Data.Int.Int64 name: "Use 'Int64' from Relude" note: "'Int64' is already exported from Relude" rhs: Int64 - warn: lhs: Data.Word.Word8 name: "Use 'Word8' from Relude" note: "'Word8' is already exported from Relude" rhs: Word8 - warn: lhs: Data.Word.Word16 name: "Use 'Word16' from Relude" note: "'Word16' is already exported from Relude" rhs: Word16 - warn: lhs: Data.Word.Word32 name: "Use 'Word32' from Relude" note: "'Word32' is already exported from Relude" rhs: Word32 - warn: lhs: Data.Word.Word64 name: "Use 'Word64' from Relude" note: "'Word64' is already exported from Relude" rhs: Word64 - warn: lhs: Data.Word.byteSwap16 name: "Use 'byteSwap16' from Relude" note: "'byteSwap16' is already exported from Relude" rhs: byteSwap16 - warn: lhs: Data.Word.byteSwap32 name: "Use 'byteSwap32' from Relude" note: "'byteSwap32' is already exported from Relude" rhs: byteSwap32 - warn: lhs: Data.Word.byteSwap64 name: "Use 'byteSwap64' from Relude" note: "'byteSwap64' is already exported from Relude" rhs: byteSwap64 - warn: lhs: Numeric.Natural.Natural name: "Use 'Natural' from Relude" note: "'Natural' is already exported from Relude" rhs: Natural - warn: lhs: System.IO.IOMode name: "Use 'IOMode' from Relude" note: "'IOMode' is already exported from Relude" rhs: IOMode - warn: lhs: System.IO.ReadMode name: "Use 'ReadMode' from Relude" note: "'ReadMode' is already exported from Relude" rhs: ReadMode - warn: lhs: System.IO.WriteMode name: "Use 'WriteMode' from Relude" note: "'WriteMode' is already exported from Relude" rhs: WriteMode - warn: lhs: System.IO.AppendMode name: "Use 'AppendMode' from Relude" note: "'AppendMode' is already exported from Relude" rhs: AppendMode - warn: lhs: System.IO.ReadWriteMode name: "Use 'ReadWriteMode' from Relude" note: "'ReadWriteMode' is already exported from Relude" rhs: ReadWriteMode - warn: lhs: Data.Ord.Down name: "Use 'Down' from Relude" note: "'Down' is already exported from Relude" rhs: Down - warn: lhs: Data.Ord.comparing name: "Use 'comparing' from Relude" note: "'comparing' is already exported from Relude" rhs: comparing - warn: lhs: Data.Coerce.Coercible name: "Use 'Coercible' from Relude" note: "'Coercible' is already exported from Relude" rhs: Coercible - warn: lhs: Data.Coerce.coerce name: "Use 'coerce' from Relude" note: "'coerce' is already exported from Relude" rhs: coerce - warn: lhs: Data.Kind.Constraint name: "Use 'Constraint' from Relude" note: "'Constraint' is already exported from Relude" rhs: Constraint - warn: lhs: Data.Kind.Type name: "Use 'Type' from Relude" note: "'Type' is already exported from Relude" rhs: Type - warn: lhs: Data.Typeable.Typeable name: "Use 'Typeable' from Relude" note: "'Typeable' is already exported from Relude" rhs: Typeable - warn: lhs: Data.Proxy.Proxy name: "Use 'Proxy' from Relude" note: "'Proxy' is already exported from Relude" rhs: Proxy - warn: lhs: Data.Typeable.Typeable name: "Use 'Typeable' from Relude" note: "'Typeable' is already exported from Relude" rhs: Typeable - warn: lhs: Data.Void.Void name: "Use 'Void' from Relude" note: "'Void' is already exported from Relude" rhs: Void - warn: lhs: Data.Void.absurd name: "Use 'absurd' from Relude" note: "'absurd' is already exported from Relude" rhs: absurd - warn: lhs: Data.Void.vacuous name: "Use 'vacuous' from Relude" note: "'vacuous' is already exported from Relude" rhs: vacuous - warn: lhs: Data.Base.maxInt name: "Use 'maxInt' from Relude" note: "'maxInt' is already exported from Relude" rhs: maxInt - warn: lhs: Data.Base.minInt name: "Use 'minInt' from Relude" note: "'minInt' is already exported from Relude" rhs: minInt - warn: lhs: Data.Base.ord name: "Use 'ord' from Relude" note: "'ord' is already exported from Relude" rhs: ord - warn: lhs: GHC.Enum.boundedEnumFrom name: "Use 'boundedEnumFrom' from Relude" note: "'boundedEnumFrom' is already exported from Relude" rhs: boundedEnumFrom - warn: lhs: GHC.Enum.boundedEnumFromThen name: "Use 'boundedEnumFromThen' from Relude" note: "'boundedEnumFromThen' is already exported from Relude" rhs: boundedEnumFromThen - warn: lhs: GHC.Generics.Generic name: "Use 'Generic' from Relude" note: "'Generic' is already exported from Relude" rhs: Generic - warn: lhs: GHC.Real.Ratio name: "Use 'Ratio' from Relude" note: "'Ratio' is already exported from Relude" rhs: Ratio - warn: lhs: GHC.Real.Rational name: "Use 'Rational' from Relude" note: "'Rational' is already exported from Relude" rhs: Rational - warn: lhs: GHC.Real.denominator name: "Use 'denominator' from Relude" note: "'denominator' is already exported from Relude" rhs: denominator - warn: lhs: GHC.Real.numerator name: "Use 'numerator' from Relude" note: "'numerator' is already exported from Relude" rhs: numerator - warn: lhs: GHC.TypeNats.CmpNat name: "Use 'CmpNat' from Relude" note: "'CmpNat' is already exported from Relude" rhs: CmpNat - warn: lhs: GHC.TypeNats.KnownNat name: "Use 'KnownNat' from Relude" note: "'KnownNat' is already exported from Relude" rhs: KnownNat - warn: lhs: GHC.TypeNats.Nat name: "Use 'Nat' from Relude" note: "'Nat' is already exported from Relude" rhs: Nat - warn: lhs: GHC.TypeNats.SomeNat name: "Use 'SomeNat' from Relude" note: "'SomeNat' is already exported from Relude" rhs: SomeNat - warn: lhs: GHC.TypeNats.natVal name: "Use 'natVal' from Relude" note: "'natVal' is already exported from Relude" rhs: natVal - warn: lhs: GHC.TypeNats.someNatVal name: "Use 'someNatVal' from Relude" note: "'someNatVal' is already exported from Relude" rhs: someNatVal - warn: lhs: GHC.TypeLits.CmpNat name: "Use 'CmpNat' from Relude" note: "'CmpNat' is already exported from Relude" rhs: CmpNat - warn: lhs: GHC.TypeLits.KnownNat name: "Use 'KnownNat' from Relude" note: "'KnownNat' is already exported from Relude" rhs: KnownNat - warn: lhs: GHC.TypeLits.Nat name: "Use 'Nat' from Relude" note: "'Nat' is already exported from Relude" rhs: Nat - warn: lhs: GHC.TypeLits.SomeNat name: "Use 'SomeNat' from Relude" note: "'SomeNat' is already exported from Relude" rhs: SomeNat - warn: lhs: GHC.TypeLits.natVal name: "Use 'natVal' from Relude" note: "'natVal' is already exported from Relude" rhs: natVal - warn: lhs: GHC.TypeLits.someNatVal name: "Use 'someNatVal' from Relude" note: "'someNatVal' is already exported from Relude" rhs: someNatVal - warn: lhs: GHC.ExecutionStack.getStackTrace name: "Use 'getStackTrace' from Relude" note: "'getStackTrace' is already exported from Relude" rhs: getStackTrace - warn: lhs: GHC.ExecutionStack.showStackTrace name: "Use 'showStackTrace' from Relude" note: "'showStackTrace' is already exported from Relude" rhs: showStackTrace - warn: lhs: GHC.OverloadedLabels.IsLabel name: "Use 'IsLabel' from Relude" note: "'IsLabel' is already exported from Relude" rhs: IsLabel - warn: lhs: GHC.OverloadedLabels.fromLabel name: "Use 'fromLabel' from Relude" note: "'fromLabel' is already exported from Relude" rhs: fromLabel - warn: lhs: GHC.Stack.CallStack name: "Use 'CallStack' from Relude" note: "'CallStack' is already exported from Relude" rhs: CallStack - warn: lhs: GHC.Stack.HasCallStack name: "Use 'HasCallStack' from Relude" note: "'HasCallStack' is already exported from Relude" rhs: HasCallStack - warn: lhs: GHC.Stack.callStack name: "Use 'callStack' from Relude" note: "'callStack' is already exported from Relude" rhs: callStack - warn: lhs: GHC.Stack.currentCallStack name: "Use 'currentCallStack' from Relude" note: "'currentCallStack' is already exported from Relude" rhs: currentCallStack - warn: lhs: GHC.Stack.getCallStack name: "Use 'getCallStack' from Relude" note: "'getCallStack' is already exported from Relude" rhs: getCallStack - warn: lhs: GHC.Stack.prettyCallStack name: "Use 'prettyCallStack' from Relude" note: "'prettyCallStack' is already exported from Relude" rhs: prettyCallStack - warn: lhs: GHC.Stack.prettySrcLoc name: "Use 'prettySrcLoc' from Relude" note: "'prettySrcLoc' is already exported from Relude" rhs: prettySrcLoc - warn: lhs: GHC.Stack.withFrozenCallStack name: "Use 'withFrozenCallStack' from Relude" note: "'withFrozenCallStack' is already exported from Relude" rhs: withFrozenCallStack - warn: lhs: Data.Bifoldable.Bifoldable name: "Use 'Bifoldable' from Relude" note: "'Bifoldable' is already exported from Relude" rhs: Bifoldable - warn: lhs: Data.Bifoldable.bifold name: "Use 'bifold' from Relude" note: "'bifold' is already exported from Relude" rhs: bifold - warn: lhs: Data.Bifoldable.bifoldMap name: "Use 'bifoldMap' from Relude" note: "'bifoldMap' is already exported from Relude" rhs: bifoldMap - warn: lhs: Data.Bifoldable.bifoldr name: "Use 'bifoldr' from Relude" note: "'bifoldr' is already exported from Relude" rhs: bifoldr - warn: lhs: Data.Bifoldable.bifoldl name: "Use 'bifoldl' from Relude" note: "'bifoldl' is already exported from Relude" rhs: bifoldl - warn: lhs: "Data.Bifoldable.bifoldl'" name: "Use 'bifoldl'' from Relude" note: "'bifoldl'' is already exported from Relude" rhs: "bifoldl'" - warn: lhs: Data.Bifoldable.bifoldlM name: "Use 'bifoldlM' from Relude" note: "'bifoldlM' is already exported from Relude" rhs: bifoldlM - warn: lhs: "Data.Bifoldable.bifoldr'" name: "Use 'bifoldr'' from Relude" note: "'bifoldr'' is already exported from Relude" rhs: "bifoldr'" - warn: lhs: Data.Bifoldable.bifoldrM name: "Use 'bifoldrM' from Relude" note: "'bifoldrM' is already exported from Relude" rhs: bifoldrM - warn: lhs: Data.Bifoldable.bitraverse_ name: "Use 'bitraverse_' from Relude" note: "'bitraverse_' is already exported from Relude" rhs: bitraverse_ - warn: lhs: Data.Bifoldable.bifor_ name: "Use 'bifor_' from Relude" note: "'bifor_' is already exported from Relude" rhs: bifor_ - warn: lhs: Data.Bifoldable.biasum name: "Use 'biasum' from Relude" note: "'biasum' is already exported from Relude" rhs: biasum - warn: lhs: Data.Bifoldable.bisequence_ name: "Use 'bisequence_' from Relude" note: "'bisequence_' is already exported from Relude" rhs: bisequence_ - warn: lhs: Data.Bifoldable.biList name: "Use 'biList' from Relude" note: "'biList' is already exported from Relude" rhs: biList - warn: lhs: Data.Bifoldable.binull name: "Use 'binull' from Relude" note: "'binull' is already exported from Relude" rhs: binull - warn: lhs: Data.Bifoldable.bilength name: "Use 'bilength' from Relude" note: "'bilength' is already exported from Relude" rhs: bilength - warn: lhs: Data.Bifoldable.bielem name: "Use 'bielem' from Relude" note: "'bielem' is already exported from Relude" rhs: bielem - warn: lhs: Data.Bifoldable.biand name: "Use 'biand' from Relude" note: "'biand' is already exported from Relude" rhs: biand - warn: lhs: Data.Bifoldable.bior name: "Use 'bior' from Relude" note: "'bior' is already exported from Relude" rhs: bior - warn: lhs: Data.Bifoldable.biany name: "Use 'biany' from Relude" note: "'biany' is already exported from Relude" rhs: biany - warn: lhs: Data.Bifoldable.biall name: "Use 'biall' from Relude" note: "'biall' is already exported from Relude" rhs: biall - warn: lhs: Data.Bifoldable.bifind name: "Use 'bifind' from Relude" note: "'bifind' is already exported from Relude" rhs: bifind - warn: lhs: Data.Bitraversable.Bitraversable name: "Use 'Bitraversable' from Relude" note: "'Bitraversable' is already exported from Relude" rhs: Bitraversable - warn: lhs: Data.Bitraversable.bitraverse name: "Use 'bitraverse' from Relude" note: "'bitraverse' is already exported from Relude" rhs: bitraverse - warn: lhs: Data.Bitraversable.bisequence name: "Use 'bisequence' from Relude" note: "'bisequence' is already exported from Relude" rhs: bisequence - warn: lhs: Data.Bitraversable.bifor name: "Use 'bifor' from Relude" note: "'bifor' is already exported from Relude" rhs: bifor - warn: lhs: Data.Bitraversable.bimapDefault name: "Use 'bimapDefault' from Relude" note: "'bimapDefault' is already exported from Relude" rhs: bimapDefault - warn: lhs: Data.Bitraversable.bifoldMapDefault name: "Use 'bifoldMapDefault' from Relude" note: "'bifoldMapDefault' is already exported from Relude" rhs: bifoldMapDefault - warn: lhs: Control.Monad.guard name: "Use 'guard' from Relude" note: "'guard' is already exported from Relude" rhs: guard - warn: lhs: Control.Monad.unless name: "Use 'unless' from Relude" note: "'unless' is already exported from Relude" rhs: unless - warn: lhs: Control.Monad.when name: "Use 'when' from Relude" note: "'when' is already exported from Relude" rhs: when - warn: lhs: Data.Bool.bool name: "Use 'bool' from Relude" note: "'bool' is already exported from Relude" rhs: bool - warn: lhs: Data.Hashable.Hashable name: "Use 'Hashable' from Relude" note: "'Hashable' is already exported from Relude" rhs: Hashable - warn: lhs: Data.Hashable.hashWithSalt name: "Use 'hashWithSalt' from Relude" note: "'hashWithSalt' is already exported from Relude" rhs: hashWithSalt - warn: lhs: Data.HashMap.Strict.HashMap name: "Use 'HashMap' from Relude" note: "'HashMap' is already exported from Relude" rhs: HashMap - warn: lhs: Data.HashSet.HashSet name: "Use 'HashSet' from Relude" note: "'HashSet' is already exported from Relude" rhs: HashSet - warn: lhs: Data.IntMap.Strict.IntMap name: "Use 'IntMap' from Relude" note: "'IntMap' is already exported from Relude" rhs: IntMap - warn: lhs: Data.IntSet.IntSet name: "Use 'IntSet' from Relude" note: "'IntSet' is already exported from Relude" rhs: IntSet - warn: lhs: Data.Map.Strict.Map name: "Use 'Map' from Relude" note: "'Map' is already exported from Relude" rhs: Map - warn: lhs: Data.Sequence.Sequence name: "Use 'Sequence' from Relude" note: "'Sequence' is already exported from Relude" rhs: Sequence - warn: lhs: Data.Set.Set name: "Use 'Set' from Relude" note: "'Set' is already exported from Relude" rhs: Set - warn: lhs: Data.Tuple.swap name: "Use 'swap' from Relude" note: "'swap' is already exported from Relude" rhs: swap - warn: lhs: Data.Vector.Vector name: "Use 'Vector' from Relude" note: "'Vector' is already exported from Relude" rhs: Vector - warn: lhs: GHC.Exts.IsList name: "Use 'IsList' from Relude" note: "'IsList' is already exported from Relude" rhs: IsList - warn: lhs: GHC.Exts.fromList name: "Use 'fromList' from Relude" note: "'fromList' is already exported from Relude" rhs: fromList - warn: lhs: GHC.Exts.fromListN name: "Use 'fromListN' from Relude" note: "'fromListN' is already exported from Relude" rhs: fromListN - warn: lhs: Debug.Trace.trace name: "Use 'trace' from Relude" note: "'trace' is already exported from Relude" rhs: trace - warn: lhs: Debug.Trace.traceShow name: "Use 'traceShow' from Relude" note: "'traceShow' is already exported from Relude" rhs: traceShow - warn: lhs: Debug.Trace.traceShowId name: "Use 'traceShowId' from Relude" note: "'traceShowId' is already exported from Relude" rhs: traceShowId - warn: lhs: Debug.Trace.traceShowM name: "Use 'traceShowM' from Relude" note: "'traceShowM' is already exported from Relude" rhs: traceShowM - warn: lhs: Debug.Trace.traceM name: "Use 'traceM' from Relude" note: "'traceM' is already exported from Relude" rhs: traceM - warn: lhs: Debug.Trace.traceId name: "Use 'traceId' from Relude" note: "'traceId' is already exported from Relude" rhs: traceId - warn: lhs: Control.DeepSeq.NFData name: "Use 'NFData' from Relude" note: "'NFData' is already exported from Relude" rhs: NFData - warn: lhs: Control.DeepSeq.rnf name: "Use 'rnf' from Relude" note: "'rnf' is already exported from Relude" rhs: rnf - warn: lhs: Control.DeepSeq.deepseq name: "Use 'deepseq' from Relude" note: "'deepseq' is already exported from Relude" rhs: deepseq - warn: lhs: Control.DeepSeq.force name: "Use 'force' from Relude" note: "'force' is already exported from Relude" rhs: force - warn: lhs: "(Control.DeepSeq.$!!)" name: "Use '$!!' from Relude" note: "Operator '($!!)' is already exported from Relude" rhs: "($!!)" - warn: lhs: Control.Exception.Exception name: "Use 'Exception' from Relude" note: "'Exception' is already exported from Relude" rhs: Exception - warn: lhs: Control.Exception.SomeException name: "Use 'SomeException' from Relude" note: "'SomeException' is already exported from Relude" rhs: SomeException - warn: lhs: Control.Exception.toException name: "Use 'toException' from Relude" note: "'toException' is already exported from Relude" rhs: toException - warn: lhs: Control.Exception.fromException name: "Use 'fromException' from Relude" note: "'fromException' is already exported from Relude" rhs: fromException - warn: lhs: Control.Exception.displayException name: "Use 'displayException' from Relude" note: "'displayException' is already exported from Relude" rhs: displayException - warn: lhs: Data.Foldable.asum name: "Use 'asum' from Relude" note: "'asum' is already exported from Relude" rhs: asum - warn: lhs: Data.Foldable.find name: "Use 'find' from Relude" note: "'find' is already exported from Relude" rhs: find - warn: lhs: Data.Foldable.find name: "Use 'find' from Relude" note: "'find' is already exported from Relude" rhs: find # - warn: # lhs: Data.Foldable.fold # name: "Use 'fold' from Relude" # note: "'fold' is already exported from Relude" # rhs: fold - warn: lhs: "Data.Foldable.foldl'" name: "Use 'foldl'' from Relude" note: "'foldl'' is already exported from Relude" rhs: "foldl'" - warn: lhs: Data.Foldable.forM_ name: "Use 'forM_' from Relude" note: "'forM_' is already exported from Relude" rhs: forM_ - warn: lhs: Data.Foldable.for_ name: "Use 'for_' from Relude" note: "'for_' is already exported from Relude" rhs: for_ - warn: lhs: Data.Foldable.sequenceA_ name: "Use 'sequenceA_' from Relude" note: "'sequenceA_' is already exported from Relude" rhs: sequenceA_ - warn: lhs: Data.Foldable.toList name: "Use 'toList' from Relude" note: "'toList' is already exported from Relude" rhs: toList - warn: lhs: Data.Foldable.traverse_ name: "Use 'traverse_' from Relude" note: "'traverse_' is already exported from Relude" rhs: traverse_ - warn: lhs: Data.Traversable.forM name: "Use 'forM' from Relude" note: "'forM' is already exported from Relude" rhs: forM - warn: lhs: Data.Traversable.mapAccumL name: "Use 'mapAccumL' from Relude" note: "'mapAccumL' is already exported from Relude" rhs: mapAccumL - warn: lhs: Data.Traversable.mapAccumR name: "Use 'mapAccumR' from Relude" note: "'mapAccumR' is already exported from Relude" rhs: mapAccumR - warn: lhs: "(Control.Arrow.&&&)" name: "Use '&&&' from Relude" note: "Operator '(&&&)' is already exported from Relude" rhs: "(&&&)" - warn: lhs: "(Control.Category.>>>)" name: "Use '>>>' from Relude" note: "Operator '(>>>)' is already exported from Relude" rhs: "(>>>)" - warn: lhs: "(Control.Category.<<<)" name: "Use '<<<' from Relude" note: "Operator '(<<<)' is already exported from Relude" rhs: "(<<<)" - warn: lhs: Data.Function.fix name: "Use 'fix' from Relude" note: "'fix' is already exported from Relude" rhs: fix - warn: lhs: Data.Function.on name: "Use 'on' from Relude" note: "'on' is already exported from Relude" rhs: 'on' - warn: lhs: Data.Bifunctor.Bifunctor name: "Use 'Bifunctor' from Relude" note: "'Bifunctor' is already exported from Relude" rhs: Bifunctor - warn: lhs: Data.Bifunctor.bimap name: "Use 'bimap' from Relude" note: "'bimap' is already exported from Relude" rhs: bimap - warn: lhs: Data.Bifunctor.first name: "Use 'first' from Relude" note: "'first' is already exported from Relude" rhs: first - warn: lhs: Data.Bifunctor.second name: "Use 'second' from Relude" note: "'second' is already exported from Relude" rhs: second - warn: lhs: Data.Functor.void name: "Use 'void' from Relude" note: "'void' is already exported from Relude" rhs: void - warn: lhs: "(Data.Functor.$>)" name: "Use '$>' from Relude" note: "Operator '($>)' is already exported from Relude" rhs: "($>)" - warn: lhs: "(Data.Functor.<&>)" name: "Use '<&>' from Relude" note: "Operator '(<&>)' is already exported from Relude" rhs: "(<&>)" - warn: lhs: Data.Functor.Compose.Compose name: "Use 'Compose' from Relude" note: "'Compose' is already exported from Relude" rhs: Compose - warn: lhs: Data.Functor.Compose.getCompose name: "Use 'getCompose' from Relude" note: "'getCompose' is already exported from Relude" rhs: getCompose - warn: lhs: Data.Functor.Identity.Identity name: "Use 'Identity' from Relude" note: "'Identity' is already exported from Relude" rhs: Identity - warn: lhs: Data.Functor.Identity.runIdentity name: "Use 'runIdentity' from Relude" note: "'runIdentity' is already exported from Relude" rhs: runIdentity - warn: lhs: Control.Concurrent.MVar.MVar name: "Use 'MVar' from Relude" note: "'MVar' is already exported from Relude" rhs: MVar - warn: lhs: Control.Concurrent.MVar.newEmptyMVar name: "Use 'newEmptyMVar' from Relude" note: "'newEmptyMVar' is already exported from Relude" rhs: newEmptyMVar - warn: lhs: Control.Concurrent.MVar.newMVar name: "Use 'newMVar' from Relude" note: "'newMVar' is already exported from Relude" rhs: newMVar - warn: lhs: Control.Concurrent.MVar.putMVar name: "Use 'putMVar' from Relude" note: "'putMVar' is already exported from Relude" rhs: putMVar - warn: lhs: Control.Concurrent.MVar.readMVar name: "Use 'readMVar' from Relude" note: "'readMVar' is already exported from Relude" rhs: readMVar - warn: lhs: Control.Concurrent.MVar.swapMVar name: "Use 'swapMVar' from Relude" note: "'swapMVar' is already exported from Relude" rhs: swapMVar - warn: lhs: Control.Concurrent.MVar.takeMVar name: "Use 'takeMVar' from Relude" note: "'takeMVar' is already exported from Relude" rhs: takeMVar - warn: lhs: Control.Concurrent.MVar.tryPutMVar name: "Use 'tryPutMVar' from Relude" note: "'tryPutMVar' is already exported from Relude" rhs: tryPutMVar - warn: lhs: Control.Concurrent.MVar.tryReadMVar name: "Use 'tryReadMVar' from Relude" note: "'tryReadMVar' is already exported from Relude" rhs: tryReadMVar - warn: lhs: Control.Concurrent.MVar.tryTakeMVar name: "Use 'tryTakeMVar' from Relude" note: "'tryTakeMVar' is already exported from Relude" rhs: tryTakeMVar - warn: lhs: Control.Monad.STM.STM name: "Use 'STM' from Relude" note: "'STM' is already exported from Relude" rhs: STM - warn: lhs: Control.Monad.STM.atomically name: "Use 'atomically' from Relude" note: "'atomically' is already exported from Relude" rhs: atomically - warn: lhs: Control.Monad.STM.throwSTM name: "Use 'throwSTM' from Relude" note: "'throwSTM' is already exported from Relude" rhs: throwSTM - warn: lhs: Control.Monad.STM.catchSTM name: "Use 'catchSTM' from Relude" note: "'catchSTM' is already exported from Relude" rhs: catchSTM - warn: lhs: Control.Concurrent.STM.TVar.TVar name: "Use 'TVar' from Relude" note: "'TVar' is already exported from Relude" rhs: TVar - warn: lhs: Control.Concurrent.STM.TVar.newTVarIO name: "Use 'newTVarIO' from Relude" note: "'newTVarIO' is already exported from Relude" rhs: newTVarIO - warn: lhs: Control.Concurrent.STM.TVar.readTVarIO name: "Use 'readTVarIO' from Relude" note: "'readTVarIO' is already exported from Relude" rhs: readTVarIO - warn: lhs: "Control.Concurrent.STM.TVar.modifyTVar'" name: "Use 'modifyTVar'' from Relude" note: "'modifyTVar'' is already exported from Relude" rhs: "modifyTVar'" - warn: lhs: Control.Concurrent.STM.TVar.newTVar name: "Use 'newTVar' from Relude" note: "'newTVar' is already exported from Relude" rhs: newTVar - warn: lhs: Control.Concurrent.STM.TVar.readTVar name: "Use 'readTVar' from Relude" note: "'readTVar' is already exported from Relude" rhs: readTVar - warn: lhs: Control.Concurrent.STM.TVar.writeTVar name: "Use 'writeTVar' from Relude" note: "'writeTVar' is already exported from Relude" rhs: writeTVar - warn: lhs: Control.Concurrent.STM.TMVar.TMVar name: "Use 'TMVar' from Relude" note: "'TMVar' is already exported from Relude" rhs: TMVar - warn: lhs: Control.Concurrent.STM.TMVar.newTMVar name: "Use 'newTMVar' from Relude" note: "'newTMVar' is already exported from Relude" rhs: newTMVar - warn: lhs: Control.Concurrent.STM.TMVar.newEmptyTMVar name: "Use 'newEmptyTMVar' from Relude" note: "'newEmptyTMVar' is already exported from Relude" rhs: newEmptyTMVar - warn: lhs: Control.Concurrent.STM.TMVar.newTMVarIO name: "Use 'newTMVarIO' from Relude" note: "'newTMVarIO' is already exported from Relude" rhs: newTMVarIO - warn: lhs: Control.Concurrent.STM.TMVar.newEmptyTMVarIO name: "Use 'newEmptyTMVarIO' from Relude" note: "'newEmptyTMVarIO' is already exported from Relude" rhs: newEmptyTMVarIO - warn: lhs: Control.Concurrent.STM.TMVar.takeTMVar name: "Use 'takeTMVar' from Relude" note: "'takeTMVar' is already exported from Relude" rhs: takeTMVar - warn: lhs: Control.Concurrent.STM.TMVar.putTMVar name: "Use 'putTMVar' from Relude" note: "'putTMVar' is already exported from Relude" rhs: putTMVar - warn: lhs: Control.Concurrent.STM.TMVar.readTMVar name: "Use 'readTMVar' from Relude" note: "'readTMVar' is already exported from Relude" rhs: readTMVar - warn: lhs: Control.Concurrent.STM.TMVar.tryReadTMVar name: "Use 'tryReadTMVar' from Relude" note: "'tryReadTMVar' is already exported from Relude" rhs: tryReadTMVar - warn: lhs: Control.Concurrent.STM.TMVar.swapTMVar name: "Use 'swapTMVar' from Relude" note: "'swapTMVar' is already exported from Relude" rhs: swapTMVar - warn: lhs: Control.Concurrent.STM.TMVar.tryTakeTMVar name: "Use 'tryTakeTMVar' from Relude" note: "'tryTakeTMVar' is already exported from Relude" rhs: tryTakeTMVar - warn: lhs: Control.Concurrent.STM.TMVar.tryPutTMVar name: "Use 'tryPutTMVar' from Relude" note: "'tryPutTMVar' is already exported from Relude" rhs: tryPutTMVar - warn: lhs: Control.Concurrent.STM.TMVar.isEmptyTMVar name: "Use 'isEmptyTMVar' from Relude" note: "'isEmptyTMVar' is already exported from Relude" rhs: isEmptyTMVar - warn: lhs: Control.Concurrent.STM.TMVar.mkWeakTMVar name: "Use 'mkWeakTMVar' from Relude" note: "'mkWeakTMVar' is already exported from Relude" rhs: mkWeakTMVar - warn: lhs: Data.IORef.IORef name: "Use 'IORef' from Relude" note: "'IORef' is already exported from Relude" rhs: IORef - warn: lhs: Data.IORef.atomicModifyIORef name: "Use 'atomicModifyIORef' from Relude" note: "'atomicModifyIORef' is already exported from Relude" rhs: atomicModifyIORef - warn: lhs: "Data.IORef.atomicModifyIORef'" name: "Use 'atomicModifyIORef'' from Relude" note: "'atomicModifyIORef'' is already exported from Relude" rhs: "atomicModifyIORef'" - warn: lhs: Data.IORef.atomicWriteIORef name: "Use 'atomicWriteIORef' from Relude" note: "'atomicWriteIORef' is already exported from Relude" rhs: atomicWriteIORef - warn: lhs: Data.IORef.modifyIORef name: "Use 'modifyIORef' from Relude" note: "'modifyIORef' is already exported from Relude" rhs: modifyIORef - warn: lhs: "Data.IORef.modifyIORef'" name: "Use 'modifyIORef'' from Relude" note: "'modifyIORef'' is already exported from Relude" rhs: "modifyIORef'" - warn: lhs: Data.IORef.newIORef name: "Use 'newIORef' from Relude" note: "'newIORef' is already exported from Relude" rhs: newIORef - warn: lhs: Data.IORef.readIORef name: "Use 'readIORef' from Relude" note: "'readIORef' is already exported from Relude" rhs: readIORef - warn: lhs: Data.IORef.writeIORef name: "Use 'writeIORef' from Relude" note: "'writeIORef' is already exported from Relude" rhs: writeIORef - warn: lhs: "atomicModifyIORef ref (\\a -> (f a, ()))" rhs: atomicModifyIORef_ ref f - warn: lhs: "atomicModifyIORef ref $ \\a -> (f a, ())" rhs: atomicModifyIORef_ ref f - warn: lhs: "atomicModifyIORef' ref $ \\a -> (f a, ())" rhs: "atomicModifyIORef'_ ref f" - warn: lhs: "atomicModifyIORef' ref (\\a -> (f a, ()))" rhs: "atomicModifyIORef'_ ref f" - warn: lhs: Data.Text.IO.getLine name: "Use 'getLine' from Relude" note: "'getLine' is already exported from Relude" rhs: getLine - warn: lhs: System.IO.hFlush name: "Use 'hFlush' from Relude" note: "'hFlush' is already exported from Relude" rhs: hFlush - warn: lhs: System.IO.hIsEOF name: "Use 'hIsEOF' from Relude" note: "'hIsEOF' is already exported from Relude" rhs: hIsEOF - warn: lhs: System.IO.hSetBuffering name: "Use 'hSetBuffering' from Relude" note: "'hSetBuffering' is already exported from Relude" rhs: hSetBuffering - warn: lhs: System.IO.hGetBuffering name: "Use 'hGetBuffering' from Relude" note: "'hGetBuffering' is already exported from Relude" rhs: hGetBuffering - warn: lhs: System.IO.Handle name: "Use 'Handle' from Relude" note: "'Handle' is already exported from Relude" rhs: Handle - warn: lhs: System.IO.stdin name: "Use 'stdin' from Relude" note: "'stdin' is already exported from Relude" rhs: stdin - warn: lhs: System.IO.stdout name: "Use 'stdout' from Relude" note: "'stdout' is already exported from Relude" rhs: stdout - warn: lhs: System.IO.stderr name: "Use 'stderr' from Relude" note: "'stderr' is already exported from Relude" rhs: stderr - warn: lhs: System.IO.withFile name: "Use 'withFile' from Relude" note: "'withFile' is already exported from Relude" rhs: withFile - warn: lhs: System.IO.BufferMode name: "Use 'BufferMode' from Relude" note: "'BufferMode' is already exported from Relude" rhs: BufferMode - warn: lhs: System.Environment.getArgs name: "Use 'getArgs' from Relude" note: "'getArgs' is already exported from Relude" rhs: getArgs - warn: lhs: System.Environment.lookupEnv name: "Use 'lookupEnv' from Relude" note: "'lookupEnv' is already exported from Relude" rhs: lookupEnv - warn: lhs: Data.List.genericDrop name: "Use 'genericDrop' from Relude" note: "'genericDrop' is already exported from Relude" rhs: genericDrop - warn: lhs: Data.List.genericLength name: "Use 'genericLength' from Relude" note: "'genericLength' is already exported from Relude" rhs: genericLength - warn: lhs: Data.List.genericReplicate name: "Use 'genericReplicate' from Relude" note: "'genericReplicate' is already exported from Relude" rhs: genericReplicate - warn: lhs: Data.List.genericSplitAt name: "Use 'genericSplitAt' from Relude" note: "'genericSplitAt' is already exported from Relude" rhs: genericSplitAt - warn: lhs: Data.List.genericTake name: "Use 'genericTake' from Relude" note: "'genericTake' is already exported from Relude" rhs: genericTake - warn: lhs: Data.List.group name: "Use 'group' from Relude" note: "'group' is already exported from Relude" rhs: group - warn: lhs: Data.List.inits name: "Use 'inits' from Relude" note: "'inits' is already exported from Relude" rhs: inits - warn: lhs: Data.List.intercalate name: "Use 'intercalate' from Relude" note: "'intercalate' is already exported from Relude" rhs: intercalate - warn: lhs: Data.List.intersperse name: "Use 'intersperse' from Relude" note: "'intersperse' is already exported from Relude" rhs: intersperse - warn: lhs: Data.List.isPrefixOf name: "Use 'isPrefixOf' from Relude" note: "'isPrefixOf' is already exported from Relude" rhs: isPrefixOf - warn: lhs: Data.List.permutations name: "Use 'permutations' from Relude" note: "'permutations' is already exported from Relude" rhs: permutations - warn: lhs: "Data.List.scanl'" name: "Use 'scanl'' from Relude" note: "'scanl'' is already exported from Relude" rhs: "scanl'" - warn: lhs: Data.List.sort name: "Use 'sort' from Relude" note: "'sort' is already exported from Relude" rhs: sort - warn: lhs: Data.List.sortBy name: "Use 'sortBy' from Relude" note: "'sortBy' is already exported from Relude" rhs: sortBy - warn: lhs: Data.List.sortOn name: "Use 'sortOn' from Relude" note: "'sortOn' is already exported from Relude" rhs: sortOn - warn: lhs: Data.List.subsequences name: "Use 'subsequences' from Relude" note: "'subsequences' is already exported from Relude" rhs: subsequences - warn: lhs: Data.List.tails name: "Use 'tails' from Relude" note: "'tails' is already exported from Relude" rhs: tails - warn: lhs: Data.List.transpose name: "Use 'transpose' from Relude" note: "'transpose' is already exported from Relude" rhs: transpose - warn: lhs: Data.List.uncons name: "Use 'uncons' from Relude" note: "'uncons' is already exported from Relude" rhs: uncons - warn: lhs: Data.List.unfoldr name: "Use 'unfoldr' from Relude" note: "'unfoldr' is already exported from Relude" rhs: unfoldr - warn: lhs: Data.List.NonEmpty.NonEmpty name: "Use 'NonEmpty' from Relude" note: "'NonEmpty' is already exported from Relude" rhs: NonEmpty - warn: lhs: "(Data.List.NonEmpty.:|)" name: "Use ':|' from Relude" note: "Operator '(:|)' is already exported from Relude" rhs: "(:|)" - warn: lhs: Data.List.NonEmpty.nonEmpty name: "Use 'nonEmpty' from Relude" note: "'nonEmpty' is already exported from Relude" rhs: nonEmpty - warn: lhs: Data.List.NonEmpty.head name: "Use 'head' from Relude" note: "'head' is already exported from Relude" rhs: head - warn: lhs: Data.List.NonEmpty.init name: "Use 'init' from Relude" note: "'init' is already exported from Relude" rhs: init - warn: lhs: Data.List.NonEmpty.last name: "Use 'last' from Relude" note: "'last' is already exported from Relude" rhs: last - warn: lhs: Data.List.NonEmpty.tail name: "Use 'tail' from Relude" note: "'tail' is already exported from Relude" rhs: tail - warn: lhs: GHC.Exts.sortWith name: "Use 'sortWith' from Relude" note: "'sortWith' is already exported from Relude" rhs: sortWith - warn: lhs: Control.Monad.Except.ExceptT name: "Use 'ExceptT' from Relude" note: "'ExceptT' is already exported from Relude" rhs: ExceptT - warn: lhs: Control.Monad.Except.runExceptT name: "Use 'runExceptT' from Relude" note: "'runExceptT' is already exported from Relude" rhs: runExceptT - warn: lhs: Control.Monad.Reader.MonadReader name: "Use 'MonadReader' from Relude" note: "'MonadReader' is already exported from Relude" rhs: MonadReader - warn: lhs: Control.Monad.Reader.Reader name: "Use 'Reader' from Relude" note: "'Reader' is already exported from Relude" rhs: Reader - warn: lhs: Control.Monad.Reader.ReaderT name: "Use 'ReaderT' from Relude" note: "'ReaderT' is already exported from Relude" rhs: ReaderT - warn: lhs: Control.Monad.Reader.runReaderT name: "Use 'runReaderT' from Relude" note: "'runReaderT' is already exported from Relude" rhs: runReaderT - warn: lhs: Control.Monad.Reader.ask name: "Use 'ask' from Relude" note: "'ask' is already exported from Relude" rhs: ask - warn: lhs: Control.Monad.Reader.asks name: "Use 'asks' from Relude" note: "'asks' is already exported from Relude" rhs: asks - warn: lhs: Control.Monad.Reader.local name: "Use 'local' from Relude" note: "'local' is already exported from Relude" rhs: local - warn: lhs: Control.Monad.Reader.reader name: "Use 'reader' from Relude" note: "'reader' is already exported from Relude" rhs: reader - warn: lhs: Control.Monad.Reader.runReader name: "Use 'runReader' from Relude" note: "'runReader' is already exported from Relude" rhs: runReader - warn: lhs: Control.Monad.Reader.withReader name: "Use 'withReader' from Relude" note: "'withReader' is already exported from Relude" rhs: withReader - warn: lhs: Control.Monad.Reader.withReaderT name: "Use 'withReaderT' from Relude" note: "'withReaderT' is already exported from Relude" rhs: withReaderT - warn: lhs: Control.Monad.State.Strict.MonadState name: "Use 'MonadState' from Relude" note: "'MonadState' is already exported from Relude" rhs: MonadState - warn: lhs: Control.Monad.State.Strict.State name: "Use 'State' from Relude" note: "'State' is already exported from Relude" rhs: State - warn: lhs: Control.Monad.State.Strict.StateT name: "Use 'StateT' from Relude" note: "'StateT' is already exported from Relude" rhs: StateT - warn: lhs: Control.Monad.State.Strict.runStateT name: "Use 'runStateT' from Relude" note: "'runStateT' is already exported from Relude" rhs: runStateT - warn: lhs: Control.Monad.State.Strict.evalState name: "Use 'evalState' from Relude" note: "'evalState' is already exported from Relude" rhs: evalState - warn: lhs: Control.Monad.State.Strict.evalStateT name: "Use 'evalStateT' from Relude" note: "'evalStateT' is already exported from Relude" rhs: evalStateT - warn: lhs: Control.Monad.State.Strict.execState name: "Use 'execState' from Relude" note: "'execState' is already exported from Relude" rhs: execState - warn: lhs: Control.Monad.State.Strict.execStateT name: "Use 'execStateT' from Relude" note: "'execStateT' is already exported from Relude" rhs: execStateT - warn: lhs: Control.Monad.State.Strict.get name: "Use 'get' from Relude" note: "'get' is already exported from Relude" rhs: get - warn: lhs: Control.Monad.State.Strict.gets name: "Use 'gets' from Relude" note: "'gets' is already exported from Relude" rhs: gets - warn: lhs: Control.Monad.State.Strict.modify name: "Use 'modify' from Relude" note: "'modify' is already exported from Relude" rhs: modify - warn: lhs: "Control.Monad.State.Strict.modify'" name: "Use 'modify'' from Relude" note: "'modify'' is already exported from Relude" rhs: "modify'" - warn: lhs: Control.Monad.State.Strict.put name: "Use 'put' from Relude" note: "'put' is already exported from Relude" rhs: put - warn: lhs: Control.Monad.State.Strict.runState name: "Use 'runState' from Relude" note: "'runState' is already exported from Relude" rhs: runState - warn: lhs: Control.Monad.State.Strict.state name: "Use 'state' from Relude" note: "'state' is already exported from Relude" rhs: state - warn: lhs: Control.Monad.State.Strict.withState name: "Use 'withState' from Relude" note: "'withState' is already exported from Relude" rhs: withState - warn: lhs: Control.Monad.Trans.MonadIO name: "Use 'MonadIO' from Relude" note: "'MonadIO' is already exported from Relude" rhs: MonadIO - warn: lhs: Control.Monad.Trans.MonadTrans name: "Use 'MonadTrans' from Relude" note: "'MonadTrans' is already exported from Relude" rhs: MonadTrans - warn: lhs: Control.Monad.Trans.lift name: "Use 'lift' from Relude" note: "'lift' is already exported from Relude" rhs: lift - warn: lhs: Control.Monad.Trans.liftIO name: "Use 'liftIO' from Relude" note: "'liftIO' is already exported from Relude" rhs: liftIO - warn: lhs: Control.Monad.Trans.Identity.IdentityT name: "Use 'IdentityT' from Relude" note: "'IdentityT' is already exported from Relude" rhs: IdentityT - warn: lhs: Control.Monad.Trans.Identity.runIdentityT name: "Use 'runIdentityT' from Relude" note: "'runIdentityT' is already exported from Relude" rhs: runIdentityT - warn: lhs: Control.Monad.Trans.Maybe.MaybeT name: "Use 'MaybeT' from Relude" note: "'MaybeT' is already exported from Relude" rhs: MaybeT - warn: lhs: Control.Monad.Trans.Maybe.maybeToExceptT name: "Use 'maybeToExceptT' from Relude" note: "'maybeToExceptT' is already exported from Relude" rhs: maybeToExceptT - warn: lhs: Control.Monad.Trans.Maybe.exceptToMaybeT name: "Use 'exceptToMaybeT' from Relude" note: "'exceptToMaybeT' is already exported from Relude" rhs: exceptToMaybeT - warn: lhs: Control.Monad.MonadPlus name: "Use 'MonadPlus' from Relude" note: "'MonadPlus' is already exported from Relude" rhs: MonadPlus - warn: lhs: Control.Monad.mzero name: "Use 'mzero' from Relude" note: "'mzero' is already exported from Relude" rhs: mzero - warn: lhs: Control.Monad.mplus name: "Use 'mplus' from Relude" note: "'mplus' is already exported from Relude" rhs: mplus - warn: lhs: Control.Monad.filterM name: "Use 'filterM' from Relude" note: "'filterM' is already exported from Relude" rhs: filterM - warn: lhs: Control.Monad.forever name: "Use 'forever' from Relude" note: "'forever' is already exported from Relude" rhs: forever - warn: lhs: Control.Monad.join name: "Use 'join' from Relude" note: "'join' is already exported from Relude" rhs: join - warn: lhs: Control.Monad.mapAndUnzipM name: "Use 'mapAndUnzipM' from Relude" note: "'mapAndUnzipM' is already exported from Relude" rhs: mapAndUnzipM - warn: lhs: Control.Monad.mfilter name: "Use 'mfilter' from Relude" note: "'mfilter' is already exported from Relude" rhs: mfilter - warn: lhs: Control.Monad.replicateM name: "Use 'replicateM' from Relude" note: "'replicateM' is already exported from Relude" rhs: replicateM - warn: lhs: Control.Monad.replicateM_ name: "Use 'replicateM_' from Relude" note: "'replicateM_' is already exported from Relude" rhs: replicateM_ - warn: lhs: Control.Monad.zipWithM name: "Use 'zipWithM' from Relude" note: "'zipWithM' is already exported from Relude" rhs: zipWithM - warn: lhs: Control.Monad.zipWithM_ name: "Use 'zipWithM_' from Relude" note: "'zipWithM_' is already exported from Relude" rhs: zipWithM_ - warn: lhs: "(Control.Monad.<$!>)" name: "Use '<$!>' from Relude" note: "Operator '(<$!>)' is already exported from Relude" rhs: "(<$!>)" - warn: lhs: "(Control.Monad.<=<)" name: "Use '<=<' from Relude" note: "Operator '(<=<)' is already exported from Relude" rhs: "(<=<)" - warn: lhs: "(Control.Monad.=<<)" name: "Use '=<<' from Relude" note: "Operator '(=<<)' is already exported from Relude" rhs: "(=<<)" - warn: lhs: "(Control.Monad.>=>)" name: "Use '>=>' from Relude" note: "Operator '(>=>)' is already exported from Relude" rhs: "(>=>)" - warn: lhs: Control.Monad.Fail.MonadFail name: "Use 'MonadFail' from Relude" note: "'MonadFail' is already exported from Relude" rhs: MonadFail - warn: lhs: Data.Maybe.catMaybes name: "Use 'catMaybes' from Relude" note: "'catMaybes' is already exported from Relude" rhs: catMaybes - warn: lhs: Data.Maybe.fromMaybe name: "Use 'fromMaybe' from Relude" note: "'fromMaybe' is already exported from Relude" rhs: fromMaybe - warn: lhs: Data.Maybe.isJust name: "Use 'isJust' from Relude" note: "'isJust' is already exported from Relude" rhs: isJust - warn: lhs: Data.Maybe.isNothing name: "Use 'isNothing' from Relude" note: "'isNothing' is already exported from Relude" rhs: isNothing - warn: lhs: Data.Maybe.listToMaybe name: "Use 'listToMaybe' from Relude" note: "'listToMaybe' is already exported from Relude" rhs: listToMaybe - warn: lhs: Data.Maybe.mapMaybe name: "Use 'mapMaybe' from Relude" note: "'mapMaybe' is already exported from Relude" rhs: mapMaybe - warn: lhs: Data.Maybe.maybeToList name: "Use 'maybeToList' from Relude" note: "'maybeToList' is already exported from Relude" rhs: maybeToList - warn: lhs: Data.Either.isLeft name: "Use 'isLeft' from Relude" note: "'isLeft' is already exported from Relude" rhs: isLeft - warn: lhs: Data.Either.isRight name: "Use 'isRight' from Relude" note: "'isRight' is already exported from Relude" rhs: isRight - warn: lhs: Data.Either.lefts name: "Use 'lefts' from Relude" note: "'lefts' is already exported from Relude" rhs: lefts - warn: lhs: Data.Either.partitionEithers name: "Use 'partitionEithers' from Relude" note: "'partitionEithers' is already exported from Relude" rhs: partitionEithers - warn: lhs: Data.Either.rights name: "Use 'rights' from Relude" note: "'rights' is already exported from Relude" rhs: rights - warn: lhs: Data.Monoid.All name: "Use 'All' from Relude" note: "'All' is already exported from Relude" rhs: All - warn: lhs: Data.Monoid.getAll name: "Use 'getAll' from Relude" note: "'getAll' is already exported from Relude" rhs: getAll - warn: lhs: Data.Monoid.Alt name: "Use 'Alt' from Relude" note: "'Alt' is already exported from Relude" rhs: Alt - warn: lhs: Data.Monoid.getAlt name: "Use 'getAlt' from Relude" note: "'getAlt' is already exported from Relude" rhs: getAlt - warn: lhs: Data.Monoid.Any name: "Use 'Any' from Relude" note: "'Any' is already exported from Relude" rhs: Any - warn: lhs: Data.Monoid.getAny name: "Use 'getAny' from Relude" note: "'getAny' is already exported from Relude" rhs: getAny - warn: lhs: Data.Monoid.Ap name: "Use 'Ap' from Relude" note: "'Ap' is already exported from Relude" rhs: Ap - warn: lhs: Data.Monoid.getAp name: "Use 'getAp' from Relude" note: "'getAp' is already exported from Relude" rhs: getAp - warn: lhs: Data.Monoid.Dual name: "Use 'Dual' from Relude" note: "'Dual' is already exported from Relude" rhs: Dual - warn: lhs: Data.Monoid.getDual name: "Use 'getDual' from Relude" note: "'getDual' is already exported from Relude" rhs: getDual - warn: lhs: Data.Monoid.Endo name: "Use 'Endo' from Relude" note: "'Endo' is already exported from Relude" rhs: Endo - warn: lhs: Data.Monoid.appEndo name: "Use 'appEndo' from Relude" note: "'appEndo' is already exported from Relude" rhs: appEndo - warn: lhs: Data.Monoid.First name: "Use 'First' from Relude" note: "'First' is already exported from Relude" rhs: First - warn: lhs: Data.Monoid.getFirst name: "Use 'getFirst' from Relude" note: "'getFirst' is already exported from Relude" rhs: getFirst - warn: lhs: Data.Monoid.Last name: "Use 'Last' from Relude" note: "'Last' is already exported from Relude" rhs: Last - warn: lhs: Data.Monoid.getLast name: "Use 'getLast' from Relude" note: "'getLast' is already exported from Relude" rhs: getLast - warn: lhs: Data.Monoid.Product name: "Use 'Product' from Relude" note: "'Product' is already exported from Relude" rhs: Product - warn: lhs: Data.Monoid.getProduct name: "Use 'getProduct' from Relude" note: "'getProduct' is already exported from Relude" rhs: getProduct - warn: lhs: Data.Monoid.Sum name: "Use 'Sum' from Relude" note: "'Sum' is already exported from Relude" rhs: Sum - warn: lhs: Data.Monoid.getSum name: "Use 'getSum' from Relude" note: "'getSum' is already exported from Relude" rhs: getSum - warn: lhs: Data.Semigroup.Semigroup name: "Use 'Semigroup' from Relude" note: "'Semigroup' is already exported from Relude" rhs: Semigroup - warn: lhs: Data.Semigroup.sconcat name: "Use 'sconcat' from Relude" note: "'sconcat' is already exported from Relude" rhs: sconcat - warn: lhs: Data.Semigroup.stimes name: "Use 'stimes' from Relude" note: "'stimes' is already exported from Relude" rhs: stimes - warn: lhs: "(Data.Semigroup.<>)" name: "Use '<>' from Relude" note: "Operator '(<>)' is already exported from Relude" rhs: "(<>)" - warn: lhs: Data.Semigroup.WrappedMonoid name: "Use 'WrappedMonoid' from Relude" note: "'WrappedMonoid' is already exported from Relude" rhs: WrappedMonoid - warn: lhs: Data.Semigroup.cycle1 name: "Use 'cycle1' from Relude" note: "'cycle1' is already exported from Relude" rhs: cycle1 - warn: lhs: Data.Semigroup.mtimesDefault name: "Use 'mtimesDefault' from Relude" note: "'mtimesDefault' is already exported from Relude" rhs: mtimesDefault - warn: lhs: Data.Semigroup.stimesIdempotent name: "Use 'stimesIdempotent' from Relude" note: "'stimesIdempotent' is already exported from Relude" rhs: stimesIdempotent - warn: lhs: Data.Semigroup.stimesIdempotentMonoid name: "Use 'stimesIdempotentMonoid' from Relude" note: "'stimesIdempotentMonoid' is already exported from Relude" rhs: stimesIdempotentMonoid - warn: lhs: Data.Semigroup.stimesMonoid name: "Use 'stimesMonoid' from Relude" note: "'stimesMonoid' is already exported from Relude" rhs: stimesMonoid - warn: lhs: Data.ByteString.ByteString name: "Use 'ByteString' from Relude" note: "'ByteString' is already exported from Relude" rhs: ByteString - warn: lhs: Data.ByteString.Short.ShortByteString name: "Use 'ShortByteString' from Relude" note: "'ShortByteString' is already exported from Relude" rhs: ShortByteString - warn: lhs: Data.ByteString.Short.toShort name: "Use 'toShort' from Relude" note: "'toShort' is already exported from Relude" rhs: toShort - warn: lhs: Data.ByteString.Short.fromShort name: "Use 'fromShort' from Relude" note: "'fromShort' is already exported from Relude" rhs: fromShort - warn: lhs: Data.String.IsString name: "Use 'IsString' from Relude" note: "'IsString' is already exported from Relude" rhs: IsString - warn: lhs: Data.String.fromString name: "Use 'fromString' from Relude" note: "'fromString' is already exported from Relude" rhs: fromString - warn: lhs: Data.Text.Text name: "Use 'Text' from Relude" note: "'Text' is already exported from Relude" rhs: Text - warn: lhs: Data.Text.lines name: "Use 'lines' from Relude" note: "'lines' is already exported from Relude" rhs: lines - warn: lhs: Data.Text.unlines name: "Use 'unlines' from Relude" note: "'unlines' is already exported from Relude" rhs: unlines - warn: lhs: Data.Text.words name: "Use 'words' from Relude" note: "'words' is already exported from Relude" rhs: words - warn: lhs: Data.Text.unwords name: "Use 'unwords' from Relude" note: "'unwords' is already exported from Relude" rhs: unwords - warn: lhs: "Data.Text.Encoding.decodeUtf8'" name: "Use 'decodeUtf8'' from Relude" note: "'decodeUtf8'' is already exported from Relude" rhs: "decodeUtf8'" - warn: lhs: Data.Text.Encoding.decodeUtf8With name: "Use 'decodeUtf8With' from Relude" note: "'decodeUtf8With' is already exported from Relude" rhs: decodeUtf8With - warn: lhs: Data.Text.Encoding.Error.OnDecodeError name: "Use 'OnDecodeError' from Relude" note: "'OnDecodeError' is already exported from Relude" rhs: OnDecodeError - warn: lhs: Data.Text.Encoding.Error.OnError name: "Use 'OnError' from Relude" note: "'OnError' is already exported from Relude" rhs: OnError - warn: lhs: Data.Text.Encoding.Error.UnicodeException name: "Use 'UnicodeException' from Relude" note: "'UnicodeException' is already exported from Relude" rhs: UnicodeException - warn: lhs: Data.Text.Encoding.Error.lenientDecode name: "Use 'lenientDecode' from Relude" note: "'lenientDecode' is already exported from Relude" rhs: lenientDecode - warn: lhs: Data.Text.Encoding.Error.strictDecode name: "Use 'strictDecode' from Relude" note: "'strictDecode' is already exported from Relude" rhs: strictDecode - warn: lhs: Text.Read.Read name: "Use 'Read' from Relude" note: "'Read' is already exported from Relude" rhs: Read - warn: lhs: Text.Read.readMaybe name: "Use 'readMaybe' from Relude" note: "'readMaybe' is already exported from Relude" rhs: readMaybe - warn: lhs: "(liftIO (newEmptyMVar ))" name: "'liftIO' is not needed" note: "If you import 'newEmptyMVar' from Relude, it's already lifted" rhs: newEmptyMVar - warn: lhs: "(liftIO (newMVar x))" name: "'liftIO' is not needed" note: "If you import 'newMVar' from Relude, it's already lifted" rhs: newMVar - warn: lhs: "(liftIO (putMVar x y))" name: "'liftIO' is not needed" note: "If you import 'putMVar' from Relude, it's already lifted" rhs: putMVar - warn: lhs: "(liftIO (readMVar x))" name: "'liftIO' is not needed" note: "If you import 'readMVar' from Relude, it's already lifted" rhs: readMVar - warn: lhs: "(liftIO (swapMVar x y))" name: "'liftIO' is not needed" note: "If you import 'swapMVar' from Relude, it's already lifted" rhs: swapMVar - warn: lhs: "(liftIO (takeMVar x))" name: "'liftIO' is not needed" note: "If you import 'takeMVar' from Relude, it's already lifted" rhs: takeMVar - warn: lhs: "(liftIO (tryPutMVar x y))" name: "'liftIO' is not needed" note: "If you import 'tryPutMVar' from Relude, it's already lifted" rhs: tryPutMVar - warn: lhs: "(liftIO (tryReadMVar x))" name: "'liftIO' is not needed" note: "If you import 'tryReadMVar' from Relude, it's already lifted" rhs: tryReadMVar - warn: lhs: "(liftIO (tryTakeMVar x))" name: "'liftIO' is not needed" note: "If you import 'tryTakeMVar' from Relude, it's already lifted" rhs: tryTakeMVar - warn: lhs: "(liftIO (atomically x))" name: "'liftIO' is not needed" note: "If you import 'atomically' from Relude, it's already lifted" rhs: atomically - warn: lhs: "(liftIO (newTVarIO x))" name: "'liftIO' is not needed" note: "If you import 'newTVarIO' from Relude, it's already lifted" rhs: newTVarIO - warn: lhs: "(liftIO (readTVarIO x))" name: "'liftIO' is not needed" note: "If you import 'readTVarIO' from Relude, it's already lifted" rhs: readTVarIO - warn: lhs: "(liftIO (newTMVarIO x))" name: "'liftIO' is not needed" note: "If you import 'newTMVarIO' from Relude, it's already lifted" rhs: newTMVarIO - warn: lhs: "(liftIO (newEmptyTMVarIO ))" name: "'liftIO' is not needed" note: "If you import 'newEmptyTMVarIO' from Relude, it's already lifted" rhs: newEmptyTMVarIO - warn: lhs: "(liftIO (exitWith x))" name: "'liftIO' is not needed" note: "If you import 'exitWith' from Relude, it's already lifted" rhs: exitWith - warn: lhs: "(liftIO (exitFailure ))" name: "'liftIO' is not needed" note: "If you import 'exitFailure' from Relude, it's already lifted" rhs: exitFailure - warn: lhs: "(liftIO (exitSuccess ))" name: "'liftIO' is not needed" note: "If you import 'exitSuccess' from Relude, it's already lifted" rhs: exitSuccess - warn: lhs: "(liftIO (die x))" name: "'liftIO' is not needed" note: "If you import 'die' from Relude, it's already lifted" rhs: die - warn: lhs: "(liftIO (readFile x))" name: "'liftIO' is not needed" note: "If you import 'readFile' from Relude, it's already lifted" rhs: readFile - warn: lhs: "(liftIO (writeFile x y))" name: "'liftIO' is not needed" note: "If you import 'writeFile' from Relude, it's already lifted" rhs: writeFile - warn: lhs: "(liftIO (appendFile x y))" name: "'liftIO' is not needed" note: "If you import 'appendFile' from Relude, it's already lifted" rhs: appendFile - warn: lhs: "(liftIO (readFileText x))" name: "'liftIO' is not needed" note: "If you import 'readFileText' from Relude, it's already lifted" rhs: readFileText - warn: lhs: "(liftIO (writeFileText x y))" name: "'liftIO' is not needed" note: "If you import 'writeFileText' from Relude, it's already lifted" rhs: writeFileText - warn: lhs: "(liftIO (appendFileText x y))" name: "'liftIO' is not needed" note: "If you import 'appendFileText' from Relude, it's already lifted" rhs: appendFileText - warn: lhs: "(liftIO (readFileLText x))" name: "'liftIO' is not needed" note: "If you import 'readFileLText' from Relude, it's already lifted" rhs: readFileLText - warn: lhs: "(liftIO (writeFileLText x y))" name: "'liftIO' is not needed" note: "If you import 'writeFileLText' from Relude, it's already lifted" rhs: writeFileLText - warn: lhs: "(liftIO (appendFileLText x y))" name: "'liftIO' is not needed" note: "If you import 'appendFileLText' from Relude, it's already lifted" rhs: appendFileLText - warn: lhs: "(liftIO (readFileBS x))" name: "'liftIO' is not needed" note: "If you import 'readFileBS' from Relude, it's already lifted" rhs: readFileBS - warn: lhs: "(liftIO (writeFileBS x y))" name: "'liftIO' is not needed" note: "If you import 'writeFileBS' from Relude, it's already lifted" rhs: writeFileBS - warn: lhs: "(liftIO (appendFileBS x y))" name: "'liftIO' is not needed" note: "If you import 'appendFileBS' from Relude, it's already lifted" rhs: appendFileBS - warn: lhs: "(liftIO (readFileLBS x))" name: "'liftIO' is not needed" note: "If you import 'readFileLBS' from Relude, it's already lifted" rhs: readFileLBS - warn: lhs: "(liftIO (writeFileLBS x y))" name: "'liftIO' is not needed" note: "If you import 'writeFileLBS' from Relude, it's already lifted" rhs: writeFileLBS - warn: lhs: "(liftIO (appendFileLBS x y))" name: "'liftIO' is not needed" note: "If you import 'appendFileLBS' from Relude, it's already lifted" rhs: appendFileLBS - warn: lhs: "(liftIO (newIORef x))" name: "'liftIO' is not needed" note: "If you import 'newIORef' from Relude, it's already lifted" rhs: newIORef - warn: lhs: "(liftIO (readIORef x))" name: "'liftIO' is not needed" note: "If you import 'readIORef' from Relude, it's already lifted" rhs: readIORef - warn: lhs: "(liftIO (writeIORef x y))" name: "'liftIO' is not needed" note: "If you import 'writeIORef' from Relude, it's already lifted" rhs: writeIORef - warn: lhs: "(liftIO (modifyIORef x y))" name: "'liftIO' is not needed" note: "If you import 'modifyIORef' from Relude, it's already lifted" rhs: modifyIORef - warn: lhs: "(liftIO (modifyIORef' x y))" name: "'liftIO' is not needed" note: "If you import 'modifyIORef'' from Relude, it's already lifted" rhs: "modifyIORef'" - warn: lhs: "(liftIO (atomicModifyIORef x y))" name: "'liftIO' is not needed" note: "If you import 'atomicModifyIORef' from Relude, it's already lifted" rhs: atomicModifyIORef - warn: lhs: "(liftIO (atomicModifyIORef' x y))" name: "'liftIO' is not needed" note: "If you import 'atomicModifyIORef'' from Relude, it's already lifted" rhs: "atomicModifyIORef'" - warn: lhs: "(liftIO (atomicWriteIORef x y))" name: "'liftIO' is not needed" note: "If you import 'atomicWriteIORef' from Relude, it's already lifted" rhs: atomicWriteIORef - warn: lhs: "(liftIO (getLine ))" name: "'liftIO' is not needed" note: "If you import 'getLine' from Relude, it's already lifted" rhs: getLine - warn: lhs: "(liftIO (print x))" name: "'liftIO' is not needed" note: "If you import 'print' from Relude, it's already lifted" rhs: print - warn: lhs: "(liftIO (putStr x))" name: "'liftIO' is not needed" note: "If you import 'putStr' from Relude, it's already lifted" rhs: putStr - warn: lhs: "(liftIO (putStrLn x))" name: "'liftIO' is not needed" note: "If you import 'putStrLn' from Relude, it's already lifted" rhs: putStrLn - warn: lhs: "(liftIO (putText x))" name: "'liftIO' is not needed" note: "If you import 'putText' from Relude, it's already lifted" rhs: putText - warn: lhs: "(liftIO (putTextLn x))" name: "'liftIO' is not needed" note: "If you import 'putTextLn' from Relude, it's already lifted" rhs: putTextLn - warn: lhs: "(liftIO (putLText x))" name: "'liftIO' is not needed" note: "If you import 'putLText' from Relude, it's already lifted" rhs: putLText - warn: lhs: "(liftIO (putLTextLn x))" name: "'liftIO' is not needed" note: "If you import 'putLTextLn' from Relude, it's already lifted" rhs: putLTextLn - warn: lhs: "(liftIO (putBS x))" name: "'liftIO' is not needed" note: "If you import 'putBS' from Relude, it's already lifted" rhs: putBS - warn: lhs: "(liftIO (putBSLn x))" name: "'liftIO' is not needed" note: "If you import 'putBSLn' from Relude, it's already lifted" rhs: putBSLn - warn: lhs: "(liftIO (putLBS x))" name: "'liftIO' is not needed" note: "If you import 'putLBS' from Relude, it's already lifted" rhs: putLBS - warn: lhs: "(liftIO (putLBSLn x))" name: "'liftIO' is not needed" note: "If you import 'putLBSLn' from Relude, it's already lifted" rhs: putLBSLn - warn: lhs: "(liftIO (hFlush x))" name: "'liftIO' is not needed" note: "If you import 'hFlush' from Relude, it's already lifted" rhs: hFlush - warn: lhs: "(liftIO (hIsEOF x))" name: "'liftIO' is not needed" note: "If you import 'hIsEOF' from Relude, it's already lifted" rhs: hIsEOF - warn: lhs: "(liftIO (hSetBuffering x y))" name: "'liftIO' is not needed" note: "If you import 'hSetBuffering' from Relude, it's already lifted" rhs: hSetBuffering - warn: lhs: "(liftIO (hGetBuffering x))" name: "'liftIO' is not needed" note: "If you import 'hGetBuffering' from Relude, it's already lifted" rhs: hGetBuffering - warn: lhs: "(liftIO (getArgs ))" name: "'liftIO' is not needed" note: "If you import 'getArgs' from Relude, it's already lifted" rhs: getArgs - warn: lhs: "(liftIO (lookupEnv x))" name: "'liftIO' is not needed" note: "If you import 'lookupEnv' from Relude, it's already lifted" rhs: lookupEnv - hint: lhs: "fmap (bimap f g)" note: "Use `bimapF` from `Relude.Extra.Bifunctor`" rhs: bimapF f g - hint: lhs: "bimap f g <$> x" note: "Use `bimapF` from `Relude.Extra.Bifunctor`" rhs: bimapF f g x - hint: lhs: "fmap (first f)" note: "Use `firstF` from `Relude.Extra.Bifunctor`" rhs: firstF f - hint: lhs: fmap . first note: "Use `firstF` from `Relude.Extra.Bifunctor`" rhs: firstF - hint: lhs: "fmap (second f)" note: "Use `secondF` from `Relude.Extra.Bifunctor`" rhs: secondF f - hint: lhs: fmap . second note: "Use `secondF` from `Relude.Extra.Bifunctor`" rhs: secondF - hint: lhs: "[minBound .. maxBound]" note: "Use `universe` from `Relude.Extra.Enum`" rhs: universe - hint: lhs: succ note: "`succ` from `Prelude` is a pure function but it may throw exception. Consider using `next` from `Relude.Extra.Enum` instead." rhs: next - hint: lhs: pred note: "`pred` from `Prelude` is a pure function but it may throw exception. Consider using `prev` from `Relude.Extra.Enum` instead." rhs: prev - hint: lhs: toEnum note: "`toEnum` from `Prelude` is a pure function but it may throw exception. Consider using `safeToEnum` from `Relude.Extra.Enum` instead." rhs: safeToEnum - hint: lhs: sum xs / length xs note: "Use `average` from `Relude.Extra.Foldable`" rhs: average xs - hint: lhs: "\\a -> (a, a)" note: "Use `dup` from `Relude.Extra.Tuple`" rhs: dup - hint: lhs: "\\a -> (f a, a)" note: "Use `toFst` from `Relude.Extra.Tuple`" rhs: toFst f - hint: lhs: "\\a -> (a, f a)" note: "Use `toSnd` from `Relude.Extra.Tuple`" rhs: toSnd f - hint: lhs: fmap . toFst note: "Use `fmapToFst` from `Relude.Extra.Tuple`" rhs: fmapToFst - hint: lhs: "fmap (toFst f)" note: "Use `fmapToFst` from `Relude.Extra.Tuple`" rhs: fmapToFst f - hint: lhs: fmap . toSnd note: "Use `fmapToSnd` from `Relude.Extra.Tuple`" rhs: fmapToSnd - hint: lhs: "fmap (toSnd f)" note: "Use `fmapToSnd` from `Relude.Extra.Tuple`" rhs: fmapToSnd f - hint: lhs: map . toFst note: "Use `fmapToFst` from `Relude.Extra.Tuple`" rhs: fmapToFst - hint: lhs: "map (toFst f)" note: "Use `fmapToFst` from `Relude.Extra.Tuple`" rhs: fmapToFst f - hint: lhs: map . toSnd note: "Use `fmapToSnd` from `Relude.Extra.Tuple`" rhs: fmapToSnd - hint: lhs: "map (toSnd f)" note: "Use `fmapToSnd` from `Relude.Extra.Tuple`" rhs: fmapToSnd f - hint: lhs: "fmap (,a) (f a)" note: "Use `traverseToFst` from `Relude.Extra.Tuple`" rhs: traverseToFst f a - hint: lhs: "fmap (flip (,) a) (f a)" note: "Use `traverseToFst` from `Relude.Extra.Tuple`" rhs: traverseToFst f a - hint: lhs: "(,a) <$> f a" note: "Use `traverseToFst` from `Relude.Extra.Tuple`" rhs: traverseToFst f a - hint: lhs: "flip (,) a <$> f a" note: "Use `traverseToFst` from `Relude.Extra.Tuple`" rhs: traverseToFst f a - hint: lhs: "fmap (a,) (f a)" note: "Use `traverseToSnd` from `Relude.Extra.Tuple`" rhs: traverseToSnd f a - hint: lhs: "fmap ((,) a) (f a)" note: "Use `traverseToSnd` from `Relude.Extra.Tuple`" rhs: traverseToSnd f a - hint: lhs: "(a,) <$> f a" note: "Use `traverseToSnd` from `Relude.Extra.Tuple`" rhs: traverseToSnd f a - hint: lhs: "(,) a <$> f a" note: "Use `traverseToSnd` from `Relude.Extra.Tuple`" rhs: traverseToSnd f a ================================================ FILE: CHANGELOG.md ================================================ # Changelog All notable changes to this project (as seen by library users) will be documented in this file. The CHANGELOG is available on [Github](https://github.com/luc-tielen/souffle-haskell.git/CHANGELOG.md). ## [0.2.0] - Unreleased ### Added - Logical negation (of a single rule clause) - Typed hole support - Comparison operators - Arithmetic operators (`+`, `-`, `*`, `/`) - Possibility to link in external functions - LSP support - Document highlight - Hover - Diagnostics - Improved dead code elimination - Optimization passes: - HoistConstraints (faster searches by narrowing search-space as early as possible) - CLI: Allow emitting initial and transformed RA IR - Support named fields in type definitions and extern definitions - Support transpiling to Souffle - Support running semantic analysis on multiple threads ### Changed - Relations now can have additional qualifiers marking them as inputs or outputs. Not providing any qualifier means it is now an internal fact. ### Fixed - 0 is now parsed correctly as a number. - Type holes now correctly show all possible results in a rule. - BTree implementation is now better suited for large sets of facts ## [0.1.0] - 2022-11-20 ### Added - WebAssembly support - Support for the `string` data type - Wildcards are now supported in rule bodies - Assignments are now supported in rule bodies - Support for multiple occurences of the same variable in a single clause of a rule body - (UTF-8) strings in relations are now supported - Optimizations on the AST level: - Copy propagation - Dead code elimination ### Changed - Improved error reporting - Parsing now continues after failure and reports multiple errors back to the user at once. ### Fixed - Rules with multiple equalities. - Edgecase in index selection algorithm. The algorithm now does not take `NoElem` variants into account. ## [0.0.1] - 2022-06-14 ### Added - First MVP of the compiler! The happy path should work as expected, unsupported features or semantic errors should result in a (poorly formatted) error. ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Code of Conduct Contact: luc.tielen@gmail.com ## Why have a Code of Conduct? As contributors and maintainers of this project, we are committed to providing a friendly, safe and welcoming environment for all, regardless of age, disability, gender, nationality, race, religion, sexuality, or similar personal characteristic. The goal of the Code of Conduct is to specify a baseline standard of behavior so that people with different social values and communication styles can talk about Eclair effectively, productively, and respectfully, even in face of disagreements. The Code of Conduct also provides a mechanism for resolving conflicts in the community when they arise. ## Our Values These are the values Eclair developers should aspire to: - Be friendly and welcoming - Be kind - Remember that people have varying communication styles and that not everyone is using their native language. (Meaning and tone can be lost in translation.) - Interpret the arguments of others in good faith, do not seek to disagree. - When we do disagree, try to understand why. - Be thoughtful - Productive communication requires effort. Think about how your words will be interpreted. - Remember that sometimes it is best to refrain entirely from commenting. - Be respectful - In particular, respect differences of opinion. It is important that we resolve disagreements and differing views constructively. - Be constructive - Avoid derailing: stay on topic; if you want to talk about something else, start a new conversation. - Avoid unconstructive criticism: don't merely decry the current state of affairs; offer — or at least solicit — suggestions as to how things may be improved. - Avoid harsh words and stern tone: we are all aligned towards the well-being of the community and the progress of the ecosystem. Harsh words exclude, demotivate, and lead to unnecessary conflict. - Avoid snarking (pithy, unproductive, sniping comments). - Avoid microaggressions (brief and commonplace verbal, behavioral and environmental indignities that communicate hostile, derogatory or negative slights and insults towards a project, person or group). - Be responsible - What you say and do matters. Take responsibility for your words and actions, including their consequences, whether intended or otherwise. The following actions are explicitly forbidden: - Insulting, demeaning, hateful, or threatening remarks. - Discrimination based on age, disability, gender, nationality, race, religion, sexuality, or similar personal characteristic. - Bullying or systematic harassment. - Unwelcome sexual advances. - Incitement to any of these. ## Where does the Code of Conduct apply? If you participate in or contribute to the Eclair ecosystem in any way, you are encouraged to follow the Code of Conduct while doing so. Explicit enforcement of the Code of Conduct applies to the official mediums operated by the Eclair project: - The [official GitHub project][1] and code reviews. - The **[#Eclair][2]** Discord[2]. Other Eclair activities (such as conferences, meetups, and unofficial forums) are encouraged to adopt this Code of Conduct. Such groups must provide their own contact information. Project maintainers may block, remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by emailing: luc.tielen@gmail.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. **All reports will be kept confidential**. **The goal of the Code of Conduct is to resolve conflicts in the most harmonious way possible**. We hope that in most cases issues may be resolved through polite discussion and mutual agreement. Bannings and other forceful measures are to be employed only as a last resort. **Do not** post about the issue publicly or try to rally sentiment against a particular individual or group. ## Acknowledgements This document was based on the Code of Conduct from the Elixir project (dated Jul/2023), which in turn was based on the Go project (dated Sep/2021) and the Contributor Covenant (v1.4). [1]: https://github.com/luc-tielen/eclair-lang [2]: https://discord.gg/mC2arUrxKg ================================================ FILE: Dockerfile ================================================ FROM primordus/souffle-ubuntu:2.3 ARG LLVM_VERSION=17 SHELL [ "/bin/bash", "-c" ] # install packages RUN echo 'tzdata tzdata/Areas select Europe' | debconf-set-selections \ && echo 'tzdata tzdata/Zones/Europe select Paris' | debconf-set-selections \ && apt-get update \ && DEBIAN_FRONTEND=noninteractive apt-get install -y \ wget software-properties-common gnupg curl libffi-dev make \ python3 python3-pip libgmp-dev \ && curl -o - https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.2/install.sh | bash \ && source /root/.nvm/nvm.sh \ && nvm install 18.1.0 \ && echo "source /root/.ghcup/env" >> ~/.bashrc \ # install llvm 17 && mkdir -p /tmp/llvm-dir \ && cd /tmp/llvm-dir \ && wget https://apt.llvm.org/llvm.sh \ && chmod +x llvm.sh \ && ./llvm.sh $LLVM_VERSION \ && cd /tmp \ && rm -rf /tmp/llvm-dir \ && cd /usr/bin \ && ln -s /usr/lib/llvm-$LLVM_VERSION/bin/split-file \ && ln -s /usr/lib/llvm-$LLVM_VERSION/bin/FileCheck \ && ln -s clang-$LLVM_VERSION clang \ && ln -s wasm-ld-$LLVM_VERSION wasm-ld \ && cd - \ && pip install lit==14.0.6 \ # install ghcup, ghc-9.6.3 and cabal-3.10.1.0 && curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | \ BOOTSTRAP_HASKELL_NONINTERACTIVE=1 BOOTSTRAP_HASKELL_GHC_VERSION=9.6.3 BOOTSTRAP_HASKELL_CABAL_VERSION=3.10.1.0 \ BOOTSTRAP_HASKELL_INSTALL_STACK=1 BOOTSTRAP_HASKELL_INSTALL_HLS=1 BOOTSTRAP_HASKELL_ADJUST_BASHRC=P sh \ && source /root/.ghcup/env \ && cabal install cabal-fmt \ && cabal install hspec-discover \ && apt-get autoremove -y \ && apt-get purge -y --auto-remove \ && rm -rf /var/lib/apt/lists/* VOLUME /code WORKDIR /app/build ENV DATALOG_DIR=/app/build/cbits RUN echo -e '#!/bin/bash\nsource /root/.ghcup/env\nsource /root/.nvm/nvm.sh\nexec "$@"\n' > /app/build/entrypoint.sh \ && chmod u+x /app/build/entrypoint.sh # The entrypoint script sources ghcup setup script so we can easily call cabal etc. ENTRYPOINT [ "/app/build/entrypoint.sh" ] COPY . . RUN source /root/.ghcup/env && make build \ && echo -e '#!/bin/bash\nsource /root/.ghcup/env\n' > /usr/bin/eclair \ && source /root/.ghcup/env \ && echo -e "`cabal list-bin eclair` \"\$@\"" >> /usr/bin/eclair \ && chmod u+x /usr/bin/eclair # The default command to run, shows the help menu CMD [ "eclair", "--help" ] ================================================ FILE: LICENSE ================================================ Copyright Luc Tielen (c) 2022 All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Luc Tielen nor the names of other contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================ FILE: Makefile ================================================ build: configure @cabal build configure: @cabal configure -f eclair-debug --enable-tests clean: @cabal clean test: @DATALOG_DIR=cbits/ cabal run eclair-test @lit tests/ -v cabal-file: @cabal-fmt --Werror -i eclair-lang.cabal .PHONY: build configure clean test cabal-file ================================================ FILE: README.md ================================================ Logo for the Eclair programming language _An experimental and minimal Datalog implementation that compiles down to LLVM._ [![Build](https://github.com/luc-tielen/eclair-lang/actions/workflows/build.yml/badge.svg)](https://github.com/luc-tielen/eclair-lang/actions/workflows/build.yml) ## Features Eclair is a minimal Datalog (for now). It supports the following features: - Facts containing literals - Rules consisting of one or more clauses. - Rules can be non-recursive, recursive or mutually recursive. Right now it compiles to LLVM but be aware there might still be bugs. Some edge cases might not be handled yet. ## Motivating example Let's say we want to find out which points are reachable in a graph. We can determine which points are reachable using the following two logical rules: 1. One point is reachable from another point, iff there is a direct edge between those two points. 2. One point is reachable from another point, iff there is a third point 'z' such that there is a direct edge between 'x' and 'z', and between 'z' and 'y'. The Eclair code below can be used to calculate the solution: ```eclair @def edge(u32, u32). @def reachable(u32, u32). reachable(x, y) :- edge(x, y). reachable(x, z) :- edge(x, y), reachable(y, z). ``` The above code can be compiled to LLVM using the Docker image provided by this repo: ```bash $ git clone git@github.com:luc-tielen/eclair-lang.git $ cd eclair-lang $ docker build . -t eclair # The next line assumes the eclair code is saved as "example.dl" in the current directory $ docker run -v $PWD:/code --rm -it eclair:latest compile /code/example.dl # NOTE: output can be redirected to a file using standard shell functionality: docker run ... > example.ll ``` This will emit the generated LLVM IR to the stdout of the terminal. If we save this generated LLVM IR to a file (e.g. `example.ll`), we can link it with the following C code that calls into Eclair, using the following command: `clang -o program main.c example.ll`. ```c // Save this file as "main.c". #include #include #include #include struct program; extern struct program* eclair_program_init(); extern void eclair_program_destroy(struct program*); extern void eclair_program_run(struct program*); extern void eclair_add_facts(struct program*, uint16_t fact_type, uint32_t* data, size_t fact_count); extern void eclair_add_fact(struct program*, uint16_t fact_type, uint32_t* data); extern uint32_t* eclair_get_facts(struct program*, uint16_t fact_type); extern void eclair_free_buffer(uint32_t* data); int main(int argc, char** argv) { struct program* prog = eclair_program_init(); // edge(1,2), edge(2,3) uint32_t data[] = { 1, 2, 2, 3 }; eclair_add_facts(prog, 0, data, 2); eclair_program_run(prog); // NOTE: normally you call btree_size here to figure out the size, but I know there are only 3 facts uint32_t* data_out = eclair_get_facts(prog, 1); printf("REACHABLE: (%d, %d)\n", data_out[0], data_out[1]); // (1,2) printf("REACHABLE: (%d, %d)\n", data_out[2], data_out[3]); // (2,3) printf("REACHABLE: (%d, %d)\n", data_out[4], data_out[5]); // (1,3) eclair_free_buffer(data_out); eclair_program_destroy(prog); return 0; } ``` If you run the resulting program, this should print the reachable node pairs `(1,2)`, `(2,3)` and `(1,3)` to the screen! ## Roadmap - [x] LSP support - [x] Allow setting options on relations for performance finetuning - [x] Comparison operators, != operator - [x] Support arithmetic operators - [x] Generic, extensible primops - [x] Support logical negation - [ ] Release 0.2.0 - [ ] Signed integer type (i32) - [ ] Unary negation - [ ] Optimizations on the AST / RA / LLVM level - [ ] Support other underlying data structures than btree - [ ] Syntactic sugar (disjunctions in rule bodies, multiple rule heads, ...) - [ ] Support Datalog programs spanning multiple files - [ ] ... This roadmap is not set in stone, but it gives an idea on the direction of the project. :smile: ## Contributing to Eclair Contributions are welcome! Take a look at the [getting started guide](./docs/getting_started.md) on how to set up your machine to build, run and test the project. Once setup, the Makefile contains the most commonly used commands needed during development. You can also use the `Dockerfile` in this repository if you want to experiment with Eclair without installing the toolchain yourself. You can do that as follows: ```bash $ docker build -f Dockerfile . -t eclair $ touch test.eclair # Here you can put your Eclair code $ docker run -v $PWD:/code --rm -it eclair eclair compile /code/test.eclair ``` ## Documentation Take a look at our [docs folder](./docs/) for more information about Eclair. ## Why the name? Eclair is inspired by [Soufflé](https://souffle-lang.github.io/), a high performance Datalog that compiles to C++. Because of the similarities, I chose a different kind of food that I like. I mean, an eclair contains _both_ chocolate and pudding, what's not to like!? Logo art by [Bruno Monts](https://www.instagram.com/bruno_monts/), with special thanks to the [Fission](https://fission.codes) team. Please contact Luc Tielen before using the logo for anything. ================================================ FILE: cabal.project ================================================ packages: . source-repository-package type: git location: https://github.com/luc-tielen/llvm-codegen.git tag: 83b04cb576208ea74ddd62016e4fa03f0df138ac source-repository-package type: git location: https://github.com/luc-tielen/souffle-haskell.git tag: e441c84f1d64890e31c92fbb278c074ae8bcaff5 source-repository-package type: git location: https://github.com/luc-tielen/diagnose.git tag: 24a1d7a2b716d74c1fe44d47941a76c9f5a90c23 ================================================ FILE: cbits/semantic_analysis.dl ================================================ // Input facts .decl lit_number(node_id: unsigned, value: unsigned) .decl lit_string(node_id: unsigned, value: symbol) .decl variable(node_id: unsigned, var_name: symbol) .decl constraint(node_id: unsigned, op: symbol, lhs_node_id: unsigned, rhs_node_id: unsigned) .decl binop(node_id: unsigned, op: symbol, lhs_node_id: unsigned, rhs_node_id: unsigned) .decl atom(node_id: unsigned, name: symbol) .decl atom_arg(atom_id: unsigned, atom_arg_pos: unsigned, atom_arg_id: unsigned) .decl rule(rule_id: unsigned, name: symbol) .decl rule_arg(rule_id: unsigned, rule_arg_pos: unsigned, rule_arg_id: unsigned) .decl rule_clause(rule_id: unsigned, rule_clause_pos: unsigned, rule_clause_id: unsigned) .decl negation(negation_node_id: unsigned, inner_node_id: unsigned) .decl input_relation(relation_name: symbol) .decl output_relation(relation_name: symbol) .decl internal_relation(relation_name: symbol) .decl extern_definition(node_id: unsigned, extern_name: symbol) .decl declare_type(node_id: unsigned, name: symbol) .decl module(node_id: unsigned) .decl module_declaration(module_id: unsigned, declaration_id: unsigned) .decl scoped_value(scope_id: unsigned, value_id: unsigned) // Internal rules .decl relation_atom(node_id: unsigned, name: symbol) .decl extern_atom(node_id: unsigned, name: symbol) .decl grounded_node(rule_node_id: unsigned, node_id: unsigned) .decl assign(node_id: unsigned, lhs_node_id: unsigned, rhs_node_id: unsigned) inline .decl inequality_op(op: symbol) .decl has_output_relation(node_id: unsigned) .decl literal_contradiction(lit_id1: unsigned, lit_id2: unsigned) .decl wildcard(node_id: unsigned) inline .decl rule_head_var(rule_id: unsigned, var_id: unsigned, var_name: symbol) .decl alias(rule_id: unsigned, id1: unsigned, id2: unsigned) .decl points_to(rule_id: unsigned, id1: unsigned, id2: unsigned) .decl depends_on(r1: symbol, r2: symbol) .decl transitive_depends_on(r1: symbol, r2: symbol) .decl source(r: symbol) .decl has_definitions(relation: symbol) .decl live_rule(relation: symbol) .decl dependency_cycle(relation: symbol) .decl rule_scope(rule_id: unsigned, scope_id: unsigned) .decl constrained_rule_var(rule_node_id: unsigned, var_node_id: unsigned, var_name: symbol) // Output facts / rules .decl grounded_variable(rule_id: unsigned, var_name: symbol) .decl ungrounded_variable(rule_id: unsigned, var_id: unsigned, var_name: symbol) .decl ungrounded_external_atom(rule_id: unsigned, atom_id: unsigned, atom_name: symbol) .decl wildcard_in_fact(fact_node_id: unsigned, fact_arg_id: unsigned, pos: unsigned) .decl wildcard_in_extern(atom_node_id: unsigned, atom_arg_id: unsigned, pos: unsigned) .decl wildcard_in_rule_head(rule_node_id: unsigned, rule_arg_id: unsigned, pos: unsigned) .decl wildcard_in_constraint(constraint_node_id: unsigned, wildcard_node_id: unsigned) .decl wildcard_in_binop(binop_node_id: unsigned, wildcard_node_id: unsigned) .decl unconstrained_rule_var(rule_node_id: unsigned, var_node_id: unsigned, var_name: symbol) .decl rule_with_contradiction(rule_id: unsigned) .decl dead_code(node_id: unsigned) .decl no_output_relation(node_id: unsigned) .decl dead_internal_relation(node_id: unsigned, relation_name: symbol) .decl conflicting_definitions(node_id1: unsigned, node_id2: unsigned, name: symbol) .decl extern_used_as_fact(node_id: unsigned, extern_node_id: unsigned, name: symbol) .decl extern_used_as_rule(node_id: unsigned, extern_node_id: unsigned, name: symbol) .decl cyclic_negation(negation_id: unsigned) .input lit_number .input lit_string .input variable .input constraint .input binop .input atom .input atom_arg .input rule .input rule_arg .input rule_clause .input negation .input input_relation .input output_relation .input internal_relation .input extern_definition .input declare_type .input module .input module_declaration .input scoped_value .output wildcard_in_fact .output wildcard_in_extern .output ungrounded_variable .output ungrounded_external_atom .output wildcard_in_rule_head .output wildcard_in_constraint .output wildcard_in_binop .output unconstrained_rule_var .output dead_code .output no_output_relation .output dead_internal_relation .output conflicting_definitions .output extern_used_as_fact .output extern_used_as_rule .output cyclic_negation // An atom that is not defined externally. This is an important distinction // since external atoms cannot ground variables! relation_atom(node_id, name) :- declare_type(_, name), atom(node_id, name). // An atom that is defined externally. extern_atom(node_id, name) :- extern_definition(_, name), atom(node_id, name). // r1 depends on r2 if rule r1 refers to r2 in the body depends_on(r1, r2) :- rule(rule_id, r1), rule_clause(rule_id, _, clause_id), atom(clause_id, r2). // Variant for negated atoms. depends_on(r1, r2) :- rule(rule_id, r1), rule_clause(rule_id, _, negation_id), negation(negation_id, atom_id), atom(atom_id, r2). // Variant for extern functions. depends_on(r1, r2) :- rule(rule_id, r1), rule_clause(rule_id, _, assign_id), assign(assign_id, lhs_node_id, rhs_node_id), ( extern_atom(lhs_node_id, r2); extern_atom(rhs_node_id, r2) ). transitive_depends_on(r1, r2) :- depends_on(r1, r2). transitive_depends_on(r1, r3) :- depends_on(r1, r2), transitive_depends_on(r2, r3). // Rules have cyclic dependencies if a rule depends on itself (transitively) dependency_cycle(r) :- transitive_depends_on(r, r). // Negations are not allowed inside a rule if the negated atom is part of a // dependency cycle since this is not stratifiable. cyclic_negation(negation_id) :- dependency_cycle(r1), transitive_depends_on(r1, r2), rule(rule_id, r2), rule_clause(rule_id, _, negation_id), negation(negation_id, atom_id), atom(atom_id, r3), transitive_depends_on(r3, r1). // An input can always be a source of data. source(r) :- input_relation(r). // An internal or output relation can be a source of data if they define top level facts. source(r) :- module_declaration(_, atom_id), atom(atom_id, r), !input_relation(r). // internal or output relation // An output rule is live if it is a direct source of data. live_rule(r) :- output_relation(r), source(r). // An output rule is live if there is a path from the source to this output. live_rule(r1) :- output_relation(r1), transitive_depends_on(r1, r2), source(r2). // A rule is live if it is depended on by another live rule. live_rule(r2) :- depends_on(r1, r2), live_rule(r1). // Dead rules are the opposite set of all the live rules. dead_code(node_id) :- rule(node_id, r), !live_rule(r). // Type definitions also need to be marked as dead. dead_code(node_id) :- declare_type(node_id, r), !live_rule(r). // Extern definitions also need to be marked as dead. dead_code(node_id) :- extern_definition(node_id, r), !live_rule(r). // Atoms too. dead_code(node_id) :- atom(node_id, r), !live_rule(r). // Rules are dead if one of the clauses is statically known to produce no results. dead_code(rule_id) :- rule_with_contradiction(rule_id). // A rule is dead if it depends on another dead rule. // Note that this only looks at one specific rule that contains the dead code, not the entire relation. dead_code(rule_id) :- rule_clause(rule_id, _, rule_clause_id), dead_code(rule_clause_id). has_definitions(r) :- module_declaration(_, node_id), ( atom(node_id, r); rule(node_id, r) ). // We consider an internal relation to be dead if there are no top level atoms or rules. dead_internal_relation(decl_id, r) :- internal_relation(r), declare_type(decl_id, r), !has_definitions(r). has_output_relation(node_id) :- module_declaration(node_id, decl_id), declare_type(decl_id, r), output_relation(r). no_output_relation(node_id) :- module_declaration(node_id, _), !has_output_relation(node_id). // Top level facts: no variables allowed ungrounded_variable(atom_id, var_id, var_name) :- module_declaration(_, atom_id), atom(atom_id, _), scoped_value(atom_id, var_id), variable(var_id, var_name), var_name != "_". // Rules: no variables allowed in rule head if not used in a rule clause ungrounded_variable(rule_id, var_id, var_name) :- rule_head_var(rule_id, var_id, var_name), var_name != "_", !grounded_variable(rule_id, var_name). // Only compare by variable name! // Variables used in a constraint (comparison, equality or inequality) need to be grounded. ungrounded_variable(rule_id, var_id, var_name) :- rule_clause(rule_id, _, rule_clause_id), constraint(rule_clause_id, op, var_id, _), variable(var_id, var_name), var_name != "_", !grounded_variable(rule_id, var_name). ungrounded_variable(rule_id, var_id, var_name) :- rule_clause(rule_id, _, rule_clause_id), constraint(rule_clause_id, op, _, var_id), variable(var_id, var_name), var_name != "_", !grounded_variable(rule_id, var_name). // Variables used in a binop need to be grounded. ungrounded_variable(rule_id, var_id, var_name) :- binop(_, _, var_id, _), variable(var_id, var_name), scoped_value(scope_id, var_id), rule_scope(rule_id, scope_id), !grounded_variable(rule_id, var_name). ungrounded_variable(rule_id, var_id, var_name) :- binop(_, _, _, var_id), variable(var_id, var_name), scoped_value(scope_id, var_id), rule_scope(rule_id, scope_id), !grounded_variable(rule_id, var_name). // Variables used in a negation need to be grounded. ungrounded_variable(rule_id, var_id, var_name) :- negation(negation_id, _), rule_clause(rule_id, _, negation_id), scoped_value(negation_id, var_id), variable(var_id, var_name), !grounded_node(rule_id, var_id). // External atoms used in a fact need to be grounded. ungrounded_external_atom(fact_id, atom_id, atom_name) :- module_declaration(_, fact_id), atom_arg(fact_id, _, atom_id), extern_atom(atom_id, atom_name), !grounded_node(fact_id, atom_id). // External atoms used in a rule head need to be grounded. ungrounded_external_atom(rule_id, atom_id, atom_name) :- rule_arg(rule_id, _, atom_id), atom(atom_id, atom_name), scoped_value(rule_id, atom_id), !grounded_node(rule_id, atom_id). // External atoms used in a comparison or inequality need to be grounded. ungrounded_external_atom(rule_id, atom_id, atom_name) :- rule_clause(rule_id, _, rule_clause_id), constraint(rule_clause_id, op, _, atom_id), inequality_op(op), atom(atom_id, atom_name), scoped_value(rule_id, atom_id), !grounded_node(rule_id, atom_id). ungrounded_external_atom(rule_id, atom_id, atom_name) :- rule_clause(rule_id, _, rule_clause_id), constraint(rule_clause_id, op, atom_id, _), inequality_op(op), atom(atom_id, atom_name), scoped_value(rule_id, atom_id), !grounded_node(rule_id, atom_id). // External atoms used in a binop need to be grounded. ungrounded_external_atom(rule_id, atom_id, atom_name) :- binop(_, _, _, atom_id), atom(atom_id, atom_name), scoped_value(rule_id, atom_id), !grounded_node(rule_id, atom_id). ungrounded_external_atom(rule_id, atom_id, atom_name) :- binop(_, _, atom_id, _), atom(atom_id, atom_name), scoped_value(rule_id, atom_id), !grounded_node(rule_id, atom_id). rule_scope(rule_id, rule_id) :- rule(rule_id, _). rule_scope(rule_id, negation_id) :- rule_clause(rule_id, _, negation_id), negation(negation_id, _). inequality_op("!="). inequality_op("<"). inequality_op("<="). inequality_op(">"). inequality_op(">="). wildcard(node_id) :- variable(node_id, "_"). wildcard_in_rule_head(rule_id, rule_arg_id, pos) :- rule_arg(rule_id, pos, rule_arg_id), wildcard(rule_arg_id). wildcard_in_fact(atom_id, atom_arg_id, pos) :- module_declaration(_, atom_id), atom_arg(atom_id, pos, atom_arg_id), wildcard(atom_arg_id). wildcard_in_extern(atom_id, atom_arg_id, pos) :- rule(rule_id, _), scoped_value(rule_id, atom_id), extern_atom(atom_id, _), atom_arg(atom_id, pos, atom_arg_id), wildcard(atom_arg_id). wildcard_in_constraint(constraint_node_id, lhs_node_id) :- constraint(constraint_node_id, _, lhs_node_id, _), wildcard(lhs_node_id). wildcard_in_constraint(constraint_node_id, rhs_node_id) :- constraint(constraint_node_id, _, _, rhs_node_id), wildcard(rhs_node_id). wildcard_in_binop(binop_node_id, lhs_node_id) :- binop(binop_node_id, _, lhs_node_id, _), wildcard(lhs_node_id). wildcard_in_binop(binop_node_id, rhs_node_id) :- binop(binop_node_id, _, _, rhs_node_id), wildcard(rhs_node_id). // A rule variable is unconstrained if there is no other occurrence of the variable in the rule. // (This works because groundedness of a variable is also checked.. unconstrained_rule_var(rule_id, var_id, var_name) :- rule_scope(rule_id, scope_id), scoped_value(scope_id, var_id), variable(var_id, var_name), !constrained_rule_var(rule_id, var_id, var_name). // This could be done much simpler using count aggregate but this is not implemented in Eclair yet. constrained_rule_var(rule_id, var_id1, var_name) :- rule_scope(rule_id, scope_id1), rule_scope(rule_id, scope_id2), scoped_value(scope_id1, var_id1), scoped_value(scope_id2, var_id2), variable(var_id1, var_name), variable(var_id2, var_name), var_id1 != var_id2. assign(node_id, lhs_node_id, rhs_node_id) :- constraint(node_id, "=", lhs_node_id, rhs_node_id). // All variables with the same name in a rule are aliases of each other. alias(rule_id, var_id1, var_id2) :- scoped_value(rule_id, var_id1), variable(var_id1, var_name), scoped_value(rule_id, var_id2), var_id1 != var_id2, variable(var_id2, var_name). // Two values are aliases if they are used inside an equality. // NOTE: Datalog supports both x = 123 and 123 = x. alias(rule_id, id1, id2), alias(rule_id, id2, id1) :- rule_clause(rule_id, _, rule_clause_id), assign(rule_clause_id, id1, id2). // Non-recursive case: what does a variable point to? points_to(rule_id, id1, id2) :- alias(rule_id, id1, id2), variable(id1, _). // Recursive case: a = b, b = c results in a = c points_to(rule_id, id1, id4) :- points_to(rule_id, id1, id2), variable(id2, var_name), variable(id3, var_name), alias(rule_id, id3, id4). // If we find two variables that point to different literal values, // then there is a contradiction. rule_with_contradiction(rule_id) :- points_to(rule_id, start_id, id1), points_to(rule_id, start_id, id2), literal_contradiction(id1, id2). // This is also true for simple cases like '123 = 456'. rule_with_contradiction(rule_id) :- rule_clause(rule_id, _, rule_clause_id), assign(rule_clause_id, id1, id2), literal_contradiction(id1, id2). literal_contradiction(id1, id2) :- lit_number(id1, value1), lit_number(id2, value2), value1 != value2. literal_contradiction(id1, id2) :- lit_string(id1, value1), lit_string(id2, value2), value1 != value2. // Helper relation for getting all variables in head of a rule. rule_head_var(rule_id, var_id, var_name) :- rule_arg(rule_id, _, var_id), variable(var_id, var_name). // Helper relation for getting all grounded variables in body of a rule. grounded_variable(rule_id, var_name) :- grounded_node(rule_id, var_id), variable(var_id, var_name). // Variables are grounded if they are used in an atom (defined using '@def'). grounded_node(rule_id, var_id) :- rule_clause(rule_id, _, rule_clause_id), relation_atom(rule_clause_id, _), atom_arg(rule_clause_id, _, var_id), variable(var_id, _). // All variables with same name are grounded at the same time grounded_node(rule_id, var_id2) :- scoped_value(rule_id, var_id1), variable(var_id1, var_name), grounded_node(rule_id, var_id1), scoped_value(rule_id, var_id2), variable(var_id2, var_name). // Variables are grounded inside a negation if they are grounded in another clause. grounded_node(rule_id, var_id2) :- negation(negation_id, _), rule_clause(rule_id, _, negation_id), scoped_value(rule_id, var_id1), variable(var_id1, var_name), grounded_node(rule_id, var_id1), scoped_value(negation_id, var_id2), variable(var_id2, var_name). // Literals are always grounded. grounded_node(rule_id, node_id) :- scoped_value(rule_id, node_id), lit_number(node_id, _). grounded_node(rule_id, node_id) :- scoped_value(rule_id, node_id), lit_string(node_id, _). // A binop is grounded if both sides are grounded. grounded_node(rule_id, node_id) :- grounded_node(rule_id, lhs_node_id), grounded_node(rule_id, rhs_node_id), binop(node_id, _, lhs_node_id, rhs_node_id). // Assignment grounds one var, if the other side is already grounded. grounded_node(rule_id, rhs_node_id) :- rule_clause(rule_id, _, rule_clause_id), assign(rule_clause_id, lhs_node_id, rhs_node_id), grounded_node(rule_id, lhs_node_id), variable(rhs_node_id, _). grounded_node(rule_id, lhs_node_id) :- rule_clause(rule_id, _, rule_clause_id), assign(rule_clause_id, lhs_node_id, rhs_node_id), grounded_node(rule_id, rhs_node_id), variable(lhs_node_id, _). conflicting_definitions(node_id, node_id2, name) :- declare_type(node_id, name), declare_type(node_id2, name), node_id < node_id2. conflicting_definitions(node_id, node_id2, name) :- extern_definition(node_id, name), extern_definition(node_id2, name), node_id < node_id2. conflicting_definitions(node_id, node_id2, name) :- declare_type(node_id, name), extern_definition(node_id2, name), node_id < node_id2. conflicting_definitions(node_id, node_id2, name) :- extern_definition(node_id, name), declare_type(node_id2, name), node_id < node_id2. extern_used_as_fact(node_id, extern_node_id, name) :- extern_definition(extern_node_id, name), module_declaration(_, node_id), atom(node_id, name). extern_used_as_rule(node_id, extern_node_id, name) :- extern_definition(extern_node_id, name), rule(node_id, name). ================================================ FILE: docs/architecture_choices.md ================================================ # Architecture choices This document contains all the high level choices that have been made with regards to the architecture of the compiler. Besides this document, there are also [several blogposts](https://luctielen.com/) with deep dives on specific parts of the compiler. ## Inspired by Souffle Eclair's approach to compiling high level Datalog syntax to assembly is heavily inspired by Souffle Datalog (most notably: the compilation to relational algebra and the minimum index selection algorithm). At the top of the corresponding source code in Eclair, there is a comment pointing to the paper so contributors can consult the theoretic background behind the code easily. Eclair does have some notable differences though. First and foremost: it is written in Haskell. This choice was made because Haskell makes it easy to express high level ideas and algorithms that you commonly run into when building a compiler. As an additional benefit, Haskell already has a great ecosystem of libraries for building a compiler. The second big change compared to Souffle is that Eclair compiles down directly to LLVM instead of C++. This gives greater control of the generated assembly (assembly is generated using a monad / "builder pattern" instead of concatenating strings to form a C++ program) and also makes it easily portable to other platforms (including WebAssembly). On top of that we can leverage existing LLVM tools to analyze / transform the generated LLVM IR. ## IR Design Eclair makes use of four different intermediate representations (IRs). Each of the IRs has a different focus / view of the program. By using multiple IRs, we can also gradually lower the Datalog syntax to the assembly level. The four different IRs are: 1. AST 2. RA 3. EIR 4. LLVM IR Each of these IRs is discussed in the subsections below. Every IR can be pretty printed for inspection (when debugging a compiler issue or when writing Eclair code). Each of the IRs are designed in a similar way: they all consist of a single data type each. This might be a bit controversial for most Haskellers that value type safety, but this ends up working out because: 1. The parser, semantic analysis and typesystem steps halt when they determine the program is invalid; 2. Often we are only interested in a really small part of the IR anyway; 3. Most Haskell libraries support one simple type best. The singly-typed IR is a conciously made trade-off, but it gives us great benefits. Transformations (a large part of the compiler!) can be written down succinctly thanks to the [recursion-schemes library](https://hackage.haskell.org/package/recursion-schemes). The transforms are guaranteed to terminate when written this way, and are automatically composable. Besides that, all algorithms can be written down as a pattern-match that focuses on only one node of the IR. Besides having singly-typed IRs, each data constructor in the IR type has a unique node ID attached to it. This makes it possible to link data from outside the IR to it, without having to keep modifying the IR over and over. This is especially useful for the semantic analysis and typesystem parts of the compiler, since they can refer to parts of the program via a node ID. ### AST The first IR is the `AST` type. AST stands for "Abstract Syntax Tree" and is a tree representation of the original source code. Semantic analysis and typechecking happens on this IR before any transformations are performed, so we can report back exact locations to users of Eclair. The AST is the most "high level" / starting IR. AST compiles down to RA, which is described in the next section. ### RA `RA` stands for "Relational Algebra". It represents a Eclair Datalog program as a set of relational algebra operations. The data type is pretty much copied directly from the Souffle paper, with some minor modifications. By first transforming the AST to RA, we can subsequently lower the code even further down to the assembly level (via EIR and LLVM IR). ### EIR `EIR` is an abbreviation for "Eclair IR". It is a IR designed to be very close to LLVM IR, but with the focus that it is also easy to debug and inspect. It was mainly created to make the final lowering to LLVM IR as trivial as possible, but it ended up being also useful for stitching the various Eclair functions in the runtime together. ### LLVM IR The LLVM IR is the final IR this compiler makes use of and bears the closest resemblance to assembly. The [llvm-codegen library](https://github.com/luc-tielen/llvm-codegen) is used to generate LLVM instructions. From this point onwards, we can make use of the LLVM compiler framework to get many optimizations and other tools all for free. ## Query-based compiler Eclair is a so-called "query-based compiler". What this means is: 1. Each stage of the compiler builds on top of previous stages, 2. You can query the results of each of these "sub-computations". This kind of architecture ends up being very useful for a compiler since a compiler often is not a "pipeline" as it is usually presented, but instead has a graph structure where later stages can depend on earlier stages. On top of that, it makes it simple to access information at each stage of the compiler, making it easy to write tools that can query the compiler as a database. (Useful for developer tools such as LSP!) The [rock library](https://hackage.haskell.org/package/rock) builds on top of this idea and provides an API for describing your compiler in terms of build system rules. ## The parser The parser is written using parser combinators (from the `megaparsec` library). This approach was chosen because now this parser is fully written in Haskell, making it trivial to integrate with the rest of the compiler. On top of that, it gives you full control over how the parsing happens. In the Eclair compiler the parser adds a unique node id to each parsed AST node (see section about IR design). ## Semantic analysis Eclair makes use of Souffle Datalog during semantic analysis. Using Datalog for semantic analysis is great, because you can write all your logic really succinctly by writing down the "patterns" you are looking for in the AST and let Datalog deduce all results. The fact that each AST node has a unique ID (see section about IR design) makes it easy to refer to certain parts of a program and also to make it easy to serialize data back and forth between Haskell and Datalog. Eventually, Eclair will be a bootstrapped compiler, meaning all the parts where Souffle Datalog is currently used will be swapped out with an Eclair Datalog counterpart. This will make it much easier to distribute and run the compiler (since there is one big dependency less required). ## Typesystem The typesystem is a bidirectional typesystem. This means that the typesystem either checks a term against an expected type, or it tries to infer a type. Bidirectional typecheckers are great because they are "straight-forward" to implement (you pretty much need to write two functions that pattern match on the syntax and handle each node type correspondingly), and produce better error messages than typesystems that make heavy use of constraint solving. On top of this, the typesystem tries to report as many type errors as possible at once and with additional context how it came to these conclusions. This is done to make the developer experience better. ## Error rendering An effort is made to make Eclair errors as clear as possible for developers (using Rust and Elm for inspiration). Right now we use the [diagnose library](https://github.com/Mesabloo/diagnose) for reporting the errors since it allows us to focus on other parts of the compiler, but later this error rendering system will be implemented in the Eclair codebase itself to allow for more customization. ## Tranformations Eclair has a general [Transform](https://github.com/luc-tielen/eclair-lang/blob/main/lib/Eclair/Transform.hs) type that can be used for transforming the various IRs. Transformations can have two goals: they either simplify the IR, or they try to optimize it (or both). Eclair is a nano-pass compiler. Transformations should be small, focused and composable; so that you can reason about them. It's better to have a few extra passes in the compiler instead of a lot of extra complexity. The `Transform` type provides helper functions and typeclass instances to compose them into bigger transforms anyway. Transformations can have local state, but the way it is setup it is impossible for this state to "leak out" to the outside world. This is again done to make it easier to reason about, while not limiting what's possible inside a transform. Finally, transforms always run in a `TransformM` monad. This monad offers a way of generating new unique node IDs in case extra IR nodes are generated. ## Tests Currently the Eclair test suite is divided into two parts: 1. Unit tests written directly in Haskell 2. "Integration" tests that are executed by the `lit` executable provided by LLVM. Over time, most of the tests will be integration tests, since they are more rigorous and test larger parts of the compiler. On top of that, these style of tests allow you to write an example directly in Eclair and compare the result against actual output of the compiler. ================================================ FILE: docs/getting_started.md ================================================ # Getting started Eclair requires a Haskell toolchain, Souffle 2.3 and LLVM 14 to be installed on your system. If you notice that the installation instructions below are incomplete or outdated, please open a [Github issue](https://github.com/luc-tielen/eclair-lang/issues). ## Pre-requisites ### Ubuntu NOTE: These commands were tested with Ubuntu 20.04, they may not work with older versions. #### Installing the Haskell toolchain Run the following commands to install `ghcup`, `ghc` and `cabal`. `cabal-fmt`, `hspec-discover` and `hlint` are also installed but they are only needed when working on the compiler. ```bash $ curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | sh $ ghcup tui # In the terminal UI, select GHC 9.2.4, Haskell language server 1.8 and Cabal 3.6. # Important: both install + set them! $ cabal install cabal-fmt $ cabal install hspec-discover $ cabal install hlint ``` Verify you installed the correct versions by running the commands below, and comparing them against the versions mentioned in the previous command: ```bash $ ghc --version $ haskell-language-server-wrapper --version $ cabal --version ``` #### Installing Souffle Run the following commands to download and build Souffle from source: ```bash $ sudo apt install bison build-essential cmake doxygen flex g++ git \ libffi-dev libncurses5-dev libsqlite3-dev make mcpp python sqlite zlib1g-dev $ git clone git@github.com:souffle-lang/souffle.git $ cd souffle $ git checkout 2.3 $ cmake -S . -B build -DCMAKE_BUILD_TYPE=Release $ cmake --build build -j $ sudo cmake --build build --target install ``` If this went correctly, Souffle should now be globally installed on your system. Check this by executing the following command; it should print out the version of Souffle (2.3). ```bash $ souffle --version ``` #### Installing LLVM Next we need to install LLVM 14. Run the steps below to install it on your system. ```bash # If these packages are not available on your system, try installing using this # link: https://apt.llvm.org/. $ sudo apt install llvm-14 $ sudo apt install lld-14 $ sudo apt install clang-14 # Optional, if you want to use clang instead of llc # to compile the LLVM IR # The following is only needed for development / testing $ cd ~/.local/bin # Or any other directory that is on your $PATH $ ln -s /usr/lib/llvm-14/bin/split-file $ ln -s /usr/bin/FileCheck-14 FileCheck $ pip install lit==14.0.6 ``` ### Windows Assuming you have Windows subsytem for Linux, the commands above should also work for Windows? (If somebody could verify this, that would be great!) ### OSX NOTE: These commands were tested with Intel MacOS 13.0, they may or may not not work with older versions or on an ARM-based machine. #### Installing the Haskell toolchain Run the following commands (see https://www.haskell.org/ghcup/) to install `ghcup`, `ghc` and `cabal`. ```bash $ curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | sh $ ghcup tui # In the terminal UI, select GHC 9.2.4, Haskell language server 1.8 and Cabal 3.6. # Important: both install + set them! ``` The following commands are only needed when working on the compiler. Run the commands to install `cabal-fmt`, `hspec-discover`, and `hlint`. ```bash $ cabal install cabal-fmt $ cabal install hspec-discover $ cabal install hlint ``` Verify you installed the correct versions by running the commands below, and compare them against the versions mentioned in the previous command: ```bash $ ghc --version $ haskell-language-server-wrapper --version $ cabal --version ``` #### Installing Souffle Run the following commands to download and build Souffle from source (instructions taken from [here](https://souffle-lang.github.io/build#mac-os-x-build)): ```bash $ brew update $ brew install cmake bison libffi mcpp pkg-config $ brew reinstall gcc $ brew link bison --force $ brew link libffi --force $ brew install souffle-lang/souffle/souffle ``` If this went correctly, Souffle should now be globally installed on your system. Check this by executing the following command; it should print out the version of Souffle (2.3). ```bash $ souffle --version ``` #### Installing LLVM Next we need to install LLVM 14. Run the steps below to install it on your system. ```bash $ brew install llvm@14 # The following is only needed for development / testing $ cd ~/.local/bin # Or any other directory that is on your $PATH $ ln -s /usr/local/opt/llvm@14/bin/split-file $ ln -s /usr/local/opt/llvm@14/bin/FileCheck $ pip install lit==14.0.6 $ brew install node ``` ## Building Eclair Now that all the pre-requisites are built, you can build Eclair. ```bash $ cabal build $ cabal run eclair-test # Unit tests $ lit tests -v # Integration tests $ cabal list-bin eclair # <= returns path to the Eclair compiler executable ``` ================================================ FILE: eclair-lang.cabal ================================================ cabal-version: 2.2 name: eclair-lang version: 0.2.0 synopsis: Eclair: an experimental and minimal Datalog that compiles to LLVM. description: Eclair: an experimental and minimal Datalog that compiles to LLVM. category: Compiler homepage: https://github.com/luc-tielen/eclair-lang author: Luc Tielen maintainer: luc.tielen@gmail.com copyright: Luc Tielen, 2023 license: BSD-3-Clause license-file: LICENSE build-type: Simple extra-source-files: cbits/semantic_analysis.dl CHANGELOG.md LICENSE README.md flag debug description: Enables stack traces. manual: True default: False library -- cabal-fmt: expand lib exposed-modules: Eclair Eclair.ArgParser Eclair.AST.Analysis Eclair.AST.Codegen Eclair.AST.IR Eclair.AST.Lower Eclair.AST.Transforms Eclair.AST.Transforms.ConstantFolding Eclair.AST.Transforms.DeadCodeElimination Eclair.AST.Transforms.NormalizeRules Eclair.AST.Transforms.RemoveAliases Eclair.AST.Transforms.ReplaceStrings Eclair.Common.Config Eclair.Common.Extern Eclair.Common.Id Eclair.Common.Literal Eclair.Common.Location Eclair.Common.Operator Eclair.Common.Pretty Eclair.Comonads Eclair.EIR.IR Eclair.EIR.Lower Eclair.EIR.Lower.API Eclair.EIR.Lower.Codegen Eclair.EIR.Lower.Externals Eclair.Error Eclair.JSON Eclair.LLVM.Allocator.Arena Eclair.LLVM.Allocator.Common Eclair.LLVM.Allocator.Malloc Eclair.LLVM.Allocator.Page Eclair.LLVM.BTree Eclair.LLVM.BTree.Bounds Eclair.LLVM.BTree.Compare Eclair.LLVM.BTree.Create Eclair.LLVM.BTree.Destroy Eclair.LLVM.BTree.Find Eclair.LLVM.BTree.Insert Eclair.LLVM.BTree.Iterator Eclair.LLVM.BTree.Size Eclair.LLVM.BTree.Types Eclair.LLVM.Codegen Eclair.LLVM.Config Eclair.LLVM.Externals Eclair.LLVM.Hash Eclair.LLVM.HashMap Eclair.LLVM.Metadata Eclair.LLVM.Symbol Eclair.LLVM.SymbolTable Eclair.LLVM.Table Eclair.LLVM.Template Eclair.LLVM.Vector Eclair.LSP Eclair.LSP.Handlers Eclair.LSP.Handlers.Diagnostics Eclair.LSP.Handlers.DocumentHighlight Eclair.LSP.Handlers.Hover Eclair.LSP.JSON Eclair.LSP.Monad Eclair.LSP.Types Eclair.LSP.VFS Eclair.Parser Eclair.RA.Codegen Eclair.RA.IndexSelection Eclair.RA.IR Eclair.RA.Lower Eclair.RA.Transforms Eclair.RA.Transforms.HoistConstraints Eclair.Souffle.IR Eclair.Transform Eclair.TypeSystem Prelude hs-source-dirs: lib default-extensions: DataKinds DeriveAnyClass DeriveFoldable DeriveFunctor DeriveGeneric DeriveTraversable DerivingStrategies DerivingVia FlexibleContexts FlexibleInstances KindSignatures LambdaCase OverloadedStrings PatternSynonyms RankNTypes RecursiveDo ScopedTypeVariables TupleSections TypeFamilies ViewPatterns ghc-options: -Wall -Wincomplete-patterns -fhide-source-paths -fno-show-valid-hole-fits -fno-sort-valid-hole-fits cxx-options: -std=c++17 -D__EMBEDDED_SOUFFLE__ -Wall build-depends: , algebraic-graphs <1 , base >=4.7 && <5 , bytestring >=0.11 && <0.12 , comonad >=5 && <6 , containers <1 , dependent-sum >=0.6 && <1 , diagnose >=2.3 && <2.4 , directory >=1 && <2 , dlist >=1 && <2 , exceptions >=0.10 && <0.11 , extra >=1 && <2 , ghc-prim <1 , hermes-json <1 , llvm-codegen , megaparsec >=9 && <10 , mmorph >=1 && <2 , mtl >=2 && <3 , optparse-applicative >=0.16 && <0.17 , parser-combinators >=1.3 && <1.4 , prettyprinter >=1.7 && <1.8 , prettyprinter-ansi-terminal >=1 && <2 , recursion-schemes >=5 && <6 , relude >=1.2 && <1.3 , rock >=0.3 && <0.4 , souffle-haskell ==4.0.0 , text >=2 && <3 , text-builder-linear <1 , transformers <1 , vector >=0.12 && <0.13 mixins: base hiding (Prelude) default-language: Haskell2010 if os(osx) extra-libraries: c++ if flag(debug) ghc-options: -fplugin=StackTrace.Plugin build-depends: haskell-stack-trace-plugin ==0.1.3.0 if os(linux) extra-libraries: stdc++ executable eclair main-is: Main.hs other-modules: Paths_eclair_lang autogen-modules: Paths_eclair_lang hs-source-dirs: src/eclair default-extensions: DataKinds DeriveAnyClass DeriveFoldable DeriveFunctor DeriveGeneric DeriveTraversable DerivingStrategies DerivingVia FlexibleContexts FlexibleInstances KindSignatures LambdaCase OverloadedStrings PatternSynonyms RankNTypes RecursiveDo ScopedTypeVariables TupleSections TypeFamilies ViewPatterns ghc-options: -Wall -Wincomplete-patterns -fhide-source-paths -fno-show-valid-hole-fits -fno-sort-valid-hole-fits -threaded -rtsopts -with-rtsopts=-N cxx-options: -std=c++17 -D__EMBEDDED_SOUFFLE__ build-depends: , algebraic-graphs <1 , base >=4.7 && <5 , bytestring >=0.11 && <0.12 , comonad >=5 && <6 , containers <1 , dependent-sum >=0.6 && <1 , diagnose >=2.3 && <2.4 , directory >=1 && <2 , dlist >=1 && <2 , eclair-lang , exceptions >=0.10 && <0.11 , extra >=1 && <2 , llvm-codegen , megaparsec >=9 && <10 , mmorph >=1 && <2 , mtl >=2 && <3 , optparse-applicative >=0.16 && <0.17 , parser-combinators >=1.3 && <1.4 , prettyprinter >=1.7 && <1.8 , prettyprinter-ansi-terminal >=1 && <2 , process >=1.6 && <1.7 , recursion-schemes >=5 && <6 , relude >=1.2 && <1.3 , rock >=0.3 && <0.4 , souffle-haskell ==4.0.0 , text >=2 && <3 , transformers <1 , vector >=0.12 && <0.13 mixins: base hiding (Prelude) default-language: Haskell2010 if os(osx) extra-libraries: c++ if flag(debug) ghc-options: -fplugin=StackTrace.Plugin build-depends: haskell-stack-trace-plugin ==0.1.3.0 test-suite eclair-test type: exitcode-stdio-1.0 main-is: test.hs -- cabal-fmt: expand tests/eclair other-modules: Paths_eclair_lang Test.Eclair.ArgParserSpec Test.Eclair.JSONSpec Test.Eclair.LLVM.Allocator.MallocSpec Test.Eclair.LLVM.Allocator.PageSpec Test.Eclair.LLVM.Allocator.Utils Test.Eclair.LLVM.BTreeSpec Test.Eclair.LLVM.HashMapSpec Test.Eclair.LLVM.HashSpec Test.Eclair.LLVM.SymbolSpec Test.Eclair.LLVM.SymbolTableSpec Test.Eclair.LLVM.SymbolUtils Test.Eclair.LLVM.VectorSpec Test.Eclair.LSP.HandlersSpec Test.Eclair.LSP.JSONSpec Test.Eclair.RA.IndexSelectionSpec autogen-modules: Paths_eclair_lang hs-source-dirs: tests/eclair default-extensions: DataKinds DeriveAnyClass DeriveFoldable DeriveFunctor DeriveGeneric DeriveTraversable DerivingStrategies DerivingVia FlexibleContexts FlexibleInstances KindSignatures LambdaCase OverloadedStrings PatternSynonyms RankNTypes RecursiveDo ScopedTypeVariables TupleSections TypeFamilies ViewPatterns ghc-options: -Wall -Wincomplete-patterns -fhide-source-paths -fno-show-valid-hole-fits -fno-sort-valid-hole-fits cxx-options: -std=c++17 -D__EMBEDDED_SOUFFLE__ build-depends: , algebraic-graphs <1 , array >=0.5 && <1 , base >=4.7 && <5 , bytestring >=0.11 && <0.12 , comonad >=5 && <6 , containers <1 , dependent-sum >=0.6 && <1 , diagnose >=2.3 && <2.4 , dlist >=1 && <2 , eclair-lang , exceptions >=0.10 && <0.11 , extra >=1 && <2 , filepath >=1 && <2 , hedgehog >=1 && <2 , hermes-json <1 , hspec >=2.6.1 && <3.0.0 , hspec-hedgehog <1 , libffi >=0.2 && <1 , llvm-codegen , megaparsec >=9 && <10 , mmorph >=1 && <2 , mtl >=2 && <3 , neat-interpolation <1 , optparse-applicative >=0.16 && <0.17 , parser-combinators >=1.3 && <1.4 , prettyprinter >=1.7 && <1.8 , prettyprinter-ansi-terminal >=1 && <2 , random >=1.2 && <2 , recursion-schemes >=5 && <6 , relude >=1.2 && <1.3 , rock >=0.3 && <0.4 , silently >=1.2 && <1.3 , souffle-haskell ==4.0.0 , text >=2 && <3 , transformers <1 , unix >=2.8 && <3 , vector >=0.12 && <0.13 mixins: base hiding (Prelude) default-language: Haskell2010 if os(osx) extra-libraries: c++ if flag(debug) ghc-options: -fplugin=StackTrace.Plugin build-depends: haskell-stack-trace-plugin ==0.1.3.0 ================================================ FILE: hie.yaml ================================================ cradle: cabal: - path: "lib" component: "lib:eclair-lang" - path: "src/eclair/Main.hs" component: "eclair-lang:exe:eclair" - path: "src/eclair/Paths_eclair_lang.hs" component: "eclair-lang:exe:eclair" - path: "src/lsp" component: "eclair-lang:exe:eclair-lsp-server" - path: "tests/eclair" component: "eclair-lang:test:eclair-test" - path: "tests/lsp/Main.hs" component: "eclair-lang:test:eclair-lsp-test" ================================================ FILE: lib/Eclair/AST/Analysis.hs ================================================ {-# LANGUAGE UndecidableInstances #-} module Eclair.AST.Analysis ( Result(..) , SemanticInfo(..) , SemanticErrors(..) , hasSemanticErrors , runAnalysis , UngroundedVar(..) , WildcardInFact(..) , WildcardInRuleHead(..) , WildcardInConstraint(..) , WildcardInBinOp(..) , WildcardInExtern(..) , UnconstrainedRuleVar(..) , DeadCode(..) , DeadInternalRelation(..) , NoOutputRelation(..) , ConflictingDefinitionGroup(..) , ExternUsedAsFact(..) , ExternUsedAsRule(..) , CyclicNegation(..) , NodeId(..) , Container , computeUsageMapping ) where import qualified Data.List.NonEmpty as NE import Data.List.Extra (nubOrdOn) import qualified Language.Souffle.Interpreted as S import qualified Language.Souffle.Analysis as S import qualified Eclair.AST.IR as IR import qualified Data.Map as Map import Eclair.Common.Id import Eclair.Common.Location (NodeId(..)) type Position = Word32 -- The facts submitted to Datalog closely follow the AST structure, -- but are denormalized so that Datalog can easily process it. data LitNumber = LitNumber NodeId Word32 deriving stock Generic deriving anyclass S.Marshal deriving S.Fact via S.FactOptions LitNumber "lit_number" 'S.Input data LitString = LitString NodeId Text deriving stock Generic deriving anyclass S.Marshal deriving S.Fact via S.FactOptions LitString "lit_string" 'S.Input data Var = Var NodeId Id deriving stock Generic deriving anyclass S.Marshal deriving S.Fact via S.FactOptions Var "variable" 'S.Input newtype Hole = Hole NodeId deriving stock Generic deriving anyclass S.Marshal deriving S.Fact via S.FactOptions Hole "hole" 'S.Input data Constraint = Constraint { constraintId :: NodeId , constraintOperator :: Text , constraintLhsId :: NodeId , constraintRhsId :: NodeId } deriving stock Generic deriving anyclass S.Marshal deriving S.Fact via S.FactOptions Constraint "constraint" 'S.Input data BinOp = BinOp { binOpId :: NodeId , op :: Text , binOpLhsId :: NodeId , binOpRhsId :: NodeId } deriving stock Generic deriving anyclass S.Marshal deriving S.Fact via S.FactOptions BinOp "binop" 'S.Input data Atom = Atom NodeId Id deriving stock Generic deriving anyclass S.Marshal deriving S.Fact via S.FactOptions Atom "atom" 'S.Input data AtomArg = AtomArg { atomId :: NodeId, atomArgPos :: Word32, atomArgId :: NodeId } deriving stock Generic deriving anyclass S.Marshal deriving S.Fact via S.FactOptions AtomArg "atom_arg" 'S.Input data Rule = Rule NodeId Id deriving stock Generic deriving anyclass S.Marshal deriving S.Fact via S.FactOptions Rule "rule" 'S.Input data RuleArg = RuleArg { raRuleId :: NodeId, raArgPos :: Word32, raArgId :: NodeId } deriving stock Generic deriving anyclass S.Marshal deriving S.Fact via S.FactOptions RuleArg "rule_arg" 'S.Input data RuleClause = RuleClause { rcRuleId :: NodeId, rcClausePos :: Word32, rcClauseId :: NodeId } deriving stock Generic deriving anyclass S.Marshal deriving S.Fact via S.FactOptions RuleClause "rule_clause" 'S.Input data Negation = Negation { negationNodeId :: NodeId , negationInnerNodeId :: NodeId } deriving stock Generic deriving anyclass S.Marshal deriving S.Fact via S.FactOptions Negation "negation" 'S.Input -- NOTE: not storing types right now, but might be useful later? data DeclareType = DeclareType NodeId Id deriving stock Generic deriving anyclass S.Marshal deriving S.Fact via S.FactOptions DeclareType "declare_type" 'S.Input data ExternDefinition = ExternDefinition NodeId Id deriving stock Generic deriving anyclass S.Marshal deriving S.Fact via S.FactOptions ExternDefinition "extern_definition" 'S.Input newtype InputRelation = InputRelation Id deriving stock Generic deriving anyclass S.Marshal deriving S.Fact via S.FactOptions InputRelation "input_relation" 'S.Input newtype OutputRelation = OutputRelation Id deriving stock Generic deriving anyclass S.Marshal deriving S.Fact via S.FactOptions OutputRelation "output_relation" 'S.Input newtype InternalRelation = InternalRelation Id deriving stock Generic deriving anyclass S.Marshal deriving S.Fact via S.FactOptions InternalRelation "internal_relation" 'S.Input newtype Module = Module NodeId deriving stock Generic deriving anyclass S.Marshal deriving S.Fact via S.FactOptions Module "module" 'S.Input data ModuleDecl = ModuleDecl { moduleId :: NodeId, declId :: NodeId } deriving stock Generic deriving anyclass S.Marshal deriving S.Fact via S.FactOptions ModuleDecl "module_declaration" 'S.Input data ScopedValue = ScopedValue { svScopeId :: NodeId, svNodeId :: NodeId } deriving stock Generic deriving anyclass S.Marshal deriving S.Fact via S.FactOptions ScopedValue "scoped_value" 'S.Input data UngroundedVar loc = UngroundedVar { ungroundedRuleLoc :: loc , ungroundedVarLoc :: loc , ungroundedVarName :: Id } deriving stock (Generic, Eq, Functor) deriving anyclass S.Marshal deriving S.Fact via S.FactOptions (UngroundedVar loc) "ungrounded_variable" 'S.Output data WildcardInFact loc = WildcardInFact { factLoc :: loc , factArgLoc :: loc , wildcardFactPos :: Position } deriving stock (Generic, Eq, Functor) deriving anyclass S.Marshal deriving S.Fact via S.FactOptions (WildcardInFact loc) "wildcard_in_fact" 'S.Output data WildcardInRuleHead loc = WildcardInRuleHead { wildcardRuleLoc :: loc , wildcardRuleArgLoc :: loc , wildcardRuleHeadPos :: Position } deriving stock (Generic, Eq, Functor) deriving anyclass S.Marshal deriving S.Fact via S.FactOptions (WildcardInRuleHead loc) "wildcard_in_rule_head" 'S.Output data WildcardInConstraint loc = WildcardInConstraint { wildcardConstraintLoc :: loc , wildcardConstraintPos :: loc } deriving stock (Generic, Eq, Functor) deriving anyclass S.Marshal deriving S.Fact via S.FactOptions (WildcardInConstraint loc) "wildcard_in_constraint" 'S.Output data WildcardInBinOp loc = WildcardInBinOp { wildcardBinOpLoc :: loc , wildcardBinOpPos :: loc } deriving stock (Generic, Eq, Functor) deriving anyclass S.Marshal deriving S.Fact via S.FactOptions (WildcardInBinOp loc) "wildcard_in_binop" 'S.Output data WildcardInExtern loc = WildcardInExtern { wildcardExternAtomLoc :: loc , wildcardExternAtomArgLoc :: loc , wildcardExternArgPos :: Position } deriving stock (Generic, Eq, Functor) deriving anyclass S.Marshal deriving S.Fact via S.FactOptions (WildcardInExtern loc) "wildcard_in_extern" 'S.Output data UnconstrainedRuleVar loc = UnconstrainedRuleVar { urvRuleLoc :: loc , urvVarLoc :: loc , urvVarName :: Id } deriving stock (Generic, Eq, Functor) deriving anyclass S.Marshal deriving S.Fact via S.FactOptions (UnconstrainedRuleVar loc) "unconstrained_rule_var" 'S.Output newtype DeadCode = DeadCode { unDeadCode :: NodeId } deriving stock (Generic, Eq) deriving anyclass S.Marshal deriving S.Fact via S.FactOptions DeadCode "dead_code" 'S.Output newtype NoOutputRelation loc = NoOutputRelation loc deriving stock (Generic, Eq, Functor) deriving anyclass S.Marshal deriving S.Fact via S.FactOptions (NoOutputRelation loc) "no_output_relation" 'S.Output data DeadInternalRelation loc = DeadInternalRelation loc Id deriving stock (Generic, Eq, Functor) deriving anyclass S.Marshal deriving S.Fact via S.FactOptions (DeadInternalRelation loc) "dead_internal_relation" 'S.Output data ConflictingDefinitions loc = ConflictingDefinitions { cdFirstLoc :: loc , cdSecondLoc :: loc , cdName :: Id } deriving stock (Generic, Eq, Functor) deriving anyclass S.Marshal deriving S.Fact via S.FactOptions (ConflictingDefinitions loc) "conflicting_definitions" 'S.Output data ConflictingDefinitionGroup loc = ConflictingDefinitionGroup { cdgName :: Id , cdgLocs :: NonEmpty loc } deriving stock (Eq, Functor) data ExternUsedAsFact loc = ExternUsedAsFact { externAsFactLoc :: loc , externAsFactExternLoc :: loc , externAsFactName :: Id } deriving stock (Generic, Eq, Functor) deriving anyclass S.Marshal deriving S.Fact via S.FactOptions (ExternUsedAsFact loc) "extern_used_as_fact" 'S.Output data ExternUsedAsRule loc = ExternUsedAsRule { externAsRuleLoc :: loc , externAsRuleExternLoc :: loc , externAsRuleName :: Id } deriving stock (Generic, Eq, Functor) deriving anyclass S.Marshal deriving S.Fact via S.FactOptions (ExternUsedAsRule loc) "extern_used_as_rule" 'S.Output newtype CyclicNegation loc = CyclicNegation loc deriving stock (Generic, Eq, Functor) deriving anyclass S.Marshal deriving S.Fact via S.FactOptions (CyclicNegation loc) "cyclic_negation" 'S.Output data SemanticAnalysis = SemanticAnalysis deriving S.Program via S.ProgramOptions SemanticAnalysis "semantic_analysis" '[ LitNumber , LitString , Var , Hole , Constraint , BinOp , Atom , AtomArg , Rule , RuleArg , RuleClause , Negation , DeclareType , ExternDefinition , InputRelation , OutputRelation , InternalRelation , Module , ModuleDecl , ScopedValue , UngroundedVar NodeId , WildcardInRuleHead NodeId , WildcardInFact NodeId , WildcardInConstraint NodeId , WildcardInBinOp NodeId , WildcardInExtern NodeId , UnconstrainedRuleVar NodeId , DeadCode , NoOutputRelation NodeId , DeadInternalRelation NodeId , ConflictingDefinitions NodeId , ExternUsedAsFact NodeId , ExternUsedAsRule NodeId , CyclicNegation NodeId ] -- TODO: change to Vector when finished for performance type Container = [] newtype SemanticInfo = SemanticInfo { deadCodeIds :: Container DeadCode } deriving Eq data Result = Result { semanticInfo :: SemanticInfo , semanticErrors :: SemanticErrors NodeId } deriving Eq data SemanticErrors loc = SemanticErrors { ungroundedVars :: Container (UngroundedVar loc) , wildcardsInFacts :: Container (WildcardInFact loc) , wildcardsInRuleHeads :: Container (WildcardInRuleHead loc) , wildcardsInConstraints :: Container (WildcardInConstraint loc) , wildcardsInBinOps :: Container (WildcardInBinOp loc) , wildcardsInExternAtoms :: Container (WildcardInExtern loc) , unconstrainedVars :: Container (UnconstrainedRuleVar loc) , deadInternalRelations :: Container (DeadInternalRelation loc) , noOutputRelations :: Container (NoOutputRelation loc) , conflictingDefinitions :: Container (ConflictingDefinitionGroup loc) , externsUsedAsFact :: Container (ExternUsedAsFact loc) , externsUsedAsRule :: Container (ExternUsedAsRule loc) , cyclicNegations :: Container (CyclicNegation loc) } deriving (Eq, Functor) hasSemanticErrors :: Result -> Bool hasSemanticErrors result = isNotNull ungroundedVars || isNotNull wildcardsInFacts || isNotNull wildcardsInRuleHeads || isNotNull wildcardsInConstraints || isNotNull wildcardsInBinOps || isNotNull wildcardsInExternAtoms || isNotNull unconstrainedVars || isNotNull deadInternalRelations || isNotNull noOutputRelations || isNotNull conflictingDefinitions || isNotNull externsUsedAsFact || isNotNull cyclicNegations where errs = semanticErrors result isNotNull :: (SemanticErrors NodeId -> [a]) -> Bool isNotNull f = not . null $ f errs analysis :: Word -> S.Handle SemanticAnalysis -> S.Analysis S.SouffleM IR.AST Result analysis numCores prog = S.mkAnalysis addFacts run getFacts where addFacts :: IR.AST -> S.SouffleM () addFacts ast = usingReaderT Nothing $ flip (zygo IR.getNodeIdF) ast $ \case IR.LitF nodeId lit -> do mScopeId <- ask for_ mScopeId $ \scopeId -> S.addFact prog $ ScopedValue scopeId nodeId case lit of IR.LNumber x -> S.addFact prog $ LitNumber nodeId x IR.LString x -> S.addFact prog $ LitString nodeId x IR.PWildcardF nodeId -> S.addFact prog $ Var nodeId (Id "_") IR.VarF nodeId var -> do S.addFact prog $ Var nodeId var mScopeId <- ask for_ mScopeId $ \scopeId -> S.addFact prog $ ScopedValue scopeId nodeId IR.HoleF nodeId -> S.addFact prog $ Hole nodeId IR.BinOpF nodeId arithOp (lhsId', lhsAction) (rhsId', rhsAction) -> do let textualOp = case arithOp of IR.Plus -> "+" IR.Minus -> "-" IR.Multiply -> "*" IR.Divide -> "/" mScopeId <- ask for_ mScopeId $ \scopeId -> S.addFact prog $ ScopedValue scopeId nodeId S.addFact prog $ BinOp nodeId textualOp lhsId' rhsId' lhsAction rhsAction IR.ConstraintF nodeId constraintOp (lhsId', lhsAction) (rhsId', rhsAction) -> do let textualOp = case constraintOp of IR.Equals -> "=" IR.NotEquals -> "!=" IR.LessThan -> "<" IR.LessOrEqual -> "<=" IR.GreaterThan -> "<" IR.GreaterOrEqual -> "<=" S.addFact prog $ Constraint nodeId textualOp lhsId' rhsId' lhsAction rhsAction IR.NotF nodeId (innerNodeId, action) -> do S.addFact prog $ Negation nodeId innerNodeId local (const $ Just nodeId) action IR.AtomF nodeId atom (unzip -> (argNodeIds, actions)) -> do S.addFact prog $ Atom nodeId atom mScopeId <- ask S.addFacts prog $ mapWithPos (AtomArg nodeId) argNodeIds for_ mScopeId $ \scopeId -> S.addFact prog $ ScopedValue scopeId nodeId let maybeAddScope = if isJust mScopeId then id else local (const $ Just nodeId) maybeAddScope $ sequence_ actions IR.RuleF nodeId rule ruleArgs ruleClauses -> do let (argNodeIds, argActions) = unzip ruleArgs (clauseNodeIds, clauseActions) = unzip ruleClauses S.addFact prog $ Rule nodeId rule S.addFacts prog $ mapWithPos (RuleArg nodeId) argNodeIds S.addFacts prog $ mapWithPos (RuleClause nodeId) clauseNodeIds local (const $ Just nodeId) $ do sequence_ argActions sequence_ clauseActions IR.ExternDefinitionF nodeId name _ _ -> do S.addFact prog $ ExternDefinition nodeId name IR.DeclareTypeF nodeId name _ usageMode -> do S.addFact prog $ DeclareType nodeId name case usageMode of IR.Input -> S.addFact prog $ InputRelation name IR.Output -> S.addFact prog $ OutputRelation name IR.InputOutput -> do S.addFact prog $ InputRelation name S.addFact prog $ OutputRelation name IR.Internal -> S.addFact prog $ InternalRelation name IR.ModuleF nodeId (unzip -> (declNodeIds, actions)) -> do S.addFact prog $ Module nodeId S.addFacts prog $ map (ModuleDecl nodeId) declNodeIds sequence_ actions run :: S.SouffleM () run = do S.setNumThreads prog (fromIntegral numCores) S.run prog getFacts :: S.SouffleM Result getFacts = do info <- SemanticInfo <$> S.getFacts prog errs <- SemanticErrors <$> S.getFacts prog <*> S.getFacts prog <*> S.getFacts prog <*> S.getFacts prog <*> S.getFacts prog <*> S.getFacts prog <*> S.getFacts prog <*> S.getFacts prog <*> S.getFacts prog <*> (groupConflicts <$> S.getFacts prog) <*> S.getFacts prog <*> S.getFacts prog <*> S.getFacts prog pure $ Result info errs mapWithPos :: (Word32 -> a -> b) -> [a] -> [b] mapWithPos g = zipWith g [0..] groupConflicts :: Container (ConflictingDefinitions NodeId) -> Container (ConflictingDefinitionGroup NodeId) groupConflicts conflicts = conflicts & sortWith sameConflict & groupBy ((==) `on` sameConflict) & map (\cg -> let firstConflict = head cg declName = cdName firstConflict locs = NE.cons (cdFirstLoc firstConflict) (map cdSecondLoc cg) in ConflictingDefinitionGroup declName locs ) & nubOrdOn cdgName where sameConflict = cdName &&& cdFirstLoc runAnalysis :: Word -> IR.AST -> IO Result runAnalysis numCores ast = S.runSouffle SemanticAnalysis $ \case Nothing -> panic "Failed to load Souffle during semantic analysis!" Just prog -> S.execAnalysis (analysis numCores prog) ast computeUsageMapping :: IR.AST -> Map Id IR.UsageMode computeUsageMapping ast = Map.fromList pairs where pairs = flip cata ast $ \case IR.DeclareTypeF _ name _ mode -> one (name, mode) astf -> fold astf ================================================ FILE: lib/Eclair/AST/Codegen.hs ================================================ {-# LANGUAGE DerivingVia #-} module Eclair.AST.Codegen ( CodegenM , Env(..) , runCodegen , toTerm , project , search , loop , parallel , merge , swap , purge , exit , noElemOf , if' ) where import Prelude hiding (swap, project) import Data.DList (DList) import qualified Data.DList as DList import qualified Eclair.RA.IR as RA import qualified Eclair.AST.IR as AST import Eclair.Common.Location import Eclair.Common.Literal import Eclair.Common.Operator import Eclair.Common.Id import Eclair.Common.Extern type AST = AST.AST type RA = RA.RA type Relation = RA.Relation type Alias = RA.Alias type Variable = Id type Column = Int newtype Row = Row { unRow :: Int } deriving (Eq, Ord) data InLoop = InLoop deriving Eq data Env = Env { envRow :: Row , envExterns :: [Extern] , envLoopContext :: Maybe InLoop } data LowerState = LowerState { nextNodeId :: Word32 -- NOTE: Unrelated to NodeIDs used in AST! -- Constraints that can be resolved directly, but need to be emitted later. , directConstraints :: CodegenM RA -> CodegenM RA -- We keep track of which alias + column maps to which variables for later -- generation of constraints. , varMapping :: DList (Alias, Column, Variable) } newtype CodegenM a = CodegenM (RWS Env () LowerState a) deriving (Functor, Applicative, Monad, MonadReader Env, MonadState LowerState) via RWS Env () LowerState runCodegen :: [Extern] -> CodegenM a -> a runCodegen externs (CodegenM m) = -- NOTE: NodeId starts at 1, since module is manually created, and has NodeId 0 let beginState = LowerState 1 id mempty in fst $ evalRWS m (Env (Row 0) externs Nothing) beginState freshNodeId :: CodegenM NodeId freshNodeId = do next <- gets nextNodeId modify $ \s -> s { nextNodeId = next + 1 } pure $ NodeId next project :: Relation -> [CodegenM RA] -> CodegenM RA project r terms = do nodeId <- freshNodeId (addDirectConstraints, mapping) <- gets (directConstraints &&& varMapping) let grouped = mapping & toList & sortOn varNameOf & groupBy ((==) `on` varNameOf) eqs = map toIndirectConstraint grouped addIndirectConstraints = foldl' (.) id eqs noElemConstraint <- lookupNoElemConstraint addDirectConstraints . addIndirectConstraints . noElemConstraint $ RA.Project nodeId r <$> sequence terms where varNameOf (_, _, v) = v resolveAliasValue (a, col, _) = do nodeId <- freshNodeId pure $ RA.ColumnIndex nodeId a col toIndirectConstraint bindingGroup m = case bindingGroup of initial :| rest -> do aliasValue <- resolveAliasValue initial aliasValues <- traverse resolveAliasValue rest let constraints = map (if' Equals aliasValue) aliasValues wrapConstraints = foldl' (.) id constraints wrapConstraints m lookupNoElemConstraint = do loopCtx <- asks envLoopContext if loopCtx == Just InLoop then pure $ noElemOf (stripIdPrefixes r) terms else pure id search :: Relation -> [AST] -> CodegenM RA -> CodegenM RA search r terms inner = do nodeId <- freshNodeId -- Potentially reset var mapping when we reach the first search, -- this makes it possible to easily support multiple project statements. maybeResetSearchState a <- relationToAlias r zipWithM_ (\col t -> emitSearchTerm t a col) [0..] terms action <- local nextRow inner pure $ RA.Search nodeId r a [] action where nextRow s = s { envRow = Row . (+1) . unRow $ envRow s } maybeResetSearchState = do Row row <- asks envRow when (row == 0) $ do modify $ \s -> s { varMapping = mempty } emitSearchTerm :: AST -> Alias -> Column -> CodegenM () emitSearchTerm ast a col = do -- Based on what the term resolved to, we might need to create additional -- constraints. Literals can directly be converted to a constraint, variables -- are solved at the end (in the project statement). case ast of AST.Lit {} -> addDirectConstraint ast a col AST.BinOp {} -> addDirectConstraint ast a col AST.PWildcard _ -> pass AST.Var _ v -> -- We append new constraints at the end. -- This will cause indices to always trigger as soon as possible, -- which narrows down the search space and speeds up the query. modify $ \s -> s { varMapping = DList.snoc (varMapping s) (a, col, v) } _ -> pass addDirectConstraint ast a col = do ra <- toTerm ast nodeId <- freshNodeId let aliasValue = RA.ColumnIndex nodeId a col constraint = if' Equals aliasValue ra modify $ \s -> s { directConstraints = directConstraints s . constraint } loop :: [CodegenM RA] -> CodegenM RA loop ms = local (\env -> env { envLoopContext = Just InLoop}) $ do nodeId <- freshNodeId RA.Loop nodeId <$> sequence ms parallel :: [CodegenM RA] -> CodegenM RA parallel = \case [m] -> m ms -> do nodeId <- freshNodeId RA.Par nodeId <$> sequence ms merge :: Relation -> Relation -> CodegenM RA merge from' to' = do nodeId <- freshNodeId pure $ RA.Merge nodeId from' to' swap :: Relation -> Relation -> CodegenM RA swap r1 r2 = do nodeId <- freshNodeId pure $ RA.Swap nodeId r1 r2 purge :: Relation -> CodegenM RA purge r = do nodeId <- freshNodeId pure $ RA.Purge nodeId r exit :: [Relation] -> CodegenM RA exit rs = do nodeId <- freshNodeId pure $ RA.Exit nodeId rs noElemOf :: Relation -> [CodegenM RA] -> CodegenM RA -> CodegenM RA noElemOf r ts inner = do notElemNodeId <- freshNodeId ifNodeId <- freshNodeId cond <- RA.NotElem notElemNodeId r <$> sequence ts RA.If ifNodeId cond <$> inner if' :: LogicalOp -> RA -> RA -> CodegenM RA -> CodegenM RA if' op lhs rhs body = do cmpNodeId <- freshNodeId ifNodeId <- freshNodeId let cond = RA.CompareOp cmpNodeId op lhs rhs RA.If ifNodeId cond <$> body toTerm :: AST -> CodegenM RA toTerm ast = do nodeId <- freshNodeId case ast of AST.Lit _ (LNumber lit) -> pure $ RA.Lit nodeId lit AST.PWildcard _ -> pure $ RA.Undef nodeId AST.Var _ v -> do gets (find (\(_, _, v') -> v == v') . varMapping) >>= \case Just (alias, col, _) -> do pure $ RA.ColumnIndex nodeId alias col Nothing -> panic $ "Found ungrounded variable '" <> unId v <> "' in 'toTerm'!" AST.BinOp _ op lhs rhs -> do lhsTerm <- toTerm lhs rhsTerm <- toTerm rhs pure $ RA.PrimOp nodeId (RA.BuiltinOp op) [lhsTerm, rhsTerm] AST.Atom _ name args -> do RA.PrimOp nodeId (RA.ExternOp name) <$> traverse toTerm args _ -> panic "Unexpected case in 'toTerm'!" relationToAlias :: Relation -> CodegenM Alias relationToAlias r = asks (appendToId r . show . unRow . envRow) ================================================ FILE: lib/Eclair/AST/IR.hs ================================================ {-# LANGUAGE TemplateHaskell, OverloadedStrings #-} module Eclair.AST.IR ( AST(.., PWildcard) , ASTF(.., PWildcardF) , Value , Clause , Decl , Literal(..) , Type(..) , ArithmeticOp(..) , LogicalOp(..) , isEqualityOp , getNodeId , getNodeIdF , getExternDefs , UsageMode(..) , Attributes ) where import Prettyprinter import Eclair.Common.Id import Eclair.Common.Operator import Eclair.Common.Extern import Eclair.Common.Literal import Eclair.Common.Pretty import Eclair.Common.Location type Value = AST type Clause = AST type Decl = AST data Type = U32 | Str | TUnknown Int -- NOTE: unification variable, only used internally! deriving (Eq, Ord, Show) data UsageMode = Input | Output | InputOutput | Internal -- This variant is only used internally (pun intended). deriving (Eq, Show) -- Later this will also contain (Maybe StorageType), ... type Attributes = UsageMode -- NOTE: There is no explicit "AND" node, conjunctions are inlined into other -- nodes (as lists of clauses). data AST -- Expressions = Lit NodeId Literal | Var NodeId Id | Hole NodeId | BinOp NodeId ArithmeticOp AST AST -- Statements | Constraint NodeId LogicalOp AST AST | Rule NodeId Id [Value] [Clause] | Not NodeId Clause | Atom NodeId Id [Value] -- Can be both a Datalog relation, or a externally defined function / constraint | ExternDefinition NodeId Id [(Maybe Id, Type)] (Maybe Type) | DeclareType NodeId Id [(Maybe Id, Type)] Attributes | Module NodeId [Decl] deriving (Eq, Show) pattern PWildcard :: NodeId -> AST pattern PWildcard nodeId = Var nodeId (Id "_") makeBaseFunctor ''AST pattern PWildcardF :: NodeId -> ASTF r pattern PWildcardF nodeId = VarF nodeId (Id "_") getNodeId :: AST -> NodeId getNodeId = \case Module nodeId _ -> nodeId DeclareType nodeId _ _ _ -> nodeId ExternDefinition nodeId _ _ _ -> nodeId Rule nodeId _ _ _ -> nodeId Not nodeId _ -> nodeId Atom nodeId _ _ -> nodeId BinOp nodeId _ _ _ -> nodeId Constraint nodeId _ _ _ -> nodeId Lit nodeId _ -> nodeId Var nodeId _ -> nodeId Hole nodeId -> nodeId getNodeIdF :: ASTF a -> NodeId getNodeIdF = \case ModuleF nodeId _ -> nodeId DeclareTypeF nodeId _ _ _ -> nodeId ExternDefinitionF nodeId _ _ _ -> nodeId RuleF nodeId _ _ _ -> nodeId NotF nodeId _ -> nodeId AtomF nodeId _ _ -> nodeId BinOpF nodeId _ _ _ -> nodeId ConstraintF nodeId _ _ _ -> nodeId LitF nodeId _ -> nodeId VarF nodeId _ -> nodeId HoleF nodeId -> nodeId getExternDefs :: AST -> [Extern] getExternDefs = cata $ \case ExternDefinitionF _ name argTys mRetTy -> let extKind = if isJust mRetTy then ExternFunction else ExternConstraint in one $ Extern name (length argTys) extKind astf -> fold astf instance Pretty Type where pretty = \case U32 -> "u32" Str -> "string" TUnknown x -> "ty" <> show x data RenderPosition = TopLevel | Nested instance Pretty AST where pretty ast = runReader (pretty' ast) TopLevel where pretty' = \case Lit _ x -> pure $ pretty x Var _ v -> pure $ pretty v Hole _ -> pure "?" BinOp _ op lhs rhs -> do lhs' <- pretty' lhs rhs' <- pretty' rhs pure $ parens $ lhs' <+> pretty op <+> rhs' Constraint _ op lhs rhs -> do lhs' <- pretty' lhs rhs' <- pretty' rhs pure $ lhs' <+> pretty op <+> rhs' Not _ clause -> ("!" <>) <$> pretty' clause Atom _ name values -> do end <- ask <&> \case TopLevel -> "." Nested -> mempty values' <- traverse pretty' values pure $ pretty name <> parens (withCommas values') <> end Rule _ name values clauses -> do (values', clauses') <- local (const Nested) $ do (,) <$> traverse pretty' values <*> traverse pretty' clauses let separators = replicate (length clauses - 1) "," ++ ["."] pure $ pretty name <> parens (withCommas values') <+> ":-" <> hardline <> indent 2 (vsep (zipWith (<>) clauses' separators)) ExternDefinition _ name args mRetTy -> do let prettyRetTy = case mRetTy of Just retTy -> " " <> pretty retTy Nothing -> mempty pure $ "@extern" <+> pretty name <> parens (withCommas $ map prettyArg args) <> prettyRetTy <> "." DeclareType _ name tys attrs -> pure $ "@def" <+> pretty name <> parens (withCommas $ map prettyArg tys) <> prettyAttrs <> "." where prettyAttrs = case attrs of Internal -> "" Input -> " input" Output -> " output" InputOutput -> " input output" Module _ decls -> do decls' <- traverse pretty' decls pure $ vsep $ intersperse mempty decls' prettyArg (mName, ty) = maybe (pretty ty) (\fieldName -> pretty fieldName <> ":" <+> pretty ty) mName ================================================ FILE: lib/Eclair/AST/Lower.hs ================================================ module Eclair.AST.Lower ( compileToRA ) where import Prelude hiding (swap, project) import qualified Data.Graph as G import qualified Data.Map as M import Eclair.AST.Codegen import Eclair.AST.IR hiding (Clause) import Eclair.Common.Id import Eclair.Common.Location (NodeId(..)) import qualified Eclair.RA.IR as RA import Eclair.Common.Extern type RA = RA.RA type Relation = RA.Relation compileToRA :: [Extern] -> AST -> RA compileToRA externs ast = RA.Module (NodeId 0) $ concatMap processDecls sortedDecls where sortedDecls = scc ast processDecls :: [AST] -> [RA] processDecls = \case [Atom _ name values] -> runCodegen externs $ let literals = map toTerm values in one <$> project name literals [Rule _ name args clauses] -> let terms = map toTerm args in runCodegen externs $ processSingleRule [name] name terms clauses rules -> -- case for multiple mutually recursive rules let sccNames = rules & mapMaybe (\case Rule _ name _ _ -> Just name _ -> Nothing) in runCodegen externs $ processMultipleRules sccNames rules scc :: AST -> [[AST]] scc = \case Module _ decls -> map G.flattenSCC sortedDecls' where relevantDecls = filter isRelevant decls sortedDecls' = G.stronglyConnComp $ zipWith (\i d -> (d, i, refersTo d)) [0..] relevantDecls declLineMapping = M.fromListWith (<>) $ zipWith (\i d -> (nameFor d, [i])) [0..] relevantDecls isRelevant = \case Atom {} -> True Rule {} -> True Not {} -> True _ -> False nameFor = \case Atom _ name _ -> name Rule _ name _ _ -> name _ -> unreachable -- Because of "isRelevant" refersTo :: AST -> [Int] refersTo = \case Rule _ _ _ clauses -> -- If no top level facts are defined, no entry exists in declLine mapping -> default to -1 concatMap (fromMaybe [-1] . flip M.lookup declLineMapping . dependsOn) $ filter isRelevant clauses _ -> [] dependsOn = \case Atom _ name _ -> name Rule _ name _ _ -> name Not _ (Atom _ name _) -> name _ -> unreachable -- Because of "isRelevant" _ -> unreachable -- Because rejected by parser where unreachable = panic "Unreachable code in 'scc'" -- NOTE: These rules can all be evaluated in parallel inside the fixpoint loop processMultipleRules :: [Relation] -> [AST] -> CodegenM [RA] processMultipleRules sccNames rules = sequence stmts where stmts = mergeStmts <> [loop (purgeStmts <> ruleStmts <> [exitStmt] <> endLoopStmts)] mergeStmts = map (\r -> merge r (deltaRelationOf r)) uniqRelations purgeStmts = map (purge . newRelationOf) uniqRelations ruleStmts = [parallel $ map lowerRule rulesInfo] exitStmt = exit $ map newRelationOf uniqRelations endLoopStmts = concatMap toMergeAndSwapStmts uniqRelations toMergeAndSwapStmts r = let newRelation = newRelationOf r deltaRelation = deltaRelationOf r in [merge newRelation r, swap newRelation deltaRelation] rulesInfo = mapMaybe extractRuleData rules relations = map (\(r, _, _) -> r) rulesInfo uniqRelations = uniqOrderPreserving relations -- TODO: better func name lowerRule (r, map toTerm -> ts, clauses) = recursiveRuleToStmts sccNames r ts clauses processSingleRule :: [Relation] -> Relation -> [CodegenM RA] -> [AST] -> CodegenM [RA] processSingleRule sccNames relation terms clauses | isRecursive sccNames clauses = let deltaRelation = deltaRelationOf relation newRelation = newRelationOf relation stmts = [ merge relation deltaRelation , loop [ purge newRelation , ruleToStmt sccNames relation terms clauses , exit [newRelation] , merge newRelation relation , swap newRelation deltaRelation ] ] in sequence stmts | otherwise = one <$> ruleToStmt sccNames relation terms clauses ruleToStmt :: [Relation] -> Relation -> [CodegenM RA] -> [AST] -> CodegenM RA ruleToStmt sccNames relation terms clauses | isRecursive sccNames clauses = recursiveRuleToStmts sccNames relation terms clauses | otherwise = nestedSearchAndProject relation terms clauses mempty recursiveRuleToStmts :: [Relation] -> Relation -> [CodegenM RA] -> [AST] -> CodegenM RA recursiveRuleToStmts sccNames relation terms clauses = parallel $ [ stmt | i <- [0..sccClauseCount - 1] , let sccAtom = maybeAt i sccAtoms clauses' = map (maybeToDeltaClause sccAtom) clauses sccAtoms' = drop (i + 1) sccAtoms stmt = nestedSearchAndProject newRelation terms clauses' sccAtoms' ] where newRelation = newRelationOf relation sccAtoms = clauses & filter isPartOfScc & mapMaybe (\case Atom _ name args -> Just (name, args) _ -> Nothing) sccClauseCount = length sccAtoms isPartOfScc = \case Atom _ name _ -> name `elem` sccNames _ -> False maybeToDeltaClause sccAtom = \case Atom nodeId clauseName args | sccAtom == Just (clauseName, args) -> Atom nodeId (deltaRelationOf clauseName) args clause -> clause nestedSearchAndProject :: Relation -> [CodegenM RA] -> [AST] -> [(Relation, [AST])] -> CodegenM RA nestedSearchAndProject intoRelation terms clauses sccAtoms = flip (foldr processRuleClause) clauses $ addNegatedDeltaAtoms sccAtoms $ project intoRelation terms where processRuleClause :: AST -> CodegenM RA -> CodegenM RA processRuleClause clause inner = case clause of Not _ (Atom _ clauseName args) -> do -- No starts with check here, since cyclic negation is not allowed. let terms' = map toTerm args noElemOf clauseName terms' inner Constraint _ op lhs rhs -> do lhsTerm <- toTerm lhs rhsTerm <- toTerm rhs if' op lhsTerm rhsTerm inner Atom _ clauseName args -> do externs <- asks envExterns let isExtern = isJust $ find (\(Extern name _ _) -> clauseName == name) externs if isExtern then do clause' <- toTerm clause zero <- toTerm (Lit (NodeId 0) $ LNumber 0) if' NotEquals clause' zero inner else search clauseName args inner _ -> panic "Unexpected rule clause in 'nestedSearchAndProject'!" addNegatedDeltaAtoms = foldr (\(clauseName, args) wrapper -> wrapper . addNegatedDeltaAtom clauseName args) id addNegatedDeltaAtom :: Relation -> [AST] -> CodegenM RA -> CodegenM RA addNegatedDeltaAtom clauseName args = noElemOf (deltaRelationOf clauseName) (map toTerm args) isRecursive :: [Relation] -> [AST] -> Bool isRecursive sccNames clauses = let atomNames = flip mapMaybe clauses $ \case Atom _ name _ -> Just name _ -> Nothing in any (`elem` atomNames) sccNames extractRuleData :: AST -> Maybe (Relation, [AST], [AST]) extractRuleData = \case Rule _ name args clauses -> Just (name, args, clauses) _ -> Nothing newRelationOf, deltaRelationOf :: Relation -> Relation deltaRelationOf = prependToId deltaPrefix newRelationOf = prependToId newPrefix ================================================ FILE: lib/Eclair/AST/Transforms/ConstantFolding.hs ================================================ module Eclair.AST.Transforms.ConstantFolding ( transform ) where import Eclair.AST.IR import Eclair.Transform transform :: Transform AST AST transform = pureTransform $ cata $ \case BinOpF nodeId op (Lit _ (LNumber lhs)) (Lit _ (LNumber rhs)) -> let opFn = case op of Plus -> (+) Minus -> (-) Multiply -> (*) Divide -> div in Lit nodeId $ LNumber $ opFn lhs rhs ast -> embed ast ================================================ FILE: lib/Eclair/AST/Transforms/DeadCodeElimination.hs ================================================ module Eclair.AST.Transforms.DeadCodeElimination ( transform ) where import Eclair.Transform import Eclair.AST.Analysis import Eclair.AST.IR transform :: Container DeadCode -> Transform AST AST transform analysis = pureTransform $ cata $ \case ModuleF nodeId decls -> Module nodeId $ filter (not . isDead) decls RuleF nodeId name args clauses -> Rule nodeId name args $ filter (\c -> not (isDead c || isRedundantAssign c)) clauses astf -> embed astf where deadCodeNodeIds = map unDeadCode analysis isDead ast = getNodeId ast `elem` deadCodeNodeIds isRedundantAssign = \case Constraint _ Equals lhs rhs -> lhs == rhs _ -> False ================================================ FILE: lib/Eclair/AST/Transforms/NormalizeRules.hs ================================================ module Eclair.AST.Transforms.NormalizeRules ( transform ) where import Data.List (partition) import Eclair.Transform import Eclair.AST.IR -- This transform prepares the AST for lowering to RA by: -- 1. Shifting all constraints and negations to the end. transform :: Transform AST AST transform = pureTransform $ cata rewriteVars where rewriteVars = \case RuleF nodeId name values clauses -> do let (matching, rest) = partition isConstraintOrNegation clauses (constraints, negations) = partition isConstraint matching Rule nodeId name values $ rest <> constraints <> negations astf -> embed astf isConstraintOrNegation :: AST -> Bool isConstraintOrNegation = \case Constraint {} -> True Not {} -> True _ -> False isConstraint :: AST -> Bool isConstraint = \case Constraint {} -> True _ -> False ================================================ FILE: lib/Eclair/AST/Transforms/RemoveAliases.hs ================================================ module Eclair.AST.Transforms.RemoveAliases ( transform ) where import qualified Data.Map as M import Eclair.Transform import Eclair.AST.IR import Eclair.Comonads import Eclair.Common.Extern -- This transform reduces the amount of helper variables used in assignments. transform :: [Extern] -> Transform AST AST transform externs = Transform $ usingReaderT Nothing . gcata (distribute directlyGroundedVars equatedVars) rewrite where distribute :: Corecursive t => (Base t (t, a) -> a) -> (Base t (t, b) -> b) -> Base t (Quad t a b c) -> Quad t a b (Base t c) distribute f g m = let base_t_t = map qFirst m base_t_ta = map (qFirst &&& qSecond) m base_t_tb = map (qFirst &&& qThird) m base_t_c = map qFourth m in Quad (embed base_t_t) (f base_t_ta) (g base_t_tb) base_t_c externNames = map (\(Extern name _ _) -> name) externs -- Finds all vars directly inside a relation atom (not in a negation). directlyGroundedVars = \case AtomF _ name args | name `notElem` externNames -> flip mapMaybe args $ \case (Var _ v, _) -> Just v _ -> Nothing NotF {} -> mempty astf -> foldMap snd astf -- Find all vars used in equalities equatedVars = \case ConstraintF _ Equals lhs rhs -> case (fst lhs, fst rhs) of (lhs'@(Var _ v1), rhs'@(Var _ v2)) -> [(v1, rhs'), (v2, lhs')] (Var _ v, rhs') -> one (v, rhs') (lhs', Var _ v) -> one (v, lhs') _ -> mempty astf -> foldMap snd astf -- Aliases = equated vars - directly grounded vars findAliases dgVars = filter ((`notElem` dgVars) . fst) rewrite = \case RuleF nodeId name args clauses -> do let dgVars = concatMap qSecond clauses eqs = concatMap qThird clauses subst = M.fromList $ filter (not . occursCheck) $ findAliases dgVars eqs local (const $ Just subst) $ Rule nodeId name <$> traverse extract args <*> traverse extract clauses VarF nodeId v -> do let var = Var nodeId v -- Because of how the substitution is constructed, -- it's always safe to try and replace variables. maybe var (`resolveAliases` var) <$> ask astf -> embed <$> traverse extract astf resolveAliases subst = ana $ \case Var nodeId v -> maybe (VarF nodeId v) project $ M.lookup v subst ast -> project ast -- Occurs check is needed to prevent aliases from growing larger and larger. occursCheck (v, ast) = let vars = flip cata ast $ \case VarF _ var -> [var] astf -> fold astf in v `elem` vars ================================================ FILE: lib/Eclair/AST/Transforms/ReplaceStrings.hs ================================================ module Eclair.AST.Transforms.ReplaceStrings ( StringMap , transform ) where import qualified Data.Map as Map import Eclair.AST.IR import Eclair.Common.Id import Eclair.Transform -- NOTE: "String" here means an eclair string! type StringMap = Map Text Word32 transform :: Transform AST (AST, StringMap) transform = Transform $ usingStateT mempty . cata rewrite where rewrite :: RewriteRuleT (StateT StringMap) AST rewrite = \case DeclareTypeF nodeId name tys attrs -> do -- By putting the relation names in the symbol table, we can easily -- create a unique integer constant for the fact type mapping. _ <- replaceString (unId name) pure $ DeclareType nodeId name tys attrs LitF nodeId (LString s) -> Lit nodeId . LNumber <$> replaceString s astf -> embed <$> sequence astf replaceString :: Monad m => Text -> StateT StringMap m Word32 replaceString str = do value <- gets $ \strMap -> let count = fromIntegral $ length strMap in Map.findWithDefault count str strMap modify $ Map.insert str value pure value ================================================ FILE: lib/Eclair/AST/Transforms.hs ================================================ module Eclair.AST.Transforms ( simplify , ReplaceStrings.StringMap ) where import Eclair.AST.IR import Eclair.AST.Analysis import Eclair.Transform import qualified Eclair.AST.Transforms.ConstantFolding as ConstantFolding import qualified Eclair.AST.Transforms.RemoveAliases as RemoveAliases import qualified Eclair.AST.Transforms.DeadCodeElimination as DCE import qualified Eclair.AST.Transforms.ReplaceStrings as ReplaceStrings import qualified Eclair.AST.Transforms.NormalizeRules as NormalizeRules import Eclair.Common.Extern -- Transforms can be grouped into 3 parts: -- -- 1. transforms that need to run a single time, before optimizations -- 2. main optimization pipeline (runs until fixpoint is reached) -- 3. transforms that need to run a single time, after optimizations simplify :: NodeId -> [Extern] -> SemanticInfo -> AST -> (AST, ReplaceStrings.StringMap) simplify nodeId externs analysis = runTransform nodeId -- Transforms before optimizations: $ ConstantFolding.transform -- Optimizations that run until fixpoint is reached: >>> RemoveAliases.transform externs >>> ConstantFolding.transform >>> DCE.transform (deadCodeIds analysis) -- Transforms after optimizations: >>> NormalizeRules.transform >>> ReplaceStrings.transform ================================================ FILE: lib/Eclair/ArgParser.hs ================================================ module Eclair.ArgParser ( parseArgs , parser , Config(..) , CompileConfig(..) , EmitKind(..) , Target(..) ) where import Eclair.Common.Config import Options.Applicative import qualified Data.List.Extra as L parseArgs :: [String] -> IO Config parseArgs = handleParseResult . execParserPure parserPrefs parserInfo where desc = fullDesc <> progDesc "The Eclair Datalog compiler." parserPrefs = prefs $ showHelpOnError <> showHelpOnEmpty parserInfo = info (parser <**> helper) desc parser :: Parser Config parser = hsubparser (longCompileCommand <> shortCompileCommand) <|> hsubparser lspCommand where longCompileCommand = command "compile" compileCommand shortCompileCommand = command "c" compileCommand compileCommand = info compileParser compileDesc compileDesc = fullDesc <> header "eclair compile" <> progDesc "Compiles Datalog files." lspCommand = command "lsp" $ info (pure LSP) lspDesc lspDesc = fullDesc <> header "eclair lsp" <> progDesc "Runs the Eclair LSP server." compileParser :: Parser Config compileParser = Compile <$> compileParser' where compileParser' = CompileConfig <$> argument str (metavar "FILE" <> help "The main Datalog file to compile.") <*> emitKindParser <*> optional targetParser <*> numCoresParser targetParser :: Parser Target targetParser = option (maybeReader parseTarget) $ metavar "TARGET" <> long "target" <> short 't' <> help desc where desc = "Select the target CPU architecture. Default is to use the host architecture. Supported options: 'wasm32'." parseTarget = \case "wasm32" -> Just Wasm32 _ -> Nothing emitKindParser :: Parser EmitKind emitKindParser = option (maybeReader readEmitKind) (long "emit" <> value EmitLLVM <> help desc) where readEmitKind opt = case L.lower opt of "ast-transformed" -> Just EmitTransformedAST "ra" -> Just EmitRA "ra-transformed" -> Just EmitTransformedRA "eir" -> Just EmitEIR "llvm" -> Just EmitLLVM "souffle" -> Just EmitSouffle _ -> Nothing desc = "Compile to a specific format. Defaults to LLVM IR. Supported options: 'ast-transformed, 'ra', 'ra-transformed', 'eir', 'llvm' and 'souffle'." numCoresParser :: Parser Word numCoresParser = option auto $ long "jobs" <> metavar "JOBS" <> short 'j' <> value 1 <> help "Number of threads used for compilation." ================================================ FILE: lib/Eclair/Common/Config.hs ================================================ module Eclair.Common.Config ( EmitKind(..) , CompileConfig(..) , Target(..) , Config(..) ) where data EmitKind = EmitTransformedAST | EmitRA | EmitTransformedRA | EmitEIR | EmitLLVM | EmitSouffle -- TODO: object file, WASM, ... deriving (Eq, Show) -- TODO: optimization levels (-Ox), include dirs (-I), logging level (-q, -v), timing, ... data CompileConfig = CompileConfig { mainFile :: FilePath , emitKind :: EmitKind , cpuTarget :: Maybe Target -- Nothing = compile to host architecture , numCores :: Word -- Maximum number of cores Eclair is allowed to use } deriving (Eq, Show) data Target = Wasm32 deriving (Eq, Show) data Config = Compile CompileConfig | LSP deriving (Eq, Show) ================================================ FILE: lib/Eclair/Common/Extern.hs ================================================ module Eclair.Common.Extern ( Extern(..) , ExternKind(..) ) where import Eclair.Common.Id data Extern = Extern Id Int ExternKind deriving (Eq, Show) data ExternKind = ExternConstraint | ExternFunction deriving (Eq, Show) ================================================ FILE: lib/Eclair/Common/Id.hs ================================================ module Eclair.Common.Id ( Id(..) , prependToId , appendToId , startsWithId , stripIdPrefixes , startsWithIdPrefix , deltaPrefix , newPrefix ) where import qualified Data.Text as T import qualified Language.Souffle.Marshal as S import Prettyprinter newtype Id = Id { unId :: Text } deriving (Eq, Ord, Show, Generic) deriving anyclass S.Marshal instance Pretty Id where pretty = pretty . unId appendToId :: Id -> Text -> Id appendToId (Id x) y = Id (x <> y) prependToId :: Text -> Id -> Id prependToId x (Id y) = Id (x <> y) startsWithId :: Id -> Id -> Bool startsWithId (Id x) (Id start) = start `T.isPrefixOf` x stripIdPrefixes :: Id -> Id stripIdPrefixes (Id x) = Id $ stripPrefixes x where stripPrefixes t = foldl' stripPrefix t [deltaPrefix, newPrefix] stripPrefix acc prefix = fromMaybe acc (T.stripPrefix prefix acc) -- TODO: make all prefixes starts with special symbol, invalid in syntax deltaPrefix, newPrefix :: Text deltaPrefix = "delta_" newPrefix = "new_" startsWithIdPrefix :: Id -> Bool startsWithIdPrefix (Id x) = any (`T.isPrefixOf` x) [deltaPrefix, newPrefix] ================================================ FILE: lib/Eclair/Common/Literal.hs ================================================ module Eclair.Common.Literal ( Literal(..) ) where import Prettyprinter (Pretty (pretty), dquotes) data Literal = LNumber Word32 | LString Text deriving (Eq, Show) instance Pretty Literal where pretty = \case LNumber x -> pretty x LString x -> dquotes $ pretty x ================================================ FILE: lib/Eclair/Common/Location.hs ================================================ module Eclair.Common.Location ( NodeId(..) , Span(..) , SpanMap(..) , SourcePos(..) , SourceSpan(..) , insertSpan , lookupSpan , lookupNodeId , spanToSourceSpan ) where import qualified Text.Megaparsec as P import qualified Data.Map as M import qualified Language.Souffle.Marshal as S import Data.Maybe (fromJust) newtype NodeId = NodeId { unNodeId :: Word32 } deriving (Eq, Ord, Show, Generic) deriving S.Marshal -- A source span (begin and end position) data Span = Span { beginPos :: {-# UNPACK #-} !Int , endPos :: {-# UNPACK #-} !Int } deriving Show data SpanMap = SpanMap { spanMapPath :: !FilePath , spanMapSpans :: !(Map Word32 Span) } deriving Show insertSpan :: NodeId -> Span -> SpanMap -> SpanMap insertSpan nodeId span' (SpanMap path m) = SpanMap path (M.insert (unNodeId nodeId) span' m) -- NOTE: this assumes the node ID is generated by parsing the same file that resulted in the SpanMap. lookupSpan :: SpanMap -> NodeId -> Span lookupSpan (SpanMap _path m) nodeId = fromJust $ M.lookup (unNodeId nodeId) m -- Finds the most specific NodeId (that corresponds with the smallest span) lookupNodeId :: SpanMap -> Int -> Maybe NodeId lookupNodeId (SpanMap _ m) offset = m & M.toList & filter (containsOffset . snd) -- Just sorting by span size is not enough, sometimes we have two spans -- with identical widths (e.g. with parentheses). The last one will always -- be the node ID that belongs to the smallest (most specific) node. & sortWith (spanSize . snd &&& negate . fst) & viaNonEmpty head & map (NodeId . fst) where containsOffset span' = offset >= beginPos span' && offset < endPos span' spanSize span' = endPos span' - beginPos span' -- Helpers for producing error messages: -- Line and column information. 0-based! data SourcePos = SourcePos { sourcePosLine :: {-# UNPACK #-} !Int , sourcePosColumn :: {-# UNPACK #-} !Int } deriving (Eq, Ord, Show) data SourceSpan = SourceSpan { sourceSpanFile :: FilePath , sourceSpanBegin :: {-# UNPACK #-} !SourcePos , sourceSpanEnd :: {-# UNPACK #-} !SourcePos } deriving (Eq, Show) spanToSourceSpan :: FilePath -> Text -> Span -> SourceSpan spanToSourceSpan path text span'@(Span begin end) = either raiseError id parseResult where parseResult = P.runParser parser path text parser :: P.Parsec Void Text SourceSpan parser = do _ <- P.takeP Nothing begin beginPos' <- P.getSourcePos _ <- P.takeP Nothing diff endPos' <- P.getSourcePos let beginSourcePos = SourcePos (line beginPos' - 1) (column beginPos' - 1) endSourcePos = SourcePos (line endPos' - 1) (column endPos' - 1) pure $ SourceSpan path beginSourcePos endSourcePos where diff = end - begin line = P.unPos . P.sourceLine column = P.unPos . P.sourceColumn raiseError = const $ panic $ "Failed to get source location for file '" <> toText path <> "' and span " <> show span' ================================================ FILE: lib/Eclair/Common/Operator.hs ================================================ module Eclair.Common.Operator ( ArithmeticOp(..) , LogicalOp(..) , isEqualityOp , invertLogicalOp ) where import Eclair.Common.Pretty data ArithmeticOp = Plus | Minus | Multiply | Divide -- NOTE: integer division deriving (Eq, Show) data LogicalOp = Equals -- = | NotEquals -- != | LessThan -- < | LessOrEqual -- <= | GreaterThan -- > | GreaterOrEqual -- >= deriving (Eq, Show) invertLogicalOp :: LogicalOp -> LogicalOp invertLogicalOp = \case Equals -> NotEquals NotEquals -> Equals LessThan -> GreaterOrEqual GreaterThan -> LessOrEqual LessOrEqual -> GreaterThan GreaterOrEqual -> LessThan isEqualityOp :: LogicalOp -> Bool isEqualityOp = \case Equals -> True NotEquals -> True _ -> False instance Pretty ArithmeticOp where pretty = \case Plus -> "+" Minus -> "-" Multiply -> "*" Divide -> "/" instance Pretty LogicalOp where pretty = \case Equals -> "=" NotEquals -> "!=" LessThan -> "<" LessOrEqual -> "<=" GreaterThan -> ">" GreaterOrEqual -> ">=" ================================================ FILE: lib/Eclair/Common/Pretty.hs ================================================ module Eclair.Common.Pretty ( module Eclair.Common.Pretty , module Prettyprinter , module Prettyprinter.Render.Text ) where import Prettyprinter import Prettyprinter.Render.Text printDoc :: Pretty a => a -> Text printDoc = renderStrict . layoutSmart defaultLayoutOptions . pretty indentation :: Int indentation = 2 interleaveWith :: Doc ann -> [Doc ann] -> Doc ann interleaveWith d = hsep . punctuate d withCommas :: [Doc ann] -> Doc ann withCommas = interleaveWith comma withAnds :: [Doc ann] -> Doc ann withAnds = interleaveWith (space <> "and") between :: Doc ann -> Doc ann -> Doc ann -> Doc ann between begin end doc = begin <> doc <> end ================================================ FILE: lib/Eclair/Comonads.hs ================================================ module Eclair.Comonads ( module Eclair.Comonads ) where data Triple a b c = Triple { tFst :: a , tSnd :: b , tThd :: c } deriving Functor instance Comonad (Triple a b) where extract (Triple _ _ c) = c duplicate (Triple a b c) = Triple a b (Triple a b c) data Quad a b c d = Quad { qFirst :: a , qSecond :: b , qThird :: c , qFourth :: d } deriving Functor instance Comonad (Quad a b c) where extract (Quad _ _ _ d) = d duplicate (Quad a b c d) = Quad a b c (Quad a b c d) ================================================ FILE: lib/Eclair/EIR/IR.hs ================================================ {-# LANGUAGE TemplateHaskell #-} module Eclair.EIR.IR ( EIR(..) , EIRF(..) , Relation , Op(..) , LogicalOp(..) , ArithmeticOp(..) , Type(..) , Function(..) , LabelId(..) , Visibility(..) ) where import Eclair.Common.Id import Eclair.Common.Operator import Eclair.Common.Literal import Eclair.Common.Pretty import Eclair.RA.IndexSelection (Index) import Eclair.LLVM.Metadata type Relation = Id data Type = Program | Value | Iter | Pointer Type | Void deriving (Eq, Show) data Function = InitializeEmpty | Destroy | Purge | Swap | InsertRange Relation Index -- InsertRange specialized for this relation and index | IsEmpty | Size | Contains | Insert | IterCurrent | IterNext | IterIsEqual | IterLowerBound | IterUpperBound | IterBegin | IterEnd deriving (Eq, Show) newtype LabelId = LabelId Text deriving (Eq, Show) instance IsString LabelId where fromString = LabelId . fromString data Op = RelationOp Relation Index Function -- a primop for operations on relations | SymbolTableInit | SymbolTableDestroy | SymbolTableInsert | ComparisonOp LogicalOp | ArithOp ArithmeticOp | ExternOp Id deriving (Eq, Show) data Visibility = Public | Private deriving (Eq, Show) data EIR = Module [EIR] -- A module is the same as a block, but is rendered in a different way. | Block [EIR] | Function Visibility Text [Type] Type EIR | FunctionArg Int | DeclareProgram [(Relation, Metadata)] | FieldAccess EIR Int | Var Text | Assign EIR EIR | PrimOp Op [EIR] -- A primitive operation, these tend to be simple function calls or operators | HeapAllocateProgram | FreeProgram EIR | StackAllocate Relation Index Type | Par [EIR] | Loop [EIR] | If EIR EIR | Not EIR | And EIR EIR | Jump LabelId | Label LabelId | Return EIR | Lit Literal deriving (Eq, Show) makeBaseFunctor ''EIR indentBlock :: Doc ann -> Doc ann -> Doc ann -> Doc ann indentBlock begin end blk = nest indentation (begin <> hardline <> blk) <> hardline <> end braceBlock :: Doc ann -> Doc ann braceBlock = indentBlock "{" "}" statementBlock :: Pretty a => [a] -> Doc ann statementBlock = braceBlock . vsep . map pretty instance Pretty Type where pretty = \case Program -> "Program" Value -> "Value" Iter -> "Iter" Pointer ty -> "*" <> pretty ty Void -> "Void" instance Pretty Function where pretty = \case InitializeEmpty -> "init_empty" Destroy -> "destroy" Purge -> "purge" Swap -> "swap" InsertRange r idx -> "insert_range" <> angles (pretty r <> pretty idx) IsEmpty -> "is_empty" Contains -> "contains" Insert -> "insert" IterCurrent -> "iter_current" IterNext -> "iter_next" IterIsEqual -> "iter_is_equal" IterLowerBound -> "iter_lower_bound" IterUpperBound -> "iter_upper_bound" IterBegin -> "iter_begin" IterEnd -> "iter_end" Size -> "size" instance Pretty LabelId where pretty (LabelId label) = pretty label instance Pretty Op where pretty = \case SymbolTableInit -> "symbol_table.init" SymbolTableDestroy -> "symbol_table.destroy" SymbolTableInsert -> "symbol_table.insert" RelationOp r _idx fn -> pretty r <> "." <> pretty fn ComparisonOp op -> -- Since `=` is already used for assignment in EIR, we use `==` for comparison. if op == Equals then "==" else pretty op ArithOp op -> pretty op ExternOp op -> pretty op instance Pretty EIR where pretty = \case Module stmts -> -- This adds newlines in between top level EIR statements vsep $ intersperse mempty $ map pretty stmts Block stmts -> statementBlock stmts Function visibility name tys retTy body -> let fn = if visibility == Public then "export fn" else "fn" in vsep [ fn <+> pretty name <> parens (withCommas $ map pretty tys) <+> "->" <+> pretty retTy , pretty body -- Note: This is already a Block ] FunctionArg pos -> "FN_ARG" <> brackets (pretty pos) DeclareProgram metadatas -> vsep [ "declare_type" <+> "Program" , braceBlock . vsep $ "symbol_table" : map (\(r, meta) -> pretty r <+> pretty meta) metadatas ] FieldAccess ptr pos -> pretty ptr <> "." <> pretty pos Var v -> pretty v Assign var value -> pretty var <+> "=" <+> pretty value PrimOp op [arg1, arg2] | isInfixPrimOp op -> parens $ pretty arg1 <+> pretty op <+> pretty arg2 PrimOp op args -> pretty op <> parens (withCommas $ map pretty args) HeapAllocateProgram -> "heap_allocate_program" FreeProgram ptr -> "free_program" <> parens (pretty ptr) StackAllocate r _idx ty -> pretty r <> "." <> "stack_allocate" <+> pretty ty Par stmts -> vsep ["parallel", statementBlock stmts] Loop stmts -> vsep ["loop", statementBlock stmts] If cond body -> let wrap = case body of Block _ -> identity _ -> braceBlock in vsep ["if" <+> parens (pretty cond), wrap (pretty body)] Not bool' -> "not" <+> pretty bool' And bool1 bool2 -> pretty bool1 <+> "&&" <+> pretty bool2 Jump label -> "goto" <+> pretty label Label label -> pretty label <> colon Return value -> "return" <+> pretty value Lit x -> pretty x isInfixPrimOp :: Op -> Bool isInfixPrimOp = \case ComparisonOp {} -> True ArithOp {} -> True _ -> False ================================================ FILE: lib/Eclair/EIR/Lower/API.hs ================================================ {-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-} module Eclair.EIR.Lower.API ( CodegenInOutT , mkInOutState , codegenAPI , apiFunction ) where import Prelude hiding (void) import Control.Monad.Morph import Data.Traversable (for) import Data.Maybe (fromJust) import qualified Data.Set as S import qualified Data.Map as M import qualified Data.List as L import qualified Eclair.EIR.IR as EIR import qualified Eclair.LLVM.Symbol as Symbol import qualified Eclair.LLVM.SymbolTable as SymbolTable import Eclair.LLVM.Codegen as LLVM import Eclair.LLVM.Metadata import Eclair.EIR.Lower.Codegen import Eclair.AST.IR (UsageMode(..)) import Eclair.RA.IndexSelection import Eclair.Common.Id type Relation = EIR.Relation -- Helper data type that pre-computes most of the important data. data InOutState = InOutState { relationMapping :: Map Relation Word32 , relationNumColumns :: Map Relation Int , relations :: Set Relation , indicesByRelation :: Map Relation [Index] , offsetsByRelationAndIndex :: Map (Relation, Index) Int , inOutLowerState :: LowerState } type CodegenInOutT = ReaderT InOutState codegenAPI :: Map Id Word32 -> Map Relation UsageMode -> [(Relation, Metadata)] -> LowerState -> ModuleBuilderT IO () codegenAPI relMapping usageMapping metas lowerState = do usingReaderT (mkInOutState relMapping metas lowerState) $ do addFactsFn <- generateAddFactsFn usageMapping _ <- generateAddFact addFactsFn _ <- generateGetFactsFn usageMapping _ <- generateFreeBufferFn _ <- generateFactCountFn usageMapping _ <- generateEncodeStringFn _ <- generateDecodeStringFn pass generateAddFact :: MonadFix m => Operand -> CodegenInOutT (ModuleBuilderT m) Operand generateAddFact addFactsFn = do lowerState <- asks inOutLowerState let args = [ (ptr (programType lowerState), ParameterName "eclair_program") , (i32, ParameterName "fact_type") , (ptr i32, ParameterName "memory") ] returnType = void apiFunction "eclair_add_fact" args returnType $ \[program, factType, memory] -> do _ <- call addFactsFn [program, factType, memory, int32 1] retVoid generateAddFactsFn :: MonadFix m => Map Id UsageMode -> CodegenInOutT (ModuleBuilderT m) Operand generateAddFactsFn usageMapping = do inOutState <- ask let lowerState = inOutLowerState inOutState rels = S.filter (isInputRelation usageMapping) $ relations inOutState args = [ (ptr (programType lowerState), ParameterName "eclair_program") , (i32, ParameterName "fact_type") , (ptr i32, ParameterName "memory") , (i32, ParameterName "fact_count") ] returnType = void apiFunction "eclair_add_facts" args returnType $ \[program, factType, memory, factCount] -> mdo switchOnFactType rels (relationMapping inOutState) retVoid factType $ \r -> do indexes <- indicesForRelation r for_ indexes $ \idx -> do numCols <- fromIntegral <$> numColsForRelation r treeOffset <- int32 . toInteger <$> offsetForRelationAndIndex r idx relationPtr <- gep program [int32 0, treeOffset] -- TODO: don't re-calculate this type, do this based on value datatype created in each runtime data structure let arrayPtr = ptrcast (ArrayType numCols i32) memory loopFor (int32 0) (`ult` factCount) (add (int32 1)) $ \i -> do valuePtr <- gep arrayPtr [i] fn <- toCodegenInOut lowerState $ lookupFunction r idx EIR.Insert call fn [relationPtr, valuePtr] br end -- early return end <- blockNamed "end" retVoid generateGetFactsFn :: MonadFix m => Map Relation UsageMode -> CodegenInOutT (ModuleBuilderT m) Operand generateGetFactsFn usageMapping = do inOutState <- ask let lowerState = inOutLowerState inOutState rels = S.filter (isOutputRelation usageMapping) $ relations inOutState args = [ (ptr (programType lowerState), ParameterName "eclair_program") , (i32, ParameterName "fact_type") ] returnType = ptr i32 mallocFn = extMalloc $ externals lowerState apiFunction "eclair_get_facts" args returnType $ \[program, factType] -> do switchOnFactType rels (relationMapping inOutState) (ret $ nullPtr i32) factType $ \r -> do indexes <- indicesForRelation r let idx = fromJust $ findLongestIndex indexes doCall op args' = do fn <- toCodegenInOut lowerState $ lookupFunction r idx op call fn args' numCols <- numColsForRelation r let valueSize = 4 * numCols -- TODO: should use LLVM "valueSize" instead of re-calculating here treeOffset <- int32 . toInteger <$> offsetForRelationAndIndex r idx relationPtr <- gep program [int32 0, treeOffset] relationSize <- doCall EIR.Size [relationPtr] >>= (`trunc` i32) memorySize <- mul relationSize (int32 $ toInteger valueSize) memory <- call mallocFn [memorySize] let arrayPtr = ptrcast (ArrayType (fromIntegral numCols) i32) memory iPtr <- alloca i32 (Just (int32 1)) 0 store iPtr 0 (int32 0) let iterTy = evalState (toLLVMType r idx EIR.Iter) lowerState currIter <- alloca iterTy (Just (int32 1)) 0 endIter <- alloca iterTy (Just (int32 1)) 0 _ <- doCall EIR.IterBegin [relationPtr, currIter] _ <- doCall EIR.IterEnd [relationPtr, endIter] let loopCondition = do isEqual <- doCall EIR.IterIsEqual [currIter, endIter] not' isEqual loopWhile loopCondition $ do i <- load iPtr 0 valuePtr <- gep arrayPtr [i] currentVal <- doCall EIR.IterCurrent [currIter] copy (mkPath []) currentVal valuePtr i' <- add i (int32 1) store iPtr 0 i' doCall EIR.IterNext [currIter] ret $ ptrcast i32 memory generateFreeBufferFn :: Monad m => CodegenInOutT (ModuleBuilderT m) Operand generateFreeBufferFn = do lowerState <- asks inOutLowerState let freeFn = extFree $ externals lowerState args = [(ptr i32, ParameterName "buffer")] returnType = void apiFunction "eclair_free_buffer" args returnType $ \[buf] -> mdo let memory = ptrcast i8 buf _ <- call freeFn [memory] retVoid generateFactCountFn :: MonadFix m => Map Id UsageMode -> CodegenInOutT (ModuleBuilderT m) Operand generateFactCountFn usageMapping = do inOutState <- ask let lowerState = inOutLowerState inOutState rels = S.filter (isOutputRelation usageMapping) $ relations inOutState args = [ (ptr (programType lowerState), ParameterName "eclair_program") , (i32, ParameterName "fact_type") ] returnType = i32 apiFunction "eclair_fact_count" args returnType $ \[program, factType] -> do switchOnFactType rels (relationMapping inOutState) (ret $ int32 0) factType $ \r -> do indexes <- indicesForRelation r let idx = fromJust $ findLongestIndex indexes doCall op args' = do fn <- toCodegenInOut lowerState $ lookupFunction r idx op call fn args' treeOffset <- int32 . toInteger <$> offsetForRelationAndIndex r idx relationPtr <- gep program [int32 0, treeOffset] relationSize <- doCall EIR.Size [relationPtr] ret =<< trunc relationSize i32 -- NOTE: string does not need to be 0-terminated, length field is used to determine length (in bytes). -- Eclair makes an internal copy of the string, for simpler memory management. generateEncodeStringFn :: MonadFix m => CodegenInOutT (ModuleBuilderT m) Operand generateEncodeStringFn = do lowerState <- asks inOutLowerState let args = [ (ptr (programType lowerState), "eclair_program") , (i32, "string_length") , (ptr i8, "string_data") ] (symbolTable, symbol) = (symbolTableFns &&& symbolFns) lowerState exts = externals lowerState apiFunction "eclair_encode_string" args i32 $ \[program, len, stringData] -> do stringDataCopy <- call (extMalloc exts) [len] lenBytes <- zext len i64 _ <- call (extMemcpy exts) [stringDataCopy, stringData, lenBytes, bit 0] symbolPtr <- alloca (Symbol.tySymbol symbol) (Just (int32 1)) 0 _ <- call (Symbol.symbolInit symbol) [symbolPtr, len, stringDataCopy] symbolTablePtr <- getSymbolTablePtr program index <- call (SymbolTable.symbolTableLookupIndex symbolTable) [symbolTablePtr, symbolPtr] alreadyContainsSymbol <- index `ne` int32 0xFFFFFFFF if' alreadyContainsSymbol $ do -- Since the string was not added to the table, the memory pointed to by -- the symbol is not managed by the symbol table, so we need to manually free the data. _ <- call (extFree exts) [stringDataCopy] ret index -- No free needed here, automatically called when symbol table is cleaned up. ret =<< call (SymbolTable.symbolTableFindOrInsert symbolTable) [symbolTablePtr, symbolPtr] -- NOTE: do not free the returned string/byte array, -- this happens automatically when eclair_destroy is called generateDecodeStringFn :: MonadFix m => CodegenInOutT (ModuleBuilderT m) Operand generateDecodeStringFn = do lowerState <- asks inOutLowerState let args = [ (ptr (programType lowerState), "eclair_program") , (i32, "string_index") ] symbolTable = symbolTableFns lowerState apiFunction "eclair_decode_string" args (ptr i8) $ \[program, idx] -> do symbolTablePtr <- getSymbolTablePtr program containsIndex <- call (SymbolTable.symbolTableContainsIndex symbolTable) [symbolTablePtr, idx] if' containsIndex $ do symbolPtr <- call (SymbolTable.symbolTableLookupSymbol symbolTable) [symbolTablePtr, idx] ret $ ptrcast i8 symbolPtr ret $ nullPtr i8 toCodegenInOut :: Monad m => LowerState -> CodegenT m Operand -> IRBuilderT (CodegenInOutT (ModuleBuilderT m)) Operand toCodegenInOut lowerState m = hoist lift $ runCodegenM m lowerState mkInOutState :: Map Relation Word32 -> [(Relation, Metadata)] -> LowerState -> InOutState mkInOutState relMapping metas ls = InOutState relMapping relNumCols rels indicesByRel offsetsByRelAndIndex ls where rs = map fst metas -- NOTE: disregards all "special" relations, since they should not be visible to the end user! rels = S.fromList $ filter (not . startsWithIdPrefix) rs relNumCols = M.fromList [ (r, numCols) | r <- rs , let numCols = getNumColumns (snd $ fromJust $ L.find ((== r) . fst) metas) ] relInfos = M.keys $ fnsMap ls indicesByRel = M.fromAscListWith (<>) $ map (second one) $ sortWith fst relInfos offsetsByRelAndIndex = M.fromDistinctAscList [ (ri, offset) | ri <- sortNub relInfos -- + 1 due to symbol table at position 0 in program struct , let offset = 1 + fromJust (L.elemIndex ri relInfos) ] indicesForRelation :: MonadReader InOutState m => Relation -> m [Index] indicesForRelation r = do inOutState <- ask pure . fromJust . M.lookup r $ indicesByRelation inOutState offsetForRelationAndIndex :: MonadReader InOutState m => Relation -> Index -> m Int offsetForRelationAndIndex r idx = do inOutState <- ask pure . fromJust . M.lookup (r, idx) $ offsetsByRelationAndIndex inOutState numColsForRelation :: MonadReader InOutState m => Relation -> m Int numColsForRelation r = fromJust . M.lookup r . relationNumColumns <$> ask switchOnFactType :: MonadFix m => Set Relation -> Map Relation Word32 -> IRBuilderT m () -> Operand -> (Relation -> IRBuilderT m ()) -> IRBuilderT m () switchOnFactType rels stringMap defaultCase factType generateCase = mdo switch factType end caseBlocks caseBlocks <- for (M.toList relMapping) $ \(r, factNum) -> do caseBlock <- blockNamed (unId r) generateCase r pure (int32 $ toInteger factNum, caseBlock) end <- blockNamed "switch.default" defaultCase where relMapping = M.restrictKeys stringMap rels -- A helper function for easily finding the "longest" index. -- This is important for when you need to retrieve facts, since this index will -- contain the most facts. findLongestIndex :: [Index] -> Maybe Index findLongestIndex = -- TODO use NonEmpty viaNonEmpty head . sortOn (Dual . length . unIndex) isOutputRelation :: Map Relation UsageMode -> Relation -> Bool isOutputRelation usageMapping r = case M.lookup r usageMapping of Just Output -> True Just InputOutput -> True _ -> False isInputRelation :: Map Relation UsageMode -> Relation -> Bool isInputRelation usageMapping r = case M.lookup r usageMapping of Just Input -> True Just InputOutput -> True _ -> False getSymbolTablePtr :: (MonadModuleBuilder m, MonadIRBuilder m) => Operand -> m Operand getSymbolTablePtr program = gep program [int32 0, int32 0] apiFunction :: (MonadModuleBuilder m, HasSuffix m) => Name -> [(LLVM.Type, ParameterName)] -> LLVM.Type -> ([Operand] -> IRBuilderT m a) -> m Operand apiFunction fnName args retTy body = withFunctionAttributes (WasmExportName (unName fnName):) $ function fnName args retTy body ================================================ FILE: lib/Eclair/EIR/Lower/Codegen.hs ================================================ module Eclair.EIR.Lower.Codegen ( CodegenT , runCodegenM , LowerState(..) , Table(..) , Externals(..) , labelToName , lookupFunction , lookupPrimOp , toLLVMType , lookupVar , addVarBinding , newGlobalVarName , loadIfNeeded ) where import Prelude hiding (void) import Control.Monad.Morph import Data.Maybe (fromJust) import qualified Data.Map as M import Eclair.LLVM.Codegen import Eclair.LLVM.Table import Eclair.LLVM.Externals import qualified Eclair.LLVM.Symbol as Symbol import qualified Eclair.LLVM.SymbolTable as SymbolTable import qualified Eclair.EIR.IR as EIR import Eclair.RA.IndexSelection import Eclair.Common.Id type Relation = EIR.Relation type EIR = EIR.EIR type VarMap = Map Text Operand type TableMap = Map (Relation, Index) Table data LowerState = LowerState { programType :: Type , programSizeBytes :: Word64 , symbolTableFns :: SymbolTable.SymbolTable , symbolFns :: Symbol.Symbol , fnsMap :: TableMap , varMap :: VarMap , globalVarCounter :: Int , externals :: Externals , externFns :: Map Id Operand } type CodegenT m = StateT LowerState (IRBuilderT (ModuleBuilderT m)) runCodegenM :: Monad m => CodegenT m a -> LowerState -> IRBuilderT (ModuleBuilderT m) a runCodegenM = evalStateT labelToName :: EIR.LabelId -> Name labelToName (EIR.LabelId lbl) = Name lbl -- This is a function mostly used by `lookupPrimOp`, but also for calling functions during fact IO lookupFunction :: Monad m => Relation -> Index -> EIR.Function -> CodegenT m Operand lookupFunction r idx fn = do tableMap <- gets fnsMap let table = unsafeLookup r idx tableMap extractFn tableMap table where extractFn tableMap table = case fn of EIR.InitializeEmpty -> pure $ fnInitEmpty table EIR.Destroy -> pure $ fnDestroy table EIR.Purge -> pure $ fnPurge table EIR.Swap -> pure $ fnSwap table EIR.InsertRange r2 idx2 -> do let templatedFn = fnInsertRangeTemplate table suffix = unId r <> "_" <> unId r2 table2 = unsafeLookup r2 idx2 tableMap iterParams = IteratorParams { ipIterCurrent = fnIterCurrent table2 , ipIterNext = fnIterNext table2 , ipIterIsEqual = fnIterIsEqual table2 , ipTypeIter = typeIter table2 } -- I think we don't need to cache instantiations here (to avoid duplicate functions) lift . lift $ hoist generalize $ instantiate suffix iterParams templatedFn EIR.IsEmpty -> pure $ fnIsEmpty table EIR.Size -> pure $ fnSize table EIR.Contains -> pure $ fnContains table EIR.Insert -> pure $ fnInsert table EIR.IterCurrent -> pure $ fnIterCurrent table EIR.IterNext -> pure $ fnIterNext table EIR.IterIsEqual -> pure $ fnIterIsEqual table EIR.IterLowerBound -> pure $ fnLowerBound table EIR.IterUpperBound -> pure $ fnUpperBound table EIR.IterBegin -> pure $ fnBegin table EIR.IterEnd -> pure $ fnEnd table unsafeLookup r' idx' = fromJust . M.lookup (r', idx') type PrimOp m = Either Operand (Operand -> Operand -> CodegenT m Operand) lookupPrimOp :: Monad m => EIR.Op -> CodegenT m (PrimOp m) lookupPrimOp = \case EIR.SymbolTableInit -> toSymbolTableOp SymbolTable.symbolTableInit EIR.SymbolTableDestroy -> toSymbolTableOp SymbolTable.symbolTableDestroy EIR.SymbolTableInsert -> toSymbolTableOp SymbolTable.symbolTableFindOrInsert EIR.RelationOp r idx fn -> Left <$> lookupFunction r idx fn EIR.ComparisonOp op -> pure $ Right $ case op of EIR.Equals -> eq EIR.NotEquals -> ne -- NOTE: this will result in issues for signed integers in the future, but ignoring that for now.. -- We can pass along the type then? EIR.LessThan -> ult EIR.LessOrEqual -> ule EIR.GreaterThan -> ugt EIR.GreaterOrEqual -> uge EIR.ArithOp op -> -- NOTE: this will result in issues for signed integers in the future, but ignoring that for now.. -- We can pass along the type then? pure $ Right $ case op of EIR.Plus -> add EIR.Minus -> sub EIR.Multiply -> mul EIR.Divide -> udiv EIR.ExternOp opName -> do Left <$> gets (fromJust . M.lookup opName . externFns) where toSymbolTableOp llvmOp = Left <$> do symbolTable <- gets symbolTableFns pure $ llvmOp symbolTable toLLVMType :: MonadState LowerState m => Relation -> Index -> EIR.Type -> m Type toLLVMType r idx = go where go = \case EIR.Program -> programType <$> get EIR.Iter -> typeIter . fromJust . M.lookup (r, idx) <$> gets fnsMap EIR.Value -> typeValue . fromJust . M.lookup (r, idx) <$> gets fnsMap EIR.Void -> pure void EIR.Pointer ty -> ptr <$> go ty -- Only called internally, should always be called on a var that exists. lookupVar :: MonadState LowerState m => Text -> m Operand lookupVar v = gets (fromJust . M.lookup v . varMap) addVarBinding :: MonadState LowerState m => Text -> Operand -> m () addVarBinding var value = modify $ \s -> s { varMap = M.insert var value (varMap s) } newGlobalVarName :: MonadState LowerState m => Text -> m Name newGlobalVarName name = do count <- gets globalVarCounter modify $ \s -> s { globalVarCounter = count + 1 } pure $ Name $ name <> "_" <> show count -- NOTE: this is for the case when we are assigning 1 field of a struct/array -- to another of the same kind, where the right side needs to be loaded before -- storing it to the left side of the equation. loadIfNeeded :: MonadIRBuilder m => m Operand -> EIR -> m Operand loadIfNeeded operand = \case EIR.FieldAccess _ _ -> flip load 0 =<< operand _ -> operand ================================================ FILE: lib/Eclair/EIR/Lower/Externals.hs ================================================ {-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-} module Eclair.EIR.Lower.Externals ( createExternals ) where import Prelude hiding (void) import Eclair.EIR.Lower.Codegen import Eclair.LLVM.Codegen as LLVM import Eclair.Common.Config createExternals :: ConfigT (ModuleBuilderT IO) Externals createExternals = do target <- cfgTargetTriple <$> getConfig mallocFn <- lift $ generateMallocFn target freeFn <- lift $ generateFreeFn target memsetFn <- extern "llvm.memset.p0i8.i64" [ptr i8, i8, i64, i1] void memcpyFn <- extern "llvm.memcpy.p0i8.p0i8.i64" [ptr i8, ptr i8, i64, i1] void memcmpFn <- if target == Just Wasm32 then lift generateMemCmpFn else extern "memcmp" [ptr i8, ptr i8, i64] i32 -- TODO write alternatives for WASM mmapFn <- extern "mmap" [ptr void, i64, i32, i32, i32, i32] (ptr void) munmapFn <- extern "munmap" [ptr void, i64] i32 pure $ Externals mallocFn freeFn memsetFn memcpyFn memcmpFn mmapFn munmapFn generateMallocFn :: Monad m => Maybe Target -> ModuleBuilderT m Operand generateMallocFn target = do mallocFn <- extern "malloc" [i32] (ptr i8) when (target == Just Wasm32) $ do _ <- withFunctionAttributes (const [WasmExportName "eclair_malloc"]) $ function "eclair_malloc" [(i32, "byte_count")] (ptr i8) $ \[byteCount] -> ret =<< call mallocFn [byteCount] pass pure mallocFn generateFreeFn :: Monad m => Maybe Target -> ModuleBuilderT m Operand generateFreeFn target = do freeFn <- extern "free" [ptr i8] void when (target == Just Wasm32) $ do _ <- withFunctionAttributes (const [WasmExportName "eclair_free"]) $ function "eclair_free" [(ptr i8, "memory")] void $ \[memoryPtr] -> call freeFn [memoryPtr] pass pure freeFn -- NOTE: we only care about 0 if they are equal! generateMemCmpFn :: MonadFix m => ModuleBuilderT m Operand generateMemCmpFn = do let args = [(ptr i8, "array1"), (ptr i8, "array2"), (i64, "byte_count")] function "memcmp_wasm32" args i32 $ \[array1, array2, byteCount] -> do i64Count <- byteCount `udiv` int64 8 restCount <- byteCount `and` int64 7 -- modulo 8 let i64Array1 = ptrcast i64 array1 let i64Array2 = ptrcast i64 array2 loopFor (int64 0) (`ult` i64Count) (add (int64 1)) $ \i -> do valuePtr1 <- gep i64Array1 [i] valuePtr2 <- gep i64Array2 [i] value1 <- load valuePtr1 0 value2 <- load valuePtr2 0 isNotEqual <- value1 `ne` value2 if' isNotEqual $ ret $ int32 1 startIdx <- mul i64Count (int64 8) loopFor (int64 0) (`ult` restCount) (add (int64 1)) $ \i -> do idx <- add i startIdx valuePtr1 <- gep array1 [idx] valuePtr2 <- gep array2 [idx] value1 <- load valuePtr1 0 value2 <- load valuePtr2 0 isNotEqual <- value1 `ne` value2 if' isNotEqual $ ret $ int32 1 ret $ int32 0 ================================================ FILE: lib/Eclair/EIR/Lower.hs ================================================ {-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-} module Eclair.EIR.Lower ( compileToLLVM ) where import Prelude hiding (void) import qualified Prelude import qualified Relude (swap) import Control.Monad.Morph hiding (embed) import qualified Data.ByteString as BS import qualified Data.Map as M import Data.List ((!!)) import Foreign.ForeignPtr import qualified Eclair.EIR.IR as EIR import qualified Eclair.LLVM.BTree as BTree import qualified Eclair.LLVM.Symbol as Symbol import qualified Eclair.LLVM.Vector as Vector import qualified Eclair.LLVM.HashMap as HashMap import qualified Eclair.LLVM.SymbolTable as SymbolTable import Eclair.EIR.Lower.Codegen import Eclair.EIR.Lower.Externals import Eclair.EIR.Lower.API import Eclair.LLVM.Codegen as LLVM import qualified LLVM.C.API as LibLLVM import Eclair.LLVM.Metadata import Eclair.LLVM.Hash import Eclair.RA.IndexSelection import Eclair.Common.Config import Eclair.Comonads import Eclair.AST.IR import Eclair.AST.Transforms.ReplaceStrings (StringMap) import Eclair.Common.Id import Eclair.Common.Extern type EIR = EIR.EIR type EIRF = EIR.EIRF type Relation = EIR.Relation compileToLLVM :: Maybe Target -> StringMap -> Map Relation UsageMode -> [Extern] -> EIR -> IO Module compileToLLVM target stringMapping usageMapping externDefs eir = do ctx <- LibLLVM.mkContext llvmMod <- LibLLVM.mkModule ctx "eclair" if target == Just Wasm32 then do -- layout string found in Rust compiler (wasm32_unknown_unknown.rs) let wasmDataLayout = "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-n32:64-S128-ni:1:10:20" td <- LibLLVM.mkTargetData wasmDataLayout LibLLVM.setTargetData llvmMod td withForeignPtr td $ \tdPtr -> do compile (Config target ctx tdPtr) eir else do -- use host layout td <- LibLLVM.getTargetData llvmMod compile (Config target ctx td) eir where compile cfg = \case EIR.Module (EIR.DeclareProgram metas : decls) -> runModuleBuilderT $ runConfigT cfg $ do let ctx = cfgLLVMContext cfg td = cfgTargetData cfg exts <- createExternals (metaMapping, fnss) <- runCacheT $ traverse (codegenRuntime exts . snd) metas codegenDebugInfos metaMapping (symbolTable, symbol) <- codegenSymbolTable exts let symbolTableTy = SymbolTable.tySymbolTable symbolTable fnsInfo = zip (map (map getIndexFromMeta) metas) fnss fnsMap' = M.fromList fnsInfo -- TODO: add hash based on filepath of the file we're compiling? programTy <- typedef "program" Off (symbolTableTy : map typeObj fnss) programSize <- withLLVMTypeInfo ctx $ llvmSizeOf ctx td programTy lift $ do externs <- traverse (processExtern symbolTableTy) externDefs let externMap = M.fromList externs lowerState = LowerState programTy programSize symbolTable symbol fnsMap' mempty 0 exts externMap traverse_ (processDecl lowerState) decls codegenAPI relMapping usageMapping metas lowerState _ -> panic "Unexpected top level EIR declarations when compiling to LLVM!" relMapping = M.mapKeys Id stringMapping processExtern symbolTableTy (Extern fnName argCount extKind) = do let argTys = ptr symbolTableTy : replicate argCount i32 retTy = if extKind == ExternConstraint then i1 else i32 -- i1, or i8 for bool? fn <- extern (Name $ unId fnName) argTys retTy pure (fnName, fn) processDecl lowerState = \case EIR.Function visibility name tys retTy body -> do let unusedRelation = panic "Unexpected use of relation for function type when lowering EIR to LLVM." unusedIndex = panic "Unexpected use of index for function type when lowering EIR to LLVM." getType ty = evalStateT (toLLVMType unusedRelation unusedIndex ty) lowerState argTypes <- liftIO $ traverse getType tys returnType <- liftIO $ getType retTy let args = map (, ParameterName "arg") argTypes fn = if visibility == EIR.Public then apiFunction else function -- Only public functions are exposed, rest is only used internally fn (Name name) args returnType $ \args' -> do runCodegenM (fnBodyToLLVM args' body) lowerState _ -> panic "Unexpected top level EIR declaration when compiling to LLVM!" fnBodyToLLVM :: MonadFix m => [Operand] -> EIR -> CodegenT m () fnBodyToLLVM args = lowerM instrToOperand instrToUnit where instrToOperand :: Monad m => EIRF (EIR, CodegenT m Operand) -> CodegenT m Operand instrToOperand = \case EIR.FunctionArgF pos -> pure $ args !! pos EIR.FieldAccessF (snd -> structOrVar) pos -> do -- NOTE: structOrVar is always a pointer to a heap-/stack-allocated -- value so we need to first deref the pointer, and then index into the -- fields of the value ('addr' does this for us). On top of that, we -- can only compute the address here and not do a load as well, since -- sometimes this pointer is used in a "store" instruction. addr (mkPath [int32 $ toInteger pos]) =<< structOrVar EIR.VarF v -> lookupVar v EIR.NotF (snd -> bool') -> not' =<< bool' EIR.AndF (snd -> bool1) (snd -> bool2) -> do b1 <- bool1 b2 <- bool2 and b1 b2 EIR.PrimOpF op args' -> invokePrimOp op args' EIR.HeapAllocateProgramF -> do (malloc, (programTy, programSize)) <- gets (extMalloc . externals &&& programType &&& programSizeBytes) let memorySize = int32 $ fromIntegral programSize pointer <- call malloc [memorySize] pure $ ptrcast programTy pointer EIR.StackAllocateF r idx ty -> do theType <- toLLVMType r idx ty alloca theType (Just (int32 1)) 0 EIR.LitF (LNumber value) -> pure $ int32 (fromIntegral value) EIR.LitF (LString value) -> do -- We create a global variable to statically store the string, -- but we malloc and copy the string over since the symbol table -- frees all symbols at the end. varName <- newGlobalVarName "string_literal" globalStringPtr <- globalUtf8StringPtr value varName let utf8Length = int32 $ toInteger $ BS.length $ encodeUtf8 value numBytes <- utf8Length `zext` i64 exts <- gets externals stringPtr <- call (extMalloc exts) [utf8Length] _ <- call (extMemcpy exts) [stringPtr, globalStringPtr, numBytes, bit 0] symFns <- gets symbolFns let tySymbol = Symbol.tySymbol symFns symbolPtr <- alloca tySymbol (Just (int32 1)) 0 _ <- call (Symbol.symbolInit symFns) [symbolPtr, utf8Length, stringPtr] pure symbolPtr _ -> panic "Unhandled pattern match case in 'instrToOperand' while lowering EIR to LLVM!" instrToUnit :: MonadFix m => (EIRF (Triple EIR (CodegenT m Operand) (CodegenT m ())) -> CodegenT m ()) instrToUnit = \case EIR.BlockF stmts -> do traverse_ toInstrs stmts EIR.ParF stmts -> -- NOTE: this is just sequential evaluation for now traverse_ toInstrs stmts EIR.AssignF (toOperandWithContext -> (operand, eirLHS)) (toOperandWithContext -> (val, eirRHS)) -> do case eirLHS of EIR.Var varName -> do -- Assigning to a variable: evaluate the value, and add to the varMap. -- This allows for future lookups of a variable. value <- val addVarBinding varName value _ -> do -- NOTE: here we assume we are assigning to an operand (of a struct field) -- "operand" will contain a pointer, "val" will contain the actual value -- We need to store the result to the address the pointer is pointing to. address <- operand value <- loadIfNeeded val eirRHS store address 0 value EIR.FreeProgramF (toOperand -> programVar) -> do freeFn <- gets (extFree . externals) program <- programVar let memory = ptrcast i8 program Prelude.void $ call freeFn [memory] EIR.PrimOpF op (map (Relude.swap . toOperandWithContext) -> args') -> Prelude.void $ invokePrimOp op args' EIR.LoopF stmts -> loop $ traverse_ toInstrs stmts EIR.IfF (toOperand -> cond) (toInstrs -> body) -> do condition <- cond if' condition body EIR.JumpF lbl -> br (labelToName lbl) EIR.LabelF lbl -> -- NOTE: the label should be globally unique thanks to the RA -> EIR lowering pass emitBlockStart $ labelToName lbl EIR.ReturnF (toOperand -> value) -> ret =<< value _ -> panic "Unhandled pattern match case in 'instrToUnit' while lowering EIR to LLVM!" toOperand (Triple _ operand _) = operand toOperandWithContext (Triple eir operand _) = (operand, eir) toInstrs (Triple _ _ instrs) = instrs invokePrimOp :: Monad m => EIR.Op -> [(EIR, CodegenT m Operand)] -> CodegenT m Operand invokePrimOp op args' = do lookupPrimOp op >>= \case Left fn -> call fn =<< traverse snd args' Right compareInstr -> case args' of [(a, lhs), (b, rhs)] -> do valueA <- loadIfNeeded lhs a valueB <- loadIfNeeded rhs b compareInstr valueA valueB _ -> panic "Unexpected amount of arguments in 'invokePrimOp'!" -- lowerM is a recursion-scheme that behaves like a zygomorphism, but it is -- enhanced in the sense that both functions passed to the zygomorphism have -- access to the original subtree. -- -- NOTE: zygo effect is kind of abused here, since due to lazyness we can choose what -- we need to compile to LLVM: instructions either return "()" or an "Operand". -- para effect is needed since we need access to the original subtree in the -- assignment case to check if we are assigning to a variable or not, allowing -- us to easily transform an "expression-oriented" EIR to statement based LLVM IR. lowerM :: (EIRF (EIR, CodegenT m Operand) -> CodegenT m Operand) -> (EIRF (Triple EIR (CodegenT m Operand) (CodegenT m ())) -> CodegenT m ()) -> EIR -> CodegenT m () lowerM f = gcata (distribute f) where distribute :: Corecursive t => (Base t (t, b) -> b) -> (Base t (Triple t b a) -> Triple t b (Base t a)) distribute g m = let base_t_t = map tFst m base_t_tb = map (tFst &&& tSnd) m base_t_a = map tThd m in Triple (embed base_t_t) (g base_t_tb) base_t_a -- We need an Int somewhere later on during codegen. -- So we don't convert to a 'Suffix' at this point yet. type IntSuffix = Int type CacheT = StateT (Map Metadata (IntSuffix, Table)) runCacheT :: Monad m => CacheT m a -> m (Map Metadata IntSuffix, a) runCacheT m = do (a, s) <- runStateT m mempty pure (map fst s, a) codegenRuntime :: Externals -> Metadata -> CacheT (ConfigT (ModuleBuilderT IO)) Table codegenRuntime exts meta = gets (M.lookup meta) >>= \case Nothing -> do suffix <- gets length fns <- cgRuntime suffix modify $ M.insert meta (suffix, fns) pure fns Just (_, cachedFns) -> pure cachedFns where cgRuntime suffix = lift $ case meta of BTree meta' -> hoist (instantiate (show suffix) meta') $ BTree.codegen exts codegenDebugInfos :: MonadModuleBuilder m => Map Metadata Int -> m () codegenDebugInfos metaMapping = traverse_ (uncurry codegenDebugInfo) $ M.toList metaMapping where codegenDebugInfo meta i = let hash = getHash meta name = Name $ ("specialize_debug_info." <>) $ unHash hash in global name i32 (Int 32 $ toInteger i) codegenSymbolTable :: Externals -> ConfigT (ModuleBuilderT IO) (SymbolTable.SymbolTable, Symbol.Symbol) codegenSymbolTable exts = do symbol <- lift $ hoist intoIO $ Symbol.codegen exts let tySymbol = Symbol.tySymbol symbol symbolDestructor iterPtr = do _ <- call (Symbol.symbolDestroy symbol) [iterPtr] pass -- Only this vector does the cleanup of all the symbols, to prevent double frees vec <- hoist (instantiate "symbol" tySymbol) $ Vector.codegen exts (Just symbolDestructor) hashMap <- HashMap.codegen symbol exts symbolTable <- lift $ hoist intoIO $ SymbolTable.codegen tySymbol vec hashMap pure (symbolTable, symbol) where intoIO = pure . runIdentity getIndexFromMeta :: Metadata -> Index getIndexFromMeta = \case BTree meta -> Index $ BTree.index meta ================================================ FILE: lib/Eclair/Error.hs ================================================ {-# LANGUAGE MultiParamTypeClasses #-} {-# OPTIONS_GHC -fno-warn-orphans #-} module Eclair.Error ( EclairError(..) , Issue(..) , Location(..) , Pos(..) , posToSourcePos , locationToSourceSpan , handleErrorsCLI , errorToIssues , renderIssueMessage ) where import qualified Data.Map as M import qualified Text.Megaparsec as P import Data.List (partition) import Eclair.AST.Analysis import Eclair.TypeSystem import Eclair.Parser import Eclair.Common.Id import Eclair.Common.Location import Eclair.Souffle.IR import Prettyprinter import Prettyprinter.Render.Terminal import Error.Diagnose hiding (stderr) data EclairError = ParseErr FilePath ParsingError | TypeErr FilePath SpanMap [TypeError NodeId] | SemanticErr FilePath SpanMap (SemanticErrors NodeId) | ConversionErr FilePath SpanMap (ConversionError NodeId) -- TODO refactor using an error reporting monad? -- Handle errors when running in the CLI. handleErrorsCLI :: EclairError -> IO () handleErrorsCLI e = do useColorEnvVar <- fromMaybe "1" <$> lookupEnv "ECLAIR_USE_COLOR" let useColors = if useColorEnvVar /= "0" then Just UseColor else Nothing hPutDoc stderr =<< errToDoc useColors e where errToDoc useColor = \case ParseErr file' err' -> do case err' of FileNotFound {} -> pure $ "File not found: " <> pretty file' <> ".\n" ParsingError parseError -> do content <- decodeUtf8 <$> readFileBS file' let reports = map fst $ errReportsWithLocationsFromBundle "Failed to parse file" parseError diagnostic = foldl' addReport def reports diagnostic' = addFile diagnostic file' content in pure $ prettyError useColor diagnostic' TypeErr file' spanMap errs -> do content <- decodeUtf8 <$> readFileBS file' let errsWithPositions = getSourcePosCLI file' content spanMap <<$>> errs reports = map typeErrorToReport errsWithPositions diagnostic = foldl' addReport def reports diagnostic' = addFile diagnostic file' (toString content) in pure $ prettyError useColor diagnostic' SemanticErr file' spanMap semanticErr -> do content <- decodeUtf8 <$> readFileBS file' let semanticErrsWithPositions = map (getSourcePosCLI file' content spanMap) semanticErr reports = map fst $ semanticErrorsToReportsWithLocations semanticErrsWithPositions diagnostic = foldl' addReport def reports diagnostic' = addFile diagnostic file' (toString content) in pure $ prettyError useColor diagnostic' ConversionErr file' spanMap conversionErr -> do content <- decodeUtf8 <$> readFileBS file' let errWithPosition = map (getSourcePosCLI file' content spanMap) conversionErr report = conversionErrorToReport errWithPosition diagnostic = addReport def report diagnostic' = addFile diagnostic file' (toString content) pure $ prettyError useColor diagnostic' -- A single position in the code. 0-based! data Pos = Pos { posLine :: {-# UNPACK #-} !Word32 , posColumn :: {-# UNPACK #-} !Word32 } posToSourcePos :: Pos -> SourcePos posToSourcePos (Pos l c) = SourcePos (fromIntegral l) (fromIntegral c) -- Actual location in the code (a range). -- Contains the file, start and end of the position. data Location = Location { locationFile :: FilePath , locationStart :: {-# UNPACK #-} !Pos , locationEnd :: {-# UNPACK #-} !Pos } locationToSourceSpan :: Location -> SourceSpan locationToSourceSpan loc = SourceSpan (locationFile loc) posBegin posEnd where posBegin = posToSourcePos $ locationStart loc posEnd = posToSourcePos $ locationEnd loc -- A helper type for referring to an issue at a location. data Issue = Issue { issueMessage :: Report Text , issueLocation :: Location } renderIssueMessage :: Issue -> Text renderIssueMessage issue = -- TODO generate a diagnostic per line of the report (like in Rust). let report = issueMessage issue in getReportTitle report getReportTitle :: Report Text -> Text getReportTitle = \case Err _ summary _ _ -> summary Warn _ summary _ _ -> summary -- A helper function that can be used from the LSP. Splits all errors into -- separate issues for most flexibility and fine-grained reporting. errorToIssues :: (FilePath -> IO Text) -> EclairError -> IO [Issue] errorToIssues readTextFile = \case ParseErr file' err' -> do case err' of FileNotFound {} -> do let errMessage = "File not found: " <> toText file' report = Err Nothing errMessage [] [] noLocationInfo = Location file' (Pos 0 0) (Pos 0 0) pure $ one $ Issue report noLocationInfo ParsingError parseError -> do let reportsWithLocs = errReportsWithLocationsFromBundle "Failed to parse file" parseError pure $ map (uncurry Issue) reportsWithLocs TypeErr file' spanMap errs -> do content <- readTextFile file' let errsWithPositions = getSourcePos file' content spanMap <<$>> errs toLocation = positionToLocation . mainErrorPosition pure $ map (uncurry Issue . (typeErrorToReport &&& toLocation)) errsWithPositions SemanticErr file' spanMap semanticErrs -> do content <- readTextFile file' let semanticErrsWithPositions = map (getSourcePos file' content spanMap) semanticErrs reportsWithLocs = semanticErrorsToReportsWithLocations semanticErrsWithPositions pure $ map (uncurry Issue) reportsWithLocs ConversionErr file' spanMap conversionErr -> do content <- readTextFile file' let errWithPosition = map (getSourcePos file' content spanMap) conversionErr report = conversionErrorToReport errWithPosition loc = positionToLocation $ mainErrorPosition errWithPosition pure $ one $ Issue report loc typeErrorToReport :: TypeError Position -> Report Text typeErrorToReport e = case e of UnknownConstraint _ factName -> let title = "Missing type definition" markers = [(mainErrorPosition e, This $ "Could not find a type definition for '" <> unId factName <> "'.")] hints = [ Hint $ "Add a type definition for '" <> unId factName <> "'." , Hint $ "Add an extern definition for '" <> unId factName <> "'." ] in Err Nothing title markers hints UnknownFunction _ factName -> let title = "Missing type definition" markers = [(mainErrorPosition e, This $ "Could not find a type definition for '" <> unId factName <> "'.")] hints = [ Hint $ "Add an extern definition for '" <> unId factName <> "'." ] in Err Nothing title markers hints ArgCountMismatch factName (expectedSrcLoc, expectedCount) (actualSrcLoc, actualCount) -> let title = "Found an unexpected amount of arguments for '" <> unId factName <> "'" markers = [ (actualSrcLoc, This $ show actualCount <> pluralize actualCount " argument is" " arguments are" <> " provided here.") , (expectedSrcLoc, Where $ "'" <> unId factName <> "' is defined with " <> show expectedCount <> " " <> pluralize expectedCount "argument" "arguments" <> ".") ] hints = [Hint $ "You can solve this by passing exactly " <> show expectedCount <> " " <> pluralize expectedCount "argument" "arguments" <> " to '" <> unId factName <> "'."] in Err Nothing title markers hints TypeMismatch _ actualTy expectedTy ctx -> Err Nothing title markers hints where title = "Type mismatch" lastMarker = (mainErrorPosition e, This $ show (length ctx + 1) <> ") Expected this to be of type " <> renderType expectedTy <> "," <> " but it actually has type " <> renderType actualTy <> ".") markers = zipWith renderDeduction markerTypes (toList ctx) ++ [lastMarker] markerTypes = map (, Where) [1..] hints = [] -- Can we even give a meaningful error here? Look in type env? (if a var is used) UnificationFailure _ _ ctx -> Err Nothing title markers hints where title = "Type unification failure" markerTypes = markersForTypeError ctx markers = zipWith renderDeduction markerTypes (toList ctx) hints = [] -- What can we even give as a hint here? That it is a logical error? HoleFound _ ctx holeTy typeEnv -> Err Nothing title markers hints where title = "Found hole" markerTypes = map (, Where) [1..] deductions = zipWith renderDeduction markerTypes (toList ctx) markers = deductions <> [(mainErrorPosition e, This $ show (length deductions + 1) <> ") Found hole with type " <> renderType holeTy <> ".")] typeEntries = typeEnv & M.mapWithKey (\var ty -> (ty, renderBinding var ty)) & toList (candidates, others) = partition (\(entryTy, _) -> entryTy == holeTy) typeEntries hints = map (Hint . (("Possible candidate: " <>) . snd)) candidates <> if null others then [] else [Hint "Other variables include:"] <> map (Hint . snd) others renderBinding var ty = unId var <> " :: " <> renderType ty UnexpectedFunctionType _ defPos -> let title = "Invalid use of function" markers = [ (mainErrorPosition e, This "Expected a constraint here.") , (defPos, Where "Previously defined as a function here.") ] hints = [ Hint "Maybe you meant to declare this an external constraint instead?" , Hint "Remove the invalid function." ] in Err Nothing title markers hints UnexpectedConstraintType _ defPos -> let title = "Invalid use of constraint" markers = [ (mainErrorPosition e, This "Expected a function.") , (defPos, Where "Previously defined as a constraint here.") ] hints = [ Hint "Maybe you meant to declare this as a function instead?" , Hint "Remove the invalid constraint." ] in Err Nothing title markers hints where markersForTypeError ctx = zip [1..] $ replicate (length ctx - 1) Where ++ [This] renderDeduction :: (Int, Text -> Marker a) -> Context Position -> (Position, Marker a) renderDeduction (i, mkMarker) = \case WhileChecking srcLoc -> (srcLoc, mkMarker $ show i <> ") While checking the type of this..") WhileInferring srcLoc -> (srcLoc, mkMarker $ show i <> ") While inferring the type of this..") WhileUnifying srcLoc -> (srcLoc, mkMarker $ show i <> ") While unifying these types..") conversionErrorToReport :: ConversionError Position -> Report Text conversionErrorToReport e = case e of HoleNotSupported _ -> let title = "Unsupported feature in Souffle" markers = [(mainErrorPosition e, This "Souffle has no support for holes.")] hints = [Hint "Replace the hole with a variable or literal."] in Err Nothing title markers hints UnsupportedCase _ -> let title = "Unsupported feature in Souffle" markers = [(mainErrorPosition e, This "Eclair can't transpile extern definitions yet.")] hints = [Hint "Please open a github issue asking for this feature."] in Err Nothing title markers hints UnsupportedType _ ty -> let title = "Unsupported type in Souffle" markers = [(mainErrorPosition e, This $ "Souffle has no support for the " <> renderType ty <> " type.")] hints = [] in Err Nothing title markers hints ungroundedVarToReport :: UngroundedVar Position -> Report Text ungroundedVarToReport e@(UngroundedVar srcLocRule _ var) = let title = "Ungrounded variable" srcLocVar = mainErrorPosition e markers = [ (srcLocVar, This $ "The variable '" <> unId var <> "' is ungrounded, meaning it is not directly bound as an argument to a relation.") , (srcLocRule, Where $ "This contains no clauses that refer to '" <> unId var <> "'.") ] hints = [Hint $ "Use the variable '" <> unId var <> "' as an argument in a relation."] in Err Nothing title markers hints wildcardInFactToReport :: WildcardInFact Position -> Report Text wildcardInFactToReport e@(WildcardInFact srcLocFact _ _pos) = let title = "Wildcard in top level fact" markers = [ (mainErrorPosition e, This "Wildcard found.") , (srcLocFact, Where "A top level fact only supports constants.\nVariables or wildcards are not allowed.") ] hints = [Hint "Replace the wildcard with a constant."] in Err Nothing title markers hints wildcardInExternToReport :: WildcardInExtern Position -> Report Text wildcardInExternToReport e@(WildcardInExtern atomLoc _ _) = Err Nothing title markers hints where title = "Wildcard in externally defined atom" markers = [ (mainErrorPosition e, This "Wildcard found.") , (atomLoc, Where "An external atom only supports constants or grounded variables.") ] hints = [Hint "Replace the wildcard with a constant or grounded variable."] unconstrainedVarToReport :: UnconstrainedRuleVar Position -> Report Text unconstrainedVarToReport e@(UnconstrainedRuleVar ruleLoc _ varName) = Err Nothing title markers hints where title = "Found unconstrained variable" markers = [ (mainErrorPosition e, This $ "The variable '" <> unId varName <> "' only occurs once.") , (ruleLoc, Where $ "This rule contains no other references to '" <> unId varName <> "'.") ] hints = [ Hint "Replace the variable with a wildcard ('_')." , Hint "Use the variable in another rule clause." ] wildcardInRuleHeadToReport :: WildcardInRuleHead Position -> Report Text wildcardInRuleHeadToReport e@(WildcardInRuleHead srcLocRule _ _pos) = let title = "Wildcard in 'head' of rule" markers = [ (mainErrorPosition e, This "Wildcard found.") , (srcLocRule, Where "Only constants and variables are allowed in the head of a rule.\nWildcards are not allowed.") ] hints = [Hint "Replace the wildcard with a constant or a variable."] in Err Nothing title markers hints wildcardInConstraintToReport :: WildcardInConstraint Position -> Report Text wildcardInConstraintToReport e@(WildcardInConstraint srcLocConstraint _) = let title = "Found wildcard in constraint" markers = [ (mainErrorPosition e, This "Wildcard found.") , (srcLocConstraint, Where "Only constants and variables are allowed in a constraint.") ] hints = [ Hint "This statement can be removed since it has no effect." , Hint "Replace the wildcard with a variable." ] in Err Nothing title markers hints wildcardInBinOpToReport :: WildcardInBinOp Position -> Report Text wildcardInBinOpToReport e@(WildcardInBinOp srcLocBinOp _) = let title = "Found wildcard in binary operation" markers = [ (mainErrorPosition e, This "Wildcard found.") , (srcLocBinOp, Where "Only constants and variables are allowed in a binary operation.") ] hints = [Hint "Replace the wildcard with a variable or literal."] in Err Nothing title markers hints deadInternalRelationToReport :: DeadInternalRelation Position -> Report Text deadInternalRelationToReport e@(DeadInternalRelation _ r) = let title = "Dead internal relation" markers = [(mainErrorPosition e, This $ "The internal rule '" <> unId r <> "' has no facts or rules defined and will never produce results.")] hints = [ Hint "This might indicate a logic error in your code." , Hint "Remove this rule if it is no longer needed." , Hint "Add 'input' to the declaration to indicate this rule is an input." ] in Err Nothing title markers hints noOutputRelationsToReport :: NoOutputRelation Position -> Report Text noOutputRelationsToReport e@(NoOutputRelation _) = let title = "No output relations found" markers = [(mainErrorPosition e, This "This module does not produce any results")] hints = [ Hint "Add an 'output' qualifier to one of the relations defined in this module." ] in Err Nothing title markers hints conflictingDefinitionsToReport :: ConflictingDefinitionGroup Position -> Report Text conflictingDefinitionsToReport e@(ConflictingDefinitionGroup name locs) = Err Nothing title (mainMarker:markers) hints where title = "Multiple definitions for '" <> unId name <> "'" mainMarker = (mainErrorPosition e, This $ "'" <> unId name <> "' is originally defined here.") markers = tail locs & toList & map (, Where $ "'" <> unId name <> "' is re-defined here.") hints = [Hint $ "You can solve this by removing the duplicate definitions for '" <> unId name <> "'."] externUsedAsFactToReport :: ExternUsedAsFact Position -> Report Text externUsedAsFactToReport e@(ExternUsedAsFact _ externLoc name) = Err Nothing title markers hints where title = "Extern definition used as top level fact" markers = [ (mainErrorPosition e, This $ "'" <> unId name <> "' is used as a fact here, which is not allowed for extern definitions.") , (externLoc, Where $ "'" <> unId name <> "' previously defined here as external.") ] hints = [ Hint $ "Convert '" <> unId name <> "' to a relation." , Hint "Remove the top level fact." ] externUsedAsRuleToReport :: ExternUsedAsRule Position -> Report Text externUsedAsRuleToReport e@(ExternUsedAsRule _ externLoc name) = Err Nothing title markers hints where title = "Extern definition used in rule head" markers = [ (mainErrorPosition e, This $ "'" <> unId name <> "' is used as a rule head here, which is not allowed for extern definitions.") , (externLoc, Where $ "'" <> unId name <> "' previously defined here as external.") ] hints = [ Hint $ "Convert '" <> unId name <> "' to a relation." , Hint "Remove the rule." ] cyclicNegationToReport :: CyclicNegation Position -> Report Text cyclicNegationToReport e = Err Nothing title markers hints where title = "Negation used in recursive set of rules" markers = [ (mainErrorPosition e, This "This negation is used in a set of rules that is recursive, which is not allowed.") ] hints = [ Hint "Restructure the program so the negation does not occur in the set of recursive rules." , Hint "Remove the negation entirely." ] -- NOTE: pattern match is done this way to keep track of additional errors that need to be reported {-# ANN semanticErrorsToReportsWithLocations ("HLint: ignore Use record patterns" :: String) #-} semanticErrorsToReportsWithLocations :: SemanticErrors Position -> [(Report Text, Location)] semanticErrorsToReportsWithLocations e@(SemanticErrors _ _ _ _ _ _ _ _ _ _ _ _ _) = concat [ ungroundedVarReports , wildcardInFactReports , wildcardInRuleHeadReports , wildcardInConstraintReports , wildcardInBinOpReports , wildcardInExternReports , unconstrainedVarReports , deadInternalRelationReports , noOutputReports , conflictingDefinitionReports , externUsedAsFactReports , externUsedAsRuleReports , cyclicNegationReports ] where getReportsWithLocationsFor :: HasMainErrorPosition a => (SemanticErrors Position -> Container a) -> (a -> Report Text) -> [(Report Text, Location)] getReportsWithLocationsFor f g = map (g &&& positionToLocation . mainErrorPosition) (f e) ungroundedVarReports = getReportsWithLocationsFor ungroundedVars ungroundedVarToReport wildcardInFactReports = getReportsWithLocationsFor wildcardsInFacts wildcardInFactToReport wildcardInRuleHeadReports = getReportsWithLocationsFor wildcardsInRuleHeads wildcardInRuleHeadToReport wildcardInConstraintReports = getReportsWithLocationsFor wildcardsInConstraints wildcardInConstraintToReport wildcardInBinOpReports = getReportsWithLocationsFor wildcardsInBinOps wildcardInBinOpToReport wildcardInExternReports = getReportsWithLocationsFor wildcardsInExternAtoms wildcardInExternToReport unconstrainedVarReports = getReportsWithLocationsFor unconstrainedVars unconstrainedVarToReport deadInternalRelationReports = getReportsWithLocationsFor deadInternalRelations deadInternalRelationToReport noOutputReports = getReportsWithLocationsFor noOutputRelations noOutputRelationsToReport conflictingDefinitionReports = getReportsWithLocationsFor conflictingDefinitions conflictingDefinitionsToReport externUsedAsFactReports = getReportsWithLocationsFor externsUsedAsFact externUsedAsFactToReport externUsedAsRuleReports = getReportsWithLocationsFor externsUsedAsRule externUsedAsRuleToReport cyclicNegationReports = getReportsWithLocationsFor cyclicNegations cyclicNegationToReport pluralize :: Int -> Text -> Text -> Text pluralize count singular plural' = if count == 1 then singular else plural' getSourcePos :: FilePath -> Text -> SpanMap -> NodeId -> Position getSourcePos file' fileContent spanMap nodeId = let span' = lookupSpan spanMap nodeId in sourceSpanToPosition $ spanToSourceSpan file' fileContent span' -- Diagnose is 1-based, Eclair 0-based -- TODO get rid of this hack (and diagnose alltogether) getSourcePosCLI :: FilePath -> Text -> SpanMap -> NodeId -> Position getSourcePosCLI file' fileContent spanMap nodeId = addOffset $ getSourcePos file' fileContent spanMap nodeId where addOffset pos = Position (both (+1) $ begin pos) (both (+1) $ end pos) (file pos) sourceSpanToPosition :: SourceSpan -> Position sourceSpanToPosition sourceSpan = let beginPos' = sourceSpanBegin sourceSpan endPos' = sourceSpanEnd sourceSpan start = (sourcePosLine beginPos', sourcePosColumn beginPos') end' = (sourcePosLine endPos', sourcePosColumn endPos') in Position start end' (sourceSpanFile sourceSpan) positionToLocation :: Position -> Location positionToLocation position = let locStart = uncurry Pos (both fromIntegral $ begin position) locEnd = uncurry Pos (both fromIntegral $ end position) in Location (file position) locStart locEnd both :: (a -> b) -> (a, a) -> (b, b) both f = bimap f f renderType :: Type -> Text renderType ty = let userFacingType = case ty of U32 -> "u32" Str -> "string" TUnknown x -> "t" <> show x in "'" <> userFacingType <> "'" class HasMainErrorPosition a where mainErrorPosition :: a -> Position instance HasMainErrorPosition (NoOutputRelation Position) where mainErrorPosition (NoOutputRelation pos) = startOfFile $ file pos where startOfFile = Position (1, 1) (1, 2) -- Diagnose is 1-based! instance HasMainErrorPosition (DeadInternalRelation Position) where mainErrorPosition (DeadInternalRelation pos _) = pos instance HasMainErrorPosition (WildcardInConstraint Position) where mainErrorPosition (WildcardInConstraint _ pos) = pos instance HasMainErrorPosition (WildcardInBinOp Position) where mainErrorPosition (WildcardInBinOp _ pos) = pos instance HasMainErrorPosition (WildcardInFact Position) where mainErrorPosition (WildcardInFact _ factArgPos _) = factArgPos instance HasMainErrorPosition (WildcardInExtern Position) where mainErrorPosition (WildcardInExtern _ externArgPos _) = externArgPos instance HasMainErrorPosition (UnconstrainedRuleVar Position) where mainErrorPosition (UnconstrainedRuleVar _ varPos _) = varPos instance HasMainErrorPosition (UngroundedVar Position) where mainErrorPosition (UngroundedVar _ varPos _) = varPos instance HasMainErrorPosition (WildcardInRuleHead Position) where mainErrorPosition (WildcardInRuleHead _ ruleArgPos _) = ruleArgPos instance HasMainErrorPosition (ConflictingDefinitionGroup Position) where mainErrorPosition (ConflictingDefinitionGroup _ positions) = head positions instance HasMainErrorPosition (ExternUsedAsFact Position) where mainErrorPosition (ExternUsedAsFact pos _ _) = pos instance HasMainErrorPosition (ExternUsedAsRule Position) where mainErrorPosition (ExternUsedAsRule pos _ _) = pos instance HasMainErrorPosition (CyclicNegation Position) where mainErrorPosition (CyclicNegation pos) = pos instance HasMainErrorPosition (TypeError Position) where mainErrorPosition = \case UnknownConstraint pos _ -> pos UnknownFunction pos _ -> pos ArgCountMismatch _ _ (pos, _) -> pos TypeMismatch pos _ _ _ -> pos UnificationFailure _ _ ctx -> getContextLocation (last ctx) HoleFound pos _ _ _ -> pos UnexpectedFunctionType pos _ -> pos UnexpectedConstraintType pos _ -> pos instance HasMainErrorPosition (ConversionError Position) where mainErrorPosition = \case HoleNotSupported pos -> pos UnsupportedType pos _ -> pos UnsupportedCase pos -> pos -- Helper function to transform a Megaparsec error bundle into multiple reports -- Extracted from the Diagnose library, and simplified for usage in Eclair. errReportsWithLocationsFromBundle :: Text -> P.ParseErrorBundle Text CustomParseErr -> [(Report Text, Location)] errReportsWithLocationsFromBundle msg errBundle = toList (addLabelAndLocation <$> P.bundleErrors errBundle) where addLabelAndLocation e = let (_, pos) = P.reachOffset (P.errorOffset e) (P.bundlePosState errBundle) source = fromSourcePos (P.pstateSourcePos pos) msgs = lines $ toText (P.parseErrorTextPretty e) markers = case msgs of [m] -> [(source, This m)] [m1, m2] -> [(source, This m1), (source, Where m2)] _ -> [(source, This "<>")] report = Err Nothing msg markers mempty in (report, positionToLocation source) fromSourcePos sourcePos = let begin' = both (fromIntegral . P.unPos) (P.sourceLine sourcePos, P.sourceColumn sourcePos) end' = second (+ 1) begin' in Position begin' end' (P.sourceName sourcePos) data UseColor = UseColor deriving Eq prettyError :: Maybe UseColor -> Diagnostic Text -> Doc AnsiStyle prettyError useColor = applyStyle . prettyDiagnostic useUnicode tabSpaces where applyStyle = if useColor == Just UseColor then style else unAnnotate useUnicode = True tabSpaces = 2 style :: Style style = reAnnotate style' where style' = \case ThisColor isError -> colorDull $ if isError then Red else Yellow MaybeColor -> color Magenta WhereColor -> colorDull Blue HintColor -> colorDull Cyan FileColor -> bold <> colorDull Green RuleColor -> bold <> color Black KindColor isError -> bold <> style' (ThisColor isError) NoLineColor -> bold <> colorDull Magenta MarkerStyle st -> bold <> style' st CodeStyle -> color White ================================================ FILE: lib/Eclair/JSON.hs ================================================ {-# LANGUAGE LinearTypes, MagicHash #-} -- | Helper module encoding Haskell values as JSON. -- Only a limited set of functionality is provided. -- (Needed since hermes-json only does decoding of JSON.) module Eclair.JSON ( JSON(..) , encodeJSON ) where import Data.Text.Builder.Linear.Buffer import GHC.Prim (Addr#) data JSON = Null | Boolean Bool | Number Int | String Text | Object [(Text, JSON)] | Array [JSON] encodeJSON :: JSON -> Text encodeJSON json = runBuffer (`toJSON'` json) where toJSON' :: Buffer %1 -> JSON -> Buffer toJSON' buf = \case Null -> buf |># "null"# Boolean b -> buf |># if b then "true"# else "false"# Number x -> buf |> show x String s -> dquotes buf (|> s) Object pairs -> braces buf (\buf' -> sepBy ","# buf' pairs (\buf'' (k, v) -> (dquotes buf'' (|> k) |>. ':') `toJSON'` v ) ) Array elems -> brackets buf (\buf' -> sepBy ","# buf' elems toJSON') type BufferDecorator = Buffer %1 -> (Buffer %1 -> Buffer) -> Buffer brackets :: BufferDecorator brackets = betweenChars '[' ']' {-# INLINABLE brackets #-} braces :: BufferDecorator braces = betweenChars '{' '}' {-# INLINABLE braces #-} dquotes :: BufferDecorator dquotes = betweenChars '"' '"' {-# INLINABLE dquotes #-} betweenChars :: Char -> Char -> BufferDecorator betweenChars begin end buf f = f (buf |>. begin) |>. end {-# INLINABLE betweenChars #-} sepBy :: forall a. Addr# -> Buffer %1 -> [a] -> (Buffer %1 -> a -> Buffer) -> Buffer sepBy separator buf as f = foldlIntoBuffer combine buf parts where parts = intersperse Nothing $ map Just as combine :: Buffer %1 -> Maybe a -> Buffer combine buf' = \case Nothing -> buf' |># separator Just a -> f buf' a {-# INLINABLE sepBy #-} ================================================ FILE: lib/Eclair/LLVM/Allocator/Arena.hs ================================================ {-# LANGUAGE GADTs #-} module Eclair.LLVM.Allocator.Arena ( Arena , allocator ) where import Prelude hiding (void) import Eclair.LLVM.Allocator.Common import Eclair.LLVM.Codegen data Arena a -- TODO support multiple implementations, switch based on an enum: -- - variant that only allocates, never frees until destroyed -- - variant that can free sometimes, when allocation counter hits 0 -- - bump up vs bump down (each has it's own benefits): https://fitzgeraldnick.com/2019/11/01/always-bump-downwards.html allocator :: Int -> Allocator a -> Allocator (Arena a) allocator arenaSize inner = Allocator { aType = mkType , aInit = arenaInit arenaSize , aDestroy = arenaDestroy arenaSize , aAlloc = arenaAlloc , aFree = arenaFree , aKind = Node , aInner = inner } mkType :: Text -> Type -> AllocCodegenM Type mkType prefix baseTy = typedef (Name $ prefix <> "arena_allocator") Off [ baseTy -- inner allocator , ptr void -- start , ptr void -- current ] innerPtr :: Operand -> AllocIRCodegenM Operand innerPtr = addr innerAllocOf arenaInit :: Int -> VTable -> InitFn arenaInit arenaSize fns alloc = do inner <- innerPtr alloc vtInit fns inner let size = int32 $ fromIntegral arenaSize memory <- vtAllocate fns inner size didAllocationFail <- memory `eq` nullPtr void if' didAllocationFail $ do assign startPtrOf alloc (nullPtr void) assign currentPtrOf alloc (nullPtr void) retVoid memoryEnd <- gep memory [size] assign startPtrOf alloc memory -- NOTE: we start at end and bump down, this can be implemented faster -- https://fitzgeraldnick.com/2019/11/01/always-bump-downwards.html assign currentPtrOf alloc memoryEnd arenaDestroy :: Int -> VTable -> DestroyFn arenaDestroy arenaSize fns alloc = do startPtr <- deref startPtrOf alloc isNull <- startPtr `eq` nullPtr void if' isNull $ do vtDestroy fns =<< innerPtr alloc retVoid inner <- innerPtr alloc let numBytes = int32 $ fromIntegral arenaSize vtDeallocate fns inner startPtr numBytes vtDestroy fns =<< innerPtr alloc assign startPtrOf alloc (nullPtr void) assign currentPtrOf alloc (nullPtr void) arenaAlloc :: VTable -> AllocateFn arenaAlloc _ alloc numBytes = do startPtr <- deref startPtrOf alloc currentPtr <- deref currentPtrOf alloc numBytesNegated <- sub (int32 0) numBytes newPtr <- gep currentPtr [numBytesNegated] newAddr <- ptrtoint newPtr i64 startAddr <- ptrtoint startPtr i64 noSpaceLeft <- newAddr `ult` startAddr if' noSpaceLeft $ do pure $ nullPtr void assign currentPtrOf alloc newPtr pure newPtr -- Arena can't free individual pieces of memory, only everything at once arenaFree :: VTable -> DeallocateFn arenaFree _ _ _ _ = pass data Paths = Alloc | InnerAlloc | StartPtr | CurrentPtr innerAllocOf :: Path Alloc InnerAlloc innerAllocOf = mkPath [int32 0] startPtrOf :: Path Alloc StartPtr startPtrOf = mkPath [int32 1] currentPtrOf :: Path Alloc CurrentPtr currentPtrOf = mkPath [int32 2] ================================================ FILE: lib/Eclair/LLVM/Allocator/Common.hs ================================================ {-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-} {-# LANGUAGE GADTs #-} module Eclair.LLVM.Allocator.Common ( Allocator(..) , Blueprint(..) , AllocatorKind(..) , AllocatorKindTag(..) , VTable(..) , None(..) , AllocCodegenM , AllocIRCodegenM , InitFn , DestroyFn , AllocateFn , DeallocateFn , cgAlloc , mkBaseAllocator , module Eclair.LLVM.Externals ) where import Prelude hiding (void) import qualified Data.Kind as K import Eclair.LLVM.Codegen import Eclair.LLVM.Externals type AllocCodegenM = StateT Externals ModuleBuilder type AllocIRCodegenM = IRBuilderT AllocCodegenM type InitFn = Operand -- allocator -> AllocIRCodegenM () type DestroyFn = Operand -- allocator -> AllocIRCodegenM () type AllocateFn = Operand -- allocator -> Operand -- length (in bytes) -> AllocIRCodegenM Operand type DeallocateFn = Operand -- allocator -> Operand -- ptr -> Operand -- length -> AllocIRCodegenM () -- TODO add resize everywhere, just like alloc and free -- type ResizeFn = Operand -- ptr -- -> Operand -- length -- -> AllocIRCodegenM () -- Helper type that for performing actions on an allocator. -- This allows a composed allocator to call inner functionality freely. data VTable = VTable { vtInit :: InitFn , vtDestroy :: DestroyFn , vtAllocate :: AllocateFn , vtDeallocate :: DeallocateFn } data AllocatorKindTag = IsRoot | IsNode data AllocatorKind (k :: AllocatorKindTag) where Root :: AllocatorKind 'IsRoot Node :: AllocatorKind 'IsNode type family CreateTypeFn (k :: AllocatorKindTag) where CreateTypeFn 'IsRoot = Text -> AllocCodegenM Type CreateTypeFn 'IsNode = Text -> Type -> AllocCodegenM Type type family AllocatorFn (k :: AllocatorKindTag) (ty :: K.Type) where AllocatorFn 'IsRoot f = f AllocatorFn 'IsNode f = VTable -> f data None a = None type family BackingAllocator (k :: AllocatorKindTag) :: (K.Type -> K.Type) where BackingAllocator 'IsRoot = None BackingAllocator 'IsNode = Allocator -- First we build up the allocator info in a data-structure. -- "Stateless" allocators carry no state, and call direct functions provided by the OS (mmap, malloc, ...) -- "Stateful" allocators do have state, and can further enhance the behavior of the underlying allocator. data Allocator repr where Allocator :: { aType :: CreateTypeFn k , aInit :: AllocatorFn k InitFn , aDestroy :: AllocatorFn k DestroyFn , aAlloc :: AllocatorFn k AllocateFn , aFree :: AllocatorFn k DeallocateFn , aInner :: BackingAllocator k inner , aKind :: AllocatorKind k } -> Allocator repr -- Helper type for keeping references to generated allocator code data Blueprint repr = Blueprint { bpType :: Type , bpInitFn :: Operand , bpDestroyFn :: Operand , bpAllocateFn :: Operand , bpDeallocateFn :: Operand } -- Helper type during codegen process. data Gen = Gen { generateTy :: Text -> AllocCodegenM Type , generateInit :: InitFn , generateDestroy :: DestroyFn , generateAlloc :: AllocateFn , generateFree :: DeallocateFn } -- This function does the actual code generation of the allocator. cgAlloc :: Text -> Allocator repr -> AllocCodegenM (Blueprint repr) cgAlloc prefix allocator = do let g = cgHelper allocator allocatorTy <- generateTy g prefix allocFn <- function (Name $ prefix <> "_alloc") [(ptr allocatorTy, "allocator"), (i32, "size")] (ptr void) $ \[alloc, size] -> do ret =<< generateAlloc g alloc size freeFn <- function (Name $ prefix <> "_free") [(ptr allocatorTy, "allocator"), (ptr i8, "memory"), (i32, "size")] void $ \[alloc, memory, size] -> do generateFree g alloc memory size initFn <- function (Name $ prefix <> "_init") [(ptr allocatorTy, "allocator")] void $ \[alloc] -> do generateInit g alloc destroyFn <- function (Name $ prefix <> "_destroy") [(ptr allocatorTy, "allocator")] void $ \[alloc] -> do generateDestroy g alloc pure $ Blueprint allocatorTy initFn destroyFn allocFn freeFn where -- Recursively generates the code cgHelper :: Allocator repr -> Gen cgHelper = \case Allocator genTy genInit genDestroy genAlloc genFree innerAlloc kind -> case kind of Root -> Gen { generateTy = genTy , generateInit = genInit , generateDestroy = genDestroy , generateAlloc = genAlloc , generateFree = genFree } Node -> let generated = cgHelper innerAlloc vtable = VTable { vtInit = generateInit generated , vtDestroy = generateDestroy generated , vtAllocate = generateAlloc generated , vtDeallocate = generateFree generated } in Gen { generateTy = \namePrefix -> do -- First generate inner type, then outer type. ty <- generateTy generated namePrefix genTy namePrefix ty -- Pass generated inner code as function, then generate outer code based on that. , generateInit = genInit vtable , generateDestroy = genDestroy vtable , generateAlloc = genAlloc vtable , generateFree = genFree vtable } mkBaseAllocator :: (Text -> AllocCodegenM Type) -> (Operand -> AllocIRCodegenM Operand) -> (Operand -> Operand -> AllocIRCodegenM ()) -> Allocator repr mkBaseAllocator mkType allocFn freeFn = Allocator { aType = mkType , aInit = const pass , aDestroy = const pass , aAlloc = const allocFn , aFree = const freeFn , aKind = Root , aInner = None } ================================================ FILE: lib/Eclair/LLVM/Allocator/Malloc.hs ================================================ module Eclair.LLVM.Allocator.Malloc ( Malloc , allocator ) where import Eclair.LLVM.Allocator.Common import Eclair.LLVM.Codegen data Malloc allocator :: Allocator Malloc allocator = mkBaseAllocator mkType allocFn freeFn mkType :: Text -> AllocCodegenM Type mkType prefix = typedef (Name $ prefix <> "_mallocator") Off [] allocFn :: Operand -> AllocIRCodegenM Operand allocFn numBytes = do malloc <- gets extMalloc call malloc [numBytes] freeFn :: Operand -> Operand -> AllocIRCodegenM () freeFn memory _ = do free <- gets extFree _ <- call free [memory] pass ================================================ FILE: lib/Eclair/LLVM/Allocator/Page.hs ================================================ module Eclair.LLVM.Allocator.Page ( Page , allocator , roundToNearestPageSize -- for testing only ) where import Eclair.LLVM.Allocator.Common import Eclair.LLVM.Codegen data Page -- TODO: parametrize on page size (add argument, pass to helper functions) allocator :: Allocator Page allocator = mkBaseAllocator mkType allocatePages freePages mkType :: Text -> AllocCodegenM Type mkType prefix = typedef (Name $ prefix <> "page_allocator") Off [] pageSize :: Operand pageSize = int32 4096 allocatePages :: Operand -> AllocIRCodegenM Operand allocatePages numBytes = do mmap <- gets extMmap numBytes' <- flip zext i64 =<< roundToNearestPageSize numBytes let hint = nullPtr VoidType protRead = int32 1 protWrite = int32 2 mapPrivate = int32 2 mapAnonymous = int32 32 noFd = int32 (-1) offset = int32 0 prot <- protRead `or` protWrite -- allow both reads and writes flags <- mapPrivate `or` mapAnonymous -- anonymous private mapping, can be used as RAM call mmap [hint, numBytes', prot, flags, noFd, offset] roundToNearestPageSize :: Operand -> AllocIRCodegenM Operand roundToNearestPageSize numBytes = do x1 <- numBytes `add` pageSize x2 <- x1 `sub` int32 1 y <- int32 0 `sub` pageSize x2 `and` y freePages :: Operand -> Operand -> AllocIRCodegenM () freePages memory len = do munmap <- gets extMunmap len' <- zext len i64 _ <- call munmap [memory, len'] pass ================================================ FILE: lib/Eclair/LLVM/BTree/Bounds.hs ================================================ {-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-} module Eclair.LLVM.BTree.Bounds ( mkLinearSearchLowerBound , mkLinearSearchUpperBound , mkBtreeLowerBound , mkBtreeUpperBound ) where import Prelude hiding (void) import Eclair.LLVM.BTree.Types mkLinearSearchLowerBound :: Operand -> ModuleCodegen Operand mkLinearSearchLowerBound compareValues = do value <- typeOf Value let args = [(ptr value, "val"), (ptr value, "current"), (ptr value, "end")] function "eclair_btree_linear_search_lower_bound" args (ptr value) $ \[val, curr, end] -> mdo -- Finds an iterator to first element not less than given value. currentPtr <- allocate (ptr value) curr let loopCondition = do current <- load currentPtr 0 current `ne` end loopWhile loopCondition $ mdo current <- load currentPtr 0 result <- call compareValues [current, val] isGtOrEqThan <- result `ne` int8 (-1) if' isGtOrEqThan $ ret current current' <- gep current [int32 1] store currentPtr 0 current' ret end mkLinearSearchUpperBound :: Operand -> ModuleCodegen Operand mkLinearSearchUpperBound compareValues = do value <- typeOf Value let args = [(ptr value, "val"), (ptr value, "current"), (ptr value, "end")] function "eclair_btree_linear_search_upper_bound" args (ptr value) $ \[val, curr, end] -> mdo -- Finds an iterator to first element that is greater than given value. currentPtr <- allocate (ptr value) curr let loopCondition = do current <- load currentPtr 0 current `ne` end loopWhile loopCondition $ mdo current <- load currentPtr 0 result <- call compareValues [current, val] isGreaterThan <- result `eq` int8 1 if' isGreaterThan $ ret current current' <- gep current [int32 1] store currentPtr 0 current' ret end mkBtreeLowerBound :: Operand -> Operand -> Operand -> Operand -> Operand -> ModuleCodegen Operand mkBtreeLowerBound isEmptyTree iterInit iterInitEnd searchLowerBound compareValues = do tree <- typeOf BTree iter <- typeOf Iterator node <- typeOf Node innerNode <- typeOf InnerNode value <- typeOf Value valSize <- asks (valueSize . typeSizes) let args = [(ptr tree, "tree"), (ptr value, "val"), (ptr iter, "result")] function "eclair_btree_lower_bound" args void $ \[t, val, result] -> mdo isEmpty <- call isEmptyTree [t] if' isEmpty $ do _ <- call iterInitEnd [result] retVoid res <- allocateIter _ <- call iterInitEnd [res] currentPtr <- allocate (ptr node) =<< deref rootPtrOf t loop $ mdo current <- load currentPtr 0 numElems <- deref (metaOf ->> numElemsOf) current first <- addr (valueAt (int16 0)) current last <- addr (valueAt numElems) current pos <- call searchLowerBound [val, first, last] idx <- pointerDiff i16 pos first >>= (`udiv` int32 (toInteger valSize)) isLeaf <- deref (metaOf ->> nodeTypeOf) current >>= (`eq` leafNodeTypeVal) if' isLeaf $ mdo isLast <- pos `eq` last condBr isLast handleLast handleOther handleLast <- blockNamed "handle_last" copy currentPtrOf res result copy valuePosOf res result retVoid handleOther <- blockNamed "handle_not_last" _ <- call iterInit [result, current, idx] retVoid isNotLast <- pos `ne` last if' isNotLast $ do matchFound' <- (int8 0 `eq`) =<< call compareValues [pos, val] if' matchFound' $ do _ <- call iterInit [result, current, idx] retVoid if' isNotLast $ do call iterInit [res, current, idx] let iCurrent = ptrcast innerNode current store currentPtr 0 =<< deref (childAt idx) iCurrent mkBtreeUpperBound :: Operand -> Operand -> Operand -> Operand -> ModuleCodegen Operand mkBtreeUpperBound isEmptyTree iterInit iterInitEnd searchUpperBound = do tree <- typeOf BTree iter <- typeOf Iterator node <- typeOf Node innerNode <- typeOf InnerNode value <- typeOf Value valSize <- asks (valueSize . typeSizes) let args = [(ptr tree, "tree"), (ptr value, "val"), (ptr iter, "result")] function "eclair_btree_upper_bound" args void $ \[t, val, result] -> mdo isEmpty <- call isEmptyTree [t] if' isEmpty $ do _ <- call iterInitEnd [result] retVoid res <- allocateIter _ <- call iterInitEnd [res] currentPtr <- allocate (ptr node) =<< deref rootPtrOf t loop $ mdo current <- load currentPtr 0 numElems <- deref (metaOf ->> numElemsOf) current first <- addr (valueAt (int16 0)) current last <- addr (valueAt numElems) current pos <- call searchUpperBound [val, first, last] idx <- pointerDiff i16 pos first >>= (`udiv` int32 (toInteger valSize)) isLeaf <- deref (metaOf ->> nodeTypeOf) current >>= (`eq` leafNodeTypeVal) if' isLeaf $ mdo isLast <- pos `eq` last condBr isLast handleLast handleOther handleLast <- blockNamed "handle_last" copy currentPtrOf res result copy valuePosOf res result retVoid handleOther <- blockNamed "handle_not_last" _ <- call iterInit [result, current, idx] retVoid -- Can the following be done with just pointer comparisons? isNotLast <- pos `ne` last if' isNotLast $ do call iterInit [result, current, idx] let iCurrent = ptrcast innerNode current store currentPtr 0 =<< deref (childAt idx) iCurrent ================================================ FILE: lib/Eclair/LLVM/BTree/Compare.hs ================================================ {-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-} module Eclair.LLVM.BTree.Compare ( mkCompare ) where import Eclair.LLVM.BTree.Types import qualified Data.Map as Map mkCompare :: ModuleCodegen Operand mkCompare = do settings <- getParams tys <- asks types let column' = columnTy tys value = valueTy tys compare' <- function "eclair_btree_value_compare" [(column', "lhs"), (column', "rhs")] i8 $ \[lhs, rhs] -> mdo result1 <- lhs `ult` rhs if' result1 $ ret $ int8 (-1) result2 <- lhs `ugt` rhs ret =<< select result2 (int8 1) (int8 0) function "eclair_btree_value_compare_values" [(ptr value, "lhs"), (ptr value, "rhs")] i8 $ \[lhs, rhs] -> mdo let columns = map fromIntegral $ index settings results <- flip execStateT mempty $ flip (zygo endCheck) columns $ \case Nil -> pass Cons col (atEnd, asm) -> do blk <- blockNamed "comparison" let indices = [int32 0, int32 col] lhsPtr <- gep lhs indices rhsPtr <- gep rhs indices lhsValue <- load lhsPtr 0 rhsValue <- load rhsPtr 0 compareResult <- call compare' [lhsValue, rhsValue] modify $ Map.insert compareResult blk case atEnd of End -> br end Continue -> mdo isEqual <- compareResult `eq` int8 0 condBr isEqual continue end asm continue <- currentBlock pass end <- blockNamed "end" ret =<< phi (Map.toList results) where endCheck = \case Nil -> End _ -> Continue data ControlFlow = Continue | End ================================================ FILE: lib/Eclair/LLVM/BTree/Create.hs ================================================ {-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-} module Eclair.LLVM.BTree.Create ( mkNodeNew , mkBtreeInit , mkBtreeInitEmpty , mkBtreeSwap ) where import Prelude hiding (void, swap) import Eclair.LLVM.BTree.Types mkNodeNew :: ModuleCodegen Operand mkNodeNew = mdo md <- getParams nodeType <- typeOf NodeType node <- typeOf Node innerNode <- typeOf InnerNode sizes <- asks typeSizes let numKeys' = numKeys md sizes ptrSize = pointerSize sizes valuesByteCount = numKeys' * valueSize sizes leafSize = int32 . toInteger $ leafNodeSize sizes innerSize = int32 . toInteger $ innerNodeSize sizes malloc <- asks (extMalloc . externals) function "eclair_btree_node_new" [(nodeType, "type")] (ptr node) $ \[ty] -> mdo structSize <- select ty innerSize leafSize memory <- call malloc [structSize] let n = ptrcast node memory assign (metaOf ->> parentOf) n (nullPtr node) assign (metaOf ->> posInParentOf) n (int16 0) assign (metaOf ->> numElemsOf) n (int16 0) assign (metaOf ->> nodeTypeOf) n ty valuesPtr <- addr valuesOf n memset valuesPtr 0 valuesByteCount isInner <- ty `eq` innerNodeTypeVal if' isInner $ mdo let inner = ptrcast innerNode n let childrenByteCount = (numKeys' + 1) * ptrSize childrenPtr <- addr childrenOf inner memset childrenPtr 0 childrenByteCount ret n mkBtreeInitEmpty :: ModuleCodegen Operand mkBtreeInitEmpty = do tree <- typeOf BTree node <- typeOf Node function "eclair_btree_init_empty" [(ptr tree, "tree")] void $ \[t] -> mdo assign rootPtrOf t (nullPtr node) assign firstPtrOf t (nullPtr node) mkBtreeInit :: Operand -> ModuleCodegen Operand mkBtreeInit btreeInsertRange = do tree <- typeOf BTree iter <- typeOf Iterator let args = [(ptr tree, "tree"), (ptr iter, "start"), (ptr iter, "end")] function "eclair_btree_init" args void $ \[t, start, end] -> mdo _ <- call btreeInsertRange [t, start, end] pass mkBtreeSwap :: ModuleCodegen Operand mkBtreeSwap = do tree <- typeOf BTree function "eclair_btree_swap" [(ptr tree, "lhs"), (ptr tree, "rhs")] void $ \[lhs, rhs] -> for_ [rootPtrOf, firstPtrOf] $ \path -> swap path lhs rhs ================================================ FILE: lib/Eclair/LLVM/BTree/Destroy.hs ================================================ {-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-} module Eclair.LLVM.BTree.Destroy ( mkBtreeDestroy , mkBtreeClear ) where import Prelude hiding (void) import Eclair.LLVM.BTree.Types mkBtreeDestroy :: Operand -> ModuleCodegen Operand mkBtreeDestroy btreeClear = do tree <- typeOf BTree function "eclair_btree_destroy" [(ptr tree, "tree")] void $ \[t] -> do _ <- call btreeClear [t] pass mkNodeDelete :: ModuleCodegen Operand mkNodeDelete = mdo node <- typeOf Node innerNode <- typeOf InnerNode free <- asks (extFree . externals) nodeDelete <- function "eclair_btree_node_delete" [(ptr node, "node")] void $ \[n] -> mdo nodeTy <- deref (metaOf ->> nodeTypeOf) n isInner <- nodeTy `eq` innerNodeTypeVal if' isInner $ do -- Delete children of inner node let inner = ptrcast innerNode n numElements <- deref (metaOf ->> numElemsOf) n loopFor (int16 0) (`ule` numElements) (add (int16 1)) $ \i -> mdo child <- deref (childAt i) inner isNotNull <- child `ne` nullPtr node if' isNotNull $ call nodeDelete [child] let memory = ptrcast i8 n _ <- call free [memory] pass pure nodeDelete mkBtreeClear :: ModuleCodegen Operand mkBtreeClear = do tree <- typeOf BTree node <- typeOf Node nodeDelete <- mkNodeDelete function "eclair_btree_clear" [(ptr tree, "tree")] void $ \[t] -> do root <- deref rootPtrOf t isNotNull <- root `ne` nullPtr node if' isNotNull $ do _ <- call nodeDelete [root] assign rootPtrOf t (nullPtr node) assign firstPtrOf t (nullPtr node) ================================================ FILE: lib/Eclair/LLVM/BTree/Find.hs ================================================ {-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-} module Eclair.LLVM.BTree.Find ( mkBtreeContains , mkBtreeFind ) where import Prelude hiding (void) import Eclair.LLVM.BTree.Types mkBtreeContains :: Operand -> Operand -> Operand -> ModuleCodegen Operand mkBtreeContains iterIsEqual btreeFind btreeEnd = do tree <- typeOf BTree value <- typeOf Value function "eclair_btree_contains" [(ptr tree, "tree"), (ptr value, "val")] i1 $ \[t, val] -> do iterPtr <- allocateIter endIterPtr <- allocateIter _ <- call btreeFind [t, val, iterPtr] _ <- call btreeEnd [t, endIterPtr] isEqual <- call iterIsEqual [iterPtr, endIterPtr] ret =<< not' isEqual mkBtreeFind :: Operand -> Operand -> Operand -> Operand -> Operand -> ModuleCodegen Operand mkBtreeFind isEmptyTree searchLowerBound compareValues iterInit iterInitEnd = do tree <- typeOf BTree iter <- typeOf Iterator node <- typeOf Node innerNode <- typeOf InnerNode value <- typeOf Value valSize <- asks (valueSize . typeSizes) let args = [(ptr tree, "tree"), (ptr value, "val"), (ptr iter, "result")] function "eclair_btree_find" args void $ \[t, val, result] -> mdo isEmpty <- call isEmptyTree [t] if' isEmpty $ do _ <- call iterInitEnd [result] retVoid currentPtr <- allocate (ptr node) =<< deref rootPtrOf t -- Find iterator using iterative approach loop $ mdo current <- load currentPtr 0 numElems <- deref (metaOf ->> numElemsOf) current first <- addr (valueAt (int16 0)) current last <- addr (valueAt numElems) current pos <- call searchLowerBound [val, first, last] idx <- pointerDiff i16 pos first >>= (`udiv` int32 (toInteger valSize)) -- Can the following equality check be done using just pointers? foundMatch <- pos `ult` last if' foundMatch $ do matchesVal <- (int8 0 `eq`) =<< call compareValues [pos, val] if' matchesVal $ do _ <- call iterInit [result, current, idx] retVoid isLeaf <- deref (metaOf ->> nodeTypeOf) current >>= (`eq` leafNodeTypeVal) if' isLeaf $ do _ <- call iterInitEnd [result] retVoid -- Continue search in child node let iCurrent = ptrcast innerNode current store currentPtr 0 =<< deref (childAt idx) iCurrent ================================================ FILE: lib/Eclair/LLVM/BTree/Insert.hs ================================================ {-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-} module Eclair.LLVM.BTree.Insert ( mkBtreeInsertValue , mkBtreeInsertRangeTemplate ) where import Prelude hiding (void) import Eclair.LLVM.Table import Eclair.LLVM.BTree.Types mkNodeSplitPoint :: ModuleCodegen Operand mkNodeSplitPoint = mdo nodeSize <- typeOf NodeSize numberOfKeys <- numKeysAsOperand function "eclair_btree_node_split_point" [] nodeSize $ \_ -> mdo a' <- mul (int16 3) numberOfKeys a <- udiv a' (int16 4) b <- sub numberOfKeys (int16 2) ret =<< minimum' Unsigned a b mkSplit :: Operand -> Operand -> Operand -> ModuleCodegen Operand mkSplit nodeNew nodeSplitPoint growParent = mdo node <- typeOf Node innerNode <- typeOf InnerNode numberOfKeys <- numKeysAsOperand function "eclair_btree_node_split" [(ptr node, "node"), (ptr (ptr node), "root")] void $ \[n, root] -> mdo -- TODO: how to do assertions in LLVM? -- assert(n->meta.num_elements == NUM_KEYS); splitPoint <- call nodeSplitPoint [] splitPoint' <- add (int16 1) splitPoint ty <- deref (metaOf ->> nodeTypeOf) n -- Create a new sibling node and move some of the data to sibling sibling <- call nodeNew [ty] jPtr <- allocate i16 (int16 0) loopFor splitPoint' (`ult` numberOfKeys) (add (int16 1)) $ \i -> mdo j <- load jPtr 0 assign (valueAt j) sibling =<< deref (valueAt i) n store jPtr 0 =<< add (int16 1) j isInner <- ty `eq` innerNodeTypeVal if' isInner $ mdo let iSibling = ptrcast innerNode sibling let iN = ptrcast innerNode n store jPtr 0 (int16 0) loopFor splitPoint' (`ule` numberOfKeys) (add (int16 1)) $ \i -> mdo j <- load jPtr 0 iChild <- deref (childAt i) iN assign (metaOf ->> parentOf) iChild sibling assign (metaOf ->> posInParentOf) iChild j assign (childAt j) iSibling iChild store jPtr 0 =<< add (int16 1) j assign (metaOf ->> numElemsOf) n splitPoint siblingNumKeys <- sub numberOfKeys splitPoint >>= flip sub (int16 1) assign (metaOf ->> numElemsOf) sibling siblingNumKeys _ <- call growParent [n, root, sibling] pass mkGrowParent :: Operand -> Operand -> ModuleCodegen Operand mkGrowParent nodeNew insertInner = mdo node <- typeOf Node innerNode <- typeOf InnerNode function "eclair_btree_node_grow_parent" [(ptr node, "node"), (ptr (ptr node), "root"), (ptr node, "sibling")] void $ \[n, root, sibling] -> mdo parent <- deref (metaOf ->> parentOf) n isNull <- parent `eq` nullPtr node numElems <- deref (metaOf ->> numElemsOf) n condBr isNull createNewRoot insertNewNodeInParent createNewRoot <- blockNamed "create_new_root" -- TODO: assert(n == *root) newRoot <- call nodeNew [innerNodeTypeVal] let iNewRoot = ptrcast innerNode newRoot assign (metaOf ->> numElemsOf) newRoot (int16 1) lastValueOfN <- deref (valueAt numElems) n assign (valueAt (int16 0)) newRoot lastValueOfN assign (childAt (int16 0)) iNewRoot n assign (childAt (int16 1)) iNewRoot sibling assign (metaOf ->> parentOf) n newRoot assign (metaOf ->> parentOf) sibling newRoot -- assign (metaOf ->> posInParentOf) n (int16 0) -- Not needed, root already has position 0 assign (metaOf ->> posInParentOf) sibling (int16 1) store root 0 newRoot retVoid insertNewNodeInParent <- blockNamed "insert_new_node_in_parent" pos <- deref (metaOf ->> posInParentOf) n lastValuePtr <- addr (valueAt numElems) n _ <- call insertInner [parent, root, pos, n, lastValuePtr, sibling] retVoid mkInsertInner :: Operand -> ModuleCodegen Operand mkInsertInner rebalanceOrSplit = mdo node <- typeOf Node innerNode <- typeOf InnerNode nodeSize <- typeOf NodeSize value <- typeOf Value let args = [ (ptr node, "node"), (ptr (ptr node), "root") , (nodeSize, "pos"), (ptr node, "predecessor") , (ptr value, "key"), (ptr node, "new_node") ] numberOfKeys <- numKeysAsOperand insertInner <- function "eclair_btree_node_insert_inner" args void $ \[n, root, pos, predecessor, key, newNode] -> mdo -- Need to allocate pos on the stack, otherwise pos updates are -- not visible later on! posPtr <- allocate nodeSize pos numElems <- deref (metaOf ->> numElemsOf) n needsRebalanceOrSplit <- numElems `uge` numberOfKeys if' needsRebalanceOrSplit $ do position' <- load posPtr 0 position'' <- sub position' =<< call rebalanceOrSplit [n, root, pos] store posPtr 0 position'' numElems' <- deref (metaOf ->> numElemsOf) n -- NOTE: n might be updated in rebalanceOrSplit needsInsertInNewNode <- position'' `ugt` numElems' if' needsInsertInNewNode $ do -- Insertion needs to be done in new sibling node: pos''' <- sub position'' numElems' >>= flip sub (int16 1) store posPtr 0 pos''' parent <- ptrcast innerNode <$> deref (metaOf ->> parentOf) n siblingPos <- add (int16 1) =<< deref (metaOf ->> posInParentOf) n sibling <- deref (childAt siblingPos) parent _ <- call insertInner [sibling, root, pos''', predecessor, key, newNode] retVoid -- Move bigger keys one forward let iN = ptrcast innerNode n numElems'' <- deref (metaOf ->> numElemsOf) n startIdx <- sub numElems'' (int16 1) pos' <- load posPtr 0 loopFor startIdx (`sge` pos') (`sub` int16 1) $ \i -> mdo j <- add i (int16 1) k <- add i (int16 2) assign (valueAt j) n =<< deref (valueAt i) n assign (childAt k) iN =<< deref (childAt j) iN childK <- deref (childAt k) iN increment int16 (metaOf ->> posInParentOf) childK -- TODO: assert(i_n->children[pos] == predecessor); -- Insert new element assign (valueAt pos') n =<< load key 0 pos'' <- add pos' (int16 1) assign (childAt pos'') iN newNode assign (metaOf ->> parentOf) newNode n assign (metaOf ->> posInParentOf) newNode pos'' increment int16 (metaOf ->> numElemsOf) n pure insertInner mkRebalanceOrSplit :: Operand -> ModuleCodegen Operand mkRebalanceOrSplit splitFn = mdo node <- typeOf Node innerNode <- typeOf InnerNode nodeSize <- typeOf NodeSize numberOfKeys <- numKeysAsOperand let args = [(ptr node, "node"), (ptr (ptr node), "root"), (nodeSize, "idx")] function "eclair_btree_node_rebalance_or_split" args nodeSize $ \[n, root, idx] -> mdo -- TODO assert(n->meta.num_elements == NUM_KEYS); parent <- ptrcast innerNode <$> deref (metaOf ->> parentOf) n pos <- deref (metaOf ->> posInParentOf) n hasParent <- parent `ne` nullPtr node posGTZero <- pos `ugt` int16 0 shouldRebalance <- and hasParent posGTZero condBr shouldRebalance rebalance split rebalance <- blockNamed "rebalance" -- Option A) re-balance data pos' <- sub pos (int16 1) left <- deref (childAt pos') parent -- Compute amount of elements movable to the left leftSlotsOpen <- calculateLeftSlotsOpen numberOfKeys left idx hasOpenLeftSlots <- leftSlotsOpen `ugt` int16 0 if' hasOpenLeftSlots $ do splitPos <- deref (metaOf ->> posInParentOf) n >>= (`sub` int16 1) splitter <- addr (baseOf ->> valueAt splitPos) parent splitterValue <- load splitter 0 -- Move keys to left node leftNumElems <- deref (metaOf ->> numElemsOf) left assign (valueAt leftNumElems) left splitterValue leftSlotsOpen' <- sub leftSlotsOpen (int16 1) loopFor (int16 0) (`ult` leftSlotsOpen') (add (int16 1)) $ \i -> do j <- add leftNumElems (int16 1) >>= add i assign (valueAt j) left =<< deref (valueAt i) n store splitter 0 =<< deref (valueAt leftSlotsOpen') n -- Shift keys in this node to the left numElemsN <- deref (metaOf ->> numElemsOf) n idxEnd <- sub numElemsN leftSlotsOpen loopFor (int16 0) (`ult` idxEnd) (add (int16 1)) $ \i -> do j <- add i leftSlotsOpen assign (valueAt i) n =<< deref (valueAt j) n -- And children (if necessary) isInnerNode <- deref (metaOf ->> nodeTypeOf) n >>= (`eq` innerNodeTypeVal) if' isInnerNode $ do let iN = ptrcast innerNode n let iLeft = ptrcast innerNode left -- Move children loopFor (int16 0) (`ult` leftSlotsOpen) (add (int16 1)) $ \i -> do leftNumElems' <- deref (metaOf ->> numElemsOf) left leftPos <- add leftNumElems' (int16 1) >>= add i assign (childAt leftPos) iLeft =<< deref (childAt i) iN -- Update moved children loopFor (int16 0) (`ult` leftSlotsOpen) (add (int16 1)) $ \i -> do leftNumElems' <- deref (metaOf ->> numElemsOf) left leftPos <- add leftNumElems' (int16 1) >>= add i child <- deref (childAt i) iN assign (metaOf ->> parentOf) child left assign (metaOf ->> posInParentOf) child leftPos -- Shift child pointer to the left endIdx <- sub numElemsN leftSlotsOpen >>= add (int16 1) loopFor (int16 0) (`ult` endIdx) (add (int16 1)) $ \i -> do j <- add i leftSlotsOpen assign (childAt i) iN =<< deref (childAt j) iN -- Update position of children child <- deref (childAt i) iN assign (metaOf ->> posInParentOf) child i -- Update node sizes update (metaOf ->> numElemsOf) left (`add` leftSlotsOpen) update (metaOf ->> numElemsOf) n (`sub` leftSlotsOpen) ret leftSlotsOpen br split split <- blockNamed "split" -- Option B) split _ <- call splitFn [n, root] ret (int16 0) -- No re-balancing where calculateLeftSlotsOpen numberOfKeys left' idx = do numElems <- deref (metaOf ->> numElemsOf) left' openSlots <- sub numberOfKeys numElems isLessThan <- openSlots `slt` idx select isLessThan openSlots idx mkBtreeInsertValue :: Operand -> Operand -> Operand -> Operand -> Operand -> ModuleCodegen Operand mkBtreeInsertValue nodeNew compareValues searchLowerBound searchUpperBound isEmptyTree = mdo tree <- typeOf BTree node <- typeOf Node value <- typeOf Value numberOfKeys <- numKeysAsOperand splitPoint <- mkNodeSplitPoint split <- mkSplit nodeNew splitPoint growParent growParent <- mkGrowParent nodeNew insertInner insertInner <- mkInsertInner rebalanceOrSplit rebalanceOrSplit <- mkRebalanceOrSplit split function "eclair_btree_insert_value" [(ptr tree, "tree"), (ptr value, "val")] i1 $ \[t, val] -> mdo isEmpty <- call isEmptyTree [t] condBr isEmpty emptyCase nonEmptyCase emptyCase <- blockNamed "empty" leaf <- call nodeNew [leafNodeTypeVal] assign (metaOf ->> numElemsOf) leaf (int16 1) assign (valueAt (int16 0)) leaf =<< load val 0 assign rootPtrOf t leaf assign firstPtrOf t leaf br inserted nonEmptyCase <- blockNamed "non_empty" -- Insert using iterative approach currentPtr <- allocate (ptr node) =<< deref rootPtrOf t loop $ mdo loopBlock <- currentBlock current <- load currentPtr 0 isInner <- deref (metaOf ->> nodeTypeOf) current >>= (`eq` innerNodeTypeVal) condBr isInner inner leaf inner <- blockNamed "inner" insertInNonEmptyInnerNode loopBlock noInsert currentPtr current val leaf <- blockNamed "leaf" insertInNonEmptyLeafNode rebalanceOrSplit noInsert inserted t currentPtr current val numberOfKeys noInsert <- blockNamed "no_insert" ret (bit 0) inserted <- blockNamed "inserted_new_value" ret (bit 1) where insertInNonEmptyInnerNode loopBlock noInsert currentPtr current val = mdo innerNode <- typeOf InnerNode valSize <- asks (valueSize . typeSizes) numElems <- deref (metaOf ->> numElemsOf) current first <- addr (valueAt (int16 0)) current last <- addr (valueAt numElems) current pos <- call searchLowerBound [val, first, last] idx <- pointerDiff i16 pos first >>= (`udiv` int32 (toInteger valSize)) notLast <- pos `ne` last if' notLast $ do alreadyInserted <- (int8 0 `eq`) =<< call compareValues [pos, val] condBr alreadyInserted noInsert continueInsert continueInsert <- blockNamed "inner_continue_insert" let iCurrent = ptrcast innerNode current store currentPtr 0 =<< deref (childAt idx) iCurrent br loopBlock insertInNonEmptyLeafNode rebalanceOrSplit noInsert inserted t currentPtr current val numberOfKeys = mdo -- Rest is for leaf nodes innerNode <- typeOf InnerNode valSize <- asks (valueSize . typeSizes) -- TODO: assert(current->meta.type == LEAF_NODE); numElems <- deref (metaOf ->> numElemsOf) current first <- addr (valueAt (int16 0)) current last <- addr (valueAt numElems) current pos <- call searchUpperBound [val, first, last] distance <- pointerDiff i16 pos first >>= (`udiv` int32 (toInteger valSize)) idxPtr <- allocate i16 distance notFirst <- pos `ne` first if' notFirst $ do valueAtPrevPos <- gep pos [int32 (-1)] alreadyInserted <- (int8 0 `eq`) =<< call compareValues [valueAtPrevPos, val] condBr alreadyInserted noInsert continueInsert continueInsert <- blockNamed "leaf_continue_insert" nodeIsFull <- numElems `uge` numberOfKeys condBr nodeIsFull split noSplit split <- blockNamed "split" root <- addr rootPtrOf t idx <- load idxPtr 0 res <- call rebalanceOrSplit [current, root, idx] idx' <- sub idx res store idxPtr 0 idx' -- Insert in right fragment if needed numElems' <- deref (metaOf ->> numElemsOf) current -- NOTE: numElems' modified after rebalanceOrSplit shouldInsertRight <- idx' `ugt` numElems' if' shouldInsertRight $ do numElems'' <- add numElems' (int16 1) idx'' <- sub idx' numElems'' store idxPtr 0 idx'' parent <- ptrcast innerNode <$> deref (metaOf ->> parentOf) current nextPos <- deref (metaOf ->> posInParentOf) current >>= add (int16 1) store currentPtr 0 =<< deref (childAt nextPos) parent br noSplit noSplit <- blockNamed "no_split" -- No split -> move keys and insert new element current' <- load currentPtr 0 -- NOTE: current might have changed in previous part idx''' <- load idxPtr 0 numElems''' <- deref (metaOf ->> numElemsOf) current' -- NOTE: Might've been updated in the meantime loopFor numElems''' (`ugt` idx''') (`sub` int16 1) $ \j -> do -- TODO: memmove possible? j' <- sub j (int16 1) assign (valueAt j) current' =<< deref (valueAt j') current' assign (valueAt idx''') current' =<< load val 0 update (metaOf ->> numElemsOf) current' (add (int16 1)) br inserted mkBtreeInsertRangeTemplate :: Operand -> ModuleCodegen (Template IteratorParams Operand) mkBtreeInsertRangeTemplate btreeInsertValue = do -- Context of BTree template tree <- typeOf BTree pure $ do -- Context of insert range template iterParams <- getParams let iterTy = ipTypeIter iterParams args = [(ptr tree, "tree"), (ptr iterTy, "begin"), (ptr iterTy, "end")] function "eclair_btree_insert_range" args void $ \[t, begin, end] -> do let loopCondition = do isEqual <- call (ipIterIsEqual iterParams) [begin, end] not' isEqual loopWhile loopCondition $ do -- NOTE: Can directly insert value in other btree, same array type! val <- call (ipIterCurrent iterParams) [begin] _ <- call btreeInsertValue [t, val] call (ipIterNext iterParams) [begin] ================================================ FILE: lib/Eclair/LLVM/BTree/Iterator.hs ================================================ {-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-} module Eclair.LLVM.BTree.Iterator ( mkIteratorInit , mkIteratorInitEnd , mkIteratorIsEqual , mkIteratorCurrent , mkIteratorNext , mkBtreeBegin , mkBtreeEnd ) where import Prelude hiding (void) import Eclair.LLVM.BTree.Types mkIteratorInit :: ModuleCodegen Operand mkIteratorInit = do iter <- typeOf Iterator node <- typeOf Node nodeSize <- typeOf NodeSize let args = [(ptr iter, "iter"), (ptr node, "cur"), (nodeSize, "pos")] function "eclair_btree_iterator_init" args void $ \[it, cur, pos] -> do assign currentPtrOf it cur assign valuePosOf it pos mkIteratorInitEnd :: Operand -> ModuleCodegen Operand mkIteratorInitEnd iterInit = do iter <- typeOf Iterator node <- typeOf Node function "eclair_btree_iterator_end_init" [(ptr iter, "iter")] void $ \[it] -> do _ <- call iterInit [it, nullPtr node, int16 0] retVoid mkIteratorIsEqual :: ModuleCodegen Operand mkIteratorIsEqual = do iter <- typeOf Iterator function "eclair_btree_iterator_is_equal" [(ptr iter, "lhs"), (ptr iter, "rhs")] i1 $ \[lhs, rhs] -> mdo currentLhs <- deref currentPtrOf lhs currentRhs <- deref currentPtrOf rhs isDifferentPtrs <- currentLhs `ne` currentRhs if' isDifferentPtrs $ ret (bit 0) valuePosLhs <- deref valuePosOf lhs valuePosRhs <- deref valuePosOf rhs ret =<< valuePosLhs `eq` valuePosRhs mkIteratorCurrent :: ModuleCodegen Operand mkIteratorCurrent = do iter <- typeOf Iterator value <- typeOf Value function "eclair_btree_iterator_current" [(ptr iter, "iter")] (ptr value) $ \[it] -> mdo valuePos <- deref valuePosOf it currentNode <- deref currentPtrOf it ret =<< addr (valueAt valuePos) currentNode mkIteratorNext :: ModuleCodegen Operand mkIteratorNext = do iter <- typeOf Iterator function "eclair_btree_iterator_next" [(ptr iter, "iter")] void $ \[it] -> mdo current <- deref currentPtrOf it isInner <- deref (metaOf ->> nodeTypeOf) current >>= (`eq` innerNodeTypeVal) if' isInner $ do innerIterNext leafNextBlock it retVoid leafNextBlock <- leafIterNext it pass where leafIterNext iter = mdo leafNextBlock <- blockNamed "leaf.next" node <- typeOf Node -- Case 1: Still elements left to iterate -> increment position increment int16 valuePosOf iter valuePos <- deref valuePosOf iter current <- deref currentPtrOf iter numElems <- deref (metaOf ->> numElemsOf) current hasNextInLeaf <- valuePos `ult` numElems if' hasNextInLeaf retVoid -- Case 2: at right-most element -> go to next inner node let loopCondition = mdo isNull <- deref currentPtrOf iter >>= (`eq` nullPtr node) condBr isNull nullBlock notNullBlock nullBlock <- blockNamed "leaf.no_parent" br endLoopCondition notNullBlock <- blockNamed "leaf.has_parent" pos' <- deref valuePosOf iter current' <- deref currentPtrOf iter numElems' <- deref (metaOf ->> numElemsOf) current' atEnd <- pos' `eq` numElems' br endLoopCondition endLoopCondition <- blockNamed "loop.condition.end" phi [(bit 0, nullBlock), (atEnd, notNullBlock)] loopWhile loopCondition $ do current' <- deref currentPtrOf iter assign valuePosOf iter =<< deref (metaOf ->> posInParentOf) current' assign currentPtrOf iter =<< deref (metaOf ->> parentOf) current' pure leafNextBlock innerIterNext leafNext iter = mdo node <- typeOf Node innerNode <- typeOf InnerNode -- Case 3: Go to left most child in inner node (a leaf node) nextPos <- deref valuePosOf iter >>= add (int16 1) iCurrent <- ptrcast innerNode <$> deref currentPtrOf iter currentPtr <- allocate (ptr node) =<< deref (childAt nextPos) iCurrent let loopCondition' = do ty <- deref (metaOf ->> nodeTypeOf) =<< load currentPtr 0 ty `eq` innerNodeTypeVal loopWhile loopCondition' $ do iCurrent' <- ptrcast innerNode <$> load currentPtr 0 firstChild <- deref (childAt (int16 0)) iCurrent' store currentPtr 0 firstChild currentLeaf <- load currentPtr 0 assign currentPtrOf iter currentLeaf assign valuePosOf iter (int16 0) -- Leaf nodes may be empty due to biased insertion => go to next isNotEmpty <- deref (metaOf ->> numElemsOf) currentLeaf >>= (`ne` int16 0) if' isNotEmpty $ do retVoid br leafNext mkBtreeBegin :: ModuleCodegen Operand mkBtreeBegin = do tree <- typeOf BTree iter <- typeOf Iterator function "eclair_btree_begin" [(ptr tree, "tree"), (ptr iter, "result")] void $ \[t, result] -> do assign currentPtrOf result =<< deref firstPtrOf t assign valuePosOf result (int16 0) mkBtreeEnd :: Operand -> ModuleCodegen Operand mkBtreeEnd iteratorInitEnd = do tree <- typeOf BTree iter <- typeOf Iterator function "eclair_btree_end" [(ptr tree, "tree"), (ptr iter, "result")] void $ \[_t, result] -> do _ <- call iteratorInitEnd [result] pass ================================================ FILE: lib/Eclair/LLVM/BTree/Size.hs ================================================ {-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-} module Eclair.LLVM.BTree.Size ( mkNodeCountEntries , mkBtreeIsEmpty , mkBtreeSize ) where import Prelude hiding (void) import Eclair.LLVM.BTree.Types mkNodeCountEntries :: ModuleCodegen Operand mkNodeCountEntries = mdo node <- typeOf Node countEntries <- function "eclair_btree_node_count_entries" [(ptr node, "node")] i64 $ \[n] -> mdo numElements <- deref (metaOf ->> numElemsOf) n ty <- deref (metaOf ->> nodeTypeOf) n isLeaf <- ty `eq` leafNodeTypeVal numElements' <- zext numElements i64 if' isLeaf $ ret numElements' count <- loopChildren n i64 numElements' $ \entryCount child -> mdo childNodeCount <- call countEntries [child] add entryCount childNodeCount ret count pure countEntries where loopChildren n ty beginValue f = mdo innerNode <- typeOf InnerNode let inner = ptrcast innerNode n result <- allocate ty beginValue numElements <- deref (metaOf ->> numElemsOf) n loopFor (int16 0) (`ule` numElements) (add (int16 1)) $ \i -> mdo currentResult <- load result 0 child <- deref (childAt i) inner updatedResult <- f currentResult child store result 0 updatedResult load result 0 mkBtreeIsEmpty :: ModuleCodegen Operand mkBtreeIsEmpty = do tree <- typeOf BTree node <- typeOf Node function "eclair_btree_is_empty" [(ptr tree, "tree")] i1 $ \[t] -> do root <- deref rootPtrOf t ret =<< root `eq` nullPtr node mkBtreeSize :: Operand -> ModuleCodegen Operand mkBtreeSize nodeCountEntries = do tree <- typeOf BTree node <- typeOf Node function "eclair_btree_size" [(ptr tree, "tree")] i64 $ \[t] -> mdo root <- deref rootPtrOf t isNull <- root `eq` nullPtr node condBr isNull nullBlock notNullBlock nullBlock <- blockNamed "null" ret (int64 0) notNullBlock <- blockNamed "not_null" count <- call nodeCountEntries [root] ret count ================================================ FILE: lib/Eclair/LLVM/BTree/Types.hs ================================================ module Eclair.LLVM.BTree.Types ( Meta(..) , Types(..) , SearchIndex , SearchType(..) , Sizes(..) , CGState(..) , IRCodegen , ModuleCodegen , Index(..) , metaOf , valuesOf , valueAt , parentOf , posInParentOf , numElemsOf , nodeTypeOf , baseOf , childrenOf , childAt , currentPtrOf , valuePosOf , rootPtrOf , firstPtrOf , DataType(..) , typeOf , allocateIter , memset , leafNodeTypeVal , innerNodeTypeVal , numKeysAsOperand , numKeysHelper , numKeys , module Eclair.LLVM.Externals , module Eclair.LLVM.Codegen ) where import Eclair.LLVM.Codegen import Eclair.LLVM.Hash import Eclair.LLVM.Externals import Prettyprinter data Meta = Meta { numColumns :: Int -- Amount of columns each node has , index :: SearchIndex -- Which columns are used to index values , blockSize :: Word64 -- Number of bytes per btree node , searchType :: SearchType -- Search strategy used in a single node } deriving stock (Eq, Ord, Show) deriving stock Generic deriving ToHash via HashWithPrefix "btree" Meta instance Pretty Meta where pretty meta = "num_columns=" <> pretty (numColumns meta) <> comma <+> -- TODO: use "withCommas" "index=" <> brackets (Prelude.fold $ intersperse comma $ map pretty (index meta)) <> comma <+> "block_size=" <> pretty (blockSize meta) <> comma <+> "search_type=" <> pretty (searchType meta) type Column = Int type SearchIndex = [Column] data SearchType = Linear | Binary deriving stock (Eq, Ord, Show) deriving stock (Generic, Enum) instance ToHash SearchType where getHash = \case Linear -> getHash ("linear" :: Text) Binary -> getHash ("binary" :: Text) instance Pretty SearchType where pretty Linear = "linear" pretty Binary = "binary" data Types = Types { btreeTy :: Type , iteratorTy :: Type , nodeSizeTy :: Type , nodeTypeTy :: Type , nodeTy :: Type , leafNodeTy :: Type , innerNodeTy :: Type , valueTy :: Type , columnTy :: Type } data Sizes = Sizes { pointerSize :: Word64 , valueSize :: Word64 , nodeDataSize :: Word64 , leafNodeSize :: Word64 , innerNodeSize :: Word64 } -- State used during rest of the btree codegen data CGState = CGState { types :: Types , typeSizes :: Sizes , externals :: Externals } type IRCodegen = IRBuilderT ModuleCodegen type ModuleCodegen = ReaderT CGState (Template Meta) data Index = NodeIdx | InnerNodeIdx | MetaIdx | ValueIdx | PositionIdx | NumElemsIdx | NodeTypeIdx | IteratorIdx | TreeIdx | ArrayOf Index | PtrOf Index metaOf :: Path 'NodeIdx 'MetaIdx metaOf = mkPath [int32 0] valuesOf :: Path 'NodeIdx ('ArrayOf 'ValueIdx) valuesOf = mkPath [int32 1] valueAt :: Operand -> Path 'NodeIdx 'ValueIdx valueAt idx = mkPath [int32 1, idx] parentOf :: Path 'MetaIdx 'NodeIdx parentOf = mkPath [int32 0] posInParentOf :: Path 'MetaIdx 'PositionIdx posInParentOf = mkPath [int32 1] numElemsOf :: Path 'MetaIdx 'NumElemsIdx numElemsOf = mkPath [int32 2] nodeTypeOf :: Path 'MetaIdx 'NodeTypeIdx nodeTypeOf = mkPath [int32 3] baseOf :: Path 'InnerNodeIdx 'NodeIdx baseOf = mkPath [int32 0] childrenOf :: Path 'InnerNodeIdx ('ArrayOf 'NodeIdx) childrenOf = mkPath [int32 1] childAt :: Operand -> Path 'InnerNodeIdx ('PtrOf 'NodeIdx) childAt idx = mkPath [int32 1, idx] currentPtrOf :: Path 'IteratorIdx ('PtrOf 'NodeIdx) currentPtrOf = mkPath [int32 0] valuePosOf :: Path 'IteratorIdx 'PositionIdx valuePosOf = mkPath [int32 1] rootPtrOf :: Path 'TreeIdx ('PtrOf 'NodeIdx) rootPtrOf = mkPath [int32 0] firstPtrOf :: Path 'TreeIdx ('PtrOf 'NodeIdx) firstPtrOf = mkPath [int32 1] data DataType = NodeType | Node | InnerNode | Value | NodeSize | Iterator | BTree typeOf :: MonadReader CGState m => DataType -> m Type typeOf dt = let getType = case dt of Node -> nodeTy NodeType -> nodeTypeTy InnerNode -> innerNodeTy Value -> valueTy NodeSize -> nodeSizeTy Iterator -> iteratorTy BTree -> btreeTy in getType <$> asks types memset :: Operand -> Word8 -> Word64 -> IRCodegen () memset p val byteCount = do memsetFn <- asks (extMemset . externals) let p' = ptrcast i8 p _ <- call memsetFn [ p' , int8 $ fromIntegral val , int64 (fromIntegral byteCount) , bit 0 ] pass leafNodeTypeVal, innerNodeTypeVal :: Operand leafNodeTypeVal = bit 0 innerNodeTypeVal = bit 1 numKeys :: Meta -> Sizes -> Word64 numKeys settings sizes = numKeysHelper settings nodeMetaSize valueByteSize where nodeMetaSize = nodeDataSize sizes valueByteSize = valueSize sizes -- NOTE: Where possible, use the more userfriendly numKeys function numKeysHelper :: Meta -> Word64 -> Word64 -> Word64 numKeysHelper settings nodeMetaSize valueByteSize = max 3 desiredNumberOfKeys where blockByteSize = blockSize settings valuesByteSize = if blockByteSize > nodeMetaSize then blockByteSize - nodeMetaSize else 0 desiredNumberOfKeys = valuesByteSize `div` valueByteSize numKeysAsOperand :: ModuleCodegen Operand numKeysAsOperand = do metadata <- getParams sizes <- asks typeSizes pure $ int16 $ toInteger $ numKeys metadata sizes -- NOTE: this only allocates on stack, but doesn't initialize it, -- this still needs to happen in rest of the code allocateIter :: IRCodegen Operand allocateIter = do iter <- typeOf Iterator alloca iter (Just (int32 1)) 0 ================================================ FILE: lib/Eclair/LLVM/BTree.hs ================================================ {-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-} {-# LANGUAGE FlexibleContexts, ScopedTypeVariables #-} module Eclair.LLVM.BTree ( Meta(..) , SearchIndex , SearchType(..) , codegen ) where import Prelude hiding (void, swap) import Control.Monad.Morph import Eclair.LLVM.Codegen import Eclair.LLVM.Table import Eclair.LLVM.BTree.Types import Eclair.LLVM.BTree.Create import Eclair.LLVM.BTree.Destroy import Eclair.LLVM.BTree.Compare import Eclair.LLVM.BTree.Iterator import Eclair.LLVM.BTree.Insert import Eclair.LLVM.BTree.Find import Eclair.LLVM.BTree.Bounds import Eclair.LLVM.BTree.Size codegen :: Externals -> ConfigT (TemplateT Meta IO) Table codegen exts = do sizes <- computeSizes lift $ hoist intoIO $ do tys <- generateTypes sizes runReaderT generateTableFunctions $ CGState tys sizes exts where intoIO = pure . runIdentity computeSizes :: ConfigT (TemplateT Meta IO) Sizes computeSizes = do (ctx, td) <- (cfgLLVMContext &&& cfgTargetData) <$> getConfig settings <- getParams let nodeDataTy = StructureType Off [ -- Next type doesn't matter here, but we need to break the -- cyclic loop or Haskell will throw an exception. ptrTy -- parent , i16 -- position_in_parent , i16 -- num_elements , i1 -- node type ] ptrTy = ptr i8 valueType = ArrayType (fromIntegral $ numColumns settings) i32 (ptrSz, valueSz, nodeDataSz) <- withLLVMTypeInfo ctx $ do let sizeOf = llvmSizeOf ctx td pointerSize' <- sizeOf ptrTy valueSize' <- sizeOf valueType nodeDataSize' <- sizeOf nodeDataTy pure (pointerSize', valueSize', nodeDataSize') let numKeys' = fromIntegral $ numKeysHelper settings nodeDataSz valueSz nodeType = StructureType Off [nodeDataTy, ArrayType numKeys' valueType] innerNodeType = StructureType Off [nodeType, ArrayType (numKeys' + 1) (ptr nodeType)] (leafNodeSz, innerNodeSz) <- withLLVMTypeInfo ctx $ do let sizeOf = llvmSizeOf ctx td leafNodeSize' <- sizeOf nodeType innerNodeSize' <- sizeOf innerNodeType pure (leafNodeSize', innerNodeSize') pure $ Sizes ptrSz valueSz nodeDataSz leafNodeSz innerNodeSz generateTypes :: (MonadModuleBuilder m, MonadFix m, MonadTemplate Meta m, HasSuffix m) => Sizes -> m Types generateTypes sizes = mdo meta <- getParams let numKeys' = fromIntegral $ numKeys meta sizes let columnTy' = i32 valueTy' = ArrayType (fromIntegral $ numColumns meta) columnTy' positionTy = i16 nodeSizeTy' = i16 -- Note: used to be size_t/i64 nodeTypeTy' = i1 nodeDataName = "node_data_t" nodeDataTy <- typedef nodeDataName Off [ ptr nodeTy -- parent , positionTy -- position_in_parent , nodeSizeTy' -- num_elements , nodeTypeTy' -- node type ] nodeTy <- typedef "node_t" Off [ nodeDataTy -- meta , ArrayType numKeys' valueTy' -- values ] let leafNodeTy' = nodeTy innerNodeTy <- typedef "inner_node_t" Off [ nodeTy -- base , ArrayType (numKeys' + 1) (ptr nodeTy) -- children ] btreeIteratorTy <- typedef "btree_iterator_t" Off [ ptr nodeTy -- current , positionTy -- value pos ] btreeTy <- typedef "btree_t" Off [ ptr nodeTy -- root , ptr nodeTy -- first ] pure $ Types { btreeTy = btreeTy , iteratorTy = btreeIteratorTy , nodeSizeTy = nodeSizeTy' , nodeTypeTy = nodeTypeTy' , nodeTy = nodeTy , leafNodeTy = leafNodeTy' , innerNodeTy = innerNodeTy , valueTy = valueTy' , columnTy = columnTy' } generateTableFunctions :: ModuleCodegen Table generateTableFunctions = mdo tree <- typeOf BTree iter <- typeOf Iterator value <- typeOf Value compareValues <- mkCompare nodeNew <- mkNodeNew nodeCountEntries <- mkNodeCountEntries iterInit <- mkIteratorInit iterInitEnd <- mkIteratorInitEnd iterInit iterIsEqual <- mkIteratorIsEqual iterCurrent <- mkIteratorCurrent iterNext <- mkIteratorNext searchLowerBound <- mkLinearSearchLowerBound compareValues searchUpperBound <- mkLinearSearchUpperBound compareValues btreeInitEmpty <- mkBtreeInitEmpty btreeInit <- mkBtreeInit btreeInsertRange btreeDestroy <- mkBtreeDestroy btreeClear isEmptyTree <- mkBtreeIsEmpty btreeSize <- mkBtreeSize nodeCountEntries btreeInsert <- mkBtreeInsertValue nodeNew compareValues searchLowerBound searchUpperBound isEmptyTree btreeInsertRangeTemplate <- mkBtreeInsertRangeTemplate btreeInsert -- We need to instantiate it atleast once for use in the BTree itself. let iterParams = IteratorParams { ipIterCurrent = iterCurrent , ipIterNext = iterNext , ipIterIsEqual = iterIsEqual , ipTypeIter = iter } btreeInsertRange <- lift $ partialInstantiate iterParams btreeInsertRangeTemplate btreeBegin <- mkBtreeBegin btreeEnd <- mkBtreeEnd iterInitEnd btreeContains <- mkBtreeContains iterIsEqual btreeFind btreeEnd btreeFind <- mkBtreeFind isEmptyTree searchLowerBound compareValues iterInit iterInitEnd btreeLowerBound <- mkBtreeLowerBound isEmptyTree iterInit iterInitEnd searchLowerBound compareValues btreeUpperBound <- mkBtreeUpperBound isEmptyTree iterInit iterInitEnd searchUpperBound btreeClear <- mkBtreeClear btreeSwap <- mkBtreeSwap pure Table { fnInit = btreeInit , fnInitEmpty = btreeInitEmpty , fnDestroy = btreeDestroy , fnPurge = btreeClear , fnSwap = btreeSwap , fnBegin = btreeBegin , fnEnd = btreeEnd , fnInsert = btreeInsert , fnInsertRangeTemplate = btreeInsertRangeTemplate , fnIsEmpty = isEmptyTree , fnSize = btreeSize , fnLowerBound = btreeLowerBound , fnUpperBound = btreeUpperBound , fnContains = btreeContains , fnIterIsEqual = iterIsEqual , fnIterCurrent = iterCurrent , fnIterNext = iterNext , typeObj = tree , typeIter = iter , typeValue = value } ================================================ FILE: lib/Eclair/LLVM/Codegen.hs ================================================ {-# LANGUAGE RoleAnnotations, PolyKinds #-} module Eclair.LLVM.Codegen ( module Eclair.LLVM.Codegen , module Eclair.LLVM.Template , module Eclair.LLVM.Config , module LLVM.Codegen ) where import qualified Data.Map as M import Foreign.ForeignPtr import Foreign.Ptr hiding (nullPtr) import Eclair.LLVM.Template import LLVM.Codegen hiding (function, typedef, typeOf) import qualified LLVM.C.API as LibLLVM import Eclair.LLVM.Config llvmSizeOf :: (MonadModuleBuilder m, MonadIO m) => ForeignPtr LibLLVM.Context -> Ptr LibLLVM.TargetData -> Type -> m Word64 llvmSizeOf ctx td ty = liftIO $ do ty' <- encodeType ctx ty LibLLVM.sizeOfType td ty' withLLVMTypeInfo :: (MonadModuleBuilder m, MonadIO m) => ForeignPtr LibLLVM.Context -> m a -> m a withLLVMTypeInfo ctx m = do -- First, we forward declare all struct types known up to this point, typedefs <- getTypedefs structTys <- liftIO $ M.traverseWithKey (forwardDeclareStruct ctx) typedefs -- Then we serialize all types (including structs, with their bodies), _ <- liftIO $ traverse (serialize ctx) structTys -- Finally, we can call the function with all type info available in LLVM. m where forwardDeclareStruct ctx' name structTy = (,structTy) <$> LibLLVM.mkOpaqueStructType ctx' name serialize :: ForeignPtr LibLLVM.Context -> (Ptr LibLLVM.Type, Type) -> IO () serialize ctx' (llvmTy, ty) = case ty of StructureType packed tys -> do tys' <- traverse (encodeType ctx') tys LibLLVM.setNamedStructBody llvmTy tys' packed _ -> panic $ "Unexpected typedef: only structs are allowed, but got: " <> show ty -- NOTE: this only works if all the named structs are known beforehand (a.k.a. forward declared)! encodeType :: ForeignPtr LibLLVM.Context -> Type -> IO (Ptr LibLLVM.Type) encodeType ctx = go where go = \case VoidType -> LibLLVM.mkVoidType ctx IntType bits -> LibLLVM.mkIntType ctx bits PointerType ty -> LibLLVM.mkPointerType =<< go ty StructureType packed tys -> do tys' <- traverse go tys LibLLVM.mkAnonStructType ctx tys' packed ArrayType count ty -> do ty' <- go ty LibLLVM.mkArrayType ty' count FunctionType retTy argTys -> do retTy' <- go retTy argTys' <- traverse go argTys LibLLVM.mkFunctionType retTy' argTys' NamedTypeReference name -> LibLLVM.getTypeByName ctx name ================================================ FILE: lib/Eclair/LLVM/Config.hs ================================================ module Eclair.LLVM.Config ( Config(..) , ConfigT , runConfigT , MonadConfig(..) ) where import qualified LLVM.C.API as LibLLVM import LLVM.Codegen import Control.Monad.Morph import Foreign.ForeignPtr import Foreign.Ptr import Eclair.Common.Config (Target) -- This is a helper module for carrying around specific information -- when compiling to a specific LLVM platform. data Config = Config { cfgTargetTriple :: Maybe Target , cfgLLVMContext :: ForeignPtr LibLLVM.Context , cfgTargetData :: Ptr LibLLVM.TargetData } -- TODO: automatically wrap ModuleBuilderT and call it CompileT? newtype ConfigT m a = ConfigT { unConfigT :: ReaderT Config m a } deriving (Functor, Applicative, Monad, MonadFix, MonadIO, MonadModuleBuilder) via ReaderT Config m deriving MonadTrans via ReaderT Config instance MFunctor ConfigT where hoist f = ConfigT . hoist f . unConfigT runConfigT :: Config -> ConfigT m a -> m a runConfigT cfg m = runReaderT (unConfigT m) cfg class Monad m => MonadConfig m where getConfig :: m Config instance Monad m => MonadConfig (ConfigT m) where getConfig = ConfigT ask -- TODO other instances ================================================ FILE: lib/Eclair/LLVM/Externals.hs ================================================ module Eclair.LLVM.Externals ( Externals(..) ) where import Eclair.LLVM.Codegen (Operand) -- Functions that are defined outside of LLVM. data Externals = Externals { extMalloc :: Operand , extFree :: Operand , extMemset :: Operand , extMemcpy :: Operand , extMemcmp :: Operand , extMmap :: Operand , extMunmap :: Operand } ================================================ FILE: lib/Eclair/LLVM/Hash.hs ================================================ {-# LANGUAGE TypeApplications, UndecidableInstances, TypeOperators, DefaultSignatures #-} module Eclair.LLVM.Hash ( Hash , unHash , HashEnum(..) , HashWithPrefix(..) , HashOnly(..) , ToHash(..) ) where import qualified Data.Text as T newtype Hash = Hash { unHash :: T.Text } instance Semigroup Hash where Hash h1 <> Hash h2 = Hash $ h1 <> "__" <> h2 newtype HashEnum a = HashEnum a newtype HashWithPrefix (prefix :: Symbol) a = HashWithPrefix a newtype HashOnly (ty :: Symbol) a = HashOnly a class ToHash a where getHash :: a -> Hash default getHash :: (Generic a, GToHash (Rep a)) => a -> Hash getHash a = gGetHash (from a) instance ToHash T.Text where getHash = Hash instance ToHash Int where getHash = Hash . show instance ToHash Word64 where getHash = Hash . show instance ToHash a => ToHash [a] where getHash = getFoldableHash instance ToHash a => ToHash (NonEmpty a) where getHash = getFoldableHash instance ToHash a => ToHash (Set a) where getHash = getFoldableHash getFoldableHash :: (Foldable f, ToHash a) => f a -> Hash getFoldableHash = Hash . mconcat . intersperse "_" . map (unHash . getHash) . toList instance (Enum a) => ToHash (HashEnum a) where getHash (HashEnum a) = getHash $ fromEnum a instance forall prefix a. (KnownSymbol prefix, Generic a, GToHash (Rep a)) => ToHash (HashWithPrefix prefix a) where getHash (HashWithPrefix a) = let pre = Hash $ toText $ symbolVal (Proxy :: Proxy prefix) h = gGetHash (from a) in pre <> h instance (HasField ty a b, ToHash b) => ToHash (HashOnly ty a) where getHash (HashOnly a) = getHash $ getField @ty a class GToHash f where gGetHash :: f a -> Hash instance ToHash a => GToHash (K1 i a) where gGetHash (K1 x) = getHash x instance GToHash U1 where gGetHash U1 = Hash "0" instance GToHash a => GToHash (M1 i c a) where gGetHash (M1 x) = gGetHash x instance (GToHash f, GToHash g) => GToHash (f :*: g) where gGetHash (a :*: b) = gGetHash a <> gGetHash b ================================================ FILE: lib/Eclair/LLVM/HashMap.hs ================================================ {-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-} module Eclair.LLVM.HashMap ( HashMap(..) , Types(..) , codegen ) where import Prelude hiding (void, HashMap) import Control.Monad.Morph import Eclair.LLVM.Codegen import Eclair.LLVM.Externals import qualified Eclair.LLVM.Symbol as Symbol import qualified Eclair.LLVM.Vector as Vector -- NOTE: this is a really naive hashmap (no growing / re-hashing yet), -- should be replaced by something more sophisticated, like: -- https://github.com/souffle-lang/souffle/blob/d1522e06c6e99c259951dc348689a77fa5f5c932/src/include/souffle/datastructure/ConcurrentInsertOnlyHashMap.h data Types = Types { tyHashMap :: Type , tyKey :: Type , tyValue :: Type , tyEntry :: Type -- struct containing key + value type } data HashMap = HashMap { hashMapTypes :: Types , hashMapInit :: Operand , hashMapDestroy :: Operand , hashMapGetOrPutValue :: Operand , hashMapLookup :: Operand , hashMapContains :: Operand } data CGState = CGState { types :: Types , symbolCodegen :: Symbol.Symbol , vectorCodegen :: Vector.Vector } type ModuleCodegen = ReaderT CGState ModuleBuilder type IRCodegen = IRBuilderT ModuleCodegen -- NOTE: no need to turn into template (for now) codegen :: Symbol.Symbol -> Externals -> ConfigT (ModuleBuilderT IO) HashMap codegen symbol exts = do let keyTy = Symbol.tySymbol symbol valueTy = i32 entryTy <- typedef "entry_t" Off [keyTy, valueTy] vec <- hoist (instantiate "entry" entryTy) $ Vector.codegen exts Nothing lift $ do let vecTy = Vector.tyVector $ Vector.vectorTypes vec hashMapTy <- typedef "hashmap_t" Off [ArrayType capacity vecTy] let tys = Types { tyHashMap = hashMapTy , tyKey = keyTy , tyValue = valueTy , tyEntry = entryTy } hoist intoIO $ runReaderT generateFunctions $ CGState tys symbol vec where intoIO = pure . runIdentity -- TODO make this variable sized capacity :: Word32 capacity = 64 generateFunctions :: ModuleCodegen HashMap generateFunctions = do tys <- asks types hmHash <- mkHash hmInit <- mkHashMapInit hmDestroy <- mkHashMapDestroy hmGetOrPutValue <- mkHashMapGetOrPutValue hmHash hmLookup <- mkHashMapLookup hmHash hmContains <- mkHashMapContains hmHash pure $ HashMap { hashMapTypes = tys , hashMapInit = hmInit , hashMapDestroy = hmDestroy , hashMapGetOrPutValue = hmGetOrPutValue , hashMapLookup = hmLookup , hashMapContains = hmContains } mkHash :: ModuleCodegen Operand mkHash = do symbolTy <- symbolType let hashTy = i32 -- TODO better hash function? function "eclair_symbol_hash" [(ptr symbolTy, "symbol")] hashTy $ \[symbol] -> do hashPtr <- allocate hashTy (int32 0) symbolSize <- deref Symbol.sizeOf symbol dataPtr <- deref Symbol.dataOf symbol loopFor (int32 0) (`ult` symbolSize) (add (int32 1)) $ \i -> do -- We need a raw gep here, since the data is dynamically allocated. bytePtr <- gep dataPtr [i] byte <- load bytePtr 0 >>= (`zext` i32) currentHashCode <- load hashPtr 0 result1 <- mul (int32 31) currentHashCode result2 <- add byte result1 store hashPtr 0 result2 hashCode <- load hashPtr 0 ret =<< modulo hashCode capacity mkHashMapInit :: ModuleCodegen Operand mkHashMapInit = do (hmTy, vec) <- asks (tyHashMap . types &&& vectorCodegen) function "eclair_hashmap_init" [(ptr hmTy, "hashmap")] void $ \[hm] -> do loopBuckets hm $ \bucketPtr -> do call (Vector.vectorInit vec) [bucketPtr] mkHashMapDestroy :: ModuleCodegen Operand mkHashMapDestroy = do (hmTy, vec) <- asks (tyHashMap . types &&& vectorCodegen) function "eclair_hashmap_destroy" [(ptr hmTy, "hashmap")] void $ \[hm] -> do loopBuckets hm $ \bucketPtr -> do call (Vector.vectorDestroy vec) [bucketPtr] mkHashMapGetOrPutValue :: Operand -> ModuleCodegen Operand mkHashMapGetOrPutValue hashFn = do (hmTy, entryTy) <- asks ((tyHashMap &&& tyEntry) . types) symbolTy <- asks (Symbol.tySymbol . symbolCodegen) vec <- asks vectorCodegen let args = [(ptr hmTy, "hashmap"), (ptr symbolTy, "symbol"), (i32, "value")] function "eclair_hashmap_get_or_put_value" args i32 $ \[hm, symbolPtr, value] -> do bucketPtr <- bucketForHash hashFn hm symbolPtr loopEntriesInBucket symbolPtr bucketPtr $ ret <=< deref valueOf symbolValue <- load symbolPtr 0 newEntryPtr <- alloca entryTy Nothing 0 assign symbolOf newEntryPtr symbolValue assign valueOf newEntryPtr value _ <- call (Vector.vectorPush vec) [bucketPtr, newEntryPtr] ret value -- NOTE: this is a unsafe lookup, assumes element is in there! -- NOTE: the index 0xFFFFFFFF is assumed to mean: not found. -- This should be a safe assumption as long as there are less keys than this in the hashmap mkHashMapLookup :: Operand -> ModuleCodegen Operand mkHashMapLookup hashFn = do (hmTy, symbolTy) <- asks (tyHashMap . types &&& Symbol.tySymbol . symbolCodegen) let args = [(ptr hmTy, "hashmap"), (ptr symbolTy, "symbol")] function "eclair_hashmap_lookup" args i32 $ \[hm, symbolPtr] -> do bucketPtr <- bucketForHash hashFn hm symbolPtr loopEntriesInBucket symbolPtr bucketPtr $ ret <=< deref valueOf ret $ int32 0xFFFFFFFF mkHashMapContains :: Operand -> ModuleCodegen Operand mkHashMapContains hashFn = do (hmTy, symbolTy) <- asks (tyHashMap . types &&& Symbol.tySymbol . symbolCodegen) let args = [(ptr hmTy, "hashmap"), (ptr symbolTy, "symbol")] function "eclair_hashmap_contains" args i1 $ \[hm, symbolPtr] -> do bucketPtr <- bucketForHash hashFn hm symbolPtr loopEntriesInBucket symbolPtr bucketPtr $ const $ ret (bit 1) ret $ bit 0 bucketForHash :: Operand -> Operand -> Operand -> IRCodegen Operand bucketForHash hashFn hm symbolPtr = do h <- call hashFn [symbolPtr] idx <- modulo h capacity addr (bucketAt idx) hm loopEntriesInBucket :: Operand -> Operand -> (Operand -> IRCodegen a) -> IRCodegen () loopEntriesInBucket symbolPtr bucketPtr instrsForMatch = do (vec, symbol) <- asks (vectorCodegen &&& symbolCodegen) entryCount <- call (Vector.vectorSize vec) [bucketPtr] loopFor (int32 0) (`ult` entryCount) (add (int32 1)) $ \i -> do entryPtr <- call (Vector.vectorGetValue vec) [bucketPtr, i] entrySymbolPtr <- addr symbolOf entryPtr isMatch <- call (Symbol.symbolIsEqual symbol) [entrySymbolPtr, symbolPtr] if' isMatch $ instrsForMatch entryPtr -- Helpers -- NOTE: this assumes capacity is a power of 2! -- TODO: add proper modulo instruction to llvm-codegen modulo :: Operand -> Word32 -> IRCodegen Operand modulo value divisor = value `and` int32 (toInteger divisor - 1) loopBuckets :: Operand -> (Operand -> IRCodegen a) -> IRCodegen () loopBuckets hm f = do let currentCapacity = int32 $ toInteger capacity loopFor (int32 0) (`ult` currentCapacity) (add (int32 1)) $ \i -> do bucketPtr <- addr (bucketAt i) hm f bucketPtr symbolType :: ModuleCodegen Type symbolType = asks (Symbol.tySymbol . symbolCodegen) data Index = HashMapIdx | BucketIdx -- A vector inside the hashmap | EntryIdx | SymbolIdx | ValueIdx bucketAt :: Operand -> Path 'HashMapIdx 'BucketIdx bucketAt idx = mkPath [int32 0, idx] symbolOf :: Path 'EntryIdx 'SymbolIdx symbolOf = mkPath [int32 0] valueOf :: Path 'EntryIdx 'ValueIdx valueOf = mkPath [int32 1] ================================================ FILE: lib/Eclair/LLVM/Metadata.hs ================================================ module Eclair.LLVM.Metadata ( Metadata(..) , mkMeta , getIndex , getNumColumns ) where import Eclair.RA.IndexSelection (Index(..)) import Eclair.TypeSystem import Eclair.LLVM.Hash import qualified Eclair.LLVM.BTree as BTree import Prettyprinter newtype Metadata = BTree BTree.Meta deriving (Eq, Ord, Show) deriving ToHash via BTree.Meta -- TODO: support other datastructures (Trie, ...) instance Pretty Metadata where pretty (BTree meta) = "btree" <> parens (pretty meta) mkMeta :: Index -> [Type] -> Metadata mkMeta (Index columns) ts = -- TODO: choose datastructure based on index/types BTree $ BTree.Meta { BTree.numColumns = length ts , BTree.index = columns , BTree.blockSize = 256 , BTree.searchType = BTree.Linear } getIndex :: Metadata -> Index getIndex = \case BTree meta -> Index $ BTree.index meta getNumColumns :: Metadata -> Int getNumColumns = \case BTree meta -> BTree.numColumns meta ================================================ FILE: lib/Eclair/LLVM/Symbol.hs ================================================ {-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-} module Eclair.LLVM.Symbol ( Symbol(..) , codegen , sizeOf , dataOf ) where import Prelude hiding (Symbol, void) import Eclair.LLVM.Codegen import Eclair.LLVM.Externals data Symbol = Symbol { tySymbol :: Type -- A symbol (string). Assumes UTF-8 encoding. , symbolInit :: Operand , symbolDestroy :: Operand , symbolIsEqual :: Operand } data CGState = CGState { externals :: Externals , tySym :: Type } type ModuleCodegen = ReaderT CGState ModuleBuilder codegen :: Externals -> ModuleBuilder Symbol codegen exts = do symbolTy <- generateTypes runReaderT generateFunctions $ CGState exts symbolTy generateTypes :: ModuleBuilder Type generateTypes = -- For now, only up to 4GB of strings are supported. -- TODO consider strings with i8 and i16 as size also typedef "symbol_t" On [i32, ptr i8] generateFunctions :: ModuleCodegen Symbol generateFunctions = do symbolTy <- asks tySym symInit <- mkSymbolInit symDestroy <- mkSymbolDestroy symIsEqual <- mkSymbolIsEqual pure $ Symbol { tySymbol = symbolTy , symbolInit = symInit , symbolDestroy = symDestroy , symbolIsEqual = symIsEqual } -- NOTE: this copies data to the new struct, and assumes memory has already been -- allocated for the symbol. Don't pass a const char* / ptr i8 that point to -- static memory because it will be freed during cleanup. mkSymbolInit :: ModuleCodegen Operand mkSymbolInit = do symbolTy <- asks tySym let args = [(ptr symbolTy, "symbol"), (i32, "size"), (ptr i8, "data")] function "eclair_symbol_init" args void $ \[symbol, size, utf8Data] -> do -- assert(symbol && "symbol cannot be NULL!"); -- assert(utf8Data && "data cannot be NULL!"); assign sizeOf symbol size assign dataOf symbol utf8Data -- NOTE: this only destroys the memory this symbol is pointing to. mkSymbolDestroy :: ModuleCodegen Operand mkSymbolDestroy = do (symbolTy, freeFn) <- asks (tySym &&& extFree . externals) let args = [(ptr symbolTy, "symbol")] function "eclair_symbol_destroy" args void $ \[symbol] -> do -- assert(symbol && "symbol cannot be NULL!"); dataPtr <- deref dataOf symbol call freeFn [dataPtr] mkSymbolIsEqual :: ModuleCodegen Operand mkSymbolIsEqual = do (symbolTy, memcmpFn) <- asks (tySym &&& extMemcmp . externals) let args = [(ptr symbolTy, "symbol1"), (ptr symbolTy, "symbol2")] function "eclair_symbol_is_equal" args i1 $ \[symbol1, symbol2] -> do -- assert(symbol1 && "symbol1 cannot be NULL!"); -- assert(symbol2 && "symbol2 cannot be NULL!"); size1 <- deref sizeOf symbol1 size2 <- deref sizeOf symbol2 isNotEqualSize <- size1 `ne` size2 if' isNotEqualSize $ ret $ bit 0 data1 <- deref dataOf symbol1 data2 <- deref dataOf symbol2 size1' <- zext size1 i64 result <- call memcmpFn [data1, data2, size1'] ret =<< result `eq` bit 0 data Index = SymbolIdx | SizeIdx | DataIdx sizeOf :: Path 'SymbolIdx 'SizeIdx sizeOf = mkPath [int32 0] dataOf :: Path 'SymbolIdx 'DataIdx dataOf = mkPath [int32 1] ================================================ FILE: lib/Eclair/LLVM/SymbolTable.hs ================================================ {-# OPTIONS_GHC -Wno-unused-top-binds #-} {-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-} module Eclair.LLVM.SymbolTable ( SymbolTable(..) , codegen ) where import Prelude hiding (void) import LLVM.Codegen import qualified Eclair.LLVM.Vector as Vector import qualified Eclair.LLVM.HashMap as HashMap data SymbolTable = SymbolTable { tySymbolTable :: Type , symbolTableInit :: Operand , symbolTableDestroy :: Operand , symbolTableFindOrInsert :: Operand , symbolTableContainsIndex :: Operand , symbolTableContainsSymbol :: Operand , symbolTableLookupIndex :: Operand , symbolTableLookupSymbol :: Operand } data CGState = CGState { symbolTableTy :: Type , symbolTy :: Type , vectorCodegen :: Vector.Vector , hashMapCodegen :: HashMap.HashMap } type ModuleCodegen = ReaderT CGState ModuleBuilder codegen :: Type -> Vector.Vector -> HashMap.HashMap -> ModuleBuilder SymbolTable codegen symbolTy' vec hm = do let vecTy = Vector.tyVector $ Vector.vectorTypes vec hmTy = HashMap.tyHashMap $ HashMap.hashMapTypes hm ty <- typedef "symbol_table" Off [ vecTy -- maps indexes (i32) to symbols , hmTy -- maps symbols to indexes ] runReaderT generateFunctions $ CGState ty symbolTy' vec hm generateFunctions :: ModuleCodegen SymbolTable generateFunctions = do ty <- asks symbolTableTy stInit <- mkSymbolTableInit stDestroy <- mkSymbolTableDestroy stFindOrInsert <- mkSymbolTableFindOrInsert stContainsIndex <- mkSymbolTableContainsIndex stContainsSymbol <- mkSymbolTableContainsSymbol stLookupIndex <- mkSymbolTableLookupIndex stLookupSymbol <- mkSymbolTableLookupSymbol pure $ SymbolTable { tySymbolTable = ty , symbolTableInit = stInit , symbolTableDestroy = stDestroy , symbolTableFindOrInsert = stFindOrInsert , symbolTableContainsIndex = stContainsIndex , symbolTableContainsSymbol = stContainsSymbol , symbolTableLookupIndex = stLookupIndex , symbolTableLookupSymbol = stLookupSymbol } mkSymbolTableInit :: ModuleCodegen Operand mkSymbolTableInit = do CGState ty _ vec hm <- ask function "eclair_symbol_table_init" [(ptr ty, "table")] void $ \[symTab] -> do -- assert(table && "symbol table cannot be NULL!"); vecPtr <- addr vecOf symTab hmPtr <- addr hashMapOf symTab _ <- call (Vector.vectorInit vec) [vecPtr] _ <- call (HashMap.hashMapInit hm) [hmPtr] pass mkSymbolTableDestroy :: ModuleCodegen Operand mkSymbolTableDestroy = do CGState ty _ vec hm <- ask function "eclair_symbol_table_destroy" [(ptr ty, "table")] void $ \[symTab] -> do -- assert(table && "symbol table cannot be NULL!"); vecPtr <- addr vecOf symTab hmPtr <- addr hashMapOf symTab _ <- call (Vector.vectorDestroy vec) [vecPtr] _ <- call (HashMap.hashMapDestroy hm) [hmPtr] pass mkSymbolTableFindOrInsert :: ModuleCodegen Operand mkSymbolTableFindOrInsert = do CGState ty symbolTy' vec hm <- ask let args = [(ptr ty, "table"), (ptr symbolTy', "symbol")] function "eclair_symbol_table_find_or_insert" args i32 $ \[symTabPtr, symbolPtr] -> do -- assert(table && "symbol table cannot be NULL!"); vecPtr <- addr vecOf symTabPtr hmPtr <- addr hashMapOf symTabPtr count <- call (Vector.vectorSize vec) [vecPtr] value <- call (HashMap.hashMapGetOrPutValue hm) [hmPtr, symbolPtr, count] isInsertOfNewValue <- count `eq` value if' isInsertOfNewValue $ do -- New value is being inserted, so we need to update mapping in other direction. call (Vector.vectorPush vec) [vecPtr, symbolPtr] ret value mkSymbolTableContainsIndex :: ModuleCodegen Operand mkSymbolTableContainsIndex = do CGState ty _ vec _ <- ask let args = [(ptr ty, "table"), (i32, "index")] function "eclair_symbol_table_contains_index" args i1 $ \[symTabPtr, idx] -> do -- assert(table && "symbol table cannot be NULL!"); vecPtr <- addr vecOf symTabPtr size <- call (Vector.vectorSize vec) [vecPtr] ret =<< idx `ult` size mkSymbolTableContainsSymbol :: ModuleCodegen Operand mkSymbolTableContainsSymbol = do CGState ty symbolTy' _ hm <- ask let args = [(ptr ty, "table"), (ptr symbolTy', "symbol")] function "eclair_symbol_table_contains_symbol" args i1 $ \[symTabPtr, symbolPtr] -> do hmPtr <- addr hashMapOf symTabPtr ret =<< call (HashMap.hashMapContains hm) [hmPtr, symbolPtr] mkSymbolTableLookupIndex :: ModuleCodegen Operand mkSymbolTableLookupIndex = do CGState ty symbolTy' _ hm <- ask let args = [(ptr ty, "table"), (ptr symbolTy', "symbol")] function "eclair_symbol_table_lookup_index" args i32 $ \[symTabPtr, symbolPtr] -> do hmPtr <- addr hashMapOf symTabPtr ret =<< call (HashMap.hashMapLookup hm) [hmPtr, symbolPtr] mkSymbolTableLookupSymbol :: ModuleCodegen Operand mkSymbolTableLookupSymbol = do CGState ty symbolTy' vec _ <- ask let args = [(ptr ty, "table"), (i32, "index")] function "eclair_symbol_table_lookup_symbol" args (ptr symbolTy') $ \[symTabPtr, idx] -> do -- assert(symbol_table_contains_index(table, index)); vecPtr <- addr vecOf symTabPtr ret =<< call (Vector.vectorGetValue vec) [vecPtr, idx] -- Helpers data Index = SymbolTableIdx | VecIdx | HashMapIdx vecOf :: Path 'SymbolTableIdx 'VecIdx vecOf = mkPath [int32 0] hashMapOf :: Path 'SymbolTableIdx 'HashMapIdx hashMapOf = mkPath [int32 1] ================================================ FILE: lib/Eclair/LLVM/Table.hs ================================================ module Eclair.LLVM.Table ( Table(..) , IteratorParams(..) ) where import Eclair.LLVM.Codegen (Operand, Type, Template) -- A data type used as template params for 'fnInsertRange' defined below. data IteratorParams = IteratorParams { ipIterCurrent :: Operand , ipIterNext :: Operand , ipIterIsEqual :: Operand , ipTypeIter :: Type } -- A data type representing all functionality of a Datalog table / container. -- This is similar to a vtable in C++, except here everything is guaranteed to be inlined -- because of specialization. Each of the operands refers to a different LLVM function. -- The types are also "exported" because they are used in other parts of the code. data Table = Table { fnInit :: Operand , fnInitEmpty :: Operand , fnDestroy :: Operand , fnPurge :: Operand , fnSwap :: Operand , fnBegin :: Operand , fnEnd :: Operand , fnIsEmpty :: Operand , fnSize :: Operand , fnLowerBound :: Operand , fnUpperBound :: Operand , fnContains :: Operand , fnInsert :: Operand , fnInsertRangeTemplate :: Template IteratorParams Operand , fnIterIsEqual :: Operand , fnIterCurrent :: Operand , fnIterNext :: Operand , typeObj :: Type , typeIter :: Type , typeValue :: Type } ================================================ FILE: lib/Eclair/LLVM/Template.hs ================================================ {-# LANGUAGE UndecidableInstances, FunctionalDependencies #-} module Eclair.LLVM.Template ( TemplateT , Template , HasSuffix(..) , MonadTemplate(..) , Suffix , cmapParams , instantiate , partialInstantiate , function , typedef ) where import Control.Monad.Morph import LLVM.Codegen hiding (function, typedef) import qualified LLVM.Codegen as CG import qualified Control.Monad.State.Lazy as LazyState import qualified Control.Monad.State.Strict as StrictState import qualified Control.Monad.RWS.Lazy as LazyRWS import qualified Control.Monad.RWS.Strict as StrictRWS import Eclair.LLVM.Config type Suffix = Text -- | A MTL-like monad transformer that allows generating code in a way similar to C++ templates. -- Instead of complicated machinery in the compiler, this transformer just adds a suffix to all generated functions and types. -- It is up to the programmer to make sure all provided suffixes to one template are unique! -- The type variable 'p' is short for "template parameters" and can be used to tweak (specialize) the code generation. -- The type variable 'm' allows running this stack in a pure context, or in a stack on top of IO. newtype TemplateT p m a = TemplateT { unTemplateT :: ReaderT (Suffix, p) (ModuleBuilderT m) a } deriving (Functor, Applicative, Monad, MonadFix, MonadIO, MonadError e, MonadState s, MonadModuleBuilder) via ReaderT (Suffix, p) (ModuleBuilderT m) type Template p = TemplateT p Identity instance MFunctor (TemplateT p) where hoist nat = TemplateT . hoist (hoist nat) . unTemplateT instance MonadReader r m => MonadReader r (TemplateT p m) where ask = lift ask local f (TemplateT m) = TemplateT $ hoist (local f) m class HasSuffix m where getSuffix :: m Suffix instance Monad m => HasSuffix (TemplateT p m) where getSuffix = TemplateT $ asks (("_" <>) . fst) -- The following instance makes 'function' behave the same as in llvm-codegen instance Monad m => HasSuffix (ModuleBuilderT m) where getSuffix = pure mempty -- This allows getting the suffix inside a function body with llvm-codegen instance (Monad m, HasSuffix m) => HasSuffix (IRBuilderT m) where getSuffix = lift getSuffix -- MTL boilerplate: instance (Monad m, HasSuffix m) => HasSuffix (ReaderT r m) where getSuffix = lift getSuffix instance (Monad m, HasSuffix m, Monoid w) => HasSuffix (WriterT w m) where getSuffix = lift getSuffix instance (Monad m, HasSuffix m) => HasSuffix (LazyState.StateT w m) where getSuffix = lift getSuffix instance (Monad m, HasSuffix m) => HasSuffix (StrictState.StateT w m) where getSuffix = lift getSuffix instance (Monad m, HasSuffix m, Monoid w) => HasSuffix (LazyRWS.RWST r w s m) where getSuffix = lift getSuffix instance (Monad m, HasSuffix m, Monoid w) => HasSuffix (StrictRWS.RWST r w s m) where getSuffix = lift getSuffix instance (Monad m, HasSuffix m) => HasSuffix (ExceptT e m) where getSuffix = lift getSuffix instance (Monad m, HasSuffix m) => HasSuffix (ConfigT m) where getSuffix = lift getSuffix class MonadTemplate p m | m -> p where getParams :: m p instance Monad m => MonadTemplate p (TemplateT p m) where getParams = TemplateT $ asks snd instance (Monad m, MonadTemplate p m) => MonadTemplate p (ReaderT r m) where getParams = lift getParams instance (Monad m, MonadTemplate p m, Monoid w) => MonadTemplate p (WriterT w m) where getParams = lift getParams instance (Monad m, MonadTemplate p m, Monoid w) => MonadTemplate p (LazyState.StateT w m) where getParams = lift getParams instance (Monad m, MonadTemplate p m, Monoid w) => MonadTemplate p (StrictState.StateT w m) where getParams = lift getParams instance (Monad m, MonadTemplate p m, Monoid w) => MonadTemplate p (LazyRWS.RWST r w s m) where getParams = lift getParams instance (Monad m, MonadTemplate p m, Monoid w) => MonadTemplate p (StrictRWS.RWST r w s m) where getParams = lift getParams instance (Monad m, MonadTemplate p m) => MonadTemplate p (ExceptT e m) where getParams = lift getParams -- This allows getting the params inside a function body with llvm-codegen instance (Monad m, MonadTemplate p m) => MonadTemplate p (IRBuilderT m) where getParams = lift getParams instance (Monad m, MonadTemplate p m) => MonadTemplate p (ConfigT m) where getParams = lift getParams instance MonadTrans (TemplateT p) where lift m = TemplateT $ ReaderT $ const $ lift m -- "contramap" over the template params. -- Useful if you only need access to part of the template data -- and/or types of the params don't match. cmapParams :: (p2 -> p1) -> TemplateT p1 m a -> TemplateT p2 m a cmapParams f (TemplateT m) = TemplateT $ flip withReaderT m $ second f -- This instantiates a template, given a template name suffix and some template parameters. -- The result is the underlying ModuleBuilerT which generates specialized code based on the parameters. instantiate :: Suffix -> p -> TemplateT p m a -> ModuleBuilderT m a instantiate suffix p (TemplateT t) = runReaderT t (suffix, p) -- This instantiates a template and wraps it into another template. -- Useful for templated member functions on a templated object. partialInstantiate :: Monad m => p1 -> TemplateT p1 m a -> TemplateT p2 m a partialInstantiate p t = do suffix <- getSuffix embedIntoTemplate $ instantiate suffix p t where -- This embeds a plain ModuleBuilderT action into a template. -- This action has no access to the actual template params from this point onwards. embedIntoTemplate :: ModuleBuilderT m a -> TemplateT p m a embedIntoTemplate m = TemplateT $ ReaderT $ const m -- The next functions replace the corresponding functions defined in llvm-codegen. -- The functions automatically add a suffix if needed, to guarantee unique function names. -- In the actual codegen, you will still need to call 'getParams' to get access to the params, -- to do the actual specialization based on them. function :: (MonadModuleBuilder m, HasSuffix m) => Name -> [(Type, ParameterName)] -> Type -> ([Operand] -> IRBuilderT m a) -> m Operand function (unName -> name) args retTy body = do suffix <- getSuffix let nameWithSuffix = Name $ name <> suffix CG.function nameWithSuffix args retTy body typedef :: (MonadModuleBuilder m, HasSuffix m) => Name -> Flag Packed -> [Type] -> m Type typedef (unName -> name) packedFlag tys = do suffix <- getSuffix let nameWithSuffix = Name $ name <> suffix CG.typedef nameWithSuffix packedFlag tys ================================================ FILE: lib/Eclair/LLVM/Vector.hs ================================================ {-# OPTIONS_GHC -Wno-unused-top-binds #-} {-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-} module Eclair.LLVM.Vector ( Vector(..) , Types(..) , Destructor , codegen , startPtrOf ) where import Prelude hiding (EQ, void) import Control.Monad.Morph import Eclair.LLVM.Codegen import Eclair.LLVM.Externals data Types = Types { tyIndex :: Type , tyElement :: Type , tyVector :: Type } data Vector = Vector { vectorTypes :: Types , vectorInit :: Operand , vectorDestroy :: Operand , vectorPush :: Operand , vectorSize :: Operand , vectorGetValue :: Operand } -- Type representing what to do when an element of the vector is destroyed. -- The operand is a pointer to an element type that needs to be cleaned up. type Destructor = Operand -> IRCodegen () data CGState = CGState { externals :: Externals , types :: Types , sizeElement :: Word64 , destructor :: Maybe Destructor } type VectorParams = Type type ModuleCodegen = ReaderT CGState (Template VectorParams) type IRCodegen = IRBuilderT ModuleCodegen codegen :: Externals -> Maybe Destructor -> ConfigT (TemplateT VectorParams IO) Vector codegen exts dtor = do tyElem <- getParams (ctx, td) <- (cfgLLVMContext &&& cfgTargetData) <$> getConfig sizeOfElem <- withLLVMTypeInfo ctx $ llvmSizeOf ctx td tyElem hoist (hoist intoIO) $ lift $ do tys <- generateTypes runReaderT generateFunctions $ CGState exts tys sizeOfElem dtor where intoIO = pure . runIdentity generateTypes :: Template VectorParams Types generateTypes = do tyElem <- getParams tyVec <- typedef "vector_t" Off [ ptr tyElem -- pointer to start of the vector , ptr tyElem -- pointer to one element past end of the vector , i32 -- capacity: how many elements can fit inside the vector ] pure $ Types { tyIndex = i32 , tyElement = tyElem , tyVector = tyVec } generateFunctions :: ModuleCodegen Vector generateFunctions = do tys <- asks types vInit <- mkVectorInit vDestroy <- mkVectorDestroy vSize <- mkVectorSize vPush <- mkVectorPush vSize vGetValue <- mkVectorGetValue pure $ Vector { vectorTypes = tys , vectorInit = vInit , vectorDestroy = vDestroy , vectorPush = vPush , vectorSize = vSize , vectorGetValue = vGetValue } initialCapacity :: Int initialCapacity = 16 -- or 0? growFactor :: Int growFactor = 2 -- or 1.5? needs rounding then.. -- NOTE: Assumes vector memory already allocated in other code mkVectorInit :: ModuleCodegen Operand mkVectorInit = do CGState exts tys sizeOfElem _ <- ask let (vecTy, elemTy) = (tyVector &&& tyElement) tys mallocFn = extMalloc exts function "eclair_vector_init" [(ptr vecTy, "vec")] void $ \[vec] -> do -- assert(vec && "Vector should not be null"); let numBytes = int32 . toInteger $ sizeOfElem * fromIntegral initialCapacity memoryPtr <- ptrcast elemTy <$> call mallocFn [numBytes] -- assert(memory && "Failed to allocate memory!"); assign startPtrOf vec memoryPtr assign endPtrOf vec memoryPtr assign capacityOf vec (int32 $ fromIntegral initialCapacity) -- NOTE: Assumes vector memory already allocated in other code mkVectorDestroy :: ModuleCodegen Operand mkVectorDestroy = do CGState exts tys _ elemDestructor <- ask let (vecTy, elemTy) = (tyVector &&& tyElement) tys freeFn = extFree exts function "eclair_vector_destroy" [(ptr vecTy, "vec")] void $ \[vec] -> do -- assert(vec && "Vector should not be null"); for_ elemDestructor $ \destructor' -> do iterPtrPtr <- allocate (ptr elemTy) =<< deref startPtrOf vec let hasNext = do iterPtr <- load iterPtrPtr 0 endPtr <- deref endPtrOf vec iterPtr `ne` endPtr loopWhile hasNext $ do iterPtr <- load iterPtrPtr 0 destructor' iterPtr store iterPtrPtr 0 =<< incrementPtr iterPtr startPtr <- ptrcast i8 <$> deref startPtrOf vec call freeFn [startPtr] -- NOTE: Returns the index at which the element was inserted => no size necessary -- NOTE: does not check for uniqueness! mkVectorPush :: Operand -> ModuleCodegen Operand mkVectorPush vectorSize' = do CGState exts tys sizeElem _ <- ask let (vecTy, elemTy) = (tyVector &&& tyElement) tys mallocFn = extMalloc exts freeFn = extFree exts memcpyFn = extMemcpy exts sizeOfElem = int32 $ toInteger sizeElem vectorGrow <- function "eclair_vector_grow" [(ptr vecTy, "vec")] void $ \[vec] -> do -- NOTE: size == capacity in this function -- assert(vec && "Vector should not be null"); currentCapacity <- deref capacityOf vec currentNumBytes <- mul currentCapacity sizeOfElem >>= (`zext` i64) newCapacity <- mul currentCapacity (int32 $ toInteger growFactor) newNumBytes <- mul newCapacity sizeOfElem newMemoryPtr <- ptrcast elemTy <$> call mallocFn [newNumBytes] -- assert(new_memory && "Failed to allocate more memory for vector!"); newMemoryEndPtr <- gep newMemoryPtr [currentCapacity] startPtr <- ptrcast i8 <$> deref startPtrOf vec let newMemoryPtrBytes = ptrcast i8 newMemoryPtr _ <- call memcpyFn [newMemoryPtrBytes, startPtr, currentNumBytes, bit 0] _ <- call freeFn [startPtr] assign startPtrOf vec newMemoryPtr assign endPtrOf vec newMemoryEndPtr assign capacityOf vec newCapacity function "eclair_vector_push" [(ptr vecTy, "vec"), (ptr elemTy, "elem")] i32 $ \[vec, elem'] -> do -- assert(vec && "Vector should not be null"); numElems <- call vectorSize' [vec] capacity <- deref capacityOf vec isFull <- numElems `eq` capacity if' isFull $ do call vectorGrow [vec] -- Look up vec->end again, pointers can be invalidated due to potential resize! endPtr <- deref endPtrOf vec store endPtr 0 =<< load elem' 0 update endPtrOf vec incrementPtr ret numElems mkVectorSize :: ModuleCodegen Operand mkVectorSize = do CGState _ tys sizeElem _ <- ask let vecTy = tyVector tys sizeOfElem = int32 $ toInteger sizeElem function "eclair_vector_size" [(ptr vecTy, "vec")] i32 $ \[vec] -> do -- assert(vec && "Vector should not be null"); startPtr <- deref startPtrOf vec endPtr <- deref endPtrOf vec byteDiff <- pointerDiff i32 endPtr startPtr ret =<< udiv byteDiff sizeOfElem mkVectorGetValue :: ModuleCodegen Operand mkVectorGetValue = do (vecTy, elemTy) <- asks ((tyVector &&& tyElement) . types) function "eclair_vector_get_value" [(ptr vecTy, "vec"), (i32, "idx")] (ptr elemTy) $ \[vec, idx] -> do startPtr <- deref startPtrOf vec -- We need a raw gep here, since this is a dynamically allocated pointer that we need to offset. ret =<< gep startPtr [idx] -- Helper functions: incrementPtr :: Operand -> IRCodegen Operand incrementPtr = (`gep` [int32 1]) data Index = VectorIdx | StartPtrIdx | EndPtrIdx | CapacityIdx startPtrOf :: Path 'VectorIdx 'StartPtrIdx startPtrOf = mkPath [int32 0] endPtrOf :: Path 'VectorIdx 'EndPtrIdx endPtrOf = mkPath [int32 1] capacityOf :: Path 'VectorIdx 'CapacityIdx capacityOf = mkPath [int32 2] ================================================ FILE: lib/Eclair/LSP/Handlers/Diagnostics.hs ================================================ module Eclair.LSP.Handlers.Diagnostics ( diagnosticsHandler , DiagnosticSource(..) , Severity(..) , Diagnostic(..) , DiagnosticsResult(..) ) where import Eclair import Eclair.LSP.Monad import Eclair.Common.Location import Eclair.Error data DiagnosticSource = Parser | Typesystem | SemanticAnalysis | Transpiler deriving (Eq, Show) data Severity = Error -- (for now Eclair only has errors) deriving (Eq, Show) data Diagnostic = Diagnostic DiagnosticSource SourceSpan Severity Text deriving (Eq, Show) data DiagnosticsResult = DiagnosticsOk [Diagnostic] | DiagnosticsError FilePath (Maybe SourcePos) Text deriving (Eq, Show) diagnosticsHandler :: FilePath -> LspM DiagnosticsResult diagnosticsHandler path = do params <- getParams mFileContents <- lift $ vfsLookupFile path case mFileContents of Nothing -> pure $ DiagnosticsError path Nothing "File not found in VFS!" Just _fileContents -> do errs <- liftLSP $ emitDiagnostics params path diagnostics <- mconcat <$> traverse errorToDiagnostics errs pure $ DiagnosticsOk diagnostics where errorToDiagnostics :: EclairError -> LspM [Diagnostic] errorToDiagnostics err = do vfsVar <- lift getVfsVar vfs <- liftIO $ readMVar vfsVar let readSourceFile = unsafeReadFromVFS vfs source = diagnosticSource err issues <- liftLSP $ errorToIssues readSourceFile err traverse (toDiagnostic source) issues toDiagnostic source issue = do let msg = renderIssueMessage issue srcSpan = locationToSourceSpan $ issueLocation issue pure $ Diagnostic source srcSpan Error msg diagnosticSource :: EclairError -> DiagnosticSource diagnosticSource = \case ParseErr {} -> Parser TypeErr {} -> Typesystem SemanticErr {} -> SemanticAnalysis ConversionErr {} -> Transpiler ================================================ FILE: lib/Eclair/LSP/Handlers/DocumentHighlight.hs ================================================ module Eclair.LSP.Handlers.DocumentHighlight ( documentHighlightHandler , DocHLResult(..) ) where import Eclair import Eclair.AST.IR import Eclair.Common.Location import Eclair.LSP.Monad data DocHLResult = DocHLOk [SourceSpan] | DocHLError FilePath SourcePos Text deriving (Eq, Show) documentHighlightHandler :: FilePath -> SourcePos -> LspM DocHLResult documentHighlightHandler path srcPos = do mFileContents <- lift $ vfsLookupFile path case mFileContents of Nothing -> pure $ DocHLError path srcPos "File not found in VFS!" Just fileContents -> case posToOffset srcPos fileContents of Left err -> pure $ DocHLError path srcPos err Right fileOffset -> do params <- getParams parseResult <- liftLSP $ runExceptT $ do (ast, spanMap) <- ExceptT (parse params path) let mNodeId = lookupNodeId spanMap fileOffset (ast, spanMap,) <$> liftEither (maybeToRight [] mNodeId) processParseResult fileContents parseResult where processParseResult fileContents = \case Left _errs -> pure $ DocHLError path srcPos "Failed to get highlight information!" Right (ast, spanMap, nodeId) -> do let refs = findReferences ast nodeId highlights = getHighlights path fileContents spanMap refs pure $ DocHLOk highlights getHighlights file fileContent spanMap = map (\refNodeId -> let span' = lookupSpan spanMap refNodeId sourceSpan = spanToSourceSpan file fileContent span' in sourceSpan) -- TODO make this a build task -- TODO implement for concepts besides variables findReferences :: AST -> NodeId -> [NodeId] findReferences ast nodeId = fst <$> zygo getVarId getRefs ast where getVarId = \case PWildcardF {} -> -- Wildcard matches with nothing. mempty VarF varNodeId var | nodeId == varNodeId -> First (Just var) astf -> fold astf getRefs = \case ModuleF _ decls -> foldMap snd $ filter (isJust . getFirst . fst) decls RuleF _ _ args clauses -> do let subtrees = args <> clauses in case getFirst $ foldMap fst subtrees of Nothing -> mempty Just var -> filter ((== var) . snd) $ foldMap snd subtrees VarF varNodeId var -> [(varNodeId, var)] astf -> foldMap snd astf ================================================ FILE: lib/Eclair/LSP/Handlers/Hover.hs ================================================ module Eclair.LSP.Handlers.Hover ( hoverHandler , HoverResult(..) ) where import Eclair import Eclair.Error import Eclair.TypeSystem hiding (typeCheck) import Eclair.Common.Location import Eclair.LSP.Monad import qualified Data.Map as M data HoverResult = HoverOk SourceSpan Type | HoverError FilePath SourcePos Text deriving (Eq, Show) hoverHandler :: FilePath -> SourcePos -> LspM HoverResult hoverHandler path srcPos = do mFileContents <- lift $ vfsLookupFile path case mFileContents of Nothing -> pure $ HoverError path srcPos "File not found in VFS!" Just fileContents -> case posToOffset srcPos fileContents of Left err -> pure $ HoverError path srcPos err Right fileOffset -> do processHoverOffset fileContents fileOffset where processHoverOffset fileContents fileOffset = do params <- getParams tcResult <- runExceptT $ do (ast, spanMap) <- ExceptT (liftLSP $ parse params path) (ast, spanMap,) <$> ExceptT (liftLSP $ typeCheck params path) case tcResult of Left errs -> processErrors errs Right (_, spanMap, typeInfo) -> processTypeInfo fileContents fileOffset spanMap typeInfo processTypeInfo fileContents fileOffset spanMap typeInfo = do let maybeResult = do nodeId <- lookupNodeId spanMap fileOffset ty <- M.lookup nodeId (resolvedTypes typeInfo) pure (nodeId, ty) case maybeResult of Nothing -> pure $ HoverError path srcPos "No type information for this position!" Just (nodeId, ty) -> do let span' = lookupSpan spanMap nodeId srcSpan = spanToSourceSpan path fileContents span' pure $ HoverOk srcSpan ty processErrors errs = do vfsVar <- lift getVfsVar vfs <- liftIO $ readMVar vfsVar issues <- traverse (liftLSP . errorToIssues (unsafeReadFromVFS vfs)) errs case findIssueAtPosition issues of Nothing -> pure $ HoverError path srcPos "File contains errors!" Just issue -> do let msg = renderIssueMessage issue pure $ HoverError path srcPos msg findIssueAtPosition issues = flip find (concat issues) $ \i -> let loc = issueLocation i startPos' = posToSourcePos $ locationStart loc endPos' = posToSourcePos $ locationEnd loc in startPos' <= srcPos && srcPos <= endPos' ================================================ FILE: lib/Eclair/LSP/Handlers.hs ================================================ module Eclair.LSP.Handlers ( module Eclair.LSP.Handlers.Hover , module Eclair.LSP.Handlers.Diagnostics , module Eclair.LSP.Handlers.DocumentHighlight ) where import Eclair.LSP.Handlers.Hover import Eclair.LSP.Handlers.Diagnostics import Eclair.LSP.Handlers.DocumentHighlight ================================================ FILE: lib/Eclair/LSP/JSON.hs ================================================ module Eclair.LSP.JSON ( responseToJSON , diagnosticToJSON , diagnosticSourceToJSON , severityToJSON , srcSpanToJSON , srcPosToJSON , typeToJSON , commandDecoder , hoverDecoder , referencesDecoder , diagnosticsDecoder , updateVfsDecoder , srcPosDecoder ) where import qualified Eclair.JSON as J import qualified Data.Hermes as H import Eclair.Common.Pretty import Eclair.TypeSystem hiding (typeCheck) import Eclair.LSP.Handlers import Eclair.LSP.Types import Eclair.Common.Location commandDecoder :: H.Decoder Command commandDecoder = H.object $ do cmdType <- H.atKey "type" H.text let mDecoder = case cmdType of "hover" -> Just hoverDecoder "document-highlight" -> Just referencesDecoder "diagnostics" -> Just diagnosticsDecoder "update-vfs" -> Just updateVfsDecoder "shutdown" -> Nothing _ -> Nothing -- TODO return exception? case mDecoder of Nothing -> pure Shutdown Just decoder -> do H.atKey "command" decoder hoverDecoder :: H.Decoder Command hoverDecoder = H.object $ Hover <$> H.atKey "file" H.string <*> H.atKey "position" srcPosDecoder referencesDecoder :: H.Decoder Command referencesDecoder = H.object $ DocumentHighlight <$> H.atKey "file" H.string <*> H.atKey "position" srcPosDecoder diagnosticsDecoder :: H.Decoder Command diagnosticsDecoder = H.object $ Diagnostics <$> H.atKey "file" H.string updateVfsDecoder :: H.Decoder Command updateVfsDecoder = H.object $ UpdateVFS <$> H.atKey "file" H.string <*> H.atKey "contents" H.text srcPosDecoder :: H.Decoder SourcePos srcPosDecoder = H.object $ SourcePos <$> H.atKey "line" H.int <*> H.atKey "column" H.int successResponse :: Text -> J.JSON -> J.JSON successResponse responseKey response = J.Object [ ("type", J.String "success"), (responseKey, response) ] errorResponse :: J.JSON -> J.JSON errorResponse response = J.Object [ ("type", J.String "error"), ("error", response) ] responseToJSON :: Response -> J.JSON responseToJSON = \case HoverResponse (HoverOk srcSpan ty) -> successResponse "hover" $ J.Object [ ("location", srcSpanToJSON srcSpan) , ("type", typeToJSON ty) ] HoverResponse (HoverError path pos err) -> errorResponse $ J.Object [ ("file", J.String $ toText path) , ("position", srcPosToJSON pos) , ("message", J.String err) ] DocumentHighlightResponse (DocHLOk refs) -> successResponse "highlights" $ J.Array $ map srcSpanToJSON refs DocumentHighlightResponse (DocHLError path pos err) -> errorResponse $ J.Object [ ("file", J.String $ toText path) , ("position", srcPosToJSON pos) , ("message", J.String err) ] DiagnosticsResponse (DiagnosticsOk diagnostics) -> successResponse "diagnostics" $ J.Array $ map diagnosticToJSON diagnostics DiagnosticsResponse (DiagnosticsError path mPos err) -> errorResponse $ J.Object [ ("file", J.String $ toText path) , ("position", srcPosToJSON $ fromMaybe (SourcePos 0 0) mPos) , ("message", J.String err) ] SuccessResponse -> J.Object [("success", J.Boolean True)] ShuttingDown -> J.Object [("shutdown", J.Boolean True)] diagnosticToJSON :: Diagnostic -> J.JSON diagnosticToJSON (Diagnostic source srcSpan severity msg) = J.Object [ ("location", srcSpanToJSON srcSpan) , ("source", diagnosticSourceToJSON source) , ("severity", severityToJSON severity) , ("message", J.String msg) ] diagnosticSourceToJSON :: DiagnosticSource -> J.JSON diagnosticSourceToJSON src = J.String $ show src severityToJSON :: Severity -> J.JSON severityToJSON Error = J.String "error" srcSpanToJSON :: SourceSpan -> J.JSON srcSpanToJSON srcSpan = J.Object [ ("file", J.String $ toText path) , ("start", J.Object [ ("line", J.Number $ sourcePosLine start) , ("column", J.Number $ sourcePosColumn start) ] ) , ("end", J.Object [ ("line", J.Number $ sourcePosLine end) , ("column", J.Number $ sourcePosColumn end) ] ) ] where path = sourceSpanFile srcSpan start = sourceSpanBegin srcSpan end = sourceSpanEnd srcSpan srcPosToJSON :: SourcePos -> J.JSON srcPosToJSON pos = J.Object [ ("line", J.Number $ sourcePosLine pos) , ("column", J.Number $ sourcePosColumn pos) ] typeToJSON :: Type -> J.JSON typeToJSON = J.String . printDoc ================================================ FILE: lib/Eclair/LSP/Monad.hs ================================================ module Eclair.LSP.Monad ( LspM , runLSP , liftLSP , getParams , module Eclair.LSP.VFS , posToOffset ) where import Eclair (Parameters(..)) import Eclair.LSP.VFS import Eclair.Common.Location import qualified Text.Megaparsec as P import qualified Text.Megaparsec.Char as P type LspM = ReaderT Parameters (VFST IO) runLSP :: LspM a -> IO a runLSP m = do runVFST $ runReaderT m placeHolderParams where -- TODO make number of cores configurable via CLI placeHolderParams = Parameters 1 Nothing mempty getParams :: LspM Parameters getParams = ask liftLSP :: IO a -> LspM a liftLSP = lift . lift -- A hack to go from the LSP position to the offset in the file. -- TODO check for off by 1 errors! -- TODO move to Parser.hs posToOffset :: SourcePos -> Text -> Either Text Int posToOffset lspPos fileContents = do case P.runParser p "" fileContents of Left _ -> Left "Error computing location offset in file!" Right offset -> pure offset where p :: P.Parsec Void Text Int p = do -- Skip to correct line replicateM_ (fromIntegral $ sourcePosLine lspPos) $ do void $ P.takeWhileP Nothing (/= '\n') P.char '\n' -- Skip to correct column void $ P.takeP Nothing (fromIntegral $ sourcePosColumn lspPos) P.getOffset ================================================ FILE: lib/Eclair/LSP/Types.hs ================================================ module Eclair.LSP.Types ( Command(..) , Response(..) ) where import Eclair.Common.Location import Eclair.LSP.Handlers data Command = Hover FilePath SourcePos | DocumentHighlight FilePath SourcePos | Diagnostics FilePath | UpdateVFS FilePath Text | Shutdown deriving (Eq, Show) data Response = HoverResponse HoverResult | DocumentHighlightResponse DocHLResult | DiagnosticsResponse DiagnosticsResult | SuccessResponse | ShuttingDown ================================================ FILE: lib/Eclair/LSP/VFS.hs ================================================ module Eclair.LSP.VFS ( VFS , VFST , runVFST , getVfsVar , vfsSetFile , vfsLookupFile , unsafeReadFromVFS ) where import qualified Data.Map as M import Data.Maybe (fromJust) -- Virtual file system (some files might not be saved yet to disk!) type VFS = M.Map FilePath Text newtype VFST m a = VFST (ReaderT (MVar VFS) m a) deriving ( Functor, Applicative, Monad , MonadReader (MVar VFS), MonadIO) via ReaderT (MVar VFS) m instance MonadTrans VFST where lift m = VFST $ lift m runVFST :: MonadIO m => VFST m a -> m a runVFST (VFST m) = do vfsVar <- liftIO $ newMVar mempty runReaderT m vfsVar getVfsVar :: Monad m => VFST m (MVar VFS) getVfsVar = ask -- NOTE: Can be used both for adding and updating vfsSetFile :: MonadIO m => FilePath -> Text -> VFST m () vfsSetFile path contents = do vfsVar <- getVfsVar liftIO $ modifyMVar_ vfsVar $ pure . M.insert path contents vfsLookupFile :: MonadIO m => FilePath -> VFST m (Maybe Text) vfsLookupFile path = do vfsVar <- getVfsVar liftIO $ do vfs <- readMVar vfsVar pure $! M.lookup path vfs -- Helper function used in a couple of handlers to directly read from VFS -- Can be used as a drop-in replacement for "tryReadFile" in the compiler -- pipeline once the VFS argument is applied. unsafeReadFromVFS :: VFS -> FilePath -> IO Text unsafeReadFromVFS vfs path = pure . fromJust $ M.lookup path vfs ================================================ FILE: lib/Eclair/LSP.hs ================================================ module Eclair.LSP ( lspMain ) where import Eclair (Parameters(..)) import Eclair.LSP.Handlers import Eclair.LSP.Monad import Eclair.LSP.Types import Eclair.LSP.JSON import qualified Eclair.JSON as J import qualified Data.Hermes as H import qualified Data.Text.IO as TIO import qualified Data.ByteString as BS import qualified Data.Map as M data NextStep = Continue | Stop lspMain :: IO () lspMain = do env <- H.mkHermesEnv Nothing runLSP $ do vfsVar <- lift getVfsVar let readVFS path = do vfs <- readMVar vfsVar pure $! M.lookup path vfs -- TODO make numCores configurable via CLI let params = Parameters 1 Nothing readVFS local (const params) $ go env where go env = readCommand env >>= \case Left _err -> sendResponse ShuttingDown Right command -> do (nextStep, result) <- processCommand command sendResponse result case nextStep of Continue -> go env Stop -> pass processCommand :: Command -> LspM (NextStep, Response) processCommand = \case Hover path srcPos -> do result <- hoverHandler path srcPos pure (Continue, HoverResponse result) DocumentHighlight path srcPos -> do hls <- documentHighlightHandler path srcPos pure (Continue, DocumentHighlightResponse hls) Diagnostics path -> do diagnostics <- diagnosticsHandler path pure (Continue, DiagnosticsResponse diagnostics) UpdateVFS path fileContents -> do lift $ vfsSetFile path fileContents pure (Continue, SuccessResponse) Shutdown -> pure (Stop, ShuttingDown) readCommand :: H.HermesEnv -> LspM (Either H.HermesException Command) readCommand env = liftLSP $ H.parseByteString env commandDecoder <$> BS.hGetLine stdin sendResponse :: Response -> LspM () sendResponse resp = liftLSP $ do TIO.hPutStrLn stdout txt hFlush stdout where txt = J.encodeJSON json json = responseToJSON resp ================================================ FILE: lib/Eclair/Parser.hs ================================================ module Eclair.Parser ( parseFile , parseText , Parser , ParseError , CustomParseErr , ParsingError(..) ) where import Data.Char import Eclair.AST.IR import Eclair.Common.Id import Eclair.Common.Location import qualified Data.Text as T import qualified Data.Vector as V import qualified Data.Text.Read as TR import qualified Data.List.NonEmpty as NE import qualified Text.Megaparsec as P import qualified Text.Megaparsec.Internal as P import qualified Text.Megaparsec.Char as P import qualified Text.Megaparsec.Char.Lexer as L import qualified Control.Monad.Combinators.Expr as L data CustomParseErr = TooManyInputOptions | TooManyOutputOptions deriving (Eq, Ord, Show) instance P.ShowErrorComponent CustomParseErr where showErrorComponent = \case TooManyInputOptions -> "More than one option of type 'input' is not allowed." TooManyOutputOptions -> "More than one option of type 'output' is not allowed." type ParseError = P.ParseErrorBundle Text CustomParseErr type ParserState = (Word32, SpanMap) type Parser = P.ParsecT CustomParseErr Text (State ParserState) data ParsingError = FileNotFound FilePath | ParsingError ParseError deriving (Show) parseFile :: (FilePath -> IO (Maybe Text)) -> FilePath -> IO (AST, NodeId, SpanMap, Maybe ParsingError) parseFile tryReadFile path = do mContents <- tryReadFile path case mContents of Nothing -> pure (emptyModule, NodeId 0, SpanMap path mempty, Just $ FileNotFound path) Just contents -> pure $ parseText path contents parseText :: FilePath -> Text -> (AST, NodeId, SpanMap, Maybe ParsingError) parseText path text = f $ runState (runParserT path text astParser) (0, SpanMap path mempty) where f ((mAst, mErr), s) = (fromMaybe emptyModule mAst, NodeId $ fst s, snd s, mErr) emptyModule :: AST emptyModule = Module (NodeId 0) mempty -- Uses the internals of Megaparsec to try and return a parse result along -- with possible parse errors. runParserT :: FilePath -> Text -> Parser a -> State ParserState (Maybe a, Maybe ParsingError) runParserT path text p = do let s = initialState path text (P.Reply s' _ result) <- P.runParsecT p s let toBundle es = P.ParseErrorBundle { P.bundleErrors = NE.sortWith P.errorOffset es, P.bundlePosState = P.statePosState s } pure $ case result of P.Error fatalError -> (Nothing, Just $ ParsingError $ toBundle $ fatalError :| P.stateParseErrors s') P.OK _ x -> let nonFatalErrs = viaNonEmpty toBundle (P.stateParseErrors s') in (Just x, ParsingError <$> nonFatalErrs) where initialState name s = P.State { P.stateInput = s, P.stateOffset = 0, P.statePosState = P.PosState { P.pstateInput = s, P.pstateOffset = 0, P.pstateSourcePos = P.initialPos name, P.pstateTabWidth = P.defaultTabWidth, P.pstateLinePrefix = "" }, P.stateParseErrors = [] } freshNodeId :: Parser NodeId freshNodeId = do nodeId <- gets (NodeId . fst) modify $ first (+1) pure nodeId withNodeId :: (NodeId -> Parser a) -> Parser a withNodeId f = do nodeId <- freshNodeId begin <- P.getOffset parsed <- f nodeId end <- P.getOffset modify $ second (insertSpan nodeId (Span begin end)) pure parsed astParser :: Parser AST astParser = withNodeId $ \nodeId -> do whitespace decls <- withRecovery '.' declParser `P.endBy` whitespace P.eof pure $ Module nodeId $ catMaybes decls declParser :: Parser AST declParser = do c <- P.lookAhead P.anySingle case c of '@' -> do withNodeId $ \nodeId -> typedefParser nodeId <|> externParser nodeId _ -> factOrRuleParser externParser :: NodeId -> Parser AST externParser nodeId = do void $ lexeme $ P.chunk "@extern" name <- lexeme identifier args <- lexeme $ betweenParens $ argParser `P.sepBy1` lexeme comma mRetTy <- optional typeParser void $ P.char '.' pure $ ExternDefinition nodeId name args mRetTy typedefParser :: NodeId -> Parser AST typedefParser nodeId = do void $ lexeme $ P.chunk "@def" name <- lexeme identifier args <- lexeme $ betweenParens $ argParser `P.sepBy1` lexeme comma attrs <- attributesParser void $ P.char '.' pure $ DeclareType nodeId name args attrs where attributesParser = map (fromMaybe Internal) $ lexeme $ optional $ do options <- some attrParser let (inputs, outputs) = partitionEithers options inputLength = length inputs outputLength = length outputs when (inputLength > 1) $ do P.customFailure TooManyInputOptions when (outputLength > 1) $ do P.customFailure TooManyOutputOptions pure $ case (inputLength, outputLength) of (0, 1) -> Output (1, 0) -> Input _ -> InputOutput attrParser = lexeme $ Left <$> P.chunk "input" <|> Right <$> P.chunk "output" argParser :: Parser (Maybe Id, Type) argParser = P.try argWithoutName <|> argWithName where argWithoutName = (Nothing,) <$> typeParser argWithName = P.label "field name" $ do mFieldName <- optional $ do name <- lexeme identifier _ <- lexeme $ P.char ':' pure name ty <- typeParser pure (mFieldName, ty) typeParser :: Parser Type typeParser = P.label "type" $ lexeme $ u32 <|> str where u32 = U32 <$ P.chunk "u32" str = Str <$ P.chunk "string" data FactOrRule = FactType | RuleType factOrRuleParser :: Parser AST factOrRuleParser = withNodeId $ \nodeId -> do name <- lexeme identifier args <- lexeme $ betweenParens $ exprParser `P.sepBy1` comma declType <- lexeme (RuleType <$ P.chunk ":-") <|> (FactType <$ P.chunk ".") case declType of RuleType -> do body <- ruleClauseParser `P.sepBy1` comma <* period pure $ Rule nodeId name args body FactType -> pure $ Atom nodeId name args where period = P.char '.' comma :: Parser Char comma = lexeme $ P.char ',' ruleClauseParser :: Parser AST ruleClauseParser = do negationParser <|> P.try (atomParser <* P.notFollowedBy opParser) <|> constraintParser where opParser = void constraintOpParser <|> void arithmeticOpParser arithmeticOpParser = P.choice $ concatMap (map $ P.char . snd) arithmeticOps negationParser :: Parser AST negationParser = withNodeId $ \nodeId -> do void $ lexeme $ P.char '!' Not nodeId <$> atomParser atomParser :: Parser AST atomParser = lexeme $ do withNodeId $ \nodeId -> do name <- lexeme identifier args <- betweenParens $ exprParser `P.sepBy1` comma pure $ Atom nodeId name args constraintParser :: Parser AST constraintParser = withNodeId $ \nodeId -> do lhs <- lexeme exprParser op <- constraintOpParser rhs <- lexeme exprParser pure $ Constraint nodeId op lhs rhs exprParser :: Parser AST exprParser = lexeme $ withNodeId (L.makeExprParser termParser . precedenceTable) where precedenceTable nodeId = map (map (uncurry (binOp nodeId))) arithmeticOps binOp nodeId op c = L.InfixL (BinOp nodeId op <$ lexeme (P.char c)) termParser = lexeme $ betweenParens exprParser <|> value where value = withNodeId $ \nodeId -> Hole nodeId <$ P.char '?' <|> P.try varParser <|> atomParser <|> Lit nodeId <$> literal arithmeticOps :: [[(ArithmeticOp, Char)]] arithmeticOps = [ [(Multiply, '*'), (Divide, '/')] , [(Plus, '+'), (Minus, '-')] ] varParser :: Parser AST varParser = lexeme $ withNodeId $ \nodeId -> do v <- Var nodeId <$> (identifier <|> wildcard) P.notFollowedBy $ P.char '(' pure v constraintOpParser :: Parser LogicalOp constraintOpParser = P.label "equality or comparison operator" $ lexeme $ do toOp Equals (P.char '=') <|> toOp LessOrEqual (P.string "<=") <|> toOp LessThan (P.char '<') <|> toOp GreaterOrEqual (P.string ">=") <|> toOp GreaterThan (P.char '>') <|> toOp NotEquals (P.string "!=") where toOp op p = op <$ lexeme p -- Not sure if we want to support something like _abc? wildcard :: Parser Id wildcard = Id . one <$> P.char '_' identifier :: Parser Id identifier = Id <$> do firstLetter <- P.letterChar P. "start of identifier" rest <- P.takeWhileP (Just "rest of identifier") isIdentifierChar let parsed = T.cons firstLetter rest when (parsed `V.elem` reserved) $ do fail . toString $ "Reserved keyword: " <> parsed pure parsed where isIdentifierChar c = isAlphaNum c || c == '_' -- List of reserved words, not allowed to be used in identifiers. reserved :: V.Vector Text reserved = V.fromList [] literal :: Parser Literal literal = number <|> string digitVector :: V.Vector Char digitVector = V.fromList ['1'..'9'] number :: Parser Literal number = LNumber <$> do positiveNumber <|> zero where zero = 0 <$ P.char '0' positiveNumber = do firstDigit <- P.satisfy (`V.elem` digitVector) P. "non-zero digit" digits <- P.takeWhileP Nothing isDigit P.notFollowedBy P.letterChar case TR.decimal $ T.cons firstDigit digits of Right (result, _) -> pure result Left err -> panic . toText $ "Error occurred during parsing of decimal number: " <> err string :: Parser Literal string = LString <$> do P.between ("\"" P. "string literal") "\"" $ toText <$> many (P.try escaped <|> normal) where escaped = do void $ P.char '\\' toEscapedChar <$> P.satisfy isEscapeChar isEscapeChar c = c `elem` ['"', '\\', 'n', 'r', 't', 'b', 'f', 'v', '0'] toEscapedChar = \case '"' -> '\"' '\\' -> '\\' 'n' -> '\n' 'r' -> '\r' 't' -> '\t' 'b' -> '\b' 'f' -> '\f' 'v' -> '\v' '0' -> '\0' _ -> panic "Unreachable code in string parser!" normal = P.anySingleBut '"' lexeme :: Parser a -> Parser a lexeme = L.lexeme whitespace whitespace :: Parser () whitespace = L.space spaceParser commentParser blockCommentParser where spaceParser = P.skipSome wsChar wsChar = void (P.satisfy $ \c -> c == ' ' || c == '\n') P. "whitespace" commentParser = L.skipLineComment "//" blockCommentParser = L.skipBlockComment "/*" "*/" betweenParens :: Parser a -> Parser a betweenParens = P.between (lexeme $ P.char '(') (P.char ')') . lexeme -- | Helper for parsers that can recover from errors. -- In case of error, keeps parsing up to and including 'endChar' withRecovery :: Char -> Parser a -> Parser (Maybe a) withRecovery endChar p = P.withRecovery handleErr $ map Just p where handleErr err = do P.registerParseError err _ <- P.takeWhileP Nothing (/= endChar) _ <- P.char endChar pure Nothing ================================================ FILE: lib/Eclair/RA/Codegen.hs ================================================ module Eclair.RA.Codegen ( CodegenM , runCodegen , LowerState(..) , mkLowerState , CGState(..) , CGInfo(..) , getLowerState , Relation , Alias , getFirstFieldOffset , getContainerInfoByOffset , idxFromConstraints , lookupRelationByIndex , lookupAlias , withUpdatedAlias , withEndLabel , withSearchState , block , declareProgram , fn , apiFn , fnArg , call , primOp , fieldAccess , heapAllocProgram , freeProgram , stackAlloc , loop , jump , labelId , label , parallel , ret , var , assign , if' , not' , and' , equals , notEquals , lessThan , lessOrEqual , greaterThan , greaterOrEqual , mkExternOp , mkArithOp , plus , minus , multiply , divide , lit ) where import Data.Maybe (fromJust) import qualified Data.List as List import qualified Data.Map as M import qualified Data.Set as S import Eclair.RA.IndexSelection import Eclair.TypeSystem import Eclair.Common.Id import Eclair.Common.Literal import qualified Eclair.EIR.IR as EIR import qualified Eclair.RA.IR as RA import qualified Eclair.LLVM.Metadata as Meta type Alias = RA.Alias type Relation = EIR.Relation type EIR = EIR.EIR type AliasMap = Map Alias EIR -- Helper data type that pre-computes some data that is used a lot, -- to speed up the compiler. data CGInfo = CGInfo { relInfo :: [(Relation, Index)] , offsetsForRelationAndIndex :: Map (Relation, Index) Int , offsetsForRelation :: Map Relation Int -- only tracks first one } data LowerState = LowerState { typeEnv :: TypedefInfo , idxMap :: IndexMap , idxSelector :: IndexSelector , cgInfo :: CGInfo , endLabel :: EIR.LabelId , aliasMap :: AliasMap } mkLowerState :: TypedefInfo -> IndexMap -> IndexSelector -> [(Relation, Index)] -> EIR.LabelId -> LowerState mkLowerState typedefInfo indexMap getIndexForSearch relInfos end = LowerState typedefInfo indexMap getIndexForSearch codegenInfo end mempty where codegenInfo = CGInfo relInfos offsetsByRelationAndIndex offsetsByRelation offsetsByRelationAndIndex = M.fromDistinctAscList [ (ri, offset) | ri <- sortNub relInfos -- + 1 due to symbol table at position 0 in program struct , let offset = 1 + fromJust (List.elemIndex ri relInfos) ] offsetsByRelation = let rs = map fst relInfos in M.fromDistinctAscList [ (r, offset) | r <- sortNub rs -- + 1 due to symbol table at position 0 in program struct , let offset = 1 + fromJust (List.elemIndex r rs) ] data CGState = Normal LowerState | Search Alias EIR LowerState type Count = Int type IdMapping = Map Text Count data Mapping = Mapping { labelMapping :: IdMapping , varMapping :: IdMapping } instance Semigroup Mapping where (Mapping lbls1 vars1) <> (Mapping lbls2 vars2) = Mapping (combine lbls1 lbls2) (combine vars1 vars2) where combine = M.unionWith (+) instance Monoid Mapping where mempty = Mapping mempty mempty newtype CodegenM a = CodeGenM (RWS CGState () Mapping a) deriving ( Functor, Applicative, Monad , MonadReader CGState , MonadState Mapping ) via (RWS CGState () Mapping) runCodegen :: LowerState -> CodegenM EIR -> EIR runCodegen ls (CodeGenM m) = fst $ evalRWS m (Normal ls) mempty withSearchState :: Alias -> EIR -> CodegenM a -> CodegenM a withSearchState alias value m = do ls <- getLowerState local (const $ Search alias value ls) m getLowerState :: CodegenM LowerState getLowerState = asks getLS where getLS = \case Normal ls -> ls Search _ _ ls -> ls block :: [CodegenM EIR] -> CodegenM EIR block ms = do actions <- sequence ms pure $ EIR.Block $ flattenBlocks actions flattenBlocks :: [EIR] -> [EIR] flattenBlocks actions = flip concatMap actions $ \case EIR.Block stmts -> stmts stmt -> [stmt] declareProgram :: [(Relation, Meta.Metadata)] -> CodegenM EIR declareProgram metas = pure $ EIR.DeclareProgram metas fn :: Text -> [EIR.Type] -> EIR.Type -> [CodegenM EIR] -> CodegenM EIR fn name tys retTy body = EIR.Function EIR.Private name tys retTy <$> block body apiFn :: Text -> [EIR.Type] -> EIR.Type -> [CodegenM EIR] -> CodegenM EIR apiFn name tys retTy body = EIR.Function EIR.Public name tys retTy <$> block body fnArg :: Int -> CodegenM EIR fnArg n = pure $ EIR.FunctionArg n call :: Relation -> Index -> EIR.Function -> [CodegenM EIR] -> CodegenM EIR call r idx fn' = primOp (EIR.RelationOp r idx fn') primOp :: EIR.Op -> [CodegenM EIR] -> CodegenM EIR primOp op args = EIR.PrimOp op <$> sequence args fieldAccess :: CodegenM EIR -> Int -> CodegenM EIR fieldAccess struct n = flip EIR.FieldAccess n <$> struct heapAllocProgram :: CodegenM EIR heapAllocProgram = pure EIR.HeapAllocateProgram freeProgram :: CodegenM EIR -> CodegenM EIR freeProgram ptr = EIR.FreeProgram <$> ptr stackAlloc :: Relation -> Index -> EIR.Type -> CodegenM EIR stackAlloc r idx ty = pure $ EIR.StackAllocate (stripIdPrefixes r) idx ty loop :: [CodegenM EIR] -> CodegenM EIR loop ms = do actions <- sequence ms pure $ EIR.Loop $ flattenBlocks actions jump :: EIR.LabelId -> CodegenM EIR jump lbl = pure $ EIR.Jump lbl -- NOTE: labelId and label are split up, so label can be used in 2 ways: -- 1) "endLabel" can also be passed into 'label' -- 2) dynamic labels used for control flow can be generated with 'labelId' and passed to 'label' labelId :: Text -> CodegenM EIR.LabelId labelId name = do mapping <- gets labelMapping (lblId, updatedMapping) <- lookupId name mapping modify $ \s -> s { labelMapping = updatedMapping } pure . EIR.LabelId $ lblId label :: EIR.LabelId -> CodegenM EIR label = pure . EIR.Label parallel :: [CodegenM EIR] -> CodegenM EIR parallel ms = do actions <- sequence ms pure $ EIR.Par $ flattenBlocks actions ret :: CodegenM EIR -> CodegenM EIR ret = map EIR.Return -- NOTE: 2nd layer is for easy integration with other helper functions -- e.g.: -- -- do -- v <- var "..." -- sequence [ ... ] var :: Text -> CodegenM (CodegenM EIR) var name = do mapping <- gets varMapping (varId, updatedMapping) <- lookupId name mapping modify $ \s -> s { varMapping = updatedMapping } pure . pure . EIR.Var $ varId assign :: CodegenM EIR -> CodegenM EIR -> CodegenM EIR assign var' value = do v <- var' value >>= \case EIR.Block stmts -> let lastStmt = List.last stmts firstStmts = List.init stmts in block (map pure $ firstStmts ++ [EIR.Assign v lastStmt]) val -> pure $ EIR.Assign v val if' :: CodegenM EIR -> CodegenM EIR -> CodegenM EIR if' cond body = do condition <- var "condition" block [ assign condition cond , EIR.If <$> condition <*> body ] not' :: CodegenM EIR -> CodegenM EIR not' bool' = EIR.Not <$> bool' and' :: CodegenM EIR -> CodegenM EIR -> CodegenM EIR and' lhs rhs = do lhsResult <- var "bool" rhsResult <- var "bool" block [ assign lhsResult lhs , assign rhsResult rhs , EIR.And <$> lhsResult <*> rhsResult ] mkArithOp :: EIR.ArithmeticOp -> CodegenM EIR -> CodegenM EIR -> CodegenM EIR mkArithOp op lhs rhs = let args = sequence [lhs, rhs] in EIR.PrimOp (EIR.ArithOp op) <$> args mkExternOp :: Id -> [CodegenM EIR] -> CodegenM EIR mkExternOp name args = do let program = EIR.FunctionArg 0 symbolTable = EIR.FieldAccess program 0 args' <- sequence args pure $ EIR.PrimOp (EIR.ExternOp name) $ symbolTable : args' plus :: CodegenM EIR -> CodegenM EIR -> CodegenM EIR plus = mkArithOp EIR.Plus minus :: CodegenM EIR -> CodegenM EIR -> CodegenM EIR minus = mkArithOp EIR.Minus multiply :: CodegenM EIR -> CodegenM EIR -> CodegenM EIR multiply = mkArithOp EIR.Multiply divide :: CodegenM EIR -> CodegenM EIR -> CodegenM EIR divide = mkArithOp EIR.Divide mkConstrainOp :: EIR.LogicalOp -> CodegenM EIR -> CodegenM EIR -> CodegenM EIR mkConstrainOp op lhs rhs = let args = sequence [lhs, rhs] in EIR.PrimOp (EIR.ComparisonOp op) <$> args equals :: CodegenM EIR -> CodegenM EIR -> CodegenM EIR equals = mkConstrainOp EIR.Equals notEquals :: CodegenM EIR -> CodegenM EIR -> CodegenM EIR notEquals = mkConstrainOp EIR.NotEquals lessThan :: CodegenM EIR -> CodegenM EIR -> CodegenM EIR lessThan = mkConstrainOp EIR.LessThan lessOrEqual :: CodegenM EIR -> CodegenM EIR -> CodegenM EIR lessOrEqual = mkConstrainOp EIR.LessOrEqual greaterThan :: CodegenM EIR -> CodegenM EIR -> CodegenM EIR greaterThan = mkConstrainOp EIR.GreaterThan greaterOrEqual :: CodegenM EIR -> CodegenM EIR -> CodegenM EIR greaterOrEqual = mkConstrainOp EIR.GreaterOrEqual lit :: Word32 -> CodegenM EIR lit x = pure $ EIR.Lit $ LNumber x lookupId :: Text -> IdMapping -> CodegenM (Text, IdMapping) lookupId name mapping = do let (mValue, updatedMapping) = M.insertLookupWithKey update name defaultValue mapping value = case mValue of Nothing -> name Just val -> name <> "_" <> show val pure (value, updatedMapping) where defaultValue = 1 update _ _ prevValue = prevValue + 1 getFieldOffset :: Relation -> Index -> CodegenM Int getFieldOffset r idx = do fromJust . M.lookup (r, idx) . offsetsForRelationAndIndex . cgInfo <$> getLowerState getFirstFieldOffset :: Relation -> CodegenM Int getFirstFieldOffset r = do fromJust . M.lookup r . offsetsForRelation . cgInfo <$> getLowerState getContainerInfoByOffset :: Int -> CodegenM (Relation, Index) getContainerInfoByOffset offset = -- - 1 due to symbol table at position 0 in program struct (List.!! (offset - 1)) . relInfo . cgInfo <$> getLowerState lookupAlias :: Alias -> CodegenM EIR lookupAlias a = ask >>= \case Normal ls -> lookupAlias' ls Search _ _ ls -> lookupAlias' ls where lookupAlias' ls = pure $ fromJust $ M.lookup a (aliasMap ls) withUpdatedAlias :: Alias -> EIR -> CodegenM a -> CodegenM a withUpdatedAlias a curr m = do state' <- ask <&> \case Normal ls -> Normal (updateAlias ls curr) Search a' v ls -> Search a' v (updateAlias ls curr) local (const state') m where updateAlias ls curr' = ls { aliasMap = M.insert a curr' (aliasMap ls) } withEndLabel :: EIR.LabelId -> CodegenM a -> CodegenM a withEndLabel end m = do local setLabel m where setLabel = \case Normal ls -> Normal (set ls) Search a v ls -> Search a v (set ls) set ls = ls { endLabel = end } idxFromConstraints :: Relation -> Alias -> [(Relation, Column)] -> CodegenM Index idxFromConstraints r a constraints = do lowerState <- getLowerState let (getIndexForSearch, indexMap) = (idxSelector &&& idxMap) lowerState r' = stripIdPrefixes r if null constraints then do -- NOTE: no constraints so we pick the longest index (total search) let mIndex = do indices <- M.lookup r' indexMap viaNonEmpty head $ sortOn (negate . length . unIndex) $ toList indices pure $ fromJust mIndex else do let columns = mapMaybe (columnsForRelation a) constraints signature = SearchSignature $ S.fromList columns idx = getIndexForSearch r signature pure idx lookupRelationByIndex :: Relation -> Index -> CodegenM EIR lookupRelationByIndex r idx = do field <- getFieldOffset r idx fieldAccess (fnArg 0) field ================================================ FILE: lib/Eclair/RA/IR.hs ================================================ {-# LANGUAGE TemplateHaskell #-} module Eclair.RA.IR ( Relation , RA(..) , RAF(..) , Alias , Clause , Action , ColumnIndex , LogicalOp(..) , ArithmeticOp(..) , Op(..) ) where import Eclair.Common.Id import Eclair.Common.Pretty import Eclair.Common.Operator import Eclair.Common.Location (NodeId(..)) type Relation = Id type Alias = Id type Clause = RA type Action = RA type ColumnIndex = Int data Op = BuiltinOp ArithmeticOp | ExternOp Id deriving (Eq, Show) -- NOTE: removed Insert, couldn't find a use? data RA = Search NodeId Relation Alias [Clause] Action | Project NodeId Relation [RA] | Merge NodeId Relation Relation | Swap NodeId Relation Relation | Purge NodeId Relation | Par NodeId [RA] | Loop NodeId [RA] -- NOTE: counttuples check is 'builtin' atm -- Later this needs to be changed to Clause to deal with 'X<100' etc as well. | Exit NodeId [Relation] | Module NodeId [RA] | Lit NodeId Word32 | Undef NodeId | ColumnIndex NodeId Relation ColumnIndex | CompareOp NodeId LogicalOp RA RA | PrimOp NodeId Op [RA] | NotElem NodeId Relation [RA] | If NodeId RA RA -- NOTE: args are condition and body deriving (Eq, Show) makeBaseFunctor ''RA prettyBlock :: Pretty a => [a] -> Doc ann prettyBlock = indentBlock . vsep . map pretty indentBlock :: Doc ann -> Doc ann indentBlock block = nest indentation (hardline <> block) instance Pretty Op where pretty = \case BuiltinOp op -> pretty op ExternOp opName -> pretty opName instance Pretty RA where pretty = \case Search _ r alias clauses inner -> let clausesText = if null clauses then "" else "where" <+> parens (withAnds $ map pretty clauses) <> space in "search" <+> pretty r <+> "as" <+> pretty alias <+> clausesText <> "do" <> prettyBlock [inner] Project _ r terms -> "project" <+> prettyValues terms <+> "into" <+> pretty r Merge _ r1 r2 -> "merge" <+> pretty r1 <+> pretty r2 Swap _ r1 r2 -> "swap" <+> pretty r1 <+> pretty r2 Purge _ r -> "purge" <+> pretty r Par _ stmts -> "parallel do" <> prettyBlock stmts Loop _ stmts -> "loop do" <> prettyBlock stmts If _ cond stmt -> "if" <+> pretty cond <+> "do" <> prettyBlock [stmt] Exit _ rs -> let texts = map formatExitCondition rs in "exit if" <+> withAnds texts Module _ stmts -> vsep $ map pretty stmts Lit _ x -> pretty x Undef _ -> "undefined" ColumnIndex _ r idx -> pretty r <> brackets (pretty idx) CompareOp _ op lhs rhs -> pretty lhs <+> pretty op <+> pretty rhs PrimOp _ op args -> case (op, args) of (BuiltinOp{}, [lhs, rhs]) -> parens $ pretty lhs <+> pretty op <+> pretty rhs _ -> pretty op <> parens (withCommas $ map pretty args) NotElem _ r terms -> prettyValues terms <+> "∉" <+> pretty r where prettyValues terms = parens (withCommas $ map pretty terms) formatExitCondition r = "counttuples" <> parens (pretty r) <+> "=" <+> "0" ================================================ FILE: lib/Eclair/RA/IndexSelection.hs ================================================ module Eclair.RA.IndexSelection ( IndexMap , IndexSelector , Index(..) , SearchSignature(..) , Column , runIndexSelection , columnsForRelation , NormalizedEquality(..) , normalizedEqToConstraints , extractEqualities , definedColumnsFor ) where -- Based on the paper "Automatic Index Selection for Large-Scale Datalog Computation" -- http://www.vldb.org/pvldb/vol12/p141-subotic.pdf import Data.Maybe (fromJust) import Eclair.Common.Id import Eclair.Common.Pretty import Eclair.RA.IR import Eclair.Comonads import Eclair.TypeSystem (TypedefInfo) import Algebra.Graph.Bipartite.AdjacencyMap import Algebra.Graph.Bipartite.AdjacencyMap.Algorithm hiding (matching) import qualified Data.Set as Set import qualified Data.Map as Map import qualified Data.List as List import qualified Data.DList.DNonEmpty as NE type Column = Int newtype SearchSignature = SearchSignature (Set Column) deriving (Eq, Ord, Show) newtype Index = Index { unIndex :: [Column] -- TODO: use NonEmpty } deriving (Eq, Ord, Show) instance Pretty Index where pretty (Index columns) = brackets $ withCommas $ map pretty columns type SearchSet = Set SearchSignature type SearchChain = NonEmpty SearchSignature type SearchMap = Map Relation SearchSet type SearchGraph = AdjacencyMap SearchSignature SearchSignature type SearchMatching = Matching SearchSignature SearchSignature type IndexSelection = [(Relation, Map SearchSignature Index)] type IndexMap = Map Relation (Set Index) type IndexSelector = Relation -> SearchSignature -> Index runIndexSelection :: TypedefInfo -> RA -> (IndexMap, IndexSelector) runIndexSelection defInfo ra = let searchMap = searchesForProgram defInfo ra indexSelection :: IndexSelection indexSelection = Map.foldrWithKey (\r searchSet acc -> let graph = buildGraph searchSet matching = maxMatching graph chains = getChainsFromMatching graph matching indices = indicesFromChains searchSet chains in (r, indices):acc) mempty searchMap combineIdxs idxs idx = idxs <> one idx indexMap = foldl' combineIdxs mempty <$> Map.fromList indexSelection indexSelector r s = fromJust $ do indexMapping <- snd <$> List.find ((== r) . fst) indexSelection Map.lookup s indexMapping in (indexMap, indexSelector) data SearchFact = SearchOn Relation SearchSignature | Related Relation Relation deriving (Eq, Ord) -- All relations (including delta_XXX, new_XXX) need atleast one index for full order search -- Swap operation requires indices of r1 and r2 to be related. searchesForProgram :: TypedefInfo -> RA -> SearchMap searchesForProgram defInfo ra = let raSearchFacts = execState (gcata (dsitribute extractEqualities) constraintsForRA ra) mempty relationFullSearches = getFullSearchesForRelations defInfo facts = raSearchFacts <> relationFullSearches in solve facts where addFact fact = modify (fact:) constraintsForRA = \case SearchF _ r a (foldMap tSnd -> eqs) (tThd -> action) -> do -- 1. Only direct constraints in the search matter, since that is what -- is used to select the index with, afterwards we are already -- looping over the value! -- 2. Only equality constraints matter, not "NoElem" constraints! let cs = concatMap normalizedEqToConstraints eqs relevantCols = mapMaybe (columnsForRelation a) cs signature = SearchSignature $ Set.fromList relevantCols unless (null relevantCols) $ do addFact $ SearchOn r signature action NotElemF _ r cols -> do let values = map tFst cols cs = definedColumnsFor values signature = SearchSignature $ Set.fromList cs addFact $ SearchOn r signature MergeF _ from' _ -> do -- Always add a full search signature for the from relation, so we don't lose any data. let columns = columnsFor . fromJust $ Map.lookup (stripIdPrefixes from') defInfo signature = SearchSignature $ Set.fromList columns addFact $ SearchOn from' signature SwapF _ r1 r2 -> addFact $ Related r1 r2 raf -> traverse_ tThd raf dsitribute :: Corecursive t => (Base t (t, b) -> b) -> (Base t (Triple t b a) -> Triple t b (Base t a)) dsitribute g m = let base_t_t = map tFst m base_t_tb = map (tFst &&& tSnd) m base_t_a = map tThd m in Triple (embed base_t_t) (g base_t_tb) base_t_a -- For every relation we add atleast 1 one index with all columns. getFullSearchesForRelations :: TypedefInfo -> [SearchFact] getFullSearchesForRelations defInfo = [ SearchOn r . toSearchSignature $ unsafeLookup r | r <- Map.keys defInfo ] where unsafeLookup r = fromJust $ Map.lookup r defInfo toSearchSignature = SearchSignature . Set.fromList . columnsFor data NormalizedEquality = NormalizedEquality Alias Column RA deriving Show extractEqualities :: RAF (RA, [NormalizedEquality]) -> [NormalizedEquality] extractEqualities = \case CompareOpF _ Equals (lhs, _) (rhs, _) | isDefined lhs && isDefined rhs -> toEqualities lhs rhs raf -> foldMap snd raf where isDefined = \case Undef {} -> False _ -> True toEqualities lhs rhs = case (lhs, rhs) of (ColumnIndex _ lA lCol, ColumnIndex _ rA rCol) -> [ NormalizedEquality lA lCol rhs , NormalizedEquality rA rCol lhs ] (ColumnIndex _ lA lCol, _) -> [NormalizedEquality lA lCol rhs] (_, ColumnIndex {}) -> toEqualities rhs lhs _ -> mempty normalizedEqToConstraints :: NormalizedEquality -> [(Relation, Column)] normalizedEqToConstraints = \case NormalizedEquality a1 c1 ra -> case ra of ColumnIndex _ a2 c2 -> [(a1, c1), (a2, c2)] _ -> [(a1, c1)] columnsForRelation :: Relation -> (Relation, Column) -> Maybe Column columnsForRelation r (r', col) | r == r' = Just col | otherwise = Nothing solve :: [SearchFact] -> SearchMap solve facts = execState (traverse solveOne $ sort facts) mempty where solveOne = \case SearchOn r signature -> modify (Map.insertWith (<>) r (one signature)) Related r1 r2 -> do signatures1 <- gets (Map.findWithDefault mempty r1) signatures2 <- gets (Map.findWithDefault mempty r2) let combined = signatures1 <> signatures2 modify $ Map.insert r1 combined . Map.insert r2 combined buildGraph :: SearchSet -> SearchGraph buildGraph searchSet = vertices searches searches `overlay` edges [(l, r) | l <- searches, r <- searches, l `isSubsetOf` r] where searches = toList searchSet isSubsetOf (SearchSignature xs) (SearchSignature ys) = xs `Set.isProperSubsetOf` ys getChainsFromMatching :: SearchGraph -> SearchMatching -> Set SearchChain getChainsFromMatching g m = let (covered, uncovered) = List.partition (`leftCovered` m) $ leftVertexList g uncoveredChains = map one uncovered coveredChains = map (\n -> NE.toNonEmpty $ getChain (pure n) n) covered in Set.fromList $ uncoveredChains <> coveredChains where leftCovered :: Ord a => a -> Matching a b -> Bool leftCovered a = Map.member a . pairOfLeft -- getChain alternates between U and V side of the bipartite graph -- A lookup is done on V side: -- - if it finds no match, we have reached end of the chain -- - Otherwise, we found the next node in the chain, and use -- this node to find rest of the chain. getChain acc u = case Map.lookup u (pairOfLeft m) of Nothing -> -- Longest chain at end, needed in indexForChain acc Just v -> -- Implicitly swap U and V side by passing in v as u: getChain (NE.snoc acc v) v indicesFromChains :: SearchSet -> Set SearchChain -> Map SearchSignature Index indicesFromChains (Set.toList -> searchSet) (Set.toList -> chains) = Map.fromList [ (signature, indexForChain chain) | chain <- chains , signature <- searchSet , signature `elem` chain ] -- NOTE: assumes chain is sorted from shortest to longest indexForChain :: SearchChain -> Index indexForChain chain = Index $ foldMap Set.toList columns where SearchSignature shortest :| rest = chain diffColumns = zipWith columnDiff rest (toList chain) columns = shortest : diffColumns columnDiff (SearchSignature long) (SearchSignature short) = long Set.\\ short columnsFor :: [a] -> [Int] columnsFor = zipWith const [0..] definedColumnsFor :: [RA] -> [Int] definedColumnsFor values = catMaybes $ zipWith f [0..] values where f c = \case Undef {} -> Nothing _ -> Just c ================================================ FILE: lib/Eclair/RA/Lower.hs ================================================ module Eclair.RA.Lower ( compileToEIR ) where import Prelude import Data.Maybe (fromJust) import qualified Data.List as List import qualified Data.Map as Map import qualified Data.Set as Set import Eclair.Common.Id import Eclair.Common.Literal import Eclair.Comonads hiding (Quad) import Eclair.TypeSystem import Eclair.AST.Transforms.ReplaceStrings (StringMap) import Eclair.RA.Codegen import Eclair.EIR.IR (EIR) import Eclair.RA.IR (RA) import Eclair.RA.IndexSelection import qualified Eclair.EIR.IR as EIR import qualified Eclair.RA.IR as RA import qualified Eclair.LLVM.Metadata as M compileToEIR :: StringMap -> TypedefInfo -> RA -> EIR compileToEIR stringMap typedefInfo ra = let (indexMap, getIndexForSearch) = runIndexSelection typedefInfo ra containerInfos' = getContainerInfos indexMap typedefInfo end = "the.end" lowerState = mkLowerState typedefInfo indexMap getIndexForSearch (map fst containerInfos') end moduleStmts :: [CodegenM EIR] moduleStmts = [ declareProgram $ map (\((r, _), m) -> (r, m)) containerInfos' , compileInit stringMap , compileDestroy , compileRun ra ] in EIR.Module $ map (runCodegen lowerState) moduleStmts compileInit :: StringMap -> CodegenM EIR compileInit stringMap = do program <- var "program" let symbolTable = fieldAccess program 0 symbolTableInitAction = primOp EIR.SymbolTableInit [symbolTable] relationInitActions <- forEachRelation program $ \(r, idx) relationPtr -> call r idx EIR.InitializeEmpty [relationPtr] let addSymbolActions = toSymbolTableInsertActions symbolTable stringMap initActions = symbolTableInitAction : relationInitActions <> addSymbolActions apiFn "eclair_program_init" [] (EIR.Pointer EIR.Program) $ assign program heapAllocProgram : initActions -- Open question: if some facts are known at compile time, search for derived facts up front? <> [ ret program ] toSymbolTableInsertActions :: CodegenM EIR -> StringMap -> [CodegenM EIR] toSymbolTableInsertActions symbolTable stringMap = map (doInsert . fst) $ sortWith snd $ Map.toList stringMap where doInsert symbol = primOp EIR.SymbolTableInsert [symbolTable, litStr symbol] litStr = pure . EIR.Lit . LString compileDestroy :: CodegenM EIR compileDestroy = do let program = fnArg 0 symbolTableDestroyAction = primOp EIR.SymbolTableDestroy [fieldAccess program 0] relationDestroyActions <- forEachRelation program $ \(r, idx) relationPtr -> call r idx EIR.Destroy [relationPtr] let destroyActions = symbolTableDestroyAction : relationDestroyActions apiFn "eclair_program_destroy" [EIR.Pointer EIR.Program] EIR.Void $ destroyActions <> [ freeProgram program ] compileRun :: RA -> CodegenM EIR compileRun ra = do apiFn "eclair_program_run" [EIR.Pointer EIR.Program] EIR.Void [generateProgramInstructions ra] generateProgramInstructions :: RA -> CodegenM EIR generateProgramInstructions = gcata (distribute extractEqualities) $ \case RA.ModuleF _ (map extract -> actions) -> block actions RA.ParF _ (map extract -> actions) -> parallel actions RA.SearchF _ r alias clauses (extract -> action) -> do let eqsInSearch = foldMap tSnd clauses eqs = concatMap normalizedEqToConstraints eqsInSearch idx <- idxFromConstraints r alias eqs let relationPtr = lookupRelationByIndex r idx isConstrain = \case RA.CompareOp {} -> True _ -> False queryClauses = map extract $ filter ((not . isConstrain) . tFst) clauses query = List.foldl1' and' queryClauses (initBoundValues, lbValue, ubValue) <- initValue r idx alias eqsInSearch block [ initBoundValues , rangeQuery r idx relationPtr lbValue ubValue $ \iter -> do current <- var "current" block [ assign current $ call r idx EIR.IterCurrent [iter] , do currentValue <- current case length queryClauses of 0 -> -- No query to check: always matches withUpdatedAlias alias currentValue action _ -> do withSearchState alias currentValue $ withUpdatedAlias alias currentValue $ if' query action ] ] RA.ProjectF _ r (map extract -> unresolvedValues) -> do values <- sequence unresolvedValues let values' = map pure values indices <- indexesForRelation r var' <- var "value" let -- NOTE: for allocating a value, the index does not matter -- (a value is always represented as [N x i32] internally) -- This saves us doing a few stack allocations. firstIdx = fromJust $ viaNonEmpty head indices allocValue = assign var' $ stackAlloc r firstIdx EIR.Value assignStmts = zipWith (assign . fieldAccess var') [0..] values' insertStmts = flip map indices $ \idx -> -- NOTE: The insert function is different for each r + idx combination though! call r idx EIR.Insert [lookupRelationByIndex r idx, var'] block $ allocValue : assignStmts <> insertStmts RA.PurgeF _ r -> block =<< relationUnaryFn r EIR.Purge RA.SwapF _ r1 r2 -> block =<< relationBinFn r1 r2 EIR.Swap RA.MergeF _ r1 r2 -> do -- NOTE: r1 = from/src, r2 = to/dst -- TODO: which idx? just select first matching? or idx on all columns? idxR1 <- fromJust . viaNonEmpty head <$> indexesForRelation r1 let relation1Ptr = lookupRelationByIndex r1 idxR1 indices2 <- indexesForRelation r2 block $ flip map indices2 $ \idxR2 -> do beginIter <- var "begin_iter" endIter <- var "end_iter" let relation2Ptr = lookupRelationByIndex r2 idxR2 block [ assign beginIter $ stackAlloc r1 idxR1 EIR.Iter , assign endIter $ stackAlloc r1 idxR1 EIR.Iter , call r1 idxR1 EIR.IterBegin [relation1Ptr, beginIter] , call r1 idxR1 EIR.IterEnd [relation1Ptr, endIter] , call r2 idxR2 (EIR.InsertRange r1 idxR1) [relation2Ptr, beginIter, endIter] ] RA.LoopF _ (map extract -> actions) -> do end <- labelId "loop.end" block [withEndLabel end $ loop actions, label end] RA.IfF _ (extract -> condition) (extract -> action) -> do if' condition action RA.PrimOpF _ (RA.BuiltinOp op) (map extract -> args) -> case args of [lhs, rhs] -> do let toArithmetic = case op of RA.Plus -> plus RA.Minus -> minus RA.Multiply -> multiply RA.Divide -> divide toArithmetic lhs rhs _ -> panic "Unexpected case in 'generateProgramInstructions' while lowering RA!" RA.PrimOpF _ (RA.ExternOp opName) (map extract -> args) -> do mkExternOp opName args RA.CompareOpF _ op (extract -> lhs) (extract -> rhs) -> do let toComparison = case op of RA.Equals -> equals RA.NotEquals -> notEquals RA.LessThan -> lessThan RA.LessOrEqual -> lessOrEqual RA.GreaterThan -> greaterThan RA.GreaterOrEqual -> greaterOrEqual toComparison lhs rhs RA.ExitF _ rs -> do end <- endLabel <$> getLowerState foldl' f (jump end) =<< traverse getFirstFieldOffset rs where f inner field = do (r, idx) <- getContainerInfoByOffset field let programPtr = fnArg 0 relationPtr = fieldAccess programPtr field isEmpty = call r idx EIR.IsEmpty [relationPtr] if' isEmpty inner RA.LitF _ x -> lit x RA.NotElemF _ r values -> do if containsUndefined $ map tFst values then existenceCheckPartialSearch else existenceCheckTotalSearch where containsUndefined = isJust . find (\case RA.Undef {} -> True _ -> False) existenceCheckPartialSearch = do getIndexForSearch <- idxSelector <$> getLowerState lbValue <- var "lower_bound_value" ubValue <- var "upper_bound_value" beginIter <- var "begin_iter" endIter <- var "end_iter" let values' = map tFst values cs = definedColumnsFor values' signature = SearchSignature $ Set.fromList cs idx = getIndexForSearch r signature lbValueWithCols = zipWith (curry $ second $ lowerConstrainValue (lit 0x00000000)) [0..] values' ubValueWithCols = zipWith (curry $ second $ lowerConstrainValue (lit 0xffffffff)) [0..] values' lbAssignStmts = block $ map (\(i, val) -> assign (fieldAccess lbValue i) val) lbValueWithCols ubAssignStmts = block $ map (\(i, val) -> assign (fieldAccess ubValue i) val) ubValueWithCols relationPtr = lookupRelationByIndex r idx block [ assign lbValue $ stackAlloc r idx EIR.Value , lbAssignStmts , assign ubValue $ stackAlloc r idx EIR.Value , ubAssignStmts , assign beginIter $ stackAlloc r idx EIR.Iter , assign endIter $ stackAlloc r idx EIR.Iter , call r idx EIR.IterLowerBound [relationPtr, lbValue, beginIter] , call r idx EIR.IterUpperBound [relationPtr, ubValue, endIter] , call r idx EIR.IterIsEqual [beginIter, endIter] ] existenceCheckTotalSearch = do let columnValues = map extract values value <- var "value" let idx = mkFindIndex columnValues relationPtr = lookupRelationByIndex r idx allocValue = assign value $ stackAlloc r idx EIR.Value containsVar <- var "contains_result" let assignActions = zipWith (assign . fieldAccess value) [0..] columnValues containsAction = assign containsVar $ call r idx EIR.Contains [relationPtr, value] block $ allocValue : assignActions <> [containsAction, not' containsVar] RA.ColumnIndexF _ a' col -> ask >>= \case Search a value _ -> if a == a' then getColumn value col else do currentAliasValue <- lookupAlias a' getColumn currentAliasValue col Normal _ -> do currentAliasValue <- lookupAlias a' getColumn currentAliasValue col where getColumn value = fieldAccess (pure value) RA.UndefF {} -> panic "Undef should not occur when lowering to EIR!" where distribute :: Corecursive t => (Base t (t, a) -> a) -> (Base t (Triple t a b) -> Triple t a (Base t b)) distribute f m = let base_t_t = map tFst m base_t_ta = map (tFst &&& tSnd) m base_t_b = map tThd m in Triple (embed base_t_t) (f base_t_ta) base_t_b rangeQuery :: Relation -> Index -> CodegenM EIR -> CodegenM EIR -> CodegenM EIR -> (CodegenM EIR -> CodegenM EIR) -> CodegenM EIR rangeQuery r idx relationPtr lbValue ubValue loopAction = do beginIter <- var "begin_iter" endIter <- var "end_iter" endLabel' <- labelId "range_query.end" let allocBeginIter = assign beginIter $ stackAlloc r idx EIR.Iter allocEndIter = assign endIter $ stackAlloc r idx EIR.Iter initLB = call r idx EIR.IterLowerBound [relationPtr, lbValue, beginIter] initUB = call r idx EIR.IterUpperBound [relationPtr, ubValue, endIter] advanceIter = call r idx EIR.IterNext [beginIter] isAtEnd = call r idx EIR.IterIsEqual [beginIter, endIter] stopIfFinished = if' isAtEnd (jump endLabel') loopStmts = [stopIfFinished, loopAction beginIter, advanceIter] block [allocBeginIter, allocEndIter, initLB, initUB, loop loopStmts, label endLabel'] -- NOTE: only supports unsigned integers for now! initValue :: Relation -> Index -> RA.Alias -> [NormalizedEquality] -> CodegenM (CodegenM EIR, CodegenM EIR, CodegenM EIR) initValue r idx a eqs = do let r' = stripIdPrefixes r typeInfo <- fromJust . Map.lookup r' . typeEnv <$> getLowerState lbValue <- var "lower_bound_value" ubValue <- var "upper_bound_value" let columnNrs = take (length typeInfo) [0..] lbAllocValue = assign lbValue $ stackAlloc r idx EIR.Value ubAllocValue = assign ubValue $ stackAlloc r idx EIR.Value -- TODO stack allocate all values so they are not computed twice? lbValuesWithCols = [(nr, x) | nr <- columnNrs, let x = constrain (lit 0x00000000) nr] ubValuesWithCols = [(nr, x) | nr <- columnNrs, let x = constrain (lit 0xffffffff) nr] lbAssignStmts = map (\(i, val) -> assign (fieldAccess lbValue i) val) lbValuesWithCols ubAssignStmts = map (\(i, val) -> assign (fieldAccess ubValue i) val) ubValuesWithCols pure (block $ lbAllocValue : ubAllocValue : lbAssignStmts <> ubAssignStmts, lbValue, ubValue) where constrain bound col = case find (\(NormalizedEquality a' col' _) -> a == a' && col == col') eqs of Nothing -> bound Just (NormalizedEquality _ _ ra) -> lowerConstrainValue bound ra -- let NormalizedEquality _ _ ra = fromJust $ find (\(NormalizedEquality a' col' _) -> a == a' && col == col') eqs -- in lowerConstrainValue bound ra lowerConstrainValue :: CodegenM EIR -> RA -> CodegenM EIR lowerConstrainValue bound = \case RA.Lit _ x -> lit x RA.Undef _ -> bound RA.ColumnIndex _ a' col' -> fieldAccess (lookupAlias a') col' RA.PrimOp _ (RA.BuiltinOp op) [lhs, rhs] -> mkArithOp op (lowerConstrainValue bound lhs) (lowerConstrainValue bound rhs) RA.PrimOp _ (RA.ExternOp opName) args -> mkExternOp opName $ map (lowerConstrainValue bound) args _ -> panic "Unsupported initial value while lowering to RA" forEachRelation :: CodegenM EIR -> ((Relation, Index) -> CodegenM EIR -> CodegenM EIR) -> CodegenM [CodegenM EIR] forEachRelation program f = do cis <- relInfo . cgInfo <$> getLowerState pure $ zipWith doCall [1..] cis where doCall fieldOffset relationInfo = f relationInfo (fieldAccess program fieldOffset) relationUnaryFn :: Relation -> EIR.Function -> CodegenM [CodegenM EIR] relationUnaryFn r fn' = forEachIndex r $ \idx -> do call r idx fn' [lookupRelationByIndex r idx] -- NOTE: assumes r1 and r2 have same underlying representation -- (guaranteed by earlier compiler stages) relationBinFn :: Relation -> Relation -> EIR.Function -> CodegenM [CodegenM EIR] relationBinFn r1 r2 fn' = forEachIndex r1 $ \idx -> do call r1 idx fn' [ lookupRelationByIndex r1 idx , lookupRelationByIndex r2 idx ] forEachIndex :: Relation -> (Index -> CodegenM EIR) -> CodegenM [CodegenM EIR] forEachIndex r f = do indices <- indexesForRelation r pure $ map f indices getContainerInfos :: IndexMap -> TypedefInfo -> [((Relation, Index), M.Metadata)] getContainerInfos indexMap typedefInfo = containerInfos' where combinations r idxs = (r,) <$> Set.toList idxs toContainerInfo r idx = let r' = stripIdPrefixes r meta = M.mkMeta idx $ fromJust $ Map.lookup r' typedefInfo in ((r, idx), meta) storesList = Map.foldMapWithKey combinations indexMap containerInfos' = map (uncurry toContainerInfo) storesList -- NOTE: only use this index for a total search (all columns constrained) mkFindIndex :: [a] -> Index mkFindIndex = Index . zipWith const [0..] indexesForRelation :: Relation -> CodegenM [Index] indexesForRelation r = Set.toList . fromJust . Map.lookup r . idxMap <$> getLowerState ================================================ FILE: lib/Eclair/RA/Transforms/HoistConstraints.hs ================================================ module Eclair.RA.Transforms.HoistConstraints ( transform ) where import Eclair.Transform import Eclair.RA.IR import Eclair.Comonads import Eclair.Common.Id import Eclair.Common.Location (NodeId(..)) import Data.List (partition) data HoistState = HoistState { seenAliases :: [Id] , remainingConstraints :: [(NodeId, [Id], RA)] } transform :: Transform RA RA transform = let beginState = HoistState mempty mempty in Transform $ usingReaderT beginState . rewrite where rewrite = gcata (distribute collectAliases collectConstraints) hoistConstraints distribute :: Corecursive t => (Base t a -> a) -> (Base t (t, a, b) -> b) -> (Base t (Quad t a b c) -> Quad t a b (Base t c)) distribute f g m = let base_t_t = map qFirst m base_t_a = map qSecond m base_t_tab = map (\q -> (qFirst q, qSecond q, qThird q)) m base_t_c = map qFourth m in Quad (embed base_t_t) (f base_t_a) (g base_t_tab) base_t_c collectAliases = \case ColumnIndexF _ alias _ -> one alias raf -> fold raf collectConstraints = \case IfF nodeId condition inner -> do let cond = getCondition nodeId condition cond : getValues inner raf -> foldMap getValues raf where getCondition nodeId (ra, aliases, _) = (nodeId, aliases, ra) getValues (_, _, values) = values hoistConstraints = \case SearchF nodeId r a cs inner -> do isFirstSearch <- asks (null . seenAliases) let innerConstraints = qThird inner withUpdatedEnv = local $ if isFirstSearch then const $ HoistState [a] innerConstraints else \s -> s { seenAliases = a : seenAliases s } withUpdatedEnv $ do HoistState aliases constraints <- ask -- partition all the things! let (covered, rest) = partition (coversAllAliases aliases) constraints (indexable, nonIndexable) = partition supportsIndex covered (relationChecks, nonRelationChecks) = partition dependsOnARelation nonIndexable cs' = map qFirst cs <> map (\(_, _, c) -> c) indexable addNonRelationConstraints = nonRelationChecks & map (\(nodeId', _, cond) -> If nodeId' cond) & foldl' (.) id addNonIndexableConstraints = relationChecks & map (\(nodeId', _, cond) -> If nodeId' cond) & foldl' (.) id let transformSearch = addNonRelationConstraints . Search nodeId r a cs' . addNonIndexableConstraints local (\s -> s { remainingConstraints = rest }) $ do transformSearch <$> qFourth inner ProjectF nodeId r vals -> do -- NOTE: remaining conditions are not removed here, to support multiple projections in the future. remaining <- asks (map (\(nodeId', _, cond) -> (nodeId', cond)) . remainingConstraints) projectStmt <- Project nodeId r <$> traverse qFourth vals pure $ foldr (\(nodeId', cond) inner -> If nodeId' cond inner) projectStmt remaining IfF _ _ inner -> -- NOTE: constraints are already in the state and handled in -- project and search, so we don't do anything here. qFourth inner raf -> embed <$> traverse qFourth raf coversAllAliases aliases (_, as, _) = all (`elem` aliases) as supportsIndex = \case (_, _, ra) -> isIndexable ra isIndexable = \case CompareOp _ op (ColumnIndex _ a1 _) (ColumnIndex _ a2 _) -> isIndexableOp op && a1 /= a2 -- Only other relations or constants are allowed to appear in a value. -- Other relations besides the current alias are "constant" due to the -- way the algorithm works. CompareOp _ op (ColumnIndex _ a1 _) value -> isIndexableOp op && a1 `notElem` cata collectAliases value CompareOp _ op value (ColumnIndex _ a1 _) -> isIndexableOp op && a1 `notElem` cata collectAliases value _ -> False -- TODO add pass for RA so <= and >= can be used in an index isIndexableOp = (== Equals) dependsOnARelation (_, _, ra) = let dependencies = flip cata ra $ \case ColumnIndexF _ a _ -> [a] raf -> fold raf in not $ null dependencies ================================================ FILE: lib/Eclair/RA/Transforms.hs ================================================ module Eclair.RA.Transforms ( simplify ) where import Eclair.Common.Location (NodeId(..)) import Eclair.RA.IR import Eclair.Transform import qualified Eclair.RA.Transforms.HoistConstraints as HoistConstraints simplify :: RA -> RA simplify = runTransform (NodeId 0) HoistConstraints.transform ================================================ FILE: lib/Eclair/Souffle/IR.hs ================================================ module Eclair.Souffle.IR ( Souffle(..) , ConversionError(..) , toSouffle ) where import qualified Eclair.AST.IR as AST import Prettyprinter import Eclair.Common.Pretty import Eclair.Common.Literal import Eclair.Common.Id import Eclair.Common.Location (NodeId) import Eclair.Common.Operator type AST = AST.AST data Type = Unsigned | Symbol data UsageMode = Input | Output data Souffle = Lit Literal | Var Id | BinOp ArithmeticOp Souffle Souffle | Constraint LogicalOp Souffle Souffle | Rule Id [Souffle] [Souffle] | Not Souffle | Atom Id [Souffle] | DeclareType Id [(Maybe Id, Type)] | DeclareUsage Id UsageMode | Module [Souffle] data ConversionError loc = HoleNotSupported loc | UnsupportedType loc AST.Type | UnsupportedCase loc deriving Functor toSouffle :: AST -> Either (ConversionError NodeId) Souffle toSouffle = map Module . cata f where f :: AST.ASTF (Either (ConversionError NodeId) [Souffle]) -> Either (ConversionError NodeId) [Souffle] f = \case AST.ModuleF _ decls -> do map mconcat $ sequence decls AST.DeclareTypeF nodeId name tys usageMode -> do souffleTys <- traverse (toSouffleArg nodeId) tys let declType = DeclareType name souffleTys usageDecls = case usageMode of AST.Input -> one $ DeclareUsage name Input AST.Output -> one $ DeclareUsage name Output AST.InputOutput -> [ DeclareUsage name Input , DeclareUsage name Output ] AST.Internal -> mempty pure $ declType : usageDecls AST.AtomF _ name args -> do args' <- mconcat <$> sequence args pure $ one $ Atom name args' AST.RuleF _ name args clauses -> do args' <- mconcat <$> sequence args clauses' <- mconcat <$> sequence clauses pure $ one $ Rule name args' clauses' AST.VarF _ name -> pure $ one $ Var name AST.LitF _ lit -> pure $ one $ Lit lit AST.HoleF nodeId -> throwError $ HoleNotSupported nodeId AST.BinOpF _ op lhs rhs -> do lhs' <- lhs rhs' <- rhs pure $ BinOp op <$> lhs' <*> rhs' AST.ConstraintF _ op lhs rhs -> do lhs' <- lhs rhs' <- rhs pure $ Constraint op <$> lhs' <*> rhs' AST.NotF _ inner -> do inner' <- inner pure $ Not <$> inner' astf -> let nodeId = AST.getNodeIdF astf in throwError $ UnsupportedCase nodeId toSouffleArg :: NodeId -> (Maybe Id, AST.Type) -> Either (ConversionError NodeId) (Maybe Id, Type) toSouffleArg nodeId = \case (mName, AST.U32) -> pure (mName, Unsigned) (mName, AST.Str) -> pure (mName, Symbol) ty -> throwError $ UnsupportedType nodeId $ snd ty instance Pretty Type where pretty = \case Unsigned -> "unsigned" Symbol -> "symbol" data RenderPosition = TopLevel | Nested instance Pretty Souffle where pretty souffleIR = runReader (pretty' souffleIR) TopLevel where pretty' = \case Lit x -> pure $ pretty x Var v -> pure $ pretty v BinOp op lhs rhs -> do lhs' <- pretty' lhs rhs' <- pretty' rhs pure $ parens $ lhs' <+> pretty op <+> rhs' Constraint op lhs rhs -> do lhs' <- pretty' lhs rhs' <- pretty' rhs pure $ lhs' <+> pretty op <+> rhs' Not clause -> ("!" <>) <$> pretty' clause Atom name values -> do end <- ask <&> \case TopLevel -> "." Nested -> mempty values' <- traverse pretty' values pure $ pretty name <> parens (withCommas values') <> end Rule name values clauses -> do (values', clauses') <- local (const Nested) $ do (,) <$> traverse pretty' values <*> traverse pretty' clauses let separators = replicate (length clauses - 1) "," <> ["."] pure $ pretty name <> parens (withCommas values') <+> ":-" <> hardline <> indent 2 (vsep (zipWith (<>) clauses' separators)) DeclareType name args -> do let defaultArgNames = map (\col -> Id $ "arg_" <> show col) [0 :: Int ..] argNames = zipWith prettyArg args defaultArgNames argDocs = zipWith (\argName arg -> argName <> ":" <+> pretty (snd arg)) argNames args pure $ ".decl" <+> pretty name <> parens (withCommas argDocs) where prettyArg arg defaultArg = pretty $ fromMaybe defaultArg $ fst arg DeclareUsage name usageMode -> pure $ case usageMode of Input -> ".input" <+> pretty name Output -> ".output" <+> pretty name Module decls -> do decls' <- traverse pretty' decls pure $ vsep $ intersperse mempty decls' ================================================ FILE: lib/Eclair/Transform.hs ================================================ module Eclair.Transform ( Transform(..) , pureTransform , TransformM , runTransform , fixTransform , freshNodeId , RewriteRule , RewriteRuleT ) where import Eclair.Common.Location (NodeId(..)) -- A helper monad that provides a fresh supply of unused node IDs. newtype TransformM a = TransformM (State NodeId a) deriving (Functor, Applicative, Monad) via State NodeId -- | Generates a fresh 'NodeId'. freshNodeId :: TransformM NodeId freshNodeId = TransformM $ do node <- get modify $ \(NodeId y) -> NodeId (y + 1) pure node -- | The main type in this module. A transform is an effectful function from -- one type to another. Usually the type variables 'a' and 'b' represent IRs in -- the compiler. (These IRs can potentially be the same, which can be useful -- for creating "rewrite rules"). Transforms can be composed together using the -- instances and functions defined in this module. newtype Transform a b = Transform (a -> TransformM b) deriving (Semigroup, Monoid) via Ap (Kleisli TransformM a) b deriving (Category, Arrow) via Kleisli TransformM -- | Helper function for creating a transform that doesn't require any effects. pureTransform :: (a -> b) -> Transform a b pureTransform f = Transform $ pure . f -- | Converts a 'Transform' to an equivalent pure function. -- The 'NodeId' that is passed in is used as a starting point for generating -- additional IDs during the transform (if necessary). runTransform :: NodeId -> Transform ir1 ir2 -> ir1 -> ir2 runTransform nodeId (Transform f) ir1 = runTransformM (f ir1) where runTransformM :: TransformM a -> a runTransformM (TransformM m) = evalState m nodeId -- | A function that recursively keeps applying a 'Transform' until a fixpoint -- is reached and no more changes occur. fixTransform :: Eq ir => Transform ir ir -> Transform ir ir fixTransform (Transform f) = Transform $ fix $ \recur ir -> do ir' <- f ir if ir' == ir then pure ir' else recur ir' -- | Helper type synonym for a rewrite rule, making use of the recursion-schemes library. -- This is the simplest form of rewrite rule, which allows no extra effects -- except for the state managed by the 'Transform' itself. type RewriteRule ir = Base ir (TransformM ir) -> TransformM ir -- | Helper type synonym for a rewrite rule, making use of the recursion-schemes library. -- This type of rewrite rule allows adding additional effects via the -- monad transformer 't'. type RewriteRuleT t ir = Base ir (t TransformM ir) -> t TransformM ir ================================================ FILE: lib/Eclair/TypeSystem.hs ================================================ module Eclair.TypeSystem ( Type(..) , TypeError(..) , Context(..) , getContextLocation , TypeInfo(..) , DefinitionType(..) , TypedefInfo , ExternDefInfo , typeCheck ) where import qualified Data.Map as Map import qualified Data.IntMap as IntMap import qualified Data.DList as DList import Data.DList (DList) import qualified Data.DList.DNonEmpty as DNonEmpty import Data.DList.DNonEmpty (DNonEmpty) import Control.Monad.Extra import Eclair.AST.IR import Eclair.Common.Id import Eclair.Common.Location (NodeId) -- NOTE: This module contains a lot of partial functions due to the fact -- that the rest of the compiler relies heavily on recursion-schemes. -- This is however one place where I have not figured out how to apply a -- recursion-scheme here.. yet! -- -- TODO: Maybe a variant of a mutumorphism might work here? data DefinitionType = ConstraintType [Type] | FunctionType [Type] Type deriving (Eq, Show) type ExternDefInfo = Map Id DefinitionType type TypedefInfo = Map Id [Type] data TypeInfo = TypeInfo { infoTypedefs :: TypedefInfo -- NOTE: only typedefs are needed, no external definitions , resolvedTypes :: Map NodeId Type } deriving Show instance Semigroup TypeInfo where TypeInfo tdInfo1 resolved1 <> TypeInfo tdInfo2 resolved2 = TypeInfo (tdInfo1 <> tdInfo2) (resolved1 <> resolved2) instance Monoid TypeInfo where mempty = TypeInfo mempty mempty data Context loc = WhileChecking loc | WhileInferring loc | WhileUnifying loc deriving (Eq, Ord, Show, Functor) -- NOTE: for now, no actual types are checked since everything is a u32. data TypeError loc = UnknownConstraint loc Id | UnknownFunction loc Id | ArgCountMismatch Id (loc, Int) (loc, Int) | TypeMismatch loc Type Type (NonEmpty (Context loc)) -- 1st type is actual, 2nd is expected | UnificationFailure Type Type (NonEmpty (Context loc)) | HoleFound loc (NonEmpty (Context loc)) Type (Map Id Type) -- 1st is error location, 2nd is definition location. | UnexpectedFunctionType loc loc | UnexpectedConstraintType loc loc deriving (Eq, Ord, Show, Functor) -- Only used internally in this module. type Ctx = Context NodeId type TypeErr = TypeError NodeId type DefMap = Map Id (NodeId, DefinitionType) type UnresolvedHole = Type -> Map Id Type -> TypeErr data Env = Env { defs :: DefMap , tcContext :: DNonEmpty Ctx } -- State used to report type information back to the user (via LSP) data TrackingState = TrackingState { directlyResolvedTypes :: Map NodeId Type -- literals are immediately resolved , trackedVariables :: [(Id, NodeId)] } data CheckState = CheckState { typeEnv :: Map Id Type , substitution :: IntMap Type , varCounter :: Int , errors :: DList TypeErr , unresolvedHoles :: DList (Type, UnresolvedHole) -- The tracking state is not needed for the typechecking algorithm, -- but is used to report information back to the user (via LSP). , trackingState :: TrackingState } newtype TypeCheckM a = TypeCheckM (RWS Env () CheckState a) deriving (Functor, Applicative, Monad, MonadState CheckState, MonadReader Env) via (RWS Env () CheckState) typeCheck :: AST -> Either [TypeErr] TypeInfo typeCheck ast | null typeErrors = pure $ TypeInfo (map snd typeDefMap) resolvedTys | otherwise = throwError typeErrors where (typeDefMap, externDefMap) = cata (combine extractTypedefs extractExternDefs) ast defMap = map (map ConstraintType) typeDefMap <> externDefMap (typeErrors, resolvedTys) = checkDecls defMap ast combine f g = f . map fst &&& g . map snd extractTypedefs = \case DeclareTypeF nodeId name args _ -> one (name, (nodeId, map snd args)) AtomF {} -> mempty RuleF {} -> mempty astf -> fold astf extractExternDefs = \case ExternDefinitionF nodeId name args mRetTy -> let tys = map snd args in one (name, (nodeId, maybe (ConstraintType tys) (FunctionType tys) mRetTy)) AtomF {} -> mempty RuleF {} -> mempty astf -> fold astf -- TODO: try to merge with checkDecl? checkDecls :: DefMap -> AST -> ([TypeErr], Map NodeId Type) checkDecls defMap = \case Module _ decls -> -- NOTE: By using runM once per decl, each decl is checked with a clean state let results = map (\d -> runM (beginContext d) defMap $ typecheckDecl d) decls in bimap fold fold $ partitionEithers results _ -> panic "Unexpected AST node in 'checkDecls'" where beginContext d = WhileChecking (getNodeId d) typecheckDecl d = do checkDecl d processUnresolvedHoles checkDecl :: AST -> TypeCheckM () checkDecl ast = case ast of DeclareType {} -> pass ExternDefinition {} -> pass Atom nodeId name args -> do ctx <- getContext -- TODO find better way to update context only for non-top level atoms let isTopLevelFact = isTopLevel ctx updateCtx = if isTopLevelFact then id else addCtx updateCtx $ do lookupRelationType name >>= \case Just (nodeId', ConstraintType types) -> do checkArgCount name (nodeId', types) (nodeId, args) zipWithM_ checkExpr args types Just (nodeId', FunctionType {}) -> do emitError $ UnexpectedFunctionType nodeId nodeId' Nothing -> emitError $ UnknownConstraint nodeId name Rule nodeId name args clauses -> do lookupRelationType name >>= \case Just (nodeId', ConstraintType types) -> do checkArgCount name (nodeId', types) (nodeId, args) zipWithM_ checkExpr args types Just (nodeId', FunctionType {}) -> do emitError $ UnexpectedFunctionType nodeId nodeId' Nothing -> emitError $ UnknownConstraint nodeId name traverse_ checkDecl clauses Constraint nodeId op lhs rhs -> addCtx $ do if isEqualityOp op then do lhsTy <- inferExpr lhs rhsTy <- inferExpr rhs -- NOTE: Because inferred types of vars can contain unification variables, -- we need to try and unify them. unifyType nodeId lhsTy rhsTy else do -- Comparison => both sides need to be numbers checkExpr lhs U32 checkExpr rhs U32 Not _ clause -> addCtx $ do checkDecl clause _ -> panic "Unexpected case in 'checkDecl'" where addCtx = addContext (WhileChecking $ getNodeId ast) isTopLevel = (== 1) . length checkArgCount :: Id -> (NodeId, [Type]) -> (NodeId, [AST]) -> TypeCheckM () checkArgCount name (nodeIdTypedef, types) (nodeId, args) = do let actualArgCount = length args expectedArgCount = length types when (actualArgCount /= expectedArgCount) $ emitError $ ArgCountMismatch name (nodeIdTypedef, expectedArgCount) (nodeId, actualArgCount) checkExpr :: AST -> Type -> TypeCheckM () checkExpr ast expectedTy = do let nodeId = getNodeId ast addContext (WhileChecking nodeId) $ case ast of l@Lit {} -> do actualTy <- inferExpr l -- NOTE: No need to call 'unifyType', types of literals are always concrete types. when (actualTy /= expectedTy) $ do ctx <- getContext emitError $ TypeMismatch nodeId actualTy expectedTy ctx PWildcard {} -> -- NOTE: no checking happens for wildcards, since they always refer to -- unique vars (and thus cannot cause type errors) trackDirectlyResolvedType nodeId expectedTy Var _ var -> do trackVariable nodeId var lookupVarType var >>= \case Nothing -> -- TODO: also store in context/state a variable was bound here for better errors? bindVar var expectedTy Just actualTy -> do when (actualTy /= expectedTy) $ do ctx <- getContext emitError $ TypeMismatch nodeId actualTy expectedTy ctx Hole {} -> do holeTy <- emitHoleFoundError nodeId unifyType nodeId holeTy expectedTy BinOp _ _ lhs rhs -> do -- Arithmetic expressions always need to be numbers. checkExpr lhs U32 checkExpr rhs U32 Atom _ name args -> do lookupRelationType name >>= \case Just (nodeId', FunctionType types actualRetTy) -> do checkArgCount name (nodeId', types) (nodeId, args) zipWithM_ checkExpr args types when (actualRetTy /= expectedTy) $ do ctx <- getContext emitError $ TypeMismatch nodeId actualRetTy expectedTy ctx Just (nodeId', ConstraintType {}) -> do emitError $ UnexpectedConstraintType nodeId nodeId' Nothing -> emitError $ UnknownFunction nodeId name e -> do -- Basically an unexpected / unhandled case => try inferring as a last resort. actualTy <- inferExpr e unifyType (getNodeId e) actualTy expectedTy inferExpr :: AST -> TypeCheckM Type inferExpr ast = do let nodeId = getNodeId ast addContext (WhileInferring nodeId) $ case ast of Lit _ lit -> do let ty = case lit of LNumber {} -> U32 LString {} -> Str trackDirectlyResolvedType nodeId ty pure ty Var _ var -> do trackVariable nodeId var lookupVarType var >>= \case Nothing -> do ty <- freshType -- This introduces potentially a unification variable into the type environment, but we need this -- for complicated rule bodies where a variable occurs in more than one place. -- (Otherwise a variable is treated as new each time). bindVar var ty pure ty Just ty -> pure ty Hole {} -> emitHoleFoundError nodeId BinOp _ _ lhs rhs -> do -- Arithmetic expressions always need to be numbers. checkExpr lhs U32 checkExpr rhs U32 pure U32 Atom _ name args -> do lookupRelationType name >>= \case Just (nodeId', FunctionType types retTy) -> do checkArgCount name (nodeId', types) (nodeId, args) zipWithM_ checkExpr args types pure retTy Just (nodeId', ConstraintType {}) -> do emitError $ UnexpectedConstraintType nodeId nodeId' -- We generate a fresh type which will always unify, but typechecking will fail anyway freshType Nothing -> do emitError $ UnknownFunction nodeId name -- We generate a fresh type which will always unify, but typechecking will fail anyway freshType _ -> panic "Unexpected case in 'inferExpr'" runM :: Ctx -> DefMap -> TypeCheckM a -> Either [TypeErr] (Map NodeId Type) runM ctx defMap action = let (TypeCheckM m) = action *> computeTypeInfoById trackState = TrackingState mempty mempty tcState = CheckState mempty mempty 0 mempty mempty trackState env = Env defMap (pure ctx) (typeInfoById, endState, _) = runRWS m env tcState errs = toList . errors $ endState in if null errs then Right typeInfoById else Left errs emitError :: TypeErr -> TypeCheckM () emitError err = modify $ \s -> s { errors = DList.snoc (errors s) err } bindVar :: Id -> Type -> TypeCheckM () bindVar var ty = modify $ \s -> s { typeEnv = Map.insert var ty (typeEnv s) } lookupRelationType :: Id -> TypeCheckM (Maybe (NodeId, DefinitionType)) lookupRelationType name = asks (Map.lookup name . defs) -- A variable can either be a concrete type, or a unification variable. -- In case of unification variable, try looking it up in current substitution. -- As a result, this will make it so variables in a rule body with same name have the same type. -- NOTE: if this ends up not being powerful enough, need to upgrade to SCC + a solver for each group of "linked" variables. lookupVarType :: Id -> TypeCheckM (Maybe Type) lookupVarType var = do (maybeVarTy, subst) <- gets (Map.lookup var . typeEnv &&& substitution) case maybeVarTy of Just (TUnknown u) -> pure $ IntMap.lookup u subst maybeTy -> pure maybeTy -- Generates a fresh unification variable. freshType :: TypeCheckM Type freshType = do x <- gets varCounter modify $ \s -> s { varCounter = varCounter s + 1 } pure $ TUnknown x -- | Tries to unify 2 types, emitting an error in case it fails to unify. unifyType :: NodeId -> Type -> Type -> TypeCheckM () unifyType nodeId t1 t2 = addContext (WhileUnifying nodeId) $ do subst <- gets substitution unifyType' (substituteType subst t1) (substituteType subst t2) where unifyType' ty1 ty2 = case (ty1, ty2) of (U32, U32) -> pass (Str, Str) -> pass (TUnknown x, TUnknown y) | x == y -> pass (TUnknown u, ty) -> updateSubst u ty (ty, TUnknown u) -> updateSubst u ty _ -> do ctx <- getContext emitError $ UnificationFailure ty1 ty2 ctx -- Update the current substitutions updateSubst :: Int -> Type -> TypeCheckM () updateSubst u ty = modify $ \s -> s { substitution = IntMap.insert u ty (substitution s) } -- Recursively applies a substitution to a type substituteType :: IntMap Type -> Type -> Type substituteType subst = \case U32 -> U32 Str -> Str TUnknown unknown -> case IntMap.lookup unknown subst of Nothing -> TUnknown unknown Just (TUnknown unknown') | unknown == unknown' -> TUnknown unknown Just ty -> substituteType subst ty computeTypeInfoById :: TypeCheckM (Map NodeId Type) computeTypeInfoById = do ts <- gets trackingState let vars = Map.toList $ Map.fromListWith (<>) $ one <<$>> trackedVariables ts resolvedVarTypes <- flip concatMapM vars $ \(var, nodeIds) -> do varTy <- lookupVarType var pure $ mapMaybe (\nodeId -> (nodeId,) <$> varTy) nodeIds pure $ directlyResolvedTypes ts <> Map.fromList resolvedVarTypes addContext :: Ctx -> (TypeCheckM a -> TypeCheckM a) addContext ctx = local $ \env -> env { tcContext = tcContext env `DNonEmpty.snoc` ctx } getContext :: TypeCheckM (NonEmpty Ctx) getContext = asks (DNonEmpty.toNonEmpty . tcContext) getContextLocation :: Context loc -> loc getContextLocation = \case WhileChecking loc -> loc WhileInferring loc -> loc WhileUnifying loc -> loc emitHoleFoundError :: NodeId -> TypeCheckM Type emitHoleFoundError nodeId = do ty <- freshType -- We generate a fresh type, and use this later to figure out the type of the hole. ctx <- getContext modify' $ \s -> s { unresolvedHoles = DList.snoc (unresolvedHoles s) (ty, HoleFound nodeId ctx) } pure ty processUnresolvedHoles :: TypeCheckM () processUnresolvedHoles = do holes <- gets (toList . unresolvedHoles) unless (null holes) $ do (env, subst) <- gets (typeEnv &&& substitution) let solvedEnv = map (substituteType subst) env forM_ holes $ \(holeTy, hole) -> emitError $ hole (substituteType subst holeTy) solvedEnv modify' $ \s -> s { unresolvedHoles = mempty } trackDirectlyResolvedType :: NodeId -> Type -> TypeCheckM () trackDirectlyResolvedType nodeId ty = do ts <- gets trackingState let ts' = ts { directlyResolvedTypes = directlyResolvedTypes ts <> one (nodeId, ty) } modify $ \s -> s { trackingState = ts' } trackVariable :: NodeId -> Id -> TypeCheckM () trackVariable nodeId var = do ts <- gets trackingState let ts' = ts { trackedVariables = (var, nodeId) : trackedVariables ts } modify $ \s -> s { trackingState = ts' } ================================================ FILE: lib/Eclair.hs ================================================ {-# LANGUAGE GADTs, StandaloneDeriving #-} module Eclair ( parse , semanticAnalysis , typeCheck , emitDiagnostics , emitTransformedAST , emitRA , emitTransformedRA , emitEIR , emitLLVM , emitSouffle , Parameters(..) , EclairError(..) , handleErrorsCLI ) where import Control.Exception import Eclair.AST.Lower import Eclair.RA.Lower import Eclair.EIR.Lower import Eclair.Parser import Eclair.Common.Pretty import Eclair.Error import Eclair.Common.Id import Eclair.Common.Location import Eclair.Common.Extern import Eclair.Common.Config (Target(..)) import Eclair.AST.IR import Eclair.AST.Transforms (StringMap) import Eclair.Souffle.IR import qualified Eclair.AST.Transforms as AST import qualified Eclair.RA.IR as RA import qualified Eclair.RA.Transforms as RA import qualified Eclair.EIR.IR as EIR import qualified Eclair.TypeSystem as TS import qualified Eclair.AST.Analysis as SA import LLVM.Codegen (Module, ppllvm) import qualified Rock import Data.GADT.Compare import Data.Some import Data.Type.Equality type RA = RA.RA type EIR = EIR.EIR data Query a where Parse :: FilePath -> Query (AST, NodeId, SpanMap) RunSemanticAnalysis :: FilePath -> Query SA.SemanticInfo Typecheck :: FilePath -> Query TS.TypeInfo EmitDiagnostics :: FilePath -> Query () TransformAST :: FilePath -> Query (AST, StringMap) EmitTransformedAST :: FilePath -> Query () CompileRA :: FilePath -> Query RA TransformRA :: FilePath -> Query RA EmitRA :: FilePath -> Query () EmitTransformedRA :: FilePath -> Query () CompileEIR :: FilePath -> Query EIR EmitEIR :: FilePath -> Query () CompileLLVM :: FilePath -> Query Module EmitLLVM :: FilePath -> Query () EmitSouffle :: FilePath -> Query () StringMapping :: FilePath -> Query (Map Text Word32) UsageMapping :: FilePath -> Query (Map Id UsageMode) ExternDefinitions :: FilePath -> Query [Extern] deriving instance Eq (Query a) instance GEq Query where geq a b = case (a, b) of (Parse file1, Parse file2) | file1 == file2 -> Just Refl (RunSemanticAnalysis file1, RunSemanticAnalysis file2) | file1 == file2 -> Just Refl (Typecheck file1, Typecheck file2) | file1 == file2 -> Just Refl (EmitDiagnostics file1, EmitDiagnostics file2) | file1 == file2 -> Just Refl (TransformAST file1, TransformAST file2) | file1 == file2 -> Just Refl (EmitTransformedAST file1, EmitTransformedAST file2) | file1 == file2 -> Just Refl (CompileRA file1, CompileRA file2) | file1 == file2 -> Just Refl (TransformRA file1, TransformRA file2) | file1 == file2 -> Just Refl (EmitRA file1, EmitRA file2) | file1 == file2 -> Just Refl (EmitTransformedRA file1, EmitTransformedRA file2) | file1 == file2 -> Just Refl (CompileEIR file1, CompileEIR file2) | file1 == file2 -> Just Refl (EmitEIR file1, EmitEIR file2) | file1 == file2 -> Just Refl (CompileLLVM file1, CompileLLVM file2) | file1 == file2 -> Just Refl (EmitLLVM file1, EmitLLVM file2) | file1 == file2 -> Just Refl (EmitSouffle file1, EmitSouffle file2) | file1 == file2 -> Just Refl (StringMapping file1, StringMapping file2) | file1 == file2 -> Just Refl (UsageMapping file1, UsageMapping file2) | file1 == file2 -> Just Refl (ExternDefinitions file1, ExternDefinitions file2) | file1 == file2 -> Just Refl _ -> Nothing queryFilePath :: Query a -> FilePath queryFilePath = \case Parse path -> path RunSemanticAnalysis path -> path Typecheck path -> path EmitDiagnostics path -> path TransformAST path -> path EmitTransformedAST path -> path CompileRA path -> path TransformRA path -> path EmitRA path -> path EmitTransformedRA path -> path CompileEIR path -> path EmitEIR path -> path CompileLLVM path -> path EmitLLVM path -> path EmitSouffle path -> path StringMapping path -> path UsageMapping path -> path ExternDefinitions path -> path queryEnum :: Query a -> Int queryEnum = \case Parse {} -> 0 RunSemanticAnalysis {} -> 1 Typecheck {} -> 2 EmitDiagnostics {} -> 3 TransformAST {} -> 4 EmitTransformedAST {} -> 5 CompileRA {} -> 6 TransformRA {} -> 7 EmitRA {} -> 8 EmitTransformedRA {} -> 9 CompileEIR {} -> 10 EmitEIR {} -> 11 CompileLLVM {} -> 12 EmitLLVM {} -> 13 EmitSouffle {} -> 14 StringMapping {} -> 15 UsageMapping {} -> 16 ExternDefinitions {} -> 17 instance Hashable (Query a) where hashWithSalt salt = hashWithSalt salt . (queryFilePath &&& queryEnum) instance Hashable (Some Query) where hashWithSalt salt (Some query) = hashWithSalt salt query data Parameters = Parameters { paramsNumCores :: Word , paramsTarget :: !(Maybe Target) , paramsReadSourceFile :: FilePath -> IO (Maybe Text) } rules :: Rock.Task Query () -> Parameters -> Rock.GenRules (Rock.Writer [EclairError] Query) Query rules abortOnError params (Rock.Writer query) = case query of Parse path -> liftIO $ do (ast, nodeId, spanMap, mParseErr) <- parseFile (paramsReadSourceFile params) path pure ((ast, nodeId, spanMap), ParseErr path <$> maybeToList mParseErr) RunSemanticAnalysis path -> do (ast, _, spans) <- Rock.fetch (Parse path) result <- liftIO $ SA.runAnalysis (paramsNumCores params) ast let errs = if SA.hasSemanticErrors result then one $ SemanticErr path spans $ SA.semanticErrors result else mempty pure (SA.semanticInfo result, errs) Typecheck path -> do (ast, _, spans) <- Rock.fetch (Parse path) case TS.typeCheck ast of Left err -> pure (mempty, one $ TypeErr path spans err) Right typeInfo -> pure (typeInfo, mempty) EmitDiagnostics path -> noError $ do -- Just triggering these tasks collects all the corresponding errors. _ <- Rock.fetch (Parse path) _ <- Rock.fetch (RunSemanticAnalysis path) _ <- Rock.fetch (Typecheck path) pass TransformAST path -> noError $ do -- Need to run SA and typechecking before any transformations / lowering -- to ensure we don't perform work on invalid programs. -- And thanks to rock, the results will be cached anyway. analysis <- Rock.fetch (RunSemanticAnalysis path) _ <- Rock.fetch (Typecheck path) -- Past this point, the code should be valid! -- We abort if this is not the case. abortOnError (ast, nodeId, _) <- Rock.fetch (Parse path) externDefs <- Rock.fetch (ExternDefinitions path) pure $ AST.simplify nodeId externDefs analysis ast EmitTransformedAST path -> noError $ do (ast, _) <- Rock.fetch (TransformAST path) liftIO $ putTextLn $ printDoc ast StringMapping path -> noError $ do (_, mapping) <- Rock.fetch (TransformAST path) pure mapping UsageMapping path -> noError $ do (ast, _, _) <- Rock.fetch (Parse path) pure $ SA.computeUsageMapping ast ExternDefinitions path -> noError $ do (ast, _, _) <- Rock.fetch (Parse path) pure $ getExternDefs ast CompileRA path -> noError $ do ast <- fst <$> Rock.fetch (TransformAST path) externDefs <- Rock.fetch (ExternDefinitions path) pure $ compileToRA externDefs ast TransformRA path -> noError $ do ra <- Rock.fetch (CompileRA path) pure $ RA.simplify ra EmitRA path -> noError $ do ra <- Rock.fetch (CompileRA path) liftIO $ putTextLn $ printDoc ra EmitTransformedRA path -> noError $ do ra <- Rock.fetch (TransformRA path) liftIO $ putTextLn $ printDoc ra CompileEIR path -> noError $ do stringMapping <- Rock.fetch (StringMapping path) ra <- Rock.fetch (TransformRA path) typeInfo <- Rock.fetch (Typecheck path) pure $ compileToEIR stringMapping (TS.infoTypedefs typeInfo) ra EmitEIR path -> noError $ do eir <- Rock.fetch (CompileEIR path) liftIO $ putTextLn $ printDoc eir CompileLLVM path -> noError $ do eir <- Rock.fetch (CompileEIR path) stringMapping <- Rock.fetch (StringMapping path) usageMapping <- Rock.fetch (UsageMapping path) externDefs <- Rock.fetch (ExternDefinitions path) liftIO $ compileToLLVM (paramsTarget params) stringMapping usageMapping externDefs eir EmitLLVM path -> noError $ do llvmModule <- Rock.fetch (CompileLLVM path) liftIO $ putTextLn $ ppllvm llvmModule EmitSouffle path -> do (ast, _, spanMap) <- Rock.fetch (Parse path) case toSouffle ast of Left err -> pure ((), one $ ConversionErr path spanMap err) Right souffleIR -> do liftIO $ putTextLn $ printDoc souffleIR pure ((), mempty) -- Helper function for tasks that don't emit any errors. noError :: Rock.Task Query a -> Rock.Task Query (a, [EclairError]) noError task = (, mempty) <$> task type CompilerM a = IO (Either [EclairError] a) data Aborted = Aborted deriving (Show, Exception) runQuery :: Parameters -> Query a -> CompilerM a runQuery params query = do memoVar <- newIORef mempty errRef <- newIORef mempty let onError :: q -> [EclairError] -> Rock.Task Query () onError _ errs = liftIO $ modifyIORef errRef (<> errs) abortOnError = do errs <- readIORef errRef unless (null errs) $ liftIO $ throwIO Aborted handleAbort = handle $ \Aborted -> do errs <- readIORef errRef pure $ Left errs task = Rock.fetch query handleAbort $ do result <- Rock.runTask (Rock.memoise memoVar $ Rock.writer onError $ rules abortOnError params) task errs <- readIORef errRef pure $ if null errs then Right result else Left errs parse :: Parameters -> FilePath -> CompilerM (AST, SpanMap) parse params = map (map (\(ast, _, spanMap) -> (ast, spanMap))) . runQuery params . Parse semanticAnalysis :: Parameters -> FilePath -> CompilerM SA.SemanticInfo semanticAnalysis params = runQuery params . RunSemanticAnalysis typeCheck :: Parameters -> FilePath -> CompilerM TS.TypeInfo typeCheck params = runQuery params . Typecheck emitDiagnostics :: Parameters -> FilePath -> IO [EclairError] emitDiagnostics params = do f <<$>> runQuery params . EmitDiagnostics where f = fromLeft mempty emitTransformedAST :: Parameters -> FilePath -> CompilerM () emitTransformedAST params = runQuery params . EmitTransformedAST emitRA :: Parameters -> FilePath -> CompilerM () emitRA params = runQuery params . EmitRA emitTransformedRA :: Parameters -> FilePath -> CompilerM () emitTransformedRA params = runQuery params . EmitTransformedRA emitEIR :: Parameters -> FilePath -> CompilerM () emitEIR params = runQuery params . EmitEIR emitLLVM :: Parameters -> FilePath -> CompilerM () emitLLVM params = runQuery params . EmitLLVM emitSouffle :: Parameters -> FilePath -> CompilerM () emitSouffle params = runQuery params . EmitSouffle ================================================ FILE: lib/Prelude.hs ================================================ module Prelude ( module Relude , module Control.Arrow , module Control.Monad.Writer.Strict , module Control.Monad.RWS.Strict , module Control.Monad.Except , module Control.Monad.Fix , module Control.Category , module Control.Comonad , module Data.Functor.Foldable , module Data.Functor.Foldable.TH , module GHC.TypeLits , module GHC.Generics , module GHC.Records , IsString(..) , map , panic , groupBy , modifyMVar_ , uniqOrderPreserving ) where import Relude hiding ( Type, Constraint, Op , and, or, id, (.), map, first , absurd ) import Control.Arrow hiding (second, loop, (<+>)) import Control.Comonad import Control.Category import Control.Concurrent.MVar (modifyMVar_) import Control.Monad.Writer.Strict hiding (pass) import Control.Monad.RWS.Strict hiding (pass) import Control.Monad.Except import Control.Monad.Fix import Data.Functor.Foldable hiding (fold, unfold, refold, hoist) import Data.Functor.Foldable.TH import GHC.TypeLits (KnownSymbol, Symbol, symbolVal) import GHC.Generics (Rep, K1(..), U1(..), M1(..), (:*:)(..), from, to) import GHC.Records (HasField(..)) import qualified Data.List.Extra as E map :: Functor f => (a -> b) -> f a -> f b map = fmap {-# INLINABLE map #-} panic :: Text -> a panic = error groupBy :: (a -> a -> Bool) -> [a] -> [NonEmpty a] groupBy eq = \case [] -> [] (x:xs) -> (x :| ys) : groupBy eq zs where (ys,zs) = span (eq x) xs uniqOrderPreserving :: Ord a => [a] -> [a] uniqOrderPreserving = map snd . sortWith fst . E.nubOrdOn snd . zip [0 :: Int ..] ================================================ FILE: src/eclair/Main.hs ================================================ module Main (main) where import Eclair.ArgParser import Eclair.LSP import Eclair import GHC.IO.Encoding import System.Directory import qualified Data.Text.IO as TIO tryReadFile :: FilePath -> IO (Maybe Text) tryReadFile file = do fileExists <- doesFileExist file if fileExists then Just . decodeUtf8 <$> readFileBS file else pure Nothing main :: IO () main = do setLocaleEncoding utf8 arguments <- getArgs parseArgs arguments >>= \case Compile cfg -> do let file = mainFile cfg fn = case emitKind cfg of EmitTransformedAST -> emitTransformedAST EmitRA -> emitRA EmitTransformedRA -> emitTransformedRA EmitEIR -> emitEIR EmitLLVM -> emitLLVM EmitSouffle -> emitSouffle params = Parameters (numCores cfg) (cpuTarget cfg) tryReadFile whenLeftM_ (fn params file) $ \errs -> do let errActions = errs & map handleErrorsCLI & intersperse (TIO.hPutStr stderr "\n") sequence_ errActions LSP -> lspMain ================================================ FILE: tests/.gitignore ================================================ /**/.lit_test_times.txt Output/ ================================================ FILE: tests/ast_transforms/constant_folding.eclair ================================================ // RUN: split-file %s %t // RUN: %eclair compile --emit ast-transformed %t/program.eclair > %t/actual.out // RUN: diff %t/expected.out %t/actual.out //--- program.eclair @def number(u32) input. @def arithmetic(u32) output. @def arithmetic2(u32, u32) output. arithmetic(123 + 456). arithmetic(456 - 123). arithmetic(123 * 456). arithmetic(123 / 456). arithmetic(123 * 456 + 789). arithmetic(x) :- number(x), x = 123 + (456 * 789). arithmetic(x) :- number(x), number(y), x = y + 1, x = 1 + y, x = y + y. arithmetic2(x, y) :- number(x), number(y), x = 1 + y, y = x + 1. //--- expected.out @def number(u32) input. @def arithmetic(u32) output. @def arithmetic2(u32, u32) output. arithmetic(579). arithmetic(333). arithmetic(56088). arithmetic(0). arithmetic(56877). arithmetic(x) :- number(x), x = 359907. arithmetic(x) :- number(x), number(y), x = (y + 1), x = (1 + y), x = (y + y). arithmetic2(x, y) :- number(x), number(y), x = (1 + y), y = (x + 1). ================================================ FILE: tests/ast_transforms/copy_propagation.eclair ================================================ // RUN: split-file %s %t // RUN: %eclair compile --emit ast-transformed %t/program.eclair > %t/actual.out // RUN: diff %t/expected.out %t/actual.out //--- program.eclair @def fact1(u32, u32) input. @def fact2(u32) output. @def fact3(u32) input. @def fact4(u32, u32) output. @def fact5(u32) output. fact2(x) :- x = y, fact1(x, z), y = z. fact2(x) :- x = y, y = z, fact1(x, z). fact2(x) :- z = x, x = y, y = z, fact1(x, z). fact2(x) :- y = x, y = z, fact1(x, z). fact2(x) :- z = x, fact1(x, z). fact4(y, z) :- fact3(x), y = x + 3, y = x - 1, y = z + y, z = y + x. fact5(y) :- fact3(x), y = x + 3. //--- expected.out @def fact1(u32, u32) input. @def fact2(u32) output. @def fact3(u32) input. @def fact4(u32, u32) output. @def fact5(u32) output. fact2(x) :- fact1(x, z), x = z. fact2(x) :- fact1(x, z), x = z. fact2(x) :- fact1(x, z), z = x, x = z. fact2(x) :- fact1(x, z), z = x. fact2(x) :- fact1(x, z), z = x. fact4((x - 1), ((x - 1) + x)) :- fact3(x), (x - 1) = (x + 3), (x - 1) = (((x - 1) + x) + (x - 1)). fact5((x + 3)) :- fact3(x). ================================================ FILE: tests/ast_transforms/dead_code_elimination.eclair ================================================ // RUN: split-file %s %t // RUN: %eclair compile %t/program1.eclair --emit ast-transformed > %t/actual1.out // RUN: diff %t/expected1.out %t/actual1.out //--- program1.eclair @def empty_output(u32) output. @def unused_input(u32) input. @def unused_internal(u32). @def unused_rule(u32). @def another_input(u32) input. unused_internal(1). unused_rule(x) :- another_input(x). @def live1(u32) input output. @def live2(u32) output. @def live3(u32). @def live4(u32) output. @def live5(u32) input. @def live6(u32). @def live7(u32) output. @def live8(u32) output. @extern func(field1: u32) u32. @extern func2(u32) u32. @extern constraint(field1: u32). live2(123). live3(x) :- live2(x). live4(x) :- live3(x). live6(x) :- live5(x). live7(x) :- live6(x), x = func(123), constraint(x). live8(x) :- live6(x), func2(123) = x. // NOTE: Rule with contradictions is tested in another file already. //--- expected1.out @def live1(u32) input output. @def live2(u32) output. @def live3(u32). @def live4(u32) output. @def live5(u32) input. @def live6(u32). @def live7(u32) output. @def live8(u32) output. @extern func(field1: u32) u32. @extern func2(u32) u32. @extern constraint(field1: u32). live2(123). live3(x) :- live2(x). live4(x) :- live3(x). live6(x) :- live5(x). live7(x) :- live6(x), constraint(x), x = func(123). live8(x) :- live6(x), func2(123) = x. ================================================ FILE: tests/ast_transforms/remove_contradictions.eclair ================================================ // RUN: split-file %s %t // RUN: %eclair compile --emit ast-transformed %t/program.eclair > %t/actual.out // RUN: diff %t/expected.out %t/actual.out //--- program.eclair @def fact1(u32) input. @def fact2(u32) output. fact2(x) :- 123 = 456, fact1(x). fact2(x) :- "abc" = "def", fact1(x). fact2(x) :- x = 123, x = 456, fact1(x). fact2(x) :- y = "abc", y = "def", fact1(x). fact2(x) :- x = 123, x = y, y = z, fact1(x), z = 456. //--- expected.out @def fact1(u32) input. @def fact2(u32) output. ================================================ FILE: tests/ast_transforms/shift_assignments.eclair ================================================ // RUN: split-file %s %t // RUN: %eclair compile --emit ast-transformed %t/program1.eclair > %t/actual1.out // RUN: diff %t/expected1.out %t/actual1.out // RUN: %eclair compile --emit ast-transformed %t/program2.eclair > %t/actual2.out // RUN: diff %t/expected2.out %t/actual2.out //--- program1.eclair @def fact1(u32, u32) input. @def fact2(u32, u32) output. fact2(x, 1) :- z = x, fact1(x, z), y = 123, fact1(y, x). fact2(x, y) :- 123 = x, fact1(y, x). //--- expected1.out @def fact1(u32, u32) input. @def fact2(u32, u32) output. fact2(x, 1) :- fact1(x, z), fact1(y, x), z = x, y = 123. fact2(x, y) :- fact1(y, x), 123 = x. //--- program2.eclair @def edge(u32, u32) input. @def path(u32, u32) output. edge(1,2). path(x, y) :- edge(x, z), path(z, y). //--- expected2.out @def edge(u32, u32) input. @def path(u32, u32) output. edge(1, 2). path(x, y) :- edge(x, z), path(z, y). ================================================ FILE: tests/check.sh ================================================ #!/bin/bash grep -rE "(fdescribe|fit)" tests/eclair if [ "$?" == "0" ]; then echo "Found disabled tests (marked with fdescribe / fit), aborting!" exit 1 fi grep -rE "(\sxit|pending)" tests/eclair/Test if [ "$?" == "0" ]; then echo "Found pending tests, aborting!" exit 1 fi echo "All tests are enabled!" exit 0 ================================================ FILE: tests/eclair/Test/Eclair/ArgParserSpec.hs ================================================ module Test.Eclair.ArgParserSpec ( module Test.Eclair.ArgParserSpec ) where import Test.Hspec import qualified Data.Text as T import Eclair.ArgParser import Control.Exception import System.IO.Silently import System.Exit parseArgs' :: Text -> IO Config parseArgs' args = parseArgs (map toString $ T.split (== ' ') args) shouldFail :: IO a -> IO () shouldFail m = hSilence [stderr] $ do void m `catch` handler where handler = \case ExitFailure 1 -> pass e -> panic $ "Unknown error: " <> show e spec :: Spec spec = describe "argument parsing" $ do describe "compile mode" $ parallel $ do it "supports 'compile' as the command" $ do cfg <- parseArgs' "compile test.dl" cfg `shouldBe` Compile (CompileConfig "test.dl" EmitLLVM Nothing 1) it "supports 'c' as the command" $ do cfg <- parseArgs' "c test.dl" cfg `shouldBe` Compile (CompileConfig "test.dl" EmitLLVM Nothing 1) it "supports no other commands" $ do shouldFail $ parseArgs' "unknown" shouldFail $ parseArgs' "unknown arg1" it "requires a main file" $ do shouldFail $ parseArgs' "c" it "supports emitting RA" $ do for_ ["ra", "RA"] $ \ra -> do cfg <- parseArgs' $ "c test.dl --emit " <> ra cfg `shouldBe` Compile (CompileConfig "test.dl" EmitRA Nothing 1) it "supports emitting EIR" $ do for_ ["eir", "EIR"] $ \eir -> do cfg <- parseArgs' $ "c test.dl --emit " <> eir cfg `shouldBe` Compile (CompileConfig "test.dl" EmitEIR Nothing 1) it "supports emitting LLVM IR" $ do for_ ["llvm", "LLVM"] $ \llvm -> do cfg <- parseArgs' $ "c test.dl --emit " <> llvm cfg `shouldBe` Compile (CompileConfig "test.dl" EmitLLVM Nothing 1) it "does not support emitting anything else" $ do shouldFail $ parseArgs' "c test.dl --emit unknown-ir" it "defaults to emitting LLVM IR" $ do cfg <- parseArgs' "c test.dl" cfg `shouldBe` Compile (CompileConfig "test.dl" EmitLLVM Nothing 1) it "parses wasm32 as target architecture" $ do cfg <- parseArgs' "c test.dl -t wasm32" cfg `shouldBe` Compile (CompileConfig "test.dl" EmitLLVM (Just Wasm32) 1) cfg2 <- parseArgs' "c test.dl --target wasm32" cfg2 `shouldBe` Compile (CompileConfig "test.dl" EmitLLVM (Just Wasm32) 1) it "defaults to using 1 job" $ do cfg <- parseArgs' "c test.dl --emit llvm" cfg `shouldBe` Compile (CompileConfig "test.dl" EmitLLVM Nothing 1) it "is possible to configure number of jobs" $ do cfg <- parseArgs' "c test.dl --emit llvm -j 8" cfg `shouldBe` Compile (CompileConfig "test.dl" EmitLLVM Nothing 8) ================================================ FILE: tests/eclair/Test/Eclair/JSONSpec.hs ================================================ {-# LANGUAGE QuasiQuotes #-} module Test.Eclair.JSONSpec ( module Test.Eclair.JSONSpec ) where import Eclair.JSON import Test.Hspec import NeatInterpolation spec :: Spec spec = describe "JSON encoding" $ parallel $ do it "can encode null" $ do encodeJSON Null `shouldBe` "null" it "can encode booleans" $ do encodeJSON (Boolean True) `shouldBe` "true" encodeJSON (Boolean False) `shouldBe` "false" it "can encode strings" $ do encodeJSON (String "abcdef") `shouldBe` [text|"abcdef"|] encodeJSON (String "123") `shouldBe` [text|"123"|] it "can encode integers" $ do encodeJSON (Number 42) `shouldBe` "42" encodeJSON (Number 123) `shouldBe` "123" it "can encode objects" $ do encodeJSON (Object [("line", Number 10), ("column", Number 33)]) `shouldBe` [text| {"line":10,"column":33} |] encodeJSON (Object [("a", Null), ("b", Boolean True)]) `shouldBe` [text| {"a":null,"b":true} |] it "can encode arrays" $ do encodeJSON (Array []) `shouldBe` "[]" encodeJSON (Array [Number 123, String "abc", Null]) `shouldBe` [text| [123,"abc",null] |] ================================================ FILE: tests/eclair/Test/Eclair/LLVM/Allocator/MallocSpec.hs ================================================ {-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-} module Test.Eclair.LLVM.Allocator.MallocSpec ( module Test.Eclair.LLVM.Allocator.MallocSpec ) where import Prelude hiding (void) import Eclair.LLVM.Allocator.Malloc import Eclair.LLVM.Allocator.Common import Eclair.LLVM.Codegen hiding (retVoid, nullPtr) import System.Directory.Extra import System.Posix.DynamicLinker import Control.Exception import Foreign.Ptr import Foreign hiding (void) import Test.Eclair.LLVM.Allocator.Utils import Test.Hspec data Mallocator spec :: Spec spec = describe "Mallocator" $ aroundAll (setupAndTeardown testDir) $ parallel $ do it "can be initialized and destroyed" $ \bindings -> withAlloc bindings $ \obj -> do fnInit bindings obj fnDestroy bindings obj it "can allocate and free memory" $ \bindings -> do let numBytes = 1 value = 42 withAlloc bindings $ \obj -> do fnInit bindings obj memory <- fnAlloc bindings obj numBytes memory `shouldNotBe` nullPtr poke memory value value' <- peek memory fnFree bindings obj memory numBytes fnDestroy bindings obj value' `shouldBe` value setupAndTeardown :: FilePath -> ActionWith (Bindings Mallocator) -> IO () setupAndTeardown dir = bracket (setup dir) teardown setup :: FilePath -> IO (Bindings Mallocator) setup dir = do createDirectoryIfMissing False dir compileAllocatorCode allocator prefix cgExternals cgTestCode dir loadNativeCode prefix dir teardown :: Bindings Mallocator -> IO () teardown = dlclose . dynamicLib cgExternals :: ModuleBuilderT IO Externals cgExternals = do mallocFn <- extern "malloc" [i32] (ptr i8) freeFn <- extern "free" [ptr i8] void pure $ Externals mallocFn freeFn notUsed notUsed notUsed notUsed notUsed -- Helper test code for initializing and freeing a struct from native code: cgTestCode :: Type -> Externals -> ModuleBuilderT IO () cgTestCode ty exts = do let mallocFn = extMalloc exts freeFn = extFree exts _ <- function "mallocator_new" [] (ptr ty) $ \[] -> ret =<< call mallocFn [int32 1] _ <- function "mallocator_delete" [(ptr ty, "allocator")] void $ \[alloc] -> call freeFn [alloc] pass prefix :: Text prefix = "mallocator" testDir :: FilePath testDir = "/tmp/eclair-mallocator" notUsed :: a notUsed = panic "Not used" ================================================ FILE: tests/eclair/Test/Eclair/LLVM/Allocator/PageSpec.hs ================================================ {-# OPTIONS_GHC -Wno-deprecations -Wno-incomplete-uni-patterns #-} module Test.Eclair.LLVM.Allocator.PageSpec ( module Test.Eclair.LLVM.Allocator.PageSpec ) where import Prelude hiding (void) import Control.Monad.Morph import Eclair.LLVM.Allocator.Page import Eclair.LLVM.Allocator.Common import Test.Eclair.LLVM.Allocator.Utils import Eclair.LLVM.Codegen hiding (retVoid) import System.Directory.Extra import System.Posix.DynamicLinker import Test.Hspec import Control.Exception (bracket) import Foreign hiding (void) import Foreign.LibFFI data PageAllocator spec :: Spec spec = describe "PageAllocator" $ aroundAll (setupAndTeardown testDir) $ parallel $ do it "can be initialized and destroyed" $ \bindings -> withAlloc bindings $ \obj -> do fnInit bindings obj fnDestroy bindings obj it "can allocate and free memory" $ \bindings -> do let numBytes = 1 value = 42 withAlloc bindings $ \obj -> do fnInit bindings obj memory <- fnAlloc bindings obj numBytes let memoryEnd = memory `plusPtr` 4095 poke memory value poke memoryEnd value value' <- peek memory valueEnd <- peek memoryEnd fnFree bindings obj memory numBytes fnDestroy bindings obj value' `shouldBe` value valueEnd `shouldBe` value it "rounds up to the nearest page size" $ \_ -> do withNearestPageSize $ \roundFn -> do result1 <- roundFn 1 result2 <- roundFn 4096 result3 <- roundFn 4097 result4 <- roundFn 0 result5 <- roundFn 12345678 result1 `shouldBe` 4096 result2 `shouldBe` 4096 result3 `shouldBe` (4096 * 2) result4 `shouldBe` 0 result5 `shouldBe` 12349440 setupAndTeardown :: FilePath -> ActionWith (Bindings PageAllocator) -> IO () setupAndTeardown dir = bracket (setup dir) teardown setup :: FilePath -> IO (Bindings PageAllocator) setup dir = do createDirectoryIfMissing False dir compileAllocatorCode allocator prefix cgExternals cgTestCode dir loadNativeCode prefix dir teardown :: Bindings PageAllocator -> IO () teardown = dlclose . dynamicLib cgExternals :: ModuleBuilderT IO Externals cgExternals = do -- Need malloc and free to allocate the allocator itself mallocFn <- extern "malloc" [i32] (ptr i8) freeFn <- extern "free" [ptr i8] void -- mmap [hint, numBytes', prot, flags, noFd, offset] mmapFn <- extern "mmap" [ptr void, i64, i32, i32, i32, i32] (ptr void) -- munmap [memory, len'] munmapFn <- extern "munmap" [ptr void, i64] i32 pure $ Externals mallocFn freeFn notUsed notUsed notUsed mmapFn munmapFn -- Helper test code for allocating and freeing a struct from native code: cgTestCode :: Type -> Externals -> ModuleBuilderT IO () cgTestCode ty exts = do let mallocFn = extMalloc exts freeFn = extFree exts _ <- function "pageallocator_new" [] (ptr ty) $ \[] -> ret =<< call mallocFn [int32 1] _ <- function "pageallocator_delete" [(ptr ty, "allocator")] void $ \[alloc] -> call freeFn [alloc] let roundToNearestInstructions numBytes = hoist (hoist intoIO) $ hoist (`evalStateT` exts) $ roundToNearestPageSize numBytes _ <- function "nearest_page_size" [(i32, "num_bytes")] i32 $ \[num] -> ret =<< roundToNearestInstructions num pass withNearestPageSize :: ((Word32 -> IO Word32) -> IO ()) -> IO () withNearestPageSize f = bracket open close (\(_, roundFn) -> f roundFn) where open = do dl <- dlopen (soFile testDir) [RTLD_LAZY] roundingFn <- dlsym dl "nearest_page_size" let roundFn numBytes = fromIntegral <$> callFFI roundingFn retCUInt [argCUInt $ fromIntegral numBytes] pure (dl, roundFn) close = dlclose . fst prefix :: Text prefix = "pageallocator" testDir :: FilePath testDir = "/tmp/eclair-pageallocator" notUsed :: a notUsed = panic "Not used" intoIO :: Identity a -> IO a intoIO = pure . runIdentity ================================================ FILE: tests/eclair/Test/Eclair/LLVM/Allocator/Utils.hs ================================================ {-# OPTIONS_GHC -Wno-deprecations #-} module Test.Eclair.LLVM.Allocator.Utils ( Bindings(..) , compileAllocatorCode , loadNativeCode , soFile ) where import System.Process.Extra import System.FilePath import System.Posix.DynamicLinker import Eclair.LLVM.Codegen hiding (retVoid, nullPtr) import Eclair.LLVM.Allocator.Common import Control.Monad.Morph import Control.Exception import Foreign.LibFFI import Foreign.Ptr import Foreign.C type I8 = CUChar data Bindings a = Bindings { dynamicLib :: DL , withAlloc :: (Ptr a -> IO ()) -> IO () , fnAlloc :: Ptr a -> CSize -> IO (Ptr I8) , fnFree :: Ptr a -> Ptr I8 -> CSize -> IO () , fnInit :: Ptr a -> IO () , fnDestroy :: Ptr a -> IO () } compileAllocatorCode :: Allocator a -> Text -> ModuleBuilderT IO Externals -> (Type -> Externals -> ModuleBuilderT IO ()) -> FilePath -> IO () compileAllocatorCode allocator prefix cgExts cgHelperCode dir = do llvmIR <- runModuleBuilderT $ do exts <- cgExts let cgBlueprint = flip evalStateT exts $ cgAlloc prefix allocator blueprint <- hoist intoIO cgBlueprint cgHelperCode (bpType blueprint) exts let llvmIRText = ppllvm llvmIR writeFileText (llFile dir) llvmIRText callProcess "clang" ["-fPIC", "-shared", "-O0", "-o", soFile dir, llFile dir] intoIO :: Identity a -> IO a intoIO = pure . runIdentity llFile, soFile :: FilePath -> FilePath llFile dir = dir "allocator.ll" soFile dir = dir "allocator.so" loadNativeCode :: Text -> FilePath -> IO (Bindings a) loadNativeCode (toString -> pfx) dir = do lib <- dlopen (soFile dir) [RTLD_LAZY] newFn <- dlsym lib (pfx <> "_new") deleteFn <- dlsym lib (pfx <> "_delete") allocFn <- dlsym lib (pfx <> "_alloc") freeFn <- dlsym lib (pfx <> "_free") initFn <- dlsym lib (pfx <> "_init") destroyFn <- dlsym lib (pfx <> "_destroy") pure $ Bindings { dynamicLib = lib , withAlloc = mkWithAlloc newFn deleteFn , fnAlloc = mkAlloc allocFn , fnFree = mkFree freeFn , fnInit = mkInit initFn , fnDestroy = mkDestroy destroyFn } where mkAlloc fn mallocator numBytes = callFFI fn (retPtr retCUChar) [ argPtr mallocator , argCUInt $ fromIntegral numBytes ] mkFree fn mallocator memory numBytes = callFFI fn retVoid [ argPtr mallocator , argPtr memory , argCSize $ fromIntegral numBytes ] mkInit fn mallocator = callFFI fn retVoid [argPtr mallocator] mkDestroy fn mallocator = callFFI fn retVoid [argPtr mallocator] mkNew fn = callFFI fn (retPtr retVoid) [] mkDelete fn mallocator = callFFI fn retVoid [argPtr mallocator] mkWithAlloc newFn deleteFn = bracket (castPtr <$> mkNew newFn) (mkDelete deleteFn) ================================================ FILE: tests/eclair/Test/Eclair/LLVM/BTreeSpec.hs ================================================ {-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-} module Test.Eclair.LLVM.BTreeSpec ( module Test.Eclair.LLVM.BTreeSpec ) where import Prelude hiding (void) import qualified Relude as R import System.Directory.Extra import System.Process.Extra import System.Posix.DynamicLinker import System.FilePath import Control.Exception import Control.Monad.Morph import System.Random import Data.Array.IO hiding (index) import Foreign.LibFFI import Foreign hiding (void, newArray) import Eclair.LLVM.BTree import Eclair.LLVM.Table import Eclair.LLVM.Externals import Eclair.LLVM.Codegen hiding (retVoid, nullPtr) import qualified LLVM.C.API as LibLLVM import Test.Hspec data BTree data Iter type Value = Word32 data Bindings = Bindings { dynamicLib :: DL , withTree :: (Ptr BTree -> IO ()) -> IO () , withIter :: forall a. (Ptr Iter -> IO a) -> IO a , withValue :: forall a. Value -> (Ptr Value -> IO a) -> IO a , bInit :: Ptr BTree -> IO () , bDestroy :: Ptr BTree -> IO () , bPurge :: Ptr BTree -> IO () , bSwap :: Ptr BTree -> Ptr BTree -> IO () , bBegin :: Ptr BTree -> Ptr Iter -> IO () , bEnd :: Ptr BTree -> Ptr Iter -> IO () , bInsert :: Ptr BTree -> Ptr Value -> IO Bool , bMerge :: Ptr BTree -> Ptr BTree -> IO () , bEmpty :: Ptr BTree -> IO Bool , bSize :: Ptr BTree -> IO Word64 , bNodeCount :: Ptr BTree -> IO Word64 , bDepth :: Ptr BTree -> IO Word32 , bLowerBound :: forall a. Ptr BTree -> Ptr Value -> (Ptr Iter -> IO a) -> IO a , bUpperBound :: forall a. Ptr BTree -> Ptr Value -> (Ptr Iter -> IO a) -> IO a , bContains :: Ptr BTree -> Ptr Value -> IO Bool , bIterCurrent :: Ptr Iter -> IO (Ptr Value) , bIterNext :: Ptr Iter -> IO () , bIterIsEqual :: Ptr Iter -> Ptr Iter -> IO Bool } spec :: Spec spec = describe "BTree" $ aroundAll (setupAndTeardown testDir) $ parallel $ do it "can be initialized and destroyed" $ \bindings -> do withTree bindings $ \tree -> do bInit bindings tree bDestroy bindings tree it "is possible to remove all elements from the tree" $ \bindings -> withTree bindings $ \tree -> do bInit bindings tree bPurge bindings tree -- empty trees empty1 <- bEmpty bindings tree bPurge bindings tree -- calling it again empty2 <- bEmpty bindings tree withValue bindings 1 $ R.void . bInsert bindings tree empty3 <- bEmpty bindings tree bPurge bindings tree -- non-empty tree empty4 <- bEmpty bindings tree bPurge bindings tree -- calling it again empty5 <- bEmpty bindings tree for_ [1..100] $ \i -> withValue bindings i $ R.void . bInsert bindings tree bPurge bindings tree -- calling it again empty6 <- bEmpty bindings tree bDestroy bindings tree empty1 `shouldBe` True empty2 `shouldBe` True empty3 `shouldBe` False empty4 `shouldBe` True empty5 `shouldBe` True empty6 `shouldBe` True it "should be possible to merge one tree into another" $ \bindings -> withTree bindings $ \tree1 -> do withTree bindings $ \tree2 -> do bInit bindings tree1 bInit bindings tree2 for_ [1..4] $ \i -> do withValue bindings i $ bInsert bindings tree1 for_ [2, 4, 6] $ \i -> do withValue bindings i $ bInsert bindings tree2 -- tree1 = "destination", tree2 = "source" bMerge bindings tree1 tree2 list <- treeToList bindings tree1 bDestroy bindings tree1 bDestroy bindings tree2 list `shouldBe` [1, 2, 3, 4, 6] it "is possible to swap two trees" $ \bindings -> do withTree bindings $ \tree1 -> do withTree bindings $ \tree2 -> do bInit bindings tree1 bInit bindings tree2 for_ [1..100] $ \i -> do withValue bindings i $ \value -> do _ <- bInsert bindings tree1 value pass withValue bindings (i + 100) $ \value -> do _ <- bInsert bindings tree2 value pass c1 <- withValue bindings 42 $ bContains bindings tree1 c2 <- withValue bindings 78 $ bContains bindings tree1 c3 <- withValue bindings 142 $ bContains bindings tree2 c4 <- withValue bindings 178 $ bContains bindings tree2 bSwap bindings tree1 tree2 c5 <- withValue bindings 42 $ bContains bindings tree2 c6 <- withValue bindings 78 $ bContains bindings tree2 c7 <- withValue bindings 142 $ bContains bindings tree1 c8 <- withValue bindings 178 $ bContains bindings tree1 bDestroy bindings tree1 bDestroy bindings tree2 let result = R.and [c1, c2, c3, c4, c5, c6, c7, c8] result `shouldBe` True it "is possible to get begin and end iterators" $ \bindings -> withTree bindings $ \tree -> do withIters bindings $ \beginIter endIter -> do bInit bindings tree bBegin bindings tree beginIter bEnd bindings tree endIter beginIter `shouldNotBe` nullPtr endIter `shouldNotBe` nullPtr bDestroy bindings tree it "is possible to iterate over the tree" $ \bindings -> withTree bindings $ \tree -> do bInit bindings tree withValue bindings 4 $ R.void . bInsert bindings tree withValue bindings 2 $ R.void . bInsert bindings tree withValue bindings 5 $ R.void . bInsert bindings tree withValue bindings 1 $ R.void . bInsert bindings tree withValue bindings 3 $ R.void . bInsert bindings tree withIters bindings $ \beginIter endIter -> do bBegin bindings tree beginIter bEnd bindings tree endIter isEqual <- bIterIsEqual bindings beginIter endIter isEqual `shouldBe` False values <- treeToList bindings tree bDestroy bindings tree values `shouldBe` [1, 2, 3, 4, 5] it "should have equal begin and end iterators if tree is empty" $ \bindings -> withTree bindings $ \tree -> do withIters bindings $ \beginIter endIter -> do bInit bindings tree bBegin bindings tree beginIter bEnd bindings tree endIter isEqual <- bIterIsEqual bindings beginIter endIter isEqual `shouldBe` True bDestroy bindings tree it "is possible to insert a value" $ \bindings -> withTree bindings $ \tree -> do withValue bindings 1 $ \value -> do bInit bindings tree didInsert <- bInsert bindings tree value didInsert' <- bInsert bindings tree value didInsert `shouldBe` True didInsert' `shouldBe` False bDestroy bindings tree it "is possible to check if the tree is empty" $ \bindings -> withTree bindings $ \tree -> do bInit bindings tree empty1 <- bEmpty bindings tree withValue bindings 1 $ R.void . bInsert bindings tree empty2 <- bEmpty bindings tree withValue bindings 2 $ R.void . bInsert bindings tree empty3 <- bEmpty bindings tree bDestroy bindings tree empty1 `shouldBe` True empty2 `shouldBe` False empty3 `shouldBe` False it "is possible to lookup the size of the tree" $ \bindings -> withTree bindings $ \tree -> do bInit bindings tree size1 <- bSize bindings tree R.void $ withValue bindings 1 $ bInsert bindings tree size2 <- bSize bindings tree for_ [2..100] $ \i -> do withValue bindings i $ bInsert bindings tree size3 <- bSize bindings tree bDestroy bindings tree size1 `shouldBe` 0 size2 `shouldBe` 1 size3 `shouldBe` 100 it "is possible to check if the tree contains a certain value" $ \bindings -> withTree bindings $ \tree -> do bInit bindings tree c1 <- withValue bindings 1000 $ bContains bindings tree R.void $ withValue bindings 1000 $ bInsert bindings tree c2 <- withValue bindings 1000 $ bContains bindings tree for_ [1..100] $ \i -> withValue bindings i $ \value -> do _ <- bInsert bindings tree value pass c3 <- withValue bindings 42 $ bContains bindings tree c4 <- withValue bindings 78 $ bContains bindings tree c5 <- withValue bindings 132 $ bContains bindings tree c1 `shouldBe` False c2 `shouldBe` True c3 `shouldBe` True c4 `shouldBe` True c5 `shouldBe` False bDestroy bindings tree -- Tests below are taken from Souffle's test suite it "should support basic operations on the btree" $ \bindings -> withTree bindings $ \tree -> do bInit bindings tree -- check initial conditions bSize bindings tree >>= (`shouldBe` 0) bNodeCount bindings tree >>= (`shouldBe` 0) bDepth bindings tree >>= (`shouldBe` 0) withValue bindings 10 (bContains bindings tree) >>= (`shouldBe` False) withValue bindings 12 (bContains bindings tree) >>= (`shouldBe` False) withValue bindings 14 (bContains bindings tree) >>= (`shouldBe` False) -- add an element R.void $ withValue bindings 12 (bInsert bindings tree) bSize bindings tree >>= (`shouldBe` 1) bNodeCount bindings tree >>= (`shouldBe` 1) bDepth bindings tree >>= (`shouldBe` 1) withValue bindings 10 (bContains bindings tree) >>= (`shouldBe` False) withValue bindings 12 (bContains bindings tree) >>= (`shouldBe` True) -- TODO failing withValue bindings 14 (bContains bindings tree) >>= (`shouldBe` False) -- add a larger element R.void $ withValue bindings 14 (bInsert bindings tree) bSize bindings tree >>= (`shouldBe` 2) bNodeCount bindings tree >>= (`shouldBe` 1) bDepth bindings tree >>= (`shouldBe` 1) withValue bindings 10 (bContains bindings tree) >>= (`shouldBe` False) withValue bindings 12 (bContains bindings tree) >>= (`shouldBe` True) withValue bindings 14 (bContains bindings tree) >>= (`shouldBe` True) -- add a smaller element R.void $ withValue bindings 10 (bInsert bindings tree) bSize bindings tree >>= (`shouldBe` 3) bNodeCount bindings tree >>= (`shouldBe` 1) bDepth bindings tree >>= (`shouldBe` 1) withValue bindings 10 (bContains bindings tree) >>= (`shouldBe` True) withValue bindings 12 (bContains bindings tree) >>= (`shouldBe` True) withValue bindings 14 (bContains bindings tree) >>= (`shouldBe` True) -- cause a split R.void $ withValue bindings 11 (bInsert bindings tree) bSize bindings tree >>= (`shouldBe` 4) withValue bindings 10 (bContains bindings tree) >>= (`shouldBe` True) withValue bindings 11 (bContains bindings tree) >>= (`shouldBe` True) withValue bindings 12 (bContains bindings tree) >>= (`shouldBe` True) withValue bindings 14 (bContains bindings tree) >>= (`shouldBe` True) -- adding duplicates R.void $ withValue bindings 12 (bInsert bindings tree) bSize bindings tree >>= (`shouldBe` 4) R.void $ withValue bindings 12 (bInsert bindings tree) bSize bindings tree >>= (`shouldBe` 4) R.void $ withValue bindings 10 (bInsert bindings tree) bSize bindings tree >>= (`shouldBe` 4) R.void $ withValue bindings 15 (bInsert bindings tree) bSize bindings tree >>= (`shouldBe` 5) bNodeCount bindings tree >>= (`shouldBe` 3) bDepth bindings tree >>= (`shouldBe` 2) R.void $ withValue bindings 16 (bInsert bindings tree) bSize bindings tree >>= (`shouldBe` 6) bDestroy bindings tree it "should automatically remove duplicates" $ \bindings -> withTree bindings $ \tree -> do bInit bindings tree replicateM_ 10 $ withValue bindings 0 $ bInsert bindings tree size <- bSize bindings tree value <- withIter bindings $ \iter -> do bBegin bindings tree iter valuePtr <- bIterCurrent bindings iter peek valuePtr bDestroy bindings tree size `shouldBe` 1 value `shouldBe` 0 it "should contain the value after it is inserted" $ \bindings -> withTree bindings $ \tree -> do bInit bindings tree let n = 100 for_ [0..n] $ \i -> do R.void $ withValue bindings i $ bInsert bindings tree for_ [0..n] $ \j -> do contains <- withValue bindings j $ bContains bindings tree contains `shouldBe` (j <= i) bDestroy bindings tree it "should contain the value after it is inserted (reverse)" $ \bindings -> withTree bindings $ \tree -> do bInit bindings tree let n = 100 for_ [n, (n - 1) .. 0] $ \i -> do R.void $ withValue bindings i (bInsert bindings tree) for_ [0..n] $ \j -> do contains <- withValue bindings j (bContains bindings tree) contains `shouldBe` (j >= i) bDestroy bindings tree it "should contain the value after is inserted (shuffled)" $ \bindings -> do let list = [1..10000] shuffled <- shuffle list withTree bindings $ \tree -> do bInit bindings tree for_ shuffled $ \i -> do R.void $ withValue bindings i (bInsert bindings tree) for_ list $ \j -> do contains <- withValue bindings j (bContains bindings tree) contains `shouldBe` True bDestroy bindings tree it "should withstand iterator stress test" $ \bindings -> do let isSorted xs = sort xs == xs list = [1..300] -- for faster unit tests -- list = [1..1000] -- for real stress test shuffled <- shuffle list withTree bindings $ \tree -> do bInit bindings tree for_ shuffled $ \i -> do values <- treeToList bindings tree -- this is the main check if iterators are working correctly: isSorted values `shouldBe` True R.void $ withValue bindings i (bInsert bindings tree) bDestroy bindings tree it "should calculate correct lower and upper bounds of a value" $ \bindings -> withTree bindings $ \tree -> do let getBound f = flip (f bindings tree) (peek <=< bIterCurrent bindings) getLB = getBound bLowerBound getUB = getBound bUpperBound bInit bindings tree for_ [0..10] $ \i -> do R.void $ withValue bindings i (bInsert bindings tree) lb1 <- withValue bindings 5 getLB ub1 <- withValue bindings 5 getUB lb1 `shouldBe` 5 ub1 `shouldBe` 6 -- add duplicates and check again replicateM_ 3 $ R.void $ withValue bindings 5 $ bInsert bindings tree lb2 <- withValue bindings 5 getLB ub2 <- withValue bindings 5 getUB lb2 `shouldBe` 5 ub2 `shouldBe` 6 bDestroy bindings tree it "should calculate correct lower and upper bound for empty trees" $ \bindings -> withTree bindings $ \tree -> withIter bindings $ \endIter -> do bInit bindings tree bEnd bindings tree endIter -- empty lbIsEnd1 <- withValue bindings 5 $ flip (bLowerBound bindings tree) $ bIterIsEqual bindings endIter ubIsEnd1 <- withValue bindings 5 $ flip (bUpperBound bindings tree) $ bIterIsEqual bindings endIter lbIsEnd1 `shouldBe` True ubIsEnd1 `shouldBe` True let checkBounds expected3 expected5 = do withValue bindings 3 $ flip (bLowerBound bindings tree) $ \lbIter -> withValue bindings 3 $ flip (bUpperBound bindings tree) $ \ubIter -> do isEqual <- bIterIsEqual bindings lbIter ubIter isEqual `shouldBe` expected3 withValue bindings 5 $ flip (bLowerBound bindings tree) $ \lbIter -> withValue bindings 5 $ flip (bUpperBound bindings tree) $ \ubIter -> do isEqual <- bIterIsEqual bindings lbIter ubIter isEqual `shouldBe` expected5 -- insert 4 R.void $ withValue bindings 4 (bInsert bindings tree) checkBounds True True -- insert 6 R.void $ withValue bindings 6 (bInsert bindings tree) checkBounds True True -- insert 5 R.void $ withValue bindings 5 (bInsert bindings tree) checkBounds True False bDestroy bindings tree setupAndTeardown :: FilePath -> ActionWith Bindings -> IO () setupAndTeardown dir = bracket (setup dir) teardown setup :: FilePath -> IO Bindings setup dir = do createDirectoryIfMissing False dir let meta = Meta { numColumns = 1 , index = [0] , blockSize = 16 , searchType = Linear } cgBTree dir meta loadNativeCode dir teardown :: Bindings -> IO () teardown = dlclose . dynamicLib cgBTree :: FilePath -> Meta -> IO () cgBTree dir meta = do ctx <- LibLLVM.mkContext llvmMod <- LibLLVM.mkModule ctx "eclair" td <- LibLLVM.getTargetData llvmMod let cfg = Config Nothing ctx td llvmIR <- runModuleBuilderT $ do exts <- cgExternals table <- instantiate "test" meta $ runConfigT cfg $ codegen exts let iterParams = IteratorParams { ipIterCurrent = fnIterCurrent table , ipIterNext = fnIterNext table , ipIterIsEqual = fnIterIsEqual table , ipTypeIter = typeIter table } R.void $ hoist intoIO $ instantiate "test" iterParams $ fnInsertRangeTemplate table cgHelperCode table (extMalloc exts) (extFree exts) let llvmIRText = ppllvm llvmIR writeFileText (llFile dir) llvmIRText -- Next line is a hack, because we can't access node types from the test: appendFileText (llFile dir) helperCodeAppendix callProcess "clang" ["-fPIC", "-shared", "-O0", "-o", soFile dir, llFile dir] cgExternals :: ModuleBuilderT IO Externals cgExternals = do mallocFn <- extern "malloc" [i32] (ptr i8) freeFn <- extern "free" [ptr i8] void memsetFn <- extern "llvm.memset.p0i8.i64" [ptr i8, i8, i64, i1] void pure $ Externals mallocFn freeFn memsetFn notUsed notUsed notUsed notUsed -- Helper test code for initializing and freeing a struct from native code: cgHelperCode :: Monad m => Table -> Operand -> Operand -> ModuleBuilderT m () cgHelperCode table mallocFn freeFn = do let treeTy = typeObj table iterTy = typeIter table valueTy = typeValue table _ <- function "eclair_btree_new" [] (ptr treeTy) $ \[] -> ret =<< call mallocFn [int32 16] _ <- function "eclair_btree_delete" [(ptr treeTy, "btree")] void $ \[btree] -> call freeFn [btree] _ <- function "eclair_iter_new" [] (ptr iterTy) $ \[] -> ret =<< call mallocFn [int32 16] _ <- function "eclair_iter_delete" [(ptr iterTy, "iter")] void $ \[iter] -> call freeFn [iter] _ <- function "eclair_value_new" [] (ptr valueTy) $ \[] -> ret =<< call mallocFn [int32 4] -- Hardcoded for 1x i32 _ <- function "eclair_value_delete" [(ptr valueTy, "value")] void $ \[value] -> call freeFn [value] -- Next function is needed because returning i1 is not C ABI compatible _ <- function "eclair_btree_contains_helper_test" [(ptr treeTy, "tree"), (ptr valueTy, "val")] i8 $ \[tree, val] -> do result <- call (fnContains table) [tree, val] >>= (`zext` i8) ret result pass helperCodeAppendix :: Text helperCodeAppendix = unlines [ "" , "define external ccc i64 @node_count(ptr %node_0) {" , "start:" , " %stack.ptr_0 = alloca i64" -- count , " store i64 1, ptr %stack.ptr_0" , " %0 = getelementptr %node_t_test, ptr %node_0, i32 0, i32 0, i32 3" , " %1 = load i1, ptr %0" -- node type , " %2 = icmp eq i1 %1, 0" -- is leaf? , " br i1 %2, label %if_0, label %end_if_0" , "if_0:" , " ret i64 1" , "end_if_0:" , " %3 = getelementptr %node_t_test, ptr %node_0, i32 0, i32 0, i32 2" , " %4 = load i16, ptr %3" , " br label %for_begin_0" , "for_begin_0:" , " %5 = phi i16 [0, %end_if_0], [%12, %for_body_0]" , " %6 = icmp ule i16 %5, %4" , " br i1 %6, label %for_body_0, label %for_end_0" , "for_body_0:" , " %7 = load i64, ptr %stack.ptr_0" -- count , " %8 = getelementptr %inner_node_t_test, ptr %node_0, i32 0, i32 1, i16 %5" -- child ptr , " %9 = load ptr, ptr %8" -- child , " %10 = call ccc i64 @node_count(ptr %9)" , " %11 = add i64 %7, %10" , " store i64 %11, ptr %stack.ptr_0" , " %12 = add i16 1, %5" , " br label %for_begin_0" , "for_end_0:" , " %13 = load i64, ptr %stack.ptr_0" , " ret i64 %13" , "}" , "" , "define external ccc i64 @eclair_btree_node_count_test(ptr %tree_0) {" , "start:" , " %0 = getelementptr %btree_t_test, ptr %tree_0, i32 0, i32 0" , " %1 = load ptr, ptr %0" , " %2 = icmp eq ptr %1, zeroinitializer" , " br i1 %2, label %null_0, label %not_null_0" , "null_0:" , " ret i64 0" , "not_null_0:" , " %3 = call ccc i64 @node_count(ptr %1)" , " ret i64 %3" , "}" , "" , "define external ccc i32 @node_depth(ptr %node_0) {" , "start:" , " %0 = getelementptr %node_t_test, ptr %node_0, i32 0, i32 0, i32 3" , " %1 = load i1, ptr %0" -- node type , " %2 = icmp eq i1 %1, 0" -- is leaf? , " br i1 %2, label %if_0, label %end_if_0" , "if_0:" , " ret i32 1" , "end_if_0:" , " %3 = getelementptr %inner_node_t_test, ptr %node_0, i32 0, i32 1, i16 0" -- child ptr , " %4 = load ptr, ptr %3" -- child , " %5 = call ccc i32 @node_depth(ptr %4)" , " %6 = add i32 %5, 1" , " ret i32 %6" , "}" , "" , "define external ccc i32 @eclair_btree_depth_test(ptr %tree_0) {" , "start:" , " %0 = getelementptr %btree_t_test, ptr %tree_0, i32 0, i32 0" , " %1 = load ptr, ptr %0" , " %2 = icmp eq ptr %1, zeroinitializer" , " br i1 %2, label %null_0, label %not_null_0" , "null_0:" , " ret i32 0" , "not_null_0:" , " %3 = call ccc i32 @node_depth(ptr %1)" , " ret i32 %3" , "}" ] loadNativeCode :: FilePath -> IO Bindings loadNativeCode dir = do lib <- dlopen (soFile dir) [RTLD_LAZY] funcNewTree <- dlsym lib "eclair_btree_new" funcDeleteTree <- dlsym lib "eclair_btree_delete" funcNewIter <- dlsym lib "eclair_iter_new" funcDeleteIter <- dlsym lib "eclair_iter_delete" funcNewValue <- dlsym lib "eclair_value_new" funcDeleteValue <- dlsym lib "eclair_value_delete" funcInit <- dlsym lib "eclair_btree_init_empty_test" funcDestroy <- dlsym lib "eclair_btree_destroy_test" funcPurge <- dlsym lib "eclair_btree_clear_test" funcSwap <- dlsym lib "eclair_btree_swap_test" funcBegin <- dlsym lib "eclair_btree_begin_test" funcEnd <- dlsym lib "eclair_btree_end_test" funcInsert <- dlsym lib "eclair_btree_insert_value_test" funcMerge <- dlsym lib "eclair_btree_insert_range_test" funcEmpty <- dlsym lib "eclair_btree_is_empty_test" funcSize <- dlsym lib "eclair_btree_size_test" funcNodeCount <- dlsym lib "eclair_btree_node_count_test" funcDepth <- dlsym lib "eclair_btree_depth_test" funcContains <- dlsym lib "eclair_btree_contains_helper_test" funcLB <- dlsym lib "eclair_btree_lower_bound_test" funcUB <- dlsym lib "eclair_btree_upper_bound_test" funcIterCurrent <- dlsym lib "eclair_btree_iterator_current_test" funcIterNext <- dlsym lib "eclair_btree_iterator_next_test" funcIterIsEqual <- dlsym lib "eclair_btree_iterator_is_equal_test" let withIter' :: forall a. (Ptr Iter -> IO a) -> IO a withIter' = mkWithX funcNewIter funcDeleteIter iterCurrent = mkIterCurrent funcIterCurrent begin' = mkBegin funcBegin end' = mkEnd funcEnd pure $ Bindings { dynamicLib = lib , withTree = mkWithX funcNewTree funcDeleteTree , withIter = withIter' , withValue = \value f -> do mkWithX funcNewValue funcDeleteValue $ \valuePtr -> do poke valuePtr value f valuePtr , bInit = mkInit funcInit , bDestroy = mkDestroy funcDestroy , bPurge = mkPurge funcPurge , bSwap = mkSwap funcSwap , bBegin = begin' , bEnd = end' , bInsert = mkInsert funcInsert , bMerge = mkMerge funcMerge withIter' begin' end' , bEmpty = mkIsEmpty funcEmpty , bSize = mkSize funcSize , bNodeCount = mkNodeCount funcNodeCount , bDepth = mkDepth funcDepth , bContains = mkContains funcContains , bIterCurrent = iterCurrent , bIterNext = mkIterNext funcIterNext , bIterIsEqual = mkIterIsEqual funcIterIsEqual , bLowerBound = mkBound funcLB withIter' , bUpperBound = mkBound funcUB withIter' } where mkInit fn tree = callFFI fn retVoid [argPtr tree] mkDestroy fn tree = callFFI fn retVoid [argPtr tree] mkPurge fn tree = callFFI fn retVoid [argPtr tree] mkSwap fn tree1 tree2 = callFFI fn retVoid [argPtr tree1, argPtr tree2] mkBegin fn tree resultIter = callFFI fn retVoid [argPtr tree, argPtr resultIter] mkEnd fn tree resultIter = callFFI fn retVoid [argPtr tree, argPtr resultIter] mkInsert fn tree value = do result <- callFFI fn retCUChar [argPtr tree, argPtr value] pure $ result == 1 mkMerge fn withIter' begin' end' tree1 tree2 = do withIter' $ \beginIter -> withIter' $ \endIter -> do R.void $ begin' tree2 beginIter R.void $ end' tree2 endIter callFFI fn retVoid [argPtr tree1, argPtr beginIter, argPtr endIter] mkIsEmpty fn tree = do result <- callFFI fn retCUChar [argPtr tree] pure $ result == 1 mkSize fn tree = fromIntegral <$> callFFI fn retCULong [argPtr tree] mkNodeCount fn tree = fromIntegral <$> callFFI fn retCULong [argPtr tree] mkDepth fn tree = fromIntegral <$> callFFI fn retCUInt [argPtr tree] mkContains fn tree value = do result <- callFFI fn retCUChar [argPtr tree, argPtr value] pure $ result == 1 mkNew fn = callFFI fn (retPtr retVoid) [] mkDelete fn obj = callFFI fn retVoid [argPtr obj] mkWithX newFn deleteFn = bracket (castPtr <$> mkNew newFn) (mkDelete deleteFn) mkIterCurrent fn iter = castPtr <$> callFFI fn (retPtr retVoid) [argPtr iter] mkIterNext fn iter = callFFI fn retVoid [argPtr iter] mkIterIsEqual fn beginIter endIter = do result <- callFFI fn retCUChar [argPtr beginIter, argPtr endIter] pure $ result == 1 mkBound fn withIter' tree value f = do withIter' $ \iter -> do callFFI fn retVoid [argPtr tree, argPtr value, argPtr iter] f iter withIters :: Bindings -> (Ptr Iter -> Ptr Iter -> IO a) -> IO a withIters bindings f = withIter bindings $ \beginIter -> withIter bindings $ \endIter -> f beginIter endIter treeToList :: Bindings -> Ptr BTree -> IO [Value] treeToList bindings tree = withIters bindings $ \beginIter endIter -> do bBegin bindings tree beginIter bEnd bindings tree endIter whileM (isNotEqualIter beginIter endIter) $ do value <- bIterCurrent bindings beginIter bIterNext bindings beginIter peek value where isNotEqualIter beginIter endIter = do not <$> bIterIsEqual bindings beginIter endIter llFile, soFile :: FilePath -> FilePath llFile dir = dir "btree.ll" soFile dir = dir "btree.so" testDir :: FilePath testDir = "/tmp/eclair-btree" notUsed :: a notUsed = panic "Not used" whileM :: Monad m => m Bool -> m a -> m [a] whileM cond action = go where go = cond >>= \case True -> do x <- action xs <- go pure $ x:xs False -> pure [] shuffle :: [a] -> IO [a] shuffle xs = do array <- mkArray n xs forM [1..n] $ \i -> do j <- randomRIO (i,n) vi <- readArray array i vj <- readArray array j writeArray array j vi pure vj where n = length xs mkArray :: Int -> [a] -> IO (IOArray Int a) mkArray m = newListArray (1,m) intoIO :: Identity a -> IO a intoIO = pure . runIdentity ================================================ FILE: tests/eclair/Test/Eclair/LLVM/HashMapSpec.hs ================================================ {-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-} module Test.Eclair.LLVM.HashMapSpec ( module Test.Eclair.LLVM.HashMapSpec ) where import Prelude hiding (void, HashMap, Symbol) import Control.Exception import Control.Monad.Morph import qualified Test.Eclair.LLVM.SymbolUtils as S import qualified LLVM.C.API as LibLLVM import Eclair.LLVM.HashMap import qualified Eclair.LLVM.Symbol as S import Eclair.LLVM.Codegen hiding (retVoid, nullPtr) import Eclair.LLVM.Externals import Foreign.LibFFI import Foreign hiding (void) import System.Posix.DynamicLinker import System.Directory.Extra import System.Process.Extra import System.FilePath import Test.Hspec type Value = Word32 data Bindings = Bindings { dynamicLib :: DL , symBindings :: S.Bindings , withHashMap :: (Ptr HashMap -> IO ()) -> IO () , bInit :: Ptr HashMap -> IO () , bDestroy :: Ptr HashMap -> IO () , bGetOrPut :: Ptr HashMap -> Ptr S.Symbol -> Value -> IO Value , bLookup :: Ptr HashMap -> Ptr S.Symbol -> IO Value , bContains :: Ptr HashMap -> Ptr S.Symbol -> IO Bool } spec :: Spec spec = describe "HashMap" $ aroundAll (setupAndTeardown testDir) $ parallel $ do it "can be initialized and destroyed" $ \bindings -> withHashMap bindings $ \hm -> do bInit bindings hm bDestroy bindings hm it "stores a new value if the requested key was not found" $ \bindings -> do let sBindings = symBindings bindings withHashMap bindings $ \hm -> do bInit bindings hm withSym sBindings "abcd" $ \sym -> do value1 <- bGetOrPut bindings hm sym 42 value1 `shouldBe` 42 -- different symbol -> separate entry in the hashmap withSym sBindings "abcdef" $ \sym' -> do value3 <- bGetOrPut bindings hm sym' 34 value3 `shouldBe` 34 pass bDestroy bindings hm it "retrieves the old value if the requested key was found" $ \bindings -> do let sBindings = symBindings bindings withHashMap bindings $ \hm -> do bInit bindings hm withSym sBindings "abcd" $ \sym -> do value1 <- bGetOrPut bindings hm sym 42 value1 `shouldBe` 42 value2 <- bGetOrPut bindings hm sym 100 value2 `shouldBe` 42 -- same symbol -> same entry in the hashmap withSym sBindings "abcd" $ \sym' -> do value4 <- bGetOrPut bindings hm sym' 34 value4 `shouldBe` 42 bDestroy bindings hm it "is possible to lookup keys in the hashmap" $ \bindings -> do let sBindings = symBindings bindings withHashMap bindings $ \hm -> do bInit bindings hm -- key found withSym sBindings "abcd" $ \sym -> do _ <- bGetOrPut bindings hm sym 42 value <- bLookup bindings hm sym value `shouldBe` 42 -- key not found withSym sBindings "123" $ \sym -> do value <- bLookup bindings hm sym value `shouldBe` 0xffffffff bDestroy bindings hm it "is possible to check if a hashmap contains a key" $ \bindings -> do let sBindings = symBindings bindings withHashMap bindings $ \hm -> do bInit bindings hm -- key found withSym sBindings "abcd" $ \sym -> do _ <- bGetOrPut bindings hm sym 42 value <- bContains bindings hm sym value `shouldBe` True -- key not found withSym sBindings "123" $ \sym -> do value <- bContains bindings hm sym value `shouldBe` False bDestroy bindings hm -- TODO big hashmap test + test for colissions setupAndTeardown :: FilePath -> ActionWith Bindings -> IO () setupAndTeardown dir = bracket (setup dir) teardown setup :: FilePath -> IO Bindings setup dir = do createDirectoryIfMissing False dir compileCode cgExternals cgTestCode dir loadNativeCode dir teardown :: Bindings -> IO () teardown = dlclose . dynamicLib compileCode :: ModuleBuilderT IO Externals -> (S.Symbol -> HashMap -> Externals -> ModuleBuilderT IO ()) -> FilePath -> IO () compileCode cgExts cgHelperCode dir = do ctx <- LibLLVM.mkContext llvmMod <- LibLLVM.mkModule ctx "eclair" td <- LibLLVM.getTargetData llvmMod llvmIR <- runModuleBuilderT $ do exts <- cgExts let cfg = Config Nothing ctx td sym <- hoist intoIO $ S.codegen exts hm <- runConfigT cfg $ codegen sym exts cgHelperCode sym hm exts let llvmIRText = ppllvm llvmIR writeFileText (llFile dir) llvmIRText callProcess "clang" ["-fPIC", "-shared", "-O0", "-o", soFile dir, llFile dir] intoIO :: Identity a -> IO a intoIO = pure . runIdentity cgExternals :: ModuleBuilderT IO Externals cgExternals = do mallocFn <- extern "malloc" [i32] (ptr i8) freeFn <- extern "free" [ptr i8] void memcpyFn <- extern "memcpy" [ptr i8, ptr i8, i64] (ptr i8) memcmpFn <- extern "memcmp" [ptr i8, ptr i8, i64] i32 pure $ Externals mallocFn freeFn notUsed memcpyFn memcmpFn notUsed notUsed cgTestCode :: S.Symbol -> HashMap -> Externals -> ModuleBuilderT IO () cgTestCode sym hm exts = do let hmTypes = hashMapTypes hm hmTy = tyHashMap hmTypes tySym = tyKey hmTypes mallocFn = extMalloc exts freeFn = extFree exts _ <- function "eclair_hashmap_new" [] (ptr hmTy) $ \[] -> ret =<< call mallocFn [int32 $ 64 * 32] -- 64 vectors long _ <- function "eclair_hashmap_delete" [(ptr hmTy, "hm")] void $ \[h] -> call freeFn [h] let args = [(ptr hmTy, "hashmap"), (ptr tySym, "symbol")] _ <- function "eclair_hashmap_contains_helper" args i8 $ \[h, s] -> do result <- call (hashMapContains hm) [h, s] ret =<< result `zext` i8 S.cgTestCode sym exts loadNativeCode :: FilePath -> IO Bindings loadNativeCode dir = do lib <- dlopen (soFile dir) [RTLD_LAZY] sBindings <- S.loadNativeCode' lib fnNew <- dlsym lib "eclair_hashmap_new" fnDelete <- dlsym lib "eclair_hashmap_delete" fnInit <- dlsym lib "eclair_hashmap_init" fnDestroy <- dlsym lib "eclair_hashmap_destroy" fnGetOrPut <- dlsym lib "eclair_hashmap_get_or_put_value" fnContains <- dlsym lib "eclair_hashmap_contains_helper" fnLookup <- dlsym lib "eclair_hashmap_lookup" pure $ Bindings { dynamicLib = lib , symBindings = sBindings , withHashMap = mkWithHashMap fnNew fnDelete , bInit = mkInit fnInit , bDestroy = mkDestroy fnDestroy , bGetOrPut = mkGetOrPut fnGetOrPut , bContains = mkContains fnContains , bLookup = mkLookup fnLookup } where mkNew fn = callFFI fn (retPtr retVoid) [] mkDelete fn hm = callFFI fn retVoid [argPtr hm] mkWithHashMap fnNew fnDelete = bracket (castPtr <$> mkNew fnNew) (mkDelete fnDelete) mkInit fn hm = callFFI fn retVoid [argPtr hm] mkDestroy fn hm = callFFI fn retVoid [argPtr hm] mkGetOrPut fn hm sym value = fromIntegral <$> callFFI fn retCUInt [argPtr hm, argPtr sym, argCUInt $ fromIntegral value] mkContains fn hm sym = do result <- callFFI fn retCUChar [argPtr hm, argPtr sym] pure $ result == 1 mkLookup fn hm sym = fromIntegral <$> callFFI fn retCUInt [argPtr hm, argPtr sym] testDir :: FilePath testDir = "/tmp/eclair-hashmap" llFile, soFile :: FilePath -> FilePath llFile dir = dir "hashmap.ll" soFile dir = dir "hashmap.so" notUsed :: a notUsed = panic "Not used" withSym :: S.Bindings -> String -> (Ptr S.Symbol -> IO a) -> IO a withSym bindings str f = do S.withSymbol bindings $ \sym -> do S.bInit bindings sym str result <- f sym S.bDestroy bindings sym pure result ================================================ FILE: tests/eclair/Test/Eclair/LLVM/HashSpec.hs ================================================ module Test.Eclair.LLVM.HashSpec ( module Test.Eclair.LLVM.HashSpec ) where import qualified Data.Set as Set import Eclair.LLVM.Hash import Test.Hspec data MyOption = Option0 | Option1 deriving stock Enum deriving ToHash via HashEnum MyOption data Prefixed = Prefixed deriving stock Generic deriving ToHash via HashWithPrefix "prefix" Prefixed data Config = Config Int Int Int MyOption deriving stock Generic deriving ToHash via HashWithPrefix "config" Config data OnlyInt = OnlyInt { getInt :: Int , getText :: Text , getMyOption :: MyOption } deriving ToHash via HashOnly "getInt" OnlyInt spec :: Spec spec = describe "Hashing data" $ parallel $ do it "can hash ints" $ do unHash (getHash (1234 :: Int)) `shouldBe` "1234" it "can hash texts" $ do unHash (getHash ("12345" :: Text)) `shouldBe` "12345" it "can hash lists" $ do unHash (getHash (["123", "45", "6"] :: [Text])) `shouldBe` "123_45_6" it "can hash non empty lists" $ do let nonEmptyList = "123" :| ["45", "6"] :: NonEmpty Text unHash (getHash nonEmptyList) `shouldBe` "123_45_6" it "can hash sets" $ do let set = Set.fromList [123, 45, 6] :: Set Int -- NOTE: set is sorted => different ordering! unHash (getHash set) `shouldBe` "6_45_123" it "can hash sum types as enums" $ do unHash (getHash Option0) `shouldBe` "0" unHash (getHash Option1) `shouldBe` "1" it "can hash with a prefix" $ do unHash (getHash Prefixed) `shouldBe` "prefix__0" it "can hash generic product types" $ do let cfg = Config 1 2 3 Option1 unHash (getHash cfg) `shouldBe` "config__1__2__3__1" it "can hash only a specific field of a record" $ do let onlyInt = OnlyInt 123 "abc" Option1 unHash (getHash onlyInt) `shouldBe` "123" it "can combine hashes" $ do let x, y :: Int x = 42 y = 1000 unHash (getHash x <> getHash y) `shouldBe` "42__1000" ================================================ FILE: tests/eclair/Test/Eclair/LLVM/SymbolSpec.hs ================================================ {-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-} module Test.Eclair.LLVM.SymbolSpec ( module Test.Eclair.LLVM.SymbolSpec ) where import Prelude hiding (void, Symbol) import Test.Eclair.LLVM.SymbolUtils import Control.Monad.Morph import Control.Exception import Eclair.LLVM.Symbol import Eclair.LLVM.Codegen hiding (retVoid, nullPtr) import Eclair.LLVM.Externals import System.Posix.DynamicLinker import System.Directory.Extra import System.Process.Extra import Test.Hspec spec :: Spec spec = describe "Symbol" $ aroundAll (setupAndTeardown testDir) $ parallel $ do it "can be initialized and destroyed" $ \bindings -> withSymbol bindings $ \s -> do let str = "my example string" len = fromIntegral $ length str bInit bindings s str bLength bindings s >>= (`shouldBe` len) bData bindings s >>= (`shouldBe` str) bDestroy bindings s it "is possible to compare 2 symbols" $ \bindings -> withSymbol bindings $ \s1 -> do withSymbol bindings $ \s2 -> do bInit bindings s1 "abc" bInit bindings s2 "1234" isEq1 <- bIsEqual bindings s1 s2 isEq2 <- bIsEqual bindings s1 s1 isEq3 <- bIsEqual bindings s2 s2 bDestroy bindings s1 bDestroy bindings s2 isEq1 `shouldBe` False isEq2 `shouldBe` True isEq3 `shouldBe` True setupAndTeardown :: FilePath -> ActionWith Bindings -> IO () setupAndTeardown dir = bracket (setup dir) teardown setup :: FilePath -> IO Bindings setup dir = do createDirectoryIfMissing False dir compileCode cgExternals cgTestCode dir loadNativeCode dir teardown :: Bindings -> IO () teardown = dlclose . dynamicLib compileCode :: ModuleBuilderT IO Externals -> (Symbol -> Externals -> ModuleBuilderT IO ()) -> FilePath -> IO () compileCode cgExts cgHelperCode dir = do llvmIR <- runModuleBuilderT $ do exts <- cgExts symbol <- hoist intoIO $ codegen exts cgHelperCode symbol exts let llvmIRText = ppllvm llvmIR writeFileText (llFile dir) llvmIRText callProcess "clang" ["-fPIC", "-shared", "-O0", "-o", soFile dir, llFile dir] cgExternals :: ModuleBuilderT IO Externals cgExternals = do mallocFn <- extern "malloc" [i32] (ptr i8) freeFn <- extern "free" [ptr i8] void memcpyFn <- extern "memcpy" [ptr i8, ptr i8, i64] (ptr i8) memcmpFn <- extern "memcmp" [ptr i8, ptr i8, i64] i32 pure $ Externals mallocFn freeFn notUsed memcpyFn memcmpFn notUsed notUsed testDir :: FilePath testDir = "/tmp/eclair-symbol" notUsed :: a notUsed = panic "Not used" intoIO :: Identity a -> IO a intoIO = pure . runIdentity ================================================ FILE: tests/eclair/Test/Eclair/LLVM/SymbolTableSpec.hs ================================================ {-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-} module Test.Eclair.LLVM.SymbolTableSpec ( module Test.Eclair.LLVM.SymbolTableSpec ) where import Prelude hiding (void, Symbol) import qualified LLVM.C.API as LibLLVM import qualified Test.Eclair.LLVM.SymbolUtils as S import Control.Monad.Morph import Control.Exception import Eclair.LLVM.SymbolTable import qualified Eclair.LLVM.Symbol as S import qualified Eclair.LLVM.Vector as V import qualified Eclair.LLVM.HashMap as HM import Eclair.LLVM.Codegen hiding (retVoid, nullPtr) import Eclair.LLVM.Externals import System.Posix.DynamicLinker import System.Directory.Extra import System.Process.Extra import System.FilePath import Foreign hiding (void) import Foreign.LibFFI import Test.Hspec type Symbol = S.Symbol type Value = Word32 data Bindings = Bindings { dynamicLib :: DL , symBindings :: S.Bindings , withSymTab :: (Ptr SymbolTable -> IO ()) -> IO () , bInit :: Ptr SymbolTable -> IO () , bDestroy :: Ptr SymbolTable -> IO () , bFindOrInsert :: Ptr SymbolTable -> Ptr Symbol -> IO Value -- NOTE: no need to free returned symbol after lookup , bLookupSymbol :: Ptr SymbolTable -> Value -> IO (Ptr Symbol) , bContainsSymbol :: Ptr SymbolTable -> Ptr Symbol -> IO Bool , bLookupIndex :: Ptr SymbolTable -> Ptr Symbol -> IO Value , bContainsIndex :: Ptr SymbolTable -> Value -> IO Bool } spec :: Spec spec = describe "Symbol table" $ aroundAll (setupAndTeardown testDir) $ parallel $ do it "can be initialized and destroyed" $ \bindings -> withSymTab bindings $ \st -> do bInit bindings st bDestroy bindings st it "is possible to add symbols to the table" $ \bindings -> do let sBindings = symBindings bindings withSymTab bindings $ \st -> do bInit bindings st _ <- S.withSymbol sBindings $ \sym -> do S.bInit sBindings sym "abcd" idx <- bFindOrInsert bindings st sym idx `shouldBe` 0 idx' <- bFindOrInsert bindings st sym idx' `shouldBe` 0 -- Owned by symbol table now: -- S.bDestroy sBindings sym _ <- S.withSymbol sBindings $ \sym -> do S.bInit sBindings sym "123" idx <- bFindOrInsert bindings st sym idx `shouldBe` 1 -- Owned by symbol table now: -- S.bDestroy sBindings sym bDestroy bindings st it "is possible to check if the table contains a key" $ \bindings -> do let sBindings = symBindings bindings withSymTab bindings $ \st -> do bInit bindings st _ <- S.withSymbol sBindings $ \sym -> do S.bInit sBindings sym "abcd" result1 <- bContainsSymbol bindings st sym _ <- bFindOrInsert bindings st sym result2 <- bContainsSymbol bindings st sym S.bDestroy sBindings sym result1 `shouldBe` False result2 `shouldBe` True pass it "is possible to check if the table contains a value" $ \bindings -> do let sBindings = symBindings bindings withSymTab bindings $ \st -> do bInit bindings st _ <- S.withSymbol sBindings $ \sym -> do S.bInit sBindings sym "abcd" result1 <- bContainsIndex bindings st 0 _ <- bFindOrInsert bindings st sym result2 <- bContainsIndex bindings st 0 S.bDestroy sBindings sym result1 `shouldBe` False result2 `shouldBe` True pass it "is possible to lookup a key corresponding to a value" $ \bindings -> do let sBindings = symBindings bindings withSymTab bindings $ \st -> do bInit bindings st _ <- S.withSymbol sBindings $ \sym -> do S.bInit sBindings sym "abcd" -- NOTE: unsafe lookup, don't use it before symbol is inserted -- sym1 <- bLookupSymbol bindings st 0 idx <- bFindOrInsert bindings st sym idx `shouldBe` 0 print =<< bContainsSymbol bindings st sym sym' <- bLookupSymbol bindings st idx -- Owned by symbol table now: -- S.bDestroy sBindings sym result <- S.bIsEqual sBindings sym sym' result `shouldBe` True pass pass it "is possible to lookup a value corresponding to a key" $ \bindings -> do let sBindings = symBindings bindings withSymTab bindings $ \st -> do bInit bindings st _ <- S.withSymbol sBindings $ \sym -> do S.bInit sBindings sym "abcd" -- NOTE: unsafe lookup, don't use it before symbol is inserted idx1 <- bLookupIndex bindings st sym _ <- bFindOrInsert bindings st sym idx2 <- bLookupIndex bindings st sym S.bDestroy sBindings sym idx1 `shouldBe` 0xffffffff idx2 `shouldBe` 0 pass setupAndTeardown :: FilePath -> ActionWith Bindings -> IO () setupAndTeardown dir = bracket (setup dir) teardown setup :: FilePath -> IO Bindings setup dir = do createDirectoryIfMissing False dir compileCode cgExternals cgTestCode dir loadNativeCode dir teardown :: Bindings -> IO () teardown = dlclose . dynamicLib compileCode :: ModuleBuilderT IO Externals -> (Symbol -> SymbolTable -> Externals -> ModuleBuilderT IO ()) -> FilePath -> IO () compileCode cgExts cgHelperCode dir = do ctx <- LibLLVM.mkContext llvmMod <- LibLLVM.mkModule ctx "eclair" td <- LibLLVM.getTargetData llvmMod llvmIR <- runModuleBuilderT $ do exts <- cgExts symbol <- hoist intoIO $ S.codegen exts let cfg = Config Nothing ctx td symbolDestructor iterPtr = do _ <- call (S.symbolDestroy symbol) [iterPtr] pass vec <- instantiate "test" (S.tySymbol symbol) $ runConfigT cfg $ V.codegen exts (Just symbolDestructor) hm <- runConfigT cfg $ HM.codegen symbol exts symTab <- hoist intoIO $ codegen (S.tySymbol symbol) vec hm cgHelperCode symbol symTab exts let llvmIRText = ppllvm llvmIR writeFileText (llFile dir) llvmIRText callProcess "clang" ["-fPIC", "-shared", "-O0", "-o", soFile dir, llFile dir] cgExternals :: ModuleBuilderT IO Externals cgExternals = do mallocFn <- extern "malloc" [i32] (ptr i8) freeFn <- extern "free" [ptr i8] void memcpyFn <- extern "memcpy" [ptr i8, ptr i8, i64] (ptr i8) memcmpFn <- extern "memcmp" [ptr i8, ptr i8, i64] i32 pure $ Externals mallocFn freeFn notUsed memcpyFn memcmpFn notUsed notUsed cgTestCode :: S.Symbol -> SymbolTable -> Externals -> ModuleBuilderT IO () cgTestCode sym symTab exts = do let mallocFn = extMalloc exts freeFn = extFree exts tySym = S.tySymbol sym symTabTy = tySymbolTable symTab _ <- function "eclair_symbol_table_new" [] (ptr symTabTy) $ \[] -> ret =<< call mallocFn [int32 4096] _ <- function "eclair_symbol_table_delete" [(ptr symTabTy, "hm")] void $ \[h] -> call freeFn [h] let args = [(ptr symTabTy, "symbol_table"), (ptr tySym, "symbol")] _ <- function "eclair_symbol_table_contains_symbol_helper" args i8 $ \[st, s] -> do result <- call (symbolTableContainsSymbol symTab) [st, s] ret =<< result `zext` i8 let args' = [(ptr symTabTy, "symbol_table"), (i32, "value")] _ <- function "eclair_symbol_table_contains_index_helper" args' i8 $ \[st, v] -> do result <- call (symbolTableContainsIndex symTab) [st, v] ret =<< result `zext` i8 S.cgTestCode sym exts loadNativeCode :: FilePath -> IO Bindings loadNativeCode dir = do lib <- dlopen (soFile dir) [RTLD_LAZY] sBindings <- S.loadNativeCode' lib fnNew <- dlsym lib "eclair_symbol_table_new" fnDelete <- dlsym lib "eclair_symbol_table_delete" fnInit <- dlsym lib "eclair_symbol_table_init" fnDestroy <- dlsym lib "eclair_symbol_table_destroy" fnFindOrInsert <- dlsym lib "eclair_symbol_table_find_or_insert" fnLookupSymbol <- dlsym lib "eclair_symbol_table_lookup_symbol" fnContainsSymbol <- dlsym lib "eclair_symbol_table_contains_symbol_helper" fnLookupIndex <- dlsym lib "eclair_symbol_table_lookup_index" fnContainsIndex <- dlsym lib "eclair_symbol_table_contains_index_helper" pure $ Bindings { dynamicLib = lib , symBindings = sBindings , withSymTab = mkWithSymTab fnNew fnDelete , bInit = mkInit fnInit , bDestroy = mkDestroy fnDestroy , bFindOrInsert = mkFindOrInsert fnFindOrInsert , bLookupSymbol = mkLookupSymbol fnLookupSymbol , bLookupIndex = mkLookupIndex fnLookupIndex , bContainsSymbol = mkContainsSymbol fnContainsSymbol , bContainsIndex = mkContainsIndex fnContainsIndex } where mkNew fn = callFFI fn (retPtr retVoid) [] mkDelete fn st = callFFI fn retVoid [argPtr st] mkWithSymTab fnNew fnDelete = bracket (castPtr <$> mkNew fnNew) (mkDelete fnDelete) mkInit fn st = callFFI fn retVoid [argPtr st] mkDestroy fn st = callFFI fn retVoid [argPtr st] mkFindOrInsert fn st sym = fromIntegral <$> callFFI fn retCUInt [argPtr st, argPtr sym] mkLookupSymbol fn st value = castPtr <$> callFFI fn (retPtr retVoid) [argPtr st, argCUInt $ fromIntegral value] mkLookupIndex fn st sym = fromIntegral <$> callFFI fn retCUInt [argPtr st, argPtr sym] mkContainsSymbol fn st sym = do result <- callFFI fn retCUChar [argPtr st, argPtr sym] pure $ result == 1 mkContainsIndex fn st value = do result <- callFFI fn retCUChar [argPtr st, argCUInt $ fromIntegral value] pure $ result == 1 testDir :: FilePath testDir = "/tmp/eclair-symbol-table" notUsed :: a notUsed = panic "Not used" intoIO :: Identity a -> IO a intoIO = pure . runIdentity llFile, soFile :: FilePath -> FilePath llFile dir = dir "symbol-table.ll" soFile dir = dir "symbol-table.so" ================================================ FILE: tests/eclair/Test/Eclair/LLVM/SymbolUtils.hs ================================================ {-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-} module Test.Eclair.LLVM.SymbolUtils ( Bindings(..) , Symbol(..) , cgTestCode , loadNativeCode , loadNativeCode' , soFile , llFile ) where import Prelude hiding (void, Symbol) import Control.Exception import Eclair.LLVM.Symbol import Eclair.LLVM.Codegen hiding (retVoid, nullPtr) import Eclair.LLVM.Externals import Foreign.LibFFI import Foreign hiding (void, bit) import System.Posix.DynamicLinker import System.FilePath import Foreign.C data Bindings = Bindings { dynamicLib :: DL , withSymbol :: forall a. (Ptr Symbol -> IO a) -> IO a , bInit :: Ptr Symbol -> String -> IO () , bDestroy :: Ptr Symbol -> IO () , bIsEqual :: Ptr Symbol -> Ptr Symbol -> IO Bool , bLength :: Ptr Symbol -> IO Word32 , bData :: Ptr Symbol -> IO String } cgTestCode :: Symbol -> Externals -> ModuleBuilderT IO () cgTestCode sym exts = do let mallocFn = extMalloc exts freeFn = extFree exts memcpyFn = extMemcpy exts symTy = tySymbol sym _ <- function "eclair_symbol_new" [] (ptr symTy) $ \[] -> ret =<< call mallocFn [int32 16] _ <- function "eclair_symbol_delete" [(ptr symTy, "sym")] void $ \[s] -> call freeFn [s] let initArgs = [(ptr symTy, "sym"), (i32, "length"), (ptr i8, "data")] _ <- function "eclair_symbol_init_helper" initArgs void $ \[s, len, str] -> do -- Needed because "str" is freed afterwards memory <- call mallocFn [len] _ <- call memcpyFn [memory, str, len, bit 0] _ <- call (symbolInit sym) [s, len, memory] pass let isEqArgs = [(ptr symTy, "sym1"), (ptr symTy, "sym2")] _ <- function "eclair_symbol_is_equal_helper" isEqArgs i8 $ \[sym1, sym2] -> do isEq <- call (symbolIsEqual sym) [sym1, sym2] ret =<< isEq `zext` i8 _ <- function "eclair_symbol_length" [(ptr symTy, "sym")] i32 $ \[s] -> do lenPtr <- gep s [int32 0, int32 0] ret =<< load lenPtr 0 _ <- function "eclair_symbol_data" [(ptr symTy, "sym")] (ptr i8) $ \[s] -> do lenPtr <- gep s [int32 0, int32 1] ret =<< load lenPtr 0 pass loadNativeCode :: FilePath -> IO Bindings loadNativeCode dir = do lib <- dlopen (soFile dir) [RTLD_LAZY] loadNativeCode' lib loadNativeCode' :: DL -> IO Bindings loadNativeCode' lib = do fnNew <- dlsym lib "eclair_symbol_new" fnDelete <- dlsym lib "eclair_symbol_delete" fnInit <- dlsym lib "eclair_symbol_init_helper" fnDestroy <- dlsym lib "eclair_symbol_destroy" fnIsEqual <- dlsym lib "eclair_symbol_is_equal_helper" fnLength <- dlsym lib "eclair_symbol_length" fnData <- dlsym lib "eclair_symbol_data" let getLength = mkLength fnLength pure $ Bindings { dynamicLib = lib , withSymbol = mkWithSymbol fnNew fnDelete , bInit = mkInit fnInit , bDestroy = mkDestroy fnDestroy , bIsEqual = mkIsEqual fnIsEqual , bLength = getLength , bData = mkData fnData getLength } where mkNew fn = callFFI fn (retPtr retVoid) [] mkDelete fn sym = callFFI fn retVoid [argPtr sym] mkWithSymbol fnNew fnDelete = bracket (castPtr <$> mkNew fnNew) (mkDelete fnDelete) mkInit fn sym str = do let len = fromIntegral $ length str callFFI fn retVoid [argPtr sym, argCUInt len, argString str] mkDestroy fn sym = callFFI fn retVoid [argPtr sym] mkIsEqual fn sym1 sym2 = do result <- callFFI fn retCUChar [argPtr sym1, argPtr sym2] pure $ result == 1 mkLength fn sym = do fromIntegral <$> callFFI fn retCUInt [argPtr sym] mkData fn getLength sym = do len <- fromIntegral <$> getLength sym strPtr <- callFFI fn (retPtr retCChar) [argPtr sym] peekCAStringLen (strPtr, len) soFile :: FilePath -> FilePath soFile dir = dir "symbol.so" llFile :: FilePath -> FilePath llFile dir = dir "symbol.ll" ================================================ FILE: tests/eclair/Test/Eclair/LLVM/VectorSpec.hs ================================================ {-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-} module Test.Eclair.LLVM.VectorSpec ( module Test.Eclair.LLVM.VectorSpec ) where import Prelude hiding (void) import qualified LLVM.C.API as LibLLVM import Eclair.LLVM.Vector import Eclair.LLVM.Codegen hiding (retVoid, nullPtr) import Eclair.LLVM.Externals import Foreign.LibFFI import Foreign hiding (void) import System.Posix.DynamicLinker import Control.Exception import System.Directory.Extra import System.Process.Extra import System.FilePath import Test.Hspec type Value = Int data Bindings = Bindings { dynamicLib :: DL , withVec :: (Ptr Vector -> IO ()) -> IO () , bInit :: Ptr Vector -> IO () , bDestroy :: Ptr Vector -> IO () , bPush :: Ptr Vector -> Value -> IO Word32 , bSize :: Ptr Vector -> IO Word64 , bCapacity :: Ptr Vector -> IO Word64 , bGetValue :: Ptr Vector -> Int -> IO Value } spec :: Spec spec = describe "Vector" $ aroundAll (setupAndTeardown testDir) $ parallel $ do it "can be initialized and destroyed" $ \bindings -> withVec bindings $ \v -> do bInit bindings v bDestroy bindings v it "can store multiple elements" $ \bindings -> do withVec bindings $ \v -> do bInit bindings v idx1 <- bPush bindings v 42 idx2 <- bPush bindings v 123 value2 <- bGetValue bindings v 1 value1 <- bGetValue bindings v 0 bDestroy bindings v idx1 `shouldBe` 0 idx2 `shouldBe` 1 value1 `shouldBe` 42 value2 `shouldBe` 123 it "can store duplicate values" $ \bindings -> do withVec bindings $ \v -> do bInit bindings v idx1 <- bPush bindings v 42 idx2 <- bPush bindings v 42 value1 <- bGetValue bindings v 0 value2 <- bGetValue bindings v 1 bDestroy bindings v idx1 `shouldBe` 0 idx2 `shouldBe` 1 value1 `shouldBe` 42 value2 `shouldBe` 42 it "keeps track of the number of elements inside" $ \bindings -> withVec bindings $ \v -> do bInit bindings v bSize bindings v >>= (`shouldBe` 0) -- This vector allocates on initialization bCapacity bindings v >>= (`shouldBe` 16) _ <- bPush bindings v 1 bSize bindings v >>= (`shouldBe` 1) bCapacity bindings v >>= (`shouldBe` 16) _ <- bPush bindings v 2 bSize bindings v >>= (`shouldBe` 2) bCapacity bindings v >>= (`shouldBe` 16) for_ [0..13] $ bPush bindings v bSize bindings v >>= (`shouldBe` 16) bCapacity bindings v >>= (`shouldBe` 16) _ <- bPush bindings v 42 bSize bindings v >>= (`shouldBe` 17) bCapacity bindings v >>= (`shouldBe` 32) for_ [0..15] $ bPush bindings v bSize bindings v >>= (`shouldBe` 33) bCapacity bindings v >>= (`shouldBe` 64) bDestroy bindings v it "always keeps order of elements, even after resizing" $ \bindings -> withVec bindings $ \v -> do bInit bindings v -- This does several reallocations for_ [0..99] $ bPush bindings v bSize bindings v >>= (`shouldBe` 100) bCapacity bindings v >>= (`shouldBe` 128) for_ [0..99] $ \i -> do bGetValue bindings v i >>= (`shouldBe` i) bDestroy bindings v setupAndTeardown :: FilePath -> ActionWith Bindings -> IO () setupAndTeardown dir = bracket (setup dir) teardown setup :: FilePath -> IO Bindings setup dir = do createDirectoryIfMissing False dir compileCode cgExternals cgTestCode dir loadNativeCode dir teardown :: Bindings -> IO () teardown = dlclose . dynamicLib compileCode :: ModuleBuilderT IO Externals -> (Vector -> Operand -> Operand -> ModuleBuilderT IO ()) -> FilePath -> IO () compileCode cgExts cgHelperCode dir = do ctx <- LibLLVM.mkContext llvmMod <- LibLLVM.mkModule ctx "eclair" td <- LibLLVM.getTargetData llvmMod llvmIR <- runModuleBuilderT $ do exts <- cgExts let cfg = Config Nothing ctx td vec <- instantiate "test" i32 $ runConfigT cfg $ codegen exts Nothing -- TODO destructor cgHelperCode vec (extMalloc exts) (extFree exts) let llvmIRText = ppllvm llvmIR writeFileText (llFile dir) llvmIRText callProcess "clang" ["-fPIC", "-shared", "-O0", "-o", soFile dir, llFile dir] cgExternals :: ModuleBuilderT IO Externals cgExternals = do mallocFn <- extern "malloc" [i32] (ptr i8) freeFn <- extern "free" [ptr i8] void memcpyFn <- extern "llvm.memcpy.p0i8.p0i8.i64" [ptr i8, ptr i8, i64, i1] void pure $ Externals mallocFn freeFn notUsed memcpyFn notUsed notUsed notUsed cgTestCode :: Vector -> Operand -> Operand -> ModuleBuilderT IO () cgTestCode vec mallocFn freeFn = do let vecTypes = vectorTypes vec vecTy = tyVector vecTypes valueTy = tyElement vecTypes _ <- function "eclair_vector_new_test" [] (ptr vecTy) $ \[] -> ret =<< call mallocFn [int32 24] _ <- function "eclair_vector_delete_test" [(ptr vecTy, "vec")] void $ \[v] -> call freeFn [v] _ <- function "eclair_vector_capacity_test" [(ptr vecTy, "vec")] i32 $ \[v] -> do capPtr <- gep v [int32 0, int32 2] ret =<< load capPtr 0 _ <- function "eclair_value_new_test" [(i32, "value")] (ptr valueTy) $ \[v] -> do vPtr <- call mallocFn [int32 4] store vPtr 0 v ret vPtr _ <- function "eclair_value_delete_test" [(ptr valueTy, "value")] void $ \[v] -> call freeFn [v] pass loadNativeCode :: FilePath -> IO Bindings loadNativeCode dir = do lib <- dlopen (soFile dir) [RTLD_LAZY] fnNew <- dlsym lib "eclair_vector_new_test" fnDelete <- dlsym lib "eclair_vector_delete_test" fnValueNew <- dlsym lib "eclair_value_new_test" fnValueDelete <- dlsym lib "eclair_value_delete_test" fnInit <- dlsym lib "eclair_vector_init_test" fnDestroy <- dlsym lib "eclair_vector_destroy_test" fnPush <- dlsym lib "eclair_vector_push_test" fnSize <- dlsym lib "eclair_vector_size_test" fnCapacity <- dlsym lib "eclair_vector_capacity_test" fnGetValue <- dlsym lib "eclair_vector_get_value_test" pure $ Bindings { dynamicLib = lib , withVec = mkWithVec fnNew fnDelete , bInit = mkInit fnInit , bDestroy = mkDestroy fnDestroy , bPush = mkPush fnValueNew fnValueDelete fnPush , bSize = mkSize fnSize , bCapacity = mkCapacity fnCapacity , bGetValue = mkGetValue fnGetValue } where mkNew fn = callFFI fn (retPtr retVoid) [] mkDelete fn vec = callFFI fn retVoid [argPtr vec] mkWithVec fnNew fnDelete = bracket (castPtr <$> mkNew fnNew) (mkDelete fnDelete) mkInit fn vec = callFFI fn retVoid [argPtr vec] mkDestroy fn vec = callFFI fn retVoid [argPtr vec] mkPush fnValueNew fnValueDelete fn vec value = withValue fnValueNew fnValueDelete value $ \valuePtr -> fromIntegral <$> callFFI fn retCUInt [argPtr vec, argPtr valuePtr] mkSize fn vec = fromIntegral <$> callFFI fn retCULong [argPtr vec] mkCapacity fn vec = fromIntegral <$> callFFI fn retCULong [argPtr vec] mkGetValue fn vec idx = do resultPtr <- callFFI fn (retPtr retCUInt) [argPtr vec, argCUInt $ fromIntegral idx] fromIntegral <$> peek resultPtr withValue fnNew fnDelete value = bracket (castPtr <$> callFFI fnNew (retPtr retCUChar) [argCUInt $ fromIntegral value]) (\valuePtr -> callFFI fnDelete retVoid [argPtr valuePtr]) testDir :: FilePath testDir = "/tmp/eclair-vector" llFile, soFile :: FilePath -> FilePath llFile dir = dir "vector.ll" soFile dir = dir "vector.so" notUsed :: a notUsed = panic "Not used" ================================================ FILE: tests/eclair/Test/Eclair/LSP/HandlersSpec.hs ================================================ module Test.Eclair.LSP.HandlersSpec ( module Test.Eclair.LSP.HandlersSpec ) where import Eclair import Eclair.TypeSystem import Eclair.Common.Location import Eclair.LSP.Monad import Eclair.LSP.Handlers import qualified Data.Map as M import Test.Hspec spec :: Spec spec = describe "LSP handlers" $ parallel $ do hoverSpec documentHighlightSpec diagnosticsSpec hoverSpec :: Spec hoverSpec = describe "Hover action" $ do it "reports types on hover" $ do let file = fixture "hover.eclair" srcPos1 = SourcePos 5 7 -- 0-indexed! srcPos2 = SourcePos 11 9 (result1, result2) <- withLSP (Just file) $ do (,) <$> hoverHandler file srcPos1 <*> hoverHandler file srcPos2 result1 `shouldBe` HoverOk (SourceSpan file (SourcePos 5 7) (SourcePos 5 8)) U32 result2 `shouldBe` HoverOk (SourceSpan file (SourcePos 11 8) (SourcePos 11 13)) Str it "returns an error if file not found in vfs" $ do let file = "not_found.eclair" srcPos = SourcePos 11 10 result <- withLSP Nothing $ hoverHandler file srcPos result `shouldBe` HoverError file (SourcePos 11 10) "File not found in VFS!" it "returns an error if no hover information available for position" $ do let file = fixture "hover.eclair" srcPos1 = SourcePos 4 1 -- whitespace srcPos2 = SourcePos 14 10 -- outside of file bounds (result1, result2) <- withLSP (Just file) $ do (,) <$> hoverHandler file srcPos1 <*> hoverHandler file srcPos2 result1 `shouldBe` HoverError file srcPos1 "No type information for this position!" result2 `shouldBe` HoverError file srcPos2 "Error computing location offset in file!" it "returns an error if file failed to parse" $ do let file = fixture "unparsable.eclair" srcPos = SourcePos 4 1 result <- withLSP (Just file) $ hoverHandler file srcPos result `shouldBe` HoverError file srcPos "File contains errors!" it "returns an error if file failed to typecheck" $ do let file = fixture "type_errors.eclair" srcPos = SourcePos 4 1 result <- withLSP (Just file) $ hoverHandler file srcPos result `shouldBe` HoverError file srcPos "File contains errors!" documentHighlightSpec :: Spec documentHighlightSpec = describe "Document highlight action" $ do it "highlights the same identifiers in scope" $ do let file = fixture "document_highlight.eclair" srcPos1 = SourcePos 6 10 -- x srcPos2 = SourcePos 6 13 -- y srcPos3 = SourcePos 7 10 -- z (result1, result2, result3) <- withLSP (Just file) $ do (,,) <$> documentHighlightHandler file srcPos1 <*> documentHighlightHandler file srcPos2 <*> documentHighlightHandler file srcPos3 result1 `shouldBe` DocHLOk [ SourceSpan file (SourcePos 6 10) (SourcePos 6 11) , SourceSpan file (SourcePos 7 7) (SourcePos 7 8) ] result2 `shouldBe` DocHLOk [ SourceSpan file (SourcePos 6 13) (SourcePos 6 14) , SourceSpan file (SourcePos 8 15) (SourcePos 8 16) ] result3 `shouldBe` DocHLOk [ SourceSpan file (SourcePos 7 10) (SourcePos 7 11) , SourceSpan file (SourcePos 8 12) (SourcePos 8 13) ] it "returns an error if file not found in vfs" $ do let file = "not_found.eclair" srcPos = SourcePos 11 10 result <- withLSP Nothing $ documentHighlightHandler file srcPos result `shouldBe` DocHLError file (SourcePos 11 10) "File not found in VFS!" it "returns an error if file failed to parse" $ do let file = fixture "unparsable.eclair" srcPos = SourcePos 4 1 result <- withLSP (Just file) $ documentHighlightHandler file srcPos result `shouldBe` DocHLError file srcPos "Failed to get highlight information!" diagnosticsSpec :: Spec diagnosticsSpec = describe "Diagnostics action" $ parallel $ do it "reports nothing if file is OK" $ do let file = fixture "hover.eclair" DiagnosticsOk diags <- withLSP (Just file) $ diagnosticsHandler file length diags `shouldBe` 0 it "reports invalid syntax" $ do let file = fixture "invalid_syntax.eclair" DiagnosticsOk diags <- withLSP (Just file) $ diagnosticsHandler file length diags `shouldBe` 2 it "returns an error if file not found in vfs" $ do let file = "not_found.eclair" result <- withLSP Nothing $ diagnosticsHandler file result `shouldBe` DiagnosticsError file Nothing "File not found in VFS!" it "reports semantic errors" $ do let file = fixture "semantic_errors.eclair" DiagnosticsOk [diag] <- withLSP (Just file) $ diagnosticsHandler file let (Diagnostic _ _ _ msg) = diag toString msg `shouldContain` "Wildcard in top level fact" it "reports type errors" $ do let file = fixture "type_errors.eclair" DiagnosticsOk (_:_:diag:_) <- withLSP (Just file) $ diagnosticsHandler file let (Diagnostic _ _ _ msg) = diag toString msg `shouldContain` "Type mismatch" fixture :: FilePath -> FilePath fixture file = "./tests/eclair/fixtures/lsp/" <> file withLSP :: Maybe FilePath -> LspM a -> IO a withLSP mFile m = runLSP $ do case mFile of Nothing -> pass Just file -> do fileContents <- decodeUtf8 <$> readFileBS file lift $ vfsSetFile file fileContents vfsVar <- lift getVfsVar let readVFS path = do vfs <- readMVar vfsVar pure $! M.lookup path vfs let params = Parameters 1 Nothing readVFS local (const params) m ================================================ FILE: tests/eclair/Test/Eclair/LSP/JSONSpec.hs ================================================ {-# LANGUAGE QuasiQuotes #-} module Test.Eclair.LSP.JSONSpec ( module Test.Eclair.LSP.JSONSpec ) where import qualified Data.Hermes as H import Eclair.Common.Location import Eclair.LSP.Types import Eclair.LSP.JSON import Eclair.LSP.Handlers import Eclair.JSON import Eclair.TypeSystem import Test.Hspec import NeatInterpolation decodesAs :: (Eq a, Show a) => H.Decoder a -> Text -> a -> IO () decodesAs decoder txt expected = H.decodeEither decoder (encodeUtf8 txt) `shouldBe` Right expected spec :: Spec spec = describe "LSP JSON processing" $ parallel $ do describe "JSON encoding" $ parallel $ do it "can encode response to JSON" $ do encodeJSON (responseToJSON (HoverResponse (HoverOk (SourceSpan "/etc/passwd" (SourcePos 11 13) (SourcePos 17 19)) U32))) `shouldBe` [text| {"type":"success","hover":{"location":{"file":"/etc/passwd","start":{"line":11,"column":13},"end":{"line":17,"column":19}},"type":"u32"}} |] encodeJSON (responseToJSON (HoverResponse (HoverError "/etc/passwd" (SourcePos 11 13) "sample hover error message"))) `shouldBe` [text| {"type":"error","error":{"file":"/etc/passwd","position":{"line":11,"column":13},"message":"sample hover error message"}} |] encodeJSON (responseToJSON (DocumentHighlightResponse (DocHLOk [SourceSpan "/etc/passwd" (SourcePos 11 13) (SourcePos 17 19)]))) `shouldBe` [text| {"type":"success","highlights":[{"file":"/etc/passwd","start":{"line":11,"column":13},"end":{"line":17,"column":19}}]} |] encodeJSON (responseToJSON (DocumentHighlightResponse (DocHLError "/etc/passwd" (SourcePos 11 13) "sample highlight error message"))) `shouldBe` [text| {"type":"error","error":{"file":"/etc/passwd","position":{"line":11,"column":13},"message":"sample highlight error message"}} |] encodeJSON (responseToJSON (DiagnosticsResponse (DiagnosticsOk [Diagnostic Parser (SourceSpan "/etc/passwd" (SourcePos 11 13) (SourcePos 17 19)) Error "sample diagnostic message"]))) `shouldBe` [text| {"type":"success","diagnostics":[{"location":{"file":"/etc/passwd","start":{"line":11,"column":13},"end":{"line":17,"column":19}},"source":"Parser","severity":"error","message":"sample diagnostic message"}]} |] encodeJSON (responseToJSON (DiagnosticsResponse (DiagnosticsError "/etc/passwd" (Just (SourcePos 11 13)) "sample diagnostic error message"))) `shouldBe` [text| {"type":"error","error":{"file":"/etc/passwd","position":{"line":11,"column":13},"message":"sample diagnostic error message"}} |] encodeJSON (responseToJSON (DiagnosticsResponse (DiagnosticsError "/etc/passwd" Nothing "sample diagnostic error message"))) `shouldBe` [text| {"type":"error","error":{"file":"/etc/passwd","position":{"line":0,"column":0},"message":"sample diagnostic error message"}} |] encodeJSON (responseToJSON SuccessResponse) `shouldBe` [text| {"success":true} |] encodeJSON (responseToJSON ShuttingDown) `shouldBe` [text| {"shutdown":true} |] it "can encode diagnostic to JSON" $ do encodeJSON (diagnosticToJSON (Diagnostic Parser (SourceSpan "/etc/passwd" (SourcePos 11 13) (SourcePos 17 19)) Error "sample diagnostic message")) `shouldBe` [text| {"location":{"file":"/etc/passwd","start":{"line":11,"column":13},"end":{"line":17,"column":19}},"source":"Parser","severity":"error","message":"sample diagnostic message"} |] it "can encode diagnostic source to JSON" $ do encodeJSON (diagnosticSourceToJSON Parser) `shouldBe` [text| "Parser" |] encodeJSON (diagnosticSourceToJSON Typesystem) `shouldBe` [text| "Typesystem" |] encodeJSON (diagnosticSourceToJSON SemanticAnalysis) `shouldBe` [text| "SemanticAnalysis" |] encodeJSON (diagnosticSourceToJSON Transpiler) `shouldBe` [text| "Transpiler" |] it "can encode severity to JSON" $ do encodeJSON (severityToJSON Error) `shouldBe` [text| "error" |] it "can encode source span to JSON" $ do encodeJSON (srcSpanToJSON (SourceSpan "/etc/passwd" (SourcePos 11 13) (SourcePos 17 19))) `shouldBe` [text| {"file":"/etc/passwd","start":{"line":11,"column":13},"end":{"line":17,"column":19}} |] it "can encode source position to JSON" $ do encodeJSON (srcPosToJSON (SourcePos 32 58)) `shouldBe` [text| {"line":32,"column":58} |] it "can encode type to JSON" $ do encodeJSON (typeToJSON U32) `shouldBe` [text| "u32" |] encodeJSON (typeToJSON Str) `shouldBe` [text| "string" |] describe "JSON decoding" $ parallel $ do it "can decode hover command from JSON" $ do decodesAs commandDecoder [text| { "type": "hover", "command": { "position": {"line": 100, "column": 22}, "file": "/tmp/file.eclair" } } |] (Hover "/tmp/file.eclair" (SourcePos 100 22)) it "can decode document-highlight command from JSON" $ do decodesAs commandDecoder [text| { "type": "document-highlight", "command": { "position": {"line": 100, "column": 22}, "file": "/tmp/file.eclair" } } |] (DocumentHighlight "/tmp/file.eclair" (SourcePos 100 22)) it "can decode diagnostics command from JSON" $ do decodesAs commandDecoder [text| { "type": "diagnostics", "command": { "file": "/tmp/file.eclair" } } |] (Diagnostics "/tmp/file.eclair") it "can decode update-vfs command from JSON" $ do decodesAs commandDecoder [text| { "type": "update-vfs", "command": { "file": "/etc/passwd", "contents": "root:*:0:0:System Administrator:/var/root:/bin/sh" } } |] (UpdateVFS "/etc/passwd" "root:*:0:0:System Administrator:/var/root:/bin/sh") it "can decode a hover command from JSON" $ do decodesAs hoverDecoder [text| { "position": {"line": 100, "column": 22}, "file": "/tmp/file.eclair" } |] (Hover "/tmp/file.eclair" (SourcePos 100 22)) it "can decode a document highlight command from JSON" $ do decodesAs referencesDecoder [text| { "position": {"line": 100, "column": 22}, "file": "/tmp/file.eclair" } |] (DocumentHighlight "/tmp/file.eclair" (SourcePos 100 22)) it "can decode a diagnostics command from JSON" $ do decodesAs diagnosticsDecoder [text| { "file": "/etc/passwd" } |] (Diagnostics "/etc/passwd") it "can decode a update-vfs command from JSON" $ do decodesAs updateVfsDecoder [text| { "file": "/etc/passwd", "contents": "root:*:0:0:System Administrator:/var/root:/bin/sh" } |] (UpdateVFS "/etc/passwd" "root:*:0:0:System Administrator:/var/root:/bin/sh") it "can decode a source position from JSON" $ do decodesAs srcPosDecoder [text|{"line": 42, "column": 10}|] (SourcePos 42 10) ================================================ FILE: tests/eclair/Test/Eclair/RA/IndexSelectionSpec.hs ================================================ {-# LANGUAGE QuasiQuotes #-} module Test.Eclair.RA.IndexSelectionSpec ( module Test.Eclair.RA.IndexSelectionSpec ) where import qualified Data.Map as Map import qualified Data.Set as Set import Test.Hspec import System.FilePath import Eclair.Common.Id import Eclair.Parser import Eclair.AST.Lower import Eclair.RA.IndexSelection import Eclair.RA.Transforms import qualified Eclair.TypeSystem as TS import qualified Data.Text as T import NeatInterpolation idxSel :: FilePath -> Text -> IndexMap idxSel path text' = do let file = "tests/fixtures" path <.> "eclair" ast = (\(parsed, _, _, _) -> parsed) $ parseText file text' in case TS.typeCheck ast of Left _ -> panic $ "Failed to typecheck " <> toText file <> "!" Right typeInfo -> do let ra = simplify $ compileToRA [] ast (indexMap, _) = runIndexSelection (TS.infoTypedefs typeInfo) ra in indexMap toSelection :: [(T.Text, [[Column]])] -> IndexMap toSelection info = idxMap where (texts, colss) = unzip info f text' cols = (Id text', Set.fromList $ map Index cols) idxMap = Map.fromList $ zipWith f texts colss spec :: Spec spec = describe "Index selection" $ parallel $ do it "creates indexes for a single fact" $ do idxSel "single_fact" [text| @def edge(u32, u32). @def another(u32, u32, u32). edge(1, 2). edge(2, 3). another(1,2,3). |] `shouldBe` toSelection [("another", [[0,1,2]]), ("edge", [[0,1]])] it "creates indexes for a single non-recursive rule" $ do idxSel "single_nonrecursive_rule" [text| @def edge(u32, u32). @def path(u32, u32). edge(1,2). path(x,y) :- edge(x,y). |] `shouldBe` toSelection [("edge", [[0,1]]), ("path", [[0,1]])] it "creates indexes for nested searches correctly" $ do idxSel "multiple_rule_clauses" [text| @def first(u32). @def second(u32, u32). @def third(u32, u32). first(1). second(2, 3). third(x, y) :- first(y), second(x, y). |] `shouldBe` toSelection [ ("first", [[0]]) , ("second", [[1,0]]) , ("third", [[0,1]]) ] it "creates indexes for rules with equal columns correctly" $ do idxSel "rule_equal_columns" [text| @def a(u32). @def b(u32, u32). @def c(u32, u32, u32). @def d(u32, u32, u32, u32). @def other(u32). a(1). b(2, 3). c(4, 5, 6). d(7, 8, 9, 10). other(11). a(x) :- b(x, x), other(x). a(y) :- c(y, y, y), other(y). a(z) :- d(z, z, 12, z), other(z). |] `shouldBe` toSelection [ ("a", [[0]]) , ("b", [[0,1]]) , ("c", [[0,1,2]]) , ("d", [[2,0,1,3]]) , ("other", [[0]]) ] it "handles multiple indexes on 1 rule correctly" $ do idxSel "index_selection" [text| @def a(u32). @def b(u32). @def c(u32, u32, u32). @def d(u32). @def triple(u32, u32, u32). a(1). b(1). c(1, 2, 3). d(1). triple(4, 5, 6). a(y) :- // [2] triple(x, y, 123). b(x) :- // [0,1] => [0,1,2] triple(123, 456, x). c(x, y, z) :- // [0,1,2] triple(x, y, z). d(x) :- // [0, 2] triple(123, x, 456). |] `shouldBe` toSelection [ ("a", [[0]]) , ("b", [[0]]) , ("c", [[0,1,2]]) , ("d", [[0]]) , ("triple", [[0,1,2], [2,0]]) ] it "selects a minimal set of indexes for a rule" $ do idxSel "minimal_index_selection" [text| @def first(u32, u32, u32). @def second(u32). @def third(u32). @def fourth(u32). @def fifth(u32). // [1,0,2] ([0,1,2] re-ordered) first(1, 2, 3). second(1). third(1). fourth(1). fifth(1). second(x) :- // [0,1] => [1,0,2] first(123, 456, x). third(x) :- // [2,1] first(x, 123, 456). fourth(x) :- // [2] => [2,1] first(x, a, 123). fifth(x) :- // [1] => [1,0,2] first(x, 123, a). |] `shouldBe` toSelection [ ("first", [[1,0,2], [2,1]]) , ("second", [[0]]) , ("third", [[0]]) , ("fourth", [[0]]) , ("fifth", [[0]]) ] it "creates indexes for a rule with 2 clauses of same name" $ do idxSel "multiple_clauses_same_name" [text| @def link(u32, u32). @def chain(u32, u32, u32). link(1,2). chain(x, y, z) :- link(x, y), link(y, z). |] `shouldBe` toSelection [ ("link", [[0,1]]) , ("chain", [[0,1,2]]) ] it "creates indexes for a single recursive rule" $ do idxSel "single_recursive_rule" [text| @def edge(u32, u32). @def path(u32, u32). edge(1,2). path(x, y) :- edge(x, z), path(z, y). |] `shouldBe` toSelection [ ("delta_path", [[0,1]]) , ("new_path", [[0,1]]) , ("path", [[0,1]]) , ("edge", [[0,1]]) ] -- TODO variant where one is recursive it "creates indexes for mutually recursive rules" $ do idxSel "mutually_recursive_rules" [text| @def a(u32). @def b(u32). @def c(u32). @def d(u32). a(x) :- b(x), c(x). b(1). b(x) :- c(x), d(x). c(2). c(x) :- b(x), d(x). d(3). |] `shouldBe` toSelection [ ("a", [[0]]) , ("b", [[0]]) , ("new_b", [[0]]) , ("delta_b", [[0]]) , ("c", [[0]]) , ("new_c", [[0]]) , ("delta_c", [[0]]) , ("d", [[0]]) ] -- TODO tests for rules with >2 clauses, ... it "calculates index correctly for multiple columns at once" $ do idxSel "index_for_chain" [text| @def a(u32, u32, u32, u32, u32). @def b(u32). a(1,2,3,4,5). b(1). b(x) :- // [0,1] a(123, 123, x, y, z). b(x) :- // [2,3,4] a(x, y, 123, 123, 123). b(x) :- // [4] => [4,2,3] a(x, y, z, a, 123). |] `shouldBe` toSelection [ ("a", [[0,1,2,3,4], [4,2,3]]) , ("b", [[0]]) ] it "calculates indexes correctly for programs with no top level facts" $ do idxSel "no_top_level_facts" [text| @def edge(u32, u32). @def path(u32, u32). path(x, y) :- edge(x, y). path(x, z) :- edge(x, y), path(y, z). |] `shouldBe` toSelection [ ("delta_path", [[0,1]]) , ("new_path", [[0,1]]) , ("path", [[0,1]]) , ("edge", [[0,1]]) ] it "does not use 'NoElem' constraints to compute indexes" $ do idxSel "index_selection_should_only_check_for_equalities" [text| @def edge(u32, u32). @def reachable(u32, u32). reachable(x, y) :- edge(x, y). reachable(x, z) :- edge(x, _), reachable(_, z). |] `shouldBe` toSelection [ ("delta_reachable", [[0,1]]) , ("new_reachable", [[0,1]]) , ("reachable", [[0,1]]) , ("edge", [[0,1]]) ] it "creates indexes for a rule with arithmetic" $ do idxSel "multiple_clauses_same_name" [text| @def first(u32). @def second(u32, u32). @def third(u32, u32). first(1). second(2, 3). third(x + 1, y) :- first(y), second(x, y + 1). |] `shouldBe` toSelection [ ("first", [[0]]) , ("second", [[1,0]]) , ("third", [[0,1]]) ] it "creates indexes for negations with wildcards correctly" $ do idxSel "multiple_rule_clauses" [text| @def first(u32). @def second(u32, u32). @def third(u32, u32). first(1). second(2, 3). third(x, x) :- first(x), !second(_, x). |] `shouldBe` toSelection [ ("first", [[0]]) , ("second", [[1,0]]) , ("third", [[0,1]]) ] idxSel "multiple_rule_clauses" [text| @def first(u32). @def second(u32, u32). @def third(u32, u32). @def fourth(u32). first(1). second(2, 3). third(x, x) :- first(x), !second(_, x). fourth(x) :- first(x), !second(x, _). |] `shouldBe` toSelection [ ("first", [[0]]) , ("second", [[0,1], [1]]) , ("third", [[0,1]]) , ("fourth", [[0]]) ] ================================================ FILE: tests/eclair/fixtures/lsp/document_highlight.eclair ================================================ @def edge(u32, u32). @def reachable(u32, u32). reachable(x, y) :- edge(x, y). reachable(x, y) :- edge(x, z), reachable(z, y). ================================================ FILE: tests/eclair/fixtures/lsp/hover.eclair ================================================ @def edge(u32, u32) input. @def reachable(u32, u32) output. @def literal(string) output. reachable(x, y) :- edge(x, y). reachable(x, z) :- edge(x, y), reachable(y, z). literal("abc"). ================================================ FILE: tests/eclair/fixtures/lsp/invalid_syntax.eclair ================================================ @def number(u32) output. number(123, ). number(456). number(789, ). ================================================ FILE: tests/eclair/fixtures/lsp/semantic_errors.eclair ================================================ @def wildcard_in_fact(u32) output. wildcard_in_fact(_). ================================================ FILE: tests/eclair/fixtures/lsp/type_errors.eclair ================================================ @def edge(u32, u32). @def reachable(string, u32). @def literal(u32). reachable(x, y) :- edge(x, y). reachable(x, z) :- edge(x, y), reachable(y, z). literal("abc"). ================================================ FILE: tests/eclair/fixtures/lsp/unparsable.eclair ================================================ @def edge(u32, u32). @def reachable(u32, u32). @def literal(string). reachable(x, y) :- reachable(x, z) :- 1. literal("abc") ================================================ FILE: tests/eclair/test.hs ================================================ {-# OPTIONS_GHC -Wno-missing-export-lists #-} {-# OPTIONS_GHC -F -pgmF hspec-discover #-} ================================================ FILE: tests/end_to_end/compile_and_run_native.eclair ================================================ // This checks if eclair can be correctly compiled and linked with C code. // RUN: split-file %s %t // RUN: %eclair compile %t/program.eclair > %t/program.ll // RUN: %clang -o %t/program -O0 %t/main.c %t/program.ll // RUN: %t/program | FileCheck %s // CHECK: (1, 2) // CHECK-NEXT: (1, 3) // CHECK-NEXT: (2, 3) //--- program.eclair @def edge(u32, u32) input. @def reachable(u32, u32) output. reachable(x, y) :- edge(x, y). reachable(x, y) :- edge(x, z), reachable(z, y). //--- main.c #include #include #include #include struct program; extern struct program* eclair_program_init(); extern void eclair_program_destroy(struct program*); extern void eclair_program_run(struct program*); extern void eclair_add_facts(struct program*, uint32_t fact_type, uint32_t* data, size_t fact_count); extern void eclair_add_fact(struct program*, uint32_t fact_type, uint32_t* data); extern uint32_t* eclair_get_facts(struct program*, uint32_t fact_type); extern void eclair_free_buffer(uint32_t* data); int main(int argc, char** argv) { struct program* prog = eclair_program_init(); // edge(1,2), edge(2,3) uint32_t data[] = { 1, 2, 2, 3 }; eclair_add_facts(prog, 0, data, 2); eclair_program_run(prog); // NOTE: normally you call btree_size here to figure out the size uint32_t* data_out = eclair_get_facts(prog, 1); printf("REACHABLE: (%d, %d)\n", data_out[0], data_out[1]); // (1,2) printf("REACHABLE: (%d, %d)\n", data_out[2], data_out[3]); // (2,3) printf("REACHABLE: (%d, %d)\n", data_out[4], data_out[5]); // (1,3) eclair_free_buffer(data_out); eclair_program_destroy(prog); return 0; } ================================================ FILE: tests/end_to_end/compile_and_run_wasm.eclair ================================================ // This checks if eclair can be correctly compiled to WASM and used from JS. // RUN: mkdir -p %t && sed s@TEST_DIR@%t@g %s > %t/input.test // RUN: split-file %t/input.test %t // TODO: we really need to have our own WASM allocator.. // RUN: test -e %t/walloc.c || wget https://raw.githubusercontent.com/wingo/walloc/master/walloc.c -O %t/walloc.c // RUN: %clang -O0 --target=wasm32 -mbulk-memory -nostdlib -c -o %t/walloc.o %t/walloc.c // RUN: %eclair compile --target wasm32 %t/program.eclair > %t/program.ll // RUN: %clang -O0 --target=wasm32 -mbulk-memory -nostdlib -c -o %t/program.o %t/program.ll // RUN: %wasm-ld --no-entry --import-memory -o %t/program.wasm %t/program.o %t/walloc.o // RUN: node %t/program.js | FileCheck %s // CHECK: [ 1, 2 ] // CHECK-NEXT: [ 1, 3 ] // CHECK-NEXT: [ 1, 4 ] // CHECK-NEXT: [ 2, 3 ] // CHECK-NEXT: [ 2, 4 ] // CHECK-NEXT: [ 3, 4 ] //--- program.eclair @def edge(u32, u32) input. @def reachable(u32, u32) output. reachable(x, y) :- edge(x, y). reachable(x, y) :- edge(x, z), reachable(z, y). //--- program.js const fs = require("fs"); const addFact = ( instance, memory, program, factType, factArray, columnCount ) => { const byteCount = columnCount * Uint32Array.BYTES_PER_ELEMENT; const address = instance.exports.eclair_malloc(byteCount); const array = new Uint32Array(memory.buffer, address, byteCount); array.set(factArray); instance.exports.eclair_add_fact(program, factType, address); instance.exports.eclair_free(address); }; const addFacts = ( instance, memory, program, factType, factsArray, columnCount ) => { const byteCount = factsArray.length * columnCount * Uint32Array.BYTES_PER_ELEMENT; const address = instance.exports.eclair_malloc(byteCount); const array = new Uint32Array(memory.buffer, address, byteCount); array.set(factsArray.flat()); instance.exports.eclair_add_facts( program, factType, address, factsArray.length ); instance.exports.eclair_free(address); }; const EDGE = 0; const REACHABLE = 1; const NUM_COLUMNS = 2; const main = () => { const bytes = fs.readFileSync("TEST_DIR/program.wasm"); const mod = new WebAssembly.Module(bytes); const memory = new WebAssembly.Memory({ initial: 3, maximum: 3 }); const imports = { env: { memory } }; const instance = new WebAssembly.Instance(mod, imports); const program = instance.exports.eclair_program_init(); addFact(instance, memory, program, EDGE, [1, 2], NUM_COLUMNS); addFacts(instance, memory, program, EDGE, [[2, 3], [3, 4]], NUM_COLUMNS); instance.exports.eclair_program_run(program); const resultAddress = instance.exports.eclair_get_facts(program, REACHABLE); const factCount = instance.exports.eclair_fact_count(program, REACHABLE); const resultArray = new Uint32Array( memory.buffer, resultAddress, factCount * NUM_COLUMNS ); const results = Array.from(resultArray); for (let i = 0; i < results.length; i += NUM_COLUMNS) { const fact = results.slice(i, i + NUM_COLUMNS); console.log(fact); } instance.exports.eclair_free_buffer(resultAddress); instance.exports.eclair_program_destroy(program); }; main(); ================================================ FILE: tests/end_to_end/compile_and_run_with_extern.eclair ================================================ // RUN: split-file %s %t // RUN: %eclair compile %t/program.eclair > %t/program.ll // RUN: %clang -o %t/program -O0 %t/main.c %t/program.ll // RUN: %t/program | FileCheck %s // No output, since it never matches // CHECK: func triggered with: 456 // CHECK: func triggered with: 123 // CHECK-NEXT: COUNT: 1 // CHECK-NEXT: RESULT: 123 //--- program.eclair @def edge(u32, u32) input. @def test_externs(u32) output. @extern func(u32) u32. test_externs(func(123)). test_externs(x) :- edge(x, _), x = func(456). //--- main.c #include #include #include #include struct program; struct symbol_table; extern struct program* eclair_program_init(); extern void eclair_program_destroy(struct program*); extern void eclair_program_run(struct program*); extern void eclair_add_facts(struct program*, uint32_t fact_type, uint32_t* data, size_t fact_count); extern void eclair_add_fact(struct program*, uint32_t fact_type, uint32_t* data); extern uint32_t* eclair_get_facts(struct program*, uint32_t fact_type); extern uint32_t eclair_fact_count(struct program*, uint32_t fact_type); extern void eclair_free_buffer(uint32_t* data); uint32_t func(struct symbol_table*, uint32_t value) { printf("func triggered with: %d\n", value); return value; } int main(int argc, char** argv) { struct program* prog = eclair_program_init(); // edge(1,2), edge(2,3) uint32_t data[] = { 1, 2, 2, 3 }; eclair_add_facts(prog, 0, data, 2); eclair_program_run(prog); uint32_t fact_count = eclair_fact_count(prog, 1); uint32_t* data_out = eclair_get_facts(prog, 1); printf("COUNT: %d\n", fact_count); printf("RESULT: %d\n", data_out[0]); eclair_free_buffer(data_out); eclair_program_destroy(prog); return 0; } ================================================ FILE: tests/hello.eclair ================================================ // This is mostly a sanity check that the test-suite works // (and that LLVM can compile to LLVM correctly). // RUN: %eclair compile %s | FileCheck %s @def edge(u32, u32) input. @def reachable(u32, u32) output. reachable(x, y) :- edge(x, y). reachable(x, y) :- edge(x, z), reachable(z, y). // CHECK: eclair_program_run ================================================ FILE: tests/lit.cfg ================================================ import subprocess import lit.formats from lit.llvm.subst import ToolSubst config.name = "Eclair integration tests" config.test_format = lit.formats.ShTest(False) # 1 test per file config.suffixes = ['.eclair'] config.excludes = ['Test', 'lsp'] # Don't look in the directory for Haskell tests test_dir = os.path.dirname(__file__) config.test_source_root = test_dir config.test_exec_root = test_dir analysis_dir = test_dir + '/../cbits/' config.environment['DATALOG_DIR'] = analysis_dir config.environment['ECLAIR_USE_COLOR'] = '0' eclair = subprocess.check_output('which eclair 2> /dev/null || cabal list-bin eclair', shell=True).decode().rstrip('\n') config.substitutions.append(('%eclair', eclair)) util_dir = test_dir + '/utils/' config.substitutions.append(('%extract_snippet', util_dir + 'extract_snippet')) clang = subprocess.check_output('which clang-17 2> /dev/null || which clang', shell=True).decode().rstrip('\n') wasm_ld = subprocess.check_output('which wasm-ld-17 2> /dev/null || which wasm-ld', shell=True).decode().rstrip('\n') config.substitutions.append(('%clang', clang)) config.substitutions.append(('%wasm-ld', wasm_ld)) ================================================ FILE: tests/lowering/arithmetic.eclair ================================================ // RUN: split-file %s %t // RUN: %eclair compile --emit ra-transformed %t/program.eclair > %t/actual_ra.out // RUN: diff %t/expected_ra.out %t/actual_ra.out // RUN: %eclair compile --emit eir %t/program.eclair > %t/actual_eir.out // RUN: %extract_snippet %t/actual_eir.out "fn.*eclair_program_run" > %t/actual_eir_snippet.out // RUN: diff %t/expected_eir.out %t/actual_eir_snippet.out // RUN: %eclair compile --emit llvm %t/program.eclair > %t/actual_llvm.out // RUN: %extract_snippet %t/actual_llvm.out "define.*@eclair_program_run" > %t/actual_llvm_snippet.out // RUN: diff %t/expected_llvm.out %t/actual_llvm_snippet.out //--- program.eclair @def fact1(u32) input. @def fact2(u32) output. fact2(y) :- fact1(x), y = x + 3. fact2(x + 3) :- fact1(x). fact2((x + 1) + 2 * x) :- fact1(x). fact2(x) :- fact1(x), fact1(x + 4), fact1(x + 4). fact2((8 - x) / x) :- fact1(x). //--- expected_ra.out search fact1 as fact10 do project (((8 - fact10[0]) / fact10[0])) into fact2 search fact1 as fact10 do search fact1 as fact11 where (fact11[0] = (fact10[0] + 4)) do search fact1 as fact12 where (fact12[0] = (fact10[0] + 4)) do project (fact10[0]) into fact2 search fact1 as fact10 do project (((fact10[0] + 1) + (2 * fact10[0]))) into fact2 search fact1 as fact10 do project ((fact10[0] + 3)) into fact2 search fact1 as fact10 do project ((fact10[0] + 3)) into fact2 //--- expected_eir.out export fn eclair_program_run(*Program) -> Void { lower_bound_value = fact1.stack_allocate Value upper_bound_value = fact1.stack_allocate Value lower_bound_value.0 = 0 upper_bound_value.0 = 4294967295 begin_iter = fact1.stack_allocate Iter end_iter = fact1.stack_allocate Iter fact1.iter_lower_bound(FN_ARG[0].1, lower_bound_value, begin_iter) fact1.iter_upper_bound(FN_ARG[0].1, upper_bound_value, end_iter) loop { condition = fact1.iter_is_equal(begin_iter, end_iter) if (condition) { goto range_query.end } current = fact1.iter_current(begin_iter) value = fact2.stack_allocate Value value.0 = ((8 - current.0) / current.0) fact2.insert(FN_ARG[0].2, value) fact1.iter_next(begin_iter) } range_query.end: lower_bound_value_1 = fact1.stack_allocate Value upper_bound_value_1 = fact1.stack_allocate Value lower_bound_value_1.0 = 0 upper_bound_value_1.0 = 4294967295 begin_iter_1 = fact1.stack_allocate Iter end_iter_1 = fact1.stack_allocate Iter fact1.iter_lower_bound(FN_ARG[0].1, lower_bound_value_1, begin_iter_1) fact1.iter_upper_bound(FN_ARG[0].1, upper_bound_value_1, end_iter_1) loop { condition_1 = fact1.iter_is_equal(begin_iter_1, end_iter_1) if (condition_1) { goto range_query.end_1 } current_1 = fact1.iter_current(begin_iter_1) lower_bound_value_2 = fact1.stack_allocate Value upper_bound_value_2 = fact1.stack_allocate Value lower_bound_value_2.0 = (current_1.0 + 4) upper_bound_value_2.0 = (current_1.0 + 4) begin_iter_2 = fact1.stack_allocate Iter end_iter_2 = fact1.stack_allocate Iter fact1.iter_lower_bound(FN_ARG[0].1, lower_bound_value_2, begin_iter_2) fact1.iter_upper_bound(FN_ARG[0].1, upper_bound_value_2, end_iter_2) loop { condition_2 = fact1.iter_is_equal(begin_iter_2, end_iter_2) if (condition_2) { goto range_query.end_2 } current_2 = fact1.iter_current(begin_iter_2) lower_bound_value_3 = fact1.stack_allocate Value upper_bound_value_3 = fact1.stack_allocate Value lower_bound_value_3.0 = (current_1.0 + 4) upper_bound_value_3.0 = (current_1.0 + 4) begin_iter_3 = fact1.stack_allocate Iter end_iter_3 = fact1.stack_allocate Iter fact1.iter_lower_bound(FN_ARG[0].1, lower_bound_value_3, begin_iter_3) fact1.iter_upper_bound(FN_ARG[0].1, upper_bound_value_3, end_iter_3) loop { condition_3 = fact1.iter_is_equal(begin_iter_3, end_iter_3) if (condition_3) { goto range_query.end_3 } current_3 = fact1.iter_current(begin_iter_3) value_1 = fact2.stack_allocate Value value_1.0 = current_1.0 fact2.insert(FN_ARG[0].2, value_1) fact1.iter_next(begin_iter_3) } range_query.end_3: fact1.iter_next(begin_iter_2) } range_query.end_2: fact1.iter_next(begin_iter_1) } range_query.end_1: lower_bound_value_4 = fact1.stack_allocate Value upper_bound_value_4 = fact1.stack_allocate Value lower_bound_value_4.0 = 0 upper_bound_value_4.0 = 4294967295 begin_iter_4 = fact1.stack_allocate Iter end_iter_4 = fact1.stack_allocate Iter fact1.iter_lower_bound(FN_ARG[0].1, lower_bound_value_4, begin_iter_4) fact1.iter_upper_bound(FN_ARG[0].1, upper_bound_value_4, end_iter_4) loop { condition_4 = fact1.iter_is_equal(begin_iter_4, end_iter_4) if (condition_4) { goto range_query.end_4 } current_4 = fact1.iter_current(begin_iter_4) value_2 = fact2.stack_allocate Value value_2.0 = ((current_4.0 + 1) + (2 * current_4.0)) fact2.insert(FN_ARG[0].2, value_2) fact1.iter_next(begin_iter_4) } range_query.end_4: lower_bound_value_5 = fact1.stack_allocate Value upper_bound_value_5 = fact1.stack_allocate Value lower_bound_value_5.0 = 0 upper_bound_value_5.0 = 4294967295 begin_iter_5 = fact1.stack_allocate Iter end_iter_5 = fact1.stack_allocate Iter fact1.iter_lower_bound(FN_ARG[0].1, lower_bound_value_5, begin_iter_5) fact1.iter_upper_bound(FN_ARG[0].1, upper_bound_value_5, end_iter_5) loop { condition_5 = fact1.iter_is_equal(begin_iter_5, end_iter_5) if (condition_5) { goto range_query.end_5 } current_5 = fact1.iter_current(begin_iter_5) value_3 = fact2.stack_allocate Value value_3.0 = (current_5.0 + 3) fact2.insert(FN_ARG[0].2, value_3) fact1.iter_next(begin_iter_5) } range_query.end_5: lower_bound_value_6 = fact1.stack_allocate Value upper_bound_value_6 = fact1.stack_allocate Value lower_bound_value_6.0 = 0 upper_bound_value_6.0 = 4294967295 begin_iter_6 = fact1.stack_allocate Iter end_iter_6 = fact1.stack_allocate Iter fact1.iter_lower_bound(FN_ARG[0].1, lower_bound_value_6, begin_iter_6) fact1.iter_upper_bound(FN_ARG[0].1, upper_bound_value_6, end_iter_6) loop { condition_6 = fact1.iter_is_equal(begin_iter_6, end_iter_6) if (condition_6) { goto range_query.end_6 } current_6 = fact1.iter_current(begin_iter_6) value_4 = fact2.stack_allocate Value value_4.0 = (current_6.0 + 3) fact2.insert(FN_ARG[0].2, value_4) fact1.iter_next(begin_iter_6) } range_query.end_6: } //--- expected_llvm.out define external ccc void @eclair_program_run(ptr %arg_0) "wasm-export-name"="eclair_program_run" { start: %stack.ptr_0 = alloca [1 x i32], i32 1 %stack.ptr_1 = alloca [1 x i32], i32 1 %stack.ptr_2 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_3 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_4 = alloca [1 x i32], i32 1 %stack.ptr_5 = alloca [1 x i32], i32 1 %stack.ptr_6 = alloca [1 x i32], i32 1 %stack.ptr_7 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_8 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_9 = alloca [1 x i32], i32 1 %stack.ptr_10 = alloca [1 x i32], i32 1 %stack.ptr_11 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_12 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_13 = alloca [1 x i32], i32 1 %stack.ptr_14 = alloca [1 x i32], i32 1 %stack.ptr_15 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_16 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_17 = alloca [1 x i32], i32 1 %stack.ptr_18 = alloca [1 x i32], i32 1 %stack.ptr_19 = alloca [1 x i32], i32 1 %stack.ptr_20 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_21 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_22 = alloca [1 x i32], i32 1 %stack.ptr_23 = alloca [1 x i32], i32 1 %stack.ptr_24 = alloca [1 x i32], i32 1 %stack.ptr_25 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_26 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_27 = alloca [1 x i32], i32 1 %stack.ptr_28 = alloca [1 x i32], i32 1 %stack.ptr_29 = alloca [1 x i32], i32 1 %stack.ptr_30 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_31 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_32 = alloca [1 x i32], i32 1 %0 = getelementptr [1 x i32], ptr %stack.ptr_0, i32 0, i32 0 store i32 0, ptr %0 %1 = getelementptr [1 x i32], ptr %stack.ptr_1, i32 0, i32 0 store i32 4294967295, ptr %1 %2 = getelementptr %program, ptr %arg_0, i32 0, i32 1 call ccc void @eclair_btree_lower_bound_0(ptr %2, ptr %stack.ptr_0, ptr %stack.ptr_2) %3 = getelementptr %program, ptr %arg_0, i32 0, i32 1 call ccc void @eclair_btree_upper_bound_0(ptr %3, ptr %stack.ptr_1, ptr %stack.ptr_3) br label %loop_0 loop_0: %4 = call ccc i1 @eclair_btree_iterator_is_equal_0(ptr %stack.ptr_2, ptr %stack.ptr_3) br i1 %4, label %if_0, label %end_if_0 if_0: br label %range_query.end end_if_0: %5 = call ccc ptr @eclair_btree_iterator_current_0(ptr %stack.ptr_2) %6 = getelementptr [1 x i32], ptr %stack.ptr_4, i32 0, i32 0 %7 = getelementptr [1 x i32], ptr %5, i32 0, i32 0 %8 = load i32, ptr %7 %9 = sub i32 8, %8 %10 = getelementptr [1 x i32], ptr %5, i32 0, i32 0 %11 = load i32, ptr %10 %12 = udiv i32 %9, %11 store i32 %12, ptr %6 %13 = getelementptr %program, ptr %arg_0, i32 0, i32 2 %14 = call ccc i1 @eclair_btree_insert_value_0(ptr %13, ptr %stack.ptr_4) call ccc void @eclair_btree_iterator_next_0(ptr %stack.ptr_2) br label %loop_0 range_query.end: %15 = getelementptr [1 x i32], ptr %stack.ptr_5, i32 0, i32 0 store i32 0, ptr %15 %16 = getelementptr [1 x i32], ptr %stack.ptr_6, i32 0, i32 0 store i32 4294967295, ptr %16 %17 = getelementptr %program, ptr %arg_0, i32 0, i32 1 call ccc void @eclair_btree_lower_bound_0(ptr %17, ptr %stack.ptr_5, ptr %stack.ptr_7) %18 = getelementptr %program, ptr %arg_0, i32 0, i32 1 call ccc void @eclair_btree_upper_bound_0(ptr %18, ptr %stack.ptr_6, ptr %stack.ptr_8) br label %loop_1 loop_1: %19 = call ccc i1 @eclair_btree_iterator_is_equal_0(ptr %stack.ptr_7, ptr %stack.ptr_8) br i1 %19, label %if_1, label %end_if_1 if_1: br label %range_query.end_1 end_if_1: %20 = call ccc ptr @eclair_btree_iterator_current_0(ptr %stack.ptr_7) %21 = getelementptr [1 x i32], ptr %stack.ptr_9, i32 0, i32 0 %22 = getelementptr [1 x i32], ptr %20, i32 0, i32 0 %23 = load i32, ptr %22 %24 = add i32 %23, 4 store i32 %24, ptr %21 %25 = getelementptr [1 x i32], ptr %stack.ptr_10, i32 0, i32 0 %26 = getelementptr [1 x i32], ptr %20, i32 0, i32 0 %27 = load i32, ptr %26 %28 = add i32 %27, 4 store i32 %28, ptr %25 %29 = getelementptr %program, ptr %arg_0, i32 0, i32 1 call ccc void @eclair_btree_lower_bound_0(ptr %29, ptr %stack.ptr_9, ptr %stack.ptr_11) %30 = getelementptr %program, ptr %arg_0, i32 0, i32 1 call ccc void @eclair_btree_upper_bound_0(ptr %30, ptr %stack.ptr_10, ptr %stack.ptr_12) br label %loop_2 loop_2: %31 = call ccc i1 @eclair_btree_iterator_is_equal_0(ptr %stack.ptr_11, ptr %stack.ptr_12) br i1 %31, label %if_2, label %end_if_2 if_2: br label %range_query.end_2 end_if_2: %32 = call ccc ptr @eclair_btree_iterator_current_0(ptr %stack.ptr_11) %33 = getelementptr [1 x i32], ptr %stack.ptr_13, i32 0, i32 0 %34 = getelementptr [1 x i32], ptr %20, i32 0, i32 0 %35 = load i32, ptr %34 %36 = add i32 %35, 4 store i32 %36, ptr %33 %37 = getelementptr [1 x i32], ptr %stack.ptr_14, i32 0, i32 0 %38 = getelementptr [1 x i32], ptr %20, i32 0, i32 0 %39 = load i32, ptr %38 %40 = add i32 %39, 4 store i32 %40, ptr %37 %41 = getelementptr %program, ptr %arg_0, i32 0, i32 1 call ccc void @eclair_btree_lower_bound_0(ptr %41, ptr %stack.ptr_13, ptr %stack.ptr_15) %42 = getelementptr %program, ptr %arg_0, i32 0, i32 1 call ccc void @eclair_btree_upper_bound_0(ptr %42, ptr %stack.ptr_14, ptr %stack.ptr_16) br label %loop_3 loop_3: %43 = call ccc i1 @eclair_btree_iterator_is_equal_0(ptr %stack.ptr_15, ptr %stack.ptr_16) br i1 %43, label %if_3, label %end_if_3 if_3: br label %range_query.end_3 end_if_3: %44 = call ccc ptr @eclair_btree_iterator_current_0(ptr %stack.ptr_15) %45 = getelementptr [1 x i32], ptr %stack.ptr_17, i32 0, i32 0 %46 = getelementptr [1 x i32], ptr %20, i32 0, i32 0 %47 = load i32, ptr %46 store i32 %47, ptr %45 %48 = getelementptr %program, ptr %arg_0, i32 0, i32 2 %49 = call ccc i1 @eclair_btree_insert_value_0(ptr %48, ptr %stack.ptr_17) call ccc void @eclair_btree_iterator_next_0(ptr %stack.ptr_15) br label %loop_3 range_query.end_3: call ccc void @eclair_btree_iterator_next_0(ptr %stack.ptr_11) br label %loop_2 range_query.end_2: call ccc void @eclair_btree_iterator_next_0(ptr %stack.ptr_7) br label %loop_1 range_query.end_1: %50 = getelementptr [1 x i32], ptr %stack.ptr_18, i32 0, i32 0 store i32 0, ptr %50 %51 = getelementptr [1 x i32], ptr %stack.ptr_19, i32 0, i32 0 store i32 4294967295, ptr %51 %52 = getelementptr %program, ptr %arg_0, i32 0, i32 1 call ccc void @eclair_btree_lower_bound_0(ptr %52, ptr %stack.ptr_18, ptr %stack.ptr_20) %53 = getelementptr %program, ptr %arg_0, i32 0, i32 1 call ccc void @eclair_btree_upper_bound_0(ptr %53, ptr %stack.ptr_19, ptr %stack.ptr_21) br label %loop_4 loop_4: %54 = call ccc i1 @eclair_btree_iterator_is_equal_0(ptr %stack.ptr_20, ptr %stack.ptr_21) br i1 %54, label %if_4, label %end_if_4 if_4: br label %range_query.end_4 end_if_4: %55 = call ccc ptr @eclair_btree_iterator_current_0(ptr %stack.ptr_20) %56 = getelementptr [1 x i32], ptr %stack.ptr_22, i32 0, i32 0 %57 = getelementptr [1 x i32], ptr %55, i32 0, i32 0 %58 = load i32, ptr %57 %59 = add i32 %58, 1 %60 = getelementptr [1 x i32], ptr %55, i32 0, i32 0 %61 = load i32, ptr %60 %62 = mul i32 2, %61 %63 = add i32 %59, %62 store i32 %63, ptr %56 %64 = getelementptr %program, ptr %arg_0, i32 0, i32 2 %65 = call ccc i1 @eclair_btree_insert_value_0(ptr %64, ptr %stack.ptr_22) call ccc void @eclair_btree_iterator_next_0(ptr %stack.ptr_20) br label %loop_4 range_query.end_4: %66 = getelementptr [1 x i32], ptr %stack.ptr_23, i32 0, i32 0 store i32 0, ptr %66 %67 = getelementptr [1 x i32], ptr %stack.ptr_24, i32 0, i32 0 store i32 4294967295, ptr %67 %68 = getelementptr %program, ptr %arg_0, i32 0, i32 1 call ccc void @eclair_btree_lower_bound_0(ptr %68, ptr %stack.ptr_23, ptr %stack.ptr_25) %69 = getelementptr %program, ptr %arg_0, i32 0, i32 1 call ccc void @eclair_btree_upper_bound_0(ptr %69, ptr %stack.ptr_24, ptr %stack.ptr_26) br label %loop_5 loop_5: %70 = call ccc i1 @eclair_btree_iterator_is_equal_0(ptr %stack.ptr_25, ptr %stack.ptr_26) br i1 %70, label %if_5, label %end_if_5 if_5: br label %range_query.end_5 end_if_5: %71 = call ccc ptr @eclair_btree_iterator_current_0(ptr %stack.ptr_25) %72 = getelementptr [1 x i32], ptr %stack.ptr_27, i32 0, i32 0 %73 = getelementptr [1 x i32], ptr %71, i32 0, i32 0 %74 = load i32, ptr %73 %75 = add i32 %74, 3 store i32 %75, ptr %72 %76 = getelementptr %program, ptr %arg_0, i32 0, i32 2 %77 = call ccc i1 @eclair_btree_insert_value_0(ptr %76, ptr %stack.ptr_27) call ccc void @eclair_btree_iterator_next_0(ptr %stack.ptr_25) br label %loop_5 range_query.end_5: %78 = getelementptr [1 x i32], ptr %stack.ptr_28, i32 0, i32 0 store i32 0, ptr %78 %79 = getelementptr [1 x i32], ptr %stack.ptr_29, i32 0, i32 0 store i32 4294967295, ptr %79 %80 = getelementptr %program, ptr %arg_0, i32 0, i32 1 call ccc void @eclair_btree_lower_bound_0(ptr %80, ptr %stack.ptr_28, ptr %stack.ptr_30) %81 = getelementptr %program, ptr %arg_0, i32 0, i32 1 call ccc void @eclair_btree_upper_bound_0(ptr %81, ptr %stack.ptr_29, ptr %stack.ptr_31) br label %loop_6 loop_6: %82 = call ccc i1 @eclair_btree_iterator_is_equal_0(ptr %stack.ptr_30, ptr %stack.ptr_31) br i1 %82, label %if_6, label %end_if_6 if_6: br label %range_query.end_6 end_if_6: %83 = call ccc ptr @eclair_btree_iterator_current_0(ptr %stack.ptr_30) %84 = getelementptr [1 x i32], ptr %stack.ptr_32, i32 0, i32 0 %85 = getelementptr [1 x i32], ptr %83, i32 0, i32 0 %86 = load i32, ptr %85 %87 = add i32 %86, 3 store i32 %87, ptr %84 %88 = getelementptr %program, ptr %arg_0, i32 0, i32 2 %89 = call ccc i1 @eclair_btree_insert_value_0(ptr %88, ptr %stack.ptr_32) call ccc void @eclair_btree_iterator_next_0(ptr %stack.ptr_30) br label %loop_6 range_query.end_6: ret void } ================================================ FILE: tests/lowering/clause_with_same_vars.eclair ================================================ // RUN: split-file %s %t // RUN: %eclair compile --emit ra-transformed %t/program.eclair > %t/actual_ra.out // RUN: diff %t/expected_ra.out %t/actual_ra.out // RUN: %eclair compile --emit eir %t/program.eclair > %t/actual_eir.out // RUN: %extract_snippet %t/actual_eir.out "fn eclair_program_run" > %t/actual_eclair_program_run.out // RUN: diff %t/expected_eclair_program_run.out %t/actual_eclair_program_run.out // RUN: %eclair compile --emit llvm %t/program.eclair > %t/actual_llvm.out // RUN: %extract_snippet %t/actual_llvm.out "define.*@eclair_program_run" > %t/actual_eclair_program_run_llvm.out // RUN: diff %t/expected_eclair_program_run_llvm.out %t/actual_eclair_program_run_llvm.out //--- program.eclair @def a(u32) output. @def b(u32, u32) input. @def c(u32, u32, u32, u32, u32) input. @def other(u32) input. a(x) :- b(x, x), other(x). a(y) :- c(y, y, 42, _, y), other(y). //--- expected_ra.out search c as c0 where (c0[2] = 42) do if c0[0] = c0[1] do if c0[0] = c0[4] do search other as other1 where (c0[0] = other1[0]) do project (c0[0]) into a search b as b0 do if b0[0] = b0[1] do search other as other1 where (b0[0] = other1[0]) do project (b0[0]) into a //--- expected_eclair_program_run.out export fn eclair_program_run(*Program) -> Void { lower_bound_value = c.stack_allocate Value upper_bound_value = c.stack_allocate Value lower_bound_value.0 = 0 lower_bound_value.1 = 0 lower_bound_value.2 = 42 lower_bound_value.3 = 0 lower_bound_value.4 = 0 upper_bound_value.0 = 4294967295 upper_bound_value.1 = 4294967295 upper_bound_value.2 = 42 upper_bound_value.3 = 4294967295 upper_bound_value.4 = 4294967295 begin_iter = c.stack_allocate Iter end_iter = c.stack_allocate Iter c.iter_lower_bound(FN_ARG[0].3, lower_bound_value, begin_iter) c.iter_upper_bound(FN_ARG[0].3, upper_bound_value, end_iter) loop { condition = c.iter_is_equal(begin_iter, end_iter) if (condition) { goto range_query.end } current = c.iter_current(begin_iter) condition_1 = (current.0 == current.1) if (condition_1) { condition_2 = (current.0 == current.4) if (condition_2) { lower_bound_value_1 = other.stack_allocate Value upper_bound_value_1 = other.stack_allocate Value lower_bound_value_1.0 = current.0 upper_bound_value_1.0 = current.0 begin_iter_1 = other.stack_allocate Iter end_iter_1 = other.stack_allocate Iter other.iter_lower_bound(FN_ARG[0].4, lower_bound_value_1, begin_iter_1) other.iter_upper_bound(FN_ARG[0].4, upper_bound_value_1, end_iter_1) loop { condition_3 = other.iter_is_equal(begin_iter_1, end_iter_1) if (condition_3) { goto range_query.end_1 } current_1 = other.iter_current(begin_iter_1) value = a.stack_allocate Value value.0 = current.0 a.insert(FN_ARG[0].1, value) other.iter_next(begin_iter_1) } range_query.end_1: } } c.iter_next(begin_iter) } range_query.end: lower_bound_value_2 = b.stack_allocate Value upper_bound_value_2 = b.stack_allocate Value lower_bound_value_2.0 = 0 lower_bound_value_2.1 = 0 upper_bound_value_2.0 = 4294967295 upper_bound_value_2.1 = 4294967295 begin_iter_2 = b.stack_allocate Iter end_iter_2 = b.stack_allocate Iter b.iter_lower_bound(FN_ARG[0].2, lower_bound_value_2, begin_iter_2) b.iter_upper_bound(FN_ARG[0].2, upper_bound_value_2, end_iter_2) loop { condition_4 = b.iter_is_equal(begin_iter_2, end_iter_2) if (condition_4) { goto range_query.end_2 } current_2 = b.iter_current(begin_iter_2) condition_5 = (current_2.0 == current_2.1) if (condition_5) { lower_bound_value_3 = other.stack_allocate Value upper_bound_value_3 = other.stack_allocate Value lower_bound_value_3.0 = current_2.0 upper_bound_value_3.0 = current_2.0 begin_iter_3 = other.stack_allocate Iter end_iter_3 = other.stack_allocate Iter other.iter_lower_bound(FN_ARG[0].4, lower_bound_value_3, begin_iter_3) other.iter_upper_bound(FN_ARG[0].4, upper_bound_value_3, end_iter_3) loop { condition_6 = other.iter_is_equal(begin_iter_3, end_iter_3) if (condition_6) { goto range_query.end_3 } current_3 = other.iter_current(begin_iter_3) value_1 = a.stack_allocate Value value_1.0 = current_2.0 a.insert(FN_ARG[0].1, value_1) other.iter_next(begin_iter_3) } range_query.end_3: } b.iter_next(begin_iter_2) } range_query.end_2: } //--- expected_eclair_program_run_llvm.out define external ccc void @eclair_program_run(ptr %arg_0) "wasm-export-name"="eclair_program_run" { start: %stack.ptr_0 = alloca [5 x i32], i32 1 %stack.ptr_1 = alloca [5 x i32], i32 1 %stack.ptr_2 = alloca %btree_iterator_t_2, i32 1 %stack.ptr_3 = alloca %btree_iterator_t_2, i32 1 %stack.ptr_4 = alloca [1 x i32], i32 1 %stack.ptr_5 = alloca [1 x i32], i32 1 %stack.ptr_6 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_7 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_8 = alloca [1 x i32], i32 1 %stack.ptr_9 = alloca [2 x i32], i32 1 %stack.ptr_10 = alloca [2 x i32], i32 1 %stack.ptr_11 = alloca %btree_iterator_t_1, i32 1 %stack.ptr_12 = alloca %btree_iterator_t_1, i32 1 %stack.ptr_13 = alloca [1 x i32], i32 1 %stack.ptr_14 = alloca [1 x i32], i32 1 %stack.ptr_15 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_16 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_17 = alloca [1 x i32], i32 1 %0 = getelementptr [5 x i32], ptr %stack.ptr_0, i32 0, i32 0 store i32 0, ptr %0 %1 = getelementptr [5 x i32], ptr %stack.ptr_0, i32 0, i32 1 store i32 0, ptr %1 %2 = getelementptr [5 x i32], ptr %stack.ptr_0, i32 0, i32 2 store i32 42, ptr %2 %3 = getelementptr [5 x i32], ptr %stack.ptr_0, i32 0, i32 3 store i32 0, ptr %3 %4 = getelementptr [5 x i32], ptr %stack.ptr_0, i32 0, i32 4 store i32 0, ptr %4 %5 = getelementptr [5 x i32], ptr %stack.ptr_1, i32 0, i32 0 store i32 4294967295, ptr %5 %6 = getelementptr [5 x i32], ptr %stack.ptr_1, i32 0, i32 1 store i32 4294967295, ptr %6 %7 = getelementptr [5 x i32], ptr %stack.ptr_1, i32 0, i32 2 store i32 42, ptr %7 %8 = getelementptr [5 x i32], ptr %stack.ptr_1, i32 0, i32 3 store i32 4294967295, ptr %8 %9 = getelementptr [5 x i32], ptr %stack.ptr_1, i32 0, i32 4 store i32 4294967295, ptr %9 %10 = getelementptr %program, ptr %arg_0, i32 0, i32 3 call ccc void @eclair_btree_lower_bound_2(ptr %10, ptr %stack.ptr_0, ptr %stack.ptr_2) %11 = getelementptr %program, ptr %arg_0, i32 0, i32 3 call ccc void @eclair_btree_upper_bound_2(ptr %11, ptr %stack.ptr_1, ptr %stack.ptr_3) br label %loop_0 loop_0: %12 = call ccc i1 @eclair_btree_iterator_is_equal_2(ptr %stack.ptr_2, ptr %stack.ptr_3) br i1 %12, label %if_0, label %end_if_0 if_0: br label %range_query.end end_if_0: %13 = call ccc ptr @eclair_btree_iterator_current_2(ptr %stack.ptr_2) %14 = getelementptr [5 x i32], ptr %13, i32 0, i32 0 %15 = load i32, ptr %14 %16 = getelementptr [5 x i32], ptr %13, i32 0, i32 1 %17 = load i32, ptr %16 %18 = icmp eq i32 %15, %17 br i1 %18, label %if_1, label %end_if_3 if_1: %19 = getelementptr [5 x i32], ptr %13, i32 0, i32 0 %20 = load i32, ptr %19 %21 = getelementptr [5 x i32], ptr %13, i32 0, i32 4 %22 = load i32, ptr %21 %23 = icmp eq i32 %20, %22 br i1 %23, label %if_2, label %end_if_2 if_2: %24 = getelementptr [1 x i32], ptr %stack.ptr_4, i32 0, i32 0 %25 = getelementptr [5 x i32], ptr %13, i32 0, i32 0 %26 = load i32, ptr %25 store i32 %26, ptr %24 %27 = getelementptr [1 x i32], ptr %stack.ptr_5, i32 0, i32 0 %28 = getelementptr [5 x i32], ptr %13, i32 0, i32 0 %29 = load i32, ptr %28 store i32 %29, ptr %27 %30 = getelementptr %program, ptr %arg_0, i32 0, i32 4 call ccc void @eclair_btree_lower_bound_0(ptr %30, ptr %stack.ptr_4, ptr %stack.ptr_6) %31 = getelementptr %program, ptr %arg_0, i32 0, i32 4 call ccc void @eclair_btree_upper_bound_0(ptr %31, ptr %stack.ptr_5, ptr %stack.ptr_7) br label %loop_1 loop_1: %32 = call ccc i1 @eclair_btree_iterator_is_equal_0(ptr %stack.ptr_6, ptr %stack.ptr_7) br i1 %32, label %if_3, label %end_if_1 if_3: br label %range_query.end_1 end_if_1: %33 = call ccc ptr @eclair_btree_iterator_current_0(ptr %stack.ptr_6) %34 = getelementptr [1 x i32], ptr %stack.ptr_8, i32 0, i32 0 %35 = getelementptr [5 x i32], ptr %13, i32 0, i32 0 %36 = load i32, ptr %35 store i32 %36, ptr %34 %37 = getelementptr %program, ptr %arg_0, i32 0, i32 1 %38 = call ccc i1 @eclair_btree_insert_value_0(ptr %37, ptr %stack.ptr_8) call ccc void @eclair_btree_iterator_next_0(ptr %stack.ptr_6) br label %loop_1 range_query.end_1: br label %end_if_2 end_if_2: br label %end_if_3 end_if_3: call ccc void @eclair_btree_iterator_next_2(ptr %stack.ptr_2) br label %loop_0 range_query.end: %39 = getelementptr [2 x i32], ptr %stack.ptr_9, i32 0, i32 0 store i32 0, ptr %39 %40 = getelementptr [2 x i32], ptr %stack.ptr_9, i32 0, i32 1 store i32 0, ptr %40 %41 = getelementptr [2 x i32], ptr %stack.ptr_10, i32 0, i32 0 store i32 4294967295, ptr %41 %42 = getelementptr [2 x i32], ptr %stack.ptr_10, i32 0, i32 1 store i32 4294967295, ptr %42 %43 = getelementptr %program, ptr %arg_0, i32 0, i32 2 call ccc void @eclair_btree_lower_bound_1(ptr %43, ptr %stack.ptr_9, ptr %stack.ptr_11) %44 = getelementptr %program, ptr %arg_0, i32 0, i32 2 call ccc void @eclair_btree_upper_bound_1(ptr %44, ptr %stack.ptr_10, ptr %stack.ptr_12) br label %loop_2 loop_2: %45 = call ccc i1 @eclair_btree_iterator_is_equal_1(ptr %stack.ptr_11, ptr %stack.ptr_12) br i1 %45, label %if_4, label %end_if_4 if_4: br label %range_query.end_2 end_if_4: %46 = call ccc ptr @eclair_btree_iterator_current_1(ptr %stack.ptr_11) %47 = getelementptr [2 x i32], ptr %46, i32 0, i32 0 %48 = load i32, ptr %47 %49 = getelementptr [2 x i32], ptr %46, i32 0, i32 1 %50 = load i32, ptr %49 %51 = icmp eq i32 %48, %50 br i1 %51, label %if_5, label %end_if_6 if_5: %52 = getelementptr [1 x i32], ptr %stack.ptr_13, i32 0, i32 0 %53 = getelementptr [2 x i32], ptr %46, i32 0, i32 0 %54 = load i32, ptr %53 store i32 %54, ptr %52 %55 = getelementptr [1 x i32], ptr %stack.ptr_14, i32 0, i32 0 %56 = getelementptr [2 x i32], ptr %46, i32 0, i32 0 %57 = load i32, ptr %56 store i32 %57, ptr %55 %58 = getelementptr %program, ptr %arg_0, i32 0, i32 4 call ccc void @eclair_btree_lower_bound_0(ptr %58, ptr %stack.ptr_13, ptr %stack.ptr_15) %59 = getelementptr %program, ptr %arg_0, i32 0, i32 4 call ccc void @eclair_btree_upper_bound_0(ptr %59, ptr %stack.ptr_14, ptr %stack.ptr_16) br label %loop_3 loop_3: %60 = call ccc i1 @eclair_btree_iterator_is_equal_0(ptr %stack.ptr_15, ptr %stack.ptr_16) br i1 %60, label %if_6, label %end_if_5 if_6: br label %range_query.end_3 end_if_5: %61 = call ccc ptr @eclair_btree_iterator_current_0(ptr %stack.ptr_15) %62 = getelementptr [1 x i32], ptr %stack.ptr_17, i32 0, i32 0 %63 = getelementptr [2 x i32], ptr %46, i32 0, i32 0 %64 = load i32, ptr %63 store i32 %64, ptr %62 %65 = getelementptr %program, ptr %arg_0, i32 0, i32 1 %66 = call ccc i1 @eclair_btree_insert_value_0(ptr %65, ptr %stack.ptr_17) call ccc void @eclair_btree_iterator_next_0(ptr %stack.ptr_15) br label %loop_3 range_query.end_3: br label %end_if_6 end_if_6: call ccc void @eclair_btree_iterator_next_1(ptr %stack.ptr_11) br label %loop_2 range_query.end_2: ret void } ================================================ FILE: tests/lowering/comparisons.eclair ================================================ // RUN: split-file %s %t // RUN: %eclair compile --emit ra-transformed %t/program.eclair > %t/actual_ra.out // RUN: diff %t/expected_ra.out %t/actual_ra.out // RUN: %eclair compile --emit eir %t/program.eclair > %t/actual_eir.out // RUN: diff %t/expected_eir.out %t/actual_eir.out // RUN: %eclair compile --emit llvm %t/program.eclair > %t/actual_llvm.out // RUN: %extract_snippet %t/actual_llvm.out "define.*@eclair_program_run" > %t/actual_llvm_snippet.out // RUN: diff %t/expected_llvm.out %t/actual_llvm_snippet.out //--- program.eclair @def fact1(u32, u32) input. @def fact2(u32, u32) output. fact2(x, 1) :- z = x, fact1(x, z), y = 123, fact1(y, x). fact2(x, y) :- 123 = x, fact1(y, x). fact2(x, y) :- 123 < x, 123 <= x, 123 > x, 123 >= x, 123 != x, fact1(y, x). //--- expected_ra.out search fact1 as fact10 do if 123 < fact10[1] do if 123 <= fact10[1] do if 123 > fact10[1] do if 123 >= fact10[1] do if 123 != fact10[1] do project (fact10[1], fact10[0]) into fact2 search fact1 as fact10 where (123 = fact10[1]) do project (fact10[1], fact10[0]) into fact2 search fact1 as fact10 do if fact10[1] = fact10[0] do search fact1 as fact11 where (fact11[0] = 123 and fact10[0] = fact11[1]) do project (fact10[0], 1) into fact2 //--- expected_eir.out declare_type Program { symbol_table fact1 btree(num_columns=2, index=[1,0], block_size=256, search_type=linear) fact2 btree(num_columns=2, index=[0,1], block_size=256, search_type=linear) } export fn eclair_program_init() -> *Program { program = heap_allocate_program symbol_table.init(program.0) fact1.init_empty(program.1) fact2.init_empty(program.2) symbol_table.insert(program.0, "fact1") symbol_table.insert(program.0, "fact2") return program } export fn eclair_program_destroy(*Program) -> Void { symbol_table.destroy(FN_ARG[0].0) fact1.destroy(FN_ARG[0].1) fact2.destroy(FN_ARG[0].2) free_program(FN_ARG[0]) } export fn eclair_program_run(*Program) -> Void { lower_bound_value = fact1.stack_allocate Value upper_bound_value = fact1.stack_allocate Value lower_bound_value.0 = 0 lower_bound_value.1 = 0 upper_bound_value.0 = 4294967295 upper_bound_value.1 = 4294967295 begin_iter = fact1.stack_allocate Iter end_iter = fact1.stack_allocate Iter fact1.iter_lower_bound(FN_ARG[0].1, lower_bound_value, begin_iter) fact1.iter_upper_bound(FN_ARG[0].1, upper_bound_value, end_iter) loop { condition = fact1.iter_is_equal(begin_iter, end_iter) if (condition) { goto range_query.end } current = fact1.iter_current(begin_iter) condition_1 = (123 < current.1) if (condition_1) { condition_2 = (123 <= current.1) if (condition_2) { condition_3 = (123 > current.1) if (condition_3) { condition_4 = (123 >= current.1) if (condition_4) { condition_5 = (123 != current.1) if (condition_5) { value = fact2.stack_allocate Value value.0 = current.1 value.1 = current.0 fact2.insert(FN_ARG[0].2, value) } } } } } fact1.iter_next(begin_iter) } range_query.end: lower_bound_value_1 = fact1.stack_allocate Value upper_bound_value_1 = fact1.stack_allocate Value lower_bound_value_1.0 = 0 lower_bound_value_1.1 = 123 upper_bound_value_1.0 = 4294967295 upper_bound_value_1.1 = 123 begin_iter_1 = fact1.stack_allocate Iter end_iter_1 = fact1.stack_allocate Iter fact1.iter_lower_bound(FN_ARG[0].1, lower_bound_value_1, begin_iter_1) fact1.iter_upper_bound(FN_ARG[0].1, upper_bound_value_1, end_iter_1) loop { condition_6 = fact1.iter_is_equal(begin_iter_1, end_iter_1) if (condition_6) { goto range_query.end_1 } current_1 = fact1.iter_current(begin_iter_1) value_1 = fact2.stack_allocate Value value_1.0 = current_1.1 value_1.1 = current_1.0 fact2.insert(FN_ARG[0].2, value_1) fact1.iter_next(begin_iter_1) } range_query.end_1: lower_bound_value_2 = fact1.stack_allocate Value upper_bound_value_2 = fact1.stack_allocate Value lower_bound_value_2.0 = 0 lower_bound_value_2.1 = 0 upper_bound_value_2.0 = 4294967295 upper_bound_value_2.1 = 4294967295 begin_iter_2 = fact1.stack_allocate Iter end_iter_2 = fact1.stack_allocate Iter fact1.iter_lower_bound(FN_ARG[0].1, lower_bound_value_2, begin_iter_2) fact1.iter_upper_bound(FN_ARG[0].1, upper_bound_value_2, end_iter_2) loop { condition_7 = fact1.iter_is_equal(begin_iter_2, end_iter_2) if (condition_7) { goto range_query.end_2 } current_2 = fact1.iter_current(begin_iter_2) condition_8 = (current_2.1 == current_2.0) if (condition_8) { lower_bound_value_3 = fact1.stack_allocate Value upper_bound_value_3 = fact1.stack_allocate Value lower_bound_value_3.0 = 123 lower_bound_value_3.1 = current_2.0 upper_bound_value_3.0 = 123 upper_bound_value_3.1 = current_2.0 begin_iter_3 = fact1.stack_allocate Iter end_iter_3 = fact1.stack_allocate Iter fact1.iter_lower_bound(FN_ARG[0].1, lower_bound_value_3, begin_iter_3) fact1.iter_upper_bound(FN_ARG[0].1, upper_bound_value_3, end_iter_3) loop { condition_9 = fact1.iter_is_equal(begin_iter_3, end_iter_3) if (condition_9) { goto range_query.end_3 } current_3 = fact1.iter_current(begin_iter_3) value_2 = fact2.stack_allocate Value value_2.0 = current_2.0 value_2.1 = 1 fact2.insert(FN_ARG[0].2, value_2) fact1.iter_next(begin_iter_3) } range_query.end_3: } fact1.iter_next(begin_iter_2) } range_query.end_2: } //--- expected_llvm.out define external ccc void @eclair_program_run(ptr %arg_0) "wasm-export-name"="eclair_program_run" { start: %stack.ptr_0 = alloca [2 x i32], i32 1 %stack.ptr_1 = alloca [2 x i32], i32 1 %stack.ptr_2 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_3 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_4 = alloca [2 x i32], i32 1 %stack.ptr_5 = alloca [2 x i32], i32 1 %stack.ptr_6 = alloca [2 x i32], i32 1 %stack.ptr_7 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_8 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_9 = alloca [2 x i32], i32 1 %stack.ptr_10 = alloca [2 x i32], i32 1 %stack.ptr_11 = alloca [2 x i32], i32 1 %stack.ptr_12 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_13 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_14 = alloca [2 x i32], i32 1 %stack.ptr_15 = alloca [2 x i32], i32 1 %stack.ptr_16 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_17 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_18 = alloca [2 x i32], i32 1 %0 = getelementptr [2 x i32], ptr %stack.ptr_0, i32 0, i32 0 store i32 0, ptr %0 %1 = getelementptr [2 x i32], ptr %stack.ptr_0, i32 0, i32 1 store i32 0, ptr %1 %2 = getelementptr [2 x i32], ptr %stack.ptr_1, i32 0, i32 0 store i32 4294967295, ptr %2 %3 = getelementptr [2 x i32], ptr %stack.ptr_1, i32 0, i32 1 store i32 4294967295, ptr %3 %4 = getelementptr %program, ptr %arg_0, i32 0, i32 1 call ccc void @eclair_btree_lower_bound_0(ptr %4, ptr %stack.ptr_0, ptr %stack.ptr_2) %5 = getelementptr %program, ptr %arg_0, i32 0, i32 1 call ccc void @eclair_btree_upper_bound_0(ptr %5, ptr %stack.ptr_1, ptr %stack.ptr_3) br label %loop_0 loop_0: %6 = call ccc i1 @eclair_btree_iterator_is_equal_0(ptr %stack.ptr_2, ptr %stack.ptr_3) br i1 %6, label %if_0, label %end_if_0 if_0: br label %range_query.end end_if_0: %7 = call ccc ptr @eclair_btree_iterator_current_0(ptr %stack.ptr_2) %8 = getelementptr [2 x i32], ptr %7, i32 0, i32 1 %9 = load i32, ptr %8 %10 = icmp ult i32 123, %9 br i1 %10, label %if_1, label %end_if_5 if_1: %11 = getelementptr [2 x i32], ptr %7, i32 0, i32 1 %12 = load i32, ptr %11 %13 = icmp ule i32 123, %12 br i1 %13, label %if_2, label %end_if_4 if_2: %14 = getelementptr [2 x i32], ptr %7, i32 0, i32 1 %15 = load i32, ptr %14 %16 = icmp ugt i32 123, %15 br i1 %16, label %if_3, label %end_if_3 if_3: %17 = getelementptr [2 x i32], ptr %7, i32 0, i32 1 %18 = load i32, ptr %17 %19 = icmp uge i32 123, %18 br i1 %19, label %if_4, label %end_if_2 if_4: %20 = getelementptr [2 x i32], ptr %7, i32 0, i32 1 %21 = load i32, ptr %20 %22 = icmp ne i32 123, %21 br i1 %22, label %if_5, label %end_if_1 if_5: %23 = getelementptr [2 x i32], ptr %stack.ptr_4, i32 0, i32 0 %24 = getelementptr [2 x i32], ptr %7, i32 0, i32 1 %25 = load i32, ptr %24 store i32 %25, ptr %23 %26 = getelementptr [2 x i32], ptr %stack.ptr_4, i32 0, i32 1 %27 = getelementptr [2 x i32], ptr %7, i32 0, i32 0 %28 = load i32, ptr %27 store i32 %28, ptr %26 %29 = getelementptr %program, ptr %arg_0, i32 0, i32 2 %30 = call ccc i1 @eclair_btree_insert_value_1(ptr %29, ptr %stack.ptr_4) br label %end_if_1 end_if_1: br label %end_if_2 end_if_2: br label %end_if_3 end_if_3: br label %end_if_4 end_if_4: br label %end_if_5 end_if_5: call ccc void @eclair_btree_iterator_next_0(ptr %stack.ptr_2) br label %loop_0 range_query.end: %31 = getelementptr [2 x i32], ptr %stack.ptr_5, i32 0, i32 0 store i32 0, ptr %31 %32 = getelementptr [2 x i32], ptr %stack.ptr_5, i32 0, i32 1 store i32 123, ptr %32 %33 = getelementptr [2 x i32], ptr %stack.ptr_6, i32 0, i32 0 store i32 4294967295, ptr %33 %34 = getelementptr [2 x i32], ptr %stack.ptr_6, i32 0, i32 1 store i32 123, ptr %34 %35 = getelementptr %program, ptr %arg_0, i32 0, i32 1 call ccc void @eclair_btree_lower_bound_0(ptr %35, ptr %stack.ptr_5, ptr %stack.ptr_7) %36 = getelementptr %program, ptr %arg_0, i32 0, i32 1 call ccc void @eclair_btree_upper_bound_0(ptr %36, ptr %stack.ptr_6, ptr %stack.ptr_8) br label %loop_1 loop_1: %37 = call ccc i1 @eclair_btree_iterator_is_equal_0(ptr %stack.ptr_7, ptr %stack.ptr_8) br i1 %37, label %if_6, label %end_if_6 if_6: br label %range_query.end_1 end_if_6: %38 = call ccc ptr @eclair_btree_iterator_current_0(ptr %stack.ptr_7) %39 = getelementptr [2 x i32], ptr %stack.ptr_9, i32 0, i32 0 %40 = getelementptr [2 x i32], ptr %38, i32 0, i32 1 %41 = load i32, ptr %40 store i32 %41, ptr %39 %42 = getelementptr [2 x i32], ptr %stack.ptr_9, i32 0, i32 1 %43 = getelementptr [2 x i32], ptr %38, i32 0, i32 0 %44 = load i32, ptr %43 store i32 %44, ptr %42 %45 = getelementptr %program, ptr %arg_0, i32 0, i32 2 %46 = call ccc i1 @eclair_btree_insert_value_1(ptr %45, ptr %stack.ptr_9) call ccc void @eclair_btree_iterator_next_0(ptr %stack.ptr_7) br label %loop_1 range_query.end_1: %47 = getelementptr [2 x i32], ptr %stack.ptr_10, i32 0, i32 0 store i32 0, ptr %47 %48 = getelementptr [2 x i32], ptr %stack.ptr_10, i32 0, i32 1 store i32 0, ptr %48 %49 = getelementptr [2 x i32], ptr %stack.ptr_11, i32 0, i32 0 store i32 4294967295, ptr %49 %50 = getelementptr [2 x i32], ptr %stack.ptr_11, i32 0, i32 1 store i32 4294967295, ptr %50 %51 = getelementptr %program, ptr %arg_0, i32 0, i32 1 call ccc void @eclair_btree_lower_bound_0(ptr %51, ptr %stack.ptr_10, ptr %stack.ptr_12) %52 = getelementptr %program, ptr %arg_0, i32 0, i32 1 call ccc void @eclair_btree_upper_bound_0(ptr %52, ptr %stack.ptr_11, ptr %stack.ptr_13) br label %loop_2 loop_2: %53 = call ccc i1 @eclair_btree_iterator_is_equal_0(ptr %stack.ptr_12, ptr %stack.ptr_13) br i1 %53, label %if_7, label %end_if_7 if_7: br label %range_query.end_2 end_if_7: %54 = call ccc ptr @eclair_btree_iterator_current_0(ptr %stack.ptr_12) %55 = getelementptr [2 x i32], ptr %54, i32 0, i32 1 %56 = load i32, ptr %55 %57 = getelementptr [2 x i32], ptr %54, i32 0, i32 0 %58 = load i32, ptr %57 %59 = icmp eq i32 %56, %58 br i1 %59, label %if_8, label %end_if_9 if_8: %60 = getelementptr [2 x i32], ptr %stack.ptr_14, i32 0, i32 0 store i32 123, ptr %60 %61 = getelementptr [2 x i32], ptr %stack.ptr_14, i32 0, i32 1 %62 = getelementptr [2 x i32], ptr %54, i32 0, i32 0 %63 = load i32, ptr %62 store i32 %63, ptr %61 %64 = getelementptr [2 x i32], ptr %stack.ptr_15, i32 0, i32 0 store i32 123, ptr %64 %65 = getelementptr [2 x i32], ptr %stack.ptr_15, i32 0, i32 1 %66 = getelementptr [2 x i32], ptr %54, i32 0, i32 0 %67 = load i32, ptr %66 store i32 %67, ptr %65 %68 = getelementptr %program, ptr %arg_0, i32 0, i32 1 call ccc void @eclair_btree_lower_bound_0(ptr %68, ptr %stack.ptr_14, ptr %stack.ptr_16) %69 = getelementptr %program, ptr %arg_0, i32 0, i32 1 call ccc void @eclair_btree_upper_bound_0(ptr %69, ptr %stack.ptr_15, ptr %stack.ptr_17) br label %loop_3 loop_3: %70 = call ccc i1 @eclair_btree_iterator_is_equal_0(ptr %stack.ptr_16, ptr %stack.ptr_17) br i1 %70, label %if_9, label %end_if_8 if_9: br label %range_query.end_3 end_if_8: %71 = call ccc ptr @eclair_btree_iterator_current_0(ptr %stack.ptr_16) %72 = getelementptr [2 x i32], ptr %stack.ptr_18, i32 0, i32 0 %73 = getelementptr [2 x i32], ptr %54, i32 0, i32 0 %74 = load i32, ptr %73 store i32 %74, ptr %72 %75 = getelementptr [2 x i32], ptr %stack.ptr_18, i32 0, i32 1 store i32 1, ptr %75 %76 = getelementptr %program, ptr %arg_0, i32 0, i32 2 %77 = call ccc i1 @eclair_btree_insert_value_1(ptr %76, ptr %stack.ptr_18) call ccc void @eclair_btree_iterator_next_0(ptr %stack.ptr_16) br label %loop_3 range_query.end_3: br label %end_if_9 end_if_9: call ccc void @eclair_btree_iterator_next_0(ptr %stack.ptr_12) br label %loop_2 range_query.end_2: ret void } ================================================ FILE: tests/lowering/different_types.eclair ================================================ // TODO add tests for caching mechanism (e.g. single_nonrecursive_rule test) // RUN: split-file %s %t // RUN: %eclair compile --emit llvm %t/program.eclair > %t/actual_llvm.out // RUN: %extract_snippet %t/actual_llvm.out "define.*@eclair_add_facts" > %t/actual_eclair_add_facts_llvm.out // RUN: %extract_snippet %t/actual_llvm.out "define.*@eclair_get_facts" > %t/actual_eclair_get_facts_llvm.out // RUN: diff %t/expected_eclair_add_facts_llvm.out %t/actual_eclair_add_facts_llvm.out // RUN: diff %t/expected_eclair_get_facts_llvm.out %t/actual_eclair_get_facts_llvm.out //--- program.eclair @def a(u32) input output. @def b(u32, u32, u32) input output. //--- expected_eclair_add_facts_llvm.out define external ccc void @eclair_add_facts(ptr %eclair_program_0, i32 %fact_type_0, ptr %memory_0, i32 %fact_count_0) "wasm-export-name"="eclair_add_facts" { start: switch i32 %fact_type_0, label %switch.default_0 [i32 0, label %a_0 i32 1, label %b_0] a_0: %0 = getelementptr %program, ptr %eclair_program_0, i32 0, i32 1 br label %for_begin_0 for_begin_0: %1 = phi i32 [0, %a_0], [%5, %for_body_0] %2 = icmp ult i32 %1, %fact_count_0 br i1 %2, label %for_body_0, label %for_end_0 for_body_0: %3 = getelementptr [1 x i32], ptr %memory_0, i32 %1 %4 = call ccc i1 @eclair_btree_insert_value_0(ptr %0, ptr %3) %5 = add i32 1, %1 br label %for_begin_0 for_end_0: br label %end_0 b_0: %6 = getelementptr %program, ptr %eclair_program_0, i32 0, i32 2 br label %for_begin_1 for_begin_1: %7 = phi i32 [0, %b_0], [%11, %for_body_1] %8 = icmp ult i32 %7, %fact_count_0 br i1 %8, label %for_body_1, label %for_end_1 for_body_1: %9 = getelementptr [3 x i32], ptr %memory_0, i32 %7 %10 = call ccc i1 @eclair_btree_insert_value_1(ptr %6, ptr %9) %11 = add i32 1, %7 br label %for_begin_1 for_end_1: br label %end_0 switch.default_0: ret void end_0: ret void } //--- expected_eclair_get_facts_llvm.out define external ccc ptr @eclair_get_facts(ptr %eclair_program_0, i32 %fact_type_0) "wasm-export-name"="eclair_get_facts" { start: %stack.ptr_0 = alloca i32, i32 1 %stack.ptr_1 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_2 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_3 = alloca i32, i32 1 %stack.ptr_4 = alloca %btree_iterator_t_1, i32 1 %stack.ptr_5 = alloca %btree_iterator_t_1, i32 1 switch i32 %fact_type_0, label %switch.default_0 [i32 0, label %a_0 i32 1, label %b_0] a_0: %0 = getelementptr %program, ptr %eclair_program_0, i32 0, i32 1 %1 = call ccc i64 @eclair_btree_size_0(ptr %0) %2 = trunc i64 %1 to i32 %3 = mul i32 %2, 4 %4 = call ccc ptr @malloc(i32 %3) store i32 0, ptr %stack.ptr_0 call ccc void @eclair_btree_begin_0(ptr %0, ptr %stack.ptr_1) call ccc void @eclair_btree_end_0(ptr %0, ptr %stack.ptr_2) br label %while_begin_0 while_begin_0: %5 = call ccc i1 @eclair_btree_iterator_is_equal_0(ptr %stack.ptr_1, ptr %stack.ptr_2) %6 = select i1 %5, i1 0, i1 1 br i1 %6, label %while_body_0, label %while_end_0 while_body_0: %7 = load i32, ptr %stack.ptr_0 %8 = getelementptr [1 x i32], ptr %4, i32 %7 %9 = call ccc ptr @eclair_btree_iterator_current_0(ptr %stack.ptr_1) %10 = getelementptr [1 x i32], ptr %9, i32 0 %11 = load [1 x i32], ptr %10 %12 = getelementptr [1 x i32], ptr %8, i32 0 store [1 x i32] %11, ptr %12 %13 = add i32 %7, 1 store i32 %13, ptr %stack.ptr_0 call ccc void @eclair_btree_iterator_next_0(ptr %stack.ptr_1) br label %while_begin_0 while_end_0: ret ptr %4 b_0: %14 = getelementptr %program, ptr %eclair_program_0, i32 0, i32 2 %15 = call ccc i64 @eclair_btree_size_1(ptr %14) %16 = trunc i64 %15 to i32 %17 = mul i32 %16, 12 %18 = call ccc ptr @malloc(i32 %17) store i32 0, ptr %stack.ptr_3 call ccc void @eclair_btree_begin_1(ptr %14, ptr %stack.ptr_4) call ccc void @eclair_btree_end_1(ptr %14, ptr %stack.ptr_5) br label %while_begin_1 while_begin_1: %19 = call ccc i1 @eclair_btree_iterator_is_equal_1(ptr %stack.ptr_4, ptr %stack.ptr_5) %20 = select i1 %19, i1 0, i1 1 br i1 %20, label %while_body_1, label %while_end_1 while_body_1: %21 = load i32, ptr %stack.ptr_3 %22 = getelementptr [3 x i32], ptr %18, i32 %21 %23 = call ccc ptr @eclair_btree_iterator_current_1(ptr %stack.ptr_4) %24 = getelementptr [3 x i32], ptr %23, i32 0 %25 = load [3 x i32], ptr %24 %26 = getelementptr [3 x i32], ptr %22, i32 0 store [3 x i32] %25, ptr %26 %27 = add i32 %21, 1 store i32 %27, ptr %stack.ptr_3 call ccc void @eclair_btree_iterator_next_1(ptr %stack.ptr_4) br label %while_begin_1 while_end_1: ret ptr %18 switch.default_0: ret ptr zeroinitializer } ================================================ FILE: tests/lowering/extern_definitions.eclair ================================================ // RUN: split-file %s %t // RUN: %eclair compile --emit ra-transformed %t/program.eclair > %t/actual_ra.out // RUN: diff %t/expected_ra.out %t/actual_ra.out // RUN: %eclair compile --emit eir %t/program.eclair > %t/actual_eir.out // RUN: %extract_snippet %t/actual_eir.out "fn.*eclair_program_run" > %t/actual_eir_snippet.out // RUN: diff %t/expected_eir.out %t/actual_eir_snippet.out // RUN: %eclair compile --emit llvm %t/program.eclair > %t/actual_llvm.out // RUN: %extract_snippet %t/actual_llvm.out "define.*@eclair_program_run" > %t/actual_llvm_snippet.out // RUN: diff %t/expected_llvm.out %t/actual_llvm_snippet.out //--- program.eclair @def edge(u32, u32) input. @def test_externs(u32) output. @extern constraint(string). @extern func(u32) u32. @extern func2(u32, u32) string. test_externs(func(123)). test_externs(x) :- edge(x, _), constraint("abc"), constraint(func2(123, 456)). test_externs(x) :- edge(x, y), x = func(123), // This can be indexed on x = func(y), // This can't, x and y defined in same relation "abc" = func2(456, 789). //--- expected_ra.out if 2 = func2(456, 789) do search edge as edge0 where (edge0[0] = func(123)) do if edge0[0] = func(edge0[1]) do project (edge0[0]) into test_externs if constraint(2) != 0 do if constraint(func2(123, 456)) != 0 do search edge as edge0 do project (edge0[0]) into test_externs project (func(123)) into test_externs //--- expected_eir.out export fn eclair_program_run(*Program) -> Void { condition = (2 == func2(FN_ARG[0].0, 456, 789)) if (condition) { lower_bound_value = edge.stack_allocate Value upper_bound_value = edge.stack_allocate Value lower_bound_value.0 = func(FN_ARG[0].0, 123) lower_bound_value.1 = 0 upper_bound_value.0 = func(FN_ARG[0].0, 123) upper_bound_value.1 = 4294967295 begin_iter = edge.stack_allocate Iter end_iter = edge.stack_allocate Iter edge.iter_lower_bound(FN_ARG[0].1, lower_bound_value, begin_iter) edge.iter_upper_bound(FN_ARG[0].1, upper_bound_value, end_iter) loop { condition_1 = edge.iter_is_equal(begin_iter, end_iter) if (condition_1) { goto range_query.end } current = edge.iter_current(begin_iter) condition_2 = (current.0 == func(FN_ARG[0].0, current.1)) if (condition_2) { value = test_externs.stack_allocate Value value.0 = current.0 test_externs.insert(FN_ARG[0].2, value) } edge.iter_next(begin_iter) } range_query.end: } condition_3 = (constraint(FN_ARG[0].0, 2) != 0) if (condition_3) { condition_4 = (constraint(FN_ARG[0].0, func2(FN_ARG[0].0, 123, 456)) != 0) if (condition_4) { lower_bound_value_1 = edge.stack_allocate Value upper_bound_value_1 = edge.stack_allocate Value lower_bound_value_1.0 = 0 lower_bound_value_1.1 = 0 upper_bound_value_1.0 = 4294967295 upper_bound_value_1.1 = 4294967295 begin_iter_1 = edge.stack_allocate Iter end_iter_1 = edge.stack_allocate Iter edge.iter_lower_bound(FN_ARG[0].1, lower_bound_value_1, begin_iter_1) edge.iter_upper_bound(FN_ARG[0].1, upper_bound_value_1, end_iter_1) loop { condition_5 = edge.iter_is_equal(begin_iter_1, end_iter_1) if (condition_5) { goto range_query.end_1 } current_1 = edge.iter_current(begin_iter_1) value_1 = test_externs.stack_allocate Value value_1.0 = current_1.0 test_externs.insert(FN_ARG[0].2, value_1) edge.iter_next(begin_iter_1) } range_query.end_1: } } value_2 = test_externs.stack_allocate Value value_2.0 = func(FN_ARG[0].0, 123) test_externs.insert(FN_ARG[0].2, value_2) } //--- expected_llvm.out define external ccc void @eclair_program_run(ptr %arg_0) "wasm-export-name"="eclair_program_run" { start: %stack.ptr_0 = alloca [2 x i32], i32 1 %stack.ptr_1 = alloca [2 x i32], i32 1 %stack.ptr_2 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_3 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_4 = alloca [1 x i32], i32 1 %stack.ptr_5 = alloca [2 x i32], i32 1 %stack.ptr_6 = alloca [2 x i32], i32 1 %stack.ptr_7 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_8 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_9 = alloca [1 x i32], i32 1 %stack.ptr_10 = alloca [1 x i32], i32 1 %0 = getelementptr %program, ptr %arg_0, i32 0, i32 0 %1 = call ccc i32 @func2(ptr %0, i32 456, i32 789) %2 = icmp eq i32 2, %1 br i1 %2, label %if_0, label %end_if_2 if_0: %3 = getelementptr [2 x i32], ptr %stack.ptr_0, i32 0, i32 0 %4 = getelementptr %program, ptr %arg_0, i32 0, i32 0 %5 = call ccc i32 @func(ptr %4, i32 123) store i32 %5, ptr %3 %6 = getelementptr [2 x i32], ptr %stack.ptr_0, i32 0, i32 1 store i32 0, ptr %6 %7 = getelementptr [2 x i32], ptr %stack.ptr_1, i32 0, i32 0 %8 = getelementptr %program, ptr %arg_0, i32 0, i32 0 %9 = call ccc i32 @func(ptr %8, i32 123) store i32 %9, ptr %7 %10 = getelementptr [2 x i32], ptr %stack.ptr_1, i32 0, i32 1 store i32 4294967295, ptr %10 %11 = getelementptr %program, ptr %arg_0, i32 0, i32 1 call ccc void @eclair_btree_lower_bound_0(ptr %11, ptr %stack.ptr_0, ptr %stack.ptr_2) %12 = getelementptr %program, ptr %arg_0, i32 0, i32 1 call ccc void @eclair_btree_upper_bound_0(ptr %12, ptr %stack.ptr_1, ptr %stack.ptr_3) br label %loop_0 loop_0: %13 = call ccc i1 @eclair_btree_iterator_is_equal_0(ptr %stack.ptr_2, ptr %stack.ptr_3) br i1 %13, label %if_1, label %end_if_0 if_1: br label %range_query.end end_if_0: %14 = call ccc ptr @eclair_btree_iterator_current_0(ptr %stack.ptr_2) %15 = getelementptr [2 x i32], ptr %14, i32 0, i32 0 %16 = load i32, ptr %15 %17 = getelementptr %program, ptr %arg_0, i32 0, i32 0 %18 = getelementptr [2 x i32], ptr %14, i32 0, i32 1 %19 = call ccc i32 @func(ptr %17, ptr %18) %20 = icmp eq i32 %16, %19 br i1 %20, label %if_2, label %end_if_1 if_2: %21 = getelementptr [1 x i32], ptr %stack.ptr_4, i32 0, i32 0 %22 = getelementptr [2 x i32], ptr %14, i32 0, i32 0 %23 = load i32, ptr %22 store i32 %23, ptr %21 %24 = getelementptr %program, ptr %arg_0, i32 0, i32 2 %25 = call ccc i1 @eclair_btree_insert_value_1(ptr %24, ptr %stack.ptr_4) br label %end_if_1 end_if_1: call ccc void @eclair_btree_iterator_next_0(ptr %stack.ptr_2) br label %loop_0 range_query.end: br label %end_if_2 end_if_2: %26 = getelementptr %program, ptr %arg_0, i32 0, i32 0 %27 = call ccc i1 @constraint(ptr %26, i32 2) %28 = icmp ne i1 %27, 0 br i1 %28, label %if_3, label %end_if_5 if_3: %29 = getelementptr %program, ptr %arg_0, i32 0, i32 0 %30 = getelementptr %program, ptr %arg_0, i32 0, i32 0 %31 = call ccc i32 @func2(ptr %30, i32 123, i32 456) %32 = call ccc i1 @constraint(ptr %29, i32 %31) %33 = icmp ne i1 %32, 0 br i1 %33, label %if_4, label %end_if_4 if_4: %34 = getelementptr [2 x i32], ptr %stack.ptr_5, i32 0, i32 0 store i32 0, ptr %34 %35 = getelementptr [2 x i32], ptr %stack.ptr_5, i32 0, i32 1 store i32 0, ptr %35 %36 = getelementptr [2 x i32], ptr %stack.ptr_6, i32 0, i32 0 store i32 4294967295, ptr %36 %37 = getelementptr [2 x i32], ptr %stack.ptr_6, i32 0, i32 1 store i32 4294967295, ptr %37 %38 = getelementptr %program, ptr %arg_0, i32 0, i32 1 call ccc void @eclair_btree_lower_bound_0(ptr %38, ptr %stack.ptr_5, ptr %stack.ptr_7) %39 = getelementptr %program, ptr %arg_0, i32 0, i32 1 call ccc void @eclair_btree_upper_bound_0(ptr %39, ptr %stack.ptr_6, ptr %stack.ptr_8) br label %loop_1 loop_1: %40 = call ccc i1 @eclair_btree_iterator_is_equal_0(ptr %stack.ptr_7, ptr %stack.ptr_8) br i1 %40, label %if_5, label %end_if_3 if_5: br label %range_query.end_1 end_if_3: %41 = call ccc ptr @eclair_btree_iterator_current_0(ptr %stack.ptr_7) %42 = getelementptr [1 x i32], ptr %stack.ptr_9, i32 0, i32 0 %43 = getelementptr [2 x i32], ptr %41, i32 0, i32 0 %44 = load i32, ptr %43 store i32 %44, ptr %42 %45 = getelementptr %program, ptr %arg_0, i32 0, i32 2 %46 = call ccc i1 @eclair_btree_insert_value_1(ptr %45, ptr %stack.ptr_9) call ccc void @eclair_btree_iterator_next_0(ptr %stack.ptr_7) br label %loop_1 range_query.end_1: br label %end_if_4 end_if_4: br label %end_if_5 end_if_5: %47 = getelementptr [1 x i32], ptr %stack.ptr_10, i32 0, i32 0 %48 = getelementptr %program, ptr %arg_0, i32 0, i32 0 %49 = call ccc i32 @func(ptr %48, i32 123) store i32 %49, ptr %47 %50 = getelementptr %program, ptr %arg_0, i32 0, i32 2 %51 = call ccc i1 @eclair_btree_insert_value_1(ptr %50, ptr %stack.ptr_10) ret void } ================================================ FILE: tests/lowering/multiple_clauses_same_name.eclair ================================================ // RUN: split-file %s %t // RUN: %eclair compile --emit ra-transformed %t/program.eclair > %t/actual_ra.out // RUN: diff %t/expected_ra.out %t/actual_ra.out // RUN: %eclair compile --emit eir %t/program.eclair > %t/actual_eir.out // RUN: diff %t/expected_eir.out %t/actual_eir.out // RUN: %eclair compile --emit llvm %t/program.eclair > %t/actual_llvm.out // RUN: %extract_snippet %t/actual_llvm.out "program = " > %t/actual_eclair_program_type.out // RUN: %extract_snippet %t/actual_llvm.out "@eclair_program_init" > %t/actual_eclair_program_init_llvm.out // RUN: %extract_snippet %t/actual_llvm.out "@eclair_program_destroy" > %t/actual_eclair_program_destroy_llvm.out // RUN: %extract_snippet %t/actual_llvm.out "@eclair_program_run" > %t/actual_eclair_program_run_llvm.out // RUN: diff %t/expected_eclair_program_type.out %t/actual_eclair_program_type.out // RUN: diff %t/expected_eclair_program_init_llvm.out %t/actual_eclair_program_init_llvm.out // RUN: diff %t/expected_eclair_program_destroy_llvm.out %t/actual_eclair_program_destroy_llvm.out // RUN: diff %t/expected_eclair_program_run_llvm.out %t/actual_eclair_program_run_llvm.out //--- program.eclair @def link(u32, u32). @def chain(u32, u32, u32) output. link(1,2). chain(x, y, z) :- link(x, y), link(y, z). //--- expected_ra.out project (1, 2) into link search link as link0 do search link as link1 where (link0[1] = link1[0]) do project (link0[0], link0[1], link1[1]) into chain //--- expected_eir.out declare_type Program { symbol_table chain btree(num_columns=3, index=[0,1,2], block_size=256, search_type=linear) link btree(num_columns=2, index=[0,1], block_size=256, search_type=linear) } export fn eclair_program_init() -> *Program { program = heap_allocate_program symbol_table.init(program.0) chain.init_empty(program.1) link.init_empty(program.2) symbol_table.insert(program.0, "link") symbol_table.insert(program.0, "chain") return program } export fn eclair_program_destroy(*Program) -> Void { symbol_table.destroy(FN_ARG[0].0) chain.destroy(FN_ARG[0].1) link.destroy(FN_ARG[0].2) free_program(FN_ARG[0]) } export fn eclair_program_run(*Program) -> Void { value = link.stack_allocate Value value.0 = 1 value.1 = 2 link.insert(FN_ARG[0].2, value) lower_bound_value = link.stack_allocate Value upper_bound_value = link.stack_allocate Value lower_bound_value.0 = 0 lower_bound_value.1 = 0 upper_bound_value.0 = 4294967295 upper_bound_value.1 = 4294967295 begin_iter = link.stack_allocate Iter end_iter = link.stack_allocate Iter link.iter_lower_bound(FN_ARG[0].2, lower_bound_value, begin_iter) link.iter_upper_bound(FN_ARG[0].2, upper_bound_value, end_iter) loop { condition = link.iter_is_equal(begin_iter, end_iter) if (condition) { goto range_query.end } current = link.iter_current(begin_iter) lower_bound_value_1 = link.stack_allocate Value upper_bound_value_1 = link.stack_allocate Value lower_bound_value_1.0 = current.1 lower_bound_value_1.1 = 0 upper_bound_value_1.0 = current.1 upper_bound_value_1.1 = 4294967295 begin_iter_1 = link.stack_allocate Iter end_iter_1 = link.stack_allocate Iter link.iter_lower_bound(FN_ARG[0].2, lower_bound_value_1, begin_iter_1) link.iter_upper_bound(FN_ARG[0].2, upper_bound_value_1, end_iter_1) loop { condition_1 = link.iter_is_equal(begin_iter_1, end_iter_1) if (condition_1) { goto range_query.end_1 } current_1 = link.iter_current(begin_iter_1) value_1 = chain.stack_allocate Value value_1.0 = current.0 value_1.1 = current.1 value_1.2 = current_1.1 chain.insert(FN_ARG[0].1, value_1) link.iter_next(begin_iter_1) } range_query.end_1: link.iter_next(begin_iter) } range_query.end: } //--- expected_eclair_program_type.out %program = type {%symbol_table, %btree_t_0, %btree_t_1} //--- expected_eclair_program_init_llvm.out define external ccc ptr @eclair_program_init() "wasm-export-name"="eclair_program_init" { start: %stack.ptr_0 = alloca %symbol_t, i32 1 %stack.ptr_1 = alloca %symbol_t, i32 1 %0 = call ccc ptr @malloc(i32 1592) %1 = getelementptr %program, ptr %0, i32 0, i32 0 call ccc void @eclair_symbol_table_init(ptr %1) %2 = getelementptr %program, ptr %0, i32 0, i32 1 call ccc void @eclair_btree_init_empty_0(ptr %2) %3 = getelementptr %program, ptr %0, i32 0, i32 2 call ccc void @eclair_btree_init_empty_1(ptr %3) %4 = getelementptr %program, ptr %0, i32 0, i32 0 %5 = getelementptr inbounds [5 x i8], ptr @string_literal_0, i32 0, i32 0 %6 = zext i32 4 to i64 %7 = call ccc ptr @malloc(i32 4) call ccc void @llvm.memcpy.p0i8.p0i8.i64(ptr %7, ptr %5, i64 %6, i1 0) call ccc void @eclair_symbol_init(ptr %stack.ptr_0, i32 4, ptr %7) %8 = call ccc i32 @eclair_symbol_table_find_or_insert(ptr %4, ptr %stack.ptr_0) %9 = getelementptr %program, ptr %0, i32 0, i32 0 %10 = getelementptr inbounds [6 x i8], ptr @string_literal_1, i32 0, i32 0 %11 = zext i32 5 to i64 %12 = call ccc ptr @malloc(i32 5) call ccc void @llvm.memcpy.p0i8.p0i8.i64(ptr %12, ptr %10, i64 %11, i1 0) call ccc void @eclair_symbol_init(ptr %stack.ptr_1, i32 5, ptr %12) %13 = call ccc i32 @eclair_symbol_table_find_or_insert(ptr %9, ptr %stack.ptr_1) ret ptr %0 } //--- expected_eclair_program_destroy_llvm.out define external ccc void @eclair_program_destroy(ptr %arg_0) "wasm-export-name"="eclair_program_destroy" { start: %0 = getelementptr %program, ptr %arg_0, i32 0, i32 0 call ccc void @eclair_symbol_table_destroy(ptr %0) %1 = getelementptr %program, ptr %arg_0, i32 0, i32 1 call ccc void @eclair_btree_destroy_0(ptr %1) %2 = getelementptr %program, ptr %arg_0, i32 0, i32 2 call ccc void @eclair_btree_destroy_1(ptr %2) call ccc void @free(ptr %arg_0) ret void } //--- expected_eclair_program_run_llvm.out define external ccc void @eclair_program_run(ptr %arg_0) "wasm-export-name"="eclair_program_run" { start: %stack.ptr_0 = alloca [2 x i32], i32 1 %stack.ptr_1 = alloca [2 x i32], i32 1 %stack.ptr_2 = alloca [2 x i32], i32 1 %stack.ptr_3 = alloca %btree_iterator_t_1, i32 1 %stack.ptr_4 = alloca %btree_iterator_t_1, i32 1 %stack.ptr_5 = alloca [2 x i32], i32 1 %stack.ptr_6 = alloca [2 x i32], i32 1 %stack.ptr_7 = alloca %btree_iterator_t_1, i32 1 %stack.ptr_8 = alloca %btree_iterator_t_1, i32 1 %stack.ptr_9 = alloca [3 x i32], i32 1 %0 = getelementptr [2 x i32], ptr %stack.ptr_0, i32 0, i32 0 store i32 1, ptr %0 %1 = getelementptr [2 x i32], ptr %stack.ptr_0, i32 0, i32 1 store i32 2, ptr %1 %2 = getelementptr %program, ptr %arg_0, i32 0, i32 2 %3 = call ccc i1 @eclair_btree_insert_value_1(ptr %2, ptr %stack.ptr_0) %4 = getelementptr [2 x i32], ptr %stack.ptr_1, i32 0, i32 0 store i32 0, ptr %4 %5 = getelementptr [2 x i32], ptr %stack.ptr_1, i32 0, i32 1 store i32 0, ptr %5 %6 = getelementptr [2 x i32], ptr %stack.ptr_2, i32 0, i32 0 store i32 4294967295, ptr %6 %7 = getelementptr [2 x i32], ptr %stack.ptr_2, i32 0, i32 1 store i32 4294967295, ptr %7 %8 = getelementptr %program, ptr %arg_0, i32 0, i32 2 call ccc void @eclair_btree_lower_bound_1(ptr %8, ptr %stack.ptr_1, ptr %stack.ptr_3) %9 = getelementptr %program, ptr %arg_0, i32 0, i32 2 call ccc void @eclair_btree_upper_bound_1(ptr %9, ptr %stack.ptr_2, ptr %stack.ptr_4) br label %loop_0 loop_0: %10 = call ccc i1 @eclair_btree_iterator_is_equal_1(ptr %stack.ptr_3, ptr %stack.ptr_4) br i1 %10, label %if_0, label %end_if_0 if_0: br label %range_query.end end_if_0: %11 = call ccc ptr @eclair_btree_iterator_current_1(ptr %stack.ptr_3) %12 = getelementptr [2 x i32], ptr %stack.ptr_5, i32 0, i32 0 %13 = getelementptr [2 x i32], ptr %11, i32 0, i32 1 %14 = load i32, ptr %13 store i32 %14, ptr %12 %15 = getelementptr [2 x i32], ptr %stack.ptr_5, i32 0, i32 1 store i32 0, ptr %15 %16 = getelementptr [2 x i32], ptr %stack.ptr_6, i32 0, i32 0 %17 = getelementptr [2 x i32], ptr %11, i32 0, i32 1 %18 = load i32, ptr %17 store i32 %18, ptr %16 %19 = getelementptr [2 x i32], ptr %stack.ptr_6, i32 0, i32 1 store i32 4294967295, ptr %19 %20 = getelementptr %program, ptr %arg_0, i32 0, i32 2 call ccc void @eclair_btree_lower_bound_1(ptr %20, ptr %stack.ptr_5, ptr %stack.ptr_7) %21 = getelementptr %program, ptr %arg_0, i32 0, i32 2 call ccc void @eclair_btree_upper_bound_1(ptr %21, ptr %stack.ptr_6, ptr %stack.ptr_8) br label %loop_1 loop_1: %22 = call ccc i1 @eclair_btree_iterator_is_equal_1(ptr %stack.ptr_7, ptr %stack.ptr_8) br i1 %22, label %if_1, label %end_if_1 if_1: br label %range_query.end_1 end_if_1: %23 = call ccc ptr @eclair_btree_iterator_current_1(ptr %stack.ptr_7) %24 = getelementptr [3 x i32], ptr %stack.ptr_9, i32 0, i32 0 %25 = getelementptr [2 x i32], ptr %11, i32 0, i32 0 %26 = load i32, ptr %25 store i32 %26, ptr %24 %27 = getelementptr [3 x i32], ptr %stack.ptr_9, i32 0, i32 1 %28 = getelementptr [2 x i32], ptr %11, i32 0, i32 1 %29 = load i32, ptr %28 store i32 %29, ptr %27 %30 = getelementptr [3 x i32], ptr %stack.ptr_9, i32 0, i32 2 %31 = getelementptr [2 x i32], ptr %23, i32 0, i32 1 %32 = load i32, ptr %31 store i32 %32, ptr %30 %33 = getelementptr %program, ptr %arg_0, i32 0, i32 1 %34 = call ccc i1 @eclair_btree_insert_value_0(ptr %33, ptr %stack.ptr_9) call ccc void @eclair_btree_iterator_next_1(ptr %stack.ptr_7) br label %loop_1 range_query.end_1: call ccc void @eclair_btree_iterator_next_1(ptr %stack.ptr_3) br label %loop_0 range_query.end: ret void } ================================================ FILE: tests/lowering/multiple_rule_clauses.eclair ================================================ // RUN: split-file %s %t // RUN: %eclair compile --emit ra-transformed %t/program.eclair > %t/actual_ra.out // RUN: diff %t/expected_ra.out %t/actual_ra.out // RUN: %eclair compile --emit eir %t/program.eclair > %t/actual_eir.out // RUN: diff %t/expected_eir.out %t/actual_eir.out // RUN: %eclair compile --emit llvm %t/program.eclair > %t/actual_llvm.out // RUN: %extract_snippet %t/actual_llvm.out "program = " > %t/actual_eclair_program_type.out // RUN: %extract_snippet %t/actual_llvm.out "@eclair_program_init" > %t/actual_eclair_program_init_llvm.out // RUN: %extract_snippet %t/actual_llvm.out "@eclair_program_destroy" > %t/actual_eclair_program_destroy_llvm.out // RUN: %extract_snippet %t/actual_llvm.out "@eclair_program_run" > %t/actual_eclair_program_run_llvm.out // RUN: diff %t/expected_eclair_program_type.out %t/actual_eclair_program_type.out // RUN: diff %t/expected_eclair_program_init_llvm.out %t/actual_eclair_program_init_llvm.out // RUN: diff %t/expected_eclair_program_destroy_llvm.out %t/actual_eclair_program_destroy_llvm.out // RUN: diff %t/expected_eclair_program_run_llvm.out %t/actual_eclair_program_run_llvm.out //--- program.eclair @def first(u32). @def second(u32, u32). @def third(u32, u32) output. first(1). second(2, 3). third(x, y) :- first(y), second(x, y). //--- expected_ra.out project (2, 3) into second project (1) into first search first as first0 do search second as second1 where (first0[0] = second1[1]) do project (second1[0], first0[0]) into third //--- expected_eir.out declare_type Program { symbol_table first btree(num_columns=1, index=[0], block_size=256, search_type=linear) second btree(num_columns=2, index=[1,0], block_size=256, search_type=linear) third btree(num_columns=2, index=[0,1], block_size=256, search_type=linear) } export fn eclair_program_init() -> *Program { program = heap_allocate_program symbol_table.init(program.0) first.init_empty(program.1) second.init_empty(program.2) third.init_empty(program.3) symbol_table.insert(program.0, "first") symbol_table.insert(program.0, "second") symbol_table.insert(program.0, "third") return program } export fn eclair_program_destroy(*Program) -> Void { symbol_table.destroy(FN_ARG[0].0) first.destroy(FN_ARG[0].1) second.destroy(FN_ARG[0].2) third.destroy(FN_ARG[0].3) free_program(FN_ARG[0]) } export fn eclair_program_run(*Program) -> Void { value = second.stack_allocate Value value.0 = 2 value.1 = 3 second.insert(FN_ARG[0].2, value) value_1 = first.stack_allocate Value value_1.0 = 1 first.insert(FN_ARG[0].1, value_1) lower_bound_value = first.stack_allocate Value upper_bound_value = first.stack_allocate Value lower_bound_value.0 = 0 upper_bound_value.0 = 4294967295 begin_iter = first.stack_allocate Iter end_iter = first.stack_allocate Iter first.iter_lower_bound(FN_ARG[0].1, lower_bound_value, begin_iter) first.iter_upper_bound(FN_ARG[0].1, upper_bound_value, end_iter) loop { condition = first.iter_is_equal(begin_iter, end_iter) if (condition) { goto range_query.end } current = first.iter_current(begin_iter) lower_bound_value_1 = second.stack_allocate Value upper_bound_value_1 = second.stack_allocate Value lower_bound_value_1.0 = 0 lower_bound_value_1.1 = current.0 upper_bound_value_1.0 = 4294967295 upper_bound_value_1.1 = current.0 begin_iter_1 = second.stack_allocate Iter end_iter_1 = second.stack_allocate Iter second.iter_lower_bound(FN_ARG[0].2, lower_bound_value_1, begin_iter_1) second.iter_upper_bound(FN_ARG[0].2, upper_bound_value_1, end_iter_1) loop { condition_1 = second.iter_is_equal(begin_iter_1, end_iter_1) if (condition_1) { goto range_query.end_1 } current_1 = second.iter_current(begin_iter_1) value_2 = third.stack_allocate Value value_2.0 = current_1.0 value_2.1 = current.0 third.insert(FN_ARG[0].3, value_2) second.iter_next(begin_iter_1) } range_query.end_1: first.iter_next(begin_iter) } range_query.end: } //--- expected_eclair_program_type.out %program = type {%symbol_table, %btree_t_0, %btree_t_1, %btree_t_2} //--- expected_eclair_program_init_llvm.out define external ccc ptr @eclair_program_init() "wasm-export-name"="eclair_program_init" { start: %stack.ptr_0 = alloca %symbol_t, i32 1 %stack.ptr_1 = alloca %symbol_t, i32 1 %stack.ptr_2 = alloca %symbol_t, i32 1 %0 = call ccc ptr @malloc(i32 1608) %1 = getelementptr %program, ptr %0, i32 0, i32 0 call ccc void @eclair_symbol_table_init(ptr %1) %2 = getelementptr %program, ptr %0, i32 0, i32 1 call ccc void @eclair_btree_init_empty_0(ptr %2) %3 = getelementptr %program, ptr %0, i32 0, i32 2 call ccc void @eclair_btree_init_empty_1(ptr %3) %4 = getelementptr %program, ptr %0, i32 0, i32 3 call ccc void @eclair_btree_init_empty_2(ptr %4) %5 = getelementptr %program, ptr %0, i32 0, i32 0 %6 = getelementptr inbounds [6 x i8], ptr @string_literal_0, i32 0, i32 0 %7 = zext i32 5 to i64 %8 = call ccc ptr @malloc(i32 5) call ccc void @llvm.memcpy.p0i8.p0i8.i64(ptr %8, ptr %6, i64 %7, i1 0) call ccc void @eclair_symbol_init(ptr %stack.ptr_0, i32 5, ptr %8) %9 = call ccc i32 @eclair_symbol_table_find_or_insert(ptr %5, ptr %stack.ptr_0) %10 = getelementptr %program, ptr %0, i32 0, i32 0 %11 = getelementptr inbounds [7 x i8], ptr @string_literal_1, i32 0, i32 0 %12 = zext i32 6 to i64 %13 = call ccc ptr @malloc(i32 6) call ccc void @llvm.memcpy.p0i8.p0i8.i64(ptr %13, ptr %11, i64 %12, i1 0) call ccc void @eclair_symbol_init(ptr %stack.ptr_1, i32 6, ptr %13) %14 = call ccc i32 @eclair_symbol_table_find_or_insert(ptr %10, ptr %stack.ptr_1) %15 = getelementptr %program, ptr %0, i32 0, i32 0 %16 = getelementptr inbounds [6 x i8], ptr @string_literal_2, i32 0, i32 0 %17 = zext i32 5 to i64 %18 = call ccc ptr @malloc(i32 5) call ccc void @llvm.memcpy.p0i8.p0i8.i64(ptr %18, ptr %16, i64 %17, i1 0) call ccc void @eclair_symbol_init(ptr %stack.ptr_2, i32 5, ptr %18) %19 = call ccc i32 @eclair_symbol_table_find_or_insert(ptr %15, ptr %stack.ptr_2) ret ptr %0 } //--- expected_eclair_program_destroy_llvm.out define external ccc void @eclair_program_destroy(ptr %arg_0) "wasm-export-name"="eclair_program_destroy" { start: %0 = getelementptr %program, ptr %arg_0, i32 0, i32 0 call ccc void @eclair_symbol_table_destroy(ptr %0) %1 = getelementptr %program, ptr %arg_0, i32 0, i32 1 call ccc void @eclair_btree_destroy_0(ptr %1) %2 = getelementptr %program, ptr %arg_0, i32 0, i32 2 call ccc void @eclair_btree_destroy_1(ptr %2) %3 = getelementptr %program, ptr %arg_0, i32 0, i32 3 call ccc void @eclair_btree_destroy_2(ptr %3) call ccc void @free(ptr %arg_0) ret void } //--- expected_eclair_program_run_llvm.out define external ccc void @eclair_program_run(ptr %arg_0) "wasm-export-name"="eclair_program_run" { start: %stack.ptr_0 = alloca [2 x i32], i32 1 %stack.ptr_1 = alloca [1 x i32], i32 1 %stack.ptr_2 = alloca [1 x i32], i32 1 %stack.ptr_3 = alloca [1 x i32], i32 1 %stack.ptr_4 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_5 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_6 = alloca [2 x i32], i32 1 %stack.ptr_7 = alloca [2 x i32], i32 1 %stack.ptr_8 = alloca %btree_iterator_t_1, i32 1 %stack.ptr_9 = alloca %btree_iterator_t_1, i32 1 %stack.ptr_10 = alloca [2 x i32], i32 1 %0 = getelementptr [2 x i32], ptr %stack.ptr_0, i32 0, i32 0 store i32 2, ptr %0 %1 = getelementptr [2 x i32], ptr %stack.ptr_0, i32 0, i32 1 store i32 3, ptr %1 %2 = getelementptr %program, ptr %arg_0, i32 0, i32 2 %3 = call ccc i1 @eclair_btree_insert_value_1(ptr %2, ptr %stack.ptr_0) %4 = getelementptr [1 x i32], ptr %stack.ptr_1, i32 0, i32 0 store i32 1, ptr %4 %5 = getelementptr %program, ptr %arg_0, i32 0, i32 1 %6 = call ccc i1 @eclair_btree_insert_value_0(ptr %5, ptr %stack.ptr_1) %7 = getelementptr [1 x i32], ptr %stack.ptr_2, i32 0, i32 0 store i32 0, ptr %7 %8 = getelementptr [1 x i32], ptr %stack.ptr_3, i32 0, i32 0 store i32 4294967295, ptr %8 %9 = getelementptr %program, ptr %arg_0, i32 0, i32 1 call ccc void @eclair_btree_lower_bound_0(ptr %9, ptr %stack.ptr_2, ptr %stack.ptr_4) %10 = getelementptr %program, ptr %arg_0, i32 0, i32 1 call ccc void @eclair_btree_upper_bound_0(ptr %10, ptr %stack.ptr_3, ptr %stack.ptr_5) br label %loop_0 loop_0: %11 = call ccc i1 @eclair_btree_iterator_is_equal_0(ptr %stack.ptr_4, ptr %stack.ptr_5) br i1 %11, label %if_0, label %end_if_0 if_0: br label %range_query.end end_if_0: %12 = call ccc ptr @eclair_btree_iterator_current_0(ptr %stack.ptr_4) %13 = getelementptr [2 x i32], ptr %stack.ptr_6, i32 0, i32 0 store i32 0, ptr %13 %14 = getelementptr [2 x i32], ptr %stack.ptr_6, i32 0, i32 1 %15 = getelementptr [1 x i32], ptr %12, i32 0, i32 0 %16 = load i32, ptr %15 store i32 %16, ptr %14 %17 = getelementptr [2 x i32], ptr %stack.ptr_7, i32 0, i32 0 store i32 4294967295, ptr %17 %18 = getelementptr [2 x i32], ptr %stack.ptr_7, i32 0, i32 1 %19 = getelementptr [1 x i32], ptr %12, i32 0, i32 0 %20 = load i32, ptr %19 store i32 %20, ptr %18 %21 = getelementptr %program, ptr %arg_0, i32 0, i32 2 call ccc void @eclair_btree_lower_bound_1(ptr %21, ptr %stack.ptr_6, ptr %stack.ptr_8) %22 = getelementptr %program, ptr %arg_0, i32 0, i32 2 call ccc void @eclair_btree_upper_bound_1(ptr %22, ptr %stack.ptr_7, ptr %stack.ptr_9) br label %loop_1 loop_1: %23 = call ccc i1 @eclair_btree_iterator_is_equal_1(ptr %stack.ptr_8, ptr %stack.ptr_9) br i1 %23, label %if_1, label %end_if_1 if_1: br label %range_query.end_1 end_if_1: %24 = call ccc ptr @eclair_btree_iterator_current_1(ptr %stack.ptr_8) %25 = getelementptr [2 x i32], ptr %stack.ptr_10, i32 0, i32 0 %26 = getelementptr [2 x i32], ptr %24, i32 0, i32 0 %27 = load i32, ptr %26 store i32 %27, ptr %25 %28 = getelementptr [2 x i32], ptr %stack.ptr_10, i32 0, i32 1 %29 = getelementptr [1 x i32], ptr %12, i32 0, i32 0 %30 = load i32, ptr %29 store i32 %30, ptr %28 %31 = getelementptr %program, ptr %arg_0, i32 0, i32 3 %32 = call ccc i1 @eclair_btree_insert_value_2(ptr %31, ptr %stack.ptr_10) call ccc void @eclair_btree_iterator_next_1(ptr %stack.ptr_8) br label %loop_1 range_query.end_1: call ccc void @eclair_btree_iterator_next_0(ptr %stack.ptr_4) br label %loop_0 range_query.end: ret void } ================================================ FILE: tests/lowering/mutually_recursive_rules.eclair ================================================ // TODO variant where one is recursive // TODO tests for rules with >2 clauses, ... // RUN: split-file %s %t // RUN: %eclair compile --emit ra-transformed %t/program.eclair > %t/actual_ra.out // RUN: diff %t/expected_ra.out %t/actual_ra.out // RUN: %eclair compile --emit eir %t/program.eclair > %t/actual_eir.out // RUN: diff %t/expected_eir.out %t/actual_eir.out // RUN: %eclair compile --emit llvm %t/program.eclair > %t/actual_llvm.out // RUN: %extract_snippet %t/actual_llvm.out "program = " > %t/actual_eclair_program_type.out // RUN: %extract_snippet %t/actual_llvm.out "@eclair_program_init" > %t/actual_eclair_program_init_llvm.out // RUN: %extract_snippet %t/actual_llvm.out "@eclair_program_destroy" > %t/actual_eclair_program_destroy_llvm.out // RUN: %extract_snippet %t/actual_llvm.out "@eclair_program_run" > %t/actual_eclair_program_run_llvm.out // RUN: diff %t/expected_eclair_program_type.out %t/actual_eclair_program_type.out // RUN: diff %t/expected_eclair_program_init_llvm.out %t/actual_eclair_program_init_llvm.out // RUN: diff %t/expected_eclair_program_destroy_llvm.out %t/actual_eclair_program_destroy_llvm.out // RUN: diff %t/expected_eclair_program_run_llvm.out %t/actual_eclair_program_run_llvm.out //--- program.eclair @def a(u32) output. @def b(u32) output. @def c(u32) output. @def d(u32) output. a(x) :- b(x), c(x). b(1). b(x) :- c(x), d(x). c(2). c(x) :- b(x), d(x). d(3). //--- expected_ra.out project (3) into d project (2) into c project (1) into b merge c delta_c merge b delta_b loop do purge new_c purge new_b parallel do search delta_b as delta_b0 do if (delta_b0[0]) ∉ c do search d as d1 where (delta_b0[0] = d1[0]) do project (delta_b0[0]) into new_c search delta_c as delta_c0 do if (delta_c0[0]) ∉ b do search d as d1 where (delta_c0[0] = d1[0]) do project (delta_c0[0]) into new_b exit if counttuples(new_c) = 0 and counttuples(new_b) = 0 merge new_c c swap new_c delta_c merge new_b b swap new_b delta_b search b as b0 do search c as c1 where (b0[0] = c1[0]) do project (b0[0]) into a //--- expected_eir.out declare_type Program { symbol_table a btree(num_columns=1, index=[0], block_size=256, search_type=linear) b btree(num_columns=1, index=[0], block_size=256, search_type=linear) c btree(num_columns=1, index=[0], block_size=256, search_type=linear) d btree(num_columns=1, index=[0], block_size=256, search_type=linear) delta_b btree(num_columns=1, index=[0], block_size=256, search_type=linear) delta_c btree(num_columns=1, index=[0], block_size=256, search_type=linear) new_b btree(num_columns=1, index=[0], block_size=256, search_type=linear) new_c btree(num_columns=1, index=[0], block_size=256, search_type=linear) } export fn eclair_program_init() -> *Program { program = heap_allocate_program symbol_table.init(program.0) a.init_empty(program.1) b.init_empty(program.2) c.init_empty(program.3) d.init_empty(program.4) delta_b.init_empty(program.5) delta_c.init_empty(program.6) new_b.init_empty(program.7) new_c.init_empty(program.8) symbol_table.insert(program.0, "a") symbol_table.insert(program.0, "b") symbol_table.insert(program.0, "c") symbol_table.insert(program.0, "d") return program } export fn eclair_program_destroy(*Program) -> Void { symbol_table.destroy(FN_ARG[0].0) a.destroy(FN_ARG[0].1) b.destroy(FN_ARG[0].2) c.destroy(FN_ARG[0].3) d.destroy(FN_ARG[0].4) delta_b.destroy(FN_ARG[0].5) delta_c.destroy(FN_ARG[0].6) new_b.destroy(FN_ARG[0].7) new_c.destroy(FN_ARG[0].8) free_program(FN_ARG[0]) } export fn eclair_program_run(*Program) -> Void { value = d.stack_allocate Value value.0 = 3 d.insert(FN_ARG[0].4, value) value_1 = c.stack_allocate Value value_1.0 = 2 c.insert(FN_ARG[0].3, value_1) value_2 = b.stack_allocate Value value_2.0 = 1 b.insert(FN_ARG[0].2, value_2) begin_iter = c.stack_allocate Iter end_iter = c.stack_allocate Iter c.iter_begin(FN_ARG[0].3, begin_iter) c.iter_end(FN_ARG[0].3, end_iter) delta_c.insert_range(FN_ARG[0].6, begin_iter, end_iter) begin_iter_1 = b.stack_allocate Iter end_iter_1 = b.stack_allocate Iter b.iter_begin(FN_ARG[0].2, begin_iter_1) b.iter_end(FN_ARG[0].2, end_iter_1) delta_b.insert_range(FN_ARG[0].5, begin_iter_1, end_iter_1) loop { new_c.purge(FN_ARG[0].8) new_b.purge(FN_ARG[0].7) parallel { lower_bound_value = b.stack_allocate Value upper_bound_value = b.stack_allocate Value lower_bound_value.0 = 0 upper_bound_value.0 = 4294967295 begin_iter_2 = b.stack_allocate Iter end_iter_2 = b.stack_allocate Iter delta_b.iter_lower_bound(FN_ARG[0].5, lower_bound_value, begin_iter_2) delta_b.iter_upper_bound(FN_ARG[0].5, upper_bound_value, end_iter_2) loop { condition = delta_b.iter_is_equal(begin_iter_2, end_iter_2) if (condition) { goto range_query.end } current = delta_b.iter_current(begin_iter_2) value_3 = c.stack_allocate Value value_3.0 = current.0 contains_result = c.contains(FN_ARG[0].3, value_3) condition_1 = not contains_result if (condition_1) { lower_bound_value_1 = d.stack_allocate Value upper_bound_value_1 = d.stack_allocate Value lower_bound_value_1.0 = current.0 upper_bound_value_1.0 = current.0 begin_iter_3 = d.stack_allocate Iter end_iter_3 = d.stack_allocate Iter d.iter_lower_bound(FN_ARG[0].4, lower_bound_value_1, begin_iter_3) d.iter_upper_bound(FN_ARG[0].4, upper_bound_value_1, end_iter_3) loop { condition_2 = d.iter_is_equal(begin_iter_3, end_iter_3) if (condition_2) { goto range_query.end_1 } current_1 = d.iter_current(begin_iter_3) value_4 = c.stack_allocate Value value_4.0 = current.0 new_c.insert(FN_ARG[0].8, value_4) d.iter_next(begin_iter_3) } range_query.end_1: } delta_b.iter_next(begin_iter_2) } range_query.end: lower_bound_value_2 = c.stack_allocate Value upper_bound_value_2 = c.stack_allocate Value lower_bound_value_2.0 = 0 upper_bound_value_2.0 = 4294967295 begin_iter_4 = c.stack_allocate Iter end_iter_4 = c.stack_allocate Iter delta_c.iter_lower_bound(FN_ARG[0].6, lower_bound_value_2, begin_iter_4) delta_c.iter_upper_bound(FN_ARG[0].6, upper_bound_value_2, end_iter_4) loop { condition_3 = delta_c.iter_is_equal(begin_iter_4, end_iter_4) if (condition_3) { goto range_query.end_2 } current_2 = delta_c.iter_current(begin_iter_4) value_5 = b.stack_allocate Value value_5.0 = current_2.0 contains_result_1 = b.contains(FN_ARG[0].2, value_5) condition_4 = not contains_result_1 if (condition_4) { lower_bound_value_3 = d.stack_allocate Value upper_bound_value_3 = d.stack_allocate Value lower_bound_value_3.0 = current_2.0 upper_bound_value_3.0 = current_2.0 begin_iter_5 = d.stack_allocate Iter end_iter_5 = d.stack_allocate Iter d.iter_lower_bound(FN_ARG[0].4, lower_bound_value_3, begin_iter_5) d.iter_upper_bound(FN_ARG[0].4, upper_bound_value_3, end_iter_5) loop { condition_5 = d.iter_is_equal(begin_iter_5, end_iter_5) if (condition_5) { goto range_query.end_3 } current_3 = d.iter_current(begin_iter_5) value_6 = b.stack_allocate Value value_6.0 = current_2.0 new_b.insert(FN_ARG[0].7, value_6) d.iter_next(begin_iter_5) } range_query.end_3: } delta_c.iter_next(begin_iter_4) } range_query.end_2: } condition_6 = new_b.is_empty(FN_ARG[0].7) if (condition_6) { condition_7 = new_c.is_empty(FN_ARG[0].8) if (condition_7) { goto loop.end } } begin_iter_6 = c.stack_allocate Iter end_iter_6 = c.stack_allocate Iter new_c.iter_begin(FN_ARG[0].8, begin_iter_6) new_c.iter_end(FN_ARG[0].8, end_iter_6) c.insert_range(FN_ARG[0].3, begin_iter_6, end_iter_6) new_c.swap(FN_ARG[0].8, FN_ARG[0].6) begin_iter_7 = b.stack_allocate Iter end_iter_7 = b.stack_allocate Iter new_b.iter_begin(FN_ARG[0].7, begin_iter_7) new_b.iter_end(FN_ARG[0].7, end_iter_7) b.insert_range(FN_ARG[0].2, begin_iter_7, end_iter_7) new_b.swap(FN_ARG[0].7, FN_ARG[0].5) } loop.end: lower_bound_value_4 = b.stack_allocate Value upper_bound_value_4 = b.stack_allocate Value lower_bound_value_4.0 = 0 upper_bound_value_4.0 = 4294967295 begin_iter_8 = b.stack_allocate Iter end_iter_8 = b.stack_allocate Iter b.iter_lower_bound(FN_ARG[0].2, lower_bound_value_4, begin_iter_8) b.iter_upper_bound(FN_ARG[0].2, upper_bound_value_4, end_iter_8) loop { condition_8 = b.iter_is_equal(begin_iter_8, end_iter_8) if (condition_8) { goto range_query.end_4 } current_4 = b.iter_current(begin_iter_8) lower_bound_value_5 = c.stack_allocate Value upper_bound_value_5 = c.stack_allocate Value lower_bound_value_5.0 = current_4.0 upper_bound_value_5.0 = current_4.0 begin_iter_9 = c.stack_allocate Iter end_iter_9 = c.stack_allocate Iter c.iter_lower_bound(FN_ARG[0].3, lower_bound_value_5, begin_iter_9) c.iter_upper_bound(FN_ARG[0].3, upper_bound_value_5, end_iter_9) loop { condition_9 = c.iter_is_equal(begin_iter_9, end_iter_9) if (condition_9) { goto range_query.end_5 } current_5 = c.iter_current(begin_iter_9) value_7 = a.stack_allocate Value value_7.0 = current_4.0 a.insert(FN_ARG[0].1, value_7) c.iter_next(begin_iter_9) } range_query.end_5: b.iter_next(begin_iter_8) } range_query.end_4: } //--- expected_eclair_program_type.out %program = type {%symbol_table, %btree_t_0, %btree_t_0, %btree_t_0, %btree_t_0, %btree_t_0, %btree_t_0, %btree_t_0, %btree_t_0} //--- expected_eclair_program_init_llvm.out define external ccc ptr @eclair_program_init() "wasm-export-name"="eclair_program_init" { start: %stack.ptr_0 = alloca %symbol_t, i32 1 %stack.ptr_1 = alloca %symbol_t, i32 1 %stack.ptr_2 = alloca %symbol_t, i32 1 %stack.ptr_3 = alloca %symbol_t, i32 1 %0 = call ccc ptr @malloc(i32 1688) %1 = getelementptr %program, ptr %0, i32 0, i32 0 call ccc void @eclair_symbol_table_init(ptr %1) %2 = getelementptr %program, ptr %0, i32 0, i32 1 call ccc void @eclair_btree_init_empty_0(ptr %2) %3 = getelementptr %program, ptr %0, i32 0, i32 2 call ccc void @eclair_btree_init_empty_0(ptr %3) %4 = getelementptr %program, ptr %0, i32 0, i32 3 call ccc void @eclair_btree_init_empty_0(ptr %4) %5 = getelementptr %program, ptr %0, i32 0, i32 4 call ccc void @eclair_btree_init_empty_0(ptr %5) %6 = getelementptr %program, ptr %0, i32 0, i32 5 call ccc void @eclair_btree_init_empty_0(ptr %6) %7 = getelementptr %program, ptr %0, i32 0, i32 6 call ccc void @eclair_btree_init_empty_0(ptr %7) %8 = getelementptr %program, ptr %0, i32 0, i32 7 call ccc void @eclair_btree_init_empty_0(ptr %8) %9 = getelementptr %program, ptr %0, i32 0, i32 8 call ccc void @eclair_btree_init_empty_0(ptr %9) %10 = getelementptr %program, ptr %0, i32 0, i32 0 %11 = getelementptr inbounds [2 x i8], ptr @string_literal_0, i32 0, i32 0 %12 = zext i32 1 to i64 %13 = call ccc ptr @malloc(i32 1) call ccc void @llvm.memcpy.p0i8.p0i8.i64(ptr %13, ptr %11, i64 %12, i1 0) call ccc void @eclair_symbol_init(ptr %stack.ptr_0, i32 1, ptr %13) %14 = call ccc i32 @eclair_symbol_table_find_or_insert(ptr %10, ptr %stack.ptr_0) %15 = getelementptr %program, ptr %0, i32 0, i32 0 %16 = getelementptr inbounds [2 x i8], ptr @string_literal_1, i32 0, i32 0 %17 = zext i32 1 to i64 %18 = call ccc ptr @malloc(i32 1) call ccc void @llvm.memcpy.p0i8.p0i8.i64(ptr %18, ptr %16, i64 %17, i1 0) call ccc void @eclair_symbol_init(ptr %stack.ptr_1, i32 1, ptr %18) %19 = call ccc i32 @eclair_symbol_table_find_or_insert(ptr %15, ptr %stack.ptr_1) %20 = getelementptr %program, ptr %0, i32 0, i32 0 %21 = getelementptr inbounds [2 x i8], ptr @string_literal_2, i32 0, i32 0 %22 = zext i32 1 to i64 %23 = call ccc ptr @malloc(i32 1) call ccc void @llvm.memcpy.p0i8.p0i8.i64(ptr %23, ptr %21, i64 %22, i1 0) call ccc void @eclair_symbol_init(ptr %stack.ptr_2, i32 1, ptr %23) %24 = call ccc i32 @eclair_symbol_table_find_or_insert(ptr %20, ptr %stack.ptr_2) %25 = getelementptr %program, ptr %0, i32 0, i32 0 %26 = getelementptr inbounds [2 x i8], ptr @string_literal_3, i32 0, i32 0 %27 = zext i32 1 to i64 %28 = call ccc ptr @malloc(i32 1) call ccc void @llvm.memcpy.p0i8.p0i8.i64(ptr %28, ptr %26, i64 %27, i1 0) call ccc void @eclair_symbol_init(ptr %stack.ptr_3, i32 1, ptr %28) %29 = call ccc i32 @eclair_symbol_table_find_or_insert(ptr %25, ptr %stack.ptr_3) ret ptr %0 } //--- expected_eclair_program_destroy_llvm.out define external ccc void @eclair_program_destroy(ptr %arg_0) "wasm-export-name"="eclair_program_destroy" { start: %0 = getelementptr %program, ptr %arg_0, i32 0, i32 0 call ccc void @eclair_symbol_table_destroy(ptr %0) %1 = getelementptr %program, ptr %arg_0, i32 0, i32 1 call ccc void @eclair_btree_destroy_0(ptr %1) %2 = getelementptr %program, ptr %arg_0, i32 0, i32 2 call ccc void @eclair_btree_destroy_0(ptr %2) %3 = getelementptr %program, ptr %arg_0, i32 0, i32 3 call ccc void @eclair_btree_destroy_0(ptr %3) %4 = getelementptr %program, ptr %arg_0, i32 0, i32 4 call ccc void @eclair_btree_destroy_0(ptr %4) %5 = getelementptr %program, ptr %arg_0, i32 0, i32 5 call ccc void @eclair_btree_destroy_0(ptr %5) %6 = getelementptr %program, ptr %arg_0, i32 0, i32 6 call ccc void @eclair_btree_destroy_0(ptr %6) %7 = getelementptr %program, ptr %arg_0, i32 0, i32 7 call ccc void @eclair_btree_destroy_0(ptr %7) %8 = getelementptr %program, ptr %arg_0, i32 0, i32 8 call ccc void @eclair_btree_destroy_0(ptr %8) call ccc void @free(ptr %arg_0) ret void } //--- expected_eclair_program_run_llvm.out define external ccc void @eclair_program_run(ptr %arg_0) "wasm-export-name"="eclair_program_run" { start: %stack.ptr_0 = alloca [1 x i32], i32 1 %stack.ptr_1 = alloca [1 x i32], i32 1 %stack.ptr_2 = alloca [1 x i32], i32 1 %stack.ptr_3 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_4 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_5 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_6 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_7 = alloca [1 x i32], i32 1 %stack.ptr_8 = alloca [1 x i32], i32 1 %stack.ptr_9 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_10 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_11 = alloca [1 x i32], i32 1 %stack.ptr_12 = alloca [1 x i32], i32 1 %stack.ptr_13 = alloca [1 x i32], i32 1 %stack.ptr_14 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_15 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_16 = alloca [1 x i32], i32 1 %stack.ptr_17 = alloca [1 x i32], i32 1 %stack.ptr_18 = alloca [1 x i32], i32 1 %stack.ptr_19 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_20 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_21 = alloca [1 x i32], i32 1 %stack.ptr_22 = alloca [1 x i32], i32 1 %stack.ptr_23 = alloca [1 x i32], i32 1 %stack.ptr_24 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_25 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_26 = alloca [1 x i32], i32 1 %stack.ptr_27 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_28 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_29 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_30 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_31 = alloca [1 x i32], i32 1 %stack.ptr_32 = alloca [1 x i32], i32 1 %stack.ptr_33 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_34 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_35 = alloca [1 x i32], i32 1 %stack.ptr_36 = alloca [1 x i32], i32 1 %stack.ptr_37 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_38 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_39 = alloca [1 x i32], i32 1 %0 = getelementptr [1 x i32], ptr %stack.ptr_0, i32 0, i32 0 store i32 3, ptr %0 %1 = getelementptr %program, ptr %arg_0, i32 0, i32 4 %2 = call ccc i1 @eclair_btree_insert_value_0(ptr %1, ptr %stack.ptr_0) %3 = getelementptr [1 x i32], ptr %stack.ptr_1, i32 0, i32 0 store i32 2, ptr %3 %4 = getelementptr %program, ptr %arg_0, i32 0, i32 3 %5 = call ccc i1 @eclair_btree_insert_value_0(ptr %4, ptr %stack.ptr_1) %6 = getelementptr [1 x i32], ptr %stack.ptr_2, i32 0, i32 0 store i32 1, ptr %6 %7 = getelementptr %program, ptr %arg_0, i32 0, i32 2 %8 = call ccc i1 @eclair_btree_insert_value_0(ptr %7, ptr %stack.ptr_2) %9 = getelementptr %program, ptr %arg_0, i32 0, i32 3 call ccc void @eclair_btree_begin_0(ptr %9, ptr %stack.ptr_3) %10 = getelementptr %program, ptr %arg_0, i32 0, i32 3 call ccc void @eclair_btree_end_0(ptr %10, ptr %stack.ptr_4) %11 = getelementptr %program, ptr %arg_0, i32 0, i32 6 call ccc void @eclair_btree_insert_range_delta_c_c(ptr %11, ptr %stack.ptr_3, ptr %stack.ptr_4) %12 = getelementptr %program, ptr %arg_0, i32 0, i32 2 call ccc void @eclair_btree_begin_0(ptr %12, ptr %stack.ptr_5) %13 = getelementptr %program, ptr %arg_0, i32 0, i32 2 call ccc void @eclair_btree_end_0(ptr %13, ptr %stack.ptr_6) %14 = getelementptr %program, ptr %arg_0, i32 0, i32 5 call ccc void @eclair_btree_insert_range_delta_b_b(ptr %14, ptr %stack.ptr_5, ptr %stack.ptr_6) br label %loop_0 loop_0: %15 = getelementptr %program, ptr %arg_0, i32 0, i32 8 call ccc void @eclair_btree_clear_0(ptr %15) %16 = getelementptr %program, ptr %arg_0, i32 0, i32 7 call ccc void @eclair_btree_clear_0(ptr %16) %17 = getelementptr [1 x i32], ptr %stack.ptr_7, i32 0, i32 0 store i32 0, ptr %17 %18 = getelementptr [1 x i32], ptr %stack.ptr_8, i32 0, i32 0 store i32 4294967295, ptr %18 %19 = getelementptr %program, ptr %arg_0, i32 0, i32 5 call ccc void @eclair_btree_lower_bound_0(ptr %19, ptr %stack.ptr_7, ptr %stack.ptr_9) %20 = getelementptr %program, ptr %arg_0, i32 0, i32 5 call ccc void @eclair_btree_upper_bound_0(ptr %20, ptr %stack.ptr_8, ptr %stack.ptr_10) br label %loop_1 loop_1: %21 = call ccc i1 @eclair_btree_iterator_is_equal_0(ptr %stack.ptr_9, ptr %stack.ptr_10) br i1 %21, label %if_0, label %end_if_0 if_0: br label %range_query.end end_if_0: %22 = call ccc ptr @eclair_btree_iterator_current_0(ptr %stack.ptr_9) %23 = getelementptr [1 x i32], ptr %stack.ptr_11, i32 0, i32 0 %24 = getelementptr [1 x i32], ptr %22, i32 0, i32 0 %25 = load i32, ptr %24 store i32 %25, ptr %23 %26 = getelementptr %program, ptr %arg_0, i32 0, i32 3 %27 = call ccc i1 @eclair_btree_contains_0(ptr %26, ptr %stack.ptr_11) %28 = select i1 %27, i1 0, i1 1 br i1 %28, label %if_1, label %end_if_2 if_1: %29 = getelementptr [1 x i32], ptr %stack.ptr_12, i32 0, i32 0 %30 = getelementptr [1 x i32], ptr %22, i32 0, i32 0 %31 = load i32, ptr %30 store i32 %31, ptr %29 %32 = getelementptr [1 x i32], ptr %stack.ptr_13, i32 0, i32 0 %33 = getelementptr [1 x i32], ptr %22, i32 0, i32 0 %34 = load i32, ptr %33 store i32 %34, ptr %32 %35 = getelementptr %program, ptr %arg_0, i32 0, i32 4 call ccc void @eclair_btree_lower_bound_0(ptr %35, ptr %stack.ptr_12, ptr %stack.ptr_14) %36 = getelementptr %program, ptr %arg_0, i32 0, i32 4 call ccc void @eclair_btree_upper_bound_0(ptr %36, ptr %stack.ptr_13, ptr %stack.ptr_15) br label %loop_2 loop_2: %37 = call ccc i1 @eclair_btree_iterator_is_equal_0(ptr %stack.ptr_14, ptr %stack.ptr_15) br i1 %37, label %if_2, label %end_if_1 if_2: br label %range_query.end_1 end_if_1: %38 = call ccc ptr @eclair_btree_iterator_current_0(ptr %stack.ptr_14) %39 = getelementptr [1 x i32], ptr %stack.ptr_16, i32 0, i32 0 %40 = getelementptr [1 x i32], ptr %22, i32 0, i32 0 %41 = load i32, ptr %40 store i32 %41, ptr %39 %42 = getelementptr %program, ptr %arg_0, i32 0, i32 8 %43 = call ccc i1 @eclair_btree_insert_value_0(ptr %42, ptr %stack.ptr_16) call ccc void @eclair_btree_iterator_next_0(ptr %stack.ptr_14) br label %loop_2 range_query.end_1: br label %end_if_2 end_if_2: call ccc void @eclair_btree_iterator_next_0(ptr %stack.ptr_9) br label %loop_1 range_query.end: %44 = getelementptr [1 x i32], ptr %stack.ptr_17, i32 0, i32 0 store i32 0, ptr %44 %45 = getelementptr [1 x i32], ptr %stack.ptr_18, i32 0, i32 0 store i32 4294967295, ptr %45 %46 = getelementptr %program, ptr %arg_0, i32 0, i32 6 call ccc void @eclair_btree_lower_bound_0(ptr %46, ptr %stack.ptr_17, ptr %stack.ptr_19) %47 = getelementptr %program, ptr %arg_0, i32 0, i32 6 call ccc void @eclair_btree_upper_bound_0(ptr %47, ptr %stack.ptr_18, ptr %stack.ptr_20) br label %loop_3 loop_3: %48 = call ccc i1 @eclair_btree_iterator_is_equal_0(ptr %stack.ptr_19, ptr %stack.ptr_20) br i1 %48, label %if_3, label %end_if_3 if_3: br label %range_query.end_2 end_if_3: %49 = call ccc ptr @eclair_btree_iterator_current_0(ptr %stack.ptr_19) %50 = getelementptr [1 x i32], ptr %stack.ptr_21, i32 0, i32 0 %51 = getelementptr [1 x i32], ptr %49, i32 0, i32 0 %52 = load i32, ptr %51 store i32 %52, ptr %50 %53 = getelementptr %program, ptr %arg_0, i32 0, i32 2 %54 = call ccc i1 @eclair_btree_contains_0(ptr %53, ptr %stack.ptr_21) %55 = select i1 %54, i1 0, i1 1 br i1 %55, label %if_4, label %end_if_5 if_4: %56 = getelementptr [1 x i32], ptr %stack.ptr_22, i32 0, i32 0 %57 = getelementptr [1 x i32], ptr %49, i32 0, i32 0 %58 = load i32, ptr %57 store i32 %58, ptr %56 %59 = getelementptr [1 x i32], ptr %stack.ptr_23, i32 0, i32 0 %60 = getelementptr [1 x i32], ptr %49, i32 0, i32 0 %61 = load i32, ptr %60 store i32 %61, ptr %59 %62 = getelementptr %program, ptr %arg_0, i32 0, i32 4 call ccc void @eclair_btree_lower_bound_0(ptr %62, ptr %stack.ptr_22, ptr %stack.ptr_24) %63 = getelementptr %program, ptr %arg_0, i32 0, i32 4 call ccc void @eclair_btree_upper_bound_0(ptr %63, ptr %stack.ptr_23, ptr %stack.ptr_25) br label %loop_4 loop_4: %64 = call ccc i1 @eclair_btree_iterator_is_equal_0(ptr %stack.ptr_24, ptr %stack.ptr_25) br i1 %64, label %if_5, label %end_if_4 if_5: br label %range_query.end_3 end_if_4: %65 = call ccc ptr @eclair_btree_iterator_current_0(ptr %stack.ptr_24) %66 = getelementptr [1 x i32], ptr %stack.ptr_26, i32 0, i32 0 %67 = getelementptr [1 x i32], ptr %49, i32 0, i32 0 %68 = load i32, ptr %67 store i32 %68, ptr %66 %69 = getelementptr %program, ptr %arg_0, i32 0, i32 7 %70 = call ccc i1 @eclair_btree_insert_value_0(ptr %69, ptr %stack.ptr_26) call ccc void @eclair_btree_iterator_next_0(ptr %stack.ptr_24) br label %loop_4 range_query.end_3: br label %end_if_5 end_if_5: call ccc void @eclair_btree_iterator_next_0(ptr %stack.ptr_19) br label %loop_3 range_query.end_2: %71 = getelementptr %program, ptr %arg_0, i32 0, i32 7 %72 = call ccc i1 @eclair_btree_is_empty_0(ptr %71) br i1 %72, label %if_6, label %end_if_7 if_6: %73 = getelementptr %program, ptr %arg_0, i32 0, i32 8 %74 = call ccc i1 @eclair_btree_is_empty_0(ptr %73) br i1 %74, label %if_7, label %end_if_6 if_7: br label %loop.end end_if_6: br label %end_if_7 end_if_7: %75 = getelementptr %program, ptr %arg_0, i32 0, i32 8 call ccc void @eclair_btree_begin_0(ptr %75, ptr %stack.ptr_27) %76 = getelementptr %program, ptr %arg_0, i32 0, i32 8 call ccc void @eclair_btree_end_0(ptr %76, ptr %stack.ptr_28) %77 = getelementptr %program, ptr %arg_0, i32 0, i32 3 call ccc void @eclair_btree_insert_range_c_new_c(ptr %77, ptr %stack.ptr_27, ptr %stack.ptr_28) %78 = getelementptr %program, ptr %arg_0, i32 0, i32 8 %79 = getelementptr %program, ptr %arg_0, i32 0, i32 6 call ccc void @eclair_btree_swap_0(ptr %78, ptr %79) %80 = getelementptr %program, ptr %arg_0, i32 0, i32 7 call ccc void @eclair_btree_begin_0(ptr %80, ptr %stack.ptr_29) %81 = getelementptr %program, ptr %arg_0, i32 0, i32 7 call ccc void @eclair_btree_end_0(ptr %81, ptr %stack.ptr_30) %82 = getelementptr %program, ptr %arg_0, i32 0, i32 2 call ccc void @eclair_btree_insert_range_b_new_b(ptr %82, ptr %stack.ptr_29, ptr %stack.ptr_30) %83 = getelementptr %program, ptr %arg_0, i32 0, i32 7 %84 = getelementptr %program, ptr %arg_0, i32 0, i32 5 call ccc void @eclair_btree_swap_0(ptr %83, ptr %84) br label %loop_0 loop.end: %85 = getelementptr [1 x i32], ptr %stack.ptr_31, i32 0, i32 0 store i32 0, ptr %85 %86 = getelementptr [1 x i32], ptr %stack.ptr_32, i32 0, i32 0 store i32 4294967295, ptr %86 %87 = getelementptr %program, ptr %arg_0, i32 0, i32 2 call ccc void @eclair_btree_lower_bound_0(ptr %87, ptr %stack.ptr_31, ptr %stack.ptr_33) %88 = getelementptr %program, ptr %arg_0, i32 0, i32 2 call ccc void @eclair_btree_upper_bound_0(ptr %88, ptr %stack.ptr_32, ptr %stack.ptr_34) br label %loop_5 loop_5: %89 = call ccc i1 @eclair_btree_iterator_is_equal_0(ptr %stack.ptr_33, ptr %stack.ptr_34) br i1 %89, label %if_8, label %end_if_8 if_8: br label %range_query.end_4 end_if_8: %90 = call ccc ptr @eclair_btree_iterator_current_0(ptr %stack.ptr_33) %91 = getelementptr [1 x i32], ptr %stack.ptr_35, i32 0, i32 0 %92 = getelementptr [1 x i32], ptr %90, i32 0, i32 0 %93 = load i32, ptr %92 store i32 %93, ptr %91 %94 = getelementptr [1 x i32], ptr %stack.ptr_36, i32 0, i32 0 %95 = getelementptr [1 x i32], ptr %90, i32 0, i32 0 %96 = load i32, ptr %95 store i32 %96, ptr %94 %97 = getelementptr %program, ptr %arg_0, i32 0, i32 3 call ccc void @eclair_btree_lower_bound_0(ptr %97, ptr %stack.ptr_35, ptr %stack.ptr_37) %98 = getelementptr %program, ptr %arg_0, i32 0, i32 3 call ccc void @eclair_btree_upper_bound_0(ptr %98, ptr %stack.ptr_36, ptr %stack.ptr_38) br label %loop_6 loop_6: %99 = call ccc i1 @eclair_btree_iterator_is_equal_0(ptr %stack.ptr_37, ptr %stack.ptr_38) br i1 %99, label %if_9, label %end_if_9 if_9: br label %range_query.end_5 end_if_9: %100 = call ccc ptr @eclair_btree_iterator_current_0(ptr %stack.ptr_37) %101 = getelementptr [1 x i32], ptr %stack.ptr_39, i32 0, i32 0 %102 = getelementptr [1 x i32], ptr %90, i32 0, i32 0 %103 = load i32, ptr %102 store i32 %103, ptr %101 %104 = getelementptr %program, ptr %arg_0, i32 0, i32 1 %105 = call ccc i1 @eclair_btree_insert_value_0(ptr %104, ptr %stack.ptr_39) call ccc void @eclair_btree_iterator_next_0(ptr %stack.ptr_37) br label %loop_6 range_query.end_5: call ccc void @eclair_btree_iterator_next_0(ptr %stack.ptr_33) br label %loop_5 range_query.end_4: ret void } ================================================ FILE: tests/lowering/negation.eclair ================================================ // RUN: split-file %s %t // RUN: %eclair compile --emit ra-transformed %t/program.eclair > %t/actual_ra.out // RUN: diff %t/expected_ra.out %t/actual_ra.out // RUN: %eclair compile --emit eir %t/program.eclair > %t/actual_eir.out // RUN: %extract_snippet %t/actual_eir.out "fn.*eclair_program_run" > %t/actual_eir_snippet.out // RUN: diff %t/expected_eir.out %t/actual_eir_snippet.out // RUN: %eclair compile --emit llvm %t/program.eclair > %t/actual_llvm.out // RUN: %extract_snippet %t/actual_llvm.out "define.*@eclair_program_run" > %t/actual_llvm_snippet.out // RUN: diff %t/expected_llvm.out %t/actual_llvm_snippet.out //--- program.eclair @def fact1(u32) input. @def fact2(u32) input. @def fact3(u32) output. fact3(x) :- fact1(x), !fact1(x), !fact2(x), !fact2(y), y = x + 1. fact3(x) :- fact1(x), fact2(y), !fact1(y). //--- expected_ra.out search fact1 as fact10 do search fact2 as fact21 do if (fact21[0]) ∉ fact1 do project (fact10[0]) into fact3 search fact1 as fact10 do if (fact10[0]) ∉ fact1 do if (fact10[0]) ∉ fact2 do if ((fact10[0] + 1)) ∉ fact2 do project (fact10[0]) into fact3 //--- expected_eir.out export fn eclair_program_run(*Program) -> Void { lower_bound_value = fact1.stack_allocate Value upper_bound_value = fact1.stack_allocate Value lower_bound_value.0 = 0 upper_bound_value.0 = 4294967295 begin_iter = fact1.stack_allocate Iter end_iter = fact1.stack_allocate Iter fact1.iter_lower_bound(FN_ARG[0].1, lower_bound_value, begin_iter) fact1.iter_upper_bound(FN_ARG[0].1, upper_bound_value, end_iter) loop { condition = fact1.iter_is_equal(begin_iter, end_iter) if (condition) { goto range_query.end } current = fact1.iter_current(begin_iter) lower_bound_value_1 = fact2.stack_allocate Value upper_bound_value_1 = fact2.stack_allocate Value lower_bound_value_1.0 = 0 upper_bound_value_1.0 = 4294967295 begin_iter_1 = fact2.stack_allocate Iter end_iter_1 = fact2.stack_allocate Iter fact2.iter_lower_bound(FN_ARG[0].2, lower_bound_value_1, begin_iter_1) fact2.iter_upper_bound(FN_ARG[0].2, upper_bound_value_1, end_iter_1) loop { condition_1 = fact2.iter_is_equal(begin_iter_1, end_iter_1) if (condition_1) { goto range_query.end_1 } current_1 = fact2.iter_current(begin_iter_1) value = fact1.stack_allocate Value value.0 = current_1.0 contains_result = fact1.contains(FN_ARG[0].1, value) condition_2 = not contains_result if (condition_2) { value_1 = fact3.stack_allocate Value value_1.0 = current.0 fact3.insert(FN_ARG[0].3, value_1) } fact2.iter_next(begin_iter_1) } range_query.end_1: fact1.iter_next(begin_iter) } range_query.end: lower_bound_value_2 = fact1.stack_allocate Value upper_bound_value_2 = fact1.stack_allocate Value lower_bound_value_2.0 = 0 upper_bound_value_2.0 = 4294967295 begin_iter_2 = fact1.stack_allocate Iter end_iter_2 = fact1.stack_allocate Iter fact1.iter_lower_bound(FN_ARG[0].1, lower_bound_value_2, begin_iter_2) fact1.iter_upper_bound(FN_ARG[0].1, upper_bound_value_2, end_iter_2) loop { condition_3 = fact1.iter_is_equal(begin_iter_2, end_iter_2) if (condition_3) { goto range_query.end_2 } current_2 = fact1.iter_current(begin_iter_2) value_2 = fact1.stack_allocate Value value_2.0 = current_2.0 contains_result_1 = fact1.contains(FN_ARG[0].1, value_2) condition_4 = not contains_result_1 if (condition_4) { value_3 = fact2.stack_allocate Value value_3.0 = current_2.0 contains_result_2 = fact2.contains(FN_ARG[0].2, value_3) condition_5 = not contains_result_2 if (condition_5) { value_4 = fact2.stack_allocate Value value_4.0 = (current_2.0 + 1) contains_result_3 = fact2.contains(FN_ARG[0].2, value_4) condition_6 = not contains_result_3 if (condition_6) { value_5 = fact3.stack_allocate Value value_5.0 = current_2.0 fact3.insert(FN_ARG[0].3, value_5) } } } fact1.iter_next(begin_iter_2) } range_query.end_2: } //--- expected_llvm.out define external ccc void @eclair_program_run(ptr %arg_0) "wasm-export-name"="eclair_program_run" { start: %stack.ptr_0 = alloca [1 x i32], i32 1 %stack.ptr_1 = alloca [1 x i32], i32 1 %stack.ptr_2 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_3 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_4 = alloca [1 x i32], i32 1 %stack.ptr_5 = alloca [1 x i32], i32 1 %stack.ptr_6 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_7 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_8 = alloca [1 x i32], i32 1 %stack.ptr_9 = alloca [1 x i32], i32 1 %stack.ptr_10 = alloca [1 x i32], i32 1 %stack.ptr_11 = alloca [1 x i32], i32 1 %stack.ptr_12 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_13 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_14 = alloca [1 x i32], i32 1 %stack.ptr_15 = alloca [1 x i32], i32 1 %stack.ptr_16 = alloca [1 x i32], i32 1 %stack.ptr_17 = alloca [1 x i32], i32 1 %0 = getelementptr [1 x i32], ptr %stack.ptr_0, i32 0, i32 0 store i32 0, ptr %0 %1 = getelementptr [1 x i32], ptr %stack.ptr_1, i32 0, i32 0 store i32 4294967295, ptr %1 %2 = getelementptr %program, ptr %arg_0, i32 0, i32 1 call ccc void @eclair_btree_lower_bound_0(ptr %2, ptr %stack.ptr_0, ptr %stack.ptr_2) %3 = getelementptr %program, ptr %arg_0, i32 0, i32 1 call ccc void @eclair_btree_upper_bound_0(ptr %3, ptr %stack.ptr_1, ptr %stack.ptr_3) br label %loop_0 loop_0: %4 = call ccc i1 @eclair_btree_iterator_is_equal_0(ptr %stack.ptr_2, ptr %stack.ptr_3) br i1 %4, label %if_0, label %end_if_0 if_0: br label %range_query.end end_if_0: %5 = call ccc ptr @eclair_btree_iterator_current_0(ptr %stack.ptr_2) %6 = getelementptr [1 x i32], ptr %stack.ptr_4, i32 0, i32 0 store i32 0, ptr %6 %7 = getelementptr [1 x i32], ptr %stack.ptr_5, i32 0, i32 0 store i32 4294967295, ptr %7 %8 = getelementptr %program, ptr %arg_0, i32 0, i32 2 call ccc void @eclair_btree_lower_bound_0(ptr %8, ptr %stack.ptr_4, ptr %stack.ptr_6) %9 = getelementptr %program, ptr %arg_0, i32 0, i32 2 call ccc void @eclair_btree_upper_bound_0(ptr %9, ptr %stack.ptr_5, ptr %stack.ptr_7) br label %loop_1 loop_1: %10 = call ccc i1 @eclair_btree_iterator_is_equal_0(ptr %stack.ptr_6, ptr %stack.ptr_7) br i1 %10, label %if_1, label %end_if_1 if_1: br label %range_query.end_1 end_if_1: %11 = call ccc ptr @eclair_btree_iterator_current_0(ptr %stack.ptr_6) %12 = getelementptr [1 x i32], ptr %stack.ptr_8, i32 0, i32 0 %13 = getelementptr [1 x i32], ptr %11, i32 0, i32 0 %14 = load i32, ptr %13 store i32 %14, ptr %12 %15 = getelementptr %program, ptr %arg_0, i32 0, i32 1 %16 = call ccc i1 @eclair_btree_contains_0(ptr %15, ptr %stack.ptr_8) %17 = select i1 %16, i1 0, i1 1 br i1 %17, label %if_2, label %end_if_2 if_2: %18 = getelementptr [1 x i32], ptr %stack.ptr_9, i32 0, i32 0 %19 = getelementptr [1 x i32], ptr %5, i32 0, i32 0 %20 = load i32, ptr %19 store i32 %20, ptr %18 %21 = getelementptr %program, ptr %arg_0, i32 0, i32 3 %22 = call ccc i1 @eclair_btree_insert_value_0(ptr %21, ptr %stack.ptr_9) br label %end_if_2 end_if_2: call ccc void @eclair_btree_iterator_next_0(ptr %stack.ptr_6) br label %loop_1 range_query.end_1: call ccc void @eclair_btree_iterator_next_0(ptr %stack.ptr_2) br label %loop_0 range_query.end: %23 = getelementptr [1 x i32], ptr %stack.ptr_10, i32 0, i32 0 store i32 0, ptr %23 %24 = getelementptr [1 x i32], ptr %stack.ptr_11, i32 0, i32 0 store i32 4294967295, ptr %24 %25 = getelementptr %program, ptr %arg_0, i32 0, i32 1 call ccc void @eclair_btree_lower_bound_0(ptr %25, ptr %stack.ptr_10, ptr %stack.ptr_12) %26 = getelementptr %program, ptr %arg_0, i32 0, i32 1 call ccc void @eclair_btree_upper_bound_0(ptr %26, ptr %stack.ptr_11, ptr %stack.ptr_13) br label %loop_2 loop_2: %27 = call ccc i1 @eclair_btree_iterator_is_equal_0(ptr %stack.ptr_12, ptr %stack.ptr_13) br i1 %27, label %if_3, label %end_if_3 if_3: br label %range_query.end_2 end_if_3: %28 = call ccc ptr @eclair_btree_iterator_current_0(ptr %stack.ptr_12) %29 = getelementptr [1 x i32], ptr %stack.ptr_14, i32 0, i32 0 %30 = getelementptr [1 x i32], ptr %28, i32 0, i32 0 %31 = load i32, ptr %30 store i32 %31, ptr %29 %32 = getelementptr %program, ptr %arg_0, i32 0, i32 1 %33 = call ccc i1 @eclair_btree_contains_0(ptr %32, ptr %stack.ptr_14) %34 = select i1 %33, i1 0, i1 1 br i1 %34, label %if_4, label %end_if_6 if_4: %35 = getelementptr [1 x i32], ptr %stack.ptr_15, i32 0, i32 0 %36 = getelementptr [1 x i32], ptr %28, i32 0, i32 0 %37 = load i32, ptr %36 store i32 %37, ptr %35 %38 = getelementptr %program, ptr %arg_0, i32 0, i32 2 %39 = call ccc i1 @eclair_btree_contains_0(ptr %38, ptr %stack.ptr_15) %40 = select i1 %39, i1 0, i1 1 br i1 %40, label %if_5, label %end_if_5 if_5: %41 = getelementptr [1 x i32], ptr %stack.ptr_16, i32 0, i32 0 %42 = getelementptr [1 x i32], ptr %28, i32 0, i32 0 %43 = load i32, ptr %42 %44 = add i32 %43, 1 store i32 %44, ptr %41 %45 = getelementptr %program, ptr %arg_0, i32 0, i32 2 %46 = call ccc i1 @eclair_btree_contains_0(ptr %45, ptr %stack.ptr_16) %47 = select i1 %46, i1 0, i1 1 br i1 %47, label %if_6, label %end_if_4 if_6: %48 = getelementptr [1 x i32], ptr %stack.ptr_17, i32 0, i32 0 %49 = getelementptr [1 x i32], ptr %28, i32 0, i32 0 %50 = load i32, ptr %49 store i32 %50, ptr %48 %51 = getelementptr %program, ptr %arg_0, i32 0, i32 3 %52 = call ccc i1 @eclair_btree_insert_value_0(ptr %51, ptr %stack.ptr_17) br label %end_if_4 end_if_4: br label %end_if_5 end_if_5: br label %end_if_6 end_if_6: call ccc void @eclair_btree_iterator_next_0(ptr %stack.ptr_12) br label %loop_2 range_query.end_2: ret void } ================================================ FILE: tests/lowering/negation_with_wildcards.eclair ================================================ // RUN: split-file %s %t // RUN: %eclair compile --emit ra-transformed %t/program.eclair > %t/actual_ra.out // RUN: diff %t/expected_ra.out %t/actual_ra.out // RUN: %eclair compile --emit eir %t/program.eclair > %t/actual_eir.out // RUN: %extract_snippet %t/actual_eir.out "fn.*eclair_program_run" > %t/actual_eir_snippet.out // RUN: diff %t/expected_eir.out %t/actual_eir_snippet.out // RUN: %eclair compile --emit llvm %t/program.eclair > %t/actual_llvm.out // RUN: %extract_snippet %t/actual_llvm.out "define.*@eclair_program_run" > %t/actual_llvm_snippet.out // RUN: diff %t/expected_llvm.out %t/actual_llvm_snippet.out //--- program.eclair @def first(u32). @def second(u32, u32). @def third(u32, u32) output. first(1). second(2, 3). third(x, x) :- first(x), !second(_, x). //--- expected_ra.out project (2, 3) into second project (1) into first search first as first0 do if (undefined, first0[0]) ∉ second do project (first0[0], first0[0]) into third //--- expected_eir.out export fn eclair_program_run(*Program) -> Void { value = second.stack_allocate Value value.0 = 2 value.1 = 3 second.insert(FN_ARG[0].2, value) value_1 = first.stack_allocate Value value_1.0 = 1 first.insert(FN_ARG[0].1, value_1) lower_bound_value = first.stack_allocate Value upper_bound_value = first.stack_allocate Value lower_bound_value.0 = 0 upper_bound_value.0 = 4294967295 begin_iter = first.stack_allocate Iter end_iter = first.stack_allocate Iter first.iter_lower_bound(FN_ARG[0].1, lower_bound_value, begin_iter) first.iter_upper_bound(FN_ARG[0].1, upper_bound_value, end_iter) loop { condition = first.iter_is_equal(begin_iter, end_iter) if (condition) { goto range_query.end } current = first.iter_current(begin_iter) lower_bound_value_1 = second.stack_allocate Value lower_bound_value_1.0 = 0 lower_bound_value_1.1 = current.0 upper_bound_value_1 = second.stack_allocate Value upper_bound_value_1.0 = 4294967295 upper_bound_value_1.1 = current.0 begin_iter_1 = second.stack_allocate Iter end_iter_1 = second.stack_allocate Iter second.iter_lower_bound(FN_ARG[0].2, lower_bound_value_1, begin_iter_1) second.iter_upper_bound(FN_ARG[0].2, upper_bound_value_1, end_iter_1) condition_1 = second.iter_is_equal(begin_iter_1, end_iter_1) if (condition_1) { value_2 = third.stack_allocate Value value_2.0 = current.0 value_2.1 = current.0 third.insert(FN_ARG[0].3, value_2) } first.iter_next(begin_iter) } range_query.end: } //--- expected_llvm.out define external ccc void @eclair_program_run(ptr %arg_0) "wasm-export-name"="eclair_program_run" { start: %stack.ptr_0 = alloca [2 x i32], i32 1 %stack.ptr_1 = alloca [1 x i32], i32 1 %stack.ptr_2 = alloca [1 x i32], i32 1 %stack.ptr_3 = alloca [1 x i32], i32 1 %stack.ptr_4 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_5 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_6 = alloca [2 x i32], i32 1 %stack.ptr_7 = alloca [2 x i32], i32 1 %stack.ptr_8 = alloca %btree_iterator_t_1, i32 1 %stack.ptr_9 = alloca %btree_iterator_t_1, i32 1 %stack.ptr_10 = alloca [2 x i32], i32 1 %0 = getelementptr [2 x i32], ptr %stack.ptr_0, i32 0, i32 0 store i32 2, ptr %0 %1 = getelementptr [2 x i32], ptr %stack.ptr_0, i32 0, i32 1 store i32 3, ptr %1 %2 = getelementptr %program, ptr %arg_0, i32 0, i32 2 %3 = call ccc i1 @eclair_btree_insert_value_1(ptr %2, ptr %stack.ptr_0) %4 = getelementptr [1 x i32], ptr %stack.ptr_1, i32 0, i32 0 store i32 1, ptr %4 %5 = getelementptr %program, ptr %arg_0, i32 0, i32 1 %6 = call ccc i1 @eclair_btree_insert_value_0(ptr %5, ptr %stack.ptr_1) %7 = getelementptr [1 x i32], ptr %stack.ptr_2, i32 0, i32 0 store i32 0, ptr %7 %8 = getelementptr [1 x i32], ptr %stack.ptr_3, i32 0, i32 0 store i32 4294967295, ptr %8 %9 = getelementptr %program, ptr %arg_0, i32 0, i32 1 call ccc void @eclair_btree_lower_bound_0(ptr %9, ptr %stack.ptr_2, ptr %stack.ptr_4) %10 = getelementptr %program, ptr %arg_0, i32 0, i32 1 call ccc void @eclair_btree_upper_bound_0(ptr %10, ptr %stack.ptr_3, ptr %stack.ptr_5) br label %loop_0 loop_0: %11 = call ccc i1 @eclair_btree_iterator_is_equal_0(ptr %stack.ptr_4, ptr %stack.ptr_5) br i1 %11, label %if_0, label %end_if_0 if_0: br label %range_query.end end_if_0: %12 = call ccc ptr @eclair_btree_iterator_current_0(ptr %stack.ptr_4) %13 = getelementptr [2 x i32], ptr %stack.ptr_6, i32 0, i32 0 store i32 0, ptr %13 %14 = getelementptr [2 x i32], ptr %stack.ptr_6, i32 0, i32 1 %15 = getelementptr [1 x i32], ptr %12, i32 0, i32 0 %16 = load i32, ptr %15 store i32 %16, ptr %14 %17 = getelementptr [2 x i32], ptr %stack.ptr_7, i32 0, i32 0 store i32 4294967295, ptr %17 %18 = getelementptr [2 x i32], ptr %stack.ptr_7, i32 0, i32 1 %19 = getelementptr [1 x i32], ptr %12, i32 0, i32 0 %20 = load i32, ptr %19 store i32 %20, ptr %18 %21 = getelementptr %program, ptr %arg_0, i32 0, i32 2 call ccc void @eclair_btree_lower_bound_1(ptr %21, ptr %stack.ptr_6, ptr %stack.ptr_8) %22 = getelementptr %program, ptr %arg_0, i32 0, i32 2 call ccc void @eclair_btree_upper_bound_1(ptr %22, ptr %stack.ptr_7, ptr %stack.ptr_9) %23 = call ccc i1 @eclair_btree_iterator_is_equal_1(ptr %stack.ptr_8, ptr %stack.ptr_9) br i1 %23, label %if_1, label %end_if_1 if_1: %24 = getelementptr [2 x i32], ptr %stack.ptr_10, i32 0, i32 0 %25 = getelementptr [1 x i32], ptr %12, i32 0, i32 0 %26 = load i32, ptr %25 store i32 %26, ptr %24 %27 = getelementptr [2 x i32], ptr %stack.ptr_10, i32 0, i32 1 %28 = getelementptr [1 x i32], ptr %12, i32 0, i32 0 %29 = load i32, ptr %28 store i32 %29, ptr %27 %30 = getelementptr %program, ptr %arg_0, i32 0, i32 3 %31 = call ccc i1 @eclair_btree_insert_value_2(ptr %30, ptr %stack.ptr_10) br label %end_if_1 end_if_1: call ccc void @eclair_btree_iterator_next_0(ptr %stack.ptr_4) br label %loop_0 range_query.end: ret void } ================================================ FILE: tests/lowering/no_top_level_facts.eclair ================================================ // RUN: split-file %s %t // RUN: %eclair compile --emit ra-transformed %t/program.eclair > %t/actual_ra.out // RUN: diff %t/expected_ra.out %t/actual_ra.out // RUN: %eclair compile --emit eir %t/program.eclair > %t/actual_eir.out // RUN: diff %t/expected_eir.out %t/actual_eir.out // RUN: %eclair compile --emit llvm %t/program.eclair > %t/actual_llvm.out // RUN: %extract_snippet %t/actual_llvm.out "program = " > %t/actual_eclair_program_type.out // RUN: %extract_snippet %t/actual_llvm.out "define.*@eclair_program_init" > %t/actual_eclair_program_init_llvm.out // RUN: %extract_snippet %t/actual_llvm.out "define.*@eclair_program_destroy" > %t/actual_eclair_program_destroy_llvm.out // RUN: %extract_snippet %t/actual_llvm.out "define.*@eclair_program_run" > %t/actual_eclair_program_run_llvm.out // RUN: diff %t/expected_eclair_program_type.out %t/actual_eclair_program_type.out // RUN: diff %t/expected_eclair_program_init_llvm.out %t/actual_eclair_program_init_llvm.out // RUN: diff %t/expected_eclair_program_destroy_llvm.out %t/actual_eclair_program_destroy_llvm.out // RUN: diff %t/expected_eclair_program_run_llvm.out %t/actual_eclair_program_run_llvm.out // RUN: %extract_snippet %t/actual_llvm.out "define.*@eclair_add_facts" > %t/actual_eclair_add_facts_llvm.out // RUN: %extract_snippet %t/actual_llvm.out "define.*@eclair_get_facts" > %t/actual_eclair_get_facts_llvm.out // RUN: diff %t/expected_eclair_add_facts_llvm.out %t/actual_eclair_add_facts_llvm.out // RUN: diff %t/expected_eclair_get_facts_llvm.out %t/actual_eclair_get_facts_llvm.out //--- program.eclair @def edge(u32, u32) input. @def path(u32, u32) output. path(x, y) :- edge(x, y). path(x, z) :- edge(x, y), path(y, z). //--- expected_ra.out search edge as edge0 do project (edge0[0], edge0[1]) into path merge path delta_path loop do purge new_path search edge as edge0 do search delta_path as delta_path1 where (edge0[1] = delta_path1[0]) do if (edge0[0], delta_path1[1]) ∉ path do project (edge0[0], delta_path1[1]) into new_path exit if counttuples(new_path) = 0 merge new_path path swap new_path delta_path //--- expected_eir.out declare_type Program { symbol_table delta_path btree(num_columns=2, index=[0,1], block_size=256, search_type=linear) edge btree(num_columns=2, index=[0,1], block_size=256, search_type=linear) new_path btree(num_columns=2, index=[0,1], block_size=256, search_type=linear) path btree(num_columns=2, index=[0,1], block_size=256, search_type=linear) } export fn eclair_program_init() -> *Program { program = heap_allocate_program symbol_table.init(program.0) delta_path.init_empty(program.1) edge.init_empty(program.2) new_path.init_empty(program.3) path.init_empty(program.4) symbol_table.insert(program.0, "edge") symbol_table.insert(program.0, "path") return program } export fn eclair_program_destroy(*Program) -> Void { symbol_table.destroy(FN_ARG[0].0) delta_path.destroy(FN_ARG[0].1) edge.destroy(FN_ARG[0].2) new_path.destroy(FN_ARG[0].3) path.destroy(FN_ARG[0].4) free_program(FN_ARG[0]) } export fn eclair_program_run(*Program) -> Void { lower_bound_value = edge.stack_allocate Value upper_bound_value = edge.stack_allocate Value lower_bound_value.0 = 0 lower_bound_value.1 = 0 upper_bound_value.0 = 4294967295 upper_bound_value.1 = 4294967295 begin_iter = edge.stack_allocate Iter end_iter = edge.stack_allocate Iter edge.iter_lower_bound(FN_ARG[0].2, lower_bound_value, begin_iter) edge.iter_upper_bound(FN_ARG[0].2, upper_bound_value, end_iter) loop { condition = edge.iter_is_equal(begin_iter, end_iter) if (condition) { goto range_query.end } current = edge.iter_current(begin_iter) value = path.stack_allocate Value value.0 = current.0 value.1 = current.1 path.insert(FN_ARG[0].4, value) edge.iter_next(begin_iter) } range_query.end: begin_iter_1 = path.stack_allocate Iter end_iter_1 = path.stack_allocate Iter path.iter_begin(FN_ARG[0].4, begin_iter_1) path.iter_end(FN_ARG[0].4, end_iter_1) delta_path.insert_range(FN_ARG[0].1, begin_iter_1, end_iter_1) loop { new_path.purge(FN_ARG[0].3) lower_bound_value_1 = edge.stack_allocate Value upper_bound_value_1 = edge.stack_allocate Value lower_bound_value_1.0 = 0 lower_bound_value_1.1 = 0 upper_bound_value_1.0 = 4294967295 upper_bound_value_1.1 = 4294967295 begin_iter_2 = edge.stack_allocate Iter end_iter_2 = edge.stack_allocate Iter edge.iter_lower_bound(FN_ARG[0].2, lower_bound_value_1, begin_iter_2) edge.iter_upper_bound(FN_ARG[0].2, upper_bound_value_1, end_iter_2) loop { condition_1 = edge.iter_is_equal(begin_iter_2, end_iter_2) if (condition_1) { goto range_query.end_1 } current_1 = edge.iter_current(begin_iter_2) lower_bound_value_2 = path.stack_allocate Value upper_bound_value_2 = path.stack_allocate Value lower_bound_value_2.0 = current_1.1 lower_bound_value_2.1 = 0 upper_bound_value_2.0 = current_1.1 upper_bound_value_2.1 = 4294967295 begin_iter_3 = path.stack_allocate Iter end_iter_3 = path.stack_allocate Iter delta_path.iter_lower_bound(FN_ARG[0].1, lower_bound_value_2, begin_iter_3) delta_path.iter_upper_bound(FN_ARG[0].1, upper_bound_value_2, end_iter_3) loop { condition_2 = delta_path.iter_is_equal(begin_iter_3, end_iter_3) if (condition_2) { goto range_query.end_2 } current_2 = delta_path.iter_current(begin_iter_3) value_1 = path.stack_allocate Value value_1.0 = current_1.0 value_1.1 = current_2.1 contains_result = path.contains(FN_ARG[0].4, value_1) condition_3 = not contains_result if (condition_3) { value_2 = path.stack_allocate Value value_2.0 = current_1.0 value_2.1 = current_2.1 new_path.insert(FN_ARG[0].3, value_2) } delta_path.iter_next(begin_iter_3) } range_query.end_2: edge.iter_next(begin_iter_2) } range_query.end_1: condition_4 = new_path.is_empty(FN_ARG[0].3) if (condition_4) { goto loop.end } begin_iter_4 = path.stack_allocate Iter end_iter_4 = path.stack_allocate Iter new_path.iter_begin(FN_ARG[0].3, begin_iter_4) new_path.iter_end(FN_ARG[0].3, end_iter_4) path.insert_range(FN_ARG[0].4, begin_iter_4, end_iter_4) new_path.swap(FN_ARG[0].3, FN_ARG[0].1) } loop.end: } //--- expected_eclair_program_type.out %program = type {%symbol_table, %btree_t_0, %btree_t_0, %btree_t_0, %btree_t_0} //--- expected_eclair_program_init_llvm.out define external ccc ptr @eclair_program_init() "wasm-export-name"="eclair_program_init" { start: %stack.ptr_0 = alloca %symbol_t, i32 1 %stack.ptr_1 = alloca %symbol_t, i32 1 %0 = call ccc ptr @malloc(i32 1624) %1 = getelementptr %program, ptr %0, i32 0, i32 0 call ccc void @eclair_symbol_table_init(ptr %1) %2 = getelementptr %program, ptr %0, i32 0, i32 1 call ccc void @eclair_btree_init_empty_0(ptr %2) %3 = getelementptr %program, ptr %0, i32 0, i32 2 call ccc void @eclair_btree_init_empty_0(ptr %3) %4 = getelementptr %program, ptr %0, i32 0, i32 3 call ccc void @eclair_btree_init_empty_0(ptr %4) %5 = getelementptr %program, ptr %0, i32 0, i32 4 call ccc void @eclair_btree_init_empty_0(ptr %5) %6 = getelementptr %program, ptr %0, i32 0, i32 0 %7 = getelementptr inbounds [5 x i8], ptr @string_literal_0, i32 0, i32 0 %8 = zext i32 4 to i64 %9 = call ccc ptr @malloc(i32 4) call ccc void @llvm.memcpy.p0i8.p0i8.i64(ptr %9, ptr %7, i64 %8, i1 0) call ccc void @eclair_symbol_init(ptr %stack.ptr_0, i32 4, ptr %9) %10 = call ccc i32 @eclair_symbol_table_find_or_insert(ptr %6, ptr %stack.ptr_0) %11 = getelementptr %program, ptr %0, i32 0, i32 0 %12 = getelementptr inbounds [5 x i8], ptr @string_literal_1, i32 0, i32 0 %13 = zext i32 4 to i64 %14 = call ccc ptr @malloc(i32 4) call ccc void @llvm.memcpy.p0i8.p0i8.i64(ptr %14, ptr %12, i64 %13, i1 0) call ccc void @eclair_symbol_init(ptr %stack.ptr_1, i32 4, ptr %14) %15 = call ccc i32 @eclair_symbol_table_find_or_insert(ptr %11, ptr %stack.ptr_1) ret ptr %0 } //--- expected_eclair_program_destroy_llvm.out define external ccc void @eclair_program_destroy(ptr %arg_0) "wasm-export-name"="eclair_program_destroy" { start: %0 = getelementptr %program, ptr %arg_0, i32 0, i32 0 call ccc void @eclair_symbol_table_destroy(ptr %0) %1 = getelementptr %program, ptr %arg_0, i32 0, i32 1 call ccc void @eclair_btree_destroy_0(ptr %1) %2 = getelementptr %program, ptr %arg_0, i32 0, i32 2 call ccc void @eclair_btree_destroy_0(ptr %2) %3 = getelementptr %program, ptr %arg_0, i32 0, i32 3 call ccc void @eclair_btree_destroy_0(ptr %3) %4 = getelementptr %program, ptr %arg_0, i32 0, i32 4 call ccc void @eclair_btree_destroy_0(ptr %4) call ccc void @free(ptr %arg_0) ret void } //--- expected_eclair_program_run_llvm.out define external ccc void @eclair_program_run(ptr %arg_0) "wasm-export-name"="eclair_program_run" { start: %stack.ptr_0 = alloca [2 x i32], i32 1 %stack.ptr_1 = alloca [2 x i32], i32 1 %stack.ptr_2 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_3 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_4 = alloca [2 x i32], i32 1 %stack.ptr_5 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_6 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_7 = alloca [2 x i32], i32 1 %stack.ptr_8 = alloca [2 x i32], i32 1 %stack.ptr_9 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_10 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_11 = alloca [2 x i32], i32 1 %stack.ptr_12 = alloca [2 x i32], i32 1 %stack.ptr_13 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_14 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_15 = alloca [2 x i32], i32 1 %stack.ptr_16 = alloca [2 x i32], i32 1 %stack.ptr_17 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_18 = alloca %btree_iterator_t_0, i32 1 %0 = getelementptr [2 x i32], ptr %stack.ptr_0, i32 0, i32 0 store i32 0, ptr %0 %1 = getelementptr [2 x i32], ptr %stack.ptr_0, i32 0, i32 1 store i32 0, ptr %1 %2 = getelementptr [2 x i32], ptr %stack.ptr_1, i32 0, i32 0 store i32 4294967295, ptr %2 %3 = getelementptr [2 x i32], ptr %stack.ptr_1, i32 0, i32 1 store i32 4294967295, ptr %3 %4 = getelementptr %program, ptr %arg_0, i32 0, i32 2 call ccc void @eclair_btree_lower_bound_0(ptr %4, ptr %stack.ptr_0, ptr %stack.ptr_2) %5 = getelementptr %program, ptr %arg_0, i32 0, i32 2 call ccc void @eclair_btree_upper_bound_0(ptr %5, ptr %stack.ptr_1, ptr %stack.ptr_3) br label %loop_0 loop_0: %6 = call ccc i1 @eclair_btree_iterator_is_equal_0(ptr %stack.ptr_2, ptr %stack.ptr_3) br i1 %6, label %if_0, label %end_if_0 if_0: br label %range_query.end end_if_0: %7 = call ccc ptr @eclair_btree_iterator_current_0(ptr %stack.ptr_2) %8 = getelementptr [2 x i32], ptr %stack.ptr_4, i32 0, i32 0 %9 = getelementptr [2 x i32], ptr %7, i32 0, i32 0 %10 = load i32, ptr %9 store i32 %10, ptr %8 %11 = getelementptr [2 x i32], ptr %stack.ptr_4, i32 0, i32 1 %12 = getelementptr [2 x i32], ptr %7, i32 0, i32 1 %13 = load i32, ptr %12 store i32 %13, ptr %11 %14 = getelementptr %program, ptr %arg_0, i32 0, i32 4 %15 = call ccc i1 @eclair_btree_insert_value_0(ptr %14, ptr %stack.ptr_4) call ccc void @eclair_btree_iterator_next_0(ptr %stack.ptr_2) br label %loop_0 range_query.end: %16 = getelementptr %program, ptr %arg_0, i32 0, i32 4 call ccc void @eclair_btree_begin_0(ptr %16, ptr %stack.ptr_5) %17 = getelementptr %program, ptr %arg_0, i32 0, i32 4 call ccc void @eclair_btree_end_0(ptr %17, ptr %stack.ptr_6) %18 = getelementptr %program, ptr %arg_0, i32 0, i32 1 call ccc void @eclair_btree_insert_range_delta_path_path(ptr %18, ptr %stack.ptr_5, ptr %stack.ptr_6) br label %loop_1 loop_1: %19 = getelementptr %program, ptr %arg_0, i32 0, i32 3 call ccc void @eclair_btree_clear_0(ptr %19) %20 = getelementptr [2 x i32], ptr %stack.ptr_7, i32 0, i32 0 store i32 0, ptr %20 %21 = getelementptr [2 x i32], ptr %stack.ptr_7, i32 0, i32 1 store i32 0, ptr %21 %22 = getelementptr [2 x i32], ptr %stack.ptr_8, i32 0, i32 0 store i32 4294967295, ptr %22 %23 = getelementptr [2 x i32], ptr %stack.ptr_8, i32 0, i32 1 store i32 4294967295, ptr %23 %24 = getelementptr %program, ptr %arg_0, i32 0, i32 2 call ccc void @eclair_btree_lower_bound_0(ptr %24, ptr %stack.ptr_7, ptr %stack.ptr_9) %25 = getelementptr %program, ptr %arg_0, i32 0, i32 2 call ccc void @eclair_btree_upper_bound_0(ptr %25, ptr %stack.ptr_8, ptr %stack.ptr_10) br label %loop_2 loop_2: %26 = call ccc i1 @eclair_btree_iterator_is_equal_0(ptr %stack.ptr_9, ptr %stack.ptr_10) br i1 %26, label %if_1, label %end_if_1 if_1: br label %range_query.end_1 end_if_1: %27 = call ccc ptr @eclair_btree_iterator_current_0(ptr %stack.ptr_9) %28 = getelementptr [2 x i32], ptr %stack.ptr_11, i32 0, i32 0 %29 = getelementptr [2 x i32], ptr %27, i32 0, i32 1 %30 = load i32, ptr %29 store i32 %30, ptr %28 %31 = getelementptr [2 x i32], ptr %stack.ptr_11, i32 0, i32 1 store i32 0, ptr %31 %32 = getelementptr [2 x i32], ptr %stack.ptr_12, i32 0, i32 0 %33 = getelementptr [2 x i32], ptr %27, i32 0, i32 1 %34 = load i32, ptr %33 store i32 %34, ptr %32 %35 = getelementptr [2 x i32], ptr %stack.ptr_12, i32 0, i32 1 store i32 4294967295, ptr %35 %36 = getelementptr %program, ptr %arg_0, i32 0, i32 1 call ccc void @eclair_btree_lower_bound_0(ptr %36, ptr %stack.ptr_11, ptr %stack.ptr_13) %37 = getelementptr %program, ptr %arg_0, i32 0, i32 1 call ccc void @eclair_btree_upper_bound_0(ptr %37, ptr %stack.ptr_12, ptr %stack.ptr_14) br label %loop_3 loop_3: %38 = call ccc i1 @eclair_btree_iterator_is_equal_0(ptr %stack.ptr_13, ptr %stack.ptr_14) br i1 %38, label %if_2, label %end_if_2 if_2: br label %range_query.end_2 end_if_2: %39 = call ccc ptr @eclair_btree_iterator_current_0(ptr %stack.ptr_13) %40 = getelementptr [2 x i32], ptr %stack.ptr_15, i32 0, i32 0 %41 = getelementptr [2 x i32], ptr %27, i32 0, i32 0 %42 = load i32, ptr %41 store i32 %42, ptr %40 %43 = getelementptr [2 x i32], ptr %stack.ptr_15, i32 0, i32 1 %44 = getelementptr [2 x i32], ptr %39, i32 0, i32 1 %45 = load i32, ptr %44 store i32 %45, ptr %43 %46 = getelementptr %program, ptr %arg_0, i32 0, i32 4 %47 = call ccc i1 @eclair_btree_contains_0(ptr %46, ptr %stack.ptr_15) %48 = select i1 %47, i1 0, i1 1 br i1 %48, label %if_3, label %end_if_3 if_3: %49 = getelementptr [2 x i32], ptr %stack.ptr_16, i32 0, i32 0 %50 = getelementptr [2 x i32], ptr %27, i32 0, i32 0 %51 = load i32, ptr %50 store i32 %51, ptr %49 %52 = getelementptr [2 x i32], ptr %stack.ptr_16, i32 0, i32 1 %53 = getelementptr [2 x i32], ptr %39, i32 0, i32 1 %54 = load i32, ptr %53 store i32 %54, ptr %52 %55 = getelementptr %program, ptr %arg_0, i32 0, i32 3 %56 = call ccc i1 @eclair_btree_insert_value_0(ptr %55, ptr %stack.ptr_16) br label %end_if_3 end_if_3: call ccc void @eclair_btree_iterator_next_0(ptr %stack.ptr_13) br label %loop_3 range_query.end_2: call ccc void @eclair_btree_iterator_next_0(ptr %stack.ptr_9) br label %loop_2 range_query.end_1: %57 = getelementptr %program, ptr %arg_0, i32 0, i32 3 %58 = call ccc i1 @eclair_btree_is_empty_0(ptr %57) br i1 %58, label %if_4, label %end_if_4 if_4: br label %loop.end end_if_4: %59 = getelementptr %program, ptr %arg_0, i32 0, i32 3 call ccc void @eclair_btree_begin_0(ptr %59, ptr %stack.ptr_17) %60 = getelementptr %program, ptr %arg_0, i32 0, i32 3 call ccc void @eclair_btree_end_0(ptr %60, ptr %stack.ptr_18) %61 = getelementptr %program, ptr %arg_0, i32 0, i32 4 call ccc void @eclair_btree_insert_range_path_new_path(ptr %61, ptr %stack.ptr_17, ptr %stack.ptr_18) %62 = getelementptr %program, ptr %arg_0, i32 0, i32 3 %63 = getelementptr %program, ptr %arg_0, i32 0, i32 1 call ccc void @eclair_btree_swap_0(ptr %62, ptr %63) br label %loop_1 loop.end: ret void } //--- expected_eclair_add_facts_llvm.out define external ccc void @eclair_add_facts(ptr %eclair_program_0, i32 %fact_type_0, ptr %memory_0, i32 %fact_count_0) "wasm-export-name"="eclair_add_facts" { start: switch i32 %fact_type_0, label %switch.default_0 [i32 0, label %edge_0] edge_0: %0 = getelementptr %program, ptr %eclair_program_0, i32 0, i32 2 br label %for_begin_0 for_begin_0: %1 = phi i32 [0, %edge_0], [%5, %for_body_0] %2 = icmp ult i32 %1, %fact_count_0 br i1 %2, label %for_body_0, label %for_end_0 for_body_0: %3 = getelementptr [2 x i32], ptr %memory_0, i32 %1 %4 = call ccc i1 @eclair_btree_insert_value_0(ptr %0, ptr %3) %5 = add i32 1, %1 br label %for_begin_0 for_end_0: br label %end_0 switch.default_0: ret void end_0: ret void } //--- expected_eclair_get_facts_llvm.out define external ccc ptr @eclair_get_facts(ptr %eclair_program_0, i32 %fact_type_0) "wasm-export-name"="eclair_get_facts" { start: %stack.ptr_0 = alloca i32, i32 1 %stack.ptr_1 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_2 = alloca %btree_iterator_t_0, i32 1 switch i32 %fact_type_0, label %switch.default_0 [i32 1, label %path_0] path_0: %0 = getelementptr %program, ptr %eclair_program_0, i32 0, i32 4 %1 = call ccc i64 @eclair_btree_size_0(ptr %0) %2 = trunc i64 %1 to i32 %3 = mul i32 %2, 8 %4 = call ccc ptr @malloc(i32 %3) store i32 0, ptr %stack.ptr_0 call ccc void @eclair_btree_begin_0(ptr %0, ptr %stack.ptr_1) call ccc void @eclair_btree_end_0(ptr %0, ptr %stack.ptr_2) br label %while_begin_0 while_begin_0: %5 = call ccc i1 @eclair_btree_iterator_is_equal_0(ptr %stack.ptr_1, ptr %stack.ptr_2) %6 = select i1 %5, i1 0, i1 1 br i1 %6, label %while_body_0, label %while_end_0 while_body_0: %7 = load i32, ptr %stack.ptr_0 %8 = getelementptr [2 x i32], ptr %4, i32 %7 %9 = call ccc ptr @eclair_btree_iterator_current_0(ptr %stack.ptr_1) %10 = getelementptr [2 x i32], ptr %9, i32 0 %11 = load [2 x i32], ptr %10 %12 = getelementptr [2 x i32], ptr %8, i32 0 store [2 x i32] %11, ptr %12 %13 = add i32 %7, 1 store i32 %13, ptr %stack.ptr_0 call ccc void @eclair_btree_iterator_next_0(ptr %stack.ptr_1) br label %while_begin_0 while_end_0: ret ptr %4 switch.default_0: ret ptr zeroinitializer } ================================================ FILE: tests/lowering/recursive_mix_of_rules.eclair ================================================ // RUN: split-file %s %t // RUN: %eclair compile --emit ra-transformed %t/program.eclair > %t/actual_ra.out // RUN: diff %t/expected_ra.out %t/actual_ra.out //--- program.eclair @def edge(u32, u32) input. @def reachable(u32) output. reachable(x) :- edge(x, _). reachable(x) :- edge(_, x). reachable(x) :- edge(x, y), reachable(y). reachable(x) :- edge(y, x), reachable(y). //--- expected_ra.out search edge as edge0 do project (edge0[1]) into reachable search edge as edge0 do project (edge0[0]) into reachable merge reachable delta_reachable loop do purge new_reachable parallel do search edge as edge0 do if (edge0[1]) ∉ reachable do search delta_reachable as delta_reachable1 where (edge0[0] = delta_reachable1[0]) do project (edge0[1]) into new_reachable search edge as edge0 do if (edge0[0]) ∉ reachable do search delta_reachable as delta_reachable1 where (edge0[1] = delta_reachable1[0]) do project (edge0[0]) into new_reachable exit if counttuples(new_reachable) = 0 merge new_reachable reachable swap new_reachable delta_reachable ================================================ FILE: tests/lowering/single_non_recursive_rule.eclair ================================================ // RUN: split-file %s %t // RUN: %eclair compile --emit ra-transformed %t/program.eclair > %t/actual_ra.out // RUN: diff %t/expected_ra.out %t/actual_ra.out // RUN: %eclair compile --emit eir %t/program.eclair > %t/actual_eir.out // RUN: diff %t/expected_eir.out %t/actual_eir.out // RUN: %eclair compile --emit llvm %t/program.eclair > %t/actual_llvm.out // RUN: %extract_snippet %t/actual_llvm.out "program = " > %t/actual_eclair_program_type.out // RUN: %extract_snippet %t/actual_llvm.out "@eclair_program_init" > %t/actual_eclair_program_init_llvm.out // RUN: %extract_snippet %t/actual_llvm.out "@eclair_program_destroy" > %t/actual_eclair_program_destroy_llvm.out // RUN: %extract_snippet %t/actual_llvm.out "@eclair_program_run" > %t/actual_eclair_program_run_llvm.out // RUN: diff %t/expected_eclair_program_type.out %t/actual_eclair_program_type.out // RUN: diff %t/expected_eclair_program_init_llvm.out %t/actual_eclair_program_init_llvm.out // RUN: diff %t/expected_eclair_program_destroy_llvm.out %t/actual_eclair_program_destroy_llvm.out // RUN: diff %t/expected_eclair_program_run_llvm.out %t/actual_eclair_program_run_llvm.out //--- program.eclair @def edge(u32, u32). @def path(u32, u32) output. edge(1,2). path(x,y) :- edge(x,y). //--- expected_ra.out project (1, 2) into edge search edge as edge0 do project (edge0[0], edge0[1]) into path //--- expected_eir.out declare_type Program { symbol_table edge btree(num_columns=2, index=[0,1], block_size=256, search_type=linear) path btree(num_columns=2, index=[0,1], block_size=256, search_type=linear) } export fn eclair_program_init() -> *Program { program = heap_allocate_program symbol_table.init(program.0) edge.init_empty(program.1) path.init_empty(program.2) symbol_table.insert(program.0, "edge") symbol_table.insert(program.0, "path") return program } export fn eclair_program_destroy(*Program) -> Void { symbol_table.destroy(FN_ARG[0].0) edge.destroy(FN_ARG[0].1) path.destroy(FN_ARG[0].2) free_program(FN_ARG[0]) } export fn eclair_program_run(*Program) -> Void { value = edge.stack_allocate Value value.0 = 1 value.1 = 2 edge.insert(FN_ARG[0].1, value) lower_bound_value = edge.stack_allocate Value upper_bound_value = edge.stack_allocate Value lower_bound_value.0 = 0 lower_bound_value.1 = 0 upper_bound_value.0 = 4294967295 upper_bound_value.1 = 4294967295 begin_iter = edge.stack_allocate Iter end_iter = edge.stack_allocate Iter edge.iter_lower_bound(FN_ARG[0].1, lower_bound_value, begin_iter) edge.iter_upper_bound(FN_ARG[0].1, upper_bound_value, end_iter) loop { condition = edge.iter_is_equal(begin_iter, end_iter) if (condition) { goto range_query.end } current = edge.iter_current(begin_iter) value_1 = path.stack_allocate Value value_1.0 = current.0 value_1.1 = current.1 path.insert(FN_ARG[0].2, value_1) edge.iter_next(begin_iter) } range_query.end: } //--- expected_eclair_program_type.out %program = type {%symbol_table, %btree_t_0, %btree_t_0} //--- expected_eclair_program_init_llvm.out define external ccc ptr @eclair_program_init() "wasm-export-name"="eclair_program_init" { start: %stack.ptr_0 = alloca %symbol_t, i32 1 %stack.ptr_1 = alloca %symbol_t, i32 1 %0 = call ccc ptr @malloc(i32 1592) %1 = getelementptr %program, ptr %0, i32 0, i32 0 call ccc void @eclair_symbol_table_init(ptr %1) %2 = getelementptr %program, ptr %0, i32 0, i32 1 call ccc void @eclair_btree_init_empty_0(ptr %2) %3 = getelementptr %program, ptr %0, i32 0, i32 2 call ccc void @eclair_btree_init_empty_0(ptr %3) %4 = getelementptr %program, ptr %0, i32 0, i32 0 %5 = getelementptr inbounds [5 x i8], ptr @string_literal_0, i32 0, i32 0 %6 = zext i32 4 to i64 %7 = call ccc ptr @malloc(i32 4) call ccc void @llvm.memcpy.p0i8.p0i8.i64(ptr %7, ptr %5, i64 %6, i1 0) call ccc void @eclair_symbol_init(ptr %stack.ptr_0, i32 4, ptr %7) %8 = call ccc i32 @eclair_symbol_table_find_or_insert(ptr %4, ptr %stack.ptr_0) %9 = getelementptr %program, ptr %0, i32 0, i32 0 %10 = getelementptr inbounds [5 x i8], ptr @string_literal_1, i32 0, i32 0 %11 = zext i32 4 to i64 %12 = call ccc ptr @malloc(i32 4) call ccc void @llvm.memcpy.p0i8.p0i8.i64(ptr %12, ptr %10, i64 %11, i1 0) call ccc void @eclair_symbol_init(ptr %stack.ptr_1, i32 4, ptr %12) %13 = call ccc i32 @eclair_symbol_table_find_or_insert(ptr %9, ptr %stack.ptr_1) ret ptr %0 } //--- expected_eclair_program_destroy_llvm.out define external ccc void @eclair_program_destroy(ptr %arg_0) "wasm-export-name"="eclair_program_destroy" { start: %0 = getelementptr %program, ptr %arg_0, i32 0, i32 0 call ccc void @eclair_symbol_table_destroy(ptr %0) %1 = getelementptr %program, ptr %arg_0, i32 0, i32 1 call ccc void @eclair_btree_destroy_0(ptr %1) %2 = getelementptr %program, ptr %arg_0, i32 0, i32 2 call ccc void @eclair_btree_destroy_0(ptr %2) call ccc void @free(ptr %arg_0) ret void } //--- expected_eclair_program_run_llvm.out define external ccc void @eclair_program_run(ptr %arg_0) "wasm-export-name"="eclair_program_run" { start: %stack.ptr_0 = alloca [2 x i32], i32 1 %stack.ptr_1 = alloca [2 x i32], i32 1 %stack.ptr_2 = alloca [2 x i32], i32 1 %stack.ptr_3 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_4 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_5 = alloca [2 x i32], i32 1 %0 = getelementptr [2 x i32], ptr %stack.ptr_0, i32 0, i32 0 store i32 1, ptr %0 %1 = getelementptr [2 x i32], ptr %stack.ptr_0, i32 0, i32 1 store i32 2, ptr %1 %2 = getelementptr %program, ptr %arg_0, i32 0, i32 1 %3 = call ccc i1 @eclair_btree_insert_value_0(ptr %2, ptr %stack.ptr_0) %4 = getelementptr [2 x i32], ptr %stack.ptr_1, i32 0, i32 0 store i32 0, ptr %4 %5 = getelementptr [2 x i32], ptr %stack.ptr_1, i32 0, i32 1 store i32 0, ptr %5 %6 = getelementptr [2 x i32], ptr %stack.ptr_2, i32 0, i32 0 store i32 4294967295, ptr %6 %7 = getelementptr [2 x i32], ptr %stack.ptr_2, i32 0, i32 1 store i32 4294967295, ptr %7 %8 = getelementptr %program, ptr %arg_0, i32 0, i32 1 call ccc void @eclair_btree_lower_bound_0(ptr %8, ptr %stack.ptr_1, ptr %stack.ptr_3) %9 = getelementptr %program, ptr %arg_0, i32 0, i32 1 call ccc void @eclair_btree_upper_bound_0(ptr %9, ptr %stack.ptr_2, ptr %stack.ptr_4) br label %loop_0 loop_0: %10 = call ccc i1 @eclair_btree_iterator_is_equal_0(ptr %stack.ptr_3, ptr %stack.ptr_4) br i1 %10, label %if_0, label %end_if_0 if_0: br label %range_query.end end_if_0: %11 = call ccc ptr @eclair_btree_iterator_current_0(ptr %stack.ptr_3) %12 = getelementptr [2 x i32], ptr %stack.ptr_5, i32 0, i32 0 %13 = getelementptr [2 x i32], ptr %11, i32 0, i32 0 %14 = load i32, ptr %13 store i32 %14, ptr %12 %15 = getelementptr [2 x i32], ptr %stack.ptr_5, i32 0, i32 1 %16 = getelementptr [2 x i32], ptr %11, i32 0, i32 1 %17 = load i32, ptr %16 store i32 %17, ptr %15 %18 = getelementptr %program, ptr %arg_0, i32 0, i32 2 %19 = call ccc i1 @eclair_btree_insert_value_0(ptr %18, ptr %stack.ptr_5) call ccc void @eclair_btree_iterator_next_0(ptr %stack.ptr_3) br label %loop_0 range_query.end: ret void } ================================================ FILE: tests/lowering/single_recursive_rule.eclair ================================================ // RUN: split-file %s %t // RUN: %eclair compile --emit ra-transformed %t/program.eclair > %t/actual_ra.out // RUN: diff %t/expected_ra.out %t/actual_ra.out // NOTE: program for now also contains delta_ and new_ relations, // probably it's more efficient to move these to the stack (but left out of scope for now) // RUN: %eclair compile --emit eir %t/program.eclair > %t/actual_eir.out // RUN: diff %t/expected_eir.out %t/actual_eir.out // RUN: %eclair compile --emit llvm %t/program.eclair > %t/actual_llvm.out // RUN: %extract_snippet %t/actual_llvm.out "program = " > %t/actual_eclair_program_type.out // RUN: %extract_snippet %t/actual_llvm.out "@eclair_program_init" > %t/actual_eclair_program_init_llvm.out // RUN: %extract_snippet %t/actual_llvm.out "@eclair_program_destroy" > %t/actual_eclair_program_destroy_llvm.out // RUN: %extract_snippet %t/actual_llvm.out "@eclair_program_run" > %t/actual_eclair_program_run_llvm.out // RUN: diff %t/expected_eclair_program_type.out %t/actual_eclair_program_type.out // RUN: diff %t/expected_eclair_program_init_llvm.out %t/actual_eclair_program_init_llvm.out // RUN: diff %t/expected_eclair_program_destroy_llvm.out %t/actual_eclair_program_destroy_llvm.out // RUN: diff %t/expected_eclair_program_run_llvm.out %t/actual_eclair_program_run_llvm.out //--- program.eclair @def edge(u32, u32). @def path(u32, u32) output. edge(1,2). path(x, y) :- edge(x, z), path(z, y). //--- expected_ra.out project (1, 2) into edge merge path delta_path loop do purge new_path search edge as edge0 do search delta_path as delta_path1 where (edge0[1] = delta_path1[0]) do if (edge0[0], delta_path1[1]) ∉ path do project (edge0[0], delta_path1[1]) into new_path exit if counttuples(new_path) = 0 merge new_path path swap new_path delta_path //--- expected_eir.out declare_type Program { symbol_table delta_path btree(num_columns=2, index=[0,1], block_size=256, search_type=linear) edge btree(num_columns=2, index=[0,1], block_size=256, search_type=linear) new_path btree(num_columns=2, index=[0,1], block_size=256, search_type=linear) path btree(num_columns=2, index=[0,1], block_size=256, search_type=linear) } export fn eclair_program_init() -> *Program { program = heap_allocate_program symbol_table.init(program.0) delta_path.init_empty(program.1) edge.init_empty(program.2) new_path.init_empty(program.3) path.init_empty(program.4) symbol_table.insert(program.0, "edge") symbol_table.insert(program.0, "path") return program } export fn eclair_program_destroy(*Program) -> Void { symbol_table.destroy(FN_ARG[0].0) delta_path.destroy(FN_ARG[0].1) edge.destroy(FN_ARG[0].2) new_path.destroy(FN_ARG[0].3) path.destroy(FN_ARG[0].4) free_program(FN_ARG[0]) } export fn eclair_program_run(*Program) -> Void { value = edge.stack_allocate Value value.0 = 1 value.1 = 2 edge.insert(FN_ARG[0].2, value) begin_iter = path.stack_allocate Iter end_iter = path.stack_allocate Iter path.iter_begin(FN_ARG[0].4, begin_iter) path.iter_end(FN_ARG[0].4, end_iter) delta_path.insert_range(FN_ARG[0].1, begin_iter, end_iter) loop { new_path.purge(FN_ARG[0].3) lower_bound_value = edge.stack_allocate Value upper_bound_value = edge.stack_allocate Value lower_bound_value.0 = 0 lower_bound_value.1 = 0 upper_bound_value.0 = 4294967295 upper_bound_value.1 = 4294967295 begin_iter_1 = edge.stack_allocate Iter end_iter_1 = edge.stack_allocate Iter edge.iter_lower_bound(FN_ARG[0].2, lower_bound_value, begin_iter_1) edge.iter_upper_bound(FN_ARG[0].2, upper_bound_value, end_iter_1) loop { condition = edge.iter_is_equal(begin_iter_1, end_iter_1) if (condition) { goto range_query.end } current = edge.iter_current(begin_iter_1) lower_bound_value_1 = path.stack_allocate Value upper_bound_value_1 = path.stack_allocate Value lower_bound_value_1.0 = current.1 lower_bound_value_1.1 = 0 upper_bound_value_1.0 = current.1 upper_bound_value_1.1 = 4294967295 begin_iter_2 = path.stack_allocate Iter end_iter_2 = path.stack_allocate Iter delta_path.iter_lower_bound(FN_ARG[0].1, lower_bound_value_1, begin_iter_2) delta_path.iter_upper_bound(FN_ARG[0].1, upper_bound_value_1, end_iter_2) loop { condition_1 = delta_path.iter_is_equal(begin_iter_2, end_iter_2) if (condition_1) { goto range_query.end_1 } current_1 = delta_path.iter_current(begin_iter_2) value_1 = path.stack_allocate Value value_1.0 = current.0 value_1.1 = current_1.1 contains_result = path.contains(FN_ARG[0].4, value_1) condition_2 = not contains_result if (condition_2) { value_2 = path.stack_allocate Value value_2.0 = current.0 value_2.1 = current_1.1 new_path.insert(FN_ARG[0].3, value_2) } delta_path.iter_next(begin_iter_2) } range_query.end_1: edge.iter_next(begin_iter_1) } range_query.end: condition_3 = new_path.is_empty(FN_ARG[0].3) if (condition_3) { goto loop.end } begin_iter_3 = path.stack_allocate Iter end_iter_3 = path.stack_allocate Iter new_path.iter_begin(FN_ARG[0].3, begin_iter_3) new_path.iter_end(FN_ARG[0].3, end_iter_3) path.insert_range(FN_ARG[0].4, begin_iter_3, end_iter_3) new_path.swap(FN_ARG[0].3, FN_ARG[0].1) } loop.end: } //--- expected_eclair_program_type.out %program = type {%symbol_table, %btree_t_0, %btree_t_0, %btree_t_0, %btree_t_0} //--- expected_eclair_program_init_llvm.out define external ccc ptr @eclair_program_init() "wasm-export-name"="eclair_program_init" { start: %stack.ptr_0 = alloca %symbol_t, i32 1 %stack.ptr_1 = alloca %symbol_t, i32 1 %0 = call ccc ptr @malloc(i32 1624) %1 = getelementptr %program, ptr %0, i32 0, i32 0 call ccc void @eclair_symbol_table_init(ptr %1) %2 = getelementptr %program, ptr %0, i32 0, i32 1 call ccc void @eclair_btree_init_empty_0(ptr %2) %3 = getelementptr %program, ptr %0, i32 0, i32 2 call ccc void @eclair_btree_init_empty_0(ptr %3) %4 = getelementptr %program, ptr %0, i32 0, i32 3 call ccc void @eclair_btree_init_empty_0(ptr %4) %5 = getelementptr %program, ptr %0, i32 0, i32 4 call ccc void @eclair_btree_init_empty_0(ptr %5) %6 = getelementptr %program, ptr %0, i32 0, i32 0 %7 = getelementptr inbounds [5 x i8], ptr @string_literal_0, i32 0, i32 0 %8 = zext i32 4 to i64 %9 = call ccc ptr @malloc(i32 4) call ccc void @llvm.memcpy.p0i8.p0i8.i64(ptr %9, ptr %7, i64 %8, i1 0) call ccc void @eclair_symbol_init(ptr %stack.ptr_0, i32 4, ptr %9) %10 = call ccc i32 @eclair_symbol_table_find_or_insert(ptr %6, ptr %stack.ptr_0) %11 = getelementptr %program, ptr %0, i32 0, i32 0 %12 = getelementptr inbounds [5 x i8], ptr @string_literal_1, i32 0, i32 0 %13 = zext i32 4 to i64 %14 = call ccc ptr @malloc(i32 4) call ccc void @llvm.memcpy.p0i8.p0i8.i64(ptr %14, ptr %12, i64 %13, i1 0) call ccc void @eclair_symbol_init(ptr %stack.ptr_1, i32 4, ptr %14) %15 = call ccc i32 @eclair_symbol_table_find_or_insert(ptr %11, ptr %stack.ptr_1) ret ptr %0 } //--- expected_eclair_program_destroy_llvm.out define external ccc void @eclair_program_destroy(ptr %arg_0) "wasm-export-name"="eclair_program_destroy" { start: %0 = getelementptr %program, ptr %arg_0, i32 0, i32 0 call ccc void @eclair_symbol_table_destroy(ptr %0) %1 = getelementptr %program, ptr %arg_0, i32 0, i32 1 call ccc void @eclair_btree_destroy_0(ptr %1) %2 = getelementptr %program, ptr %arg_0, i32 0, i32 2 call ccc void @eclair_btree_destroy_0(ptr %2) %3 = getelementptr %program, ptr %arg_0, i32 0, i32 3 call ccc void @eclair_btree_destroy_0(ptr %3) %4 = getelementptr %program, ptr %arg_0, i32 0, i32 4 call ccc void @eclair_btree_destroy_0(ptr %4) call ccc void @free(ptr %arg_0) ret void } //--- expected_eclair_program_run_llvm.out define external ccc void @eclair_program_run(ptr %arg_0) "wasm-export-name"="eclair_program_run" { start: %stack.ptr_0 = alloca [2 x i32], i32 1 %stack.ptr_1 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_2 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_3 = alloca [2 x i32], i32 1 %stack.ptr_4 = alloca [2 x i32], i32 1 %stack.ptr_5 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_6 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_7 = alloca [2 x i32], i32 1 %stack.ptr_8 = alloca [2 x i32], i32 1 %stack.ptr_9 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_10 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_11 = alloca [2 x i32], i32 1 %stack.ptr_12 = alloca [2 x i32], i32 1 %stack.ptr_13 = alloca %btree_iterator_t_0, i32 1 %stack.ptr_14 = alloca %btree_iterator_t_0, i32 1 %0 = getelementptr [2 x i32], ptr %stack.ptr_0, i32 0, i32 0 store i32 1, ptr %0 %1 = getelementptr [2 x i32], ptr %stack.ptr_0, i32 0, i32 1 store i32 2, ptr %1 %2 = getelementptr %program, ptr %arg_0, i32 0, i32 2 %3 = call ccc i1 @eclair_btree_insert_value_0(ptr %2, ptr %stack.ptr_0) %4 = getelementptr %program, ptr %arg_0, i32 0, i32 4 call ccc void @eclair_btree_begin_0(ptr %4, ptr %stack.ptr_1) %5 = getelementptr %program, ptr %arg_0, i32 0, i32 4 call ccc void @eclair_btree_end_0(ptr %5, ptr %stack.ptr_2) %6 = getelementptr %program, ptr %arg_0, i32 0, i32 1 call ccc void @eclair_btree_insert_range_delta_path_path(ptr %6, ptr %stack.ptr_1, ptr %stack.ptr_2) br label %loop_0 loop_0: %7 = getelementptr %program, ptr %arg_0, i32 0, i32 3 call ccc void @eclair_btree_clear_0(ptr %7) %8 = getelementptr [2 x i32], ptr %stack.ptr_3, i32 0, i32 0 store i32 0, ptr %8 %9 = getelementptr [2 x i32], ptr %stack.ptr_3, i32 0, i32 1 store i32 0, ptr %9 %10 = getelementptr [2 x i32], ptr %stack.ptr_4, i32 0, i32 0 store i32 4294967295, ptr %10 %11 = getelementptr [2 x i32], ptr %stack.ptr_4, i32 0, i32 1 store i32 4294967295, ptr %11 %12 = getelementptr %program, ptr %arg_0, i32 0, i32 2 call ccc void @eclair_btree_lower_bound_0(ptr %12, ptr %stack.ptr_3, ptr %stack.ptr_5) %13 = getelementptr %program, ptr %arg_0, i32 0, i32 2 call ccc void @eclair_btree_upper_bound_0(ptr %13, ptr %stack.ptr_4, ptr %stack.ptr_6) br label %loop_1 loop_1: %14 = call ccc i1 @eclair_btree_iterator_is_equal_0(ptr %stack.ptr_5, ptr %stack.ptr_6) br i1 %14, label %if_0, label %end_if_0 if_0: br label %range_query.end end_if_0: %15 = call ccc ptr @eclair_btree_iterator_current_0(ptr %stack.ptr_5) %16 = getelementptr [2 x i32], ptr %stack.ptr_7, i32 0, i32 0 %17 = getelementptr [2 x i32], ptr %15, i32 0, i32 1 %18 = load i32, ptr %17 store i32 %18, ptr %16 %19 = getelementptr [2 x i32], ptr %stack.ptr_7, i32 0, i32 1 store i32 0, ptr %19 %20 = getelementptr [2 x i32], ptr %stack.ptr_8, i32 0, i32 0 %21 = getelementptr [2 x i32], ptr %15, i32 0, i32 1 %22 = load i32, ptr %21 store i32 %22, ptr %20 %23 = getelementptr [2 x i32], ptr %stack.ptr_8, i32 0, i32 1 store i32 4294967295, ptr %23 %24 = getelementptr %program, ptr %arg_0, i32 0, i32 1 call ccc void @eclair_btree_lower_bound_0(ptr %24, ptr %stack.ptr_7, ptr %stack.ptr_9) %25 = getelementptr %program, ptr %arg_0, i32 0, i32 1 call ccc void @eclair_btree_upper_bound_0(ptr %25, ptr %stack.ptr_8, ptr %stack.ptr_10) br label %loop_2 loop_2: %26 = call ccc i1 @eclair_btree_iterator_is_equal_0(ptr %stack.ptr_9, ptr %stack.ptr_10) br i1 %26, label %if_1, label %end_if_1 if_1: br label %range_query.end_1 end_if_1: %27 = call ccc ptr @eclair_btree_iterator_current_0(ptr %stack.ptr_9) %28 = getelementptr [2 x i32], ptr %stack.ptr_11, i32 0, i32 0 %29 = getelementptr [2 x i32], ptr %15, i32 0, i32 0 %30 = load i32, ptr %29 store i32 %30, ptr %28 %31 = getelementptr [2 x i32], ptr %stack.ptr_11, i32 0, i32 1 %32 = getelementptr [2 x i32], ptr %27, i32 0, i32 1 %33 = load i32, ptr %32 store i32 %33, ptr %31 %34 = getelementptr %program, ptr %arg_0, i32 0, i32 4 %35 = call ccc i1 @eclair_btree_contains_0(ptr %34, ptr %stack.ptr_11) %36 = select i1 %35, i1 0, i1 1 br i1 %36, label %if_2, label %end_if_2 if_2: %37 = getelementptr [2 x i32], ptr %stack.ptr_12, i32 0, i32 0 %38 = getelementptr [2 x i32], ptr %15, i32 0, i32 0 %39 = load i32, ptr %38 store i32 %39, ptr %37 %40 = getelementptr [2 x i32], ptr %stack.ptr_12, i32 0, i32 1 %41 = getelementptr [2 x i32], ptr %27, i32 0, i32 1 %42 = load i32, ptr %41 store i32 %42, ptr %40 %43 = getelementptr %program, ptr %arg_0, i32 0, i32 3 %44 = call ccc i1 @eclair_btree_insert_value_0(ptr %43, ptr %stack.ptr_12) br label %end_if_2 end_if_2: call ccc void @eclair_btree_iterator_next_0(ptr %stack.ptr_9) br label %loop_2 range_query.end_1: call ccc void @eclair_btree_iterator_next_0(ptr %stack.ptr_5) br label %loop_1 range_query.end: %45 = getelementptr %program, ptr %arg_0, i32 0, i32 3 %46 = call ccc i1 @eclair_btree_is_empty_0(ptr %45) br i1 %46, label %if_3, label %end_if_3 if_3: br label %loop.end end_if_3: %47 = getelementptr %program, ptr %arg_0, i32 0, i32 3 call ccc void @eclair_btree_begin_0(ptr %47, ptr %stack.ptr_13) %48 = getelementptr %program, ptr %arg_0, i32 0, i32 3 call ccc void @eclair_btree_end_0(ptr %48, ptr %stack.ptr_14) %49 = getelementptr %program, ptr %arg_0, i32 0, i32 4 call ccc void @eclair_btree_insert_range_path_new_path(ptr %49, ptr %stack.ptr_13, ptr %stack.ptr_14) %50 = getelementptr %program, ptr %arg_0, i32 0, i32 3 %51 = getelementptr %program, ptr %arg_0, i32 0, i32 1 call ccc void @eclair_btree_swap_0(ptr %50, ptr %51) br label %loop_0 loop.end: ret void } ================================================ FILE: tests/lowering/stratification.eclair ================================================ // RUN: split-file %s %t // RUN: %eclair compile --emit ra-transformed %t/program.eclair > %t/actual_ra.out // RUN: diff %t/expected_ra.out %t/actual_ra.out // RUN: %eclair compile --emit ra %t/program2.eclair > %t/actual_ra2.out // RUN: diff %t/expected_ra2.out %t/actual_ra2.out //--- program.eclair @def a(u32) input. @def b(u32) input. @def c(u32). @def d(u32). @def e(u32) output. @def f(u32, u32) input. @def g(u32, u32) output. c(x) :- a(x), !b(x). d(x) :- b(x). e(x) :- !c(x), d(x). g(x, y) :- f(x, y). g(x, z) :- f(x, y), g(y, z), !d(x). //--- expected_ra.out search f as f0 do project (f0[0], f0[1]) into g search b as b0 do project (b0[0]) into d merge g delta_g loop do purge new_g search f as f0 do if (f0[0]) ∉ d do search delta_g as delta_g1 where (f0[1] = delta_g1[0]) do if (f0[0], delta_g1[1]) ∉ g do project (f0[0], delta_g1[1]) into new_g exit if counttuples(new_g) = 0 merge new_g g swap new_g delta_g search a as a0 do if (a0[0]) ∉ b do project (a0[0]) into c search d as d0 do if (d0[0]) ∉ c do project (d0[0]) into e //--- program2.eclair @def a(u32, string) input. @def b(u32, string, u32, u32) input. @def c(u32, string) input. @def d(u32, u32) input. @def e(u32, u32). @def f(u32, string) output. e(x, y) :- d(x, y), c(y, _). e(x, y) :- e(x, z), e(x, a), b(y, _, z, a). f(x, y) :- e(x, z), a(z, y). //--- expected_ra2.out search d as d0 do search c as c1 do if d0[1] = c1[0] do project (d0[0], d0[1]) into e merge e delta_e loop do purge new_e parallel do search delta_e as delta_e0 do search e as e1 do search b as b2 do if (delta_e0[0], e1[1]) ∉ delta_e do if e1[1] = b2[3] do if delta_e0[0] = e1[0] do if delta_e0[1] = b2[2] do if (delta_e0[0], b2[0]) ∉ e do project (delta_e0[0], b2[0]) into new_e search e as e0 do search delta_e as delta_e1 do search b as b2 do if delta_e1[1] = b2[3] do if e0[0] = delta_e1[0] do if e0[1] = b2[2] do if (e0[0], b2[0]) ∉ e do project (e0[0], b2[0]) into new_e exit if counttuples(new_e) = 0 merge new_e e swap new_e delta_e search e as e0 do search a as a1 do if e0[1] = a1[0] do project (e0[0], a1[1]) into f ================================================ FILE: tests/lowering/top_level_facts.eclair ================================================ // RUN: split-file %s %t // RUN: %eclair compile --emit ra-transformed %t/program.eclair > %t/actual_ra.out // RUN: diff %t/expected_ra.out %t/actual_ra.out // RUN: %eclair compile --emit eir %t/program.eclair > %t/actual_eir.out // RUN: diff %t/expected_eir.out %t/actual_eir.out // RUN: %eclair compile --emit llvm %t/program.eclair > %t/actual_llvm.out // RUN: %extract_snippet %t/actual_llvm.out "@eclair_program_init" > %t/actual_eclair_program_init_llvm.out // RUN: %extract_snippet %t/actual_llvm.out "@eclair_program_destroy" > %t/actual_eclair_program_destroy_llvm.out // RUN: %extract_snippet %t/actual_llvm.out "@eclair_program_run" > %t/actual_eclair_program_run_llvm.out // RUN: diff %t/expected_eclair_program_init_llvm.out %t/actual_eclair_program_init_llvm.out // RUN: diff %t/expected_eclair_program_destroy_llvm.out %t/actual_eclair_program_destroy_llvm.out // RUN: diff %t/expected_eclair_program_run_llvm.out %t/actual_eclair_program_run_llvm.out //--- program.eclair @def edge(u32, u32) output. @def another(u32, u32, u32) output. edge(1, 2). edge(2, 3). another(1,2,3). //--- expected_ra.out project (1, 2, 3) into another project (2, 3) into edge project (1, 2) into edge //--- expected_eir.out declare_type Program { symbol_table another btree(num_columns=3, index=[0,1,2], block_size=256, search_type=linear) edge btree(num_columns=2, index=[0,1], block_size=256, search_type=linear) } export fn eclair_program_init() -> *Program { program = heap_allocate_program symbol_table.init(program.0) another.init_empty(program.1) edge.init_empty(program.2) symbol_table.insert(program.0, "edge") symbol_table.insert(program.0, "another") return program } export fn eclair_program_destroy(*Program) -> Void { symbol_table.destroy(FN_ARG[0].0) another.destroy(FN_ARG[0].1) edge.destroy(FN_ARG[0].2) free_program(FN_ARG[0]) } export fn eclair_program_run(*Program) -> Void { value = another.stack_allocate Value value.0 = 1 value.1 = 2 value.2 = 3 another.insert(FN_ARG[0].1, value) value_1 = edge.stack_allocate Value value_1.0 = 2 value_1.1 = 3 edge.insert(FN_ARG[0].2, value_1) value_2 = edge.stack_allocate Value value_2.0 = 1 value_2.1 = 2 edge.insert(FN_ARG[0].2, value_2) } //--- expected_eclair_program_init_llvm.out define external ccc ptr @eclair_program_init() "wasm-export-name"="eclair_program_init" { start: %stack.ptr_0 = alloca %symbol_t, i32 1 %stack.ptr_1 = alloca %symbol_t, i32 1 %0 = call ccc ptr @malloc(i32 1592) %1 = getelementptr %program, ptr %0, i32 0, i32 0 call ccc void @eclair_symbol_table_init(ptr %1) %2 = getelementptr %program, ptr %0, i32 0, i32 1 call ccc void @eclair_btree_init_empty_0(ptr %2) %3 = getelementptr %program, ptr %0, i32 0, i32 2 call ccc void @eclair_btree_init_empty_1(ptr %3) %4 = getelementptr %program, ptr %0, i32 0, i32 0 %5 = getelementptr inbounds [5 x i8], ptr @string_literal_0, i32 0, i32 0 %6 = zext i32 4 to i64 %7 = call ccc ptr @malloc(i32 4) call ccc void @llvm.memcpy.p0i8.p0i8.i64(ptr %7, ptr %5, i64 %6, i1 0) call ccc void @eclair_symbol_init(ptr %stack.ptr_0, i32 4, ptr %7) %8 = call ccc i32 @eclair_symbol_table_find_or_insert(ptr %4, ptr %stack.ptr_0) %9 = getelementptr %program, ptr %0, i32 0, i32 0 %10 = getelementptr inbounds [8 x i8], ptr @string_literal_1, i32 0, i32 0 %11 = zext i32 7 to i64 %12 = call ccc ptr @malloc(i32 7) call ccc void @llvm.memcpy.p0i8.p0i8.i64(ptr %12, ptr %10, i64 %11, i1 0) call ccc void @eclair_symbol_init(ptr %stack.ptr_1, i32 7, ptr %12) %13 = call ccc i32 @eclair_symbol_table_find_or_insert(ptr %9, ptr %stack.ptr_1) ret ptr %0 } //--- expected_eclair_program_destroy_llvm.out define external ccc void @eclair_program_destroy(ptr %arg_0) "wasm-export-name"="eclair_program_destroy" { start: %0 = getelementptr %program, ptr %arg_0, i32 0, i32 0 call ccc void @eclair_symbol_table_destroy(ptr %0) %1 = getelementptr %program, ptr %arg_0, i32 0, i32 1 call ccc void @eclair_btree_destroy_0(ptr %1) %2 = getelementptr %program, ptr %arg_0, i32 0, i32 2 call ccc void @eclair_btree_destroy_1(ptr %2) call ccc void @free(ptr %arg_0) ret void } //--- expected_eclair_program_run_llvm.out define external ccc void @eclair_program_run(ptr %arg_0) "wasm-export-name"="eclair_program_run" { start: %stack.ptr_0 = alloca [3 x i32], i32 1 %stack.ptr_1 = alloca [2 x i32], i32 1 %stack.ptr_2 = alloca [2 x i32], i32 1 %0 = getelementptr [3 x i32], ptr %stack.ptr_0, i32 0, i32 0 store i32 1, ptr %0 %1 = getelementptr [3 x i32], ptr %stack.ptr_0, i32 0, i32 1 store i32 2, ptr %1 %2 = getelementptr [3 x i32], ptr %stack.ptr_0, i32 0, i32 2 store i32 3, ptr %2 %3 = getelementptr %program, ptr %arg_0, i32 0, i32 1 %4 = call ccc i1 @eclair_btree_insert_value_0(ptr %3, ptr %stack.ptr_0) %5 = getelementptr [2 x i32], ptr %stack.ptr_1, i32 0, i32 0 store i32 2, ptr %5 %6 = getelementptr [2 x i32], ptr %stack.ptr_1, i32 0, i32 1 store i32 3, ptr %6 %7 = getelementptr %program, ptr %arg_0, i32 0, i32 2 %8 = call ccc i1 @eclair_btree_insert_value_1(ptr %7, ptr %stack.ptr_1) %9 = getelementptr [2 x i32], ptr %stack.ptr_2, i32 0, i32 0 store i32 1, ptr %9 %10 = getelementptr [2 x i32], ptr %stack.ptr_2, i32 0, i32 1 store i32 2, ptr %10 %11 = getelementptr %program, ptr %arg_0, i32 0, i32 2 %12 = call ccc i1 @eclair_btree_insert_value_1(ptr %11, ptr %stack.ptr_2) ret void } ================================================ FILE: tests/lowering/wasm_codegen.eclair ================================================ // RUN: split-file %s %t // RUN: %eclair compile --target wasm32 --emit llvm %t/program.eclair > %t/actual_llvm.out // RUN: %extract_snippet %t/actual_llvm.out "define.*@memcmp_wasm32" > %t/actual_wasm_memcmp.out // RUN: diff %t/expected_wasm_memcmp.out %t/actual_wasm_memcmp.out //--- program.eclair @def edge(u32, u32) output. @def another(u32, u32, u32) output. edge(1, 2). another(1,2,3). //--- expected_wasm_memcmp.out define external ccc i32 @memcmp_wasm32(ptr %array1_0, ptr %array2_0, i64 %byte_count_0) { start: %0 = udiv i64 %byte_count_0, 8 %1 = and i64 %byte_count_0, 7 br label %for_begin_0 for_begin_0: %2 = phi i64 [0, %start], [%9, %end_if_0] %3 = icmp ult i64 %2, %0 br i1 %3, label %for_body_0, label %for_end_0 for_body_0: %4 = getelementptr i64, ptr %array1_0, i64 %2 %5 = getelementptr i64, ptr %array2_0, i64 %2 %6 = load i64, ptr %4 %7 = load i64, ptr %5 %8 = icmp ne i64 %6, %7 br i1 %8, label %if_0, label %end_if_0 if_0: ret i32 1 end_if_0: %9 = add i64 1, %2 br label %for_begin_0 for_end_0: %10 = mul i64 %0, 8 br label %for_begin_1 for_begin_1: %11 = phi i64 [0, %for_end_0], [%19, %end_if_1] %12 = icmp ult i64 %11, %1 br i1 %12, label %for_body_1, label %for_end_1 for_body_1: %13 = add i64 %11, %10 %14 = getelementptr i8, ptr %array1_0, i64 %13 %15 = getelementptr i8, ptr %array2_0, i64 %13 %16 = load i8, ptr %14 %17 = load i8, ptr %15 %18 = icmp ne i8 %16, %17 br i1 %18, label %if_1, label %end_if_1 if_1: ret i32 1 end_if_1: %19 = add i64 1, %11 br label %for_begin_1 for_end_1: ret i32 0 } ================================================ FILE: tests/parser/error_recovery.eclair ================================================ // RUN: mkdir -p %t && sed s@TEST_DIR@%t@g %s > %t/input.test // RUN: split-file %t/input.test %t // RUN: %eclair compile --emit ra-transformed %t/program.eclair 2> %t/actual_output // RUN: diff -w %t/expected_output %t/actual_output //--- program.eclair @def edge(u32,). @def path(u32, u32) output. @def broken. edge(1, 2). path(x, y) : edge(x, y). path(x, y) :- edge(x, z), path(z, y). edge(2, 3). //--- expected_output [error]: Failed to parse file ╭──▶ TEST_DIR/program.eclair@1:15-1:16 │ 1 │ @def edge(u32,). • ┬ • ├╸ unexpected ").@de" • ╰╸ expecting field name or type ─────╯ [error]: Failed to parse file ╭──▶ TEST_DIR/program.eclair@3:12-3:13 │ 3 │ @def broken. • ┬ • ├╸ unexpected '.' • ╰╸ expecting '(' or rest of identifier ─────╯ [error]: Failed to parse file ╭──▶ TEST_DIR/program.eclair@7:12-7:13 │ 7 │ path(x, y) : • ┬ • ├╸ unexpected ":" • ╰╸ expecting ":-" or '.' ─────╯ [error]: Ungrounded variable ╭──▶ TEST_DIR/program.eclair@10:6-10:7 │ 10 │ ╭┤ path(x, y) :- • │ ┬ • │ ╰╸ The variable 'x' is ungrounded, meaning it is not directly bound as an argument to a relation. 11 │ │ edge(x, z), 12 │ ├┤ path(z, y). • │ • ╰╸ This contains no clauses that refer to 'x'. • │ Hint: Use the variable 'x' as an argument in a relation. ─────╯ [error]: Missing type definition ╭──▶ TEST_DIR/program.eclair@5:1-5:12 │ 5 │ edge(1, 2). • ┬────────── • ╰╸ Could not find a type definition for 'edge'. • │ Hint: Add a type definition for 'edge'. │ Hint: Add an extern definition for 'edge'. ─────╯ [error]: Missing type definition ╭──▶ TEST_DIR/program.eclair@11:3-11:13 │ 11 │ edge(x, z), • ┬───────── • ╰╸ Could not find a type definition for 'edge'. • │ Hint: Add a type definition for 'edge'. │ Hint: Add an extern definition for 'edge'. ─────╯ [error]: Missing type definition ╭──▶ TEST_DIR/program.eclair@14:1-14:12 │ 14 │ edge(2, 3). • ┬────────── • ╰╸ Could not find a type definition for 'edge'. • │ Hint: Add a type definition for 'edge'. │ Hint: Add an extern definition for 'edge'. ─────╯ ================================================ FILE: tests/parser/file_not_found.eclair ================================================ // RUN: mkdir -p %t && sed s@TEST_DIR@%t@g %s > %t/input.test // RUN: split-file %t/input.test %t // RUN: %eclair compile foo.eclair 2> %t/actual_output // RUN: diff -w %t/expected_output %t/actual_output //--- expected_output File not found: foo.eclair. ================================================ FILE: tests/parser/valid.eclair ================================================ // RUN: %eclair compile --emit ra-transformed %s | FileCheck %s // CHECK: project // u32 @def fact1(u32) output. @def fact2(u32, u32, u32) output. @def fact3 ( u32 , u32 ) output. fact3(0, 1). fact3(2, 3). fact2(1,2,3). // strings @def fact4(string) output. @def fact5(u32, string) output. @def fact6(string , string,string ) output . fact4(""). fact4("a"). fact4( "b" ). fact6( "c" , "d","e" ). fact4("\"\n\r\t\b\f\v\0"). // NOTE: rules and assignments are tested implicitly in other tests // mix of everything @def edge(field1: u32, field2: u32) input. @def path(u32, only_one_named_field: u32) output. @def literals_in_rule(u32) output. edge(1, 2). edge(2, 3). path(x, y) :- edge(x, y). path(x, y) :- edge(x, z), path(z, y). // This also checks for potential naming collisions @def u32(u32) input. @def string(string) input. @def mix(u32, string) input. literals_in_rule(789) :- u32( 123 ), string( "abc"), mix(456, "def"). @def constraints(u32). constraints(x) :- u32(x), x < 100, x <= 100, 100 > x, 100 >= x, 100 != x. @def arithmetic(u32). arithmetic(x) :- u32(x), x = 1 + 2 * (7 / 4), 1 + 2 - 0 < x. arithmetic(123 + 456 * 789). @extern constraint(string). @extern func(u32) u32. @extern func2(u32, u32) string. @def test_externs(u32). test_externs(func(123)). test_externs(x) :- edge(x, y), func(y) + 1 = x, x = func(y) + y, func(x) = func(y). @extern func_with_named_fields(field1: u32, field2: u32) string. @extern constraint_with_named_field(u32, another_field: u32). @def test_negation(u32). test_negation(x) :- edge(x, x), !edge(1, 2), edge(123, x). // TODO: failure cases, more thorough testing in general ================================================ FILE: tests/runtime/hashmap_test.eclair ================================================ // RUN: split-file %s %t // RUN: %eclair compile %t/program.eclair > %t/program.ll // RUN: %clang -O0 -o %t/program %t/main.c %t/program.ll // RUN: %t/program //--- program.eclair @def fact(u32) output. //--- main.c #include #include #include #include #include typedef uint32_t u32; typedef struct symbol { u32 size; char *data; } __attribute__((packed)) symbol_t; typedef struct entry { symbol_t symbol; u32 value; } entry_t; typedef struct bucket { entry_t *start; entry_t *end; u32 capacity; } bucket_t; typedef struct hashmap { bucket_t buckets[64]; } eclair_hashmap_t; static void ASSERT(bool result, const char *msg) { if (!result) { printf("Assertion failed: %s\n", msg); exit(1); } } extern void eclair_hashmap_init(void *map); extern void eclair_hashmap_destroy(void *map); extern u32 eclair_hashmap_get_or_put_value(void *map, void *key, u32 value); extern bool eclair_hashmap_contains(void *map, void *key); extern u32 eclair_hashmap_lookup(void *map, void *key); static void test_eclair_hashmap_init() { eclair_hashmap_t map; eclair_hashmap_init(&map); eclair_hashmap_destroy(&map); } static void test_eclair_hashmap_collisions() { eclair_hashmap_t map; eclair_hashmap_init(&map); // NOTE: these 2 create a collision symbol_t symbol1 = {.size = 1, .data = "<"}; symbol_t symbol2 = {.size = 1, .data = "|"}; eclair_hashmap_get_or_put_value(&map, &symbol1, 0); eclair_hashmap_get_or_put_value(&map, &symbol2, 1); u32 hash = 60; // 60 % 64 or 124 % 64 = 60 printf("%s, %s\n", map.buckets[hash].start[0].symbol.data, symbol1.data); ASSERT(map.buckets[hash].start[0].symbol.data == symbol1.data, "Unexpected symbol1 in hashmap"); ASSERT(map.buckets[hash].start[0].value == 0, "Unexpected value1 in hashmap"); ASSERT(map.buckets[hash].start[1].symbol.data == symbol2.data, "Unexpected symbol2 in hashmap"); ASSERT(map.buckets[hash].start[1].value == 1, "Unexpected value2 in hashmap"); eclair_hashmap_destroy(&map); } static void test_eclair_hashmap_get_or_put_value() { eclair_hashmap_t map; eclair_hashmap_init(&map); // NOTE: these 2 create a collision symbol_t symbol1 = {.size = 1, .data = "<"}; symbol_t symbol2 = {.size = 1, .data = "|"}; // non-existing value => adds new value u32 result1 = eclair_hashmap_get_or_put_value(&map, &symbol1, 0); u32 result2 = eclair_hashmap_get_or_put_value(&map, &symbol2, 1); ASSERT(result1 == 0, "Unexpected result from eclair_hashmap_get_or_put_value"); ASSERT(result2 == 1, "Unexpected result from eclair_hashmap_get_or_put_value"); // existing value => finds existing value u32 result3 = eclair_hashmap_get_or_put_value(&map, &symbol1, 2); u32 result4 = eclair_hashmap_get_or_put_value(&map, &symbol2, 3); ASSERT(result1 == 0, "Unexpected result from eclair_hashmap_get_or_put_value"); ASSERT(result2 == 1, "Unexpected result from eclair_hashmap_get_or_put_value"); eclair_hashmap_destroy(&map); } static void test_eclair_hashmap_contains() { eclair_hashmap_t map; eclair_hashmap_init(&map); // NOTE: these 2 create a collision symbol_t symbol1 = {.size = 1, .data = "<"}; symbol_t symbol2 = {.size = 1, .data = "|"}; eclair_hashmap_get_or_put_value(&map, &symbol1, 0); eclair_hashmap_get_or_put_value(&map, &symbol2, 1); ASSERT(eclair_hashmap_contains(&map, &symbol1), "Expected to find symbol1!"); ASSERT(eclair_hashmap_contains(&map, &symbol2), "Expected to find symbol2!"); symbol_t symbol3 = {.size = 1, .data = "a"}; ASSERT(!eclair_hashmap_contains(&map, &symbol3), "Expected not to find symbol3!"); eclair_hashmap_destroy(&map); } static void test_eclair_hashmap_lookup() { // NOTE: can't be used for value not present in map! eclair_hashmap_t map; eclair_hashmap_init(&map); // NOTE: these 2 create a collision symbol_t symbol1 = {.size = 1, .data = "<"}; symbol_t symbol2 = {.size = 1, .data = "|"}; eclair_hashmap_get_or_put_value(&map, &symbol1, 0); eclair_hashmap_get_or_put_value(&map, &symbol2, 1); symbol_t symbol3 = {.size = 1, .data = "a"}; eclair_hashmap_get_or_put_value(&map, &symbol3, 2); ASSERT(eclair_hashmap_lookup(&map, &symbol1) == 0, "Expected to find symbol1!"); ASSERT(eclair_hashmap_lookup(&map, &symbol2) == 1, "Expected to find symbol2!"); ASSERT(eclair_hashmap_lookup(&map, &symbol3) == 2, "Expected to find symbol3!"); eclair_hashmap_destroy(&map); } int main(int argc, char **argv) { printf("Starting tests..\n"); test_eclair_hashmap_init(); printf("hashmap init: OK!\n"); test_eclair_hashmap_collisions(); printf("hashmap collisions: OK!\n"); test_eclair_hashmap_get_or_put_value(); printf("hashmap get or put value: OK!\n"); test_eclair_hashmap_contains(); printf("hashmap contains: OK!\n"); test_eclair_hashmap_lookup(); printf("Hashmap: all good!\n"); return 0; } ================================================ FILE: tests/runtime/symbol_table_test.eclair ================================================ // RUN: split-file %s %t // RUN: %eclair compile %t/program.eclair > %t/program.ll // RUN: %clang -O0 -o %t/program %t/main.c %t/program.ll // RUN: %t/program //--- program.eclair @def fact(u32) output. //--- main.c #include #include #include #include #include // NOTE: strings need to be malloc'ed / strdup'ed since they get freed // when calling eclair_symbol_table_destroy typedef uint32_t u32; typedef struct symbol { u32 size; char *data; } __attribute__((packed)) symbol_t; typedef symbol_t elem_t; typedef struct vector { elem_t *start; elem_t *end; uint32_t capacity; } eclair_vector_t; typedef struct entry { symbol_t symbol; u32 value; } entry_t; typedef struct bucket { entry_t *start; entry_t *end; u32 capacity; } bucket_t; typedef struct hashmap { bucket_t buckets[64]; } eclair_hashmap_t; typedef struct symbol_table { eclair_vector_t idx2sym; eclair_hashmap_t sym2idx; } eclair_symbol_table_t; extern void eclair_symbol_table_init(eclair_symbol_table_t *table); extern void eclair_symbol_table_destroy(eclair_symbol_table_t *table); extern u32 eclair_symbol_table_find_or_insert(eclair_symbol_table_t *table, symbol_t *symbol); extern bool eclair_symbol_table_contains_index(eclair_symbol_table_t *table, u32 index); extern bool eclair_symbol_table_contains_symbol(eclair_symbol_table_t *table, symbol_t *symbol); extern u32 eclair_symbol_table_lookup_index(eclair_symbol_table_t *table, symbol_t *symbol); extern symbol_t *eclair_symbol_table_lookup_symbol(eclair_symbol_table_t *table, u32 index); static void ASSERT(bool result, const char *msg) { if (!result) { printf("Assertion failed: %s\n", msg); exit(1); } } static void test_eclair_symbol_table_init() { eclair_symbol_table_t table; eclair_symbol_table_init(&table); eclair_symbol_table_destroy(&table); } static void test_eclair_symbol_table_find_or_insert() { // NOTE: hash collisions are checked on hashmap level eclair_symbol_table_t table; eclair_symbol_table_init(&table); symbol_t symbol1 = {.size = 1, .data = strdup("a")}; symbol_t symbol2 = {.size = 1, .data = strdup("b")}; symbol_t symbol3 = {.size = 1, .data = strdup("c")}; u32 index1 = eclair_symbol_table_find_or_insert(&table, &symbol1); u32 index2 = eclair_symbol_table_find_or_insert(&table, &symbol2); u32 index3 = eclair_symbol_table_find_or_insert(&table, &symbol3); ASSERT(index1 == 0, "Received unexpected index for symbol1!"); ASSERT(index2 == 1, "Received unexpected index for symbol2!"); ASSERT(index3 == 2, "Received unexpected index for symbol3!"); u32 index4 = eclair_symbol_table_find_or_insert(&table, &symbol1); ASSERT(index4 == 0, "Received unexpected index for symbol1!"); eclair_symbol_table_destroy(&table); } static void test_eclair_symbol_table_contains_index() { eclair_symbol_table_t table; eclair_symbol_table_init(&table); symbol_t symbol = {.size = 1, .data = strdup("a")}; u32 index = eclair_symbol_table_find_or_insert(&table, &symbol); ASSERT(eclair_symbol_table_contains_index(&table, index), "Expected to find symbol for known index!"); u32 unknown_index = 42; ASSERT(!eclair_symbol_table_contains_index(&table, unknown_index), "Expected not to find symbol for known index!"); eclair_symbol_table_destroy(&table); } static void test_eclair_symbol_table_contains_symbol() { eclair_symbol_table_t table; eclair_symbol_table_init(&table); symbol_t symbol = {.size = 1, .data = strdup("a")}; eclair_symbol_table_find_or_insert(&table, &symbol); ASSERT(eclair_symbol_table_contains_symbol(&table, &symbol), "Expected to find symbol!"); symbol_t not_added = {.size = 1, .data = "b"}; ASSERT(!eclair_symbol_table_contains_symbol(&table, ¬_added), "Did not expect to find symbol!"); eclair_symbol_table_destroy(&table); } static void test_eclair_symbol_table_lookup_index() { eclair_symbol_table_t table; eclair_symbol_table_init(&table); symbol_t symbol1 = {.size = 1, .data = strdup("a")}; symbol_t symbol2 = {.size = 1, .data = strdup("b")}; eclair_symbol_table_find_or_insert(&table, &symbol1); eclair_symbol_table_find_or_insert(&table, &symbol2); u32 index1 = eclair_symbol_table_lookup_index(&table, &symbol1); u32 index2 = eclair_symbol_table_lookup_index(&table, &symbol2); ASSERT(index1 == 0, "Expected to find index for symbol1!"); ASSERT(index2 == 1, "Expected to find index for symbol2!"); // This should not occur in Datalog! // symbol_t not_added = {.size = 1, .data = "c"}; // u32 missing_index = eclair_symbol_table_lookup_symbol(&table, ¬_added); // ASSERT(missing_index == 0xFFFFFFFF, // "Did not expect to find index for missing symbol!"); eclair_symbol_table_destroy(&table); } static void test_eclair_symbol_table_lookup_symbol() { eclair_symbol_table_t table; eclair_symbol_table_init(&table); symbol_t symbol1 = {.size = 1, .data = strdup("a")}; symbol_t symbol2 = {.size = 1, .data = strdup("b")}; u32 index1 = eclair_symbol_table_find_or_insert(&table, &symbol1); u32 index2 = eclair_symbol_table_find_or_insert(&table, &symbol2); symbol_t *symbol1_result = eclair_symbol_table_lookup_symbol(&table, index1); symbol_t *symbol2_result = eclair_symbol_table_lookup_symbol(&table, index2); ASSERT(strcmp(symbol1_result->data, "a") == 0, "Expected to find index for symbol1!"); ASSERT(strcmp(symbol2_result->data, "b") == 0, "Expected to find index for symbol2!"); // This reads uninitialized memory, no safety checks are done! // Should not occur in a real DL program // u32 missing = 42; // ASSERT(strcmp(eclair_symbol_table_lookup_index(&table, missing)->data, "a") != 0, // "Expected not to find unknown index"); eclair_symbol_table_destroy(&table); } int main(int argc, char **argv) { printf("Testing symbol_table...\n"); test_eclair_symbol_table_init(); printf("eclair_symbol_table_init: OK!\n"); test_eclair_symbol_table_find_or_insert(); printf("eclair_symbol_table_find_or_insert: OK!\n"); test_eclair_symbol_table_contains_index(); printf("eclair_symbol_table_contains_index: OK!\n"); test_eclair_symbol_table_contains_symbol(); printf("eclair_symbol_table_contains_symbol: OK!\n"); test_eclair_symbol_table_lookup_symbol(); printf("eclair_symbol_table_lookup_symbol: OK!\n"); test_eclair_symbol_table_lookup_index(); printf("symbol_table: all good!\n"); return 0; } ================================================ FILE: tests/runtime/vector_test.eclair ================================================ // RUN: split-file %s %t // RUN: %eclair compile %t/program.eclair > %t/program.ll // RUN: %clang -O0 -o %t/program %t/main.c %t/program.ll // RUN: %t/program //--- program.eclair @def fact(u32) output. //--- main.c #include #include #include #include #include typedef uint32_t u32; typedef struct symbol { u32 length; char *data; } __attribute__((packed)) symbol_t; typedef symbol_t elem_t; typedef struct vector { elem_t *start; elem_t *end; uint32_t capacity; } eclair_vector_t; extern void eclair_vector_init_symbol(struct vector *); extern void eclair_vector_destroy_symbol(struct vector *); extern uint32_t eclair_vector_push_symbol(struct vector *, elem_t *); extern uint32_t eclair_vector_size_symbol(struct vector *); extern elem_t *eclair_vector_get_value_symbol(struct vector *, uint32_t); void ASSERT(bool result, const char *msg) { if (!result) { printf("Assertion failed: %s\n", msg); exit(1); } } #define INITIAL_CAPACITY 16 #define GROW_FACTOR 2 static void test_eclair_vector_init() { eclair_vector_t vec; eclair_vector_init_symbol(&vec); ASSERT(eclair_vector_size_symbol(&vec) == 0, "vector size should be 0"); ASSERT(vec.start == vec.end, ""); ASSERT(vec.capacity == INITIAL_CAPACITY, "vector capacity should be INITIAL_CAPACITY"); eclair_vector_destroy_symbol(&vec); } static void test_eclair_vector_single_push() { eclair_vector_t vec; eclair_vector_init_symbol(&vec); ASSERT(eclair_vector_size_symbol(&vec) == 0, "Unexpected size before!"); // strdup needed since the vector frees symbols symbol_t symbol = {.data = strdup("some_symbol")}; u32 idx = eclair_vector_push_symbol(&vec, &symbol); ASSERT(eclair_vector_size_symbol(&vec) == 1, "Unexpected size after!"); eclair_vector_destroy_symbol(&vec); } static void test_eclair_vector_push() { eclair_vector_t vec; eclair_vector_init_symbol(&vec); for (u32 i = 0; i < INITIAL_CAPACITY; ++i) { // strdup needed since the vector frees symbols symbol_t symbol = {.data = strdup("some_symbol")}; ASSERT(eclair_vector_size_symbol(&vec) == i, "Unexpected size before!"); u32 idx = eclair_vector_push_symbol(&vec, &symbol); ASSERT(idx == i, "Unexpected index!"); ASSERT(eclair_vector_size_symbol(&vec) == i + 1, "Unexpected size after!"); ASSERT(vec.capacity == INITIAL_CAPACITY, "Unexpected capacity"); } ASSERT(eclair_vector_size_symbol(&vec) == INITIAL_CAPACITY, "Unexpected size before resize!"); symbol_t symbol = {.data = strdup("final_symbol")}; u32 idx = eclair_vector_push_symbol(&vec, &symbol); ASSERT(idx == INITIAL_CAPACITY, "Unexpected index!"); ASSERT(eclair_vector_size_symbol(&vec) == INITIAL_CAPACITY + 1, "Unexpected size after resize!"); ASSERT(vec.capacity == GROW_FACTOR * INITIAL_CAPACITY, "Unexpected capacity"); eclair_vector_destroy_symbol(&vec); } static void test_eclair_vector_resize() { eclair_vector_t vec; eclair_vector_init_symbol(&vec); ASSERT(vec.capacity == INITIAL_CAPACITY, "Unexpected capacity"); for (u32 i = 0; i < 2 * INITIAL_CAPACITY; ++i) { // strdup needed since the vector frees symbols symbol_t symbol = {.data = strdup("some_symbol")}; u32 idx = eclair_vector_push_symbol(&vec, &symbol); } ASSERT(vec.capacity == GROW_FACTOR * INITIAL_CAPACITY, "Unexpected capacity"); for (u32 i = 0; i < 2 * INITIAL_CAPACITY; ++i) { // strdup needed since the vector frees symbols symbol_t symbol = {.data = strdup("some_symbol")}; u32 idx = eclair_vector_push_symbol(&vec, &symbol); } ASSERT(vec.capacity == GROW_FACTOR * GROW_FACTOR * INITIAL_CAPACITY, "Unexpected capacity"); eclair_vector_destroy_symbol(&vec); } int main(int argc, char **argv) { printf("Start tests..\n"); test_eclair_vector_init(); printf("eclair_vector_init: OK!\n"); test_eclair_vector_single_push(); printf("eclair_vector_single_push: OK!\n"); test_eclair_vector_push(); printf("eclair_vector_push: OK!\n"); test_eclair_vector_resize(); printf("Vector: all good!\n"); return 0; } ================================================ FILE: tests/semantic_analysis/cyclic_negation.eclair ================================================ // RUN: mkdir -p %t && sed s@TEST_DIR@%t@g %s > %t/input.test // RUN: split-file %t/input.test %t // RUN: %eclair compile %t/program1.eclair 2> %t/actual1.out // RUN: diff -w %t/expected1.out %t/actual1.out //--- program1.eclair @def a(u32) input. @def b(u32) output. @def c(u32) input. @def d(u32) output. @def e(u32) input. @def f(u32) input. @def g(u32) output. b(x) :- a(x), !b(x). d(x) :- c(x), !d(x). d(x) :- !c(x), d(x). g(x) :- f(x). f(x) :- e(x), !g(x). //--- expected1.out [error]: Negation used in recursive set of rules ╭──▶ TEST_DIR/program1.eclair@11:3-11:8 │ 11 │ !b(x). • ┬──── • ╰╸ This negation is used in a set of rules that is recursive, which is not allowed. • │ Hint: Restructure the program so the negation does not occur in the set of recursive rules. │ Hint: Remove the negation entirely. ─────╯ [error]: Negation used in recursive set of rules ╭──▶ TEST_DIR/program1.eclair@15:3-15:8 │ 15 │ !d(x). • ┬──── • ╰╸ This negation is used in a set of rules that is recursive, which is not allowed. • │ Hint: Restructure the program so the negation does not occur in the set of recursive rules. │ Hint: Remove the negation entirely. ─────╯ [error]: Negation used in recursive set of rules ╭──▶ TEST_DIR/program1.eclair@26:3-26:8 │ 26 │ !g(x). • ┬──── • ╰╸ This negation is used in a set of rules that is recursive, which is not allowed. • │ Hint: Restructure the program so the negation does not occur in the set of recursive rules. │ Hint: Remove the negation entirely. ─────╯ ================================================ FILE: tests/semantic_analysis/dead_internal_relation.eclair ================================================ // RUN: mkdir -p %t && sed s@TEST_DIR@%t@g %s > %t/input.test // RUN: split-file %t/input.test %t // RUN: %eclair compile %t/program.eclair --emit ra-transformed 2> %t/actual.out // RUN: diff %t/expected.out %t/actual.out //--- program.eclair @def fact(u32). @def fact2(u32) output. @def internal_rule(u32). internal_rule(x) :- x = 1. //--- expected.out [error]: Dead internal relation ╭──▶ TEST_DIR/program.eclair@1:1-1:16 │ 1 │ @def fact(u32). • ┬────────────── • ╰╸ The internal rule 'fact' has no facts or rules defined and will never produce results. • │ Hint: This might indicate a logic error in your code. │ Hint: Remove this rule if it is no longer needed. │ Hint: Add 'input' to the declaration to indicate this rule is an input. ─────╯ ================================================ FILE: tests/semantic_analysis/invalid_extern_usage.eclair ================================================ // RUN: mkdir -p %t && sed s@TEST_DIR@%t@g %s > %t/input.test // RUN: split-file %t/input.test %t //--- program1.eclair // RUN: %eclair compile --emit ra-transformed %t/program1.eclair 2> %t/program1.out // RUN: diff -w %t/expected1.out %t/program1.out @def edge(u32, u32) input. @def test_externs(u32) output. @extern constraint1(string). @extern func1(u32) u32. @extern func2(u32, u32) string. // Duplicate externs / functions @extern constraint1(string). @extern func1(string). @def func2(u32) output. // Usage of externs as facts constraint1("abc"). func1(123). // Usage of externs as rules @extern constraint2(u32). @extern func3(u32) u32. @extern func4(u32, u32) u32. constraint2(x) :- edge(x, 123). func3(x) :- edge(x, 123). // Invalid wildcards in extern constraints / functions test_externs(x) :- edge(x, func3(_)), edge(x, func4(_, _)), constraint2(_). // Ungrounded variables test_externs(x) :- constraint2(x), x = func4(x, x). //--- expected1.out [error]: Ungrounded variable ╭──▶ TEST_DIR/program1.eclair@35:14-35:15 │ 35 │ ╭┤ test_externs(x) :- • │ ┬ • │ ╰╸ The variable 'x' is ungrounded, meaning it is not directly bound as an argument to a relation. 36 │ │ constraint2(x), 37 │ ├┤ x = func4(x, x). • │ • ╰╸ This contains no clauses that refer to 'x'. • │ Hint: Use the variable 'x' as an argument in a relation. ─────╯ [error]: Ungrounded variable ╭──▶ TEST_DIR/program1.eclair@37:3-37:4 │ 35 │ ╭┤ test_externs(x) :- 36 │ │ constraint2(x), 37 │ ├┤ x = func4(x, x). • │ ┬ • │ ╰╸ The variable 'x' is ungrounded, meaning it is not directly bound as an argument to a relation. • │ • ╰╸ This contains no clauses that refer to 'x'. • │ Hint: Use the variable 'x' as an argument in a relation. ─────╯ [error]: Wildcard in externally defined atom ╭──▶ TEST_DIR/program1.eclair@30:17-30:18 │ 30 │ edge(x, func3(_)), • ┬─────┬─ • │ ╰╸ Wildcard found. • ╰╸ An external atom only supports constants or grounded variables. • │ Hint: Replace the wildcard with a constant or grounded variable. ─────╯ [error]: Wildcard in externally defined atom ╭──▶ TEST_DIR/program1.eclair@31:17-31:18 │ 31 │ edge(x, func4(_, _)), • ┬─────┬──── • │ ╰╸ Wildcard found. • ╰╸ An external atom only supports constants or grounded variables. • │ Hint: Replace the wildcard with a constant or grounded variable. ─────╯ [error]: Wildcard in externally defined atom ╭──▶ TEST_DIR/program1.eclair@31:20-31:21 │ 31 │ edge(x, func4(_, _)), • ┬────────┬─ • │ ╰╸ Wildcard found. • ╰╸ An external atom only supports constants or grounded variables. • │ Hint: Replace the wildcard with a constant or grounded variable. ─────╯ [error]: Wildcard in externally defined atom ╭──▶ TEST_DIR/program1.eclair@32:15-32:16 │ 32 │ constraint2(_). • ┬───────────┬─ • │ ╰╸ Wildcard found. • ╰╸ An external atom only supports constants or grounded variables. • │ Hint: Replace the wildcard with a constant or grounded variable. ─────╯ [error]: Multiple definitions for 'constraint1' ╭──▶ TEST_DIR/program1.eclair@7:1-7:29 │ 7 │ @extern constraint1(string). • ┬─────────────────────────── • ╰╸ 'constraint1' is originally defined here. • 12 │ @extern constraint1(string). • ┬─────────────────────────── • ╰╸ 'constraint1' is re-defined here. • │ Hint: You can solve this by removing the duplicate definitions for 'constraint1'. ─────╯ [error]: Multiple definitions for 'func1' ╭──▶ TEST_DIR/program1.eclair@8:1-8:24 │ 8 │ @extern func1(u32) u32. • ┬────────────────────── • ╰╸ 'func1' is originally defined here. • 13 │ @extern func1(string). • ┬───────────────────── • ╰╸ 'func1' is re-defined here. • │ Hint: You can solve this by removing the duplicate definitions for 'func1'. ─────╯ [error]: Multiple definitions for 'func2' ╭──▶ TEST_DIR/program1.eclair@9:1-9:32 │ 9 │ @extern func2(u32, u32) string. • ┬────────────────────────────── • ╰╸ 'func2' is originally defined here. • 14 │ @def func2(u32) output. • ┬────────────────────── • ╰╸ 'func2' is re-defined here. • │ Hint: You can solve this by removing the duplicate definitions for 'func2'. ─────╯ [error]: Extern definition used as top level fact ╭──▶ TEST_DIR/program1.eclair@17:1-17:20 │ 7 │ @extern constraint1(string). • ┬─────────────────────────── • ╰╸ 'constraint1' previously defined here as external. • 17 │ constraint1("abc"). • ┬────────────────── • ╰╸ 'constraint1' is used as a fact here, which is not allowed for extern definitions. • │ Hint: Convert 'constraint1' to a relation. │ Hint: Remove the top level fact. ─────╯ [error]: Extern definition used as top level fact ╭──▶ TEST_DIR/program1.eclair@17:1-17:20 │ 12 │ @extern constraint1(string). • ┬─────────────────────────── • ╰╸ 'constraint1' previously defined here as external. • 17 │ constraint1("abc"). • ┬────────────────── • ╰╸ 'constraint1' is used as a fact here, which is not allowed for extern definitions. • │ Hint: Convert 'constraint1' to a relation. │ Hint: Remove the top level fact. ─────╯ [error]: Extern definition used as top level fact ╭──▶ TEST_DIR/program1.eclair@18:1-18:12 │ 8 │ @extern func1(u32) u32. • ┬────────────────────── • ╰╸ 'func1' previously defined here as external. • 18 │ func1(123). • ┬────────── • ╰╸ 'func1' is used as a fact here, which is not allowed for extern definitions. • │ Hint: Convert 'func1' to a relation. │ Hint: Remove the top level fact. ─────╯ [error]: Extern definition used as top level fact ╭──▶ TEST_DIR/program1.eclair@18:1-18:12 │ 13 │ @extern func1(string). • ┬───────────────────── • ╰╸ 'func1' previously defined here as external. • 18 │ func1(123). • ┬────────── • ╰╸ 'func1' is used as a fact here, which is not allowed for extern definitions. • │ Hint: Convert 'func1' to a relation. │ Hint: Remove the top level fact. ─────╯ [error]: Extern definition used in rule head ╭──▶ TEST_DIR/program1.eclair@25:1-25:32 │ 21 │ @extern constraint2(u32). • ┬──────────────────────── • ╰╸ 'constraint2' previously defined here as external. • 25 │ constraint2(x) :- edge(x, 123). • ┬────────────────────────────── • ╰╸ 'constraint2' is used as a rule head here, which is not allowed for extern definitions. • │ Hint: Convert 'constraint2' to a relation. │ Hint: Remove the rule. ─────╯ [error]: Extern definition used in rule head ╭──▶ TEST_DIR/program1.eclair@26:1-26:26 │ 22 │ @extern func3(u32) u32. • ┬────────────────────── • ╰╸ 'func3' previously defined here as external. • 26 │ func3(x) :- edge(x, 123). • ┬──────────────────────── • ╰╸ 'func3' is used as a rule head here, which is not allowed for extern definitions. • │ Hint: Convert 'func3' to a relation. │ Hint: Remove the rule. ─────╯ [error]: Invalid use of function ╭──▶ TEST_DIR/program1.eclair@18:1-18:12 │ 8 │ @extern func1(u32) u32. • ┬────────────────────── • ╰╸ Previously defined as a function here. • 18 │ func1(123). • ┬────────── • ╰╸ Expected a constraint here. • │ Hint: Maybe you meant to declare this an external constraint instead? │ Hint: Remove the invalid function. ─────╯ [error]: Invalid use of function ╭──▶ TEST_DIR/program1.eclair@26:1-26:26 │ 22 │ @extern func3(u32) u32. • ┬────────────────────── • ╰╸ Previously defined as a function here. • 26 │ func3(x) :- edge(x, 123). • ┬──────────────────────── • ╰╸ Expected a constraint here. • │ Hint: Maybe you meant to declare this an external constraint instead? │ Hint: Remove the invalid function. ─────╯ ================================================ FILE: tests/semantic_analysis/invalid_options_usage.eclair ================================================ // RUN: mkdir -p %t && sed s@TEST_DIR@%t@g %s > %t/input.test // RUN: split-file %t/input.test %t //--- program1.eclair // RUN: %eclair compile %t/program1.eclair 2> %t/actual1.out // RUN: diff %t/expected1.out %t/actual1.out @def fact(string) input input. @def fact2(string) output output output. //--- expected1.out [error]: Failed to parse file ╭──▶ TEST_DIR/program1.eclair@4:30-4:31 │ 4 │ @def fact(string) input input. • ┬ • ╰╸ More than one option of type 'input' is not allowed. ─────╯ [error]: Failed to parse file ╭──▶ TEST_DIR/program1.eclair@5:40-5:41 │ 5 │ @def fact2(string) output output output. • ┬ • ╰╸ More than one option of type 'output' is not allowed. ─────╯ ================================================ FILE: tests/semantic_analysis/invalid_wildcard_usage.eclair ================================================ // NOTE: happy path is mostly skipped, this is tested implicitly by running // the compiler end-to-end in most other tests // RUN: mkdir -p %t && sed s@TEST_DIR@%t@g %s > %t/input.test // RUN: split-file %t/input.test %t // RUN: %eclair compile %t/program1.eclair 2> %t/top_level_facts_actual.out // RUN: diff %t/top_level_facts_expected.out %t/top_level_facts_actual.out //--- program1.eclair @def a(u32, u32) output. a(123, _). a(_, 123). //--- top_level_facts_expected.out [error]: Wildcard in top level fact ╭──▶ TEST_DIR/program1.eclair@3:8-3:9 │ 3 │ a(123, _). • ┬──────┬── • │ ╰╸ Wildcard found. • ╰╸ A top level fact only supports constants. • Variables or wildcards are not allowed. • │ Hint: Replace the wildcard with a constant. ─────╯ [error]: Wildcard in top level fact ╭──▶ TEST_DIR/program1.eclair@4:3-4:4 │ 4 │ a(_, 123). • ┬─┬─────── • │ ╰╸ Wildcard found. • ╰╸ A top level fact only supports constants. • Variables or wildcards are not allowed. • │ Hint: Replace the wildcard with a constant. ─────╯ //--- program2.eclair // RUN: %eclair compile %t/program2.eclair 2> %t/wildcard_in_rule_head_actual.out // RUN: diff -w %t/wildcard_in_rule_head_expected.out %t/wildcard_in_rule_head_actual.out @def a(u32, u32) output. @def b(u32) input. a(x, _) :- b(x). a(_, x) :- b(x). //--- wildcard_in_rule_head_expected.out [error]: Wildcard in 'head' of rule ╭──▶ TEST_DIR/program2.eclair@8:6-8:7 │ 8 │ ╭┤ a(x, _) :- • │ ┬ • │ ╰╸ Wildcard found. 9 │ ├┤ b(x). • │ • ╰╸ Only constants and variables are allowed in the head of a rule. • Wildcards are not allowed. • │ Hint: Replace the wildcard with a constant or a variable. ─────╯ [error]: Wildcard in 'head' of rule ╭──▶ TEST_DIR/program2.eclair@11:3-11:4 │ 11 │ ╭┤ a(_, x) :- • │ ┬ • │ ╰╸ Wildcard found. 12 │ ├┤ b(x). • │ • ╰╸ Only constants and variables are allowed in the head of a rule. • Wildcards are not allowed. • │ Hint: Replace the wildcard with a constant or a variable. ─────╯ //--- program3.eclair // RUN: %eclair compile %t/program3.eclair 2> %t/wildcard_in_assignment_actual.out // RUN: diff -w %t/wildcard_in_assignment_expected.out %t/wildcard_in_assignment_actual.out @def a(u32) output. @def b(u32) input. a(x) :- b(x), _ = 123. a(x) :- b(x), 123 = _. a(x) :- b(x), _ = _. //--- wildcard_in_assignment_expected.out [error]: Found wildcard in constraint ╭──▶ TEST_DIR/program3.eclair@10:3-10:4 │ 10 │ _ = 123. • ┬────── • ├╸ Wildcard found. • ╰╸ Only constants and variables are allowed in a constraint. • │ Hint: This statement can be removed since it has no effect. │ Hint: Replace the wildcard with a variable. ─────╯ [error]: Found wildcard in constraint ╭──▶ TEST_DIR/program3.eclair@14:9-14:10 │ 14 │ 123 = _. • ┬─────┬ • │ ╰╸ Wildcard found. • ╰╸ Only constants and variables are allowed in a constraint. • │ Hint: This statement can be removed since it has no effect. │ Hint: Replace the wildcard with a variable. ─────╯ [error]: Found wildcard in constraint ╭──▶ TEST_DIR/program3.eclair@18:3-18:4 │ 18 │ _ = _. • ┬──── • ├╸ Wildcard found. • ╰╸ Only constants and variables are allowed in a constraint. • │ Hint: This statement can be removed since it has no effect. │ Hint: Replace the wildcard with a variable. ─────╯ [error]: Found wildcard in constraint ╭──▶ TEST_DIR/program3.eclair@18:7-18:8 │ 18 │ _ = _. • ┬───┬ • │ ╰╸ Wildcard found. • ╰╸ Only constants and variables are allowed in a constraint. • │ Hint: This statement can be removed since it has no effect. │ Hint: Replace the wildcard with a variable. ─────╯ //--- program4.eclair // RUN: %eclair compile %t/program4.eclair 2> %t/wildcard_in_valid_actual.out // RUN: diff %t/wildcard_valid_expected.out %t/wildcard_in_valid_actual.out @def a(u32) output. @def b(u32, u32, u32) input. a(x) :- b(x, _, _), b(_, x, _), b(_, _, x). //--- wildcard_valid_expected.out //--- program5.eclair // RUN: %eclair compile %t/program5.eclair 2> %t/wildcard_in_comparison_actual.out // RUN: diff -w %t/wildcard_in_comparison_expected.out %t/wildcard_in_comparison_actual.out @def a(u32) output. @def b(u32) input. a(x) :- b(x), 123 < _, 123 <= _, 123 > _, 123 >= _, 123 != _. //--- wildcard_in_comparison_expected.out [error]: Found wildcard in constraint ╭──▶ TEST_DIR/program5.eclair@10:9-10:10 │ 10 │ 123 < _, • ┬─────┬ • │ ╰╸ Wildcard found. • ╰╸ Only constants and variables are allowed in a constraint. • │ Hint: This statement can be removed since it has no effect. │ Hint: Replace the wildcard with a variable. ─────╯ [error]: Found wildcard in constraint ╭──▶ TEST_DIR/program5.eclair@11:10-11:11 │ 11 │ 123 <= _, • ┬──────┬ • │ ╰╸ Wildcard found. • ╰╸ Only constants and variables are allowed in a constraint. • │ Hint: This statement can be removed since it has no effect. │ Hint: Replace the wildcard with a variable. ─────╯ [error]: Found wildcard in constraint ╭──▶ TEST_DIR/program5.eclair@12:9-12:10 │ 12 │ 123 > _, • ┬─────┬ • │ ╰╸ Wildcard found. • ╰╸ Only constants and variables are allowed in a constraint. • │ Hint: This statement can be removed since it has no effect. │ Hint: Replace the wildcard with a variable. ─────╯ [error]: Found wildcard in constraint ╭──▶ TEST_DIR/program5.eclair@13:10-13:11 │ 13 │ 123 >= _, • ┬──────┬ • │ ╰╸ Wildcard found. • ╰╸ Only constants and variables are allowed in a constraint. • │ Hint: This statement can be removed since it has no effect. │ Hint: Replace the wildcard with a variable. ─────╯ [error]: Found wildcard in constraint ╭──▶ TEST_DIR/program5.eclair@14:10-14:11 │ 14 │ 123 != _. • ┬──────┬ • │ ╰╸ Wildcard found. • ╰╸ Only constants and variables are allowed in a constraint. • │ Hint: This statement can be removed since it has no effect. │ Hint: Replace the wildcard with a variable. ─────╯ //--- program6.eclair // RUN: %eclair compile %t/program6.eclair 2> %t/wildcard_in_binop_actual.out // RUN: diff -w %t/wildcard_in_binop_expected.out %t/wildcard_in_binop_actual.out @def a(u32) output. @def b(u32) output. b(123 + _). b(_ + 123). a(x) :- b(x), x = 123 + _, x = _ + 123. //--- wildcard_in_binop_expected.out [error]: Found wildcard in binary operation ╭──▶ TEST_DIR/program6.eclair@8:9-8:10 │ 8 │ b(123 + _). • ┬─────┬ • │ ╰╸ Wildcard found. • ╰╸ Only constants and variables are allowed in a binary operation. • │ Hint: Replace the wildcard with a variable or literal. ─────╯ [error]: Found wildcard in binary operation ╭──▶ TEST_DIR/program6.eclair@9:3-9:4 │ 9 │ b(_ + 123). • ┬────── • ├╸ Wildcard found. • ╰╸ Only constants and variables are allowed in a binary operation. • │ Hint: Replace the wildcard with a variable or literal. ─────╯ [error]: Found wildcard in binary operation ╭──▶ TEST_DIR/program6.eclair@13:13-13:14 │ 13 │ x = 123 + _, • ┬─────┬ • │ ╰╸ Wildcard found. • ╰╸ Only constants and variables are allowed in a binary operation. • │ Hint: Replace the wildcard with a variable or literal. ─────╯ [error]: Found wildcard in binary operation ╭──▶ TEST_DIR/program6.eclair@14:7-14:8 │ 14 │ x = _ + 123. • ┬────── • ├╸ Wildcard found. • ╰╸ Only constants and variables are allowed in a binary operation. • │ Hint: Replace the wildcard with a variable or literal. ─────╯ ================================================ FILE: tests/semantic_analysis/no_output_relations.eclair ================================================ // RUN: mkdir -p %t && sed s@TEST_DIR@%t@g %s > %t/input.test // RUN: split-file %t/input.test %t // RUN: %eclair compile %t/program1.eclair 2> %t/actual1.out // RUN: diff -w %t/expected1.out %t/actual1.out // RUN: %eclair compile %t/program2.eclair | FileCheck %t/program2.eclair //--- program1.eclair @def fact(u32) input. fact(123). //--- expected1.out [error]: No output relations found ╭──▶ TEST_DIR/program1.eclair@1:1-1:2 │ 1 │ @def fact(u32) input. • ┬ • ╰╸ This module does not produce any results • │ Hint: Add an 'output' qualifier to one of the relations defined in this module. ─────╯ //--- program2.eclair @def fact(u32) input. @def fact2(u32) output. fact(123). fact2(x) :- fact(x). // CHECK: eclair_program_run ================================================ FILE: tests/semantic_analysis/unconstrained_variables.eclair ================================================ // RUN: mkdir -p %t && sed s@TEST_DIR@%t@g %s > %t/input.test // RUN: split-file %t/input.test %t // RUN: %eclair compile %t/program.eclair 2> %t/actual.out // RUN: diff -w %t/expected.out %t/actual.out //--- program.eclair @def fact1(u32, u32) output. @def fact2(u32) output. fact2(x) :- fact1(x, 1). fact2(2) :- fact1(x, 2), !fact1(2, x). fact2(3) :- fact1(x, 3), fact1(3, x). fact2(4) :- fact1(x, 4), x = 4. fact2(2) :- fact1(x, y). //--- expected.out [error]: Found unconstrained variable ╭──▶ TEST_DIR/program.eclair@19:9-19:10 │ 18 │ ╭┤ fact2(2) :- 19 │ ├┤ fact1(x, y). • │ ┬ • │ ╰╸ The variable 'x' only occurs once. • │ • ╰╸ This rule contains no other references to 'x'. • │ Hint: Replace the variable with a wildcard ('_'). │ Hint: Use the variable in another rule clause. ─────╯ [error]: Found unconstrained variable ╭──▶ TEST_DIR/program.eclair@19:12-19:13 │ 18 │ ╭┤ fact2(2) :- 19 │ ├┤ fact1(x, y). • │ ┬ • │ ╰╸ The variable 'y' only occurs once. • │ • ╰╸ This rule contains no other references to 'y'. • │ Hint: Replace the variable with a wildcard ('_'). │ Hint: Use the variable in another rule clause. ─────╯ ================================================ FILE: tests/semantic_analysis/ungrounded_variables.eclair ================================================ // NOTE: happy path is not tested, this is tested implicitly by running // the compiler end-to-end in most other tests // RUN: mkdir -p %t && sed s@TEST_DIR@%t@g %s > %t/input.test // RUN: split-file %t/input.test %t // RUN: %eclair compile %t/program.eclair 2> %t/top_level_facts_actual.out // RUN: diff %t/top_level_facts_expected.out %t/top_level_facts_actual.out //--- program.eclair @def edge(u32, u32) output. edge(a, 1). edge(2, b). edge(c, d). edge(e + 1, 1 + f). //--- top_level_facts_expected.out [error]: Ungrounded variable ╭──▶ TEST_DIR/program.eclair@3:6-3:7 │ 3 │ edge(a, 1). • ┬────┬───── • │ ╰╸ The variable 'a' is ungrounded, meaning it is not directly bound as an argument to a relation. • ╰╸ This contains no clauses that refer to 'a'. • │ Hint: Use the variable 'a' as an argument in a relation. ─────╯ [error]: Ungrounded variable ╭──▶ TEST_DIR/program.eclair@4:9-4:10 │ 4 │ edge(2, b). • ┬───────┬── • │ ╰╸ The variable 'b' is ungrounded, meaning it is not directly bound as an argument to a relation. • ╰╸ This contains no clauses that refer to 'b'. • │ Hint: Use the variable 'b' as an argument in a relation. ─────╯ [error]: Ungrounded variable ╭──▶ TEST_DIR/program.eclair@5:6-5:7 │ 5 │ edge(c, d). • ┬────┬───── • │ ╰╸ The variable 'c' is ungrounded, meaning it is not directly bound as an argument to a relation. • ╰╸ This contains no clauses that refer to 'c'. • │ Hint: Use the variable 'c' as an argument in a relation. ─────╯ [error]: Ungrounded variable ╭──▶ TEST_DIR/program.eclair@5:9-5:10 │ 5 │ edge(c, d). • ┬───────┬── • │ ╰╸ The variable 'd' is ungrounded, meaning it is not directly bound as an argument to a relation. • ╰╸ This contains no clauses that refer to 'd'. • │ Hint: Use the variable 'd' as an argument in a relation. ─────╯ [error]: Ungrounded variable ╭──▶ TEST_DIR/program.eclair@6:6-6:7 │ 6 │ edge(e + 1, 1 + f). • ┬────┬───────────── • │ ╰╸ The variable 'e' is ungrounded, meaning it is not directly bound as an argument to a relation. • ╰╸ This contains no clauses that refer to 'e'. • │ Hint: Use the variable 'e' as an argument in a relation. ─────╯ [error]: Ungrounded variable ╭──▶ TEST_DIR/program.eclair@6:17-6:18 │ 6 │ edge(e + 1, 1 + f). • ┬───────────────┬── • │ ╰╸ The variable 'f' is ungrounded, meaning it is not directly bound as an argument to a relation. • ╰╸ This contains no clauses that refer to 'f'. • │ Hint: Use the variable 'f' as an argument in a relation. ─────╯ //--- program2.eclair // RUN: %eclair compile %t/program2.eclair 2> %t/ungrounded_var_in_rule_actual.out // RUN: diff -w %t/ungrounded_var_in_rule_expected.out %t/ungrounded_var_in_rule_actual.out @def edge(u32, u32) input. @def reachable(u32, u32) output. reachable(x, z) :- edge(x, y). reachable(a, b) :- edge(x, y). //--- ungrounded_var_in_rule_expected.out [error]: Ungrounded variable ╭──▶ TEST_DIR/program2.eclair@8:14-8:15 │ 8 │ ╭┤ reachable(x, z) :- • │ ┬ • │ ╰╸ The variable 'z' is ungrounded, meaning it is not directly bound as an argument to a relation. 9 │ ├┤ edge(x, y). • │ • ╰╸ This contains no clauses that refer to 'z'. • │ Hint: Use the variable 'z' as an argument in a relation. ─────╯ [error]: Ungrounded variable ╭──▶ TEST_DIR/program2.eclair@11:11-11:12 │ 11 │ ╭┤ reachable(a, b) :- • │ ┬ • │ ╰╸ The variable 'a' is ungrounded, meaning it is not directly bound as an argument to a relation. 12 │ ├┤ edge(x, y). • │ • ╰╸ This contains no clauses that refer to 'a'. • │ Hint: Use the variable 'a' as an argument in a relation. ─────╯ [error]: Ungrounded variable ╭──▶ TEST_DIR/program2.eclair@11:14-11:15 │ 11 │ ╭┤ reachable(a, b) :- • │ ┬ • │ ╰╸ The variable 'b' is ungrounded, meaning it is not directly bound as an argument to a relation. 12 │ ├┤ edge(x, y). • │ • ╰╸ This contains no clauses that refer to 'b'. • │ Hint: Use the variable 'b' as an argument in a relation. ─────╯ [error]: Found unconstrained variable ╭──▶ TEST_DIR/program2.eclair@8:14-8:15 │ 8 │ ╭┤ reachable(x, z) :- • │ ┬ • │ ╰╸ The variable 'z' only occurs once. 9 │ ├┤ edge(x, y). • │ • ╰╸ This rule contains no other references to 'z'. • │ Hint: Replace the variable with a wildcard ('_'). │ Hint: Use the variable in another rule clause. ─────╯ [error]: Found unconstrained variable ╭──▶ TEST_DIR/program2.eclair@9:11-9:12 │ 8 │ ╭┤ reachable(x, z) :- 9 │ ├┤ edge(x, y). • │ ┬ • │ ╰╸ The variable 'y' only occurs once. • │ • ╰╸ This rule contains no other references to 'y'. • │ Hint: Replace the variable with a wildcard ('_'). │ Hint: Use the variable in another rule clause. ─────╯ [error]: Found unconstrained variable ╭──▶ TEST_DIR/program2.eclair@11:11-11:12 │ 11 │ ╭┤ reachable(a, b) :- • │ ┬ • │ ╰╸ The variable 'a' only occurs once. 12 │ ├┤ edge(x, y). • │ • ╰╸ This rule contains no other references to 'a'. • │ Hint: Replace the variable with a wildcard ('_'). │ Hint: Use the variable in another rule clause. ─────╯ [error]: Found unconstrained variable ╭──▶ TEST_DIR/program2.eclair@11:14-11:15 │ 11 │ ╭┤ reachable(a, b) :- • │ ┬ • │ ╰╸ The variable 'b' only occurs once. 12 │ ├┤ edge(x, y). • │ • ╰╸ This rule contains no other references to 'b'. • │ Hint: Replace the variable with a wildcard ('_'). │ Hint: Use the variable in another rule clause. ─────╯ [error]: Found unconstrained variable ╭──▶ TEST_DIR/program2.eclair@12:8-12:9 │ 11 │ ╭┤ reachable(a, b) :- 12 │ ├┤ edge(x, y). • │ ┬ • │ ╰╸ The variable 'x' only occurs once. • │ • ╰╸ This rule contains no other references to 'x'. • │ Hint: Replace the variable with a wildcard ('_'). │ Hint: Use the variable in another rule clause. ─────╯ [error]: Found unconstrained variable ╭──▶ TEST_DIR/program2.eclair@12:11-12:12 │ 11 │ ╭┤ reachable(a, b) :- 12 │ ├┤ edge(x, y). • │ ┬ • │ ╰╸ The variable 'y' only occurs once. • │ • ╰╸ This rule contains no other references to 'y'. • │ Hint: Replace the variable with a wildcard ('_'). │ Hint: Use the variable in another rule clause. ─────╯ //--- program3.eclair // RUN: %eclair compile %t/program3.eclair 2> %t/ungrounded_var_check_in_rule_body_actual.out // RUN: diff -w %t/ungrounded_var_check_in_rule_body_expected.out %t/ungrounded_var_check_in_rule_body_actual.out @def edge(u32, u32) input. @def reachable(u32, u32) output. reachable(x, z) :- edge(x, y), reachable(y, z), edge(a, b). //--- ungrounded_var_check_in_rule_body_expected.out [error]: Found unconstrained variable ╭──▶ TEST_DIR/program3.eclair@11:8-11:9 │ 8 │ ╭┤ reachable(x, z) :- 9 │ │ edge(x, y), 10 │ │ reachable(y, z), 11 │ ├┤ edge(a, b). • │ ┬ • │ ╰╸ The variable 'a' only occurs once. • │ • ╰╸ This rule contains no other references to 'a'. • │ Hint: Replace the variable with a wildcard ('_'). │ Hint: Use the variable in another rule clause. ─────╯ [error]: Found unconstrained variable ╭──▶ TEST_DIR/program3.eclair@11:11-11:12 │ 8 │ ╭┤ reachable(x, z) :- 9 │ │ edge(x, y), 10 │ │ reachable(y, z), 11 │ ├┤ edge(a, b). • │ ┬ • │ ╰╸ The variable 'b' only occurs once. • │ • ╰╸ This rule contains no other references to 'b'. • │ Hint: Replace the variable with a wildcard ('_'). │ Hint: Use the variable in another rule clause. ─────╯ //--- program4.eclair // RUN: %eclair compile %t/program4.eclair 2> %t/ungrounded_var_check_for_edge_cases.out // RUN: diff -w %t/ungrounded_var_check_for_edge_cases.out %t/ungrounded_var_check_for_edge_cases_actual.out @def bar(u32) input. @def foo(u32) output. foo(x) :- bar(x), y = y. foo(x) :- bar(x), a = b, b = a. foo(x) :- bar(x), a = b, b = c, c = a. //--- ungrounded_var_check_for_edge_cases_actual.out [error]: Ungrounded variable ╭──▶ TEST_DIR/program4.eclair@10:3-10:4 │ 8 │ ╭┤ foo(x) :- 9 │ │ bar(x), 10 │ ├┤ y = y. • │ ┬ • │ ╰╸ The variable 'y' is ungrounded, meaning it is not directly bound as an argument to a relation. • │ • ╰╸ This contains no clauses that refer to 'y'. • │ Hint: Use the variable 'y' as an argument in a relation. ─────╯ [error]: Ungrounded variable ╭──▶ TEST_DIR/program4.eclair@10:7-10:8 │ 8 │ ╭┤ foo(x) :- 9 │ │ bar(x), 10 │ ├┤ y = y. • │ ┬ • │ ╰╸ The variable 'y' is ungrounded, meaning it is not directly bound as an argument to a relation. • │ • ╰╸ This contains no clauses that refer to 'y'. • │ Hint: Use the variable 'y' as an argument in a relation. ─────╯ [error]: Ungrounded variable ╭──▶ TEST_DIR/program4.eclair@14:3-14:4 │ 12 │ ╭┤ foo(x) :- 13 │ │ bar(x), 14 │ │ a = b, • │ ┬ • │ ╰╸ The variable 'a' is ungrounded, meaning it is not directly bound as an argument to a relation. 15 │ ├┤ b = a. • │ • ╰╸ This contains no clauses that refer to 'a'. • │ Hint: Use the variable 'a' as an argument in a relation. ─────╯ [error]: Ungrounded variable ╭──▶ TEST_DIR/program4.eclair@14:7-14:8 │ 12 │ ╭┤ foo(x) :- 13 │ │ bar(x), 14 │ │ a = b, • │ ┬ • │ ╰╸ The variable 'b' is ungrounded, meaning it is not directly bound as an argument to a relation. 15 │ ├┤ b = a. • │ • ╰╸ This contains no clauses that refer to 'b'. • │ Hint: Use the variable 'b' as an argument in a relation. ─────╯ [error]: Ungrounded variable ╭──▶ TEST_DIR/program4.eclair@15:3-15:4 │ 12 │ ╭┤ foo(x) :- 13 │ │ bar(x), 14 │ │ a = b, 15 │ ├┤ b = a. • │ ┬ • │ ╰╸ The variable 'b' is ungrounded, meaning it is not directly bound as an argument to a relation. • │ • ╰╸ This contains no clauses that refer to 'b'. • │ Hint: Use the variable 'b' as an argument in a relation. ─────╯ [error]: Ungrounded variable ╭──▶ TEST_DIR/program4.eclair@15:7-15:8 │ 12 │ ╭┤ foo(x) :- 13 │ │ bar(x), 14 │ │ a = b, 15 │ ├┤ b = a. • │ ┬ • │ ╰╸ The variable 'a' is ungrounded, meaning it is not directly bound as an argument to a relation. • │ • ╰╸ This contains no clauses that refer to 'a'. • │ Hint: Use the variable 'a' as an argument in a relation. ─────╯ [error]: Ungrounded variable ╭──▶ TEST_DIR/program4.eclair@19:3-19:4 │ 17 │ ╭┤ foo(x) :- 18 │ │ bar(x), 19 │ │ a = b, • │ ┬ • │ ╰╸ The variable 'a' is ungrounded, meaning it is not directly bound as an argument to a relation. 20 │ │ b = c, 21 │ ├┤ c = a. • │ • ╰╸ This contains no clauses that refer to 'a'. • │ Hint: Use the variable 'a' as an argument in a relation. ─────╯ [error]: Ungrounded variable ╭──▶ TEST_DIR/program4.eclair@19:7-19:8 │ 17 │ ╭┤ foo(x) :- 18 │ │ bar(x), 19 │ │ a = b, • │ ┬ • │ ╰╸ The variable 'b' is ungrounded, meaning it is not directly bound as an argument to a relation. 20 │ │ b = c, 21 │ ├┤ c = a. • │ • ╰╸ This contains no clauses that refer to 'b'. • │ Hint: Use the variable 'b' as an argument in a relation. ─────╯ [error]: Ungrounded variable ╭──▶ TEST_DIR/program4.eclair@20:3-20:4 │ 17 │ ╭┤ foo(x) :- 18 │ │ bar(x), 19 │ │ a = b, 20 │ │ b = c, • │ ┬ • │ ╰╸ The variable 'b' is ungrounded, meaning it is not directly bound as an argument to a relation. 21 │ ├┤ c = a. • │ • ╰╸ This contains no clauses that refer to 'b'. • │ Hint: Use the variable 'b' as an argument in a relation. ─────╯ [error]: Ungrounded variable ╭──▶ TEST_DIR/program4.eclair@20:7-20:8 │ 17 │ ╭┤ foo(x) :- 18 │ │ bar(x), 19 │ │ a = b, 20 │ │ b = c, • │ ┬ • │ ╰╸ The variable 'c' is ungrounded, meaning it is not directly bound as an argument to a relation. 21 │ ├┤ c = a. • │ • ╰╸ This contains no clauses that refer to 'c'. • │ Hint: Use the variable 'c' as an argument in a relation. ─────╯ [error]: Ungrounded variable ╭──▶ TEST_DIR/program4.eclair@21:3-21:4 │ 17 │ ╭┤ foo(x) :- 18 │ │ bar(x), 19 │ │ a = b, 20 │ │ b = c, 21 │ ├┤ c = a. • │ ┬ • │ ╰╸ The variable 'c' is ungrounded, meaning it is not directly bound as an argument to a relation. • │ • ╰╸ This contains no clauses that refer to 'c'. • │ Hint: Use the variable 'c' as an argument in a relation. ─────╯ [error]: Ungrounded variable ╭──▶ TEST_DIR/program4.eclair@21:7-21:8 │ 17 │ ╭┤ foo(x) :- 18 │ │ bar(x), 19 │ │ a = b, 20 │ │ b = c, 21 │ ├┤ c = a. • │ ┬ • │ ╰╸ The variable 'a' is ungrounded, meaning it is not directly bound as an argument to a relation. • │ • ╰╸ This contains no clauses that refer to 'a'. • │ Hint: Use the variable 'a' as an argument in a relation. ─────╯ ================================================ FILE: tests/semantic_analysis/ungrounded_variables_arithmetic.eclair ================================================ // RUN: mkdir -p %t && sed s@TEST_DIR@%t@g %s > %t/input.test // RUN: split-file %t/input.test %t // RUN: %eclair compile %t/program1.eclair 2> %t/actual1.out // RUN: diff -w %t/expected1.out %t/actual1.out //--- program1.eclair @def a(u32) output. @def b(u32) output. b(123 + a). a(abc) :- def = 123, abc = 123 + def, b(abc). a(abc) :- abc = 123, // Should detect cycle correctly. def = abc + def. //--- expected1.out [error]: Ungrounded variable ╭──▶ TEST_DIR/program1.eclair@4:9-4:10 │ 4 │ b(123 + a). • ┬───────┬── • │ ╰╸ The variable 'a' is ungrounded, meaning it is not directly bound as an argument to a relation. • ╰╸ This contains no clauses that refer to 'a'. • │ Hint: Use the variable 'a' as an argument in a relation. ─────╯ [error]: Ungrounded variable ╭──▶ TEST_DIR/program1.eclair@14:3-14:6 │ 11 │ ╭┤ a(abc) :- 12 │ │ abc = 123, 13 │ │ // Should detect cycle correctly. 14 │ ├┤ def = abc + def. • │ ┬── • │ ╰╸ The variable 'def' is ungrounded, meaning it is not directly bound as an argument to a relation. • │ • ╰╸ This contains no clauses that refer to 'def'. • │ Hint: Use the variable 'def' as an argument in a relation. ─────╯ [error]: Ungrounded variable ╭──▶ TEST_DIR/program1.eclair@14:15-14:18 │ 11 │ ╭┤ a(abc) :- 12 │ │ abc = 123, 13 │ │ // Should detect cycle correctly. 14 │ ├┤ def = abc + def. • │ ┬── • │ ╰╸ The variable 'def' is ungrounded, meaning it is not directly bound as an argument to a relation. • │ • ╰╸ This contains no clauses that refer to 'def'. • │ Hint: Use the variable 'def' as an argument in a relation. ─────╯ ================================================ FILE: tests/semantic_analysis/ungrounded_variables_comparisons.eclair ================================================ // RUN: mkdir -p %t && sed s@TEST_DIR@%t@g %s > %t/input.test // RUN: split-file %t/input.test %t // RUN: %eclair compile %t/program1.eclair 2> %t/program1_actual.out // RUN: diff -w %t/program1_expected.out %t/program1_actual.out //--- program1.eclair @def fact(u32, u32) input. @def rule(u32, u32) output. rule(x, y) :- x < y, x <= y, x > y, x >= y, x != y, fact(x, y). //--- program1_expected.out //--- program2.eclair // RUN: %eclair compile %t/program2.eclair 2> %t/program2_actual.out // RUN: diff -w %t/program2_expected.out %t/program2_actual.out @def fact(u32, u32) input. @def rule(u32, u32) output. rule(x, y) :- x < z, z <= y, x > z, z >= y, x != z, fact(x, y). //--- program2_expected.out [error]: Ungrounded variable ╭──▶ TEST_DIR/program2.eclair@9:7-9:8 │ 8 │ ╭┤ rule(x, y) :- 9 │ │ x < z, • │ ┬ • │ ╰╸ The variable 'z' is ungrounded, meaning it is not directly bound as an argument to a relation. 10 │ │ z <= y, 11 │ │ x > z, 12 │ │ z >= y, 13 │ │ x != z, 14 │ ├┤ fact(x, y). • │ • ╰╸ This contains no clauses that refer to 'z'. • │ Hint: Use the variable 'z' as an argument in a relation. ─────╯ [error]: Ungrounded variable ╭──▶ TEST_DIR/program2.eclair@10:3-10:4 │ 8 │ ╭┤ rule(x, y) :- 9 │ │ x < z, 10 │ │ z <= y, • │ ┬ • │ ╰╸ The variable 'z' is ungrounded, meaning it is not directly bound as an argument to a relation. 11 │ │ x > z, 12 │ │ z >= y, 13 │ │ x != z, 14 │ ├┤ fact(x, y). • │ • ╰╸ This contains no clauses that refer to 'z'. • │ Hint: Use the variable 'z' as an argument in a relation. ─────╯ [error]: Ungrounded variable ╭──▶ TEST_DIR/program2.eclair@11:7-11:8 │ 8 │ ╭┤ rule(x, y) :- 9 │ │ x < z, 10 │ │ z <= y, 11 │ │ x > z, • │ ┬ • │ ╰╸ The variable 'z' is ungrounded, meaning it is not directly bound as an argument to a relation. 12 │ │ z >= y, 13 │ │ x != z, 14 │ ├┤ fact(x, y). • │ • ╰╸ This contains no clauses that refer to 'z'. • │ Hint: Use the variable 'z' as an argument in a relation. ─────╯ [error]: Ungrounded variable ╭──▶ TEST_DIR/program2.eclair@12:3-12:4 │ 8 │ ╭┤ rule(x, y) :- 9 │ │ x < z, 10 │ │ z <= y, 11 │ │ x > z, 12 │ │ z >= y, • │ ┬ • │ ╰╸ The variable 'z' is ungrounded, meaning it is not directly bound as an argument to a relation. 13 │ │ x != z, 14 │ ├┤ fact(x, y). • │ • ╰╸ This contains no clauses that refer to 'z'. • │ Hint: Use the variable 'z' as an argument in a relation. ─────╯ [error]: Ungrounded variable ╭──▶ TEST_DIR/program2.eclair@13:8-13:9 │ 8 │ ╭┤ rule(x, y) :- 9 │ │ x < z, 10 │ │ z <= y, 11 │ │ x > z, 12 │ │ z >= y, 13 │ │ x != z, • │ ┬ • │ ╰╸ The variable 'z' is ungrounded, meaning it is not directly bound as an argument to a relation. 14 │ ├┤ fact(x, y). • │ • ╰╸ This contains no clauses that refer to 'z'. • │ Hint: Use the variable 'z' as an argument in a relation. ─────╯ //--- program3.eclair // RUN: %eclair compile %t/program3.eclair 2> %t/program3_actual.out // RUN: diff -w %t/program3_expected.out %t/program3_actual.out @def fact(u32, u32) input. @def rule(u32, u32) output. rule(x, y) :- x = z, y = a, a < z, fact(x, y). rule(x, y) :- x = z, y = a, a < z, fact(z, y). //--- program3_expected.out ================================================ FILE: tests/semantic_analysis/ungrounded_variables_negations.eclair ================================================ // RUN: mkdir -p %t && sed s@TEST_DIR@%t@g %s > %t/input.test // RUN: split-file %t/input.test %t // RUN: %eclair compile %t/program1.eclair 2> %t/actual1.out // RUN: diff -w %t/expected1.out %t/actual1.out //--- program1.eclair @def a(u32) output. @def b(u32) output. a(abc) :- abc = 123, !b(abc), !b(def), !b(abc + 123). //--- expected1.out [error]: Ungrounded variable ╭──▶ TEST_DIR/program1.eclair@7:6-7:9 │ 4 │ ╭┤ a(abc) :- 5 │ │ abc = 123, 6 │ │ !b(abc), 7 │ │ !b(def), • │ ┬── • │ ╰╸ The variable 'def' is ungrounded, meaning it is not directly bound as an argument to a relation. 8 │ ├┤ !b(abc + 123). • │ • ╰╸ This contains no clauses that refer to 'def'. • │ Hint: Use the variable 'def' as an argument in a relation. ─────╯ [error]: Found unconstrained variable ╭──▶ TEST_DIR/program1.eclair@7:6-7:9 │ 4 │ ╭┤ a(abc) :- 5 │ │ abc = 123, 6 │ │ !b(abc), 7 │ │ !b(def), • │ ┬── • │ ╰╸ The variable 'def' only occurs once. 8 │ ├┤ !b(abc + 123). • │ • ╰╸ This rule contains no other references to 'def'. • │ Hint: Replace the variable with a wildcard ('_'). │ Hint: Use the variable in another rule clause. ─────╯ ================================================ FILE: tests/string_support/encode_decode_string.eclair ================================================ // RUN: split-file %s %t // RUN: %eclair compile %t/program.eclair > %t/program.ll // RUN: %clang -O0 -o %t/program %t/main.c %t/program.ll // RUN: %t/program | FileCheck %s // CHECK: 0 1 2 // CHECK-NEXT: Edge, Reachable, edge //--- program.eclair @def Edge(u32, u32) input. @def Reachable(u32, u32) output. Reachable(x, y) :- Edge(x, y). Reachable(x, y) :- Edge(x, z), Reachable(z, y). //--- main.c #include #include #include #include #include struct program; struct symbol { uint32_t length; const char* data; } __attribute__((packed)); extern struct program* eclair_program_init(); extern void eclair_program_destroy(struct program*); extern uint32_t eclair_encode_string(struct program*, uint32_t length, const char* str); extern struct symbol* eclair_decode_string(struct program*, uint32_t index); static const char* str1 = "Edge"; static const char* str2 = "Reachable"; static const char* str3 = "edge"; int main(int argc, char** argv) { struct program* prog = eclair_program_init(); uint32_t str_index1 = eclair_encode_string(prog, strlen(str1), str1); uint32_t str_index2 = eclair_encode_string(prog, strlen(str2), str2); uint32_t str_index3 = eclair_encode_string(prog, strlen(str3), str3); printf("%d %d %d\n", str_index1, str_index2, str_index3); struct symbol* symbol0 = eclair_decode_string(prog, 0); struct symbol* symbol1 = eclair_decode_string(prog, 1); struct symbol* symbol2 = eclair_decode_string(prog, str_index3); printf("%s, %s, %s\n", symbol0->data, symbol1->data, symbol2->data); eclair_program_destroy(prog); return 0; } ================================================ FILE: tests/string_support/encode_decode_string_native.eclair ================================================ // This checks if strings can be encoded / decoded between C <=> Eclair // RUN: split-file %s %t // RUN: %eclair compile %t/program.eclair > %t/program.ll // RUN: %clang -O0 -o %t/program %t/main.c %t/program.ll // RUN: %t/program | FileCheck %s // NOTE: fact is inserted at place 0, Test at place 1! // CHECK: 2 2 3 1 // CHECK-NEXT: fact, Test, some example text, some more text //--- program.eclair @def fact(u32) input output. @def Test(u32) input output. //--- main.c #include #include #include #include #include struct program; struct symbol { uint32_t length; const char* data; } __attribute__((packed)); extern struct program* eclair_program_init(); extern void eclair_program_destroy(struct program*); extern uint32_t eclair_encode_string(struct program*, uint32_t length, const char* str); extern struct symbol* eclair_decode_string(struct program*, uint32_t index); static const char* str1 = "some example text"; static const char* str2 = "some more text"; static const char* str3 = "Test"; int main(int argc, char** argv) { struct program* prog = eclair_program_init(); uint32_t str_index1 = eclair_encode_string(prog, strlen(str1), str1); uint32_t str_index2 = eclair_encode_string(prog, strlen(str1), str1); uint32_t str_index3 = eclair_encode_string(prog, strlen(str2), str2); uint32_t str_index4 = eclair_encode_string(prog, strlen(str3), str3); printf("%d %d %d %d\n", str_index1, str_index2, str_index3, str_index4); struct symbol* symbol0 = eclair_decode_string(prog, 0); struct symbol* symbol1 = eclair_decode_string(prog, 1); struct symbol* symbol2 = eclair_decode_string(prog, str_index1); struct symbol* symbol3 = eclair_decode_string(prog, str_index3); printf("%s, %s, %s, %s\n", symbol0->data, symbol1->data, symbol2->data, symbol3->data); eclair_program_destroy(prog); return 0; } ================================================ FILE: tests/string_support/encode_decode_string_wasm.eclair ================================================ // This checks if strings can be encoded / decoded between JS <=> Eclair // RUN: mkdir -p %t && sed s@TEST_DIR@%t@g %s > %t/input.test // RUN: split-file %t/input.test %t // TODO: we really need to have our own WASM allocator.. // RUN: test -e %t/walloc.c || wget https://raw.githubusercontent.com/wingo/walloc/master/walloc.c -O %t/walloc.c // RUN: %clang -O0 --target=wasm32 -mbulk-memory -nostdlib -c -o %t/walloc.o %t/walloc.c // RUN: %eclair compile --target wasm32 %t/program.eclair > %t/program.ll // RUN: %clang -O0 --target=wasm32 -mbulk-memory -nostdlib -c -o %t/program.o %t/program.ll // RUN: %wasm-ld --no-entry --import-memory --import-undefined -o %t/program.wasm %t/program.o %t/walloc.o // RUN: node %t/program.js | FileCheck %s // CHECK: 1 1 2 // CHECK-NEXT: fact some example text some more text //--- program.eclair @def fact(u32) input output. //--- program.js const fs = require("fs"); const encodeString = (memory, instance, program, string) => { const address = instance.exports.eclair_malloc(string.length * 3); const array = new Uint8Array(memory.buffer, address, string.length * 3); const { written } = new TextEncoder().encodeInto(string, array); const index = instance.exports.eclair_encode_string( program, written, address ); instance.exports.eclair_free(address); return index; }; const decodeString = (memory, instance, program, index) => { const address = instance.exports.eclair_decode_string(program, index); const symbolMemory = new Uint32Array(memory.buffer, address, 2); const [stringLength, byteArrayAddress] = symbolMemory; const bytes = new Uint8Array(memory.buffer, byteArrayAddress, stringLength); return new TextDecoder().decode(bytes); }; const main = () => { const bytes = fs.readFileSync("TEST_DIR/program.wasm"); const mod = new WebAssembly.Module(bytes); const memory = new WebAssembly.Memory({ initial: 10, maximum: 10 }); const imports = { env: { memory } }; const instance = new WebAssembly.Instance(mod, imports); const program = instance.exports.eclair_program_init(); const str = "some example text"; const str2 = "some more text"; const strIndex1 = encodeString(memory, instance, program, str); const strIndex2 = encodeString(memory, instance, program, str); const strIndex3 = encodeString(memory, instance, program, str2); console.log(strIndex1, strIndex2, strIndex3); console.log( decodeString(memory, instance, program, 0), decodeString(memory, instance, program, strIndex1), decodeString(memory, instance, program, strIndex3) ); instance.exports.eclair_program_destroy(program); }; main(); ================================================ FILE: tests/transpilation/souffle.eclair ================================================ // RUN: mkdir -p %t && sed s@TEST_DIR@%t@g %s > %t/input.test // RUN: split-file %t/input.test %t // RUN: %eclair compile --emit souffle %t/program.eclair > %t/actual_souffle.out // RUN: diff %t/expected_souffle.out %t/actual_souffle.out // RUN: %eclair compile --emit souffle %t/unsupported.eclair 2> %t/error.out // RUN: diff -w %t/expected_error.out %t/error.out // RUN: %eclair compile --emit souffle %t/unsupported2.eclair 2> %t/error.out // RUN: diff %t/expected_error2.out %t/error.out //--- program.eclair @def fact1(u32) input. @def fact2(u32) output. fact2(y) :- fact1(x), y = x + 3. fact2(x + 3) :- fact1(x). fact2((x + 1) + 2 * x) :- fact1(x). fact2(x) :- !fact1(x), fact1(x + 4), fact1(x + 4). fact2((8 - x) / x) :- fact1(x). @def fact3(u32, string) output. fact3(123, "abcd"). //--- expected_souffle.out .decl fact1(arg_0: unsigned) .input fact1 .decl fact2(arg_0: unsigned) .output fact2 fact2(y) :- fact1(x), y = (x + 3). fact2((x + 3)) :- fact1(x). fact2(((x + 1) + (2 * x))) :- fact1(x). fact2(x) :- !fact1(x), fact1((x + 4)), fact1((x + 4)). fact2(((8 - x) / x)) :- fact1(x). .decl fact3(arg_0: unsigned, arg_1: symbol) .output fact3 fact3(123, "abcd"). //--- unsupported.eclair @def fact1(u32) input. fact2(y) :- fact1(?). //--- expected_error.out [error]: Unsupported feature in Souffle ╭──▶ TEST_DIR/unsupported.eclair@4:9-4:10 │ 4 │ fact1(?). • ┬ • ╰╸ Souffle has no support for holes. • │ Hint: Replace the hole with a variable or literal. ─────╯ //--- unsupported2.eclair @extern match(string, string). //--- expected_error2.out [error]: Unsupported feature in Souffle ╭──▶ TEST_DIR/unsupported2.eclair@1:1-1:31 │ 1 │ @extern match(string, string). • ┬───────────────────────────── • ╰╸ Eclair can't transpile extern definitions yet. • │ Hint: Please open a github issue asking for this feature. ─────╯ ================================================ FILE: tests/typesystem/arg_count_mismatch.eclair ================================================ // RUN: mkdir -p %t && sed s@TEST_DIR@%t@g %s > %t/input.test // RUN: split-file %t/input.test %t // RUN: %eclair compile %t/program.eclair 2> %t/actual.out // RUN: diff -w %t/expected.out %t/actual.out //--- program.eclair @def edge(u32, u32, u32) output. @def path(u32) output. edge(1, 2). path(3, 4). @def fact1(u32, u32) input. @def fact2(u32, u32, u32) output. fact2(x, y) :- fact1(x, y). @def fact3(u32, u32) input. @def fact4(u32) output. fact4(x, y) :- fact3(x, y). @def fact5(u32, u32, u32) input. @def fact6(u32, u32) output. fact6(x, y) :- fact5(x, y). @def fact7(u32, u32) input. @def fact8(u32, u32) output. fact8(x, y) :- fact7(x, y, 123). @def a(u32, u32) output. @def b(u32) input. @def c(u32) input. a(x, y) :- b(x, 123), c(y, 456). //--- expected.out [error]: Found an unexpected amount of arguments for 'edge' ╭──▶ TEST_DIR/program.eclair@3:1-3:12 │ 1 │ @def edge(u32, u32, u32) output. • ┬─────────────────────────────── • ╰╸ 'edge' is defined with 3 arguments. • 3 │ edge(1, 2). • ┬────────── • ╰╸ 2 arguments are provided here. • │ Hint: You can solve this by passing exactly 3 arguments to 'edge'. ─────╯ [error]: Found an unexpected amount of arguments for 'path' ╭──▶ TEST_DIR/program.eclair@4:1-4:12 │ 2 │ @def path(u32) output. • ┬───────────────────── • ╰╸ 'path' is defined with 1 argument. • 4 │ path(3, 4). • ┬────────── • ╰╸ 2 arguments are provided here. • │ Hint: You can solve this by passing exactly 1 argument to 'path'. ─────╯ [error]: Found an unexpected amount of arguments for 'fact2' ╭──▶ TEST_DIR/program.eclair@9:1-10:15 │ 7 │ @def fact2(u32, u32, u32) output. • ┬──────────────────────────────── • ╰╸ 'fact2' is defined with 3 arguments. • 9 │ ╭┤ fact2(x, y) :- 10 │ ├┤ fact1(x, y). • │ • ╰╸ 2 arguments are provided here. • │ Hint: You can solve this by passing exactly 3 arguments to 'fact2'. ─────╯ [error]: Found an unexpected amount of arguments for 'fact4' ╭──▶ TEST_DIR/program.eclair@15:1-16:15 │ 13 │ @def fact4(u32) output. • ┬────────────────────── • ╰╸ 'fact4' is defined with 1 argument. • 15 │ ╭┤ fact4(x, y) :- 16 │ ├┤ fact3(x, y). • │ • ╰╸ 2 arguments are provided here. • │ Hint: You can solve this by passing exactly 1 argument to 'fact4'. ─────╯ [error]: Found an unexpected amount of arguments for 'fact5' ╭──▶ TEST_DIR/program.eclair@22:3-22:14 │ 18 │ @def fact5(u32, u32, u32) input. • ┬─────────────────────────────── • ╰╸ 'fact5' is defined with 3 arguments. • 22 │ fact5(x, y). • ┬────────── • ╰╸ 2 arguments are provided here. • │ Hint: You can solve this by passing exactly 3 arguments to 'fact5'. ─────╯ [error]: Found an unexpected amount of arguments for 'fact7' ╭──▶ TEST_DIR/program.eclair@28:3-28:19 │ 24 │ @def fact7(u32, u32) input. • ┬────────────────────────── • ╰╸ 'fact7' is defined with 2 arguments. • 28 │ fact7(x, y, 123). • ┬─────────────── • ╰╸ 3 arguments are provided here. • │ Hint: You can solve this by passing exactly 2 arguments to 'fact7'. ─────╯ [error]: Found an unexpected amount of arguments for 'b' ╭──▶ TEST_DIR/program.eclair@35:3-35:12 │ 31 │ @def b(u32) input. • ┬───────────────── • ╰╸ 'b' is defined with 1 argument. • 35 │ b(x, 123), • ┬──────── • ╰╸ 2 arguments are provided here. • │ Hint: You can solve this by passing exactly 1 argument to 'b'. ─────╯ [error]: Found an unexpected amount of arguments for 'c' ╭──▶ TEST_DIR/program.eclair@36:3-36:12 │ 32 │ @def c(u32) input. • ┬───────────────── • ╰╸ 'c' is defined with 1 argument. • 36 │ c(y, 456). • ┬──────── • ╰╸ 2 arguments are provided here. • │ Hint: You can solve this by passing exactly 1 argument to 'c'. ─────╯ ================================================ FILE: tests/typesystem/arithmetic.eclair ================================================ // RUN: mkdir -p %t && sed s@TEST_DIR@%t@g %s > %t/input.test // RUN: split-file %t/input.test %t //--- program1.eclair // RUN: %eclair compile --emit ra-transformed %t/program1.eclair | FileCheck %t/program1.eclair // CHECK: project @def number(u32) input. @def arithmetic(u32) output. arithmetic(123 + 456). arithmetic(456 - 123). arithmetic(123 * 456). arithmetic(123 / 456). arithmetic(123 * 456 + 789). arithmetic(x) :- number(x), x = 123 + (456 * 789). //--- program2.eclair // RUN: %eclair compile --emit ra-transformed %t/program2.eclair 2> %t/program2.out // RUN: diff -w %t/expected.out %t/program2.out @def number(u32) input. @def arithmetic(u32) output. arithmetic("abc" + 456). arithmetic(123 - "abc"). arithmetic(x) :- number(x), x = "abc" + (456 * "def"), "abc" = 1 + 2. //--- expected.out [error]: Type mismatch ╭──▶ TEST_DIR/program2.eclair@7:12-7:17 │ 7 │ arithmetic("abc" + 456). • ┬─────────────────────── • ╰───────────╸ 1) While checking the type of this.. • ├╸ 2) While checking the type of this.. • ├╸ 3) While checking the type of this.. • ╰╸ 4) Expected this to be of type 'u32', but it actually has type 'string'. ─────╯ [error]: Type mismatch ╭──▶ TEST_DIR/program2.eclair@8:18-8:23 │ 8 │ arithmetic(123 - "abc"). • ┬─────────────────────── • ╰─────────────────╸ 1) While checking the type of this.. • ╰──────╸ 2) While checking the type of this.. • ├╸ 3) While checking the type of this.. • ╰╸ 4) Expected this to be of type 'u32', but it actually has type 'string'. ─────╯ [error]: Type mismatch ╭──▶ TEST_DIR/program2.eclair@12:7-12:12 │ 10 │ ╭┤ arithmetic(x) :- 11 │ │ number(x), 12 │ │ x = "abc" + (456 * "def"), • │ ┬──────────────────────── • │ ╰────╸ 2) While checking the type of this.. • │ ├╸ 3) While inferring the type of this.. • │ ├╸ 4) While checking the type of this.. • │ ╰╸ 5) Expected this to be of type 'u32', but it actually has type 'string'. 13 │ ├┤ "abc" = 1 + 2. • │ • ╰╸ 1) While checking the type of this.. ─────╯ [error]: Type mismatch ╭──▶ TEST_DIR/program2.eclair@12:22-12:27 │ 10 │ ╭┤ arithmetic(x) :- 11 │ │ number(x), 12 │ │ x = "abc" + (456 * "def"), • │ ┬──────────────────────── • │ ╰───────────────────╸ 2) While checking the type of this.. • │ ╰───────────────╸ 3) While inferring the type of this.. • │ ╰──────╸ 4) While checking the type of this.. • │ ├╸ 5) While checking the type of this.. • │ ╰╸ 6) Expected this to be of type 'u32', but it actually has type 'string'. 13 │ ├┤ "abc" = 1 + 2. • │ • ╰╸ 1) While checking the type of this.. ─────╯ [error]: Type unification failure ╭──▶ TEST_DIR/program2.eclair@13:3-13:16 │ 10 │ ╭┤ arithmetic(x) :- 11 │ │ number(x), 12 │ │ x = "abc" + (456 * "def"), 13 │ ├┤ "abc" = 1 + 2. • │ ┬──────────── • │ ├╸ 2) While checking the type of this.. • │ ╰╸ 3) While unifying these types.. • │ • ╰╸ 1) While checking the type of this.. ─────╯ ================================================ FILE: tests/typesystem/comparisons.eclair ================================================ // NOTE: happy path already tested in lowering tests. // RUN: mkdir -p %t && sed s@TEST_DIR@%t@g %s > %t/input.test // RUN: split-file %t/input.test %t // RUN: %eclair compile %t/program.eclair 2> %t/actual.out // RUN: diff -w %t/expected.out %t/actual.out //--- program.eclair @def fact1(u32, u32) input. @def fact2(u32). fact2(x) :- fact1(x, _), x != "abc", "abc" != x. fact2(x) :- fact1(x, _), x < "abc", "abc" < x. fact2(x) :- fact1(x, _), x <= "abc", x >= "abc", x > "abc". //--- expected.out [error]: No output relations found ╭──▶ TEST_DIR/program.eclair@1:1-1:2 │ 1 │ @def fact1(u32, u32) input. • ┬ • ╰╸ This module does not produce any results • │ Hint: Add an 'output' qualifier to one of the relations defined in this module. ─────╯ [error]: Type unification failure ╭──▶ TEST_DIR/program.eclair@6:3-6:13 │ 4 │ ╭┤ fact2(x) :- 5 │ │ fact1(x, _), 6 │ │ x != "abc", • │ ┬───────── • │ ├╸ 2) While checking the type of this.. • │ ╰╸ 3) While unifying these types.. 7 │ ├┤ "abc" != x. • │ • ╰╸ 1) While checking the type of this.. ─────╯ [error]: Type unification failure ╭──▶ TEST_DIR/program.eclair@7:3-7:13 │ 4 │ ╭┤ fact2(x) :- 5 │ │ fact1(x, _), 6 │ │ x != "abc", 7 │ ├┤ "abc" != x. • │ ┬───────── • │ ├╸ 2) While checking the type of this.. • │ ╰╸ 3) While unifying these types.. • │ • ╰╸ 1) While checking the type of this.. ─────╯ [error]: Type mismatch ╭──▶ TEST_DIR/program.eclair@11:7-11:12 │ 9 │ ╭┤ fact2(x) :- 10 │ │ fact1(x, _), 11 │ │ x < "abc", • │ ┬──────── • │ ╰────╸ 2) While checking the type of this.. • │ ├╸ 3) While checking the type of this.. • │ ╰╸ 4) Expected this to be of type 'u32', but it actually has type 'string'. 12 │ ├┤ "abc" < x. • │ • ╰╸ 1) While checking the type of this.. ─────╯ [error]: Type mismatch ╭──▶ TEST_DIR/program.eclair@12:3-12:8 │ 9 │ ╭┤ fact2(x) :- 10 │ │ fact1(x, _), 11 │ │ x < "abc", 12 │ ├┤ "abc" < x. • │ ┬──────── • │ ├╸ 2) While checking the type of this.. • │ ├╸ 3) While checking the type of this.. • │ ╰╸ 4) Expected this to be of type 'u32', but it actually has type 'string'. • │ • ╰╸ 1) While checking the type of this.. ─────╯ [error]: Type mismatch ╭──▶ TEST_DIR/program.eclair@16:8-16:13 │ 14 │ ╭┤ fact2(x) :- 15 │ │ fact1(x, _), 16 │ │ x <= "abc", • │ ┬───────── • │ ╰─────╸ 2) While checking the type of this.. • │ ├╸ 3) While checking the type of this.. • │ ╰╸ 4) Expected this to be of type 'u32', but it actually has type 'string'. 17 │ │ x >= "abc", 18 │ ├┤ x > "abc". • │ • ╰╸ 1) While checking the type of this.. ─────╯ [error]: Type mismatch ╭──▶ TEST_DIR/program.eclair@17:8-17:13 │ 14 │ ╭┤ fact2(x) :- 15 │ │ fact1(x, _), 16 │ │ x <= "abc", 17 │ │ x >= "abc", • │ ┬───────── • │ ╰─────╸ 2) While checking the type of this.. • │ ├╸ 3) While checking the type of this.. • │ ╰╸ 4) Expected this to be of type 'u32', but it actually has type 'string'. 18 │ ├┤ x > "abc". • │ • ╰╸ 1) While checking the type of this.. ─────╯ [error]: Type mismatch ╭──▶ TEST_DIR/program.eclair@18:7-18:12 │ 14 │ ╭┤ fact2(x) :- 15 │ │ fact1(x, _), 16 │ │ x <= "abc", 17 │ │ x >= "abc", 18 │ ├┤ x > "abc". • │ ┬──────── • │ ╰────╸ 2) While checking the type of this.. • │ ├╸ 3) While checking the type of this.. • │ ╰╸ 4) Expected this to be of type 'u32', but it actually has type 'string'. • │ • ╰╸ 1) While checking the type of this.. ─────╯ ================================================ FILE: tests/typesystem/duplicate_type_declarations.eclair ================================================ // RUN: mkdir -p %t && sed s@TEST_DIR@%t@g %s > %t/input.test // RUN: split-file %t/input.test %t // RUN: %eclair compile %t/program.eclair 2> %t/actual.out // RUN: diff -w %t/expected.out %t/actual.out //--- program.eclair @def edge(u32, u32) output. @def edge(u32, u32) output. @def path(u32, u32) output. @def edge(u32, u32) output. @def path(u32, u32) output. //--- expected.out [error]: Multiple definitions for 'edge' ╭──▶ TEST_DIR/program.eclair@1:1-1:28 │ 1 │ @def edge(u32, u32) output. • ┬────────────────────────── • ╰╸ 'edge' is originally defined here. 2 │ @def edge(u32, u32) output. • ┬────────────────────────── • ╰╸ 'edge' is re-defined here. • 4 │ @def edge(u32, u32) output. • ┬────────────────────────── • ╰╸ 'edge' is re-defined here. • │ Hint: You can solve this by removing the duplicate definitions for 'edge'. ─────╯ [error]: Multiple definitions for 'path' ╭──▶ TEST_DIR/program.eclair@3:1-3:28 │ 3 │ @def path(u32, u32) output. • ┬────────────────────────── • ╰╸ 'path' is originally defined here. • 5 │ @def path(u32, u32) output. • ┬────────────────────────── • ╰╸ 'path' is re-defined here. • │ Hint: You can solve this by removing the duplicate definitions for 'path'. ─────╯ ================================================ FILE: tests/typesystem/extern_definitions.eclair ================================================ // RUN: mkdir -p %t && sed s@TEST_DIR@%t@g %s > %t/input.test // RUN: split-file %t/input.test %t //--- program1.eclair // RUN: %eclair compile --emit ra-transformed %t/program1.eclair | FileCheck %t/program1.eclair // CHECK: project @def edge(u32, u32) input. @def test_externs(u32) output. @extern constraint1(string). @extern func1(u32) u32. @extern func2(u32, u32) string. test_externs(func1(123)). test_externs(x) :- edge(x, y), constraint1("abc"), constraint1(func2(123, 456)), func1(y) + 1 = x, x = func1(y) + y, func1(x) = func1(y), func1(func1(x)) = y. //--- program2.eclair // RUN: %eclair compile --emit ra-transformed %t/program2.eclair 2> %t/program2.out // RUN: diff -w %t/expected.out %t/program2.out @extern constraint2(string). @extern func3(u32) u32. @extern func4(u32, u32) string. @def edge2(u32, u32) input. @def test_externs2(u32) output. // Type errors in functions test_externs2(func3("abc")). test_externs2(func3(123, 123)). test_externs2(func4(123, 123)). test_externs2(func4(123, 123) + 123). // Type errors in constraints test_externs2(x) :- edge2(x, 123), constraint2(x), constraint2("abc", "def"). // Using in wrong context test_externs2(constraint2("abc")). test_externs2(edge2(123, 456)). test_externs2(x) :- edge2(x, 123), func3(x). // Unknown extern definition test_externs2(x) :- edge2(x, 123), unknown_constraint(x), x = unknown_function(123). //--- expected.out [error]: Type mismatch ╭──▶ TEST_DIR/program2.eclair@12:21-12:26 │ 12 │ test_externs2(func3("abc")). • ┬─────────────────────────── • ╰────────────────────╸ 1) While checking the type of this.. • ╰──────╸ 2) While checking the type of this.. • ├╸ 3) While checking the type of this.. • ╰╸ 4) Expected this to be of type 'u32', but it actually has type 'string'. ─────╯ [error]: Found an unexpected amount of arguments for 'func3' ╭──▶ TEST_DIR/program2.eclair@13:15-13:30 │ 5 │ @extern func3(u32) u32. • ┬────────────────────── • ╰╸ 'func3' is defined with 1 argument. • 13 │ test_externs2(func3(123, 123)). • ┬────────────── • ╰╸ 2 arguments are provided here. • │ Hint: You can solve this by passing exactly 1 argument to 'func3'. ─────╯ [error]: Type mismatch ╭──▶ TEST_DIR/program2.eclair@14:15-14:30 │ 14 │ test_externs2(func4(123, 123)). • ┬────────────────────────────── • ╰──────────────╸ 1) While checking the type of this.. • ├╸ 2) While checking the type of this.. • ╰╸ 3) Expected this to be of type 'u32', but it actually has type 'string'. ─────╯ [error]: Type mismatch ╭──▶ TEST_DIR/program2.eclair@15:15-15:30 │ 15 │ test_externs2(func4(123, 123) + 123). • ┬──────────────────────────────────── • ╰──────────────╸ 1) While checking the type of this.. • ├╸ 2) While checking the type of this.. • ├╸ 3) While checking the type of this.. • ╰╸ 4) Expected this to be of type 'u32', but it actually has type 'string'. ─────╯ [error]: Type mismatch ╭──▶ TEST_DIR/program2.eclair@20:15-20:16 │ 18 │ ╭┤ test_externs2(x) :- 19 │ │ edge2(x, 123), 20 │ │ constraint2(x), • │ ┬ • │ ├╸ 2) While checking the type of this.. • │ ╰╸ 3) Expected this to be of type 'string', but it actually has type 'u32'. 21 │ ├┤ constraint2("abc", "def"). • │ • ╰╸ 1) While checking the type of this.. ─────╯ [error]: Found an unexpected amount of arguments for 'constraint2' ╭──▶ TEST_DIR/program2.eclair@21:3-21:28 │ 4 │ @extern constraint2(string). • ┬─────────────────────────── • ╰╸ 'constraint2' is defined with 1 argument. • 21 │ constraint2("abc", "def"). • ┬──────────────────────── • ╰╸ 2 arguments are provided here. • │ Hint: You can solve this by passing exactly 1 argument to 'constraint2'. ─────╯ [error]: Invalid use of constraint ╭──▶ TEST_DIR/program2.eclair@24:15-24:33 │ 4 │ @extern constraint2(string). • ┬─────────────────────────── • ╰╸ Previously defined as a constraint here. • 24 │ test_externs2(constraint2("abc")). • ┬───────────────── • ╰╸ Expected a function. • │ Hint: Maybe you meant to declare this as a function instead? │ Hint: Remove the invalid constraint. ─────╯ [error]: Invalid use of constraint ╭──▶ TEST_DIR/program2.eclair@25:15-25:30 │ 8 │ @def edge2(u32, u32) input. • ┬────────────────────────── • ╰╸ Previously defined as a constraint here. • 25 │ test_externs2(edge2(123, 456)). • ┬────────────── • ╰╸ Expected a function. • │ Hint: Maybe you meant to declare this as a function instead? │ Hint: Remove the invalid constraint. ─────╯ [error]: Invalid use of function ╭──▶ TEST_DIR/program2.eclair@28:3-28:11 │ 5 │ @extern func3(u32) u32. • ┬────────────────────── • ╰╸ Previously defined as a function here. • 28 │ func3(x). • ┬─────── • ╰╸ Expected a constraint here. • │ Hint: Maybe you meant to declare this an external constraint instead? │ Hint: Remove the invalid function. ─────╯ [error]: Missing type definition ╭──▶ TEST_DIR/program2.eclair@33:3-33:24 │ 33 │ unknown_constraint(x), • ┬──────────────────── • ╰╸ Could not find a type definition for 'unknown_constraint'. • │ Hint: Add a type definition for 'unknown_constraint'. │ Hint: Add an extern definition for 'unknown_constraint'. ─────╯ [error]: Missing type definition ╭──▶ TEST_DIR/program2.eclair@34:7-34:28 │ 34 │ x = unknown_function(123). • ┬──────────────────── • ╰╸ Could not find a type definition for 'unknown_function'. • │ Hint: Add an extern definition for 'unknown_function'. ─────╯ ================================================ FILE: tests/typesystem/negation.eclair ================================================ // RUN: mkdir -p %t && sed s@TEST_DIR@%t@g %s > %t/input.test // RUN: split-file %t/input.test %t //--- program1.eclair // RUN: %eclair compile %t/program1.eclair 2> %t/actual.out // RUN: diff -w %t/expected.out %t/actual.out @def fact1(u32) input. @def fact2(u32) output. fact2(x) :- fact1(x), !fact1(2), !fact1("abc"), !fact1(x + 1). //--- expected.out [error]: Type mismatch ╭──▶ TEST_DIR/program1.eclair@10:10-10:15 │ 7 │ ╭┤ fact2(x) :- 8 │ │ fact1(x), 9 │ │ !fact1(2), 10 │ │ !fact1("abc"), • │ ┬──────────── • │ ╰───────╸ 2) While checking the type of this.. • │ ╰──────╸ 3) While checking the type of this.. • │ ├╸ 4) While checking the type of this.. • │ ╰╸ 5) Expected this to be of type 'u32', but it actually has type 'string'. 11 │ ├┤ !fact1(x + 1). • │ • ╰╸ 1) While checking the type of this.. ─────╯ ================================================ FILE: tests/typesystem/no_rules_for_type.eclair ================================================ // RUN: %eclair compile %s | FileCheck %s @def edge(u32, u32) input. @def chain(u32, u32, u32) input output. // CHECK: chain ================================================ FILE: tests/typesystem/type_mismatch_in_rule.eclair ================================================ // RUN: mkdir -p %t && sed s@TEST_DIR@%t@g %s > %t/input.test // RUN: split-file %t/input.test %t // RUN: %eclair compile %t/program.eclair 2> %t/actual.out // RUN: diff -w %t/expected.out %t/actual.out //--- program.eclair @def fact1(u32) input. @def fact2(u32, string) output. fact2(123, x) :- fact1(x). //--- expected.out [error]: Type mismatch ╭──▶ TEST_DIR/program.eclair@4:9-4:10 │ 3 │ ╭┤ fact2(123, x) :- 4 │ ├┤ fact1(x). • │ ┬ • │ ├╸ 2) While checking the type of this.. • │ ╰╸ 3) Expected this to be of type 'u32', but it actually has type 'string'. • │ • ╰╸ 1) While checking the type of this.. ─────╯ ================================================ FILE: tests/typesystem/type_mismatch_in_rule_body.eclair ================================================ // RUN: mkdir -p %t && sed s@TEST_DIR@%t@g %s > %t/input.test // RUN: split-file %t/input.test %t // RUN: %eclair compile %t/program.eclair 2> %t/actual.out // RUN: diff -w %t/expected.out %t/actual.out //--- program.eclair @def fact1(u32, u32) input. @def fact2(u32) output. fact2(123) :- fact1("abc", 123), fact1(456, "def"), fact1("abc", "def"). @def fact3(u32) output. @def fact4(u32, string) input. fact3(123) :- fact4(x, x). //--- expected.out [error]: Type mismatch ╭──▶ TEST_DIR/program.eclair@4:9-4:14 │ 3 │ ╭┤ fact2(123) :- 4 │ │ fact1("abc", 123), • │ ┬──── • │ ├╸ 2) While checking the type of this.. • │ ╰╸ 3) Expected this to be of type 'u32', but it actually has type 'string'. 5 │ │ fact1(456, "def"), 6 │ ├┤ fact1("abc", "def"). • │ • ╰╸ 1) While checking the type of this.. ─────╯ [error]: Type mismatch ╭──▶ TEST_DIR/program.eclair@5:14-5:19 │ 3 │ ╭┤ fact2(123) :- 4 │ │ fact1("abc", 123), 5 │ │ fact1(456, "def"), • │ ┬──── • │ ├╸ 2) While checking the type of this.. • │ ╰╸ 3) Expected this to be of type 'u32', but it actually has type 'string'. 6 │ ├┤ fact1("abc", "def"). • │ • ╰╸ 1) While checking the type of this.. ─────╯ [error]: Type mismatch ╭──▶ TEST_DIR/program.eclair@6:9-6:14 │ 3 │ ╭┤ fact2(123) :- 4 │ │ fact1("abc", 123), 5 │ │ fact1(456, "def"), 6 │ ├┤ fact1("abc", "def"). • │ ┬──── • │ ├╸ 2) While checking the type of this.. • │ ╰╸ 3) Expected this to be of type 'u32', but it actually has type 'string'. • │ • ╰╸ 1) While checking the type of this.. ─────╯ [error]: Type mismatch ╭──▶ TEST_DIR/program.eclair@6:16-6:21 │ 3 │ ╭┤ fact2(123) :- 4 │ │ fact1("abc", 123), 5 │ │ fact1(456, "def"), 6 │ ├┤ fact1("abc", "def"). • │ ┬──── • │ ├╸ 2) While checking the type of this.. • │ ╰╸ 3) Expected this to be of type 'u32', but it actually has type 'string'. • │ • ╰╸ 1) While checking the type of this.. ─────╯ [error]: Type mismatch ╭──▶ TEST_DIR/program.eclair@11:12-11:13 │ 10 │ ╭┤ fact3(123) :- 11 │ ├┤ fact4(x, x). • │ ┬ • │ ├╸ 2) While checking the type of this.. • │ ╰╸ 3) Expected this to be of type 'string', but it actually has type 'u32'. • │ • ╰╸ 1) While checking the type of this.. ─────╯ ================================================ FILE: tests/typesystem/type_mismatch_in_rule_head.eclair ================================================ // RUN: mkdir -p %t && sed s@TEST_DIR@%t@g %s > %t/input.test // RUN: split-file %t/input.test %t // RUN: %eclair compile %t/program.eclair 2> %t/actual.out // RUN: diff -w %t/expected.out %t/actual.out //--- program.eclair @def edge(u32, u32) input. @def reachable(u32, u32) output. reachable(x, "abc") :- edge(x, x). reachable("abc", x) :- edge(x, x). reachable("abc", "abc") :- edge(x, x). @def fact1(u32) input. @def fact2(u32, string) output. fact2(x, x) :- fact1(x). //--- expected.out [error]: Type mismatch ╭──▶ TEST_DIR/program.eclair@3:14-3:19 │ 3 │ ╭┤ reachable(x, "abc") :- • │ ┬──── • │ ├╸ 2) While checking the type of this.. • │ ╰╸ 3) Expected this to be of type 'u32', but it actually has type 'string'. 4 │ ├┤ edge(x, x). • │ • ╰╸ 1) While checking the type of this.. ─────╯ [error]: Type mismatch ╭──▶ TEST_DIR/program.eclair@5:11-5:16 │ 5 │ ╭┤ reachable("abc", x) :- • │ ┬──── • │ ├╸ 2) While checking the type of this.. • │ ╰╸ 3) Expected this to be of type 'u32', but it actually has type 'string'. 6 │ ├┤ edge(x, x). • │ • ╰╸ 1) While checking the type of this.. ─────╯ [error]: Type mismatch ╭──▶ TEST_DIR/program.eclair@7:11-7:16 │ 7 │ ╭┤ reachable("abc", "abc") :- • │ ┬──── • │ ├╸ 2) While checking the type of this.. • │ ╰╸ 3) Expected this to be of type 'u32', but it actually has type 'string'. 8 │ ├┤ edge(x, x). • │ • ╰╸ 1) While checking the type of this.. ─────╯ [error]: Type mismatch ╭──▶ TEST_DIR/program.eclair@7:18-7:23 │ 7 │ ╭┤ reachable("abc", "abc") :- • │ ┬──── • │ ├╸ 2) While checking the type of this.. • │ ╰╸ 3) Expected this to be of type 'u32', but it actually has type 'string'. 8 │ ├┤ edge(x, x). • │ • ╰╸ 1) While checking the type of this.. ─────╯ [error]: Type mismatch ╭──▶ TEST_DIR/program.eclair@12:10-12:11 │ 12 │ ╭┤ fact2(x, x) :- • │ ┬ • │ ├╸ 2) While checking the type of this.. • │ ╰╸ 3) Expected this to be of type 'string', but it actually has type 'u32'. 13 │ ├┤ fact1(x). • │ • ╰╸ 1) While checking the type of this.. ─────╯ ================================================ FILE: tests/typesystem/type_mismatch_top_level_atoms.eclair ================================================ // RUN: mkdir -p %t && sed s@TEST_DIR@%t@g %s > %t/input.test // RUN: split-file %t/input.test %t // RUN: %eclair compile %t/program.eclair 2> %t/actual.out // RUN: diff -w %t/expected.out %t/actual.out //--- program.eclair @def fact1(u32, string) output. @def fact2(string, string) output. fact1(1, 2). fact1("abc", "def"). fact1("abc", 2). fact2(1, 2). //--- expected.out [error]: Type mismatch ╭──▶ TEST_DIR/program.eclair@3:10-3:11 │ 3 │ fact1(1, 2). • ┬─────────── • ╰─────────╸ 1) While checking the type of this.. • ├╸ 2) While checking the type of this.. • ╰╸ 3) Expected this to be of type 'string', but it actually has type 'u32'. ─────╯ [error]: Type mismatch ╭──▶ TEST_DIR/program.eclair@4:7-4:12 │ 4 │ fact1("abc", "def"). • ┬─────────────────── • ╰──────╸ 1) While checking the type of this.. • ├╸ 2) While checking the type of this.. • ╰╸ 3) Expected this to be of type 'u32', but it actually has type 'string'. ─────╯ [error]: Type mismatch ╭──▶ TEST_DIR/program.eclair@5:7-5:12 │ 5 │ fact1("abc", 2). • ┬─────────────── • ╰──────╸ 1) While checking the type of this.. • ├╸ 2) While checking the type of this.. • ╰╸ 3) Expected this to be of type 'u32', but it actually has type 'string'. ─────╯ [error]: Type mismatch ╭──▶ TEST_DIR/program.eclair@5:14-5:15 │ 5 │ fact1("abc", 2). • ┬─────────────── • ╰─────────────╸ 1) While checking the type of this.. • ├╸ 2) While checking the type of this.. • ╰╸ 3) Expected this to be of type 'string', but it actually has type 'u32'. ─────╯ [error]: Type mismatch ╭──▶ TEST_DIR/program.eclair@6:7-6:8 │ 6 │ fact2(1, 2). • ┬─────────── • ╰──────╸ 1) While checking the type of this.. • ├╸ 2) While checking the type of this.. • ╰╸ 3) Expected this to be of type 'string', but it actually has type 'u32'. ─────╯ [error]: Type mismatch ╭──▶ TEST_DIR/program.eclair@6:10-6:11 │ 6 │ fact2(1, 2). • ┬─────────── • ╰─────────╸ 1) While checking the type of this.. • ├╸ 2) While checking the type of this.. • ╰╸ 3) Expected this to be of type 'string', but it actually has type 'u32'. ─────╯ ================================================ FILE: tests/typesystem/typed_holes.eclair ================================================ // RUN: mkdir -p %t && sed s@TEST_DIR@%t@g %s > %t/input.test // RUN: split-file %t/input.test %t //--- program1.eclair // RUN: %eclair compile %t/program1.eclair 2> %t/actual1.out // RUN: diff -w %t/expected1.out %t/actual1.out @def edge(u32, u32) input. @def reachable(u32, u32) output. reachable(x, y) :- edge(x, y), edge(x, ?), x = ?. //--- expected1.out [error]: Found hole ╭──▶ TEST_DIR/program1.eclair@9:11-9:12 │ 7 │ ╭┤ reachable(x, y) :- 8 │ │ edge(x, y), 9 │ │ edge(x, ?), • │ ┬ • │ ├╸ 2) While checking the type of this.. • │ ╰╸ 3) Found hole with type 'u32'. 10 │ ├┤ x = ?. • │ • ╰╸ 1) While checking the type of this.. • │ Hint: Possible candidate: x :: 'u32' │ Hint: Possible candidate: y :: 'u32' ─────╯ [error]: Found hole ╭──▶ TEST_DIR/program1.eclair@10:7-10:8 │ 7 │ ╭┤ reachable(x, y) :- 8 │ │ edge(x, y), 9 │ │ edge(x, ?), 10 │ ├┤ x = ?. • │ ┬──── • │ ╰────╸ 2) While checking the type of this.. • │ ├╸ 3) While inferring the type of this.. • │ ╰╸ 4) Found hole with type 'u32'. • │ • ╰╸ 1) While checking the type of this.. • │ Hint: Possible candidate: x :: 'u32' │ Hint: Possible candidate: y :: 'u32' ─────╯ //--- program2.eclair // RUN: %eclair compile %t/program2.eclair 2> %t/actual2.out // RUN: diff %t/expected2.out %t/actual2.out @def fact(string, u32, u32) output. fact(?, 42, ?). //--- expected2.out [error]: Found hole ╭──▶ TEST_DIR/program2.eclair@6:6-6:7 │ 6 │ fact(?, 42, ?). • ┬────────────── • ╰─────╸ 1) While checking the type of this.. • ├╸ 2) While checking the type of this.. • ╰╸ 3) Found hole with type 'string'. ─────╯ [error]: Found hole ╭──▶ TEST_DIR/program2.eclair@6:13-6:14 │ 6 │ fact(?, 42, ?). • ┬────────────── • ╰────────────╸ 1) While checking the type of this.. • ├╸ 2) While checking the type of this.. • ╰╸ 3) Found hole with type 'u32'. ─────╯ //--- program3.eclair // RUN: %eclair compile %t/program3.eclair 2> %t/actual3.out // RUN: diff -w %t/expected3.out %t/actual3.out @def edge(u32, u32) input. @def reachable(u32, u32) output. reachable(x, z) :- edge(x, ?), reachable(y, z). //--- expected3.out [error]: Found unconstrained variable ╭──▶ TEST_DIR/program3.eclair@9:13-9:14 │ 7 │ ╭┤ reachable(x, z) :- 8 │ │ edge(x, ?), 9 │ ├┤ reachable(y, z). • │ ┬ • │ ╰╸ The variable 'y' only occurs once. • │ • ╰╸ This rule contains no other references to 'y'. • │ Hint: Replace the variable with a wildcard ('_'). │ Hint: Use the variable in another rule clause. ─────╯ [error]: Found hole ╭──▶ TEST_DIR/program3.eclair@8:11-8:12 │ 7 │ ╭┤ reachable(x, z) :- 8 │ │ edge(x, ?), • │ ┬ • │ ├╸ 2) While checking the type of this.. • │ ╰╸ 3) Found hole with type 'u32'. 9 │ ├┤ reachable(y, z). • │ • ╰╸ 1) While checking the type of this.. • │ Hint: Possible candidate: x :: 'u32' │ Hint: Possible candidate: y :: 'u32' │ Hint: Possible candidate: z :: 'u32' ─────╯ ================================================ FILE: tests/typesystem/unification_failure.eclair ================================================ // RUN: mkdir -p %t && sed s@TEST_DIR@%t@g %s > %t/input.test // RUN: split-file %t/input.test %t // RUN: %eclair compile %t/program.eclair 2> %t/actual.out // RUN: diff -w %t/expected.out %t/actual.out //--- program.eclair @def fact1(u32, u32) input. @def fact2(u32). fact2(x) :- fact1(x, _), x = "abc". @def fact3(u32, u32) input. @def fact4(u32). fact4(x) :- fact3(x, _), "abc" = x. @def fact5(string, string) input. @def fact6(string). fact6(x) :- fact5(x, _), x = 1. @def fact7(string, string) input. @def fact8(string). fact8(x) :- fact7(x, _), 1 = x. @def fact9(string, u32) input. @def fact10(string). fact10(x) :- fact9(x, y), x = y. @def fact11(u32, string) input. @def fact12(u32). fact12(x) :- x = y, fact11(x, z), y = z. @def fact13(u32, string) input. @def fact14(u32). fact14(x) :- x = y, y = z, fact13(x, z). @def fact15(u32, string) input. @def fact16(u32). fact16(x) :- x = y, y = z, z = a, fact15(x, a). @def fact17(u32, string) input. @def fact18(u32) output. fact18(x) :- x = y, y = z, fact17(x, a), z = a. //--- expected.out [error]: Type unification failure ╭──▶ TEST_DIR/program.eclair@5:3-5:12 │ 3 │ ╭┤ fact2(x) :- 4 │ │ fact1(x, _), 5 │ ├┤ x = "abc". • │ ┬──────── • │ ├╸ 2) While checking the type of this.. • │ ╰╸ 3) While unifying these types.. • │ • ╰╸ 1) While checking the type of this.. ─────╯ [error]: Type unification failure ╭──▶ TEST_DIR/program.eclair@11:3-11:12 │ 9 │ ╭┤ fact4(x) :- 10 │ │ fact3(x, _), 11 │ ├┤ "abc" = x. • │ ┬──────── • │ ├╸ 2) While checking the type of this.. • │ ╰╸ 3) While unifying these types.. • │ • ╰╸ 1) While checking the type of this.. ─────╯ [error]: Type unification failure ╭──▶ TEST_DIR/program.eclair@17:3-17:8 │ 15 │ ╭┤ fact6(x) :- 16 │ │ fact5(x, _), 17 │ ├┤ x = 1. • │ ┬──── • │ ├╸ 2) While checking the type of this.. • │ ╰╸ 3) While unifying these types.. • │ • ╰╸ 1) While checking the type of this.. ─────╯ [error]: Type unification failure ╭──▶ TEST_DIR/program.eclair@23:3-23:8 │ 21 │ ╭┤ fact8(x) :- 22 │ │ fact7(x, _), 23 │ ├┤ 1 = x. • │ ┬──── • │ ├╸ 2) While checking the type of this.. • │ ╰╸ 3) While unifying these types.. • │ • ╰╸ 1) While checking the type of this.. ─────╯ [error]: Type unification failure ╭──▶ TEST_DIR/program.eclair@29:3-29:8 │ 27 │ ╭┤ fact10(x) :- 28 │ │ fact9(x, y), 29 │ ├┤ x = y. • │ ┬──── • │ ├╸ 2) While checking the type of this.. • │ ╰╸ 3) While unifying these types.. • │ • ╰╸ 1) While checking the type of this.. ─────╯ [error]: Type unification failure ╭──▶ TEST_DIR/program.eclair@36:3-36:8 │ 33 │ ╭┤ fact12(x) :- 34 │ │ x = y, 35 │ │ fact11(x, z), 36 │ ├┤ y = z. • │ ┬──── • │ ├╸ 2) While checking the type of this.. • │ ╰╸ 3) While unifying these types.. • │ • ╰╸ 1) While checking the type of this.. ─────╯ [error]: Type mismatch ╭──▶ TEST_DIR/program.eclair@43:13-43:14 │ 40 │ ╭┤ fact14(x) :- 41 │ │ x = y, 42 │ │ y = z, 43 │ ├┤ fact13(x, z). • │ ┬ • │ ├╸ 2) While checking the type of this.. • │ ╰╸ 3) Expected this to be of type 'string', but it actually has type 'u32'. • │ • ╰╸ 1) While checking the type of this.. ─────╯ [error]: Type mismatch ╭──▶ TEST_DIR/program.eclair@51:13-51:14 │ 47 │ ╭┤ fact16(x) :- 48 │ │ x = y, 49 │ │ y = z, 50 │ │ z = a, 51 │ ├┤ fact15(x, a). • │ ┬ • │ ├╸ 2) While checking the type of this.. • │ ╰╸ 3) Expected this to be of type 'string', but it actually has type 'u32'. • │ • ╰╸ 1) While checking the type of this.. ─────╯ [error]: Type unification failure ╭──▶ TEST_DIR/program.eclair@59:3-59:8 │ 55 │ ╭┤ fact18(x) :- 56 │ │ x = y, 57 │ │ y = z, 58 │ │ fact17(x, a), 59 │ ├┤ z = a. • │ ┬──── • │ ├╸ 2) While checking the type of this.. • │ ╰╸ 3) While unifying these types.. • │ • ╰╸ 1) While checking the type of this.. ─────╯ ================================================ FILE: tests/typesystem/unknown_atom_in_rule_body.eclair ================================================ // RUN: mkdir -p %t && sed s@TEST_DIR@%t@g %s > %t/input.test // RUN: split-file %t/input.test %t // RUN: %eclair compile %t/program.eclair 2> %t/actual.out // RUN: diff -w %t/expected.out %t/actual.out //--- program.eclair @def path(u32, u32) output. path(x, y) :- edge(x, y). //--- expected.out [error]: Ungrounded variable ╭──▶ TEST_DIR/program.eclair@3:6-3:7 │ 3 │ ╭┤ path(x, y) :- • │ ┬ • │ ╰╸ The variable 'x' is ungrounded, meaning it is not directly bound as an argument to a relation. 4 │ ├┤ edge(x, y). • │ • ╰╸ This contains no clauses that refer to 'x'. • │ Hint: Use the variable 'x' as an argument in a relation. ─────╯ [error]: Ungrounded variable ╭──▶ TEST_DIR/program.eclair@3:9-3:10 │ 3 │ ╭┤ path(x, y) :- • │ ┬ • │ ╰╸ The variable 'y' is ungrounded, meaning it is not directly bound as an argument to a relation. 4 │ ├┤ edge(x, y). • │ • ╰╸ This contains no clauses that refer to 'y'. • │ Hint: Use the variable 'y' as an argument in a relation. ─────╯ [error]: Missing type definition ╭──▶ TEST_DIR/program.eclair@4:3-4:13 │ 4 │ edge(x, y). • ┬───────── • ╰╸ Could not find a type definition for 'edge'. • │ Hint: Add a type definition for 'edge'. │ Hint: Add an extern definition for 'edge'. ─────╯ ================================================ FILE: tests/typesystem/unknown_atom_in_rule_head.eclair ================================================ // RUN: mkdir -p %t && sed s@TEST_DIR@%t@g %s > %t/input.test // RUN: split-file %t/input.test %t // RUN: %eclair compile %t/program.eclair 2> %t/actual.out // RUN: diff -w %t/expected.out %t/actual.out //--- program.eclair @def edge(u32, u32) input. path(x, y) :- edge(x, y). @def out(u32) output. //--- expected.out [error]: Missing type definition ╭──▶ TEST_DIR/program.eclair@3:1-4:14 │ 3 │ ╭┤ path(x, y) :- 4 │ ├┤ edge(x, y). • │ • ╰╸ Could not find a type definition for 'path'. • │ Hint: Add a type definition for 'path'. │ Hint: Add an extern definition for 'path'. ─────╯ ================================================ FILE: tests/typesystem/unknown_atoms.eclair ================================================ // RUN: mkdir -p %t && sed s@TEST_DIR@%t@g %s > %t/input.test // RUN: split-file %t/input.test %t // RUN: %eclair compile %t/program.eclair 2> %t/actual.out // RUN: diff -w %t/expected.out %t/actual.out //--- program.eclair top_level_atom(1). path(x, y) :- edge(x, y). @def result(u32) output. //--- expected.out [error]: Ungrounded variable ╭──▶ TEST_DIR/program.eclair@3:6-3:7 │ 3 │ ╭┤ path(x, y) :- • │ ┬ • │ ╰╸ The variable 'x' is ungrounded, meaning it is not directly bound as an argument to a relation. 4 │ ├┤ edge(x, y). • │ • ╰╸ This contains no clauses that refer to 'x'. • │ Hint: Use the variable 'x' as an argument in a relation. ─────╯ [error]: Ungrounded variable ╭──▶ TEST_DIR/program.eclair@3:9-3:10 │ 3 │ ╭┤ path(x, y) :- • │ ┬ • │ ╰╸ The variable 'y' is ungrounded, meaning it is not directly bound as an argument to a relation. 4 │ ├┤ edge(x, y). • │ • ╰╸ This contains no clauses that refer to 'y'. • │ Hint: Use the variable 'y' as an argument in a relation. ─────╯ [error]: Missing type definition ╭──▶ TEST_DIR/program.eclair@1:1-1:19 │ 1 │ top_level_atom(1). • ┬───────────────── • ╰╸ Could not find a type definition for 'top_level_atom'. • │ Hint: Add a type definition for 'top_level_atom'. │ Hint: Add an extern definition for 'top_level_atom'. ─────╯ [error]: Missing type definition ╭──▶ TEST_DIR/program.eclair@3:1-4:14 │ 3 │ ╭┤ path(x, y) :- 4 │ ├┤ edge(x, y). • │ • ╰╸ Could not find a type definition for 'path'. • │ Hint: Add a type definition for 'path'. │ Hint: Add an extern definition for 'path'. ─────╯ [error]: Missing type definition ╭──▶ TEST_DIR/program.eclair@4:3-4:13 │ 4 │ edge(x, y). • ┬───────── • ╰╸ Could not find a type definition for 'edge'. • │ Hint: Add a type definition for 'edge'. │ Hint: Add an extern definition for 'edge'. ─────╯ ================================================ FILE: tests/typesystem/unknown_top_level_atoms.eclair ================================================ // RUN: mkdir -p %t && sed s@TEST_DIR@%t@g %s > %t/input.test // RUN: split-file %t/input.test %t // RUN: %eclair compile %t/program.eclair 2> %t/actual.out // RUN: diff %t/expected.out %t/actual.out //--- program.eclair edge(1, 2). path(3, 4). @def result(u32) output. //--- expected.out [error]: Missing type definition ╭──▶ TEST_DIR/program.eclair@1:1-1:12 │ 1 │ edge(1, 2). • ┬────────── • ╰╸ Could not find a type definition for 'edge'. • │ Hint: Add a type definition for 'edge'. │ Hint: Add an extern definition for 'edge'. ─────╯ [error]: Missing type definition ╭──▶ TEST_DIR/program.eclair@2:1-2:12 │ 2 │ path(3, 4). • ┬────────── • ╰╸ Could not find a type definition for 'path'. • │ Hint: Add a type definition for 'path'. │ Hint: Add an extern definition for 'path'. ─────╯ ================================================ FILE: tests/typesystem/valid.eclair ================================================ // RUN: %eclair compile %s --emit ra-transformed | FileCheck %s // CHECK: project @def fact1(u32, string). @def fact2(string, string). fact1(1, "a"). fact2("abc", "def"). @def edge(u32, u32) input. @def reachable(u32, u32) output. reachable(x, y) :- edge(x, y). reachable(1, 2) :- edge(x, x). @def fact3(u32, u32) input. @def fact4(u32). fact4(123) :- fact3(123, 456). @def fact5(u32). @def fact6(u32, u32) input. fact5(123) :- fact6(x, x). @def fact7(u32) input. @def fact8(u32, string). @def fact9(string) input. fact8(x, y) :- fact7(x), fact9(y). @def fact10(u32, u32, string) input. @def fact11(u32). fact11(x) :- fact10(x, _, _), fact10(_, x, _). @def fact12(u32, u32) input. @def fact13(u32). fact13(x) :- fact12(x, _), x = 123. fact13(x) :- fact12(x, _), 123 = x. fact13(x) :- fact12(x, y), x = y. fact13(x) :- fact12(x, _), 123 = 456. fact13(x) :- fact12(x, _), "abc" = "def". @def fact14(string, string) input. @def fact15(string). fact15(x) :- fact14(x, _), "abc" = x. fact15(x) :- fact14(x, _), x = "abc". @def fact16(u32, u32) input. @def fact17(u32) output. fact17(x) :- x = y, fact16(x, z), y = z. fact17(x) :- x = y, y = z, fact16(x, z). @def fact18(u32) input. @def fact19(u32) output. fact19(x) :- fact18(x), !fact18(2), !fact18(x + 1). ================================================ FILE: tests/utils/extract_snippet ================================================ #!/bin/bash # Print lines starting from a match on a pattern ($2), up to first empty line. # Use sed again to trim the trailing newline sed -n "/^.*$2.*/,/^\$/p" $1 | sed '/^$/d'