aboutsummaryrefslogblamecommitdiffstats
path: root/lib/compiler/test/compilation_SUITE.erl
blob: f8f74e6f7aad45425d81bd82d6e5ff13ef7c4ea8 (plain) (tree)
1
2
3
4


                   
                                                        

















                                                                         
                                                    


                     
                                         
 
         
                                

                                           

            











                                                           
                         





                                                          
 





                         
                                     
           

                                    
           















































































                                                                                   

















                      
                      



                                                                    
                                               






























































































































































                                                                                     



                                         
                                 






















                                                                          
                                 














                                                                
                                 


























































                                                                            
                                               
                              
                                                







                                                     
                                                




                                           

                                                              

                                                                 
                                 








                                                                   











                                                                           





                                                           










































































































































                                                                                     






                                                                                   
                                                         

       





















                                                                     

















                                                    













                                                          
 
           
%%
%% %CopyrightBegin%
%% 
%% Copyright Ericsson AB 1997-2012. All Rights Reserved.
%% 
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
%% 
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
%% 
%% %CopyrightEnd%
%%
%%% Purpose : Compiles various modules with tough code

-module(compilation_SUITE).

-include_lib("test_server/include/test_server.hrl").

-compile(export_all).

suite() -> [{ct_hooks,[ts_install_cth]}].

all() -> 
    test_lib:recompile(?MODULE),
    [self_compile_old_inliner,self_compile,
     {group,p}].

groups() -> 
    [{vsn,[parallel],[vsn_1,vsn_2,vsn_3]},
     {p,test_lib:parallel(),
      [compiler_1,
       compiler_3,compiler_5,beam_compiler_1,
       beam_compiler_2,beam_compiler_3,beam_compiler_4,
       beam_compiler_5,beam_compiler_6,beam_compiler_7,
       beam_compiler_8,beam_compiler_9,beam_compiler_10,
       beam_compiler_11,beam_compiler_12,
       nested_tuples_in_case_expr,otp_2330,guards,
       {group,vsn},otp_2380,otp_2141,otp_2173,otp_4790,
       const_list_256,bin_syntax_1,bin_syntax_2,
       bin_syntax_3,bin_syntax_4,bin_syntax_5,bin_syntax_6,
       live_var,convopts,
       catch_in_catch,redundant_case,long_string,otp_5076,
       complex_guard,otp_5092,otp_5151,otp_5235,otp_5244,
       trycatch_4,opt_crash,otp_5404,otp_5436,otp_5481,
       otp_5553,otp_5632,otp_5714,otp_5872,otp_6121,
       otp_6121a,otp_6121b,otp_7202,otp_7345,on_load,
       string_table,otp_8949_a,otp_8949_a,split_cases]}].

init_per_suite(Config) ->
    Config.

end_per_suite(_Config) ->
    ok.

init_per_group(_GroupName, Config) ->
    Config.

end_per_group(_GroupName, Config) ->
    Config.

-define(comp(N),
	N(Config) when is_list(Config) -> try_it(N, Config)).

-define(comp_fail(N),
	N(Config) when is_list(Config) -> failure(N, Config)).

?comp(compiler_1).
?comp(compiler_3).
?comp(compiler_4).
?comp(compiler_5).

?comp(beam_compiler_1).
?comp(beam_compiler_2).
?comp(beam_compiler_3).
?comp(beam_compiler_4).
?comp(beam_compiler_5).
?comp(beam_compiler_6).
?comp(beam_compiler_8).
?comp(beam_compiler_9).
?comp(beam_compiler_10).
?comp(beam_compiler_11).
?comp(beam_compiler_12).
?comp(beam_compiler_13).

?comp(nested_tuples_in_case_expr).

?comp(otp_2330).
?comp(otp_2380).
?comp(otp_2141).
?comp(otp_2173).
?comp(otp_4790).
?comp(otp_5235).

?comp(otp_5244).

?comp(guards).

?comp(pattern_expr).

?comp(const_list_256).

?comp(bin_syntax_1).
?comp(bin_syntax_2).
?comp(bin_syntax_3).
?comp(bin_syntax_4).

?comp(bin_syntax_6).

?comp(otp_5076).

?comp(complex_guard).

?comp(otp_5092).
?comp(otp_5151).

