Skip to content

Commit cd6dcc0

Browse files
committed
fix: Function pointer to function block body
Function pointers can now point to the body of a function block, allowing for code execution such as ``` FUNCTION_BLOCK A VAR localState: DINT := 5; END_VAR METHOD foo // ... END_METHOD printf('localState = %d$N', localState); END_FUNCTION_BLOCK FUNCTION main VAR instanceA: A; fnBodyPtr: FNPTR A := ADR(A); END_VAR fnBodyPtr^(instanceA); // prints "localState = 5" END_FUNCTION ```
1 parent 2647f98 commit cd6dcc0

File tree

49 files changed

+1570
-150
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+1570
-150
lines changed

.vscode/launch.json

Lines changed: 30 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,29 +4,31 @@
44
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
55
"version": "0.2.0",
66
"configurations": [
7-
{
8-
"type": "codelldb",
9-
"request": "launch",
10-
"name": "Debug codelldb standard_lib",
11-
"cargo": {
7+
{
8+
"type": "codelldb",
9+
"request": "launch",
10+
"name": "Debug codelldb standard_lib",
11+
"cargo": {
12+
"args": [
13+
"build",
14+
"--bin=plc",
15+
"--package=plc_driver"
16+
],
17+
"filter": {
18+
"name": "plc",
19+
"kind": "bin"
20+
}
21+
},
22+
"program": "target/debug/plc",
1223
"args": [
13-
"build",
14-
"--bin=plc",
15-
"--package=plc_driver"
24+
"libs/stdlib/iec61131-st/*.st"
1625
],
17-
"filter": {
18-
"name": "plc",
19-
"kind": "bin"
20-
}
21-
},
22-
"program": "target/debug/plc",
23-
"args": ["libs/stdlib/iec61131-st/*.st"],
24-
"cwd": "${workspaceFolder}",
25-
"env": {
26-
"RUST_LOG": "rusty"
26+
"cwd": "${workspaceFolder}",
27+
"env": {
28+
"RUST_LOG": "rusty"
29+
},
30+
"terminal": "integrated"
2731
},
28-
"terminal": "integrated"
29-
},
3032
{
3133
"type": "codelldb",
3234
"request": "launch",
@@ -43,7 +45,11 @@
4345
}
4446
},
4547
"program": "target/debug/plc",
46-
"args": ["target/demo.st", "-g", "-c"],
48+
"args": [
49+
"target/demo.st",
50+
"-g",
51+
"-c"
52+
],
4753
"cwd": "${workspaceFolder}",
4854
"env": {
4955
"RUST_LOG": "rusty"
@@ -67,7 +73,8 @@
6773
},
6874
"args": [
6975
"target/demo.st",
70-
"tests/lit/util/printf.pli"
76+
"tests/lit/util/printf.pli",
77+
"-j=1",
7178
],
7279
"cwd": "${workspaceFolder}",
7380
"env": {
@@ -107,7 +114,6 @@
107114
"cwd": "${workspaceFolder}",
108115
"program": "target/demo",
109116
"terminal": "integrated"
110-
111117
}
112118
]
113-
}
119+
}

