Skip to content

Commit 61eb9b5

Browse files
lewis-revillCohenArthur
authored andcommitted
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 ac36e70 commit 61eb9b5

File tree

3 files changed

+161
-101
lines changed

3 files changed

+161
-101
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,
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+
fs::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+
fs::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::FunctionBlock { 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: 56 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -298,14 +298,10 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> {
298298
Some(StatementAnnotation::Variable { qualified_name, .. }) => {
299299
// We will generate a GEP, which has as its base address the magic constant which
300300
// will eventually be replaced by the location of the GOT.
301-
let base = self
302-
.llvm
303-
.context
304-
.i64_type()
305-
.const_int(0xdeadbeef00000000, false)
306-
.const_to_pointer(llvm_type
307-
.ptr_type(AddressSpace::default())
308-
.ptr_type(AddressSpace::default()));
301+
let base =
302+
self.llvm.context.i64_type().const_int(0xdeadbeef00000000, false).const_to_pointer(
303+
llvm_type.ptr_type(AddressSpace::default()).ptr_type(AddressSpace::default()),
304+
);
309305

310306
self.llvm_index
311307
.find_got_index(qualified_name)
@@ -323,6 +319,37 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> {
323319
}
324320
}
325321

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

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

527565
// so grab either:
528566
// - the out-pointer if we generated one in by_ref_func_out
@@ -1412,17 +1450,19 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> {
14121450
}
14131451
}
14141452

1415-
let ctx_type =
1416-
self.annotations.get_type_or_void(context, self.index).get_type_information();
1453+
let ctx_type = self.annotations.get_type_or_void(context, self.index).get_type_information();
14171454

14181455
// no context ... so just something like 'x'
14191456
match self.annotations.get(context) {
14201457
Some(StatementAnnotation::Variable { qualified_name, .. })
1421-
| Some(StatementAnnotation::Program { qualified_name, .. }) =>
1422-
self.generate_got_access(context, &self.llvm_index.get_associated_type(ctx_type.get_name())?)?.map_or(self
1423-
.llvm_index
1424-
.find_loaded_associated_variable_value(qualified_name)
1425-
.ok_or_else(|| Diagnostic::unresolved_reference(name, offset.clone())), Ok),
1458+
| Some(StatementAnnotation::Program { qualified_name, .. }) => self
1459+
.generate_got_access(context, &self.llvm_index.get_associated_type(ctx_type.get_name())?)?
1460+
.map_or(
1461+
self.llvm_index
1462+
.find_loaded_associated_variable_value(qualified_name)
1463+
.ok_or_else(|| Diagnostic::unresolved_reference(name, offset.clone())),
1464+
Ok,
1465+
),
14261466
_ => Err(Diagnostic::unresolved_reference(name, offset.clone())),
14271467
}
14281468
}

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)