%%% By Per Gustafsson <[email protected]>

bin_syntax_5(Config) when is_list(Config) ->
    {<<45>>,<<>>} = split({int, 1}, <<1:16,45>>).   

split({int, N}, <<N:16,B:N/binary,T/binary>>) ->
    {B,T}.

%% This program works with the old version of the compiler
%% but, the core erlang that it produces have the same variable appearing
%% looks like this:
%%
%% split({int, N}, <<_core1:16, B:N/binary, T/binary>>) when _core1==N
%%
%% with my change it will look like this:
%%
%% split({int, N}, <<_core1:16, B:_core1/binary, T/binary>>) when _core1==N
%%
%% This means that everything worked fine as long as the pattern
%% matching order was left-to-right but on core erlang any order should be possible

?comp(live_var).

?comp(trycatch_4).

?comp(catch_in_catch).

?comp(opt_crash).

?comp(otp_5404).
?comp(otp_5436).
?comp(otp_5481).
?comp(otp_5553).
?comp(otp_5632).
?comp(otp_5714).
?comp(otp_5872).
?comp(otp_6121).
?comp(otp_6121a).
?comp(otp_6121b).
?comp(convopts).
?comp(otp_7202).
?comp(on_load).
?comp(on_load_inline).

beam_compiler_7(doc) ->
    "Code snippet submitted from Ulf Wiger which fails in R3 Beam.";
beam_compiler_7(suite) -> [];
beam_compiler_7(Config) when is_list(Config) ->
    ?line done = empty(2, false).

empty(N, Toggle) when N > 0 ->
    %% R3 Beam copies the second argument to the first before call.
    empty(N-1, not(Toggle));
empty(_, _) ->
    done.

redundant_case(Config) when is_list(Config) ->
    d = redundant_case_1(1),
    d = redundant_case_1(2),
    d = redundant_case_1(3),
    d = redundant_case_1(4),
    d = redundant_case_1(5),
    d = redundant_case_1({glurf,glarf}),
    ok.

%% This function always returns 'd'. Check that the compiler otptimizes
%% it properly.
redundant_case_1(1) -> d;
redundant_case_1(2) -> d;
redundant_case_1(3) -> d;
redundant_case_1(4) -> d;
redundant_case_1(_) -> d.

failure(Module, Conf) ->
    ?line Src = filename:join(?config(data_dir, Conf), atom_to_list(Module)),
    ?line Out = ?config(priv_dir,Conf),
    ?line io:format("Compiling: ~s\n", [Src]),
    ?line CompRc = compile:file(Src, [{outdir,Out},return,time]),
    ?line io:format("Result: ~p\n",[CompRc]),
    ?line case CompRc of
	      error -> ok;
	      {error,Errors,_} -> check_errors(Errors);
	      _ -> test_server:fail({no_error, CompRc})
	  end,
    ok.

check_errors([{_,Eds}|T]) ->
    check_error(Eds),
    check_errors(T);
check_errors([]) -> ok.

check_error([{_,Mod,Error}|T]) ->
    check_error_1(Mod:format_error(Error)),
    check_error(T);
check_error([{Mod,Error}|T]) ->
    check_error_1(Mod:format_error(Error)),
    check_error(T);
check_error([]) -> ok.

check_error_1(Str0) ->
    Str = lists:flatten(Str0),
    io:format("~s\n", [Str]),
    case Str of
	"internal"++_=Str ->
	    ?t:fail(internal_compiler_error);
	_ ->
	    ok
    end.

-define(TC(Body), tc(fun() -> Body end, ?LINE)).

try_it(Module, Conf) ->
    %% Change 'false' to 'true' to start a new node for every module.
    try_it(false, Module, Conf).
		       
