-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathmain.rs
More file actions
348 lines (284 loc) · 12.6 KB
/
Copy pathmain.rs
File metadata and controls
348 lines (284 loc) · 12.6 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
//! This example shows some common ways to represent structs in memory.
//!
//! We will ensure all our structs are aligned. This substantially improves performance since it
//! lowers the amount of loads a CPU has to do, and is a hard requirement for a lot of ABI's.
//!
//! We will not be covering nested structs or circular structs here. But if you are, keep in
//! mind that you will need to check for recursive data types, and either fail to compile or
//! automatically box the fields to make the structs finitely sized.
//!
//! The main function will construct two structs: One small and one large.
//! These structs will then be given as parameter to a function that returns a new struct
//! where each field has been incremented.
//!
//! Main will return an exit code representing the sum of all fields of the small struct.
//!
//! To link against system libraries and produce a binary on Linux or MacOS, you can use `gcc` or `clang`
//!
//! `$ cargo run --example struct-layouts -- -o struct-layouts.o`
//! `$ clang struct-layouts.o -o struct-layouts`
//! `$ ./struct-layouts; echo $?`
use cranelift::codegen::ir::ArgumentPurpose;
use cranelift::prelude::isa::CallConv;
use cranelift::prelude::{InstBuilder, types};
use cranelift::{codegen::ir::StackSlot, prelude as cl};
use cranelift_module::{FuncId, Linkage, Module};
use cranelift_object::ObjectModule;
use cranelift_examples::{declare_main, function_builder_from_declaration, skip_boilerplate};
fn main() {
skip_boilerplate(b"struct-layouts", |ctx, fctx, module, _args| {
let size_t = module.isa().pointer_type();
let small_struct_fields = &[types::I32, types::I32];
let large_struct_fields = &[types::I32, types::I8, types::I32, types::I16];
let main_func_id = declare_main(module);
let inc_large_funcid = declare_increment_large(module, large_struct_fields);
let inc_small_funcid = declare_increment_small(module, small_struct_fields);
// fn main() {
// let large_struct = LargeStruct {...};
// let small_struct = SmallStruct {...};
//
// let _ = inc_large_struct(large_struct);
// let incremented_small_struct = inc_small_struct(small_struct);
//
// let small_sum = incremented_small_struct.a + incremented_small_struct.b;
//
// return small_sum;
// }
{
let (mut fbuilder, _) =
function_builder_from_declaration(module, &mut ctx.func, fctx, main_func_id);
// let large_struct = LargeStruct {
// a: 1, // i32
// b: 2, // i8
// c: 3, // i32
// d: 4, // i16
// };
let large_struct: cl::Value = {
// For larger structs, we reserve space on the stack and pass it around as a pointer.
//
// Assigning a field will be loading from / storing to that pointer.
let struct_stack_slot: StackSlot =
stack_alloc(&mut fbuilder, size_of_struct(large_struct_fields));
// Here we use the `stack_` prefixed instructions to act upon the `cl::StackSlot` directly.
// In a real compiler it might be easier to first get the pointer as a `cl::Value` with
// `FunctionBuilder::ins().stack_addr(...)` and then using `FunctionBuilder::ins().store(...)`
for (i, n) in [1, 2, 3, 4].into_iter().enumerate() {
let offset = offset_of_field(i, large_struct_fields);
let value = fbuilder.ins().iconst(large_struct_fields[i], n);
fbuilder.ins().stack_store(value, struct_stack_slot, offset);
}
// Since our structs are aligned, padding was added.
//
// let large_struct = LargeStruct {
// a: 1, // i32,
// b: 2, // i8,
// _pad0: // i24
// c: 3 // i32
// d: 4 // i16
// _pad1: // i16
// };
// Convert the stack slot to a `cl::Value` pointer
fbuilder.ins().stack_addr(size_t, struct_stack_slot, 0)
};
// let small_struct = SmallStruct {
// a: 1, // i32
// b: 2, // i32
// };
let small_struct: Vec<cl::Value> = {
// For smaller structs, it's often unnecessary to introduce indirection.
// Just passing around the fields as values can allow the struct to remain entirely in
// registers.
[1, 2]
.into_iter()
.enumerate()
.map(|(i, n)| fbuilder.ins().iconst(small_struct_fields[i], n))
.collect()
};
// let _ = inc_large_struct(large_struct);
let _incremented_large_struct: cl::Value = {
let fref = module.declare_func_in_func(inc_large_funcid, &mut fbuilder.func);
let out_ptr = {
let out_stack_slot =
stack_alloc(&mut fbuilder, size_of_struct(large_struct_fields));
fbuilder.ins().stack_addr(size_t, out_stack_slot, 0)
};
fbuilder.ins().call(fref, &[large_struct, out_ptr]);
out_ptr
};
// let incremented_small_struct = inc_small_struct(small_struct);
let incremented_small_struct: Vec<cl::Value> = {
let fref = module.declare_func_in_func(inc_small_funcid, &mut fbuilder.func);
let call = fbuilder.ins().call(fref, &small_struct);
fbuilder.inst_results(call).to_vec()
};
// Calculate the sum of all fields in the small struct
//
// let small_sum = incremented_small_struct.a + incremented_small_struct.b;
let small_sum = {
let init = fbuilder.ins().iconst(types::I32, 0);
incremented_small_struct
.into_iter()
.fold(init, |sum, v| fbuilder.ins().iadd(sum, v))
};
// Return the sum of all fields in the small struct
//
// return small_sum;
fbuilder.ins().return_(&[small_sum]);
fbuilder.finalize();
println!("fn main:\n{}", &ctx.func);
module.define_function(main_func_id, ctx).unwrap();
}
// fn inc_large_struct(large: LargeStruct) -> LargeStruct {
// return LargeStruct {
// a: large.a + 1,
// b: large.b + 1,
// c: large.c + 1,
// d: large.d + 1,
// };
// }
//
// // -- Although the way we represent it in Cranelift looks like -- //
//
// fn inc_large_struct(large: &LargeStruct, out: &LargeStruct) {
// (*out+0) = *(large+0) + 1;
// (*out+4) = *(large+4) + 1;
// (*out+8) = *(large+8) + 1;
// (*out+12) = *(large+12) + 1;
// }
{
let (mut fbuilder, entry) =
function_builder_from_declaration(module, &mut ctx.func, fctx, inc_large_funcid);
// By using `trusted`, we're asserting to Cranelift that the field is aligned and the
// pointer is valid.
let flags = cl::MemFlags::trusted();
let param = fbuilder.block_params(entry)[0];
let out_pointer = fbuilder.block_params(entry)[1];
for (i, &ty) in large_struct_fields.iter().enumerate() {
let offset = offset_of_field(i, large_struct_fields);
// Access the field
let v = fbuilder.ins().load(ty, flags, param, offset);
// Increment it
let v = fbuilder.ins().iadd_imm(v, 1);
// Write it to the second struct pointer
fbuilder.ins().store(flags, v, out_pointer, offset);
}
// We don't return any values as we're using an out pointer instead
fbuilder.ins().return_(&[]);
fbuilder.finalize();
println!("fn inc_large_struct:\n{}", &ctx.func);
module.define_function(inc_large_funcid, ctx).unwrap();
}
// fn inc_small_struct(small: SmallStruct) -> SmallStruct {
// return SmallStruct {
// a: small.a + 1,
// b: small.b + 1,
// };
// }
{
let (mut fbuilder, entry) =
function_builder_from_declaration(module, &mut ctx.func, fctx, inc_small_funcid);
let a = {
let small_a = fbuilder.block_params(entry)[0];
fbuilder.ins().iadd_imm(small_a, 1)
};
let b = {
let small_b = fbuilder.block_params(entry)[1];
fbuilder.ins().iadd_imm(small_b, 1)
};
fbuilder.ins().return_(&[a, b]);
fbuilder.finalize();
println!("fn inc_small_struct:\n{}", &ctx.func);
module.define_function(inc_small_funcid, ctx).unwrap();
}
});
}
fn declare_increment_large(module: &mut ObjectModule, large_struct_fields: &[cl::Type]) -> FuncId {
let size_t = module.isa().pointer_type();
let struct_size = size_of_struct(large_struct_fields);
let sig = cl::Signature {
params: vec![
// Setting this argument purpose will generate memcpy'ing of the struct before
// crossing the function boundary, so that the instance of the struct available in
// the called function is unique.
cl::AbiParam::special(size_t, ArgumentPurpose::StructArgument(struct_size)),
// Setting this argument purpose will ensure that the pointer to write the
// returned result into will be put in the appropriate register according to
// the architecture's standards.
cl::AbiParam::special(size_t, ArgumentPurpose::StructReturn),
],
// We're not directly returning values, but instead use an out parameter.
returns: vec![],
call_conv: CallConv::Fast,
};
module
.declare_function("inc_large_struct", Linkage::Local, &sig)
.unwrap()
}
fn declare_increment_small(module: &mut ObjectModule, small_struct_fields: &[cl::Type]) -> FuncId {
let sig = cl::Signature {
// Since it's only two scalar values, it's more efficient to pass the fields
// individually in registers.
params: small_struct_fields
.iter()
.copied()
.map(cl::AbiParam::new)
.collect(),
// Since it's only two scalar values, it'll fit in the return registers
returns: small_struct_fields
.iter()
.copied()
.map(cl::AbiParam::new)
.collect(),
call_conv: CallConv::Fast,
};
module
.declare_function("inc_small_struct", Linkage::Local, &sig)
.unwrap()
}
fn stack_alloc(fbuilder: &mut cl::FunctionBuilder<'_>, size: u32) -> StackSlot {
fbuilder.create_sized_stack_slot(cl::StackSlotData::new(
cl::StackSlotKind::ExplicitSlot,
size,
0,
))
}
fn size_of_struct(fields: &[cl::Type]) -> u32 {
let mut size = 0;
// Go through all fields and increment size by each fields size and padding
for &field in fields {
size += field.bytes();
// Add padding to ensure the field is aligned
let align = alignment_of_scalar_type(field);
let padding = (align - size % align) % align;
size += padding;
}
// Add padding to the end of the struct to make the struct itself aligned
let self_align = alignment_of_struct(fields);
let end_padding = (self_align - size % self_align) % self_align;
size += end_padding;
size
}
fn alignment_of_scalar_type(of: cl::Type) -> u32 {
of.bytes()
}
fn alignment_of_struct(fields: &[cl::Type]) -> u32 {
let mut alignment = 0;
// Since we don't have nested structs, the alignment of a struct is simply its largest field.
for &field in fields {
let field_alignment = alignment_of_scalar_type(field);
alignment = alignment.max(field_alignment);
}
alignment
}
fn offset_of_field(field: usize, fields: &[cl::Type]) -> i32 {
let mut offset = 0;
// Go through all fields prior to this one and increment offset by their size and padding
for &prior in fields.iter().take(field) {
offset += prior.bytes() as i32;
// Add padding to ensure the field is aligned
let align = alignment_of_scalar_type(prior) as i32;
let padding = (align - offset % align) % align;
offset += padding;
}
offset
}