[jit] Constant folding for some Math operations on doubles (#9281)
[mono-project.git] / mono / mini / cfold.c
blobd413196c33d7a87f66746d79004a4c3e5931dfeb
1 /**
2 * \file
3 * Constant folding support
5 * Author:
6 * Paolo Molaro (lupus@ximian.com)
7 * Dietmar Maurer (dietmar@ximian.com)
9 * (C) 2003 Ximian, Inc. http://www.ximian.com
11 #include <config.h>
13 #include "mini.h"
14 #include "ir-emit.h"
16 /* WTF is this doing here?!?!? */
17 int
18 mono_is_power_of_two (guint32 val)
20 int i, j, k;
22 for (i = 0, j = 1, k = 0xfffffffe; i < 32; ++i, j = j << 1, k = k << 1) {
23 if (val & j)
24 break;
26 if (i == 32 || val & k)
27 return -1;
28 return i;
31 #ifndef G_MININT32
32 #define MYGINT32_MAX 2147483647
33 #define G_MININT32 (-MYGINT32_MAX -1)
34 #endif
36 #define FOLD_UNOP(name,op) \
37 case name: \
38 dest->inst_c0 = op arg1->inst_c0; \
39 break;
41 #define FOLD_BINOP(name, op) \
42 case name: \
43 dest->inst_c0 = arg1->inst_c0 op arg2->inst_c0; \
44 break;
46 #define FOLD_FBINOP(name, op) \
47 case name: \
48 dest->inst_p0 = (double *)mono_domain_alloc (cfg->domain, sizeof (double)); \
49 *(double *)dest->inst_p0 = (*((double *) arg1->inst_p0)) op (*((double *) arg2->inst_p0)); \
50 break;
52 #define FOLD_BINOPC(name,op,cast) \
53 case name: \
54 dest->inst_c0 = (cast)arg1->inst_c0 op (cast)arg2->inst_c0; \
55 break;
57 #define FOLD_BINOP2_IMM(name, op) \
58 case name: \
59 dest->inst_c0 = arg1->inst_c0 op ins->inst_imm; \
60 break;
62 #define FOLD_BINOPC2_IMM(name, op, cast) \
63 case name: \
64 dest->inst_c0 = (cast)arg1->inst_c0 op (cast)ins->inst_imm; \
65 break;
67 #define FOLD_BINOPCXX(name,op,cast) \
68 case name: \
69 res = (cast)arg1->inst_c0 op (cast)arg2->inst_c0; \
70 break; \
72 #define ALLOC_DEST(cfg, dest, ins) do { \
73 if (!(dest)) { \
74 MONO_INST_NEW ((cfg), (dest), -1); \
75 (dest)->dreg = (ins)->dreg; \
76 } \
77 } while (0)
79 #ifndef DISABLE_JIT
81 /**
82 * mono_constant_fold_ins:
84 * Perform constant folding on INS, using ARG1 and ARG2 as the arguments. If OVERWRITE is
85 * true, then store the result back into INS and return INS. Otherwise allocate a new ins,
86 * store the result into it and return it. If constant folding cannot be performed, return
87 * NULL.
89 MonoInst*
90 mono_constant_fold_ins (MonoCompile *cfg, MonoInst *ins, MonoInst *arg1, MonoInst *arg2, gboolean overwrite)
92 MonoInst *dest = NULL;
94 if (overwrite)
95 dest = ins;
97 switch (ins->opcode) {
98 case OP_FADD:
99 case OP_FMUL:
100 if (arg2->opcode == OP_R8CONST && arg2->opcode == OP_R8CONST){
101 ALLOC_DEST (cfg, dest, ins);
102 switch (ins->opcode) {
103 FOLD_FBINOP (OP_FADD, +);
104 FOLD_FBINOP (OP_FMUL, *);
106 dest->opcode = OP_R8CONST;
107 MONO_INST_NULLIFY_SREGS (dest);
109 break;
110 case OP_IMUL:
111 case OP_IADD:
112 case OP_IAND:
113 case OP_IOR:
114 case OP_IXOR:
115 if (arg2->opcode == OP_ICONST) {
116 if (arg1->opcode == OP_ICONST) {
117 ALLOC_DEST (cfg, dest, ins);
118 switch (ins->opcode) {
119 FOLD_BINOP (OP_IMUL, *);
120 FOLD_BINOP (OP_IADD, +);
121 FOLD_BINOP (OP_IAND, &);
122 FOLD_BINOP (OP_IOR, |);
123 FOLD_BINOP (OP_IXOR, ^);
125 dest->opcode = OP_ICONST;
126 MONO_INST_NULLIFY_SREGS (dest);
128 } else if (arg1->opcode == OP_ICONST) {
130 * This is commutative so swap the arguments, allowing the _imm variant
131 * to be used later.
133 if (mono_op_to_op_imm (ins->opcode) != -1) {
134 ALLOC_DEST (cfg, dest, ins);
135 dest->opcode = mono_op_to_op_imm (ins->opcode);
136 dest->sreg1 = ins->sreg2;
137 dest->sreg2 = -1;
138 dest->inst_imm = arg1->inst_c0;
141 break;
142 case OP_IMUL_IMM:
143 case OP_IADD_IMM:
144 case OP_IAND_IMM:
145 case OP_IOR_IMM:
146 case OP_IXOR_IMM:
147 case OP_ISUB_IMM:
148 case OP_ISHL_IMM:
149 case OP_ISHR_IMM:
150 case OP_ISHR_UN_IMM:
151 case OP_SHL_IMM:
152 if (arg1->opcode == OP_ICONST) {
153 ALLOC_DEST (cfg, dest, ins);
154 switch (ins->opcode) {
155 FOLD_BINOP2_IMM (OP_IMUL_IMM, *);
156 FOLD_BINOP2_IMM (OP_IADD_IMM, +);
157 FOLD_BINOP2_IMM (OP_IAND_IMM, &);
158 FOLD_BINOP2_IMM (OP_IOR_IMM, |);
159 FOLD_BINOP2_IMM (OP_IXOR_IMM, ^);
160 FOLD_BINOP2_IMM (OP_ISUB_IMM, -);
161 FOLD_BINOPC2_IMM (OP_ISHL_IMM, <<, gint32);
162 FOLD_BINOPC2_IMM (OP_ISHR_IMM, >>, gint32);
163 FOLD_BINOPC2_IMM (OP_ISHR_UN_IMM, >>, guint32);
164 FOLD_BINOP2_IMM (OP_SHL_IMM, <<);
166 dest->opcode = OP_ICONST;
167 MONO_INST_NULLIFY_SREGS (dest);
169 break;
170 case OP_ISUB:
171 case OP_ISHL:
172 case OP_ISHR:
173 case OP_ISHR_UN:
174 if ((arg1->opcode == OP_ICONST) && (arg2->opcode == OP_ICONST)) {
175 ALLOC_DEST (cfg, dest, ins);
176 switch (ins->opcode) {
177 FOLD_BINOP (OP_ISUB, -);
178 FOLD_BINOP (OP_ISHL, <<);
179 FOLD_BINOP (OP_ISHR, >>);
180 FOLD_BINOPC (OP_ISHR_UN, >>, guint32);
182 dest->opcode = OP_ICONST;
183 MONO_INST_NULLIFY_SREGS (dest);
185 break;
186 case OP_IDIV:
187 case OP_IDIV_UN:
188 case OP_IREM:
189 case OP_IREM_UN:
190 if ((arg1->opcode == OP_ICONST) && (arg2->opcode == OP_ICONST)) {
191 if ((arg2->inst_c0 == 0) || ((arg1->inst_c0 == G_MININT32) && (arg2->inst_c0 == -1)))
192 return NULL;
193 ALLOC_DEST (cfg, dest, ins);
194 switch (ins->opcode) {
195 FOLD_BINOPC (OP_IDIV, /, gint32);
196 FOLD_BINOPC (OP_IDIV_UN, /, guint32);
197 FOLD_BINOPC (OP_IREM, %, gint32);
198 FOLD_BINOPC (OP_IREM_UN, %, guint32);
200 dest->opcode = OP_ICONST;
201 MONO_INST_NULLIFY_SREGS (dest);
203 break;
204 case OP_IDIV_IMM:
205 case OP_IDIV_UN_IMM:
206 case OP_IREM_IMM:
207 case OP_IREM_UN_IMM:
208 if (arg1->opcode == OP_ICONST) {
209 if ((ins->inst_imm == 0) || ((arg1->inst_c0 == G_MININT32) && (ins->inst_imm == -1)))
210 return NULL;
211 ALLOC_DEST (cfg, dest, ins);
212 switch (ins->opcode) {
213 FOLD_BINOPC2_IMM (OP_IDIV_IMM, /, gint32);
214 FOLD_BINOPC2_IMM (OP_IDIV_UN_IMM, /, guint32);
215 FOLD_BINOPC2_IMM (OP_IREM_IMM, %, gint32);
216 FOLD_BINOPC2_IMM (OP_IREM_UN_IMM, %, guint32);
217 default:
218 g_assert_not_reached ();
220 dest->opcode = OP_ICONST;
221 MONO_INST_NULLIFY_SREGS (dest);
223 break;
224 /* case OP_INEG: */
225 case OP_INOT:
226 case OP_INEG:
227 if (arg1->opcode == OP_ICONST) {
228 /* INEG sets cflags on x86, and the LNEG decomposition depends on that */
229 #if SIZEOF_REGISTER == 4
230 if (ins->opcode == OP_INEG)
231 return NULL;
232 #endif
233 ALLOC_DEST (cfg, dest, ins);
234 switch (ins->opcode) {
235 FOLD_UNOP (OP_INEG,-);
236 FOLD_UNOP (OP_INOT,~);
238 dest->opcode = OP_ICONST;
239 MONO_INST_NULLIFY_SREGS (dest);
241 break;
242 case OP_SEXT_I4:
243 if (arg1->opcode == OP_ICONST && arg1->inst_c0 >= 0 && arg1->inst_c0 < (1 << 16) && overwrite) {
244 dest->opcode = OP_ICONST;
245 dest->sreg1 = -1;
246 dest->inst_c0 = arg1->inst_c0;
248 break;
249 case OP_MOVE:
250 #if SIZEOF_REGISTER == 8
251 if ((arg1->opcode == OP_ICONST) || (arg1->opcode == OP_I8CONST)) {
252 #else
253 if (arg1->opcode == OP_ICONST) {
254 #endif
255 ALLOC_DEST (cfg, dest, ins);
256 dest->opcode = arg1->opcode;
257 MONO_INST_NULLIFY_SREGS (dest);
258 dest->inst_c0 = arg1->inst_c0;
260 break;
261 case OP_VMOVE:
262 if (arg1->opcode == OP_VZERO) {
263 ALLOC_DEST (cfg, dest, ins);
264 dest->opcode = OP_VZERO;
265 dest->sreg1 = -1;
267 break;
268 case OP_XMOVE:
269 if (arg1->opcode == OP_XZERO) {
270 ALLOC_DEST (cfg, dest, ins);
271 dest->opcode = OP_XZERO;
272 dest->sreg1 = -1;
274 break;
275 case OP_COMPARE:
276 case OP_ICOMPARE:
277 case OP_COMPARE_IMM:
278 case OP_ICOMPARE_IMM: {
279 MonoInst dummy_arg2;
280 if (ins->sreg2 == -1) {
281 arg2 = &dummy_arg2;
282 arg2->opcode = OP_ICONST;
283 arg2->inst_c0 = ins->inst_imm;
286 if ((arg1->opcode == OP_ICONST) && (arg2->opcode == OP_ICONST) && ins->next) {
287 MonoInst *next = ins->next;
288 gboolean res = FALSE;
290 switch (next->opcode) {
291 case OP_CEQ:
292 case OP_ICEQ:
293 case OP_CGT:
294 case OP_ICGT:
295 case OP_CGT_UN:
296 case OP_ICGT_UN:
297 case OP_CLT:
298 case OP_ICLT:
299 case OP_CLT_UN:
300 case OP_ICLT_UN:
301 switch (next->opcode) {
302 FOLD_BINOPCXX (OP_CEQ,==,gint32);
303 FOLD_BINOPCXX (OP_ICEQ,==,gint32);
304 FOLD_BINOPCXX (OP_CGT,>,gint32);
305 FOLD_BINOPCXX (OP_ICGT,>,gint32);
306 FOLD_BINOPCXX (OP_CGT_UN,>,guint32);
307 FOLD_BINOPCXX (OP_ICGT_UN,>,guint32);
308 FOLD_BINOPCXX (OP_CLT,<,gint32);
309 FOLD_BINOPCXX (OP_ICLT,<,gint32);
310 FOLD_BINOPCXX (OP_CLT_UN,<,guint32);
311 FOLD_BINOPCXX (OP_ICLT_UN,<,guint32);
314 if (overwrite) {
315 NULLIFY_INS (ins);
316 next->opcode = OP_ICONST;
317 next->inst_c0 = res;
318 MONO_INST_NULLIFY_SREGS (next);
319 } else {
320 ALLOC_DEST (cfg, dest, ins);
321 dest->opcode = OP_ICONST;
322 dest->inst_c0 = res;
324 break;
325 case OP_IBEQ:
326 case OP_IBNE_UN:
327 case OP_IBGT:
328 case OP_IBGT_UN:
329 case OP_IBGE:
330 case OP_IBGE_UN:
331 case OP_IBLT:
332 case OP_IBLT_UN:
333 case OP_IBLE:
334 case OP_IBLE_UN:
335 switch (next->opcode) {
336 FOLD_BINOPCXX (OP_IBEQ,==,gint32);
337 FOLD_BINOPCXX (OP_IBNE_UN,!=,guint32);
338 FOLD_BINOPCXX (OP_IBGT,>,gint32);
339 FOLD_BINOPCXX (OP_IBGT_UN,>,guint32);
340 FOLD_BINOPCXX (OP_IBGE,>=,gint32);
341 FOLD_BINOPCXX (OP_IBGE_UN,>=,guint32);
342 FOLD_BINOPCXX (OP_IBLT,<,gint32);
343 FOLD_BINOPCXX (OP_IBLT_UN,<,guint32);
344 FOLD_BINOPCXX (OP_IBLE,<=,gint32);
345 FOLD_BINOPCXX (OP_IBLE_UN,<=,guint32);
348 if (overwrite) {
350 * Can't nullify OP_COMPARE here since the decompose long branch
351 * opcodes depend on it being executed. Also, the branch might not
352 * be eliminated after all if loop opts is disabled, for example.
354 if (res)
355 next->flags |= MONO_INST_CFOLD_TAKEN;
356 else
357 next->flags |= MONO_INST_CFOLD_NOT_TAKEN;
358 } else {
359 ALLOC_DEST (cfg, dest, ins);
360 dest->opcode = OP_ICONST;
361 dest->inst_c0 = res;
363 break;
364 case OP_COND_EXC_EQ:
365 res = arg1->inst_c0 == arg2->inst_c0;
366 if (!res) {
367 if (overwrite) {
368 NULLIFY_INS (ins);
369 NULLIFY_INS (next);
370 } else {
371 ALLOC_DEST (cfg, dest, ins);
372 dest->opcode = OP_ICONST;
373 dest->inst_c0 = res;
376 break;
377 case OP_NOP:
378 case OP_BR:
379 /* This happens when a conditional branch is eliminated */
380 if (next->next == NULL) {
381 /* Last ins */
382 if (overwrite)
383 NULLIFY_INS (ins);
385 break;
386 default:
387 return NULL;
390 if ((arg1->opcode == OP_PCONST) && (arg2->opcode == OP_PCONST) && ins->next) {
391 MonoInst *next = ins->next;
393 if (next->opcode == OP_LCEQ) {
394 gboolean res = arg1->inst_p0 == arg2->inst_p0;
395 if (overwrite) {
396 NULLIFY_INS (ins);
397 next->opcode = OP_ICONST;
398 next->inst_c0 = res;
399 MONO_INST_NULLIFY_SREGS (next);
400 } else {
401 ALLOC_DEST (cfg, dest, ins);
402 dest->opcode = OP_ICONST;
403 dest->inst_c0 = res;
405 break;
408 break;
410 case OP_FMOVE:
411 if (arg1->opcode == OP_R8CONST) {
412 ALLOC_DEST (cfg, dest, ins);
413 dest->opcode = OP_R8CONST;
414 dest->sreg1 = -1;
415 dest->inst_p0 = arg1->inst_p0;
417 break;
420 * TODO:
421 * conv.* opcodes.
422 * *ovf* opcodes? It's slow and hard to do in C.
423 * switch can be replaced by a simple jump
425 default:
426 return NULL;
429 return dest;
433 #endif /* DISABLE_JIT */