try_it(StartNode, Module, Conf) ->
    ?line OtherOpts = [],			%Can be changed to [time] if needed
    ?line Src = filename:join(?config(data_dir, Conf), atom_to_list(Module)),
    ?line Out = ?config(priv_dir,Conf),
    ?line io:format("Compiling: ~s\n", [Src]),
    ?line CompRc0 = compile:file(Src, [clint,{outdir,Out},report,
				       bin_opt_info|OtherOpts]),
    ?line io:format("Result: ~p\n",[CompRc0]),
    ?line {ok,_Mod} = CompRc0,

    ?line Dog = test_server:timetrap(test_server:minutes(10)),
    Node = case StartNode of
	       false ->
		   node();
	       true ->
		   ?line Pa = "-pa " ++ filename:dirname(code:which(?MODULE)),
		   ?line {ok,Node0} = start_node(compiler, Pa),
		   Node0
	   end,
		   
    ?line ok = rpc:call(Node, ?MODULE, load_and_call, [Out, Module]),
    ?line load_and_call(Out, Module),
    ?line test_server:timetrap_cancel(Dog),

    ?line NewDog = test_server:timetrap(test_server:minutes(10)),
    ?line io:format("Compiling (without optimization): ~s\n", [Src]),
    ?line CompRc1 = compile:file(Src,
				 [no_copt,no_postopt,{outdir,Out},report|OtherOpts]),

    ?line io:format("Result: ~p\n",[CompRc1]),
    ?line {ok,_Mod} = CompRc1,
    ?line ok = rpc:call(Node, ?MODULE, load_and_call, [Out, Module]),
    ?line test_server:timetrap_cancel(NewDog),

    ?line LastDog = test_server:timetrap(test_server:minutes(10)),
    ?line io:format("Compiling (with old inliner): ~s\n", [Src]),
    ?line CompRc2 = compile:file(Src, [{outdir,Out},report,bin_opt_info,
				       {inline,1000}|OtherOpts]),
    ?line io:format("Result: ~p\n",[CompRc2]),
    ?line {ok,_Mod} = CompRc2,
    ?line ok = rpc:call(Node, ?MODULE, load_and_call, [Out, Module]),
    ?line test_server:timetrap_cancel(LastDog),

    case StartNode of
	false -> ok;
	true -> ?line test_server:stop_node(Node)
    end,
    ?line test_server:timetrap_cancel(LastDog),
    ok.

load_and_call(Out, Module) ->
    ?line io:format("Loading...\n",[]),
    ?line {module,Module} = code:load_abs(filename:join(Out, Module)),

    ?line io:format("Calling...\n",[]),
    %% Call M:M, and expect ok back, that's our interface
    ?line CallRc = Module:Module(),
    ?line io:format("Got value: ~p\n",[CallRc]),

    ?line ok = CallRc,

    %% Smoke-test of beam disassembler.
    ?line test_lib:smoke_disasm(Module),

    ?line true = erlang:delete_module(Module),
    ?line true = erlang:purge_module(Module),

    %% Restore state of trap_exit just in case. (Since the compiler
    %% uses a temporary process, we will get {'EXIT',Pid,normal} messages
    %% if trap_exit is true.)

    process_flag(trap_exit, false),
    ok.


tc(F, Line) ->
    {Diff,Value} = timer:tc(erlang, apply, [F,[]]),
    io:format("~p: ~p\n", [Line,Diff]),
    Value.
    
start_node(Name, Args) ->
    case test_server:start_node(Name, slave, [{args, Args}]) of
	{ok, Node} ->
	    {ok, Node};
	Error  ->
	    ?line test_server:fail(Error)
    end.

from(H, [H | T]) -> T;
from(H, [_ | T]) -> from(H, T);
from(_, []) -> [].


vsn_1(doc) ->
    "Test generation of 'vsn' attribute";
vsn_1(suite) -> [];
vsn_1(Conf) when is_list(Conf) ->
    ?line M = vsn_1,

    ?line compile_load(M, ?config(data_dir, Conf), Conf),
    ?line Vsn1 = get_vsn(M),
    ?line timer:sleep(1000),

    ?line compile_load(M, ?config(data_dir, Conf), Conf),
    ?line Vsn2 = get_vsn(M),

    ?line compile_load(M, filename:join(?config(data_dir, Conf), "other"),
		       Conf),
    ?line Vsn3 = get_vsn(M),
    ?line if
	      Vsn1 == Vsn2, Vsn2 == Vsn3 ->
		  ok;
	      true ->
		  test_server:fail({vsn, Vsn1, Vsn2, Vsn3})
	  end,
    ok.

vsn_2(doc) ->
    "Test overriding of generation of 'vsn' attribute";
