Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
home:Ledest:erlang:24
erlang
2371-compiler-Pretty-print-types-in-BEAM-SSA-de...
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File 2371-compiler-Pretty-print-types-in-BEAM-SSA-debug-dumps.patch of Package erlang
From 38ee18fbd6349d5dd2ac07f74167895de06bf793 Mon Sep 17 00:00:00 2001 From: Frej Drejhammar <frej.drejhammar@gmail.com> Date: Wed, 12 May 2021 15:31:27 +0200 Subject: [PATCH] compiler: Pretty-print types in BEAM SSA debug dumps To help compiler developers, the compiler can be asked, during compilation, to dump its internal BEAM SSA representation of the compiled modules to a textual format. In it, type information is printed as raw terms using the format specifier `~p` which for the more complex types such as `#t_union{}` and `#t_tuple{}` become rather hard to read. This patch extends the BEAM SSA dumper with a pretty-printer for type information. The BEAM SSA type information is similar to, but not the same as, the representation used by Dialyzer so we can therefore not use Dialyzer's type printer. The textual representation of BEAM SSA types is designed to be as close to the Erlang Type Language as possible. Included in this patch is a test which checks that an ssa dump contains the pretty printed types we expect for all but three output forms. The exceptions are the bs_context and bs_matchable which never seems to occur in function result types, and the interval form of floats as the author is unable to construct a function which produces such a result type (nor does such a type occur in any module compiled by the diffable script). --- lib/compiler/src/beam_ssa_pp.erl | 106 ++++++++++++++- lib/compiler/test/compile_SUITE.erl | 92 ++++++++++++- .../test/compile_SUITE_data/types_pp.erl | 127 ++++++++++++++++++ 3 files changed, 322 insertions(+), 3 deletions(-) create mode 100644 lib/compiler/test/compile_SUITE_data/types_pp.erl diff --git a/lib/compiler/src/beam_ssa_pp.erl b/lib/compiler/src/beam_ssa_pp.erl index adb8533a81..e5ebbaed13 100644 --- a/lib/compiler/src/beam_ssa_pp.erl +++ b/lib/compiler/src/beam_ssa_pp.erl @@ -22,6 +22,7 @@ -export([format_function/1,format_instr/1,format_var/1]). -include("beam_ssa.hrl"). +-include("beam_types.hrl"). -spec format_function(beam_ssa:b_function()) -> iolist(). @@ -115,7 +116,7 @@ format_param_info([], _Break) -> format_type(T, Break) -> %% Gross hack, but it's short and simple. - Indented = lists:flatten(io_lib:format("~p", [T])), + Indented = lists:flatten(format_type(T)), string:replace(Indented, [$\n], Break, all). format_blocks(Ls, Blocks, Anno) -> @@ -274,3 +275,106 @@ format_live_interval(#b_var{}=Dst, #{live_intervals:=Intervals}) -> end; format_live_interval(_, _) -> []. +format_type(any) -> + "any()"; +format_type(#t_atom{elements=any}) -> + "atom()"; +format_type(#t_atom{elements=Es}) -> + string:join([io_lib:format("'~p'", [E]) + || E <- ordsets:to_list(Es)], " | "); +format_type(#t_bs_matchable{tail_unit=U}) -> + io_lib:format("bs_matchable(~p)", [U]); +format_type(#t_bitstring{size_unit=S}) -> + io_lib:format("bitstring(~p)", [S]); +format_type(#t_bs_context{tail_unit=U,slots=S,valid=V}) -> + io_lib:format("bs_context(~p, ~p, ~p)", [U, S, V]); +format_type(#t_fun{arity=any,type=any}) -> + "fun()"; +format_type(#t_fun{arity=any,type=T}) -> + ["fun((...) -> ", format_type(T), ")"]; +format_type(#t_fun{arity=A,type=any}) -> + ["fun((", format_fun_args(A), "))"]; +format_type(#t_fun{arity=A,type=T}) -> + ["fun((", format_fun_args(A), ") -> ", format_type(T), ")"]; +format_type(#t_map{super_key=any,super_value=any}) -> + "map()"; +format_type(#t_map{super_key=none,super_value=none}) -> + "#{}"; +format_type(#t_map{super_key=K,super_value=V}) -> + ["#{", format_type(K), "=>", format_type(V), "}"]; +format_type(number) -> + "number()"; +format_type(#t_float{elements=any}) -> + "float()"; +format_type(#t_float{elements={X,X}}) -> + io_lib:format("~p", [X]); +format_type(#t_float{elements={Low,High}}) -> + io_lib:format("~p..~p", [Low,High]); +format_type(#t_integer{elements=any}) -> + "integer()"; +format_type(#t_integer{elements={X,X}}) -> + io_lib:format("~p", [X]); +format_type(#t_integer{elements={Low,High}}) -> + io_lib:format("~p..~p", [Low,High]); +format_type(#t_list{type=ET,terminator=nil}) -> + ["list(", format_type(ET), ")"]; +format_type(#t_list{type=ET,terminator=TT}) -> + ["maybe_improper_list(", format_type(ET), ", ", format_type(TT), ")"]; +format_type(#t_cons{type=ET,terminator=nil}) -> + ["nonempty_list(", format_type(ET), ")"]; +format_type(#t_cons{type=ET,terminator=TT}) -> + ["nonempty_improper_list(", format_type(ET), ", ", format_type(TT), ")"]; +format_type(nil) -> + "nil()"; +format_type(#t_tuple{elements=Es,exact=Ex,size=S}) -> + ["{", + string:join(format_tuple_elems(S, Ex, Es, 1), ", "), + "}"]; +format_type(none) -> + "none()"; +format_type(#t_union{atom=A,list=L,number=N,tuple_set=Ts,other=O}) -> + Es = case A of + none -> []; + _ -> [format_type(A)] + end + ++ case L of + none -> []; + _ -> [format_type(L)] + end + ++ case N of + none -> []; + _ -> [format_type(N)] + end + ++ case Ts of + none -> []; + _ -> [format_tuple_set(Ts)] + end + ++ case O of + none -> []; + _ -> [format_type(O)] + end, + string:join(Es, " | "). + +format_fun_args(A) -> + string:join(lists:duplicate(A, "_"), ", "). + +format_tuple_elems(Size, true, _Elems, Idx) when Idx > Size -> + []; +format_tuple_elems(Size, false, _Elems, Idx) when Idx > Size -> + ["..."]; +format_tuple_elems(Size, Exact, Elems, Idx) -> + T = case Elems of + #{ Idx := Ty} -> Ty; + _ -> any + end, + [format_type(T)|format_tuple_elems(Size, Exact, Elems, Idx + 1)]. + +format_tuple_set(#t_tuple{}=T) -> + format_type(T); +format_tuple_set(RecordSet) -> + string:join([format_tuple_set_1(T) || T <- ordsets:to_list(RecordSet)], + " | "). + +format_tuple_set_1({{Arity,Key},#t_tuple{size=Arity,elements=Elems}=Tuple}) -> + Key = map_get(1, Elems), % Assertion + format_type(Tuple). diff --git a/lib/compiler/test/compile_SUITE.erl b/lib/compiler/test/compile_SUITE.erl index c9495c3755..9415fa9661 100644 --- a/lib/compiler/test/compile_SUITE.erl +++ b/lib/compiler/test/compile_SUITE.erl @@ -38,7 +38,7 @@ warnings/1, pre_load_check/1, env_compiler_options/1, bc_options/1, deterministic_include/1, deterministic_paths/1, compile_attribute/1, message_printing/1, other_options/1, - transforms/1, erl_compile_api/1 + transforms/1, erl_compile_api/1, types_pp/1 ]). suite() -> [{ct_hooks,[ts_install_cth]}]. @@ -57,7 +57,7 @@ all() -> env_compiler_options, custom_debug_info, bc_options, custom_compile_info, deterministic_include, deterministic_paths, compile_attribute, message_printing, other_options, transforms, - erl_compile_api]. + erl_compile_api, types_pp]. groups() -> []. @@ -1852,6 +1852,94 @@ erl_compile_api(Config) -> ok. +%% Check that an ssa dump contains the pretty printed types we expect. +%% The module we compile and dump, types_pp, is crafted so it contains +%% calls to functions which have the result types we want to check the +%% pretty printer for. We check all types except for bs_context, +%% bs_matchable and the interval form of float as the first two never +%% seem to appear in result types and the latter doesn't appear in any +%% module compiled by diffable. +types_pp(Config) when is_list(Config) -> + DataDir = proplists:get_value(data_dir, Config), + PrivDir = proplists:get_value(priv_dir, Config), + TargetDir = filename:join(PrivDir, types_pp), + File = filename:join(DataDir, "types_pp.erl"), + Listing = filename:join(TargetDir, "types_pp.ssaopt"), + ok = file:make_dir(TargetDir), + + {ok,_} = compile:file(File, [dssaopt, {outdir, TargetDir}]), + {ok, Data} = file:read_file(Listing), + Lines = string:split(binary_to_list(Data), "\n", all), + ResultTypes = get_result_types(Lines), + io:format("Calls: ~p~n", [ResultTypes]), + + TypesToCheck = [{make_atom, "'an_atom'"}, + {make_number, "number()"}, + {make_float, "3.14"}, + {make_integer, "17"}, + {make_integer_range, "0..3"}, + {make_nil, "nil()"}, + {make_list, "list(any())"}, + {make_list_of_ints, "list(integer())"}, + {make_maybe_improper_list, + "maybe_improper_list(any(), any())"}, + {make_nonempty_list, "nonempty_list(any())"}, + {make_nonempty_improper_list, + "nonempty_improper_list(any(), ''end'')"}, + {make_empty_map, "#{}"}, + {make_map, "map()"}, + {make_map_known_types, "#{integer()=>float()}"}, + {make_fun_unknown_arity_known_type, + "fun((...) -> number())"}, + {make_fun_known_arity_known_type, + "fun((_, _) -> number())"}, + {make_fun_unknown_arity_unknown_type, + "fun()"}, + {make_fun_known_arity_unknown_type, + "fun((_, _))"}, + {make_unconstrained_tuple, "{...}"}, + {make_known_size_tuple, + "{any(), any(), any(), any(), any()}"}, + {make_inexact_tuple, "{any(), any(), any(), ...}"}, + {make_union, + "'foo' | nonempty_list(1..3) | number() |" + " {'tag0', 1, 2} | {'tag1', 3, 4} | bitstring(24)"}, + {make_bitstring, "bitstring(24)"}, + {make_none, "none()"}], + lists:foreach(fun({FunName, Expected}) -> + Actual = map_get(atom_to_list(FunName), ResultTypes), + case Actual of + Expected -> + ok; + _ -> + ct:fail("Expected type of ~p is ~s, found ~s", + [FunName, Expected, Actual]) + end + end, TypesToCheck), + ok = file:del_dir_r(TargetDir), + ok. + +%% We assume that a call starts with a "Result type:"-line followed by +%% a type line, which is followed by an optional annotation before the +%% actual call. +get_result_types(Lines) -> + get_result_types(Lines, #{}). + +get_result_types([" %% Result type:"++_," %% "++TypeLine|Lines], Acc) -> + get_result_types(Lines, TypeLine, Acc); +get_result_types([_|Lines], Acc) -> + get_result_types(Lines, Acc); +get_result_types([], Acc) -> + Acc. + +get_result_types([" %% Anno: "++_|Lines], TypeLine, Acc) -> + get_result_types(Lines, TypeLine, Acc); +get_result_types([CallLine|Lines], TypeLine, Acc) -> + [_,Callee,_] = string:split(CallLine, "`", all), + get_result_types(Lines, Acc#{ Callee => TypeLine }). + + + %%% %%% Utilities. %%% diff --git a/lib/compiler/test/compile_SUITE_data/types_pp.erl b/lib/compiler/test/compile_SUITE_data/types_pp.erl new file mode 100644 index 0000000000..bcbd93a835 --- /dev/null +++ b/lib/compiler/test/compile_SUITE_data/types_pp.erl @@ -0,0 +1,127 @@ +-module(types_pp). + +-export([doit/0]). + +make_atom() -> + an_atom. + +make_number(X, Y) -> + X + Y. + +make_float() -> + 3.14. + +make_integer_range(X) -> + case X of + a -> 0; + b -> 1; + c -> 2; + _ -> 3 + end. + +make_integer() -> + 17. + +make_nil() -> + []. + +make_list(X) when is_list(X) -> + X ++ [1, 2, 3]. + +make_list_of_ints(X) when is_list(X) -> + [ Y || Y <- X, is_integer(Y)]. + +make_maybe_improper_list(X) when is_list(X) -> + X. + +make_nonempty_list(X) -> + [X]. + +make_nonempty_improper_list(X) -> + [X|'end']. + +make_empty_map() -> + #{}. + +make_map(X) when is_map(X) -> + X. + +make_map_known_types(K, V) when is_integer(K), is_float(V) -> + #{ K => V }. + +make_fun_unknown_arity_known_type() -> + case ext:f() of + 0 -> fun(X) -> X + 1 end; + 1 -> fun(X, Y) -> X + Y end + end. + +make_fun_known_arity_known_type() -> + fun(Y, Z) -> + Y + Z + end. + +make_fun_unknown_arity_unknown_type() -> + case ext:f() of + 0 -> fun(X) -> ext:f(X) end; + 1 -> fun(X, Y) -> ext:f(X, Y) end + end. + +make_fun_known_arity_unknown_type() -> + fun(Y, Z) -> + ext:f(Y, Z) + end. + +make_none() -> + exit(foo). + +make_unconstrained_tuple(X) when is_tuple(X) -> + X. + +make_known_size_tuple(X) when is_tuple(X), tuple_size(X) =:= 5 -> + X. + +make_inexact_tuple({X1,X2,X3,_X4,_X5,_X6,_X7,_X8,_X9,_X10,_X11,_X12,_X13}=X) + when is_integer(X1), is_float(X2), is_integer(X3) -> + case ext:f() of + 0 -> + {1, 2, 3}; + _ -> + X + end. + +make_union() -> + case ext:f() of + 0 -> foo; + 1 -> [1, 2, 3]; + 2 -> 7; + 3 -> 3.14; + 4 -> {tag0,1,2}; + 5 -> {tag1,3,4}; + 6 -> <<1,2,3>> + end. + +make_bitstring() -> + <<1, 2, 3>>. + +doit() -> + {make_number(ext:f(), ext:f()), make_atom(), + make_float(), + make_integer(), make_integer_range(ext:f()), + make_nil(), make_list(ext:f()), make_list_of_ints(ext:f()), + make_maybe_improper_list(ext:f()), + make_nonempty_list( ext:f()), make_nonempty_improper_list( ext:f()), + make_empty_map(), make_map(ext:f()), + make_map_known_types(ext:f(), ext:f()), + make_fun_unknown_arity_known_type(), + make_fun_known_arity_known_type(), + make_fun_unknown_arity_unknown_type(), + make_fun_known_arity_unknown_type(), + make_unconstrained_tuple(ext:f()), + make_known_size_tuple(ext:f()), + make_inexact_tuple(ext:f()), + make_union(), + make_bitstring(), + make_none() + }. + + -- 2.31.1
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor