I have this adding of const data section
pub fn add_const_data(&mut self, name: &str, contents: Vec<u8>) -> () {
self.data.define(contents.into_boxed_slice());
let id = self
.obj_mod
.declare_data(name, Linkage::Export, false, false)
.unwrap();
self.obj_mod.define_data(id, &self.data).unwrap();
}
My language code looks like the following.
const z = 1
pub const main = fn() usize {
const m = 7
const x = 4
return x + m + z
}
When everything was defined within the function it was easy for me to just reference it like so.
pub fn handle_sym(&self, op: &SymbolAccess) -> ResultFir<Variable> {
Ok(Variable::from_u32(
*self.scope.table.get(&op.ident).unwrap(),
))
}
But there isn't a symbol table for this since it just has an address.
You can use module.declare_data_in_func
passing in both the id returned from declare_data
and a mutable reference to the Function
you are creating. This method then returns a GlobalValue
you can pass to the global_value
instruction to get a Value
representing the address of the data object.
mind.blown()
Do you have an example?
Do i need to tell it to dereference that Value
that is returned?
If you want to get the value stored in the global variable you did need to load from the address returned by global_value
and if you want to change the value you need to use store
.
I think I see what you mean. I need to do some refactoring then. To pass the object_module to my IR builder.
@bjorn3 Since the IR is built to be at the function level, do you recommend an object file with all the data, and then for every function a separate object file? This should also help with caching in between builds if the function doesn't change but some other does.
Having a separate object file for each function would likely make the linker slower. The way rustc handles this is by splitting all functions between a set of codegen units each corresponding to a single object file. The amount of codegen units is fixed to 16 or 256 depending on if incr comp is enabled or not. 256 when it is enabled to maximize reuse of object files when none of the functions inside it change, and 16 for better optimizations while still enabling each codegen unit to be compiled in parallel when no reuse would happen anyway due to incr comp being disabled. (You can override this using -Ccodegen-units
)
I finally had some serious time to do the refactors. Usually holidays when family is around to help with the kids. I'm just not sure of the results. this is such an unknown space for me.
My pl
const m = 5
pub const main = fn() usize {
return 5 - m
}
objdump -D
main.o: file format elf64-x86-64
Disassembly of section .rodata:
0000000000000000 <m>:
0: 05 00 00 00 00 add $0x0,%eax
5: 00 00 add %al,(%rax)
...
Disassembly of section .text:
0000000000000000 <main>:
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: b8 05 00 00 00 mov $0x5,%eax
9: 48 8d 0d 00 00 00 00 lea 0x0(%rip),%rcx # 10 <main+0x10>
10: 48 2b 01 sub (%rcx),%rax
13: 48 89 ec mov %rbp,%rsp
16: 5d pop %rbp
17: c3 ret
const initialization
self.data is DataDescription
pub fn recurse(&mut self, expr: &TypeTree) -> () {
match expr {
TypeTree::I64(x) => self.data.define(Box::from(x.clone().to_ne_bytes())),
TypeTree::U64(x) => self.data.define(Box::from(x.clone().to_ne_bytes())),
_ => panic!("unexpected type tree in oir"),
}
}
pub fn const_init(&mut self, init: &TopInitialization, dt: &mut DataTable) -> () {
let slice = &init.left.into_symbol_init().ident;
self.recurse(init.right.as_ref());
let id = self
.obj_mod
.declare_data(slice, Linkage::Export, false, false)
.unwrap();
self.obj_mod.define_data(id, &self.data).unwrap();
dt.table.insert(slice.to_string(), id);
}
symbol referencing
pub fn handle_sym_access(
&mut self,
op: &SymbolAccess,
dtbl: &DataTable,
scopes: &Vec<ScopeTable>,
type_tables: &Vec<TypeTable>,
oir: &mut Oir,
builder: &mut FunctionBuilder,
) -> ResultFir<Variable> {
let sym = self.sym.table.get(&op.ident);
if let Some(s) = sym {
return Ok(Variable::from_u32(*s));
}
let id = dtbl.table.get(&op.ident).unwrap();
let gv = oir.obj_mod.declare_data_in_func(*id, builder.func);
let val = builder.ins().global_value(I64, gv);
let result = self.add_var();
builder.declare_var(result, I64);
let loaded = builder
.ins()
.load(I64, MemFlags::new(), val, Offset32::new(0));
builder.def_var(result, loaded);
Ok(result)
}
My only question is does this look like the right track? The program executes correctly.
Looks correct to me.
As far as knowing to easily inline the m variable of 5 into the program. That is an optimization, is there a pass manager maintained by someone or drop-in optimizations library from anyone?
Cranelift currently doesn't have any interprocedural optimizations. Neither inlining of functions, nor inlining of constant globals. In general Cranelift doesn't have much optimizations that would benefit anything other than compiling already optimized wasm to machine code.
I was merely asking if there was anyone doing this somewhere even outside of bytecode alliance/ wasmtime/ cranelift?
I'm not aware of any external optimizer for Cranelift either.
Last updated: Jan 24 2025 at 00:11 UTC