vsn_2(suite) -> [];
vsn_2(Conf) when is_list(Conf) ->
    ?line M = vsn_2,

    ?line compile_load(M, ?config(data_dir, Conf), Conf),
    ?line Vsn = get_vsn(M),
    ?line case Vsn of
	      [34] ->
		  ok;
	      _ ->
		  test_server:fail({vsn, Vsn})
	  end,
    ok.

vsn_3(doc) ->
    "Test that different code yields different generated 'vsn'";
vsn_3(suite) -> [];
vsn_3(Conf) when is_list(Conf) ->
    ?line M = vsn_3,

    ?line compile_load(M, ?config(data_dir, Conf), Conf),
    ?line Vsn1 = get_vsn(M),

    ?line compile_load(M, filename:join(?config(data_dir, Conf), "other"),
		       Conf),
    ?line Vsn2 = get_vsn(M),
    ?line if
	      Vsn1 /= Vsn2 ->
		  ok;
	      true ->
		  test_server:fail({vsn, Vsn1, Vsn2})
	  end,
    ok.

get_vsn(M) ->
    {value, {vsn, V}} = lists:keysearch(vsn, 1, M:module_info(attributes)),
    V.

long_string(Config) when is_list(Config) ->
    %% The test must complete in one minute - it should be plenty of time.
    ?line Dog = test_server:timetrap(test_server:minutes(1)),
    ?line try_it(long_string, Config),
    ?line test_server:timetrap_cancel(Dog),
    ok.

compile_load(Module, Dir, Conf) ->
    ?line Src = filename:join(Dir, atom_to_list(Module)),
    ?line Out = ?config(priv_dir,Conf),
    ?line CompRc = compile:file(Src, [{outdir,Out}]),
    ?line {ok, Module} = CompRc,
    ?line code:purge(Module),
    ?line {module, Module} =
	code:load_abs(filename:join(Out, atom_to_list(Module))),
    ok.

self_compile(Config) when is_list(Config) ->
    self_compile_1(Config, "new", [inline]).

self_compile_old_inliner(Config) when is_list(Config) ->
    %% The old inliner is useful for testing that sys_core_fold does not
    %% introduce name capture problems.
    self_compile_1(Config, "old", [verbose,{inline,500}]).

self_compile_1(Config, Prefix, Opts) ->
    ?line Dog = test_server:timetrap(test_server:minutes(40)),

    ?line Priv = ?config(priv_dir,Config),
    ?line Version = compiler_version(),

    %% Compile the compiler. (In this node to get better coverage.)
    ?line CompA = make_compiler_dir(Priv, Prefix++"compiler_a"),
    ?line VsnA = Version ++ ".0",
    ?line compile_compiler(compiler_src(), CompA, VsnA, [clint|Opts]),

    %% Compile the compiler again using the newly compiled compiler.
    %% (In another node because reloading the compiler would disturb cover.)
    CompilerB = Prefix++"compiler_b",
    CompB = make_compiler_dir(Priv, CompilerB),
    ?line VsnB = VsnA ++ ".0",
    self_compile_node(CompA, CompB, VsnB, Opts),

    %% Compare compiler directories.
    ?line compare_compilers(CompA, CompB),

    %% Compile and compare compiler C.
    ?line CompilerC = Prefix++"compiler_c",
    ?line CompC = make_compiler_dir(Priv, CompilerC),
    ?line VsnC = VsnB ++ ".0",
    self_compile_node(CompB, CompC, VsnC, Opts),
    ?line compare_compilers(CompB, CompC),

    ?line test_server:timetrap_cancel(Dog),
    ok.

self_compile_node(CompilerDir, OutDir, Version, Opts) ->
    ?line Dog = test_server:timetrap(test_server:minutes(15)),
    ?line Pa = "-pa " ++ filename:dirname(code:which(?MODULE)) ++
	" -pa " ++ CompilerDir,
    ?line Files = compiler_src(),

    %% We don't want the cover server started on the other node,
    %% because it will load the same cover-compiled code as on this
    %% node. Use a shielded node to prevent the cover server from
    %% being started.
    ?t:run_on_shielded_node(
       fun() ->
	       compile_compiler(Files, OutDir, Version, Opts)
       end, Pa),
    ?line test_server:timetrap_cancel(Dog),
    ok.

