Skip to content

Commit d706b10

Browse files
committed
feat: Generate calls via the GOT
This change involves moving the generation of the GOT from variable_generator.rs to codegen.rs, in order to also cover not only global variables but also functions and 'programs' too. Once these have been given an associated index in the GOT we can use that to replace normal direct function calls with indirect calls to a function pointer stored in the GOT. We don't do this for calls with external linkage since these won't be subject to online change.
1 parent f4e0148 commit d706b10

File tree

3 files changed

+150
-89
lines changed

3 files changed

+150
-89
lines changed

src/codegen.rs

Lines changed: 103 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Copyright (c) 2020 Ghaith Hachem and Mathias Rieder
22
use std::{
33
cell::RefCell,
4+
collections::HashMap,
5+
fs::{read_to_string, write},
46
ops::Deref,
57
path::{Path, PathBuf},
68
};
@@ -34,6 +36,7 @@ use inkwell::{
3436
module::Module,
3537
passes::PassBuilderOptions,
3638
targets::{CodeModel, FileType, InitializationConfig, RelocMode},
39+
types::BasicTypeEnum,
3740
};
3841
use plc_ast::ast::{CompilationUnit, LinkageType};
3942
use plc_diagnostics::diagnostics::Diagnostic;
@@ -84,6 +87,40 @@ pub struct GeneratedModule<'ink> {
8487
type MainFunction<T, U> = unsafe extern "C" fn(*mut T) -> U;
8588
type MainEmptyFunction<U> = unsafe extern "C" fn() -> U;
8689

