summaryrefslogtreecommitdiffhomepage
path: root/ir/be/riscv/riscv_abi.c
blob: 8e7b239b8c16082ac11e5d9b05aaf2d74655a97e (plain)
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
/**
 * @file
 * @brief       Implements function parameter lowering for the RISC-V ILP32 ABI
 * @author      Johannes Bucher
 */
#include "riscv_abi.h"

#include "riscv_bearch_t.h"
#include "irmode.h"
#include "type_t.h"

static ir_mode *fold_classes(ir_mode *c1, ir_mode *c2)
{
	if (c1 == c2) {
		return c1;
	} else if (c1 == mode_BAD) {
		return c2;
	} else if (c2 == mode_BAD) {
		return c1;
	} else {
		panic("TODO");
	}
}

static ir_mode *classify_slice_for_ilp32(ir_type const *const tp, unsigned min, unsigned max);

static ir_mode *classify_compound_by_members(ir_type const *const tp, unsigned min, unsigned max)
{
	unsigned n = get_compound_n_members(tp);
	ir_mode *current_class = mode_BAD;
	for (unsigned i = 0; i < n; i++) {
		ir_entity *member = get_compound_member(tp, i);
		ir_type *member_type = get_entity_type(member);
		unsigned member_size = get_type_size(member_type);

		unsigned member_begin = get_entity_offset(member);
		unsigned member_end = member_begin + member_size;

		// Is the member (at least partially) between min and max?
		if (min < member_end && max > member_begin) {
			if (get_entity_aligned(member) == align_non_aligned) {
				return mode_M;
			}
			unsigned min_in_member = min <= member_begin ? 0 : min - member_begin;
			unsigned max_in_member = member_end < max ? member_size : max - member_begin;
			ir_mode *member_class = classify_slice_for_ilp32(member_type, min_in_member, max_in_member);
			current_class = fold_classes(current_class, member_class);
		}
	}
	return current_class;
}

static ir_mode *classify_slice_for_ilp32(ir_type const *const tp, unsigned min, unsigned max)
{
	switch(get_type_opcode(tp)) {
		case tpo_class:
		case tpo_struct:
		case tpo_union:
			return classify_compound_by_members(tp, min, max);

		case tpo_array: {
			ir_type *elem_type = get_array_element_type(tp);
			if (min < get_type_size(tp)) {
				// We are in the array
				size_t elem_size = get_type_size(elem_type);
				if (min >= elem_size) {
					// ... but past the first element. Shift the slice range down.
					unsigned new_min = min % elem_size;
					unsigned new_max = new_min + (max - min);
					return classify_slice_for_ilp32(elem_type, new_min, new_max);
				} else {
					return classify_slice_for_ilp32(elem_type, min, max);
				}
			} else {
				return mode_BAD;
			}
		}
		case tpo_primitive: {
			ir_mode *mode = get_type_mode(tp);
			if (min >= get_type_size(tp)) {
				return mode_BAD;
			} else if (mode_is_float(mode)) {
				panic("RISC-V ILP32 ABI has no hardware floating point support");
			} else {
				return mode_Iu;
			}
		}
		case tpo_pointer:
			if (min >= get_type_size(tp)) {
				return mode_BAD;
			} else {
				return mode_Iu;
			}

		case tpo_code:
		case tpo_method:
		case tpo_segment:
		case tpo_uninitialized:
		case tpo_unknown:
			break;
	}
	panic("invalid type");
}

static aggregate_spec_t classify_for_ilp32(ir_type const *const type) {
	if (get_type_size(type) > 2 * RISCV_REGISTER_SIZE) {
		return (aggregate_spec_t) {
				.length = 1,
				.modes = { mode_P },
		};
	}

	aggregate_spec_t result = {
		.length = 0,
		.modes = { },
	};

	for (unsigned i = 0; i < 2; i++) {
		ir_mode *c = classify_slice_for_ilp32(type, RISCV_REGISTER_SIZE * i, RISCV_REGISTER_SIZE * (i + 1));
		result.modes[i] = c;
		if (c != mode_BAD) {
			result.length++;
		}
	}

	/* if type has 8 byte alignment and result is two (4-byte) mode_Iu slices we have to convert this into
	 * a single 8 byte mode_Lu slice to ensure correct alignment when passing the argument.
	 * (Variadic arguments with 2*XLEN-bit alignment and size at most 2*XLEN bits are passed in an aligned register pair) */
	if (result.length == 2 && result.modes[0] == mode_Iu && result.modes[1] == mode_Iu &&
	    (get_type_alignment(type) == 2 * RISCV_REGISTER_SIZE)) {
		result.length = 1;
		result.modes[0] = mode_Lu;
	}

	return result;
}

aggregate_spec_t riscv_lower_parameter(void *env, ir_type const *type) {
	(void)env;

	if (is_aggregate_type(type)) {
		return classify_for_ilp32(type);
	} else {
		return (aggregate_spec_t) {
				.length = 1,
				.modes = { get_type_mode(type) },
		};
	}
}

aggregate_spec_t riscv_lower_result(void *env, ir_type const *type) {
	return riscv_lower_parameter(env, type);
}