compile_compiler(Files, OutDir, Version, InlineOpts) ->
    io:format("~s", [code:which(compile)]),
    io:format("Compiling ~s into ~s", [Version,OutDir]),
    Opts = [report,
	    bin_opt_info,
	    {outdir,OutDir},
	    {d,'COMPILER_VSN',"\""++Version++"\""},
	    nowarn_shadow_vars,
	    {i,filename:join(code:lib_dir(stdlib), "include")}|InlineOpts],
    test_lib:p_run(fun(File) ->
			   case compile:file(File, Opts) of
			       {ok,_} -> ok;
			       _ -> error
			   end
		   end, Files).

compiler_src() ->
    filelib:wildcard(filename:join([code:lib_dir(compiler), "src", "*.erl"])).

compiler_modules(Dir) ->
    Files = filelib:wildcard(filename:join(Dir, "*.beam")),
    [list_to_atom(filename:rootname(filename:basename(F))) || F <- Files].

make_compiler_dir(Priv, Dir0) ->
    ?line Dir = filename:join(Priv, Dir0),
    ?line ok = file:make_dir(Dir),
    Dir.

make_current(Dir) ->    
    true = code:add_patha(Dir),
    lists:foreach(fun(File) ->
			  c:l(File)
		  end, compiler_modules(Dir)),
    io:format("~p\n", [code:which(compile)]).

compiler_version() ->
    {value,{version,Version}} = lists:keysearch(version, 1,
						compile:module_info(compile)),
    Version.

compare_compilers(ADir, BDir) ->
    {[],[],D} = beam_lib:cmp_dirs(ADir, BDir),
    [] = [T || {A,_}=T <- D,
	       filename:basename(A) =/= "beam_asm.beam"]. %Contains compiler version.


%%%
%%% The only test of the following code is that it compiles.
%%%

%% Slightly simplifed from megaco_binary_term_id_gen.
%%  beam_block failed to note that the {gc_bif,'-'...} instruction could
%%  fail, and that therefore {y,0} need to be initialized.
%%    {allocate,8,6}.
%%                     %% {init,{y,0}} needed here.       
%%    {get_list,{x,1},{x,6},{x,7}}.
%%    {'catch',{y,7},{f,3}}.
%%    {move,{x,4},{y,1}}.
%%    {move,{x,3},{y,2}}.
%%    {move,{x,2},{y,3}}.
%%    {move,{x,5},{y,4}}.
%%    {move,{x,7},{y,5}}.
%%    {move,{x,6},{y,6}}.
%%    {gc_bif,'-',{f,0},8,[{x,3},{x,6}],{x,0}}.
%%    {move,{x,0},{y,0}}.

encode_wildcards3([],[],_,_) -> [];
encode_wildcards3([Level|Levels],[BitsInLevel|BitsRest],LevelNo,TotSize) ->
    case (catch ?MODULE:encode_wildcard(Level,BitsInLevel,TotSize-BitsInLevel,
					length(Levels))) of
	{'EXIT',{Reason,Info}} ->
	    exit({Reason,{LevelNo,Info}});

	no_wildcard ->
	    encode_wildcards3(Levels,BitsRest,LevelNo+1,TotSize-BitsInLevel);
	    
	{level,Wl} ->  
	    [Wl|
	     encode_wildcards3(Levels,BitsRest,LevelNo+1,TotSize-BitsInLevel)];

	{recursive,Wr} ->  
	    [Wr]
    end.

%% Slightly simplified code from hipe_rtl_ssapre.
%%  beam_block used to do the following incorrect optimization:
%%
%%    {gc_bif,length,{f,0},1,[{x,0}],{x,3}}.
%%                                   ^^^^^ Was {x,0} - changing to {x,3} is not safe.
%%    {gc_bif,'+',{f,0},0,[{y,2},{integer,1}],{x,0}}.
%%                     ^^^ Only one register live
%%     . . .
%%    {call_last,4,{f,2},4}.   %% beam_validator noted that {x,3} wasn't live.

find_operands(Cfg,XsiGraph,[],_Count) ->
    {Cfg,XsiGraph};
