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