Skip to content

Commit

Permalink
Improved warmup doc, performance and information
Browse files Browse the repository at this point in the history
Attributes are prepended correctly
Debugging in purity analyzer
Version checking in tables implemented, but not used yet
Peephole fix where parens unnecessary removed
Versioning slightly improved
tria.clean and tria.report tasks introduced
README for tria.report improved
  • Loading branch information
hissssst committed Aug 27, 2023
1 parent f147af1 commit d65f750
Show file tree
Hide file tree
Showing 12 changed files with 261 additions and 17 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ This compiles the deps, and warms up the cache of used functions

3. `map.field` optimization. This construction is now 3 times faster

## Reporting bugs

Just use `mix tria.report "INSERT BUG TITLE HERE"` and it will automatically open tracker with information about system and env prefilled

## How to debug

```sh
Expand Down
67 changes: 67 additions & 0 deletions lib/mix/tasks/tria.clean.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
defmodule Mix.Tasks.Tria.Clean do
use Mix.Task

@moduledoc """
# Cache cleanup
Tria uses cache ets files located in `priv` directory to
optimize evaluation of some function traits like purity
or safety. This manually deletes the caches.
### Example:
```sh
mix tria.clean -y
```
### Options
* `-y` or `--yes` -- to skip confirmation step
"""

@shortdoc "Deletes tria cache files"

def run(args) do
priv = "#{:code.priv_dir(:tria)}"

to_delete =
priv
|> File.ls!()
|> Enum.flat_map(fn filename ->
if String.ends_with?(filename, "_cache.ets") do
priv
|> Path.join(filename)
|> info()
|> List.wrap()
else
[]
end
end)

confirmed? =
with false <- args in [["-y"], ["--yes"]] do
result = IO.gets("Confirm? [Y(yes); n(no)] ") in ["y\n", "Y\n", "\n"]
unless result do
IO.write [IO.ANSI.red(), "Denied. Nothing cleaned\n", IO.ANSI.reset()]
end
result
end

if confirmed? do
Enum.each(to_delete, &File.rm/1)
end
end

defp info(cache) do
IO.write [
"Deleting file: ",
IO.ANSI.green(),
cache,
IO.ANSI.reset(),
"\n"
]

cache
end

end
91 changes: 91 additions & 0 deletions lib/mix/tasks/tria.report.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
defmodule Mix.Tasks.Tria.Report do
use Mix.Task

@moduledoc """
# Reports Tria bug
Simple task which opens up your default web browser
with all automatically gatherable information prefilled
in a github issue in compiler's tracker
### Example:
Basically you may want to run something like this once
```sh
$ TRIA_DEBUG=1 mix compile > file.log
$ mix tria.report "Bug in compilation"
```
"""

@shortdoc "Creates issue on compiler's tracker automatically"

defmacrop safe(code) do
quote do
try do
unquote(code)
rescue
_ -> nil
end
end
end

def run(args) do
title = List.first(args) || "INSERT TITLE HERE"
query = URI.encode_query(%{title: "Report: #{title}", body: body(title)})
uri = "https://github.com/hissssst/tria/issues/new?" <> query
System.cmd("xdg-open", [uri])
end

defp body(title) do
"""
# #{title}
<!-- Short desription of an issue and a link to the source code here -->
## Environment
### Versions
* System info: `#{safe inspect :os.type()} #{safe inspect :os.version()}`
* Erlang: `#{safe List.to_string :erlang.system_info :system_version}`
* Elixir: `#{safe System.version()}`
* Tria: `#{Tria.version()}`
### Configuration
* compilers: `#{safe inspect Mix.Project.config()[:compilers]}`
* protocol consolidation: `#{safe inspect Mix.Project.config()[:consolidate_protocols]}`
* tria app env: `#{safe inspect Application.get_all_env(:tria)}`
### System environment
```
#{safe system_env()}
```
## Expected behaiour
<!-- Desription of how you think Tria should behave -->
## Actual behaviour
<!-- Desription of how Tria actually behaved -->
## Steps to reproduce
<!-- Steps to follow to reporduce the bug. The more precise the steps are, the better -->
"""
end

@vars ~w[
TRIA_DEBUG
TRIA_TRACE
]

defp system_env do
Enum.map_join(@vars, "\n", fn var ->
"#{var}=#{System.get_env(var)}"
end)
end
end
39 changes: 33 additions & 6 deletions lib/mix/tasks/tria.warmup.ex
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,20 @@ defmodule Mix.Tasks.Tria.Warmup do
mix tria.warmup --available
```
During analysis a question may pop up, for example:
```
:cover.analyse(:coverage, :line)
Is pure? [y(yes); n(no); S(show); s(stack)]
```
In this case, you need to respond whether `:cover.analyze/2` is a pure function. You don't
need to take a purity of expressions in arguments into account, they're there just to provide
context.
This is required since some functions in OTP are bifs or nifs, and they can't be analyzed
for purity by Tria.
### Options:
* `--loaded` -- to warm up cache only from modules which are loaded by default
Expand Down Expand Up @@ -49,23 +63,32 @@ defmodule Mix.Tasks.Tria.Warmup do

def check(modules) do
length = length modules
modules
|> Stream.with_index(1)
|> Enum.each(fn {module, index} ->

Enum.reduce(modules, 1, fn module, index ->
with(
{:ok, object_code} <- Beam.object_code(module),
{:ok, abstract_code} <- Beam.abstract_code(object_code)
) do
functions = Beam.functions(abstract_code)
flength = length(functions)

functions
|> Stream.with_index(1)
|> Enum.each(fn {{function, arity}, findex} ->
Enum.reduce(functions, {1, now()}, fn {function, arity}, {findex, ts} ->
status(length, index, module, flength, findex)
Purity.check_analyze_mfarity({module, function, arity})
now = now()
difference = now - ts
if difference > 1000 do
IO.write [
IO.ANSI.clear_line(),
"\r#{inspect module}.#{function}/#{arity} took #{difference}ms to analyze!\n"
]
end

{findex + 1, now}
end)
end

index + 1
end)
end

Expand Down Expand Up @@ -95,4 +118,8 @@ defmodule Mix.Tasks.Tria.Warmup do
]
end

defp now do
:erlang.monotonic_time(:millisecond)
end

end
10 changes: 9 additions & 1 deletion lib/tria.ex
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,15 @@ defmodule Tria do
alias Tria.Compiler.ElixirTranslator

defmacro __using__(opts) do
quote do: use Tria.Compiler.Callbacks, unquote(opts)
quote do: use(Tria.Compiler.Callbacks, unquote(opts))
end

def version do
unquote(Mix.Project.config()[:version])
<> "-"
<> System.version()
<> "-"
<> List.to_string(:erlang.system_info(:otp_release))
end

# Public interface
Expand Down
26 changes: 26 additions & 0 deletions lib/tria/compiler.ex
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,19 @@ defmodule Tria.Compiler do
end
|> prepend_attrs(tail)
end
defp prepend_attrs({:__block__, _, lines}, [{name, values} | tail]) do
attrs =
Enum.map(values, fn value ->
quote do: @(unquote(name)(unquote(Macro.escape value)))
end)

quote do
Module.register_attribute(__MODULE__, unquote(name), persist: true, accumulate: true)
unquote_splicing attrs
unquote_splicing lines
end
|> prepend_attrs(tail)
end
defp prepend_attrs(quoted, [{name, value} | tail]) when is_special_attribute(name) do
quote do
@(unquote(name)(unquote(value)))
Expand All @@ -459,6 +472,19 @@ defmodule Tria.Compiler do
end
|> prepend_attrs(tail)
end
defp prepend_attrs(quoted, [{name, values} | tail]) do
attrs =
Enum.map(values, fn value ->
quote do: @(unquote(name)(unquote(Macro.escape value)))
end)

quote do
Module.register_attribute(__MODULE__, unquote(name), persist: true, accumulate: true)
unquote_splicing attrs
unquote quoted
end
|> prepend_attrs(tail)
end

defp put_file(ast, file) do
prewalk(ast, fn
Expand Down
4 changes: 2 additions & 2 deletions lib/tria/language/analyzer/providers/tty_provider.ex
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,11 @@ defmodule Tria.Language.Analyzer.TTYProvider do
end

defp prompt(trait, {m, f, a}, %{stack: stack}) when not is_empty(stack) do
"\n#{ast_to_string {{:".", [], [m, f]}, [], a}}\n\nIs #{trait} [y(yes); n(no); S(show); s(stack)] "
"\n#{ast_to_string {{:".", [], [m, f]}, [], a}}\n\nIs #{trait}? [y(yes); n(no); S(show); s(stack)] "
end

defp prompt(trait, {m, f, a}, _) do
"\n#{ast_to_string {{:".", [], [m, f]}, [], a}}\n\nIs #{trait} [y(yes); n(no); S(show)] "
"\n#{ast_to_string {{:".", [], [m, f]}, [], a}}\n\nIs #{trait}? [y(yes); n(no); S(show)] "
end

end
7 changes: 7 additions & 0 deletions lib/tria/language/analyzer/purity.ex
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ defmodule Tria.Language.Analyzer.Purity do
import Tria.Language
import Tria.Language.MFArity, only: :macros

alias Tria.Debug
alias Tria.Debug.Tracer
alias Tria.Language.Analyzer
alias Tria.Language.Analyzer.Provider
Expand Down Expand Up @@ -97,6 +98,12 @@ defmodule Tria.Language.Analyzer.Purity do
def check_analyze_mfarity({module, function, arity}) do
call = dot_call(module, function, List.duplicate(nil, arity))
check_analyze(call)
rescue
e ->
if Debug.debugging?(:check_analyze_mfarity) do
IO.puts "Failed during analysis of #{module}.#{function}/#{arity}"
end
reraise e, __STACKTRACE__
end

@spec invalidate(module()) :: :ok
Expand Down
13 changes: 13 additions & 0 deletions lib/tria/language/function_repo/persister.ex
Original file line number Diff line number Diff line change
Expand Up @@ -137,4 +137,17 @@ defmodule Tria.Language.FunctionRepo.Persister do
end
end

defp check_version(table) do

Check warning on line 140 in lib/tria/language/function_repo/persister.ex

View workflow job for this annotation

GitHub Actions / CI

function check_version/1 is unused
case :ets.lookup(table, :__tria_version__) do
[__tria_version__: version] ->
version == Tria.version()

_ ->
false
end
end

defp put_version(table) do

Check warning on line 150 in lib/tria/language/function_repo/persister.ex

View workflow job for this annotation

GitHub Actions / CI

function put_version/1 is unused
:ets.insert(table, [__tria_version__: Tria.version()])
end
end
2 changes: 1 addition & 1 deletion lib/tria/optimizer/pass/evaluation.ex
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ defmodule Tria.Optimizer.Pass.Evaluation do
{ {:=, meta, [left, right]}, state }

:no ->
q = quote do: raise MatchError, term: unquote right
q = quote do: raise(MatchError, term: unquote(right))
{ q, hit state }
end

Expand Down
8 changes: 2 additions & 6 deletions lib/tria/optimizer/pass/peephole.ex
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,12 @@ defmodule Tria.Optimizer.Pass.Peephole do
tri :erlang.map_get(key, input)

_ ->
no_parens ast
ast
end


_ ->
no_parens ast
ast
end

other ->
Expand Down Expand Up @@ -102,8 +102,4 @@ defmodule Tria.Optimizer.Pass.Peephole do
end
end

defp no_parens({name, meta, args}) do
{name, [{:no_parens, true} | meta], args}
end

end
Loading

0 comments on commit d65f750

Please sign in to comment.