Hello there, I have a slightly trivial question.
How does one represent a structure in Cranelift IR? I know that structures are more-or-less a virtual concept and can be introduced "virtually", by representing parts of them as different objects in memory. However, what if one wants to inter-operate with C library APIs that require passing a structure? As far as I understand, some strict memory layout standard should be followed with correct paddings and organisation.
Is there any easy way of doing this? Thanks!
You'll need to calculate the size and alignment of the struct, to know how much memory to allocate, and the offsets of the fields, to know how to access the fields. Cranelift doesn't currently provide an automatic way to do this, though it is something we could consider. In many simple cases, it's straightforward -- you just append one field at a time, inserting padding at each step as needed to ensure the alignment of the field.
Depending on your needs, it may be possible to get the Rust compiler to do the work for you. If you can declare the structs you need as extern "C" structs in Rust, you can then use mem::size_of, mem::align_of, and the memoffset crate to calculate field offsets.
I think that supporting both structures and arrays is essential to avoid projects making their own implementations all the time -- this should help avoid a lot of confusion, increase accessibility and introduce only one single point of failure for bugs to appear.
Also, introducing them will mean that each improvement will benefit all the other projects (I think we all know the benefits of reusing code).
As for right now, I totally understand that WebAssembly compilers (which is the main use of Cranelift at the moment) do not require it to have such features, but, again, introducing them will help other projects, that have to think about such things.
@skyne98 I totally agree with you. Having that functionality built-in would increase the adoption of Cranelift substantially. From looking at various toy compilers, most of them forgo structures because of the added complexity (and there aren't many examples for someone new to compilers, things like LLVM's Kaleidoscope tutorial completely ignore handling data structures).
Though I also buy that this could be in a separate crate. What I'd really love is an equivalent to https://github.com/chriswailes/RLTK but in Rust
RLTK looks like a cool package.
There are a variety of parser crates for Rust, such as lalrpop and others. The simplejit-demo is using peg. It'd be cool to put some of these pieces together to start building something like RLTK. I unfortunately don't have time to do this right now, but I'd be happy to help anyone else who wanted to!
@sunfishcode, I have found some good articles regarding basic struct representations in memory. At the moment I am experimenting with implementing them in a toy language compiler.
If it does work, I think it might be a good idea to try to integrate it into Cranelift with your overseeing and mentorship.
@blanham, Kaleidoscope tutorials might have such a stance at teaching structures only because the LLVM itself handles them quite well. In the end, it is one of the built-in supported datatypes among arrays and vectors.
I think that structures remain one of the most "low-level" concepts of Cranelift, at the moment, and they are essential to the creation of languages and compilers.
@skyne98 That sounds great! I'd be happy to help mentor such a project.
Hey, related to this issue: I'm fine with implementing a struct abstraction in my frontend, but how can I make sure that create_stack_slot will have a particular alignment in the stack frame? It seems like right now, alignment is inferred from size? So, there's currently no option for frontends to specify alignment, right?
/// Get the alignment in bytes of this stack slot given the stack pointer alignment.
pub fn alignment(&self, max_align: StackSize) -> StackSize {
debug_assert!(max_align.is_power_of_two());
// We want to find the largest power of two that divides both `self.size` and `max_align`.
// That is the same as isolating the rightmost bit in `x`.
let x = self.size | max_align;
// C.f. Hacker's delight.
x & x.wrapping_neg()
}
So, for example, if I have a struct with a i32x4 and an i64:
struct Data {
x: i32x4,
y: i64,
}
size(Data) = 24, and align(Data) = 16.
But, if I do create_stack_slot(explicit, 24), the alignment of the slot will be _8?_ Assuming I'm on like x86, where the stack pointer will be aligned to 16? (Referencing the following test)
#[test]
fn alignment() {
let slot = StackSlotData::new(StackSlotKind::SpillSlot, 8);
assert_eq!(slot.alignment(4), 4);
assert_eq!(slot.alignment(8), 8);
assert_eq!(slot.alignment(16), 8);
let slot2 = StackSlotData::new(StackSlotKind::ExplicitSlot, 24);
assert_eq!(slot2.alignment(4), 4);
assert_eq!(slot2.alignment(8), 8);
assert_eq!(slot2.alignment(16), 8);
assert_eq!(slot2.alignment(32), 8);
}
So, I need to add 8 bytes of padding to the end to make it create_stack_slot(explicit, 32), so that I get an alignment of 16?
On that note, what is the motivation for defining the fn alignment that way? I assume it must be related to how Cranelift is used with wasm?
There isn't a great reason for fn alignment to be defined that way. It would be better to provide the ability for users to specify an alignment value for each stack slot.
One tricky detail is that Cranelift doesn't yet support dynamically realigning the stack in case someone might use such a mechanism to request an alignment value greater than the platform ABI provides for the stack pointer, however it seems like Cranelift could just fail to compile just cases for now.
Most helpful comment
There isn't a great reason for
fn alignmentto be defined that way. It would be better to provide the ability for users to specify an alignment value for each stack slot.One tricky detail is that Cranelift doesn't yet support dynamically realigning the stack in case someone might use such a mechanism to request an alignment value greater than the platform ABI provides for the stack pointer, however it seems like Cranelift could just fail to compile just cases for now.