1+ # This is an optimization Elixir runs on function clauses.
2+ # Whenever a variables matches against a record (a tagged
3+ # tuple), this information is stored in order to optimize
4+ # record calls.
5+ defmodule Kernel.RecordRewriter do
6+ @ moduledoc false
7+
8+ def optimize_clause ( clause ) do
9+ optimize_clause ( clause , :orddict . new )
10+ end
11+
12+ ## Clause
13+
14+ defp optimize_clause ( { :clause , line , args , guards , body } , dict ) do
15+ { args , dict } = optimize_args ( args , dict )
16+ { body , dict , res } = optimize_body ( body , dict , [ ] )
17+ { { :clause , line , args , guards , body } , dict , res }
18+ end
19+
20+ defp optimize_args ( args , dict ) do
21+ Enum . map_reduce args , dict , fn ( arg , acc ) ->
22+ { new_arg , new_acc , _res } = optimize_expr ( arg , acc )
23+ { new_arg , new_acc }
24+ end
25+ end
26+
27+ defp optimize_body ( [ ] , dict , _acc ) do
28+ { [ ] , dict , nil }
29+ end
30+
31+ defp optimize_body ( [ h ] , dict , acc ) do
32+ { new_expr , new_dict , new_res } = optimize_expr ( h , dict )
33+ { Enum . reverse ( [ new_expr | acc ] ) , new_dict , new_res }
34+ end
35+
36+ defp optimize_body ( [ h | t ] , dict , acc ) do
37+ { new_expr , new_dict , _ } = optimize_expr ( h , dict )
38+ optimize_body ( t , new_dict , [ new_expr | acc ] )
39+ end
40+
41+ ## Record helpers
42+
43+ defp record_fields ( record ) do
44+ if Code . ensure_loaded? ( record ) && function_exported? ( record , :__record__ , 1 ) do
45+ try do
46+ fields = lc { k , _ } in list record . __record__ ( :fields ) , do: k
47+ optimizable = record . __record__ ( :optimizable )
48+ { fields , optimizable }
49+ rescue
50+ [ UndefinedFunctionError , FunctionClauseError ] -> { [ ] , [ ] }
51+ end
52+ end
53+ end
54+
55+ defp record_field_info ( function ) do
56+ case atom_to_list ( function ) do
57+ 'update_' ++ field -> { :update , list_to_atom ( function ) }
58+ _ -> { :accessor , function }
59+ end
60+ end
61+
62+ defp optimize_call ( line , { record , _ } = res , left , { :atom , _ , function } , args ) do
63+ { fields , optimizable } = record_fields ( record )
64+
65+ if List . member? ( optimizable , { function , length ( args ) + 1 } ) do
66+ { kind , field } = record_field_info ( function )
67+ if index = Enum . find_index ( fields , field == & 1 ) do
68+ optimize_call ( line , res , kind , index , left , args )
69+ end
70+ end
71+ end
72+
73+ defp optimize_call ( _line , _res , _left , _right , _args ) do
74+ nil
75+ end
76+
77+ defp optimize_call ( line , _res , :accessor , index , left , [ ] ) do
78+ call = { :call , line ,
79+ { :remote , line , { :atom , 0 , :erlang } , { :atom , 0 , :element } } ,
80+ [ { :integer , 0 , index + 2 } , left ]
81+ }
82+ { call , nil }
83+ end
84+
85+ defp optimize_call ( line , res , :accessor , index , left , [ arg ] ) do
86+ call = { :call , line ,
87+ { :remote , line , { :atom , 0 , :erlang } , { :atom , 0 , :setelement } } ,
88+ [ { :integer , 0 , index + 2 } , left , arg ]
89+ }
90+ { call , res }
91+ end
92+
93+ defp optimize_call ( _line , _res , :update , _index , _left , [ _arg ] ) do
94+ nil
95+ end
96+
97+ ## Expr
98+
99+ defp optimize_expr ( { :call , call_line , { :remote , line , left , right } , args } , dict ) do
100+ { left , dict , res } = optimize_expr ( left , dict )
101+ { right , dict , _ } = optimize_expr ( right , dict )
102+ { args , dict } = optimize_args ( args , dict )
103+
104+ case optimize_call ( call_line , res , left , right , args ) do
105+ { call , call_res } ->
106+ { call , dict , call_res }
107+ nil ->
108+ { { :call , call_line , { :remote , line , left , right } , args } , dict , nil }
109+ end
110+ end
111+
112+ defp optimize_expr ( { :call , line , expr , args } , dict ) do
113+ { expr , dict , _ } = optimize_expr ( expr , dict )
114+ { args , dict } = optimize_args ( args , dict )
115+ { { :call , line , expr , args } , dict , nil }
116+ end
117+
118+ defp optimize_expr ( { :match , line , left , right } , dict ) do
119+ { left , dict , left_res } = optimize_expr ( left , dict )
120+ { right , dict , right_res } = optimize_expr ( right , dict )
121+
122+ match = { :match , line , left , right }
123+
124+ if left_res do
125+ dict = assign_vars ( extract_vars ( right , [ ] ) , dict , left_res )
126+ end
127+
128+ if right_res do
129+ dict = assign_vars ( extract_vars ( left , [ ] ) , dict , right_res )
130+ end
131+
132+ { match , dict , right_res || left_res }
133+ end
134+
135+ defp optimize_expr ( { :op , line , op , left , right } , dict ) do
136+ { left , dict , _ } = optimize_expr ( left , dict )
137+ { right , dict , _ } = optimize_expr ( right , dict )
138+ { { :op , line , op , left , right } , dict , nil }
139+ end
140+
141+ defp optimize_expr ( { :op , line , op , expr } , dict ) do
142+ { expr , dict , _ } = optimize_expr ( expr , dict )
143+ { { :op , line , op , expr } , dict , nil }
144+ end
145+
146+ defp optimize_expr ( { :bin , line , elements } , dict ) do
147+ { elements , dict } = optimize_args ( elements , dict )
148+ { { :bin , line , elements } , dict , nil }
149+ end
150+
151+ defp optimize_expr ( { :bin_element , line , expr , type1 , type2 } , dict ) do
152+ { expr , dict , _ } = optimize_expr ( expr , dict )
153+ { { :bin_element , line , expr , type1 , type2 } , dict , nil }
154+ end
155+
156+ defp optimize_expr ( { :cons , line , left , right } , dict ) do
157+ { left , dict , _ } = optimize_expr ( left , dict )
158+ { right , dict , _ } = optimize_expr ( right , dict )
159+ { { :cons , line , left , right } , dict , nil }
160+ end
161+
162+ defp optimize_expr ( { :block , line , args } , dict ) do
163+ { args , dict , res } = optimize_body ( args , dict , [ ] )
164+ { { :block , line , args } , dict , res }
165+ end
166+
167+ defp optimize_expr ( { :tuple , line , args } , dict ) do
168+ { args , dict , args_res } = optimize_tuple_args ( args , dict )
169+ args_res = if Enum . any? ( args_res ) , do: args_res , else: nil
170+
171+ res =
172+ case args do
173+ [ { :atom , _ , atom } | t ] -> atom
174+ _ -> nil
175+ end
176+
177+ { { :tuple , line , args } , dict , { res , args_res } }
178+ end
179+
180+ defp optimize_expr ( { :var , _ , name } = var , dict ) do
181+ case :orddict . find ( name , dict ) do
182+ { :ok , res } -> { var , dict , res }
183+ :error -> { var , dict , nil }
184+ end
185+ end
186+
187+ defp optimize_expr ( { :case , line , expr , clauses } , dict ) do
188+ { expr , dict , _ } = optimize_expr ( expr , dict )
189+ tuples = lc clause inlist clauses , do: optimize_clause ( clause , dict )
190+ clauses = lc { clause , _ , _ } in list tuples , do: clause
191+ dict = join_dict ( tuples )
192+ res = join_result ( tuples )
193+ { { :case , line , expr , clauses } , dict , res }
194+ end
195+
196+ defp optimize_expr ( { :receive , line , clauses } , dict ) do
197+ tuples = lc clause inlist clauses , do: optimize_clause ( clause , dict )
198+ clauses = lc { clause , _ , _ } in list tuples , do: clause
199+ dict = join_dict ( tuples )
200+ res = join_result ( tuples )
201+ { { :receive , line , clauses } , dict , res }
202+ end
203+
204+ defp optimize_expr ( { :receive , line , clauses , after_key , after_value } , dict ) do
205+ tuples = lc clause inlist clauses , do: optimize_clause ( clause , dict )
206+ clauses = lc { clause , _ , _ } in list tuples , do: clause
207+
208+ { after_key , dict , _ } = optimize_expr ( after_key , dict )
209+ { after_value , dict , res } = optimize_body ( after_value , dict , [ ] )
210+
211+ dict = join_dict ( tuples , dict )
212+ res = join_result ( tuples , res )
213+
214+ { { :receive , line , clauses , after_key , after_value } , dict , res }
215+ end
216+
217+ defp optimize_expr ( { :try , line , body , [ ] , clauses , try_after } , dict ) do
218+ tuples = lc clause inlist clauses , do: optimize_clause ( clause , dict )
219+ clauses = lc { clause , _ , _ } in list tuples , do: clause
220+
221+ { body , _ , res } = optimize_body ( body , dict , [ ] )
222+ res = join_result ( tuples , res )
223+
224+ { try_after , _ , _ } = optimize_body ( try_after , dict , [ ] )
225+ { { :try , line , body , [ ] , clauses , try_after } , dict , res }
226+ end
227+
228+ defp optimize_expr ( { :fun , line , { :function , module , name , arity } } , dict ) do
229+ { module , dict , _ } = optimize_expr ( module , dict )
230+ { name , dict , _ } = optimize_expr ( name , dict )
231+ { arity , dict , _ } = optimize_expr ( arity , dict )
232+ { { :fun , line , { :function , module , name , arity } } , dict , nil }
233+ end
234+
235+ defp optimize_expr ( { :fun , line , { :clauses , clauses } } , dict ) do
236+ clauses = lc clause inlist clauses do
237+ { clause , _ , _ } = optimize_clause ( clause , dict )
238+ clause
239+ end
240+
241+ { { :fun , line , { :clauses , clauses } } , dict , nil }
242+ end
243+
244+ defp optimize_expr ( { comprehension , line , expr , args } , dict ) when comprehension in [ :lc , :bc ] do
245+ { args , new_dict } = optimize_args ( args , dict )
246+ { expr , _ , _ } = optimize_expr ( expr , new_dict )
247+ { { comprehension , line , expr , args } , dict , nil }
248+ end
249+
250+ defp optimize_expr ( { generate , line , left , right } , dict ) when generate in [ :generate , :b_generate ] do
251+ { left , dict , _ } = optimize_expr ( left , dict )
252+ { right , dict , _ } = optimize_expr ( right , dict )
253+ { { generate , line , left , right } , dict , nil }
254+ end
255+
256+ defp optimize_expr ( other , dict ) when elem ( other , 0 ) in [ :string , :atom , :integer , :float , :nil , :fun ] do
257+ { other , dict , nil }
258+ end
259+
260+ ## Helpers
261+
262+ defp optimize_tuple_args ( args , dict ) do
263+ { final_args , { final_dict , final_acc } } =
264+ Enum . map_reduce args , { dict , [ ] } , fn ( arg , { acc_dict , acc_res } ) ->
265+ { new_arg , new_acc , res } = optimize_expr ( arg , acc_dict )
266+ { new_arg , { new_acc , [ res | acc_res ] } }
267+ end
268+
269+ { final_args , final_dict , Enum . reverse ( final_acc ) }
270+ end
271+
272+ defp assign_vars ( [ key | t ] , dict , { _ , value } = res ) when is_list ( key ) and is_list ( value ) and length ( key ) == length ( value ) do
273+ assign_vars t , assign_nested_vars ( key , dict , value ) , res
274+ end
275+
276+ defp assign_vars ( [ key | t ] , dict , { value , _ } = res ) when is_atom ( key ) and value != nil do
277+ dict =
278+ case :orddict . find ( key , dict ) do
279+ { :ok , ^ res } ->
280+ dict
281+ { :ok , { ^ value , _ } } ->
282+ :orddict . store ( key , { value , nil } , dict )
283+ { :ok , _ } ->
284+ # We are overriding a type of an existing variable,
285+ # which means the source code is invalid.
286+ :orddict . store ( key , nil , dict )
287+ :error ->
288+ :orddict . store ( key , res , dict )
289+ end
290+
291+ assign_vars t , dict , res
292+ end
293+
294+ defp assign_vars ( [ _ | t ] , dict , res ) do
295+ assign_vars t , dict , res
296+ end
297+
298+ defp assign_vars ( [ ] , dict , _res ) do
299+ dict
300+ end
301+
302+ defp assign_nested_vars ( [ vars | vt ] , dict , [ res | rt ] ) do
303+ assign_nested_vars ( vt , assign_vars ( vars , dict , res ) , rt )
304+ end
305+
306+ defp assign_nested_vars ( [ ] , dict , [ ] ) do
307+ dict
308+ end
309+
310+ defp extract_vars ( { :match , _ , left , right } , vars ) do
311+ vars = extract_vars ( right , vars )
312+ extract_vars ( left , vars )
313+ end
314+
315+ defp extract_vars ( { :var , _ , name } , vars ) do
316+ [ name | vars ]
317+ end
318+
319+ defp extract_vars ( { :tuple , _ , args } , vars ) do
320+ [ Enum . map ( args , extract_vars ( & 1 , [ ] ) ) | vars ]
321+ end
322+
323+ defp extract_vars ( _ , vars ) do
324+ vars
325+ end
326+
327+ defp join_dict ( [ { _ , dict , _ } | t ] ) do
328+ join_dict ( t , dict )
329+ end
330+
331+ defp join_dict ( [ { _ , dict , _ } | t ] , other ) do
332+ other = Enum . reduce other , other , fn
333+ { key , { value , _ } = res } , acc ->
334+ case :orddict . find ( key , dict ) do
335+ { :ok , ^ res } -> acc
336+ { :ok , { ^ value , _ } } -> :orddict . store ( key , { value , nil } , acc )
337+ { :ok , _ } -> :orddict . store ( key , nil , acc )
338+ :error -> :orddict . erase ( key , acc )
339+ end
340+ end
341+
342+ join_dict ( t , other )
343+ end
344+
345+ defp join_dict ( [ ] , other ) do
346+ other
347+ end
348+
349+ defp join_result ( [ { _ , _ , res } | t ] ) do
350+ join_result ( t , res )
351+ end
352+
353+ defp join_result ( [ { _ , _ , res } | t ] , res ) do
354+ join_result ( t , res )
355+ end
356+
357+ defp join_result ( [ { _ , _ , { res , _ } } | t ] , { res , _ } ) do
358+ join_result ( t , { res , nil } )
359+ end
360+
361+ defp join_result ( [ { _ , _ , _ } | _ ] , _res ) do
362+ nil
363+ end
364+
365+ defp join_result ( [ ] , res ) do
366+ res
367+ end
368+ end
0 commit comments