90+
pub fn read_got_layout(location: &str, format: ConfigFormat) -> Result<HashMap<String, u64>, Diagnostic> {
91+
if !Path::new(location).is_file() {
92+
// Assume if the file doesn't exist that there is no existing GOT layout yet. write_got_layout will handle
93+
// creating our file when we want to.
94+
return Ok(HashMap::new());
95+
}
96+
97+
let s =
98+
read_to_string(location).map_err(|_| Diagnostic::new("GOT layout could not be read from file"))?;
99+
match format {
100+
ConfigFormat::JSON => serde_json::from_str(&s)
101+
.map_err(|_| Diagnostic::new("Could not deserialize GOT layout from JSON")),
102+
ConfigFormat::TOML => {
103+
toml::de::from_str(&s).map_err(|_| Diagnostic::new("Could not deserialize GOT layout from TOML"))
104+
}
105+
}
106+
}
107+
108+
pub fn write_got_layout(
109+
got_entries: HashMap<String, u64>,
110+
location: &str,
111+
format: ConfigFormat,
112+
) -> Result<(), Diagnostic> {
113+
let s = match format {
114+
ConfigFormat::JSON => serde_json::to_string(&got_entries)
115+
.map_err(|_| Diagnostic::new("Could not serialize GOT layout to JSON"))?,
116+
ConfigFormat::TOML => toml::ser::to_string(&got_entries)
117+
.map_err(|_| Diagnostic::new("Could not serialize GOT layout to TOML"))?,
118+
};
119+
120+
write(location, s).map_err(|_| Diagnostic::new("GOT layout could not be written to file"))?;
121+
Ok(())
122+
}
123+
87124
impl<'ink> CodeGen<'ink> {
88125
/// constructs a new code-generator that generates CompilationUnits into a module with the given module_name
89126
pub fn new(
@@ -127,14 +164,79 @@ impl<'ink> CodeGen<'ink> {
127164
annotations,
128165
&index,
129166
&mut self.debug,
130-
self.got_layout_file.clone(),
131167
);
132168

133169
//Generate global variables
134170
let llvm_gv_index =
135171
variable_generator.generate_global_variables(dependencies, &self.module_location)?;
136172
index.merge(llvm_gv_index);
137173

174+
// Build our GOT layout here. We need to find all the names for globals, programs, and
175+
// functions and assign them indices in the GOT, taking into account prior indices.
176+
let program_globals = global_index
177+
.get_program_instances()
178+
.into_iter()
179+
.fold(Vec::new(), |mut acc, p| {
180+
acc.push(p.get_name());
181+
acc.push(p.get_qualified_name());
182+
acc
183+
});
184+
let functions = global_index.get_pous().values()
185+
.filter_map(|p| match p {
186+
PouIndexEntry::Function { name, linkage: LinkageType::Internal, is_generated: false, .. }
187+
| PouIndexEntry::Function { name, linkage: LinkageType::Internal, .. } => Some(name.as_ref()),
188+
_ => None,
189+
});
190+
let all_names = global_index
191+
.get_globals()
192+
.values()
193+
.map(|g| g.get_qualified_name())
194+
.chain(program_globals)
195+
.chain(functions)
196+
.map(|n| n.to_lowercase());
197+
198+
if let Some((location, format)) = &self.got_layout_file {
199+
let got_entries = read_got_layout(location.as_str(), *format)?;
200+
let mut new_symbols = Vec::new();
201+
let mut new_got_entries = HashMap::new();
202+
let mut new_got = HashMap::new();
203+
204+
for name in all_names {
205+
if let Some(idx) = got_entries.get(&name.to_string()) {
206+
new_got_entries.insert(name.to_string(), *idx);
207+
index.associate_got_index(&name, *idx)?;
208+
new_got.insert(*idx, name.to_string());
209+
} else {
210+
new_symbols.push(name.to_string());
211+
}
212+
}
213+
214+
// Put any names that weren't there last time in any free space in the GOT.
215+
let mut idx: u64 = 0;
216+
for name in &new_symbols {
217+
while new_got.contains_key(&idx) {
218+
idx += 1;
219+
}
220+
new_got_entries.insert(name.to_string(), idx);
221+
index.associate_got_index(name, idx)?;
222+
new_got.insert(idx, name.to_string());
223+
}
224+
225+
// Now we can write new_got_entries back out to a file.
226+
write_got_layout(new_got_entries, location.as_str(), *format)?;
227+
228+
// Construct our GOT as a new global array. We initialise this array in the loader code.
229+
let got_size = new_got.keys().max().map_or(0, |m| *m + 1);
230+
let _got = llvm.create_global_variable(
231+
&self.module,
232+
"__custom_got",
233+
BasicTypeEnum::ArrayType(Llvm::get_array_type(
234+
BasicTypeEnum::PointerType(llvm.context.i8_type().ptr_type(0.into())),
235+
got_size.try_into().expect("the computed custom GOT size is too large"),
236+
)),
237+
);
238+
}
239+
138240
//Generate opaque functions for implementations and associate them with their types
139241
let llvm = Llvm::new(context, context.create_builder());
140242
let llvm_impl_index = pou_generator::generate_implementation_stubs(

src/codegen/generators/expression_generator.rs

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@ use crate::{
1717
};
1818
use inkwell::{
1919
builder::Builder,
20-
types::{BasicType, BasicTypeEnum},
20+
types::{BasicType, BasicTypeEnum, FunctionType},
2121
values::{
22-
ArrayValue, BasicMetadataValueEnum, BasicValue, BasicValueEnum, FloatValue, IntValue, PointerValue,
23-
StructValue, VectorValue,
22+
ArrayValue, BasicMetadataValueEnum, BasicValue, BasicValueEnum, CallableValue,
23+
CallSiteValue, FloatValue, IntValue, PointerValue, StructValue, VectorValue,
2424
},
2525
AddressSpace, FloatPredicate, IntPredicate,
2626
};
@@ -316,6 +316,39 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> {
316316
}
317317
}
318318

319+
/// Generate an access to the appropriate GOT entry to achieve a call to the given function.
320+
pub fn generate_got_call(
321+
&self,
322+
qualified_name: &str,
323+
function_type: &FunctionType<'ink>,
324+
args: &[BasicMetadataValueEnum<'ink>],
325+
) -> Result<Option<CallSiteValue<'ink>>, Diagnostic> {
326+
// We will generate a GEP, which has as its base address the magic constant which
327+
// will eventually be replaced by the location of the GOT.
328+
let base = self
329+
.llvm
330+
.context
331+
.i64_type()
332+
.const_int(0xdeadbeef00000000, false)
333+
.const_to_pointer(function_type.ptr_type(0.into()).ptr_type(0.into()));
334+
335+
self.llvm_index
336+
.find_got_index(qualified_name)
337+
.map(|idx| {
338+
let mut ptr = self.llvm.load_array_element(
339+
base,
340+
&[self.llvm.context.i32_type().const_int(idx, false)],
341+
"",
342+
)?;
343+
ptr = self.llvm.load_pointer(&ptr, "").into_pointer_value();
344+
let callable = CallableValue::try_from(ptr)
345+
.map_err(|_| Diagnostic::new("Pointer was not a function pointer"))?;
346+
347+
Ok(self.llvm.builder.build_call(callable, args, "call"))
348+
})
349+
.transpose()
350+
}
351+
319352
/// generates a binary expression (e.g. a + b, x AND y, etc.) and returns the resulting `BasicValueEnum`
320353
/// - `left` the AstStatement left of the operator
321354
/// - `right` the AstStatement right of the operator
@@ -512,9 +545,17 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> {
512545
None
513546
};
514547

548+
// Check for the function within the GOT. If it's there, we need to generate an indirect
549+
// call to its location within the GOT, which should contain a function pointer.
550+
// First get the function type so our function pointer can have the correct type.
551+
let qualified_name = self.annotations.get_qualified_name(operator)
552+
.expect("Shouldn't have got this far without a name for the function");
553+
let function_type = function.get_type();
554+
let call = self.generate_got_call(qualified_name, &function_type, &arguments_list)?
555+
.unwrap_or_else(|| self.llvm.builder.build_call(function, &arguments_list, "call"));
556+
515557
// if the target is a function, declare the struct locally
516558
// assign all parameters into the struct values
517-
let call = &self.llvm.builder.build_call(function, &arguments_list, "call");
518559

519560
// so grab either:
520561
// - the out-pointer if we generated one in by_ref_func_out

src/codegen/generators/variable_generator.rs

Lines changed: 2 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,11 @@ use crate::{
55
codegen::{debug::Debug, llvm_index::LlvmTypedIndex, llvm_typesystem::cast_if_needed},
66
index::{get_initializer_name, Index, PouIndexEntry, VariableIndexEntry},
77
resolver::{AnnotationMap, AstAnnotations, Dependency},
8-
ConfigFormat,
98
};
109
use indexmap::IndexSet;
11-
use inkwell::{module::Module, types::BasicTypeEnum, values::GlobalValue};
10+
use inkwell::{module::Module, values::GlobalValue};
1211
use plc_ast::ast::LinkageType;
1312
use plc_diagnostics::diagnostics::Diagnostic;
14-
use std::collections::HashMap;
15-
use std::fs::{read_to_string, write};
16-
use std::path::Path;
1713

1814
use super::{
1915
data_type_generator::get_default_for,
@@ -24,48 +20,13 @@ use super::{
2420
use crate::codegen::debug::DebugBuilderEnum;
2521
use crate::index::FxIndexSet;
2622

27-
pub fn read_got_layout(location: &str, format: ConfigFormat) -> Result<HashMap<String, u64>, Diagnostic> {
28-
if !Path::new(location).is_file() {
29-
// Assume if the file doesn't exist that there is no existing GOT layout yet. write_got_layout will handle
30-
// creating our file when we want to.
31-
return Ok(HashMap::new());
32-
}
33-
34-
let s =
35-
read_to_string(location).map_err(|_| Diagnostic::new("GOT layout could not be read from file"))?;
36-
match format {
37-
ConfigFormat::JSON => serde_json::from_str(&s)
38-
.map_err(|_| Diagnostic::new("Could not deserialize GOT layout from JSON")),
39-
ConfigFormat::TOML => {
40-
toml::de::from_str(&s).map_err(|_| Diagnostic::new("Could not deserialize GOT layout from TOML"))
41-
}
42-
}
43-
}
44-
45-
pub fn write_got_layout(
46-
got_entries: HashMap<String, u64>,
47-
location: &str,
48-
format: ConfigFormat,
49-
) -> Result<(), Diagnostic> {
50-
let s = match format {
51-
ConfigFormat::JSON => serde_json::to_string(&got_entries)
52-
.map_err(|_| Diagnostic::new("Could not serialize GOT layout to JSON"))?,
53-
ConfigFormat::TOML => toml::ser::to_string(&got_entries)
54-
.map_err(|_| Diagnostic::new("Could not serialize GOT layout to TOML"))?,
55-
};
56-
57-
write(location, s).map_err(|_| Diagnostic::new("GOT layout could not be written to file"))?;
58-
Ok(())
59-
}
60-
6123
pub struct VariableGenerator<'ctx, 'b> {
6224
module: &'b Module<'ctx>,
6325
llvm: &'b Llvm<'ctx>,
6426
global_index: &'b Index,
6527
annotations: &'b AstAnnotations,
6628
types_index: &'b LlvmTypedIndex<'ctx>,
6729
debug: &'b mut DebugBuilderEnum<'ctx>,
68-
got_layout_file: Option<(String, ConfigFormat)>,
6930
}
7031

7132
impl<'ctx, 'b> VariableGenerator<'ctx, 'b> {
@@ -76,9 +37,8 @@ impl<'ctx, 'b> VariableGenerator<'ctx, 'b> {
7637
annotations: &'b AstAnnotations,
7738
types_index: &'b LlvmTypedIndex<'ctx>,
7839
debug: &'b mut DebugBuilderEnum<'ctx>,
79-
got_layout_file: Option<(String, ConfigFormat)>,
8040
) -> Self {
81-
VariableGenerator { module, llvm, global_index, annotations, types_index, debug, got_layout_file }
41+
VariableGenerator { module, llvm, global_index, annotations, types_index, debug }
8242
}
8343

8444
pub fn generate_global_variables(
@@ -140,48 +100,6 @@ impl<'ctx, 'b> VariableGenerator<'ctx, 'b> {
140100
);
141101
}
142102

143-
if let Some((location, format)) = &self.got_layout_file {
144-
let got_entries = read_got_layout(location.as_str(), *format)?;
145-
let mut new_globals = Vec::new();
146-
let mut new_got_entries = HashMap::new();
147-
let mut new_got = HashMap::new();
148-
149-
for (name, _) in &globals {
150-
if let Some(idx) = got_entries.get(&name.to_string()) {
151-
new_got_entries.insert(name.to_string(), *idx);
152-
index.associate_got_index(name, *idx);
153-
new_got.insert(*idx, name.to_string());
154-
} else {
155-
new_globals.push(name.to_string());
156-
}
157-
}
158-
159-
// Put any globals that weren't there last time in any free space in the GOT.
160-
let mut idx: u64 = 0;
161-
for name in &new_globals {
162-
while new_got.contains_key(&idx) {
163-
idx += 1;
164-
}
165-
new_got_entries.insert(name.to_string(), idx);
166-
index.associate_got_index(name, idx);
167-
new_got.insert(idx, name.to_string());
168-
}
169-
170-
// Now we can write new_got_entries back out to a file.
171-
write_got_layout(new_got_entries, location.as_str(), *format)?;
172-
173-
// Construct our GOT as a new global array. We initialise this array in the loader code.
174-
let got_size = new_got.keys().max().map_or(0, |m| *m + 1);
175-
let _got = self.llvm.create_global_variable(
176-
self.module,
177-
"__custom_got",
178-
BasicTypeEnum::ArrayType(Llvm::get_array_type(
179-
BasicTypeEnum::PointerType(self.llvm.context.i8_type().ptr_type(0.into())),
180-
got_size.try_into().expect("the computed custom GOT size is too large"),
181-
)),
182-
);
183-
}
184-
185103
Ok(index)
186104
}
187105

0 commit comments

Comments
 (0)