find_operands(Cfg,XsiGraph,ActiveList,Count) ->
    {NewCfg,TempActiveList}=?MODULE:find_operands_for_active_list(Cfg,XsiGraph,
								  ActiveList,[]),
    NewActiveList=lists:reverse(TempActiveList),
    [Count+1, length(NewActiveList), length(digraph:vertices(XsiGraph))],
    find_operands(NewCfg,XsiGraph,NewActiveList,Count+1).


%% The following code
%%
%%    {get_list,{x,2},{x,0},{x,1}}.
%%    {gc_bif,length,{f,0},1,[{x,0}],{x,0}}.
%%    {move,{x,0},{x,1}}.
%%
%% was incorrectly optimized to
%%
%%    {get_list,{x,2},{x,0},{y,0}}.
%%    {gc_bif,length,{f,0},3,[{x,0}],{x,1}}.
%%
%% because beam_block:is_transparent({x,1},
%%                                  {gc_bif,length,{f,0},3,[{x,0}],{x,1}}
%% incorrectly returned true.

-record(contextId,{cid,device_type,contextRef}).
-record(dpRef,{cid,tlli,ms_device_context_id}).
-record(qosProfileBssgp,{peak_bit_rate_msb,
                              peak_bit_rate_lsb,
                              t_a_precedence}).
-record(llUnitdataReq,{sapi,
                            l3_pdu_length,
                            pdu_life}).
-record(ptmsi,{value}).

otp_7345(Config) when is_list(Config) ->
    #llUnitdataReq{l3_pdu_length=3,pdu_life=4} =
	otp_7345(#contextId{}, 0, [[1,2,3],4,5]).


otp_7345(ObjRef, _RdEnv, Args) ->
    Cid = ObjRef#contextId.cid,
    _DpRef =
	#dpRef{cid = Cid,
		     ms_device_context_id = cid_id,
		     tlli = #ptmsi{value = 0}},
    _QosProfile =
	#qosProfileBssgp{peak_bit_rate_msb = 0,
			 peak_bit_rate_lsb = 80,
			 t_a_precedence = 49},
    [Cpdu|_] = Args,
    LlUnitdataReq =
	#llUnitdataReq{sapi = 7,
		       l3_pdu_length = length(Cpdu),
		       pdu_life =
		       id(42)
		       div
		       10},
    id(LlUnitdataReq).

%% Check the generation of the string table.

string_table(Config) when is_list(Config) ->
    ?line DataDir = ?config(data_dir, Config),
    ?line File = filename:join(DataDir, "string_table.erl"),
    ?line {ok,string_table,Beam,[]} = compile:file(File, [return, binary]),
    ?line {ok,{string_table,[StringTableChunk]}} = beam_lib:chunks(Beam, ["StrT"]),
    ?line {"StrT", <<"stringtable">>} = StringTableChunk,
    ok.

otp_8949_a(Config) when is_list(Config) ->
    value = otp_8949_a(),
    ok.

-record(cs, {exs,keys = [],flags = 1}).
-record(exs, {children = []}).

otp_8949_a() ->
    case id([#cs{}]) of
        [#cs{}=Cs] ->
            SomeVar = id(value),
	    if
		Cs#cs.flags band 1 =/= 0 ->
		    id(SomeVar);
		(((Cs#cs.exs)#exs.children /= [])
                 and
		   (Cs#cs.flags band (1 bsl 0 bor (1 bsl 22)) == 0));
		Cs#cs.flags band (1 bsl 22) =/= 0 ->
		    ok
	    end
    end.
    
otp_8949_b(Config) when is_list(Config) ->
    self() ! something,
    ?line value = otp_8949_b([], false),
    ?line {'EXIT',_} = (catch otp_8949_b([], true)),
    ok.

%% Would cause an endless loop in beam_utils.
otp_8949_b(A, B) ->
    Var = id(value),
    if
	A == [], B == false ->
	    ok
    end,
    receive
        something ->
	    id(Var)
    end.
    
split_cases(_) ->
    dummy1 = do_split_cases(x),
    {'EXIT',{{badmatch,b},_}} = (catch do_split_cases(y)),
    ok.

do_split_cases(A) ->
    case A of
        x ->
	    Z = dummy1;
        _ ->
	    Z = dummy2,
	    a=b
    end,
    Z.

id(I) -> I.