-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathmain.rs
More file actions
151 lines (124 loc) · 5.63 KB
/
main.rs
File metadata and controls
151 lines (124 loc) · 5.63 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
//! This example shows how to code-generate a function and emit it into an object file.
//!
//! How an object file is later turned into a runnable executable will depend on the operating
//! system you're on.
//!
//! You could produce a raw executable that isn't linked to any system libraries using `ld object.o`
//! and then declare an entrypoint function with the symbol the operating system expects ("_start" on Linux).
//!
//! In our examples, we'll be linking to libc and declare "main" which is invoked by libc.
//! When linking against libc, the main function can return an exit code.
//!
//! To link against system libraries and produce a binary on Linux or MacOS, you can use `gcc` or `clang`
//!
//! `$ cargo run --example output-a-binary`
//! `$ clang output-a-binary.o -o output-a-binary`
//! `$ ./output-a-binary; echo $?`
use cranelift::prelude::*;
use cranelift_module::{Linkage, Module};
use cranelift_object::{ObjectBuilder, ObjectModule};
use std::{fs::File, io::Write};
// The platform we're targeting.
//
// These constants may need to be changed if you're on MacOS/Windows.
const TARGET_TRIPLE: &str = "x86_64-unknown-linux";
const ENTRYPOINT_FUNCTION_SYMBOL: &str = "main";
fn main_signature(isa: &dyn isa::TargetIsa) -> Signature {
// The `CallConv` defines how primitives in parameters and return values are handled.
// Mainly which registers are used and when stack spills are used.
//
// In general, it's best to use `CallConv::Fast`.
//
// However, since the function we define is invoked from our targeted OS, we need to use
// the calling convention the OS expects.
let call_conv = isa.default_call_conv();
Signature {
call_conv,
params: vec![],
// Since we're linking to libc, we can return the exit code from main.
returns: vec![AbiParam::new(types::I32)],
}
}
fn main() {
// The ISA contains information about our intended target and acts as the settings for cranelift.
let isa = {
let mut builder = settings::builder();
// disable optimizations so disassembly will more directly correlated to our Cranelift usage
builder.set("opt_level", "none").unwrap();
builder.enable("is_pic").unwrap();
let flags = settings::Flags::new(builder);
isa::lookup_by_name(TARGET_TRIPLE)
.unwrap()
.finish(flags)
.unwrap()
};
// Cranelift has the concept of a Module which ties declarations together.
//
// Module is actually a trait, and which implementation of this trait you use will depend on
// what sort of environment you're generating code into.
//
// Our objective is to generate an ahead-of-time compiled binary.
// So we'll use the `cranelift-object` crate, which exposes `ObjectModule` as a Module implementation.
//
// Object refers to object files (`.o` on unix-like systems and `.obj` on Windows).
// These files contain unlinked machine code, and we can then use a 'linker' to merge them into our final executable.
let mut module = {
let translation_unit_name = b"output_a_binary";
let libcall_names = cranelift_module::default_libcall_names();
let builder =
ObjectBuilder::new(isa.clone(), translation_unit_name, libcall_names).unwrap();
ObjectModule::new(builder)
};
// First we declare our functions by adding which functions exist in the module and granting them their signatures.
//
// In this example, there's only one function, the program's entrypoint.
let main_declaration_func_id = {
let sig = main_signature(&*isa);
// Add this function to our Module.
module
.declare_function(ENTRYPOINT_FUNCTION_SYMBOL, Linkage::Export, &sig)
.unwrap()
};
// Define the contents of our functions
{
// These contain the context needed for generating code for a function.
//
// It's a lot more efficient to construct them once, and then re-use them for all functions.
let mut ctx = codegen::Context::new();
let mut fctx = FunctionBuilderContext::new();
let mut builder = FunctionBuilder::new(&mut ctx.func, &mut fctx);
builder.func.signature = main_signature(&*isa);
// Create the functions entry block.
let block0 = builder.create_block();
builder.switch_to_block(block0);
// When we know that there are no more blocks to be written which may jump to this block, we want to seal
// it. This improves the quality of code generation.
builder.seal_block(block0);
let one = builder.ins().iconst(types::I32, 1);
let two = builder.ins().iadd(one, one);
// Use the result of the addition as an exit code
builder.ins().return_(&[two]);
if let Err(err) = codegen::verify_function(&builder.func, isa.as_ref()) {
panic!("verifier error: {err}");
}
builder.finalize();
println!("fn {ENTRYPOINT_FUNCTION_SYMBOL}:\n{}", &ctx.func);
module
.define_function(main_declaration_func_id, &mut ctx)
.unwrap();
ctx.clear();
}
// Finalize the module to generate our `Product`.
//
// If we have additional information such as unwind information or DWARF debug information,
// they can be added to `Product`. For this example, we'll skip such optional additions.
let product = module.finish();
// Generate the object file.
{
let bytes = product.emit().unwrap();
let fname = "output-a-binary.o";
let mut f = File::create(fname).unwrap();
f.write_all(&bytes).unwrap();
println!(" wrote output to {fname}");
}
}