compiler/plc_ast/src/ast.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -709,6 +709,9 @@ pub enum DataType {
709709
/// e.g., `foo : POINTER TO DINT := ADR(stringValue)`. When true, the type of the pointer must match
710710
/// the referenced type exactly.
711711
type_safe: bool,
712+
713+
/// Indicates whether the pointer is a function pointer.
714+
is_function: bool,
712715
},
713716
StringType {
714717
name: Option<String>,
@@ -1262,6 +1265,17 @@ impl AstNode {
12621265
matches!(self.stmt, AstStatement::This)
12631266
}
12641267

1268+
pub fn is_this_deref(&self) -> bool {
1269+
match &self.stmt {
1270+
AstStatement::ReferenceExpr(
1271+
ReferenceExpr { access: ReferenceAccess::Deref, base: Some(base) },
1272+
..,
1273+
) => base.is_this(),
1274+
1275+
_ => false,
1276+
}
1277+
}
1278+
12651279
pub fn is_paren(&self) -> bool {
12661280
matches!(self.stmt, AstStatement::ParenExpression { .. })
12671281
}

src/builtins.rs

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,23 @@ lazy_static! {
5151
code: |generator, params, location| {
5252
if let [reference] = params {
5353
// Return the pointer value of a function when dealing with them, e.g. `ADR(MyFb.myMethod)`
54-
if let Some(StatementAnnotation::Function { qualified_name, .. }) = generator.annotations.get(reference) {
55-
if let Some(fn_value) = generator.llvm_index.find_associated_implementation(qualified_name) {
56-
return Ok(ExpressionValue::RValue(fn_value.as_global_value().as_pointer_value().as_basic_value_enum()));
54+
match generator.annotations.get(reference) {
55+
Some(StatementAnnotation::Function { qualified_name, .. }) => {
56+
if let Some(fn_value) = generator.llvm_index.find_associated_implementation(qualified_name) {
57+
return Ok(ExpressionValue::RValue(fn_value.as_global_value().as_pointer_value().as_basic_value_enum()));
58+
}
5759
}
58-
}
60+
61+
Some(StatementAnnotation::Type { type_name }) => {
62+
if generator.index.find_type(type_name).is_some_and(|opt| opt.information.is_function_block()) {
63+
if let Some(fn_value) = generator.llvm_index.find_associated_implementation(type_name) {
64+
return Ok(ExpressionValue::RValue(fn_value.as_global_value().as_pointer_value().as_basic_value_enum()));
65+
}
66+
}
67+
}
68+
69+
_ => (),
70+
};
5971

6072
generator
6173
.generate_lvalue(reference)
@@ -95,7 +107,7 @@ lazy_static! {
95107
let ptr_type = resolver::add_pointer_type(
96108
&mut annotator.annotation_map.new_index,
97109
input_type,
98-
true
110+
true,
99111
);
100112

101113
annotator.annotate(

src/codegen/generators/data_type_generator.rs

Lines changed: 36 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use inkwell::{
1919
values::{BasicValue, BasicValueEnum},
2020
AddressSpace,
2121
};
22-
use plc_ast::ast::{AstNode, AstStatement, PouType};
22+
use plc_ast::ast::{AstNode, AstStatement};
2323
use plc_ast::literals::AstLiteral;
2424
use plc_diagnostics::diagnostics::Diagnostic;
2525
use plc_source::source_location::SourceLocation;
@@ -206,10 +206,6 @@ impl<'ink> DataTypeGenerator<'ink, '_> {
206206
let information = data_type.get_type_information();
207207
match information {
208208
DataTypeInformation::Struct { source, .. } => match source {
209-
StructSource::Pou(PouType::Method { .. }) => {
210-
Ok(self.create_function_type(name)?.as_any_type_enum())
211-
}
212-
213209
StructSource::Pou(..) => self
214210
.types_index
215211
.get_associated_pou_type(data_type.get_name())
@@ -276,7 +272,12 @@ impl<'ink> DataTypeGenerator<'ink, '_> {
276272
.and_then(|data_type| self.create_type(name, data_type)),
277273
DataTypeInformation::Void => Ok(get_llvm_int_type(self.llvm.context, 32, "Void").into()),
278274
DataTypeInformation::Pointer { inner_type_name, .. } => {
279-
let inner_type = self.create_type(inner_type_name, self.index.get_type(inner_type_name)?)?;
275+
let inner_type = if information.is_function_pointer() {
276+
self.create_function_type(inner_type_name)?.as_any_type_enum()
277+
} else {
278+
self.create_type(inner_type_name, self.index.get_type(inner_type_name)?)?
279+
};
280+
280281
Ok(inner_type.create_ptr_type(AddressSpace::from(ADDRESS_SPACE_GENERIC)).into())
281282
}
282283
DataTypeInformation::Generic { .. } => {
@@ -298,24 +299,37 @@ impl<'ink> DataTypeGenerator<'ink, '_> {
298299

299300
// Methods are defined as functions in the LLVM IR, but carry the underlying POU type as their first
300301
// parameter to operate on them, hence push the POU type to the very first position.
301-
if let Some(PouIndexEntry::Method { parent_name, .. }) = self.index.find_pou(pou_name) {
302-
let ty = self.types_index.get_associated_type(parent_name).expect("must exist");
303-
let ty_ptr = ty.ptr_type(AddressSpace::from(ADDRESS_SPACE_GENERIC)).into();
302+
match self.index.find_pou(pou_name) {
303+
Some(PouIndexEntry::Method { parent_name, .. }) => {
304+
let ty = self.types_index.get_associated_type(parent_name).expect("must exist");
305+
let ty_ptr = ty.ptr_type(AddressSpace::from(ADDRESS_SPACE_GENERIC)).into();
306+
307+
parameter_types.push(ty_ptr);
308+
for parameter in self.index.get_declared_parameters(pou_name) {
309+
// Instead of relying on the LLVM index, we create data-types directly in here because some of
310+
// them may not have been registered yet. For example, at the time of writing this comment the
311+
// `__auto_pointer_to_DINT` type was not present in the index for a VAR_IN_OUT parameter which
312+
// resulted in an error
313+
let ty = self.create_type(
314+
parameter.get_name(),
315+
self.index.get_type(&parameter.data_type_name).expect("must exist"),
316+
)?;
317+
318+
parameter_types.push(ty.try_into().unwrap());
319+
}
320+
}
304321

305-
parameter_types.push(ty_ptr);
306-
}
322+
// Function blocks are a bit "weird" in that they only expect an instance argument even if they
323+
// define input, output and/or inout parameters. Effectively, while being methods per-se, their
324+
// calling convention differs from regular methods.
325+
Some(PouIndexEntry::FunctionBlock { name, .. }) => {
326+
let ty = self.types_index.get_associated_type(name).expect("must exist");
327+
let ty_ptr = ty.ptr_type(AddressSpace::from(ADDRESS_SPACE_GENERIC)).into();
328+
329+
parameter_types.push(ty_ptr);
330+
}
307331

308-
for parameter in self.index.get_declared_parameters(pou_name) {
309-
// Instead of relying on the LLVM index, we create data-types directly in here because some of
310-
// them may not have been registered yet. For example, at the time of writing this comment the
311-
// `__auto_pointer_to_DINT` type was not present in the index for a VAR_IN_OUT parameter which
312-
// resulted in an error
313-
let ty = self.create_type(
314-
parameter.get_name(),
315-
self.index.get_type(&parameter.data_type_name).expect("must exist"),
316-
)?;
317-
318-
parameter_types.push(ty.try_into().unwrap());
332+
_ => unreachable!("internal error, invalid method call"),
319333
}
320334

321335
let fn_type = match return_type {

src/codegen/generators/expression_generator.rs

Lines changed: 38 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,12 @@ use crate::{
3131
llvm_typesystem::{cast_if_needed, get_llvm_int_type},
3232
},
3333
index::{
34-
const_expressions::ConstId, ArgumentType, ImplementationIndexEntry, Index, PouIndexEntry,
35-
VariableIndexEntry, VariableType,
34+
const_expressions::ConstId, ArgumentType, ImplementationIndexEntry, ImplementationType, Index,
35+
PouIndexEntry, VariableIndexEntry, VariableType,
3636
},
3737
resolver::{AnnotationMap, AstAnnotations, StatementAnnotation},
38-
typesystem,
3938
typesystem::{
40-
is_same_type_class, DataType, DataTypeInformation, DataTypeInformationProvider, Dimension,
39+
self, is_same_type_class, DataType, DataTypeInformation, DataTypeInformationProvider, Dimension,
4140
StringEncoding, VarArgs, DINT_TYPE, INT_SIZE, INT_TYPE, LINT_TYPE,
4241
},
4342
};
@@ -596,12 +595,15 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> {
596595
arguments: Option<&AstNode>,
597596
) -> Result<ExpressionValue<'ink>, Diagnostic> {
598597
let Some(ReferenceExpr { base: Some(ref base), .. }) = operator.get_deref_expr() else {
599-
unreachable!("internal error, invalid function invocation")
598+
unreachable!("internal error, invalid method call")
600599
};
601600

602601
let qualified_pou_name = self.annotations.get(operator).unwrap().qualified_name().unwrap();
603602
let impl_entry = self.index.find_implementation_by_name(qualified_pou_name).unwrap();
604-
debug_assert!(impl_entry.is_method(), "internal error, invalid function invocation");
603+
debug_assert!(
604+
impl_entry.is_method() | impl_entry.is_function_block(),
605+
"internal error, invalid method call"
606+
);
605607

606608
// Get the associated variable then load it, e.g. `%localFnPtrVariable = alloca void (%Fb*)*, align 8`
607609
// followed by `%1 = load void (%Fb*)*, void (%Fb*)** %localFnPtrVariable, align 8``
@@ -613,27 +615,40 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> {
613615
// Generate the argument list; our assumption is function pointers are only supported for methods
614616
// right now, hence we explicitly fetch the instance arguments from the list. In desugared code it we
615617
// would have something alike `fnPtr^(instanceFb, arg1, arg2, ..., argN)`
616-
let arguments = {
618+
let (instance, arguments_raw, arguments_llvm) = {
617619
let arguments = arguments.map(flatten_expression_list).unwrap_or_default();
618620
let (instance, arguments) = match arguments.len() {
619621
0 => panic!("invalid desugared code, no instance argument found"),
620-
1 => (self.generate_lvalue(arguments[0])?, [].as_slice()),
621-
_ => (self.generate_lvalue(arguments[0])?, &arguments[1..]),
622+
1 => (self.generate_lvalue(arguments[0])?, Vec::new()),
623+
_ => (self.generate_lvalue(arguments[0])?, arguments[1..].to_vec()),
622624
};
623625

624-
let mut generated_arguments = self.generate_function_arguments(
625-
self.index.find_pou(qualified_pou_name).unwrap(),
626-
arguments,
627-
self.index.get_declared_parameters(qualified_pou_name),
628-
)?;
626+
let mut generated_arguments = match &impl_entry.implementation_type {
627+
ImplementationType::Method => self.generate_function_arguments(
628+
self.index.find_pou(qualified_pou_name).unwrap(),
629+
&arguments,
630+
self.index.get_declared_parameters(qualified_pou_name),
631+
)?,
632+
633+
// Function Block body calls have a slightly different calling convention compared to regular
634+
// methods. Specifically the arguments aren't passed to the call itself but rather the
635+
// instance struct is gep'ed and the arguments are stored into the gep'ed pointer value.
636+
// Best to call `--ir` on a simple function block body call to see the generated IR.
637+
ImplementationType::FunctionBlock => {
638+
self.generate_stateful_pou_arguments(qualified_pou_name, None, instance, &arguments)?;
639+
Vec::new()
640+
}
629641

630-
generated_arguments.insert(0, instance.as_basic_value_enum().into());
631-
generated_arguments
642+
_ => unreachable!("internal error, invalid method call"),
643+
};
644+
645+
generated_arguments.insert(0, instance.clone().as_basic_value_enum().into());
646+
(instance, arguments, generated_arguments)
632647
};
633648

634649
// Finally generate the function pointer call
635650
let callable = CallableValue::try_from(function_pointer_value).unwrap();
636-
let call = self.llvm.builder.build_call(callable, &arguments, "fnptr_call");
651+
let call = self.llvm.builder.build_call(callable, &arguments_llvm, "fnptr_call");
637652

638653
let value = match call.try_as_basic_value() {
639654
Either::Left(value) => value,
@@ -646,6 +661,12 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> {
646661
}
647662
};
648663

664+
// Output variables are assigned after the function block call, effectively gep'ing the instance
665+
// struct fetching the output values
666+
if impl_entry.is_function_block() {
667+
self.assign_output_values(instance, qualified_pou_name, arguments_raw)?
668+
}
669+
649670
Ok(ExpressionValue::RValue(value))
650671
}
651672

src/codegen/generators/statement_generator.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ use plc_source::source_location::SourceLocation;
3333
use rustc_hash::FxHashMap;
3434

3535
/// the full context when generating statements inside a POU
36+
#[derive(Debug)]
3637
pub struct FunctionContext<'ink, 'b> {
3738
/// the current pou's name. This means that a variable x may refer to "`linking_context`.x"
3839
pub linking_context: &'b ImplementationIndexEntry,

0 commit comments

Comments
 (0)