forked from freewilll/wcc
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfunctions.c
More file actions
1705 lines (1375 loc) · 71.2 KB
/
functions.c
File metadata and controls
1705 lines (1375 loc) · 71.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "wcc.h"
#define fpa_pl(fpa, i) (*((FunctionParamLocations *) fpa->param_locations->elements[i]))
static LongSet *allocated_functions;
static List *allocated_function_param_allocatons;
// Details about a single scalar in a struct/union
typedef struct struct_or_union_scalar {
Type *type;
int offset;
} StructOrUnionScalar;
// A list of StructOrUnionScalar
typedef struct struct_or_union_scalars {
StructOrUnionScalar **scalars;
int count;
} StructOrUnionScalars;
void init_function_allocations(void) {
allocated_functions = new_longset();
allocated_function_param_allocatons = new_list(128);
}
void free_function(Function *function, int remove_from_allocations) {
free_strmap(function->labels);
if (function->static_symbols) free_list(function->static_symbols);
wfree(function);
if (remove_from_allocations) longset_delete(allocated_functions, (long) function);
}
// Free remaining functions and other memory
void free_functions(void) {
longset_foreach(allocated_functions, it) free_function((Function *) longset_iterator_element(&it), 0);
free_longset(allocated_functions);
for (int i = 0; i < allocated_function_param_allocatons->length; i++)
free_function_param_allocaton(allocated_function_param_allocatons->elements[i]);
free_list(allocated_function_param_allocatons);
}
Function *new_function(void) {
Function *function = wcalloc(1, sizeof(Function));
longset_add(allocated_functions, (long) function);
return function;
}
static int make_struct_or_union_arg_move_instructions(
Function *function, Tac *ir, Value *param, int preg_class, int register_index,
FunctionParamLocation *location, RegisterSet *register_set);
static Value *make_param_dst_on_stack(int type, int stack_index, int offset) {
Value *dst = new_value();
dst->type = new_type(type);
dst->is_lvalue = 1;
dst->stack_index = stack_index;
dst->offset = offset;
return dst;
}
// This implements the reverse of make_int_struct_or_union_arg_move_instructions
// This is also used for moving struct/unions into function return value registers
static int make_int_struct_or_union_move_from_register_to_stack_instructions(
Function *function, Tac *ir, Type *type, FunctionParamLocation* pl,
int register_index, int stack_index, RegisterSet *register_set, int param_register_vreg) {
if (debug_function_param_mapping)
printf("Adding param move from int register %d size %d to stack_index %d, offset %d\n", register_index, pl->stru_size, stack_index, pl->stru_offset);
if (!param_register_vreg) param_register_vreg = ++function->vreg_count;
// Make shift register
Value *shift_register = new_value();
shift_register->vreg = param_register_vreg;
int live_range_preg = register_set->int_registers[register_index];
shift_register->live_range_preg = live_range_preg;
int size = pl->stru_size;
int offset = 0;
for (int i = 3; i >= 0; i--) {
int size_unit = 1 << i;
if (size_unit > size) continue;
// Make dst on stack
Value *dst = new_value();
dst->type = new_type(TYPE_CHAR + i);
dst->type->is_unsigned = 1;
dst->is_lvalue = 1;
dst->stack_index = stack_index;
dst->offset = pl->stru_offset + offset;
shift_register = dup_value(shift_register);
shift_register->type = new_type(TYPE_CHAR + i);
shift_register->type->is_unsigned = 1;
insert_instruction_from_operation(ir, IR_MOVE, dst, shift_register, 0, 1);
size -= size_unit;
offset += size_unit;
if (size == 0) break;
// Shift the register over size_unit * 8 bytes
shift_register = dup_value(shift_register);
shift_register->type = new_type(TYPE_LONG);
Value *new_shift_register = dup_value(shift_register);
new_shift_register->live_range_preg = 0;
insert_instruction_from_operation(ir, IR_BSHR, new_shift_register, shift_register, new_integral_constant(TYPE_LONG, size_unit * 8), 1);
shift_register = new_shift_register;
}
return live_range_preg;
}
// This implements the reverse of make_sse_struct_or_union_arg_move_instructions
static int make_sse_struct_or_union_move_from_register_to_stack_instructions(
Function *function, Tac *ir, Type *type, FunctionParamLocation* pl,
int register_index, int stack_index, RegisterSet *register_set, int param_register_vreg) {
if (debug_function_param_mapping)
printf("Adding param move from sse register %d size %d to stack_index %d, offset %d\n", register_index, pl->stru_size, stack_index, pl->stru_offset);
if (!param_register_vreg) param_register_vreg = ++function->vreg_count;
// Make param register value
Value *param_register = new_value();
param_register->vreg = param_register_vreg;
int live_range_preg = register_set->sse_registers[register_index];
param_register->live_range_preg = live_range_preg;
if (pl->stru_size == 4) {
// Move a single float
param_register->type = new_type(TYPE_FLOAT);
Value *dst = make_param_dst_on_stack(TYPE_FLOAT, stack_index, pl->stru_offset);
insert_instruction_from_operation(ir, IR_MOVE, dst, param_register, 0, 1);
}
else if (pl->stru_size == 8 && pl->stru_member_count == 1) {
// Move a single double
param_register->type = new_type(TYPE_DOUBLE);
Value *dst = make_param_dst_on_stack(TYPE_DOUBLE, stack_index, pl->stru_offset);
insert_instruction_from_operation(ir, IR_MOVE, dst, param_register, 0, 1);
}
else {
// Move two floats. It must first be copied into an integer register and then
// copied to the stack.
param_register->type = new_type(TYPE_DOUBLE);
Value *temp_int = new_value();
temp_int->type = new_type(TYPE_LONG);
temp_int->vreg = ++function->vreg_count;
insert_instruction_from_operation(ir, IR_MOVE_PREG_CLASS, temp_int, param_register, 0, 1);
Value *dst = make_param_dst_on_stack(TYPE_LONG, stack_index, pl->stru_offset);
insert_instruction_from_operation(ir, IR_MOVE, dst, temp_int, 0, 1);
}
return live_range_preg;
}
// Move struct/union data from registers or memory to the destination of a function call
static void add_function_call_result_moves_for_struct_or_union(Function *function, Tac *ir) {
if (ir->dst->stack_index == 0) panic("Expected a stack index for a function call returning a struct/union");
int stack_index = ir->dst->stack_index;
Type *type = ir->dst->type;
Type *function_type = ir->src1->type;
FunctionParamAllocation *fpa = function_type->function->return_value_fpa;
FunctionParamLocations *fpl = fpa->param_locations->elements[0];
Value *function_value = ir->src1;
if (fpl->locations[0].stack_offset == -1) {
// Move registers to a struct/union on the stack
ir->dst = 0;
ir = ir->next;
// Allocate result vregs and create a live range or them
int *live_range_pregs = wmalloc(sizeof(int) * 8);
for (int loc = 0; loc < fpl->count; loc++) {
FunctionParamLocation *location = &(fpl->locations[loc]);
live_range_pregs[loc] = ++function->vreg_count;
Value *dst = new_value();
dst->vreg = live_range_pregs[loc];
dst->type = location->int_register != -1 ? new_type(TYPE_LONG) : new_type(TYPE_DOUBLE);
insert_instruction_from_operation(ir, IR_CALL_ARG_REG, dst, 0, 0, 1);
}
for (int loc = 0; loc < fpl->count; loc++) {
FunctionParamLocation *location = &(fpl->locations[loc]);
int live_range_preg;
int param_register_vreg = live_range_pregs[loc];
if (location->int_register != -1)
live_range_preg = make_int_struct_or_union_move_from_register_to_stack_instructions(
function, ir, type, location, location->int_register, stack_index,
&function_return_value_register_set, param_register_vreg);
else if (location->sse_register != -1)
live_range_preg = make_sse_struct_or_union_move_from_register_to_stack_instructions(
function, ir, type, location, location->sse_register, stack_index,
&function_return_value_register_set, param_register_vreg);
else
panic("Got unexpected stack offset in add_struct_or_union_param_move");
add_to_set(function_value->return_value_live_ranges, live_range_preg);
}
wfree(live_range_pregs);
}
else {
// Move registers to a struct/union in memory. rax contains the address
// Ensure RAX doesn't get clobbered by the function call
add_to_set(function_value->return_value_live_ranges, LIVE_RANGE_PREG_RAX_INDEX);
// Take address of dst struct/union on the stack
Value *address_value = new_value();
address_value->vreg = ++function->vreg_count;
address_value->type = make_pointer_to_void();
insert_instruction_from_operation(ir, IR_ADDRESS_OF, address_value, ir->dst, 0, 1);
// Move struct/union target address into rdi
Value *rdi_value = new_value();
rdi_value->vreg = ++function->vreg_count;
rdi_value->type = make_pointer_to_void();
rdi_value->live_range_preg = LIVE_RANGE_PREG_RDI_INDEX;
insert_instruction_from_operation(ir, IR_MOVE, rdi_value, address_value, 0, 1);
// Setup dst for rax
Value *struct_dst = ir->dst;
ir->dst = new_value();
ir->dst->vreg = ++function->vreg_count;
ir->dst->type = make_pointer_to_void();
ir->dst->live_range_preg = LIVE_RANGE_PREG_RAX_INDEX;
// Prepare src1 to also be rax, but as an lvalue
Value *src1 = dup_value(ir->dst);
src1->is_lvalue = 1;
// Add the memory copy
int size = get_type_size(type);
ir = add_memory_copy(function, ir, struct_dst, src1, size);
}
}
// Convert v1 = IR_CALL... to:
// 1. v2 = IR_CALL...
// 2. v1 = v2
// Live range coalescing prevents the v1-v2 live range from getting merged with
// a special check for IR_CALL.
void add_function_call_result_moves(Function *function) {
make_vreg_count(function, 0);
for (Tac *ir = function->ir; ir; ir = ir->next) {
if (ir->operation != IR_CALL || !ir->dst) continue;
if (ir->dst->type->type == TYPE_STRUCT_OR_UNION)
add_function_call_result_moves_for_struct_or_union(function, ir);
else if (ir->dst->type->type != TYPE_LONG_DOUBLE) {
// Add move for integer, pointer, or SSE
Value *value = dup_value(ir->dst);
value->vreg = ++function->vreg_count;
Tac *tac = new_instruction(IR_MOVE);
tac->dst = ir->dst;
int is_sse = is_sse_floating_point_type(ir->dst->type);
tac->src1 = value;
tac->src1->live_range_preg = is_sse ? LIVE_RANGE_PREG_XMM00_INDEX : LIVE_RANGE_PREG_RAX_INDEX;
add_to_set(ir->src1->return_value_live_ranges, tac->src1->live_range_preg);
ir->dst = value;
insert_instruction(ir->next, tac, 1);
}
}
}
// Add IR_CALL_ARG_REG instructions that don't do anything, but ensure
// that the interference graph and register selection code create
// a live range for the interval between the function register assignment and
// the function call. Without this, there is a chance that function call
// registers are used as temporaries during the code instructions
// emitted above.
static void add_ir_call_reg_instructions(Tac *ir, Value **function_call_values, int count) {
for (int i = 0; i < count; i++)
if (function_call_values[i])
insert_instruction_from_operation(ir, IR_CALL_ARG_REG, 0, function_call_values[i], 0, 1);
}
// Move struct or union of size <= 32 into rax/rdx or xmm0/xmm1
static void add_function_return_moves_for_struct_or_union(Function *function, Tac *ir, char *identifier) {
// Determine registers
FunctionParamAllocation *fpa = init_function_param_allocaton(identifier);
add_function_param_to_allocation(fpa, ir->src1->type);
FunctionParamLocations *fpl = fpa->param_locations->elements[0];
Value **function_call_values = wcalloc(2, sizeof(Value *));
if (fpl->locations[0].stack_offset != -1) {
// Move data into memory
ir->operation = IR_NOP;
// Convert src1 to be a pointer to void
Value *src1 = dup_value(ir->src1);
src1->type = make_pointer_to_void();
Value *dst = dup_value(function->return_value_pointer);
dst->is_lvalue = 1;
int size = get_type_size(ir->src1->type);
ir = add_memory_copy(function, ir, dst, src1, size);
// Add move of the target pointer to rax
dst = new_value();
dst->type = make_pointer_to_void();
dst->live_range_preg = LIVE_RANGE_PREG_RAX_INDEX;
dst->type = make_pointer_to_void();
dst->vreg = ++function->vreg_count;
ir = insert_instruction_after_from_operation(ir, IR_MOVE, dst, function->return_value_pointer, 0);
ir = insert_instruction_after_from_operation(ir, IR_RETURN, 0, 0, 0);
}
else {
// Move the data into registers
for (int loc = fpl->count - 1; loc >= 0; loc--) {
FunctionParamLocation *location = &(fpl->locations[loc]);
int preg_class = (location->int_register != -1) ? PC_INT : PC_SSE;
int register_index = (preg_class == PC_INT) ? location->int_register : location->sse_register;
Value *param = ir->src1;
int vreg = make_struct_or_union_arg_move_instructions(function, ir, param, preg_class, register_index, location, &function_return_value_register_set);
function_call_values[loc] = new_value();
function_call_values[loc]->type = preg_class == PC_INT ? new_type(TYPE_LONG) : new_type(TYPE_DOUBLE);
function_call_values[loc]->vreg = vreg;
}
// Add instructions to ensure the values stay in the registers
add_ir_call_reg_instructions(ir, function_call_values, 2);
}
wfree(function_call_values);
}
// Add a move for a function return value. If it's a long double, a load can be done,
// otherwise, either rax or xmm0 must hold the result.
void add_function_return_moves(Function *function, char *identifier) {
for (Tac *ir = function->ir; ir; ir = ir->next) {
if ((ir->operation == IR_RETURN && !ir->src1) || ir->operation != IR_RETURN) continue;
// Implicit else, operation == IR_RETURN & ir-src1 has a value
if (ir->src1->type->type == TYPE_LONG_DOUBLE) {
insert_instruction_from_operation(ir, IR_LOAD_LONG_DOUBLE, 0, ir->src1, 0, 1);
ir->operation = IR_RETURN;
ir->dst = 0;
ir->src1 = 0;
ir->src2 = 0;
}
else if (ir->src1->type->type == TYPE_STRUCT_OR_UNION)
add_function_return_moves_for_struct_or_union(function, ir, identifier);
else {
int is_sse = is_sse_floating_point_type(function->type->target);
int live_range_preg = is_sse ? LIVE_RANGE_PREG_XMM00_INDEX : LIVE_RANGE_PREG_RAX_INDEX;
ir->src1->preferred_live_range_preg_index = live_range_preg;
ir->dst = new_value();
ir->dst->type = dup_type(function->type->target);
if (ir->dst->type->type == TYPE_ENUM) ir->dst->type = new_type(TYPE_INT);
ir->dst->vreg = ++function->vreg_count;
ir->dst->live_range_preg = live_range_preg;
ir->src1->preferred_live_range_preg_index = live_range_preg;
insert_instruction_from_operation(ir, IR_MOVE, ir->dst, ir->src1, 0, 1);
ir->operation = IR_RETURN;
ir->dst = 0;
ir->src1 = 0;
ir->src2 = 0;
}
ir->src1 = 0;
}
}
// Add a IR_MOVE instruction from a value to a function call register
// function_call_*_register_arg_index ensures that dst will become the actual
// x86_64 physical register rdi, rsi, etc
static int add_arg_move_to_register(Function *function, Tac *ir, Type *type, Value *param, int preg_class, int register_index, RegisterSet *register_set) {
int *arg_registers = preg_class == PC_INT ? int_arg_registers : sse_arg_registers;
Tac *tac = new_instruction(IR_MOVE);
// dst
tac->dst = new_value();
tac->dst->type = dup_type(type);
tac->dst->vreg = ++function->vreg_count;
tac->dst->live_range_preg = preg_class == PC_INT ? register_set->int_registers[register_index] : register_set->sse_registers[register_index];
// src
tac->src1 = param;
tac->src1->preferred_live_range_preg_index = arg_registers[register_index];
if (debug_function_arg_mapping) printf("Adding arg move from register for preg-class=%d register_index=%d\n", preg_class, register_index);
insert_instruction(ir, tac, 1);
return tac->dst->vreg;
}
// Load a scalar in a struct into a register. The scalar can be either a local, global, or lvalue in register
// If it's an lvalue in a register, it is indirected, otherwise moved.
static void load_struct_scalar_into_temp(Function *function, Tac *ir, Value *param, FunctionParamLocation *pl, Type *type, Value *temp, int offset) {
int lvalue_in_register = param->is_lvalue && param->vreg;
Value *src1 = dup_value(param);
src1->type = type;
src1->offset += pl->stru_offset + offset;
insert_instruction_from_operation(ir, lvalue_in_register ? IR_INDIRECT : IR_MOVE, temp, src1, 0, 1);
}
// Load a scalar in a struct into a register. The scalar can be either a local, global, or lvalue in register
// If it's an lvalue in a register, it is indirected, otherwise moved.
static Value *load_struct_scalar(Function *function, Tac *ir, Value *param, FunctionParamLocation *pl, Type *type) {
Value *temp = new_value();
temp->type = type;
temp->vreg = ++function->vreg_count;
load_struct_scalar_into_temp(function, ir, param, pl, temp->type, temp, 0);
return temp;
}
static Value *make_long_temp(Function *function) {
Value *result = new_value();
result->type = new_type(TYPE_LONG);
result->type->is_unsigned = 1;
result->vreg = ++function->vreg_count;
return result;
}
// Load an 8-byte into an integer register. In the best case, a single move instruction
// is produced. In the worst case, 3 load instructions with 3 bit shifts & 3 bitwise ors.
// Try sizes in order of 8, 4, 2, 1.
// The code produced:
// size = 1 load char
// size = 2 load short
// size = 3 load short, load char, shift char, or char
// size = 4 load int
// size = 5 load int, load char, shift char, or char
// size = 6 load int, load short, shift short, or short
// size = 7 load int, load short, shift short, or short, load char, shift char, or char
// size = 8 load long
static int make_int_struct_or_union_arg_move_instructions(
Function *function, Tac *ir, Value *param, int preg_class, int register_index,
FunctionParamLocation *pl, RegisterSet *register_set) {
// Make the shift register
Value *result_register = new_value();
result_register->type = new_type(TYPE_LONG);
result_register->type->is_unsigned = 1;
result_register->vreg = ++function->vreg_count;
if (debug_function_arg_mapping) printf("Adding arg move from struct to integer register_index=%d register size=%d\n", register_index, pl->stru_size);
int lvalue_in_register = param->is_lvalue && param->vreg;
int temp_loaded = 0;
int size = pl->stru_size;
int offset = 0;
for (int i = 3; i >= 0; i--) {
int size_unit = 1 << i;
if (size_unit > size) continue;
if (!temp_loaded) {
Type *type = new_type(TYPE_CHAR + i);
type->is_unsigned = 1;
load_struct_scalar_into_temp(function, ir, param, pl, type, result_register, offset);
temp_loaded = 1;
}
else {
// Load value
Value *loaded_value = make_long_temp(function);
Value *temp2 = dup_value(param);
temp2->type = new_type(TYPE_CHAR + i);
temp2->type->is_unsigned = 1;
temp2->offset += pl->stru_offset + offset;
insert_instruction_from_operation(ir, lvalue_in_register ? IR_INDIRECT : IR_MOVE, loaded_value, temp2, 0, 1);
// Shift loaded value
Value *shifted_value;
if (offset) {
shifted_value = make_long_temp(function);
insert_instruction_from_operation(ir, IR_BSHL, shifted_value, loaded_value, new_integral_constant(TYPE_LONG, offset * 8), 1);
}
else
shifted_value = loaded_value;
// Bitwise or shifted_value and put result in result_register
Value *orred_value = make_long_temp(function);
insert_instruction_from_operation(ir, IR_BOR, orred_value, shifted_value, result_register, 1);
result_register = orred_value;
}
size -= size_unit;
offset += size_unit;
if (size == 0) break;
}
return add_arg_move_to_register(function, ir, new_type(TYPE_LONG), result_register, preg_class, register_index, register_set);
}
// Load an 8-byte of a struct/union that exclusively have floats and doubles in it into a register.
// The struct/union already has an alignment of either 4 or 8, so it can be loaded with simple instructions.
static int make_sse_struct_or_union_arg_move_instructions(
Function *function, Tac *ir, Value *param, int preg_class, int register_index,
FunctionParamLocation *pl, RegisterSet *register_set) {
if (debug_function_arg_mapping) printf("Adding arg move from struct to SSE register_index=%d register size=%d\n", register_index, pl->stru_size);
if (pl->stru_size == 4) {
// Move a single float
Value *temp = load_struct_scalar(function, ir, param, pl, new_type(TYPE_FLOAT));
return add_arg_move_to_register(function, ir, new_type(TYPE_FLOAT), temp, preg_class, register_index, register_set);
}
else if (pl->stru_size == 8 && pl->stru_member_count == 1) {
// Move a single double
Value *temp = load_struct_scalar(function, ir, param, pl, new_type(TYPE_DOUBLE));
return add_arg_move_to_register(function, ir, new_type(TYPE_DOUBLE), temp, preg_class, register_index, register_set);
}
else {
// Move two floats. It must first be loaded into an integer register and then
// copied to an SSE register.
Value *temp_int = load_struct_scalar(function, ir, param, pl, new_type(TYPE_LONG));
Value *temp_sse = new_value();
temp_sse->type = new_type(TYPE_DOUBLE);
temp_sse->vreg = ++function->vreg_count;
insert_instruction_from_operation(ir, IR_MOVE_PREG_CLASS, temp_sse, temp_int, 0, 1);
return add_arg_move_to_register(function, ir, new_type(TYPE_DOUBLE), temp_sse, preg_class, register_index, register_set);
}
}
// Lookup corresponding location for preg_class/register
static FunctionParamLocation *lookup_location(int preg_class, int register_index, FunctionParamLocations *pl) {
// For the location that matches register_index
for (int loc = 0; loc < pl->count; loc++) {
FunctionParamLocation *location = &(pl->locations[loc]);
int function_call_register_arg_index = preg_class == PC_INT
? location->int_register
: location->sse_register;
if (function_call_register_arg_index == register_index) return location;
}
panic("Unhandled struct/union arg move into a register");
}
// Load a function parameter register from an struct or union 8-byte
static int make_struct_or_union_arg_move_instructions(
Function *function, Tac *ir, Value *param, int preg_class, int register_index,
FunctionParamLocation *location, RegisterSet *register_set) {
if (preg_class == PC_INT)
return make_int_struct_or_union_arg_move_instructions(function, ir, param, preg_class, register_index, location, register_set);
else
return make_sse_struct_or_union_arg_move_instructions(function, ir, param, preg_class, register_index, location, register_set);
}
// Insert IR_MOVE instructions before IR_ARG instructions for
// - the first 6 single-register args.
// - the first 8 floating point args.
// The dst of the move will be constrained so that rdi, rsi, xmm0, xmm1 etc are allocated to it.
void add_function_call_arg_moves_for_preg_class(Function *function, int preg_class) {
int function_call_count = make_function_call_count(function);
int register_count = preg_class == PC_INT ? 6 : 8;
// Values of the passed argument, i.e. by the caller
Value **arg_values = wcalloc(function_call_count * register_count, sizeof(Value *));
// param_indexes maps the register indexes to a parameter index, e.g.
// foo(int i, long double ld, int j) will produce
// param_indexes[0] = 0
// param_indexes[1] = 2
int *param_indexes = wmalloc(sizeof(int) * function_call_count * register_count);
memset(param_indexes, -1, sizeof(int) * function_call_count * register_count);
// Set to 1 if the function returns a struct in memory, which reserves rdi for a
// pointer to the return address
char *has_struct_or_union_return_values = wcalloc(function_call_count, sizeof(char));
FunctionParamLocations **param_locations = wmalloc(sizeof(FunctionParamLocations *) * function_call_count * register_count);
memset(param_locations, -1, sizeof(FunctionParamLocations *) * function_call_count * register_count);
make_vreg_count(function, 0);
for (Tac *ir = function->ir; ir; ir = ir->next) {
if (ir->operation == IR_ARG) {
FunctionParamLocations *pl = ir->src1->function_call_arg_locations;
if (ir->src1->has_struct_or_union_return_value) has_struct_or_union_return_values[ir->src1->int_value] = 1;
for (int loc = 0; loc < pl->count; loc++) {
int function_call_register_arg_index = preg_class == PC_INT
? pl->locations[loc].int_register
: pl->locations[loc].sse_register;
if (function_call_register_arg_index >= 0) {
int i = ir->src1->int_value * register_count + function_call_register_arg_index;
arg_values[i] = ir->src2;
param_indexes[i] = ir->src1->function_call_arg_index;
param_locations[i] = pl;
}
}
}
if (ir->operation == IR_CALL) {
Value **call_arg = &(arg_values[ir->src1->int_value * register_count]);
int has_struct_or_union_return_value = has_struct_or_union_return_values[ir->src1->int_value];
int *param_index = &(param_indexes[ir->src1->int_value * register_count]);
FunctionParamLocations **pls = &(param_locations[ir->src1->int_value * register_count]);
Type *called_function_type = ir->src1->type;
// Allocated registers that hold the argument value
Value **function_call_values = wcalloc(register_count, sizeof(Value *));
// Add the moves backwards so that arg 0 (rsi) is last.
int i = 0;
// Advance past the first parameter, which holds the pointer to the struct/union return value
if (has_struct_or_union_return_value && preg_class == PC_INT) {
call_arg++;
param_index++;
pls++;
i++;
}
while (i < register_count && *call_arg) {
call_arg++;
param_index++;
pls++;
i++;
}
call_arg--;
param_index--;
pls--;
i--;
for (; i >= 0; i--) {
// Bail if we're doing integers and RDI is reserved for a struct/union
// return value.
if (has_struct_or_union_return_value && preg_class == PC_INT && i == 0) break;
Type *type;
int pi = *param_index;
if (pi >= 0 && pi < called_function_type->function->param_count) {
type = called_function_type->function->param_types->elements[pi];
}
else
type = apply_default_function_call_argument_promotions((*call_arg)->type);
int function_call_vreg;
Type *function_call_vreg_type;
if (type->type == TYPE_STRUCT_OR_UNION) {
FunctionParamLocation *location = lookup_location(preg_class, i, *pls);
function_call_vreg = make_struct_or_union_arg_move_instructions(function, ir, *call_arg, preg_class, i, location, &arg_register_set);
function_call_vreg_type = preg_class == PC_INT ? new_type(TYPE_LONG) : new_type(TYPE_DOUBLE);
}
else {
if (type->type == TYPE_ARRAY) type = decay_array_to_pointer(type);
if (type->type == TYPE_ENUM) type = new_type(TYPE_INT);
function_call_vreg = add_arg_move_to_register(function, ir, type, *call_arg, preg_class, i, &arg_register_set);
function_call_vreg_type = (*call_arg)->type;
}
function_call_values[i] = new_value();
function_call_values[i]->type = function_call_vreg_type;
function_call_values[i]->vreg = function_call_vreg;
call_arg--;
param_index--;
pls--;
}
add_ir_call_reg_instructions(ir, function_call_values, register_count);
wfree(function_call_values);
}
}
wfree(arg_values);
wfree(param_indexes);
wfree(has_struct_or_union_return_values);
wfree(param_locations);
}
// Add instructions to copy a struct to the stack
static void add_function_call_arg_move_for_struct_or_union_on_stack(Function *function, Tac *ir) {
int size = get_type_size(ir->src2->type);
int rounded_up_size = (size + 7) & ~7;
// Allocate stack space with a sub $n, %rsp instruction
insert_instruction_from_operation(ir, IR_ALLOCATE_STACK, 0, new_integral_constant(TYPE_LONG, rounded_up_size), 0, 1);
// Add an instruction to move the stack pointer %rsp to a temporary register
Value *stack_pointer_temp = make_long_temp(function);
insert_instruction_from_operation(ir, IR_MOVE_STACK_PTR, stack_pointer_temp, 0, 0, 1);
// Prepare destination, which must be a * void
Value *dst = dup_value(stack_pointer_temp);
dst->type = make_pointer_to_void();
dst->is_lvalue = 1;
// Convert src to be a pointer to void
Value *src = dup_value(ir->src2);
src->type = make_pointer_to_void();
if (debug_function_arg_mapping)
printf("Adding memory copy for struct/union SI=%d rounded-up-size=%d\n", src->stack_index, rounded_up_size);
add_memory_copy(function, ir, dst, src, size);
}
// Nuke all IR_ARG instructions that have had code added that moves the value into a register
static void remove_IR_ARG_instructions_that_have_been_handled(Function *function) {
for (Tac *ir = function->ir; ir; ir = ir->next) {
if (ir->operation == IR_ARG) {
FunctionParamLocations *pl = ir->src1->function_call_arg_locations;
for (int loc = 0; loc < pl->count; loc++) {
if (pl->locations[loc].int_register != -1 || pl->locations[loc].sse_register != -1) {
ir->operation = IR_NOP;
ir->dst = 0;
ir->src1 = 0;
ir->src2 = 0;
break;
}
}
}
}
}
// Process IR_ARG insructions. They are either loaded into a register, push onto the
// stack with an IR_ARG, or, in the case of a struct/union pushed onto the stack with
// generated code.
void add_function_call_arg_moves(Function *function) {
add_function_call_arg_moves_for_preg_class(function, PC_INT);
add_function_call_arg_moves_for_preg_class(function, PC_SSE);
remove_IR_ARG_instructions_that_have_been_handled(function);
// Add memory copies for struct and unions
for (Tac *ir = function->ir; ir; ir = ir->next) {
if (ir->operation == IR_ARG) {
if (ir->src2->type->type == TYPE_STRUCT_OR_UNION) {
FunctionParamLocations *pls = ir->src1->function_call_arg_locations;
if (pls->count != 1) panic("Unexpected struct/union to stack move with locations->count != 1");
add_function_call_arg_move_for_struct_or_union_on_stack(function, ir);
ir->operation = IR_NOP;
ir->dst = 0;
ir->src1 = 0;
ir->src2 = 0;
}
}
}
// Process any added memcpy calls due to struct and union copies
add_function_call_arg_moves_for_preg_class(function, PC_INT);
remove_IR_ARG_instructions_that_have_been_handled(function);
if (debug_function_arg_mapping) {
printf("After function call arg mapping\n");
print_ir(function, 0, 0);
}
}
// Set has_address_of to 1 if a value is a parameter in a register and it's used in a & instruction
static void check_param_value_has_used_in_an_address_of(int *has_address_of, Tac *tac, Value *v) {
if (!v) return;
if (tac->operation != IR_ADDRESS_OF) return;
if (v->stack_index < 2) return;
has_address_of[v->stack_index - 2] = 1;
return;
}
static void assign_register_to_value(Value *v, int vreg) {
v->stack_index = 0;
v->is_lvalue = 0;
v->vreg = vreg;
}
// Convert stack_index in value v to a parameter register
static void convert_register_param_stack_index_to_register(Function *function, int *register_param_vregs, Value *v) {
if (v && v->stack_index >= 2 && register_param_vregs[v->stack_index - 2] != -1)
assign_register_to_value(v, register_param_vregs[v->stack_index - 2]);
}
// Convert stack_index in value v to a parameter in the stack
static void convert_register_param_stack_index_to_stack(Function *function, int *register_param_stack_indexes, Value *v) {
if (v && v->stack_index >= 2 && register_param_stack_indexes[v->stack_index - 2]) {
v->stack_index = register_param_stack_indexes[v->stack_index - 2];
v->is_lvalue = 0;
}
}
// Convert a value that has a stack index >= 2, i.e. it's a pushed parameter into a vreg
static void convert_pushed_param_stack_index_to_register(Function *function, int *stack_param_vregs, Value *v) {
if (v && !v->function_param_original_stack_index && v->stack_index >= 2 && stack_param_vregs[v->stack_index - 2] != -1)
assign_register_to_value(v, stack_param_vregs[v->stack_index - 2]);
}
// Add an instruction to move a parameter in a register to another register
static Tac *make_param_move_to_register_tac(Function *function, Type *type, int function_param_index) {
Tac *tac = new_instruction(IR_MOVE);
tac->dst = new_value();
tac->dst->type = dup_type(type);
tac->dst->vreg = ++function->vreg_count;
tac->src1 = new_value();
tac->src1->type = dup_type(tac->dst->type);
int max = is_sse_floating_point_type(type) ? 8 : 6;
if (function_param_index < max)
tac->src1->live_range_preg = is_sse_floating_point_type(type) ? sse_arg_registers[function_param_index] : int_arg_registers[function_param_index];
return tac;
}
// Add an instruction to move a parameter in a register to a new stack entry
static Tac *make_param_move_to_stack_tac(Function *function, Type *type, int function_param_index) {
Tac *tac = new_instruction(IR_MOVE);
tac->dst = new_value();
tac->dst->type = dup_type(type);
tac->dst->stack_index = -(++function->stack_register_count);
tac->src1 = new_value();
tac->src1->type = dup_type(tac->dst->type);
int max = is_sse_floating_point_type(type) ? 8 : 6;
if (function_param_index < max)
tac->src1->live_range_preg = is_sse_floating_point_type(type) ? sse_arg_registers[function_param_index] : int_arg_registers[function_param_index];
return tac;
}
// Add instructions to move struct/union data from a param register to a struct on the stack
static int add_struct_or_union_param_move(Function *function, Tac *ir, Type *type, FunctionParamLocations *pl, RegisterSet *register_set) {
// Allocate space on the stack for the struct
Value *v = new_value();
v->type = dup_type(type);
v->is_lvalue = 1;
v->stack_index = -(++function->stack_register_count);
insert_instruction_from_operation(ir, IR_DECL_LOCAL_COMP_OBJ, 0, v, 0, 1);
for (int loc = 0; loc < pl->count; loc++) {
FunctionParamLocation *location = &(pl->locations[loc]);
if (location->int_register != -1)
make_int_struct_or_union_move_from_register_to_stack_instructions(function, ir, type, location, location->int_register, v->stack_index, register_set, 0);
else if (location->sse_register != -1)
make_sse_struct_or_union_move_from_register_to_stack_instructions(function, ir, type, location, location->sse_register, v->stack_index, register_set, 0);
else
panic("Got unexpected stack offset in add_struct_or_union_param_move");
}
return v->stack_index;
}
// Convert a stack_index if stack_index_map isn't -1
void remap_stack_index(int *stack_index_remap, Value *v) {
if (v && v->stack_index >= 2 && !v->has_been_renamed && stack_index_remap[v->stack_index] != -1) {
v->stack_index = stack_index_remap[v ->stack_index];
v->has_been_renamed = 1;
}
}
// if the function returns a struct/union in memory, then the caller puts a pointer to
// the target in rdi. Make a copy of rdi in return_value_pointer for use in the
// return value code. The function returns 1 if rdi has been used in this way.
static int setup_return_for_struct_or_union(Function *function) {
FunctionParamAllocation *fpa = function->type->function->return_value_fpa;
if (fpa_pl(fpa, 0).locations[0].stack_offset == -1) return 0;
function->return_value_pointer = new_value();
function->return_value_pointer->vreg = ++function->vreg_count;
function->return_value_pointer->type = make_pointer_to_void();
// Make value for rdi register
Value *src1 = new_value();
src1->type = make_pointer_to_void();
src1->live_range_preg = LIVE_RANGE_PREG_RDI_INDEX;
src1->type = make_pointer_to_void();
src1->vreg = ++function->vreg_count;
Value *dst = dup_value(function->return_value_pointer);
insert_instruction_from_operation(ir, IR_MOVE, dst, src1, 0, 1);
return 1;
}
// For functions with variadic arguments, move registers into the register save area.
// The register save area has been allocated on the stack by the parser with the
// value set in function->register_save_area.
static void add_function_vararg_param_moves(Function *function, FunctionParamAllocation *fpa) {
// Add moves for ints registers to register save area
for (int i = fpa->single_int_register_arg_count; i < 6; i++) {
Value *src = new_value();
src->vreg = ++function->vreg_count;
src->type = new_type(TYPE_LONG);
src->live_range_preg = int_arg_registers[i];
Value *dst = dup_value(function->register_save_area);
dst->type = new_type(TYPE_LONG);
dst->offset = i * 8;
ir = insert_instruction_after_from_operation(ir, IR_MOVE, dst, src, 0);
}
// Skip SSE register copying if al is zero with a jump to "done"
Value *ldone = new_label_dst();
Value *rax = new_value();
rax->vreg = ++function->vreg_count;
rax->type = new_type(TYPE_CHAR);
rax->live_range_preg = LIVE_RANGE_PREG_RAX_INDEX;
ir = insert_instruction_after_from_operation(ir, IR_JZ, 0, rax, ldone);
// add moves for SSE registers to register save area
for (int i = fpa->single_sse_register_arg_count; i < 8; i++) {
Value *src = new_value();
src->vreg = ++function->vreg_count;
src->type = new_type(TYPE_DOUBLE);
src->live_range_preg = sse_arg_registers[i];
Value *temp_int = new_value();
temp_int->type = new_type(TYPE_LONG);
temp_int->vreg = ++function->vreg_count;
ir = insert_instruction_after_from_operation(ir, IR_MOVE_PREG_CLASS, temp_int, src, 0);
Value *dst = dup_value(function->register_save_area);
dst->type = new_type(TYPE_LONG);
dst->offset = 48 + i * 16;
ir = insert_instruction_after_from_operation(ir, IR_MOVE, dst, temp_int, 0);
}
// Add done label
ir = insert_instruction_after_from_operation(ir, IR_NOP, 0, 0, 0);
ir->label = ldone->label;
}
// Add code for a va_start() call. The va_list struct, present in src1, has to be
// populated.
static void process_function_va_start(Function *function, Tac *ir) {
Value *va_list = ir->src1;
ir->operation = IR_NOP;
ir->src1 = 0;
// Set va_list.fp_offset, the offset of the first vararg integer register
Value *fp_offset_value = new_value();
fp_offset_value->type = new_type(TYPE_INT);
fp_offset_value->type->is_unsigned = 1;
fp_offset_value->is_constant = 1;
fp_offset_value->int_value = function->fpa->single_int_register_arg_count * 8;
Value *dst = dup_value(va_list);
dst->type = new_type(TYPE_INT);
dst->type->is_unsigned = 1;
ir = insert_instruction_after_from_operation(ir, IR_MOVE, dst, fp_offset_value, 0);
// Set va_list.gp_offset, the offset of the first vararg SSE register
Value *gp_offset_value = dup_value(fp_offset_value);
gp_offset_value->int_value = 48 + function->fpa->single_sse_register_arg_count * 16;
dst = dup_value(dst);
dst->offset = 4;
ir = insert_instruction_after_from_operation(ir, IR_MOVE, dst, gp_offset_value, 0);
// Set va_list.overflow_arg_area, the address of the first vararg pushed on the stack
Value *tmp_dst = dup_value(dst);
tmp_dst->type = make_pointer_to_void();
tmp_dst->vreg = ++function->vreg_count;
Value *overflow = dup_value(dst);