diff --git a/Makefile b/Makefile new file mode 100644 index 0000000000..e46707098a --- /dev/null +++ b/Makefile @@ -0,0 +1,7 @@ +.PHONY: test run + +test: + cargo nextest run --workspace --no-fail-fast && ./scripts/build.sh --lit + +run: + cargo r -- target/demo.st tests/lit/util/printf.pli --linker=clang && ./demo.st.out diff --git a/compiler/plc_ast/src/ast.rs b/compiler/plc_ast/src/ast.rs index 4eda6cf970..0e563a2c8e 100644 --- a/compiler/plc_ast/src/ast.rs +++ b/compiler/plc_ast/src/ast.rs @@ -1397,6 +1397,16 @@ impl AstNode { pub fn with_metadata(self, metadata: MetaData) -> AstNode { AstNode { metadata: Some(metadata), ..self } } + + pub fn get_deref_expr(&self) -> Option<&ReferenceExpr> { + match &self.stmt { + AstStatement::ReferenceExpr(expr) => match expr { + ReferenceExpr { access: ReferenceAccess::Deref, .. } => Some(expr), + _ => None, + }, + _ => None, + } + } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] diff --git a/scripts/build.sh b/scripts/build.sh index 376089fa94..a5e44acf89 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -246,20 +246,37 @@ function run_package_std() { cp "$rel_dir/"*.a "$lib_dir" 2>/dev/null || log "$rel_dir does not contain *.a files" # Create an SO file from the copied a file log "Creating a shared library from the compiled static library" - log "Running : $cc --shared -L$lib_dir \ - -Wl,--whole-archive -liec61131std \ - -o $lib_dir/out.so -Wl,--no-whole-archive \ - -lm \ - -fuse-ld=lld \ - --target=$val" - $cc --shared -L"$lib_dir" \ - -Wl,--whole-archive -liec61131std \ - -o "$lib_dir/out.so" -Wl,--no-whole-archive \ - -lm \ - -fuse-ld=lld \ - --target="$val" - - mv "$lib_dir/out.so" "$lib_dir/libiec61131std.so" + # Check if we're on macOS and adjust linker flags accordingly + unameOut="$(uname -s)" + case "${unameOut}" in + Darwin*) + log "Running : $cc --shared -L$lib_dir \ + -Wl,-force_load,$lib_dir/libiec61131std.a \ + -o $lib_dir/libiec61131std.so \ + -lm -framework CoreFoundation \ + --target=$val" + $cc --shared -L"$lib_dir" \ + -Wl,-force_load,"$lib_dir/libiec61131std.a" \ + -o "$lib_dir/libiec61131std.so" \ + -lm -framework CoreFoundation \ + --target="$val" + ;; + *) + log "Running : $cc --shared -L$lib_dir \ + -Wl,--whole-archive -liec61131std \ + -o $lib_dir/libiec61131std.so -Wl,--no-whole-archive \ + -lm \ + -fuse-ld=lld \ + --target=$val" + $cc --shared -L"$lib_dir" \ + -Wl,--whole-archive -liec61131std \ + -o "$lib_dir/libiec61131std.so" -Wl,--no-whole-archive \ + -lm \ + -fuse-ld=lld \ + --target="$val" + ;; + esac + done else lib_dir=$OUTPUT_DIR/lib @@ -272,21 +289,36 @@ function run_package_std() { cp "$rel_dir/"*.a "$lib_dir" 2>/dev/null || log "$rel_dir does not contain *.a files" # Create an SO file from the copied a file log "Creating a shared library from the compiled static library" - log "Running : $cc --shared -L"$lib_dir" \ - -Wl,--whole-archive -liec61131std \ - -o "$lib_dir/out.so" -Wl,--no-whole-archive \ - -lm \ - -fuse-ld=lld " - $cc --shared -L"$lib_dir" \ - -Wl,--whole-archive -liec61131std \ - -o "$lib_dir/out.so" -Wl,--no-whole-archive \ - -lm \ - -fuse-ld=lld - mv "$lib_dir/out.so" "$lib_dir/libiec61131std.so" + # Check if we're on macOS and adjust linker flags accordingly + unameOut="$(uname -s)" + case "${unameOut}" in + Darwin*) + log "Running : $cc --shared -L"$lib_dir" \ + -Wl,-force_load,$lib_dir/libiec61131std.a \ + -o "$lib_dir/libiec61131std.so" \ + -lm -framework CoreFoundation" + $cc --shared -L"$lib_dir" \ + -Wl,-force_load,"$lib_dir/libiec61131std.a" \ + -o "$lib_dir/libiec61131std.so" \ + -lm -framework CoreFoundation + ;; + *) + log "Running : $cc --shared -L"$lib_dir" \ + -Wl,--whole-archive -liec61131std \ + -o "$lib_dir/libiec61131std.so" -Wl,--no-whole-archive \ + -lm \ + -fuse-ld=lld " + $cc --shared -L"$lib_dir" \ + -Wl,--whole-archive -liec61131std \ + -o "$lib_dir/libiec61131std.so" -Wl,--no-whole-archive \ + -lm \ + -fuse-ld=lld + ;; + esac fi log "Enabling read/write on the output folder" - chmod a+rw $OUTPUT_DIR -R + chmod -R a+rw $OUTPUT_DIR } @@ -506,7 +538,7 @@ fi if [[ -d $project_location/target/ ]]; then log "Allow access to target folders" - chmod a+rw -R $project_location/target/ + chmod -R a+rw $project_location/target/ fi if [[ $offline -ne 0 ]]; then diff --git a/src/builtins.rs b/src/builtins.rs index 2aeb42537f..fa88037b75 100644 --- a/src/builtins.rs +++ b/src/builtins.rs @@ -50,6 +50,13 @@ lazy_static! { generic_name_resolver: no_generic_name_resolver, code: |generator, params, location| { if let [reference] = params { + // Return the pointer value of a function when dealing with them, e.g. `ADR(MyFb.myMethod)` + if let Some(StatementAnnotation::Function { qualified_name, .. }) = generator.annotations.get(reference) { + if let Some(fn_value) = generator.llvm_index.find_associated_implementation(qualified_name) { + return Ok(ExpressionValue::RValue(fn_value.as_global_value().as_pointer_value().as_basic_value_enum())); + } + } + generator .generate_lvalue(reference) .map(|it| ExpressionValue::RValue(it.as_basic_value_enum())) @@ -103,6 +110,13 @@ lazy_static! { generic_name_resolver: no_generic_name_resolver, code: |generator, params, location| { if let [reference] = params { + // Return the pointer value of a function when dealing with them, e.g. `ADR(MyFb.myMethod)` + if let Some(StatementAnnotation::Function { qualified_name, .. }) = generator.annotations.get(reference) { + if let Some(fn_value) = generator.llvm_index.find_associated_implementation(qualified_name) { + return Ok(ExpressionValue::RValue(fn_value.as_global_value().as_pointer_value().as_basic_value_enum())); + } + } + generator .generate_lvalue(reference) .map(|it| ExpressionValue::RValue(it.as_basic_value_enum())) diff --git a/src/codegen.rs b/src/codegen.rs index 1de623a3fa..464438cdc3 100644 --- a/src/codegen.rs +++ b/src/codegen.rs @@ -343,7 +343,7 @@ impl<'ink> CodeGen<'ink> { let location = (&unit.file).into(); self.debug.finalize(); - log::debug!("{}", self.module.to_string()); + log::trace!("{}", self.module.to_string()); #[cfg(feature = "verify")] { @@ -382,7 +382,7 @@ impl<'ink> GeneratedModule<'ink> { .create_module_from_ir(buffer) .map_err(|it| Diagnostic::new(it.to_string_lossy()).with_error_code("E071"))?; - log::debug!("{}", module.to_string()); + log::trace!("{}", module.to_string()); Ok(GeneratedModule { module, location: path.into(), engine: RefCell::new(None) }) } @@ -391,7 +391,7 @@ impl<'ink> GeneratedModule<'ink> { self.module .link_in_module(other.module) .map_err(|it| Diagnostic::new(it.to_string_lossy()).with_error_code("E071"))?; - log::debug!("Merged: {}", self.module.to_string()); + log::trace!("Merged: {}", self.module.to_string()); Ok(self) } @@ -569,8 +569,8 @@ impl<'ink> GeneratedModule<'ink> { /// * `codegen` - The generated LLVM module to be persisted /// * `output` - The location to save the generated ir file pub fn persist_to_ir(&self, output: PathBuf) -> Result { - log::debug!("Output location: {}", output.to_string_lossy()); - log::debug!("{}", self.persist_to_string()); + log::trace!("Output location: {}", output.to_string_lossy()); + log::trace!("{}", self.persist_to_string()); self.module .print_to_file(&output) diff --git a/src/codegen/generators/data_type_generator.rs b/src/codegen/generators/data_type_generator.rs index 1279f822e5..c07923f570 100644 --- a/src/codegen/generators/data_type_generator.rs +++ b/src/codegen/generators/data_type_generator.rs @@ -1,6 +1,7 @@ // Copyright (c) 2020 Ghaith Hachem and Mathias Rieder use crate::codegen::debug::Debug; -use crate::index::{FxIndexSet, Index, VariableIndexEntry, VariableType}; +use crate::codegen::llvm_index::TypeHelper; +use crate::index::{FxIndexSet, Index, PouIndexEntry, VariableIndexEntry, VariableType}; use crate::resolver::{AstAnnotations, Dependency}; use crate::typesystem::{self, DataTypeInformation, Dimension, StringEncoding, StructSource}; use crate::{ @@ -12,12 +13,13 @@ use crate::{ typesystem::DataType, }; +use inkwell::types::{AnyType, AnyTypeEnum, FunctionType}; use inkwell::{ types::{BasicType, BasicTypeEnum}, values::{BasicValue, BasicValueEnum}, AddressSpace, }; -use plc_ast::ast::{AstNode, AstStatement}; +use plc_ast::ast::{AstNode, AstStatement, PouType}; use plc_ast::literals::AstLiteral; use plc_diagnostics::diagnostics::Diagnostic; use plc_source::source_location::SourceLocation; @@ -200,15 +202,26 @@ impl<'ink> DataTypeGenerator<'ink, '_> { /// Creates an llvm type to be associated with the given data type. /// Generates only an opaque type for structs. /// Eagerly generates but does not associate nested array and referenced aliased types - fn create_type(&mut self, name: &str, data_type: &DataType) -> Result, Diagnostic> { + fn create_type(&mut self, name: &str, data_type: &DataType) -> Result, Diagnostic> { let information = data_type.get_type_information(); match information { DataTypeInformation::Struct { source, .. } => match source { - StructSource::Pou(..) => self.types_index.get_associated_pou_type(data_type.get_name()), - StructSource::OriginalDeclaration => { - self.types_index.get_associated_type(data_type.get_name()) + StructSource::Pou(PouType::Method { .. }) => { + Ok(self.create_function_type(name)?.as_any_type_enum()) } - StructSource::Internal(_) => self.types_index.get_associated_type(data_type.get_name()), + + StructSource::Pou(..) => self + .types_index + .get_associated_pou_type(data_type.get_name()) + .map(|res| res.as_any_type_enum()), + StructSource::OriginalDeclaration => self + .types_index + .get_associated_type(data_type.get_name()) + .map(|res| res.as_any_type_enum()), + StructSource::Internal(_) => self + .types_index + .get_associated_type(data_type.get_name()) + .map(|res| res.as_any_type_enum()), }, // We distinguish between two types of arrays, normal and variable length ones. @@ -222,7 +235,7 @@ impl<'ink> DataTypeGenerator<'ink, '_> { .get_effective_type_by_name(inner_type_name) .and_then(|inner_type| self.create_type(inner_type_name, inner_type)) .and_then(|inner_type| self.create_nested_array_type(inner_type, dimensions)) - .map(|it| it.as_basic_type_enum()) + .map(|it| it.as_any_type_enum()) } } DataTypeInformation::Integer { size, .. } => { @@ -264,7 +277,7 @@ impl<'ink> DataTypeGenerator<'ink, '_> { DataTypeInformation::Void => Ok(get_llvm_int_type(self.llvm.context, 32, "Void").into()), DataTypeInformation::Pointer { inner_type_name, .. } => { let inner_type = self.create_type(inner_type_name, self.index.get_type(inner_type_name)?)?; - Ok(inner_type.ptr_type(AddressSpace::from(ADDRESS_SPACE_GENERIC)).into()) + Ok(inner_type.create_ptr_type(AddressSpace::from(ADDRESS_SPACE_GENERIC)).into()) } DataTypeInformation::Generic { .. } => { unreachable!("Generic types should not be generated") @@ -272,6 +285,54 @@ impl<'ink> DataTypeGenerator<'ink, '_> { } } + // TODO(vosa): Is this neccessary? Are the function types indexed and later on used in the expression + // generator? If not this whole commit could be reverted, double-check before merging + fn create_function_type(&mut self, pou_name: &str) -> Result, Diagnostic> { + let return_type = self + .types_index + .find_associated_type(self.index.get_return_type_or_void(pou_name).get_name()) + .map(|opt| opt.as_any_type_enum()) + .unwrap_or(self.llvm.context.void_type().as_any_type_enum()); + + let mut parameter_types = Vec::new(); + + // Methods are defined as functions in the LLVM IR, but carry the underlying POU type as their first + // parameter to operate on them, hence push the POU type to the very first position. + if let Some(PouIndexEntry::Method { parent_name, .. }) = self.index.find_pou(pou_name) { + let ty = self.types_index.get_associated_type(parent_name).expect("must exist"); + let ty_ptr = ty.ptr_type(AddressSpace::from(ADDRESS_SPACE_GENERIC)).into(); + + parameter_types.push(ty_ptr); + } + + for parameter in self.index.get_declared_parameters(pou_name) { + // Instead of relying on the LLVM index, we create data-types directly in here because some of + // them may not have been registered yet. For example, at the time of writing this comment the + // `__auto_pointer_to_DINT` type was not present in the index for a VAR_IN_OUT parameter which + // resulted in an error + let ty = self.create_type( + parameter.get_name(), + self.index.get_type(¶meter.data_type_name).expect("must exist"), + )?; + + parameter_types.push(ty.try_into().unwrap()); + } + + let fn_type = match return_type { + AnyTypeEnum::ArrayType(value) => value.fn_type(parameter_types.as_slice(), false), + AnyTypeEnum::FloatType(value) => value.fn_type(parameter_types.as_slice(), false), + AnyTypeEnum::IntType(value) => value.fn_type(parameter_types.as_slice(), false), + AnyTypeEnum::PointerType(value) => value.fn_type(parameter_types.as_slice(), false), + AnyTypeEnum::StructType(value) => value.fn_type(parameter_types.as_slice(), false), + AnyTypeEnum::VectorType(value) => value.fn_type(parameter_types.as_slice(), false), + AnyTypeEnum::VoidType(value) => value.fn_type(parameter_types.as_slice(), false), + + AnyTypeEnum::FunctionType(_) => unreachable!(), + }; + + Ok(fn_type) + } + fn generate_initial_value( &mut self, data_type: &DataType, @@ -435,9 +496,9 @@ impl<'ink> DataTypeGenerator<'ink, '_> { /// `arr: ARRAY[0..3] OF INT`. fn create_nested_array_type( &self, - inner_type: BasicTypeEnum<'ink>, + inner_type: AnyTypeEnum<'ink>, dimensions: &[Dimension], - ) -> Result, Diagnostic> { + ) -> Result, Diagnostic> { let len = dimensions .iter() .map(|dimension| { @@ -453,14 +514,17 @@ impl<'ink> DataTypeGenerator<'ink, '_> { })?; let result = match inner_type { - BasicTypeEnum::IntType(ty) => ty.array_type(len), - BasicTypeEnum::FloatType(ty) => ty.array_type(len), - BasicTypeEnum::StructType(ty) => ty.array_type(len), - BasicTypeEnum::ArrayType(ty) => ty.array_type(len), - BasicTypeEnum::PointerType(ty) => ty.array_type(len), - BasicTypeEnum::VectorType(ty) => ty.array_type(len), + AnyTypeEnum::IntType(ty) => ty.array_type(len), + AnyTypeEnum::FloatType(ty) => ty.array_type(len), + AnyTypeEnum::StructType(ty) => ty.array_type(len), + AnyTypeEnum::ArrayType(ty) => ty.array_type(len), + AnyTypeEnum::PointerType(ty) => ty.array_type(len), + AnyTypeEnum::VectorType(ty) => ty.array_type(len), + + AnyTypeEnum::FunctionType(_) => unimplemented!("function types are not supported in arrays"), + AnyTypeEnum::VoidType(_) => unimplemented!("void types not supported in arrays"), } - .as_basic_type_enum(); + .as_any_type_enum(); Ok(result) } diff --git a/src/codegen/generators/expression_generator.rs b/src/codegen/generators/expression_generator.rs index f4dcca2498..9a2bd3b685 100644 --- a/src/codegen/generators/expression_generator.rs +++ b/src/codegen/generators/expression_generator.rs @@ -9,6 +9,7 @@ use inkwell::{ }, AddressSpace, FloatPredicate, IntPredicate, }; +use itertools::Either; use rustc_hash::FxHashSet; use plc_ast::{ @@ -490,6 +491,11 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { operator: &AstNode, parameters: Option<&AstNode>, ) -> Result, Diagnostic> { + // Check if we are dealing with something alike `foo^(...)` + if self.annotations.get(operator).is_some_and(StatementAnnotation::is_fnptr) { + return self.generate_fnptr_call(operator, parameters); + } + // find the pou we're calling let pou = self.annotations.get_call_name(operator).zip(self.annotations.get_qualified_name(operator)) .and_then(|(call_name, qualified_name)| self.index.find_pou(call_name) @@ -584,6 +590,65 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { value } + fn generate_fnptr_call( + &self, + operator: &AstNode, + arguments: Option<&AstNode>, + ) -> Result, Diagnostic> { + let Some(ReferenceExpr { base: Some(ref base), .. }) = operator.get_deref_expr() else { + unreachable!("internal error, invalid function invocation") + }; + + let qualified_pou_name = self.annotations.get(operator).unwrap().qualified_name().unwrap(); + let impl_entry = self.index.find_implementation_by_name(qualified_pou_name).unwrap(); + debug_assert!(impl_entry.is_method(), "internal error, invalid function invocation"); + + // Get the associated variable then load it, e.g. `%localFnPtrVariable = alloca void (%Fb*)*, align 8` + // followed by `%1 = load void (%Fb*)*, void (%Fb*)** %localFnPtrVariable, align 8`` + let function_pointer_value = match self.generate_expression_value(base)? { + ExpressionValue::LValue(value) => self.llvm.load_pointer(&value, "").into_pointer_value(), + ExpressionValue::RValue(_) => unreachable!(), + }; + + // Generate the argument list; our assumption is function pointers are only supported for methods + // right now, hence we explicitly fetch the instance arguments from the list. In desugared code it we + // would have something alike `fnPtr^(instanceFb, arg1, arg2, ..., argN)` + let arguments = { + let arguments = arguments.map(flatten_expression_list).unwrap_or_default(); + let (instance, arguments) = match arguments.len() { + 0 => panic!("invalid desugared code, no instance argument found"), + 1 => (self.generate_lvalue(arguments[0])?, [].as_slice()), + _ => (self.generate_lvalue(arguments[0])?, &arguments[1..]), + }; + + let mut generated_arguments = self.generate_function_arguments( + self.index.find_pou(qualified_pou_name).unwrap(), + arguments, + self.index.get_declared_parameters(qualified_pou_name), + )?; + + generated_arguments.insert(0, instance.as_basic_value_enum().into()); + generated_arguments + }; + + // Finally generate the function pointer call + let callable = CallableValue::try_from(function_pointer_value).unwrap(); + let call = self.llvm.builder.build_call(callable, &arguments, "fnptr_call"); + + let value = match call.try_as_basic_value() { + Either::Left(value) => value, + Either::Right(_) => { + // TODO: When is this neccessary? + get_llvm_int_type(self.llvm.context, INT_SIZE, INT_TYPE) + .ptr_type(AddressSpace::from(ADDRESS_SPACE_CONST)) + .const_null() + .as_basic_value_enum() + } + }; + + Ok(ExpressionValue::RValue(value)) + } + /// copies the output values to the assigned output variables /// - `parameter_struct` a pointer to a struct-instance that holds all function-parameters /// - `function_name` the name of the callable @@ -911,7 +976,7 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { let mut variadic_parameters = Vec::new(); let mut passed_param_indices = Vec::new(); for (i, argument) in arguments.iter().enumerate() { - let (i, parameter, _) = get_implicit_call_parameter(argument, &declared_parameters, i)?; + let (i, argument, _) = get_implicit_call_parameter(argument, &declared_parameters, i)?; // parameter_info includes the declaration type and type name let parameter_info = declared_parameters @@ -935,11 +1000,11 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { .unwrap_or_else(|| { // if we are dealing with a variadic function, we can accept all extra parameters if pou.is_variadic() { - variadic_parameters.push(parameter); + variadic_parameters.push(argument); Ok(None) } else { // we are not variadic, we have too many parameters here - Err(Diagnostic::codegen_error("Too many parameters", parameter)) + Err(Diagnostic::codegen_error("Too many parameters", argument)) } })?; @@ -949,11 +1014,11 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { && declaration_type.is_input()) { let declared_parameter = declared_parameters.get(i); - self.generate_argument_by_ref(parameter, type_name, declared_parameter.copied())? + self.generate_argument_by_ref(argument, type_name, declared_parameter.copied())? } else { // by val - if !parameter.is_empty_statement() { - self.generate_expression(parameter)? + if !argument.is_empty_statement() { + self.generate_expression(argument)? } else if let Some(param) = declared_parameters.get(i) { self.generate_empty_expression(param)? } else { @@ -2610,13 +2675,32 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { } // `INT#target` (INT = base) - (ReferenceAccess::Cast(target), Some(_base)) => { + (ReferenceAccess::Cast(target), Some(base)) => { if target.as_ref().is_identifier() { let mr = AstFactory::create_member_reference(target.as_ref().clone(), None, target.get_id()); self.generate_expression_value(&mr) - } else { + } else if target.as_ref().is_literal(){ self.generate_expression_value(target.as_ref()) + } else { + // Otherwise just bitcast the target to the given type + let base_type = self.annotations.get_type_or_void(base, self.index); + let base_type_name = base_type.get_name(); + + // Generate the value we're casting + let target_value = self.generate_expression_value(target.as_ref())?; + + // Get the LLVM type for the cast target + let target_llvm_type = self.llvm_index.get_associated_type(base_type_name) + .map(|t| t.ptr_type(AddressSpace::from(0))) + .unwrap_or_else(|_| self.llvm.context.i8_type().ptr_type(AddressSpace::from(0))); + + // Perform the bitcast + let basic_value = target_value.get_basic_value_enum(); + let cast_ptr = self.llvm.builder.build_bitcast(basic_value, target_llvm_type, "cast"); + let cast_value = ExpressionValue::RValue(cast_ptr); + + Ok(cast_value) } } diff --git a/src/codegen/llvm_index.rs b/src/codegen/llvm_index.rs index 42a774adb1..10c5a3e224 100644 --- a/src/codegen/llvm_index.rs +++ b/src/codegen/llvm_index.rs @@ -1,6 +1,7 @@ // Copyright (c) 2020 Ghaith Hachem and Mathias Rieder -use inkwell::types::BasicTypeEnum; -use inkwell::values::{BasicValueEnum, FunctionValue, GlobalValue, PointerValue}; +use inkwell::types::{AnyTypeEnum, BasicType, BasicTypeEnum, PointerType}; +use inkwell::values::{AnyValue, BasicValueEnum, FunctionValue, GlobalValue, PointerValue}; +use inkwell::AddressSpace; use plc_diagnostics::diagnostics::Diagnostic; use plc_source::source_location::SourceLocation; use plc_util::convention::qualified_name; @@ -11,8 +12,8 @@ use rustc_hash::FxHashMap; #[derive(Debug, Clone, Default)] pub struct LlvmTypedIndex<'ink> { parent_index: Option<&'ink LlvmTypedIndex<'ink>>, - type_associations: FxHashMap>, - pou_type_associations: FxHashMap>, + type_associations: FxHashMap>, + pou_type_associations: FxHashMap>, global_values: FxHashMap>, got_indices: FxHashMap, initial_value_associations: FxHashMap>, @@ -23,6 +24,41 @@ pub struct LlvmTypedIndex<'ink> { utf16_literals: FxHashMap>, } +pub trait TypeHelper<'ink> { + #[allow(clippy::wrong_self_convention)] + fn as_basic_type(self) -> Option>; + + fn create_ptr_type(&self, address_space: AddressSpace) -> PointerType<'ink>; +} + +impl<'ink> TypeHelper<'ink> for AnyTypeEnum<'ink> { + fn as_basic_type(self) -> Option> { + match self { + AnyTypeEnum::ArrayType(value) => Some(value.as_basic_type_enum()), + AnyTypeEnum::FloatType(value) => Some(value.as_basic_type_enum()), + AnyTypeEnum::IntType(value) => Some(value.as_basic_type_enum()), + AnyTypeEnum::PointerType(value) => Some(value.as_basic_type_enum()), + AnyTypeEnum::StructType(value) => Some(value.as_basic_type_enum()), + AnyTypeEnum::VectorType(value) => Some(value.as_basic_type_enum()), + AnyTypeEnum::VoidType(_) => None, + AnyTypeEnum::FunctionType(_) => None, + } + } + + fn create_ptr_type(&self, address_space: AddressSpace) -> PointerType<'ink> { + match self { + AnyTypeEnum::ArrayType(value) => value.ptr_type(address_space), + AnyTypeEnum::FloatType(value) => value.ptr_type(address_space), + AnyTypeEnum::IntType(value) => value.ptr_type(address_space), + AnyTypeEnum::PointerType(value) => value.ptr_type(address_space), + AnyTypeEnum::StructType(value) => value.ptr_type(address_space), + AnyTypeEnum::VectorType(value) => value.ptr_type(address_space), + AnyTypeEnum::FunctionType(value) => value.ptr_type(address_space), + AnyTypeEnum::VoidType(_) => unreachable!("Void type cannot be converted to pointer"), + } + } +} + impl<'ink> LlvmTypedIndex<'ink> { pub fn create_child(parent: &'ink LlvmTypedIndex<'ink>) -> LlvmTypedIndex<'ink> { LlvmTypedIndex { @@ -73,18 +109,24 @@ impl<'ink> LlvmTypedIndex<'ink> { pub fn associate_type( &mut self, type_name: &str, - target_type: BasicTypeEnum<'ink>, + target_type: AnyTypeEnum<'ink>, ) -> Result<(), Diagnostic> { - self.type_associations.insert(type_name.to_lowercase(), target_type); + let name = type_name.to_lowercase(); + + log::debug!("registered `{name}` as type `{}`", target_type.print_to_string()); + self.type_associations.insert(name, target_type); Ok(()) } pub fn associate_pou_type( &mut self, type_name: &str, - target_type: BasicTypeEnum<'ink>, + target_type: AnyTypeEnum<'ink>, ) -> Result<(), Diagnostic> { - self.pou_type_associations.insert(type_name.to_lowercase(), target_type); + let name = type_name.to_lowercase(); + + log::debug!("registered `{name}` as POU type `{}`", target_type.print_to_string()); + self.pou_type_associations.insert(name, target_type); Ok(()) } @@ -93,7 +135,10 @@ impl<'ink> LlvmTypedIndex<'ink> { type_name: &str, initial_value: BasicValueEnum<'ink>, ) -> Result<(), Diagnostic> { - self.initial_value_associations.insert(type_name.to_lowercase(), initial_value); + let name = type_name.to_lowercase(); + + log::debug!("registered `{name}` as initial value type `{}`", initial_value.print_to_string()); + self.initial_value_associations.insert(name, initial_value); Ok(()) } @@ -103,8 +148,62 @@ impl<'ink> LlvmTypedIndex<'ink> { variable_name: &str, target_value: PointerValue<'ink>, ) -> Result<(), Diagnostic> { - let qualified_name = qualified_name(container_name, variable_name); - self.loaded_variable_associations.insert(qualified_name.to_lowercase(), target_value); + let name = qualified_name(container_name, variable_name).to_lowercase(); + + log::debug!("registered `{name}` as loaded local type `{}`", target_value.print_to_string()); + self.loaded_variable_associations.insert(name, target_value); + Ok(()) + } + + pub fn associate_global( + &mut self, + variable_name: &str, + global_variable: GlobalValue<'ink>, + ) -> Result<(), Diagnostic> { + let name = variable_name.to_lowercase(); + + log::debug!("registered `{name}` as global variable type `{}`", global_variable.print_to_string()); + self.global_values.insert(name.clone(), global_variable); + self.initial_value_associations.insert(name, global_variable.as_pointer_value().into()); + + // FIXME: Do we want to call .insert_new_got_index() here? + Ok(()) + } + + pub fn associate_implementation( + &mut self, + callable_name: &str, + function_value: FunctionValue<'ink>, + ) -> Result<(), Diagnostic> { + let name = callable_name.to_lowercase(); + + log::debug!("registered `{name}` as implementation type `{}`", function_value.print_to_string()); + self.implementations.insert(name, function_value); + + Ok(()) + } + + pub fn associate_utf08_literal(&mut self, literal: &str, literal_variable: GlobalValue<'ink>) { + log::debug!("registered literal {literal}"); + self.utf08_literals.insert(literal.to_string(), literal_variable); + } + + pub fn associate_utf16_literal(&mut self, literal: &str, literal_variable: GlobalValue<'ink>) { + log::debug!("registered literal {literal}"); + self.utf16_literals.insert(literal.to_string(), literal_variable); + } + + pub fn associate_got_index(&mut self, variable_name: &str, index: u64) -> Result<(), Diagnostic> { + let name = variable_name.to_lowercase(); + self.got_indices.insert(name, index); + + Ok(()) + } + + pub fn insert_new_got_index(&mut self, variable_name: &str) -> Result<(), Diagnostic> { + let idx = self.got_indices.values().max().copied().unwrap_or(0); + self.got_indices.insert(variable_name.to_lowercase(), idx); + Ok(()) } @@ -126,6 +225,7 @@ impl<'ink> LlvmTypedIndex<'ink> { self.type_associations .get(&type_name.to_lowercase()) .copied() + .and_then(TypeHelper::as_basic_type) .or_else(|| self.parent_index.and_then(|it| it.find_associated_type(type_name))) .or_else(|| self.find_associated_pou_type(type_name)) } @@ -134,6 +234,7 @@ impl<'ink> LlvmTypedIndex<'ink> { self.pou_type_associations .get(&type_name.to_lowercase()) .copied() + .and_then(TypeHelper::as_basic_type) .or_else(|| self.parent_index.and_then(|it| it.find_associated_pou_type(type_name))) } @@ -154,42 +255,6 @@ impl<'ink> LlvmTypedIndex<'ink> { .or_else(|| self.parent_index.and_then(|it| it.find_associated_initial_value(type_name))) } - pub fn associate_global( - &mut self, - variable_name: &str, - global_variable: GlobalValue<'ink>, - ) -> Result<(), Diagnostic> { - self.global_values.insert(variable_name.to_lowercase(), global_variable); - self.initial_value_associations - .insert(variable_name.to_lowercase(), global_variable.as_pointer_value().into()); - - // FIXME: Do we want to call .insert_new_got_index() here? - - Ok(()) - } - - pub fn associate_got_index(&mut self, variable_name: &str, index: u64) -> Result<(), Diagnostic> { - self.got_indices.insert(variable_name.to_lowercase(), index); - Ok(()) - } - - pub fn insert_new_got_index(&mut self, variable_name: &str) -> Result<(), Diagnostic> { - let idx = self.got_indices.values().max().copied().unwrap_or(0); - - self.got_indices.insert(variable_name.to_lowercase(), idx); - - Ok(()) - } - - pub fn associate_implementation( - &mut self, - callable_name: &str, - function_value: FunctionValue<'ink>, - ) -> Result<(), Diagnostic> { - self.implementations.insert(callable_name.to_lowercase(), function_value); - Ok(()) - } - pub fn find_associated_implementation(&self, callable_name: &str) -> Option> { self.implementations .get(&callable_name.to_lowercase()) @@ -222,20 +287,12 @@ impl<'ink> LlvmTypedIndex<'ink> { self.constants.get(qualified_name).copied() } - pub fn associate_utf08_literal(&mut self, literal: &str, literal_variable: GlobalValue<'ink>) { - self.utf08_literals.insert(literal.to_string(), literal_variable); - } - pub fn find_utf08_literal_string(&self, literal: &str) -> Option<&GlobalValue<'ink>> { self.utf08_literals .get(literal) .or_else(|| self.parent_index.and_then(|it| it.find_utf08_literal_string(literal))) } - pub fn associate_utf16_literal(&mut self, literal: &str, literal_variable: GlobalValue<'ink>) { - self.utf16_literals.insert(literal.to_string(), literal_variable); - } - pub fn find_utf16_literal_string(&self, literal: &str) -> Option<&GlobalValue<'ink>> { self.utf16_literals .get(literal) diff --git a/src/codegen/tests.rs b/src/codegen/tests.rs index 91b2b9154c..05fa305df8 100644 --- a/src/codegen/tests.rs +++ b/src/codegen/tests.rs @@ -7,6 +7,7 @@ mod constants_tests; mod debug_tests; mod directaccess_test; mod expression_tests; +mod fnptr; mod function_tests; mod generics_test; mod initialization_test; diff --git a/src/codegen/tests/fnptr.rs b/src/codegen/tests/fnptr.rs new file mode 100644 index 0000000000..699848318d --- /dev/null +++ b/src/codegen/tests/fnptr.rs @@ -0,0 +1,341 @@ +use crate::test_utils::tests::codegen; +use plc_util::filtered_assert_snapshot; + +#[test] +fn function_pointer_method_no_parameters() { + let result = codegen( + r" + FUNCTION_BLOCK A + METHOD foo + END_METHOD + END_FUNCTION_BLOCK + + FUNCTION main + VAR + instanceA: A; + fnPtr: POINTER TO A.foo := ADR(A.foo); + END_VAR + + fnPtr^(instanceA); + END_FUNCTION + ", + ); + + filtered_assert_snapshot!(result, @r#" + ; ModuleID = '' + source_filename = "" + target datalayout = "[filtered]" + target triple = "[filtered]" + + %A = type {} + + @__A__init = unnamed_addr constant %A zeroinitializer + + define void @A(%A* %0) { + entry: + %this = alloca %A*, align 8 + store %A* %0, %A** %this, align 8 + ret void + } + + define void @A__foo(%A* %0) { + entry: + %this = alloca %A*, align 8 + store %A* %0, %A** %this, align 8 + ret void + } + + define void @main() { + entry: + %instanceA = alloca %A, align 8 + %fnPtr = alloca void (%A*)*, align 8 + %0 = bitcast %A* %instanceA to i8* + call void @llvm.memcpy.p0i8.p0i8.i64(i8* align 1 %0, i8* align 1 bitcast (%A* @__A__init to i8*), i64 ptrtoint (%A* getelementptr (%A, %A* null, i32 1) to i64), i1 false) + store void (%A*)* @A__foo, void (%A*)** %fnPtr, align 8 + %1 = load void (%A*)*, void (%A*)** %fnPtr, align 8 + call void %1(%A* %instanceA) + ret void + } + + ; Function Attrs: argmemonly nofree nounwind willreturn + declare void @llvm.memcpy.p0i8.p0i8.i64(i8* noalias nocapture writeonly, i8* noalias nocapture readonly, i64, i1 immarg) #0 + + attributes #0 = { argmemonly nofree nounwind willreturn } + "#); +} + +#[test] +fn function_pointer_method_with_return_type() { + let result = codegen( + r" + FUNCTION_BLOCK A + METHOD foo: DINT + END_METHOD + END_FUNCTION_BLOCK + + FUNCTION main + VAR + instanceA: A; + fnPtr: POINTER TO A.foo := ADR(A.foo); + END_VAR + + fnPtr^(instanceA); + END_FUNCTION + ", + ); + + filtered_assert_snapshot!(result, @r#" + ; ModuleID = '' + source_filename = "" + target datalayout = "[filtered]" + target triple = "[filtered]" + + %A = type {} + + @__A__init = unnamed_addr constant %A zeroinitializer + + define void @A(%A* %0) { + entry: + %this = alloca %A*, align 8 + store %A* %0, %A** %this, align 8 + ret void + } + + define i32 @A__foo(%A* %0) { + entry: + %this = alloca %A*, align 8 + store %A* %0, %A** %this, align 8 + %A.foo = alloca i32, align 4 + store i32 0, i32* %A.foo, align 4 + %A__foo_ret = load i32, i32* %A.foo, align 4 + ret i32 %A__foo_ret + } + + define void @main() { + entry: + %instanceA = alloca %A, align 8 + %fnPtr = alloca i32 (%A*)*, align 8 + %0 = bitcast %A* %instanceA to i8* + call void @llvm.memcpy.p0i8.p0i8.i64(i8* align 1 %0, i8* align 1 bitcast (%A* @__A__init to i8*), i64 ptrtoint (%A* getelementptr (%A, %A* null, i32 1) to i64), i1 false) + store i32 (%A*)* @A__foo, i32 (%A*)** %fnPtr, align 8 + %1 = load i32 (%A*)*, i32 (%A*)** %fnPtr, align 8 + %fnptr_call = call i32 %1(%A* %instanceA) + ret void + } + + ; Function Attrs: argmemonly nofree nounwind willreturn + declare void @llvm.memcpy.p0i8.p0i8.i64(i8* noalias nocapture writeonly, i8* noalias nocapture readonly, i64, i1 immarg) #0 + + attributes #0 = { argmemonly nofree nounwind willreturn } + "#); +} + +#[test] +fn function_pointer_method_with_return_type_aggregate() { + let result = codegen( + r" + FUNCTION_BLOCK A + METHOD foo: STRING + foo := 'aaaaa'; + END_METHOD + + METHOD bar: ARRAY[1..5] OF DINT + bar := [1, 2, 3, 4, 5]; + END_METHOD + END_FUNCTION_BLOCK + + FUNCTION main + VAR + instanceA: A; + fnPtr: POINTER TO A.foo := ADR(A.foo); + fnPtr2: POINTER TO A.bar := ADR(A.bar); + END_VAR + + fnPtr^(instanceA); + fnPtr2^(instanceA); + END_FUNCTION + ", + ); + + filtered_assert_snapshot!(result, @r#" + ; ModuleID = '' + source_filename = "" + target datalayout = "[filtered]" + target triple = "[filtered]" + + %A = type {} + + @__A__init = unnamed_addr constant %A zeroinitializer + @utf08_literal_0 = private unnamed_addr constant [6 x i8] c"aaaaa\00" + + define void @A(%A* %0) { + entry: + %this = alloca %A*, align 8 + store %A* %0, %A** %this, align 8 + ret void + } + + define void @A__foo(%A* %0, i8* %1) { + entry: + %this = alloca %A*, align 8 + store %A* %0, %A** %this, align 8 + %foo = alloca i8*, align 8 + store i8* %1, i8** %foo, align 8 + %deref = load i8*, i8** %foo, align 8 + call void @llvm.memcpy.p0i8.p0i8.i32(i8* align 1 %deref, i8* align 1 getelementptr inbounds ([6 x i8], [6 x i8]* @utf08_literal_0, i32 0, i32 0), i32 6, i1 false) + ret void + } + + define void @A__bar(%A* %0, i32* %1) { + entry: + %this = alloca %A*, align 8 + store %A* %0, %A** %this, align 8 + %bar = alloca i32*, align 8 + store i32* %1, i32** %bar, align 8 + %deref = load i32*, i32** %bar, align 8 + store [5 x i32] [i32 1, i32 2, i32 3, i32 4, i32 5], i32* %deref, align 4 + ret void + } + + define void @main() { + entry: + %instanceA = alloca %A, align 8 + %fnPtr = alloca void (%A*, [81 x i8]*)*, align 8 + %fnPtr2 = alloca void (%A*, [5 x i32]*)*, align 8 + %0 = bitcast %A* %instanceA to i8* + call void @llvm.memcpy.p0i8.p0i8.i64(i8* align 1 %0, i8* align 1 bitcast (%A* @__A__init to i8*), i64 ptrtoint (%A* getelementptr (%A, %A* null, i32 1) to i64), i1 false) + store void (%A*, [81 x i8]*)* bitcast (void (%A*, i8*)* @A__foo to void (%A*, [81 x i8]*)*), void (%A*, [81 x i8]*)** %fnPtr, align 8 + store void (%A*, [5 x i32]*)* bitcast (void (%A*, i32*)* @A__bar to void (%A*, [5 x i32]*)*), void (%A*, [5 x i32]*)** %fnPtr2, align 8 + %__0 = alloca [81 x i8], align 1 + %1 = bitcast [81 x i8]* %__0 to i8* + call void @llvm.memset.p0i8.i64(i8* align 1 %1, i8 0, i64 ptrtoint ([81 x i8]* getelementptr ([81 x i8], [81 x i8]* null, i32 1) to i64), i1 false) + %2 = load void (%A*, [81 x i8]*)*, void (%A*, [81 x i8]*)** %fnPtr, align 8 + %3 = bitcast [81 x i8]* %__0 to i8* + call void %2(%A* %instanceA, i8* %3) + %load___0 = load [81 x i8], [81 x i8]* %__0, align 1 + %__1 = alloca [5 x i32], align 4 + %4 = bitcast [5 x i32]* %__1 to i8* + call void @llvm.memset.p0i8.i64(i8* align 1 %4, i8 0, i64 ptrtoint ([5 x i32]* getelementptr ([5 x i32], [5 x i32]* null, i32 1) to i64), i1 false) + %5 = load void (%A*, [5 x i32]*)*, void (%A*, [5 x i32]*)** %fnPtr2, align 8 + %6 = bitcast [5 x i32]* %__1 to i32* + call void %5(%A* %instanceA, i32* %6) + %load___1 = load [5 x i32], [5 x i32]* %__1, align 4 + ret void + } + + ; Function Attrs: argmemonly nofree nounwind willreturn + declare void @llvm.memcpy.p0i8.p0i8.i32(i8* noalias nocapture writeonly, i8* noalias nocapture readonly, i32, i1 immarg) #0 + + ; Function Attrs: argmemonly nofree nounwind willreturn + declare void @llvm.memcpy.p0i8.p0i8.i64(i8* noalias nocapture writeonly, i8* noalias nocapture readonly, i64, i1 immarg) #0 + + ; Function Attrs: argmemonly nofree nounwind willreturn writeonly + declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i1 immarg) #1 + + attributes #0 = { argmemonly nofree nounwind willreturn } + attributes #1 = { argmemonly nofree nounwind willreturn writeonly } + "#); +} + +#[test] +fn function_pointer_method_with_all_variable_parameter_types() { + let result = codegen( + r" + FUNCTION_BLOCK A + METHOD foo: DINT + VAR_INPUT + in: DINT; + END_VAR + + VAR_OUTPUT + out: STRING; + END_VAR + + VAR_IN_OUT + inout: ARRAY[1..5] OF DINT; + END_VAR + + END_METHOD + END_FUNCTION_BLOCK + + FUNCTION main + VAR + instanceA: A; + fnPtr: POINTER TO A.foo := ADR(A.foo); + localIn: DINT; + localOut: STRING; + localInOut: ARRAY[1..5] OF DINT; + END_VAR + + // fnPtr^(instanceA, localIn, localOut, localInOut); + fnPtr^(instanceA, out => localOut, inout := localInOut, in := localIn); // Arguments shifted by one to the right + END_FUNCTION + ", + ); + + filtered_assert_snapshot!(result, @r#" + ; ModuleID = '' + source_filename = "" + target datalayout = "[filtered]" + target triple = "[filtered]" + + %A = type {} + + @__A__init = unnamed_addr constant %A zeroinitializer + + define void @A(%A* %0) { + entry: + %this = alloca %A*, align 8 + store %A* %0, %A** %this, align 8 + ret void + } + + define i32 @A__foo(%A* %0, i32 %1, [81 x i8]* %2, i32* %3) { + entry: + %this = alloca %A*, align 8 + store %A* %0, %A** %this, align 8 + %A.foo = alloca i32, align 4 + %in = alloca i32, align 4 + store i32 %1, i32* %in, align 4 + %out = alloca [81 x i8]*, align 8 + store [81 x i8]* %2, [81 x i8]** %out, align 8 + %inout = alloca i32*, align 8 + store i32* %3, i32** %inout, align 8 + store i32 0, i32* %A.foo, align 4 + %A__foo_ret = load i32, i32* %A.foo, align 4 + ret i32 %A__foo_ret + } + + define void @main() { + entry: + %instanceA = alloca %A, align 8 + %fnPtr = alloca i32 (%A*, i32, [81 x i8]*, [5 x i32]*)*, align 8 + %localIn = alloca i32, align 4 + %localOut = alloca [81 x i8], align 1 + %localInOut = alloca [5 x i32], align 4 + %0 = bitcast %A* %instanceA to i8* + call void @llvm.memcpy.p0i8.p0i8.i64(i8* align 1 %0, i8* align 1 bitcast (%A* @__A__init to i8*), i64 ptrtoint (%A* getelementptr (%A, %A* null, i32 1) to i64), i1 false) + store i32 (%A*, i32, [81 x i8]*, [5 x i32]*)* bitcast (i32 (%A*, i32, [81 x i8]*, i32*)* @A__foo to i32 (%A*, i32, [81 x i8]*, [5 x i32]*)*), i32 (%A*, i32, [81 x i8]*, [5 x i32]*)** %fnPtr, align 8 + store i32 0, i32* %localIn, align 4 + %1 = bitcast [81 x i8]* %localOut to i8* + call void @llvm.memset.p0i8.i64(i8* align 1 %1, i8 0, i64 ptrtoint ([81 x i8]* getelementptr ([81 x i8], [81 x i8]* null, i32 1) to i64), i1 false) + %2 = bitcast [5 x i32]* %localInOut to i8* + call void @llvm.memset.p0i8.i64(i8* align 1 %2, i8 0, i64 ptrtoint ([5 x i32]* getelementptr ([5 x i32], [5 x i32]* null, i32 1) to i64), i1 false) + %3 = load i32 (%A*, i32, [81 x i8]*, [5 x i32]*)*, i32 (%A*, i32, [81 x i8]*, [5 x i32]*)** %fnPtr, align 8 + %4 = bitcast [81 x i8]* %localOut to i8* + %5 = bitcast [5 x i32]* %localInOut to i32* + %load_localIn = load i32, i32* %localIn, align 4 + %fnptr_call = call i32 %3(%A* %instanceA, i32 %load_localIn, i8* %4, i32* %5) + ret void + } + + ; Function Attrs: argmemonly nofree nounwind willreturn + declare void @llvm.memcpy.p0i8.p0i8.i64(i8* noalias nocapture writeonly, i8* noalias nocapture readonly, i64, i1 immarg) #0 + + ; Function Attrs: argmemonly nofree nounwind willreturn writeonly + declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i1 immarg) #1 + + attributes #0 = { argmemonly nofree nounwind willreturn } + attributes #1 = { argmemonly nofree nounwind willreturn writeonly } + "#); +} diff --git a/src/index.rs b/src/index.rs index 8ab3db4094..8066fee63b 100644 --- a/src/index.rs +++ b/src/index.rs @@ -1792,6 +1792,10 @@ impl Index { variable.and_then(|it| self.get_type(it.get_type_name()).ok()) } + pub fn get_return_type_or_void(&self, pou_name: &str) -> &DataType { + self.find_return_type(pou_name).unwrap_or(self.get_void_type()) + } + pub fn get_type_information_or_void(&self, type_name: &str) -> &DataTypeInformation { self.find_effective_type_by_name(type_name) .map(|it| it.get_type_information()) diff --git a/src/lowering/calls.rs b/src/lowering/calls.rs index a35dead426..0948d63d95 100644 --- a/src/lowering/calls.rs +++ b/src/lowering/calls.rs @@ -60,7 +60,10 @@ use plc_ast::{ }; use plc_source::source_location::SourceLocation; -use crate::{index::Index, resolver::AnnotationMap}; +use crate::{ + index::Index, + resolver::{AnnotationMap, StatementAnnotation}, +}; #[derive(Default, Debug, Clone)] struct VisitorContext { @@ -273,15 +276,16 @@ impl AstVisitorMut for AggregateTypeLowerer { return; }; //Get the function being called - let Some(crate::resolver::StatementAnnotation::Function { - qualified_name, - return_type: return_type_name, - generic_name, - .. - }) = annotation.get(&stmt.operator).or_else(|| annotation.get_hint(&stmt.operator)).cloned() - else { - return; - }; + let (qualified_name, return_type_name, generic_name) = + match annotation.get(&stmt.operator).or_else(|| annotation.get_hint(&stmt.operator)).cloned() { + Some(StatementAnnotation::Function { return_type, qualified_name, generic_name, .. }) => { + (qualified_name, return_type, generic_name) + } + Some(StatementAnnotation::FunctionPointer { return_type, qualified_name }) => { + (qualified_name, return_type, None) + } + _ => return, + }; //If there's a call name in the function, it is a generic and needs to be replaced. //HACK: this is because we don't lower generics let function_entry = index.find_pou(&qualified_name).expect("Function not found"); @@ -342,7 +346,14 @@ impl AstVisitorMut for AggregateTypeLowerer { let mut parameters = stmt.parameters.as_mut().map(|it| steal_expression_list(it.borrow_mut())).unwrap_or_default(); - parameters.insert(0, reference); + // When dealing with function pointers (which for now support only methods), then the alloca + // variable must be placed at index 1 rather than 0 because the instance variable must always be + // the first argument + if self.annotation.as_ref().unwrap().get(&stmt.operator).is_some_and(|opt| opt.is_fnptr()) { + parameters.insert(1, reference); + } else { + parameters.insert(0, reference); + } if is_generic_function { //For generic functions, we need to replace the generic name with the function name @@ -1198,4 +1209,41 @@ mod tests { let unit = &units.0; assert_debug_snapshot!(unit.implementations[1]); } + + #[test] + fn fnptr_no_argument() { + let id_provider = IdProvider::default(); + let (mut unit, index) = index_with_ids( + r#" + FUNCTION_BLOCK FbA + METHOD foo: STRING + END_METHOD + END_FUNCTION_BLOCK + + FUNCTION main + VAR + instanceFbA: FbA; + fooPtr: POINTER TO FbA.foo; + result: STRING; + END_VAR + + result := fooPtr^(instanceFbA); + END_FUNCTION + "#, + id_provider.clone(), + ); + + let mut lowerer = AggregateTypeLowerer { + index: Some(index), + annotation: None, + id_provider: id_provider.clone(), + ..Default::default() + }; + lowerer.visit_compilation_unit(&mut unit); + lowerer.index.replace(index_unit_with_id(&unit, id_provider.clone())); + let annotations = annotate_with_ids(&unit, lowerer.index.as_mut().unwrap(), id_provider.clone()); + lowerer.annotation.replace(Box::new(annotations)); + lowerer.visit_compilation_unit(&mut unit); + assert_debug_snapshot!(unit.implementations[2].statements[0]); + } } diff --git a/src/lowering/snapshots/rusty__lowering__calls__tests__fnptr_no_argument.snap b/src/lowering/snapshots/rusty__lowering__calls__tests__fnptr_no_argument.snap new file mode 100644 index 0000000000..0edd10b110 --- /dev/null +++ b/src/lowering/snapshots/rusty__lowering__calls__tests__fnptr_no_argument.snap @@ -0,0 +1,67 @@ +--- +source: src/lowering/calls.rs +expression: "unit.implementations[2].statements[0]" +--- +ExpressionList { + expressions: [ + Allocation { + name: "__0", + reference_type: "STRING", + }, + CallStatement { + operator: ReferenceExpr { + kind: Deref, + base: Some( + ReferenceExpr { + kind: Member( + Identifier { + name: "fooPtr", + }, + ), + base: None, + }, + ), + }, + parameters: Some( + ExpressionList { + expressions: [ + ReferenceExpr { + kind: Member( + Identifier { + name: "instanceFbA", + }, + ), + base: None, + }, + ReferenceExpr { + kind: Member( + Identifier { + name: "__0", + }, + ), + base: None, + }, + ], + }, + ), + }, + Assignment { + left: ReferenceExpr { + kind: Member( + Identifier { + name: "result", + }, + ), + base: None, + }, + right: ReferenceExpr { + kind: Member( + Identifier { + name: "__0", + }, + ), + base: None, + }, + }, + ], +} diff --git a/src/parser.rs b/src/parser.rs index ef8bb9a587..7060451424 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1007,8 +1007,13 @@ fn parse_type_reference_type_definition( name: Option, ) -> Option<(DataTypeDeclaration, Option)> { let start = lexer.range().start; - //Subrange - let referenced_type = lexer.slice_and_advance(); + + let mut referenced_type = lexer.slice_and_advance(); + + // REMOVE THIS BEFORE MERGE, ONLY USED FOR PROTOTYPE ITERATION (there will be no parsing of such expressions from the desugaring POV) + if lexer.try_consume(KeywordDot) { + referenced_type = format!("{referenced_type}.{}", lexer.slice_and_advance()); + } let bounds = if lexer.try_consume(KeywordParensOpen) { // INT (..) := diff --git a/src/resolver.rs b/src/resolver.rs index fb33cb12f5..0e8766f86e 100644 --- a/src/resolver.rs +++ b/src/resolver.rs @@ -12,7 +12,7 @@ use plc_ast::{ ast::{ self, flatten_expression_list, Allocation, Assignment, AstFactory, AstId, AstNode, AstStatement, BinaryExpression, CompilationUnit, DataType, DataTypeDeclaration, DirectAccessType, Identifier, - Interface, JumpStatement, Operator, Pou, ReferenceAccess, ReferenceExpr, TypeNature, + Interface, JumpStatement, Operator, Pou, PouType, ReferenceAccess, ReferenceExpr, TypeNature, UserTypeDeclaration, Variable, }, control_statements::{AstControlStatement, ReturnStatement}, @@ -307,7 +307,23 @@ impl TypeAnnotator<'_> { let mut generics_candidates: FxHashMap> = FxHashMap::default(); let mut params = vec![]; - let mut parameters = parameters.into_iter(); + let mut parameters = if self.annotation_map.get(operator).is_some_and(|opt| opt.is_fnptr()) { + // When dealing with a function pointer (which are only supported in the context of methods), the + // first argument will be a instance of the POU, e.g. `fnPtrToMyFbEcho^(instanceFb)`, hence we + // must skip the first argument as otherwise the remainig arguments will receive an incorrect type + // hint. Again, for example assume we have `fnPtrToMyFbEcho^(instanceFb, 'stringValue', 5)` and + // we do not skip the first argument. Then, `instanceFB` will have a type-hint of "STRING" and + // `stringValue` will have a type-hint on `DINT`. This then results in an error in the codegen. + // This is super hacky and ugly imo, but because function pointers are internal only constructs I + // think it should be fine (kinda). In a perfect world we would desugar method calls such as + // `instanceFb.echo('stringValue', 5)` into `echo(instanceFb, 'stringValue', 5)` and update the + // declared parameters to also include the instance parameter. As a positive side-effect it would + // result in us not distinguishing between functions and methods in the codegen (though that + // currently is not a big deal) but also you not reading this comment :^) + parameters[1..].iter() + } else { + parameters.iter() + }; // If we are dealing with an action call statement, we need to get the declared parameters from the parent POU in order // to annotate them with the correct type hint. @@ -474,6 +490,20 @@ pub enum StatementAnnotation { /// The call name of the function iff it differs from the qualified name (generics) call_name: Option, }, + /// A pointer to a function, mostly needed for polymorphism where function calls are handled indirectly + /// with a virtual table. + FunctionPointer { + /// The return type name of the function pointer + return_type: String, + + // XXX: In classical function pointers such information is not neccessary, e.g. in C you would have + // ` (*)()`, however in ST I + // __think__ it might be neccessary because of named arguments? Obviously this limits the use + // case of function pointers because you'd always reference a concrete function rather than some + // generic function such as `DINT(STRING, INT)`. + /// The name of the referenced function, e.g. `MyFb.myMethod` in `POINTER TO MyFb.MyMethod := ADR(...)` + qualified_name: String, + }, /// a reference to a type (e.g. `INT`) Type { type_name: String, @@ -676,6 +706,18 @@ impl StatementAnnotation { StatementAnnotation::Value { resulting_type: type_name.into() } } + /// Constructs a new [`StatementAnnotation::FunctionPointer`] with the qualified and return type name + pub fn fnptr(qualified_name: T, return_type_name: U) -> Self + where + T: Into, + U: Into, + { + StatementAnnotation::FunctionPointer { + return_type: return_type_name.into(), + qualified_name: qualified_name.into(), + } + } + pub fn create_override(definitions: Vec) -> Self { StatementAnnotation::Override { definitions } } @@ -725,6 +767,7 @@ impl StatementAnnotation { match self { StatementAnnotation::Variable { qualified_name, .. } | StatementAnnotation::Function { qualified_name, .. } + | StatementAnnotation::FunctionPointer { qualified_name, .. } | StatementAnnotation::Program { qualified_name } => Some(qualified_name.as_str()), _ => None, @@ -751,6 +794,10 @@ impl StatementAnnotation { _ => None, } } + + pub fn is_fnptr(&self) -> bool { + matches!(self, StatementAnnotation::FunctionPointer { .. }) + } } impl From<&PouIndexEntry> for StatementAnnotation { @@ -847,11 +894,12 @@ pub trait AnnotationMap { .get_hint(statement) .or_else(|| self.get(statement)) .and_then(|it| self.get_type_name_for_annotation(it)), - StatementAnnotation::Program { qualified_name } - | StatementAnnotation::Super { name: qualified_name, .. } => Some(qualified_name.as_str()), StatementAnnotation::Type { type_name } => Some(type_name), - StatementAnnotation::Function { .. } - | StatementAnnotation::Label { .. } + StatementAnnotation::Program { qualified_name } + | StatementAnnotation::Super { name: qualified_name, .. } + | StatementAnnotation::Function { qualified_name, .. } + | StatementAnnotation::FunctionPointer { qualified_name, .. } => Some(qualified_name), + StatementAnnotation::Label { .. } | StatementAnnotation::Override { .. } | StatementAnnotation::MethodDeclarations { .. } | StatementAnnotation::Property { .. } @@ -1547,15 +1595,22 @@ impl<'i> TypeAnnotator<'i> { }; if resolved_names.insert(Dependency::Datatype(datatype.get_name().to_string())) { match datatype.get_type_information() { - DataTypeInformation::Struct { members, .. } => { + DataTypeInformation::Struct { members, source, .. } => { for member in members { resolved_names = self.get_datatype_dependencies(member.get_type_name(), resolved_names); } + + if let StructSource::Pou(PouType::Method { parent, .. }) = source { + resolved_names = self.get_datatype_dependencies(parent, resolved_names); + } + resolved_names } DataTypeInformation::Array { inner_type_name, .. } | DataTypeInformation::Pointer { inner_type_name, .. } => { + resolved_names + .insert(Dependency::Datatype(datatype.get_type_information().get_name().to_string())); self.get_datatype_dependencies(inner_type_name, resolved_names) } _ => { @@ -2020,6 +2075,17 @@ impl<'i> TypeAnnotator<'i> { .find_effective_type_by_name(inner_type_name) .or(self.annotation_map.new_index.find_effective_type_by_name(inner_type_name)) { + // We might be dealing with a function pointer, e.g. `ptr^(...)` + if let Some(pou) = self.index.find_pou(&inner_type.name) { + if pou.is_method() { + let name = pou.get_name(); + let return_type = pou.get_return_type().unwrap(); + + self.annotate(stmt, StatementAnnotation::fnptr(name, return_type)); + return; + } + } + self.annotate(stmt, StatementAnnotation::value(inner_type.get_name())) } } @@ -2059,6 +2125,11 @@ impl<'i> TypeAnnotator<'i> { .iter() .find_map(|scope| scope.resolve_name(name, qualifier, self.index, ctx, &self.scopes)), + AstStatement::ReferenceExpr(_) => { + self.visit_statement(ctx, reference); + self.annotation_map.get(reference).cloned() + } + AstStatement::Literal(..) => { self.visit_statement_literals(ctx, reference); let literal_annotation = self.annotation_map.get(reference).cloned(); // return what we just annotated //TODO not elegant, we need to clone @@ -2082,12 +2153,15 @@ impl<'i> TypeAnnotator<'i> { self.visit_statement(ctx, data.index.as_ref()); Some(StatementAnnotation::value(get_direct_access_type(&data.access))) } + AstStatement::HardwareAccess(data, ..) => { let name = data.get_mangled_variable_name(); ctx.resolve_strategy.iter().find_map(|strategy| { strategy.resolve_name(&name, qualifier, self.index, ctx, &self.scopes) }) } + + AstStatement::ParenExpression(expr) => self.resolve_reference_expression(expr, qualifier, ctx), _ => None, } } @@ -2172,22 +2246,29 @@ impl<'i> TypeAnnotator<'i> { self.annotate_call_statement(operator, parameters_stmt, &ctx); }; - if let Some(StatementAnnotation::Function { return_type, .. }) = self.annotation_map.get(operator) { - if let Some(return_type) = self - .index - .find_effective_type_by_name(return_type) - .or_else(|| self.annotation_map.new_index.find_effective_type_by_name(return_type)) - { - if let Some(StatementAnnotation::ReplacementAst { .. }) = self.annotation_map.get(statement) { - // if we have a replacement ast, we do not need to annotate the function return type as it would - // overwrite the replacement ast - return; + match self.annotation_map.get(operator) { + Some(StatementAnnotation::Function { return_type, .. }) + | Some(StatementAnnotation::FunctionPointer { return_type, .. }) => { + if let Some(return_type) = self + .index + .find_effective_type_by_name(return_type) + .or_else(|| self.annotation_map.new_index.find_effective_type_by_name(return_type)) + { + if let Some(StatementAnnotation::ReplacementAst { .. }) = + self.annotation_map.get(statement) + { + // if we have a replacement ast, we do not need to annotate the function return type as it would + // overwrite the replacement ast + return; + } + self.annotate(statement, StatementAnnotation::value(return_type.get_name())); + } else { + // Assuming this is a VOID function if no annotation is present + self.annotate(statement, StatementAnnotation::value(VOID_INTERNAL_NAME)); } - self.annotate(statement, StatementAnnotation::value(return_type.get_name())); - } else { - // Assuming this is a VOID function if no annotation is present - self.annotate(statement, StatementAnnotation::value(VOID_INTERNAL_NAME)); } + + _ => (), } } @@ -2199,6 +2280,9 @@ impl<'i> TypeAnnotator<'i> { StatementAnnotation::Function { qualified_name, call_name, .. } => { call_name.as_ref().cloned().or_else(|| Some(qualified_name.clone())) } + StatementAnnotation::FunctionPointer { qualified_name, .. } => { + Some(qualified_name.clone()) + } StatementAnnotation::Program { qualified_name } => Some(qualified_name.clone()), StatementAnnotation::Variable { resulting_type, .. } => { self.index diff --git a/src/resolver/tests.rs b/src/resolver/tests.rs index d8d59204b5..187e23449c 100644 --- a/src/resolver/tests.rs +++ b/src/resolver/tests.rs @@ -1,4 +1,5 @@ mod const_resolver_tests; +mod fnptr; mod lowering; mod resolve_and_lower_init_functions; mod resolve_config_variables; diff --git a/src/resolver/tests/fnptr.rs b/src/resolver/tests/fnptr.rs new file mode 100644 index 0000000000..7eb73b7678 --- /dev/null +++ b/src/resolver/tests/fnptr.rs @@ -0,0 +1,629 @@ +use plc_ast::{ + ast::{Assignment, AstStatement, CallStatement, ReferenceAccess, ReferenceExpr}, + provider::IdProvider, +}; + +use crate::{ + resolver::{AnnotationMap, TypeAnnotator}, + test_utils::tests::index_with_ids, +}; + +#[test] +fn function_pointer_method_with_no_arguments() { + let ids = IdProvider::default(); + let (unit, index) = index_with_ids( + r" + FUNCTION_BLOCK Fb + METHOD echo: DINT + END_METHOD + END_FUNCTION_BLOCK + + FUNCTION main + VAR + instanceFb: Fb; + echoPtr: POINTER TO Fb.echo := ADR(Fb.echo); + END_VAR + + echoPtr^(instanceFb); + END_FUNCTION + ", + ids.clone(), + ); + + let (annotations, ..) = TypeAnnotator::visit_unit(&index, &unit, ids); + { + let node = &unit.implementations.iter().find(|imp| imp.name == "main").unwrap().statements[0]; + let AstStatement::CallStatement(CallStatement { operator, .. }) = node.get_stmt() else { + unreachable!(); + }; + + // echoPtr^(); + // ^^^^^^^^ + insta::assert_debug_snapshot!(annotations.get(operator), @r#" + Some( + FunctionPointer { + return_type: "DINT", + qualified_name: "Fb.echo", + }, + ) + "#); + + // echoPtr^(); + // ^^^^^^^^^^ + insta::assert_debug_snapshot!(annotations.get(node), @r#" + Some( + Value { + resulting_type: "DINT", + }, + ) + "#); + } +} + +#[test] +fn function_pointer_method_with_arguments() { + let ids = IdProvider::default(); + let (unit, index) = index_with_ids( + r" + FUNCTION_BLOCK Fb + METHOD echo: DINT + VAR_INPUT + in: DINT; + END_VAR + + VAR_OUTPUT + out: DINT; + END_VAR + + VAR_IN_OUT + inout: DINT; + END_VAR + END_METHOD + END_FUNCTION_BLOCK + + FUNCTION main + VAR + echoPtr: POINTER TO Fb.echo := ADR(Fb.echo); + localIn, localOut, localInOut: DINT; + instanceFb: Fb; + END_VAR + + echoPtr^(instanceFb); + echoPtr^(instanceFb, localIn, localOut, localInOut); + echoPtr^(instanceFb, in := localIn, out => localOut, inout := localInOut); + END_FUNCTION + ", + ids.clone(), + ); + + let (annotations, ..) = TypeAnnotator::visit_unit(&index, &unit, ids); + let statements = &unit.implementations.iter().find(|imp| imp.name == "main").unwrap().statements; + + // echoPtr^(); + { + let AstStatement::CallStatement(CallStatement { operator, .. }) = statements[0].get_stmt() else { + unreachable!(); + }; + + // echoPtr^(); + // ^^^^^^^^ + insta::assert_debug_snapshot!(annotations.get(operator), @r#" + Some( + FunctionPointer { + return_type: "DINT", + qualified_name: "Fb.echo", + }, + ) + "#); + + // echoPtr^(); + // ^^^^^^^^^^ + insta::assert_debug_snapshot!(annotations.get(&statements[0]), @r#" + Some( + Value { + resulting_type: "DINT", + }, + ) + "#); + } + + // echoPtr^(instanceFb, localIn, localOut, localInOut); + { + let AstStatement::CallStatement(CallStatement { operator, parameters: Some(parameters) }) = + statements[1].get_stmt() + else { + unreachable!(); + }; + + // echoPtr^(instanceFb, localIn, localOut, localInOut); + // ^^^^^^^^ + insta::assert_debug_snapshot!(annotations.get(operator), @r#" + Some( + FunctionPointer { + return_type: "DINT", + qualified_name: "Fb.echo", + }, + ) + "#); + + // echoPtr^(instanceFb, localIn, localOut, localInOut); + // ^^^^^^^ + let AstStatement::ExpressionList(arguments) = ¶meters.stmt else { + unreachable!(); + }; + + insta::assert_debug_snapshot!(annotations.get(&arguments[1]), @r#" + Some( + Variable { + resulting_type: "DINT", + qualified_name: "main.localIn", + constant: false, + argument_type: ByVal( + Local, + ), + auto_deref: None, + }, + ) + "#); + + // echoPtr^(instanceFb, localIn, localOut, localInOut); + // ^^^^^^^^ + let AstStatement::ExpressionList(arguments) = ¶meters.stmt else { + unreachable!(); + }; + + insta::assert_debug_snapshot!(annotations.get(&arguments[2]), @r#" + Some( + Variable { + resulting_type: "DINT", + qualified_name: "main.localOut", + constant: false, + argument_type: ByVal( + Local, + ), + auto_deref: None, + }, + ) + "#); + + // echoPtr^(instanceFb, localIn, localOut, localInOut); + // ^^^^^^^^^^ + let AstStatement::ExpressionList(arguments) = ¶meters.stmt else { + unreachable!(); + }; + + insta::assert_debug_snapshot!(annotations.get(&arguments[3]), @r#" + Some( + Variable { + resulting_type: "DINT", + qualified_name: "main.localInOut", + constant: false, + argument_type: ByVal( + Local, + ), + auto_deref: None, + }, + ) + "#); + + // echoPtr^(instanceFb, localIn, localOut, localInOut); + // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + insta::assert_debug_snapshot!(annotations.get(&statements[1]), @r#" + Some( + Value { + resulting_type: "DINT", + }, + ) + "#); + } + + // echoPtr^(instanceFb, in := localIn, out => localOut, inout := localInOut); + { + let AstStatement::CallStatement(CallStatement { operator, parameters: Some(parameters) }) = + statements[2].get_stmt() + else { + unreachable!(); + }; + + // echoPtr^(instanceFb, in := localIn, out => localOut, inout := localInOut); + // ^^^^^^^^ + insta::assert_debug_snapshot!(annotations.get(operator), @r#" + Some( + FunctionPointer { + return_type: "DINT", + qualified_name: "Fb.echo", + }, + ) + "#); + + // echoPtr^(instanceFb, in := localIn, out => localOut, inout := localInOut); + // ^^^^^^^^^^^^^ + let AstStatement::ExpressionList(arguments) = ¶meters.stmt else { + unreachable!(); + }; + + let AstStatement::Assignment(Assignment { left, right }) = &arguments[1].stmt else { + unreachable!(); + }; + insta::assert_debug_snapshot!(annotations.get(&arguments[1]), @"None"); + insta::assert_debug_snapshot!(annotations.get(&left), @r#" + Some( + Variable { + resulting_type: "DINT", + qualified_name: "Fb.echo.in", + constant: false, + argument_type: ByVal( + Input, + ), + auto_deref: None, + }, + ) + "#); + insta::assert_debug_snapshot!(annotations.get(&right), @r#" + Some( + Variable { + resulting_type: "DINT", + qualified_name: "main.localIn", + constant: false, + argument_type: ByVal( + Local, + ), + auto_deref: None, + }, + ) + "#); + + // echoPtr^(instanceFb, in := localIn, out => localOut, inout := localInOut); + // ^^^^^^^^^^^^^^^ + let AstStatement::ExpressionList(arguments) = ¶meters.stmt else { + unreachable!(); + }; + + let AstStatement::OutputAssignment(Assignment { left, right }) = &arguments[2].stmt else { + unreachable!(); + }; + insta::assert_debug_snapshot!(annotations.get(&arguments[2]), @"None"); + insta::assert_debug_snapshot!(annotations.get(&left), @r#" + Some( + Variable { + resulting_type: "DINT", + qualified_name: "Fb.echo.out", + constant: false, + argument_type: ByRef( + Output, + ), + auto_deref: Some( + Default, + ), + }, + ) + "#); + insta::assert_debug_snapshot!(annotations.get(&right), @r#" + Some( + Variable { + resulting_type: "DINT", + qualified_name: "main.localOut", + constant: false, + argument_type: ByVal( + Local, + ), + auto_deref: None, + }, + ) + "#); + + // echoPtr^(instanceFb, in := localIn, out => localOut, inout := localInOut); + // ^^^^^^^^^^^^^^^^^^^ + let AstStatement::ExpressionList(arguments) = ¶meters.stmt else { + unreachable!(); + }; + + let AstStatement::Assignment(Assignment { left, right }) = &arguments[3].stmt else { + unreachable!(); + }; + insta::assert_debug_snapshot!(annotations.get(&arguments[3]), @"None"); + insta::assert_debug_snapshot!(annotations.get(&left), @r#" + Some( + Variable { + resulting_type: "DINT", + qualified_name: "Fb.echo.inout", + constant: false, + argument_type: ByRef( + InOut, + ), + auto_deref: Some( + Default, + ), + }, + ) + "#); + insta::assert_debug_snapshot!(annotations.get(&right), @r#" + Some( + Variable { + resulting_type: "DINT", + qualified_name: "main.localInOut", + constant: false, + argument_type: ByVal( + Local, + ), + auto_deref: None, + }, + ) + "#); + } +} + +#[test] +fn void_pointer_casting() { + let id_provider = IdProvider::default(); + let (unit, index) = index_with_ids( + r" + VAR_GLOBAL + vtable_FbA_instance: vtable_FbA; + END_VAR + + TYPE vtable_FbA: STRUCT + foo: POINTER TO FbA.foo := ADR(FbA.foo); + END_STRUCT END_TYPE + + FUNCTION_BLOCK FbA + VAR + __vtable: POINTER TO __VOID; + END_VAR + + METHOD foo: DINT + END_METHOD + END_FUNCTION_BLOCK + + FUNCTION main + VAR + instanceFbA: FbA; + END_VAR + + vtable_FbA#(instanceFbA.__vtable); + vtable_FbA#(instanceFbA.__vtable).foo^(instanceFbA); + END_FUNCTION + ", + id_provider.clone(), + ); + + let (annotations, ..) = TypeAnnotator::visit_unit(&index, &unit, id_provider); + { + let node = &unit.implementations.iter().find(|imp| imp.name == "main").unwrap().statements[0]; + + // vtable_FbA#(instanceFbA.__vtable) + // ^^^^^^^^^^^^^^^^^^^^ + let AstStatement::ReferenceExpr(ReferenceExpr { access: ReferenceAccess::Cast(target), .. }) = + node.get_stmt() + else { + unreachable!(); + }; + + insta::assert_debug_snapshot!(annotations.get(target), @r#" + Some( + Variable { + resulting_type: "__FbA___vtable", + qualified_name: "FbA.__vtable", + constant: false, + argument_type: ByVal( + Local, + ), + auto_deref: None, + }, + ) + "#); + insta::assert_debug_snapshot!(annotations.get_hint(target), @"None"); + + // vtable_FbA#(instanceFbA.__vtable) + // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + insta::assert_debug_snapshot!(annotations.get(node), @r#" + Some( + Value { + resulting_type: "vtable_FbA", + }, + ) + "#); + insta::assert_debug_snapshot!(annotations.get_hint(node), @"None"); + } + + { + let node = &unit.implementations.iter().find(|imp| imp.name == "main").unwrap().statements[1]; + + // vtable_FbA#(instanceFbA.__vtable).foo^(instanceFbA); + // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + let AstStatement::CallStatement(call) = node.get_stmt() else { + unreachable!(); + }; + + insta::assert_debug_snapshot!(annotations.get(&call.operator), @r#" + Some( + FunctionPointer { + return_type: "DINT", + qualified_name: "FbA.foo", + }, + ) + "#); + insta::assert_debug_snapshot!(annotations.get_hint(&call.operator), @"None"); + } + + { + let node = &unit.implementations.iter().find(|imp| imp.name == "main").unwrap().statements[1]; + + // vtable_FbA#(instanceFbA.__vtable).foo^(instanceFbA); + // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + insta::assert_debug_snapshot!(annotations.get(&node), @r#" + Some( + Value { + resulting_type: "DINT", + }, + ) + "#); + insta::assert_debug_snapshot!(annotations.get_hint(&node), @"None"); + } +} + +#[test] +fn function_pointer_arguments_have_correct_type_hint() { + let id_provider = IdProvider::default(); + let (unit, index) = index_with_ids( + r" + FUNCTION_BLOCK A + METHOD printArgs + VAR_INPUT + message: STRING; + value: DINT; + END_VAR + END_METHOD + END_FUNCTION_BLOCK + + FUNCTION main + VAR + instanceA: A; + printArgs: POINTER TO A.printArgs := ADR(A.printArgs); + END_VAR + + instanceA.printArgs('value =', 5); + printArgs^(instanceA, 'value =', 5); + END_FUNCTION + ", + id_provider.clone(), + ); + + let (annotations, ..) = TypeAnnotator::visit_unit(&index, &unit, id_provider); + + // instanceA.printArgs('value =', 5); + // ^^^^^^^^^ + { + let node = &unit.implementations.iter().find(|imp| imp.name == "main").unwrap().statements[0]; + + let AstStatement::CallStatement(CallStatement { parameters: Some(parameters), .. }) = &node.stmt + else { + unreachable!(); + }; + + let AstStatement::ExpressionList(arguments) = ¶meters.stmt else { + unreachable!(); + }; + + insta::assert_debug_snapshot!(&arguments[0], @r#" + LiteralString { + value: "value =", + is_wide: false, + } + "#); + + insta::assert_debug_snapshot!(annotations.get(&arguments[0]), @r#" + Some( + Value { + resulting_type: "__STRING_7", + }, + ) + "#); + + insta::assert_debug_snapshot!(annotations.get_type(&arguments[0], &index), @r#" + Some( + DataType { + name: "__STRING_7", + initial_value: None, + information: String { + size: LiteralInteger( + 8, + ), + encoding: Utf8, + }, + nature: String, + location: SourceLocation { + span: None, + }, + }, + ) + "#); + + insta::assert_debug_snapshot!(annotations.get_type_hint(&arguments[0], &index), @r#" + Some( + DataType { + name: "STRING", + initial_value: None, + information: String { + size: LiteralInteger( + 81, + ), + encoding: Utf8, + }, + nature: String, + location: SourceLocation { + span: None, + }, + }, + ) + "#); + } + + // printArgs^(instanceA, 'value =', 5); + // ^^^^^^^^^ + { + let node = &unit.implementations.iter().find(|imp| imp.name == "main").unwrap().statements[1]; + + let AstStatement::CallStatement(CallStatement { parameters: Some(parameters), .. }) = &node.stmt + else { + unreachable!(); + }; + + let AstStatement::ExpressionList(arguments) = ¶meters.stmt else { + unreachable!(); + }; + + insta::assert_debug_snapshot!(&arguments[1], @r#" + LiteralString { + value: "value =", + is_wide: false, + } + "#); + + insta::assert_debug_snapshot!(annotations.get(&arguments[1]), @r#" + Some( + Value { + resulting_type: "__STRING_7", + }, + ) + "#); + + insta::assert_debug_snapshot!(annotations.get_type(&arguments[1], &index), @r#" + Some( + DataType { + name: "__STRING_7", + initial_value: None, + information: String { + size: LiteralInteger( + 8, + ), + encoding: Utf8, + }, + nature: String, + location: SourceLocation { + span: None, + }, + }, + ) + "#); + + insta::assert_debug_snapshot!(annotations.get_type_hint(&arguments[1], &index), @r#" + Some( + DataType { + name: "STRING", + initial_value: None, + information: String { + size: LiteralInteger( + 81, + ), + encoding: Utf8, + }, + nature: String, + location: SourceLocation { + span: None, + }, + }, + ) + "#); + } +} diff --git a/src/resolver/tests/resolve_expressions_tests.rs b/src/resolver/tests/resolve_expressions_tests.rs index ae439c2dde..4b9198de98 100644 --- a/src/resolver/tests/resolve_expressions_tests.rs +++ b/src/resolver/tests/resolve_expressions_tests.rs @@ -1121,7 +1121,7 @@ fn pou_expressions_resolve_types() { let statements = &unit.implementations[3].statements; //Functions and Functionblocks should not resolve to a type - let expected_types = vec!["OtherPrg", VOID_TYPE, "OtherFuncBlock"]; + let expected_types = vec!["OtherPrg", "OtherFunc", "OtherFuncBlock"]; let type_names: Vec<&str> = statements.iter().map(|s| annotations.get_type_or_void(s, &index).get_name()).collect(); assert_eq!(format!("{expected_types:?}"), format!("{type_names:?}")); @@ -1288,35 +1288,75 @@ fn function_expression_resolves_to_the_function_itself_not_its_return_type() { //WHEN the AST is annotated let (annotations, ..) = TypeAnnotator::visit_unit(&index, &unit, id_provider); - let statements = &unit.implementations[1].statements; - - // THEN we expect it to be annotated with the function itself - let foo_annotation = annotations.get(&statements[0]); - assert_eq!( - Some(&StatementAnnotation::Function { - qualified_name: "foo".into(), - return_type: "INT".into(), - generic_name: None, - call_name: None, - }), - foo_annotation - ); - // AND we expect no type to be associated with the expression - let associated_type = annotations.get_type(&statements[0], &index); - assert_eq!(None, associated_type); - let statements = &unit.implementations[0].statements; - let foo_annotation = annotations.get(&statements[0]); - assert_eq!( - Some(&StatementAnnotation::Variable { - qualified_name: "foo.foo".into(), - resulting_type: "INT".into(), + insta::assert_debug_snapshot!(annotations.get(&unit.implementations[0].statements[0]), @r#" + Some( + Variable { + resulting_type: "INT", + qualified_name: "foo.foo", constant: false, - argument_type: ArgumentType::ByVal(VariableType::Return), + argument_type: ByVal( + Return, + ), auto_deref: None, - }), - foo_annotation - ); + }, + ) + "#); + + insta::assert_debug_snapshot!(annotations.get(&unit.implementations[1].statements[0]), @r#" + Some( + Function { + return_type: "INT", + qualified_name: "foo", + generic_name: None, + call_name: None, + }, + ) + "#); + insta::assert_debug_snapshot!(annotations.get_type(&unit.implementations[1].statements[0], &index), @r#" + Some( + DataType { + name: "foo", + initial_value: None, + information: Struct { + name: "foo", + members: [ + VariableIndexEntry { + name: "foo", + qualified_name: "foo.foo", + initial_value: None, + argument_type: ByVal( + Return, + ), + is_constant: false, + is_var_external: false, + data_type_name: "INT", + location_in_parent: 0, + linkage: Internal, + binding: None, + source_location: SourceLocation { + span: Range(1:17 - 1:20), + file: Some( + "", + ), + }, + varargs: None, + }, + ], + source: Pou( + Function, + ), + }, + nature: Any, + location: SourceLocation { + span: Range(1:17 - 1:20), + file: Some( + "", + ), + }, + }, + ) + "#); } #[test] @@ -1613,7 +1653,7 @@ fn function_parameter_assignments_resolve_types() { &statements[0] { //make sure the call's operator resolved correctly - assert_eq!(annotations.get_type_or_void(operator, &index).get_name(), VOID_TYPE); + assert_eq!(annotations.get_type_or_void(operator, &index).get_name(), "foo"); assert_eq!( annotations.get(operator), Some(&StatementAnnotation::Function { diff --git a/src/validation/tests/pou_validation_tests.rs b/src/validation/tests/pou_validation_tests.rs index c305becf23..763e5a70d4 100644 --- a/src/validation/tests/pou_validation_tests.rs +++ b/src/validation/tests/pou_validation_tests.rs @@ -225,14 +225,19 @@ fn assigning_return_value_to_void_functions_returns_error() { ", ); - assert_snapshot!(diagnostics, @r###" + assert_snapshot!(diagnostics, @r" warning[E093]: Function declared as VOID, but trying to assign a return value ┌─ :3:9 │ 3 │ foo := 1; │ ^^^^^^^^ Function declared as VOID, but trying to assign a return value - "###); + error[E037]: Invalid assignment: cannot assign 'DINT' to 'foo' + ┌─ :3:9 + │ + 3 │ foo := 1; + │ ^^^^^^^^ Invalid assignment: cannot assign 'DINT' to 'foo' + "); } #[test] diff --git a/src/validation/tests/reference_resolve_tests.rs b/src/validation/tests/reference_resolve_tests.rs index ee6193bf1e..e181d0a38c 100644 --- a/src/validation/tests/reference_resolve_tests.rs +++ b/src/validation/tests/reference_resolve_tests.rs @@ -170,29 +170,6 @@ fn resolve_function_block_calls_in_structs_and_field_access() { assert_snapshot!(&diagnostics); } -/// tests wheter function's members cannot be access using the function's name as a qualifier -#[test] -fn resolve_function_members_via_qualifier() { - let diagnostics = parse_and_validate_buffered( - " - PROGRAM prg - foo(a := 1, b := 2, c := 3); (* ok *) - foo.a; (* not ok *) - foo.b; (* not ok *) - foo.c; (* not ok *) - END_PROGRAM - - FUNCTION foo : INT - VAR_INPUT - a,b,c : INT; - END_VAR - END_FUNCTION - ", - ); - - assert_snapshot!(&diagnostics); -} - /// tests whether references to privater variables do resolve, but end up in an validation problem #[test] fn reference_to_private_variable_is_illegal() { diff --git a/src/validation/tests/super_keyword_validation_tests.rs b/src/validation/tests/super_keyword_validation_tests.rs index 009b23b25a..06d3a31178 100644 --- a/src/validation/tests/super_keyword_validation_tests.rs +++ b/src/validation/tests/super_keyword_validation_tests.rs @@ -1268,6 +1268,12 @@ fn super_dereferencing_with_method_calls() { │ 19 │ x := SUPER.get_value(); // Trying to call method on pointer │ ^^^^^^^^^ `SUPER` must be dereferenced to access its members. + + error[E037]: Invalid assignment: cannot assign 'get_value' to 'parent' + ┌─ :20:17 + │ + 20 │ p2 := SUPER^.get_value; // Method call missing () + │ ^^^^^^^^^^^^^^^^^^^^^^ Invalid assignment: cannot assign 'get_value' to 'parent' "); } diff --git a/tests/lit/single/polymorphism/fnptr/method_call.st b/tests/lit/single/polymorphism/fnptr/method_call.st new file mode 100644 index 0000000000..e87a0aa42c --- /dev/null +++ b/tests/lit/single/polymorphism/fnptr/method_call.st @@ -0,0 +1,34 @@ +// RUN: (%COMPILE %s && %RUN) | %CHECK %s + +FUNCTION_BLOCK FbA + VAR + localState: DINT := 5; + END_VAR + + METHOD increaseStateByOne + localState := localState + 1; + printf('FbA::increaseStateByOne: localState = %d$N', localState); + END_METHOD + + METHOD increaseStateByArg + VAR_INPUT + in: DINT; + END_VAR + + localState := localState + in; + printf('FbA::increaseStateByArg: localState = %d$N', localState); + END_METHOD +END_FUNCTION_BLOCK + +FUNCTION main + VAR + instanceFbA: FbA; + fnPtrIncreaseStateByOne: POINTER TO FbA.increaseStateByOne := ADR(FbA.increaseStateByOne); + fnPtrIncreaseStateByArg: POINTER TO FbA.increaseStateByArg := ADR(FbA.increaseStateByArg); + END_VAR + + // CHECK: FbA::increaseStateByOne: localState = 6 + // CHECK: FbA::increaseStateByArg: localState = 10 + fnPtrIncreaseStateByOne^(instanceFbA); + fnPtrIncreaseStateByArg^(instanceFbA, 4); +END_FUNCTION \ No newline at end of file diff --git a/tests/lit/single/polymorphism/fnptr/method_call_aggregate_return.st b/tests/lit/single/polymorphism/fnptr/method_call_aggregate_return.st new file mode 100644 index 0000000000..559e9efce6 --- /dev/null +++ b/tests/lit/single/polymorphism/fnptr/method_call_aggregate_return.st @@ -0,0 +1,112 @@ +// RUN: (%COMPILE %s && %RUN) | %CHECK %s + +TYPE PositionWithExtraMetadata: + STRUCT + x: REAL; + y: REAL; + data: ARRAY[1..100] OF STRING; + END_STRUCT +END_TYPE + +FUNCTION_BLOCK FbA + VAR + localStruct: PositionWithExtraMetadata := (x := 1.0, y := 2.0, data := ['Data 1', 'Data 2', 'Data 3']); + localStringArray: ARRAY[1..3] OF STRING := ['Value 1', 'Value 2', 'Value 3']; + localStructArray: ARRAY[1..3] OF PositionWithExtraMetadata := [ + (x := 3.0, y := 4.0, data := ['Data 4', 'Data 5', 'Data 6']), + (x := 5.0, y := 6.0, data := ['Data 7', 'Data 8', 'Data 9']), + (x := 7.0, y := 8.0, data := ['Data 10', 'Data 11', 'Data 12']) + ]; + END_VAR + + METHOD returnString: STRING + returnString := 'FbA::returnString'; + END_METHOD + + METHOD returnStruct: PositionWithExtraMetadata + returnStruct := localStruct; + END_METHOD + + METHOD returnStringArray: ARRAY[1..3] OF STRING + returnStringArray := localStringArray; + END_METHOD + + METHOD returnStructArray: ARRAY[1..3] OF PositionWithExtraMetadata + returnStructArray := localStructArray; + END_METHOD +END_FUNCTION_BLOCK + +FUNCTION main + VAR + instanceA: FbA; + returnStringPtr: POINTER TO FbA.returnString := ADR(instanceA.returnString); + returnStructPtr: POINTER TO FbA.returnStruct := ADR(instanceA.returnStruct); + returnStringArrayPtr: POINTER TO FbA.returnStringArray := ADR(instanceA.returnStringArray); + returnStructArrayPtr: POINTER TO FbA.returnStructArray := ADR(instanceA.returnStructArray); + + localReturnString: STRING; + localReturnStruct: PositionWithExtraMetadata; + localReturnStringArray: ARRAY[1..3] OF STRING; + localReturnStructArray: ARRAY[1..3] OF PositionWithExtraMetadata; + END_VAR + + // localReturnString := instanceA.returnString(); + // CHECK: localReturnString = FbA::returnString + localReturnString := returnStringPtr^(instanceA); + printf('localReturnString = %s$N', ADR(localReturnString)); + + // localReturnStruct := instanceA.returnStruct(); + // CHECK: localReturnStruct.x = 1.000000 + // CHECK: localReturnStruct.y = 2.000000 + // CHECK: localReturnStruct.data[1] = Data 1 + // CHECK: localReturnStruct.data[2] = Data 2 + // CHECK: localReturnStruct.data[3] = Data 3 + localReturnStruct := returnStructPtr^(instanceA); + printf('localReturnStruct.x = %f$N', localReturnStruct.x); + printf('localReturnStruct.y = %f$N', localReturnStruct.y); + printf('localReturnStruct.data[1] = %s$N', ADR(localReturnStruct.data[1])); + printf('localReturnStruct.data[2] = %s$N', ADR(localReturnStruct.data[2])); + printf('localReturnStruct.data[3] = %s$N', ADR(localReturnStruct.data[3])); + + // localReturnStringArray := instanceA.returnStringArray(); + // CHECK: localReturnStringArray[1] = Value 1 + // CHECK: localReturnStringArray[2] = Value 2 + // CHECK: localReturnStringArray[3] = Value 3 + localReturnStringArray := returnStringArrayPtr^(instanceA); + printf('localReturnStringArray[1] = %s$N', ADR(localReturnStringArray[1])); + printf('localReturnStringArray[2] = %s$N', ADR(localReturnStringArray[2])); + printf('localReturnStringArray[3] = %s$N', ADR(localReturnStringArray[3])); + + // localReturnStructArray := instanceA.returnStructArray(); + // CHECK: localReturnStructArray[1].x = 3.000000 + // CHECK: localReturnStructArray[1].y = 4.000000 + // CHECK: localReturnStructArray[1].data[1] = Data 4 + // CHECK: localReturnStructArray[1].data[2] = Data 5 + // CHECK: localReturnStructArray[1].data[3] = Data 6 + // CHECK: localReturnStructArray[2].x = 5.000000 + // CHECK: localReturnStructArray[2].y = 6.000000 + // CHECK: localReturnStructArray[2].data[1] = Data 7 + // CHECK: localReturnStructArray[2].data[2] = Data 8 + // CHECK: localReturnStructArray[2].data[3] = Data 9 + // CHECK: localReturnStructArray[3].x = 7.000000 + // CHECK: localReturnStructArray[3].y = 8.000000 + // CHECK: localReturnStructArray[3].data[1] = Data 10 + // CHECK: localReturnStructArray[3].data[2] = Data 11 + // CHECK: localReturnStructArray[3].data[3] = Data 12 + localReturnStructArray := returnStructArrayPtr^(instanceA); + printf('localReturnStructArray[1].x = %f$N', localReturnStructArray[1].x); + printf('localReturnStructArray[1].y = %f$N', localReturnStructArray[1].y); + printf('localReturnStructArray[1].data[1] = %s$N', ADR(localReturnStructArray[1].data[1])); + printf('localReturnStructArray[1].data[2] = %s$N', ADR(localReturnStructArray[1].data[2])); + printf('localReturnStructArray[1].data[3] = %s$N', ADR(localReturnStructArray[1].data[3])); + printf('localReturnStructArray[2].x = %f$N', localReturnStructArray[2].x); + printf('localReturnStructArray[2].y = %f$N', localReturnStructArray[2].y); + printf('localReturnStructArray[2].data[1] = %s$N', ADR(localReturnStructArray[2].data[1])); + printf('localReturnStructArray[2].data[2] = %s$N', ADR(localReturnStructArray[2].data[2])); + printf('localReturnStructArray[2].data[3] = %s$N', ADR(localReturnStructArray[2].data[3])); + printf('localReturnStructArray[3].x = %f$N', localReturnStructArray[3].x); + printf('localReturnStructArray[3].y = %f$N', localReturnStructArray[3].y); + printf('localReturnStructArray[3].data[1] = %s$N', ADR(localReturnStructArray[3].data[1])); + printf('localReturnStructArray[3].data[2] = %s$N', ADR(localReturnStructArray[3].data[2])); + printf('localReturnStructArray[3].data[3] = %s$N', ADR(localReturnStructArray[3].data[3])); +END_FUNCTION \ No newline at end of file diff --git a/tests/lit/single/polymorphism/fnptr/method_call_aggregate_return_by_inout.st b/tests/lit/single/polymorphism/fnptr/method_call_aggregate_return_by_inout.st new file mode 100644 index 0000000000..43e4ad8f43 --- /dev/null +++ b/tests/lit/single/polymorphism/fnptr/method_call_aggregate_return_by_inout.st @@ -0,0 +1,65 @@ +// RUN: (%COMPILE %s && %RUN) | %CHECK %s + +TYPE PositionWithExtraMetadata: + STRUCT + x: REAL; + y: REAL; + data: ARRAY[1..100] OF STRING; + END_STRUCT +END_TYPE + +FUNCTION_BLOCK FbA + METHOD foo + VAR_OUTPUT + outString: STRING; + outStruct: PositionWithExtraMetadata; + outStructArray: ARRAY[1..5] OF PositionWithExtraMetadata; + END_VAR + + outString := 'FbA::foo result'; + outStruct.x := 1.0; + outStruct.y := 2.0; + outStruct.data[1] := 'Data 1'; + outStruct.data[2] := 'Data 2'; + outStruct.data[3] := 'Data 3'; + outStructArray[1].x := 3.0; + outStructArray[1].y := 4.0; + outStructArray[1].data[1] := 'Data 4'; + outStructArray[1].data[2] := 'Data 5'; + outStructArray[1].data[3] := 'Data 6'; + END_METHOD +END_FUNCTION_BLOCK + +FUNCTION main + VAR + fooPtr: POINTER TO FbA.foo := ADR(FbA.foo); + instanceA: FbA; + localOutString: STRING; + localOutStruct: PositionWithExtraMetadata; + localOutStructArray: ARRAY[1..5] OF PositionWithExtraMetadata; + END_VAR + + // CHECK: localOutString = FbA::foo result + // CHECK: localOutStruct.x = 1.000000 + // CHECK: localOutStruct.y = 2.000000 + // CHECK: localOutStruct.data[1] = Data 1 + // CHECK: localOutStruct.data[2] = Data 2 + // CHECK: localOutStruct.data[3] = Data 3 + // CHECK: localOutStructArray[1].x = 3.000000 + // CHECK: localOutStructArray[1].y = 4.000000 + // CHECK: localOutStructArray[1].data[1] = Data 4 + // CHECK: localOutStructArray[1].data[2] = Data 5 + // CHECK: localOutStructArray[1].data[3] = Data 6 + fooPtr^(instanceA, outStructArray => localOutStructArray, outString => localOutString, outStruct => localOutStruct ); + printf('localOutString = %s$N', ADR(localOutString)); + printf('localOutStruct.x = %f$N', localOutStruct.x); + printf('localOutStruct.y = %f$N', localOutStruct.y); + printf('localOutStruct.data[1] = %s$N', ADR(localOutStruct.data[1])); + printf('localOutStruct.data[2] = %s$N', ADR(localOutStruct.data[2])); + printf('localOutStruct.data[3] = %s$N', ADR(localOutStruct.data[3])); + printf('localOutStructArray[1].x = %f$N', localOutStructArray[1].x); + printf('localOutStructArray[1].y = %f$N', localOutStructArray[1].y); + printf('localOutStructArray[1].data[1] = %s$N', ADR(localOutStructArray[1].data[1])); + printf('localOutStructArray[1].data[2] = %s$N', ADR(localOutStructArray[1].data[2])); + printf('localOutStructArray[1].data[3] = %s$N', ADR(localOutStructArray[1].data[3])); +END_FUNCTION \ No newline at end of file diff --git a/tests/lit/single/polymorphism/fnptr/method_call_aggregate_return_by_output.st b/tests/lit/single/polymorphism/fnptr/method_call_aggregate_return_by_output.st new file mode 100644 index 0000000000..3a8d42255b --- /dev/null +++ b/tests/lit/single/polymorphism/fnptr/method_call_aggregate_return_by_output.st @@ -0,0 +1,76 @@ +// RUN: (%COMPILE %s && %RUN) | %CHECK %s + +TYPE PositionWithExtraMetadata: + STRUCT + x: REAL; + y: REAL; + data: ARRAY[1..100] OF STRING; + END_STRUCT +END_TYPE + +FUNCTION_BLOCK FbA + METHOD foo + VAR_OUTPUT + outString: STRING; + outStruct: PositionWithExtraMetadata; + outStringArray: ARRAY[1..5] OF STRING; + outStructArray: ARRAY[1..5] OF PositionWithExtraMetadata; + END_VAR + + outString := 'FbA::foo result'; + outStruct.x := 1.0; + outStruct.y := 2.0; + outStruct.data[1] := 'Data 1'; + outStruct.data[2] := 'Data 2'; + outStruct.data[3] := 'Data 3'; + outStringArray[1] := 'Data 4'; + outStringArray[2] := 'Data 5'; + outStringArray[3] := 'Data 6'; + outStructArray[1].x := 3.0; + outStructArray[1].y := 4.0; + outStructArray[1].data[1] := 'Data 7'; + outStructArray[1].data[2] := 'Data 8'; + outStructArray[1].data[3] := 'Data 9'; + END_METHOD +END_FUNCTION_BLOCK + +FUNCTION main + VAR + fooPtr: POINTER TO FbA.foo := ADR(FbA.foo); + instanceA: FbA; + localOutString: STRING; + localOutStruct: PositionWithExtraMetadata; + localOutStringArray: ARRAY[1..5] OF STRING; + localOutStructArray: ARRAY[1..5] OF PositionWithExtraMetadata; + END_VAR + + // CHECK: localOutString = FbA::foo result + // CHECK: localOutStruct.x = 1.000000 + // CHECK: localOutStruct.y = 2.000000 + // CHECK: localOutStruct.data[1] = Data 1 + // CHECK: localOutStruct.data[2] = Data 2 + // CHECK: localOutStruct.data[3] = Data 3 + // CHECK: localOutStringArray[1] = Data 4 + // CHECK: localOutStringArray[2] = Data 5 + // CHECK: localOutStringArray[3] = Data 6 + // CHECK: localOutStructArray[1].x = 3.000000 + // CHECK: localOutStructArray[1].y = 4.000000 + // CHECK: localOutStructArray[1].data[1] = Data 7 + // CHECK: localOutStructArray[1].data[2] = Data 8 + // CHECK: localOutStructArray[1].data[3] = Data 9 + fooPtr^(instanceA, outStructArray => localOutStructArray, outString => localOutString, outStruct => localOutStruct, outStringArray => localOutStringArray); + printf('localOutString = %s$N', ADR(localOutString)); + printf('localOutStruct.x = %f$N', localOutStruct.x); + printf('localOutStruct.y = %f$N', localOutStruct.y); + printf('localOutStruct.data[1] = %s$N', ADR(localOutStruct.data[1])); + printf('localOutStruct.data[2] = %s$N', ADR(localOutStruct.data[2])); + printf('localOutStruct.data[3] = %s$N', ADR(localOutStruct.data[3])); + printf('localOutStringArray[1] = %s$N', ADR(localOutStringArray[1])); + printf('localOutStringArray[2] = %s$N', ADR(localOutStringArray[2])); + printf('localOutStringArray[3] = %s$N', ADR(localOutStringArray[3])); + printf('localOutStructArray[1].x = %f$N', localOutStructArray[1].x); + printf('localOutStructArray[1].y = %f$N', localOutStructArray[1].y); + printf('localOutStructArray[1].data[1] = %s$N', ADR(localOutStructArray[1].data[1])); + printf('localOutStructArray[1].data[2] = %s$N', ADR(localOutStructArray[1].data[2])); + printf('localOutStructArray[1].data[3] = %s$N', ADR(localOutStructArray[1].data[3])); +END_FUNCTION \ No newline at end of file diff --git a/tests/lit/single/polymorphism/fnptr/method_call_by_void_pointer_cast.st b/tests/lit/single/polymorphism/fnptr/method_call_by_void_pointer_cast.st new file mode 100644 index 0000000000..0e143ba5aa --- /dev/null +++ b/tests/lit/single/polymorphism/fnptr/method_call_by_void_pointer_cast.st @@ -0,0 +1,37 @@ +// RUN: (%COMPILE %s && %RUN) | %CHECK %s + +FUNCTION_BLOCK A + METHOD printArgs + VAR_INPUT + message: STRING; + value: DINT; + END_VAR + + printf('%s %d$N', ADR(message), value); + END_METHOD +END_FUNCTION_BLOCK + +TYPE FnTable: + STRUCT + printArgs: POINTER TO A.printArgs := ADR(A.printArgs); + END_STRUCT +END_TYPE + +VAR_GLOBAL + fnTableInstance: FnTable; +END_VAR + +FUNCTION main + VAR + instanceA: A; + fnTableRef: POINTER TO __VOID := ADR(fnTableInstance); + END_VAR + + // Not a direct cast of a void pointer function but rather its virtual table, basically how the virtual + // table is also accessed in desugared code (though the access happens in the POU rather than a local + // variable in the main function) + // CHECK: value = 5 + // CHECK: value = 5 + FnTable#(fnTableRef^).printArgs^(instanceA, 'value =', 5); + FnTable#(fnTableRef^).printArgs^(instanceA, message := 'value =', value := 5); +END_FUNCTION \ No newline at end of file diff --git a/tests/lit/single/polymorphism/fnptr/method_call_inout_arguments.st b/tests/lit/single/polymorphism/fnptr/method_call_inout_arguments.st new file mode 100644 index 0000000000..9d9635e978 --- /dev/null +++ b/tests/lit/single/polymorphism/fnptr/method_call_inout_arguments.st @@ -0,0 +1,31 @@ +// RUN: (%COMPILE %s && %RUN) | %CHECK %s + +FUNCTION_BLOCK A + METHOD inoutArguments + VAR_IN_OUT + inout1: DINT; + inout2: DINT; + END_VAR + + inout1 := 100; + inout2 := 200; + printf('A::inoutArguments: inout1 = %d, inout2 = %d$N', inout1, inout2); + END_METHOD +END_FUNCTION_BLOCK + +FUNCTION main + VAR + instanceA: A; + localInOut1: DINT; + localInOut2: DINT; + + fnPtrInOutArguments: POINTER TO A.inoutArguments := ADR(A.inoutArguments); + END_VAR + + // Explicit + // CHECK: A::inoutArguments: inout1 = 100, inout2 = 200 + // CHECK: A::inoutArguments: inout1 = 100, inout2 = 200 + fnPtrInOutArguments^(instanceA, localInOut1, localInOut2); + fnPtrInOutArguments^(instanceA, inout1 := localInOut1, inout2 := localInOut2); +END_FUNCTION + diff --git a/tests/lit/single/polymorphism/fnptr/method_call_input_arguments.st b/tests/lit/single/polymorphism/fnptr/method_call_input_arguments.st new file mode 100644 index 0000000000..9be1747b29 --- /dev/null +++ b/tests/lit/single/polymorphism/fnptr/method_call_input_arguments.st @@ -0,0 +1,28 @@ +// RUN: (%COMPILE %s && %RUN) | %CHECK %s + +FUNCTION_BLOCK A + METHOD inputArguments + VAR_INPUT + in1: DINT; + in2: DINT; + END_VAR + + printf('A::inputArguments: in1 = %d, in2 = %d$N', in1, in2); + END_METHOD +END_FUNCTION_BLOCK + +FUNCTION main + VAR + instanceA: A; + localOut1: DINT; + localOut2: DINT; + + fnPtrInputArguments: POINTER TO A.inputArguments := ADR(A.inputArguments); + END_VAR + + // Implicit + // CHECK: A::inputArguments: in1 = 5, in2 = 10 + // CHECK: A::inputArguments: in1 = 5, in2 = 10 + fnPtrInputArguments^(instanceA, 5, 10); + fnPtrInputArguments^(instanceA, in1 := 5, in2 := 10); +END_FUNCTION \ No newline at end of file diff --git a/tests/lit/single/polymorphism/fnptr/method_call_output_arguments.st b/tests/lit/single/polymorphism/fnptr/method_call_output_arguments.st new file mode 100644 index 0000000000..21ed96df4f --- /dev/null +++ b/tests/lit/single/polymorphism/fnptr/method_call_output_arguments.st @@ -0,0 +1,31 @@ +// RUN: (%COMPILE %s && %RUN) | %CHECK %s + +FUNCTION_BLOCK A + METHOD outputArguments + VAR_OUTPUT + out1: DINT; + out2: DINT; + END_VAR + + out1 := 100; + out2 := 200; + printf('A::outputArguments: out1 = %d, out2 = %d$N', out1, out2); + END_METHOD +END_FUNCTION_BLOCK + +FUNCTION main + VAR + instanceA: A; + localOut1: DINT; + localOut2: DINT; + + fnPtrOutputArguments: POINTER TO A.outputArguments := ADR(A.outputArguments); + END_VAR + + // Explicit + // CHECK: A::outputArguments: out1 = 100, out2 = 200 + // CHECK: A::outputArguments: out1 = 100, out2 = 200 + fnPtrOutputArguments^(instanceA, localOut1, localOut2); + fnPtrOutputArguments^(instanceA, out1 => localOut1, out2 => localOut2); +END_FUNCTION + diff --git a/tests/lit/single/polymorphism/fnptr/method_call_overridden.st b/tests/lit/single/polymorphism/fnptr/method_call_overridden.st new file mode 100644 index 0000000000..13e851499b --- /dev/null +++ b/tests/lit/single/polymorphism/fnptr/method_call_overridden.st @@ -0,0 +1,60 @@ +// RUN: (%COMPILE %s && %RUN) | %CHECK %s + +FUNCTION_BLOCK A + VAR + nameA: STRING := 'FUNCTION_BLOCK A'; + END_VAR + + METHOD printName + printf('name = %s$N', ADR(nameA)); + END_METHOD +END_FUNCTION_BLOCK + +FUNCTION_BLOCK B EXTENDS A + VAR + nameB: STRING := 'FUNCTION_BLOCK B'; + END_VAR + + METHOD printName + printf('name = %s$N', ADR(nameB)); + END_METHOD +END_FUNCTION_BLOCK + +FUNCTION_BLOCK C + VAR + nameC: STRING := 'FUNCTION_BLOCK C'; + END_VAR + + METHOD printName + printf('name = %s$N', ADR(nameC)); + END_METHOD +END_FUNCTION_BLOCK + +FUNCTION main + VAR + instanceA: A; + instanceB: B; + instanceC: C; + fnPtrPrintName: POINTER TO A.printName := ADR(A.printName); + END_VAR + + // Ensure that child POUs can be passed to function pointers of both its own and parent POUs. When doing + // so we expect a bitcast on these passed arguments to the (in this case) parent POU. + // CHECK: name = FUNCTION_BLOCK A + // CHECK: name = FUNCTION_BLOCK A + fnPtrPrintName^(instanceA); + fnPtrPrintName^(instanceB); // <- fnPtrPrintName points to A.printName but we pass instanceB which should be fine and result in a bitcast + + // To showcase, we could pass an UNRELATED instance to the function pointer. Because it expects something + // along `void (%A)` in LLVM IR (no return type, no parameters) passing `instanceC` should result in a + // bitcast to `A`. The function (printName) should then GEP the first member field because `name` is + // positioned there, resulting in access onto `nameC` rather than `nameA` and therefore a complete + // different result when compared with a child instance. Illustrated (without size information) + // `type A = { string }` -- expanded --> `type A = { nameA }` + // `type B = { A, string }` -- expanded --> `type B = { { nameA }, nameB }` + // `type C = { string} -- expanded --> `type C = { nameC }` + // That should explain why we get `FUNCTION_BLOCK A` when passing `instanceB` and also why we get + // `FUNCTION_BLOCK C` when passing `instanceC` + // CHECK: name = FUNCTION_BLOCK C + fnPtrPrintName^(instanceC); +END_FUNCTION \ No newline at end of file diff --git a/tests/lit/single/polymorphism/fnptr/method_call_using_this.st b/tests/lit/single/polymorphism/fnptr/method_call_using_this.st new file mode 100644 index 0000000000..9af4052f02 --- /dev/null +++ b/tests/lit/single/polymorphism/fnptr/method_call_using_this.st @@ -0,0 +1,77 @@ +// RUN: (%COMPILE %s && %RUN) | %CHECK %s + +VAR_GLOBAL + instanceVirtuaTableA: VirtualTableA; + instanceVirtuaTableB: VirtualTableB; + instanceVirtuaTableC: VirtualTableC; +END_VAR + +TYPE VirtualTableA: + STRUCT + getName: POINTER TO A.getName := ADR(A.getName); + printName: POINTER TO A.printName := ADR(A.printName); + END_STRUCT +END_TYPE + +TYPE VirtualTableB: + STRUCT + getName: POINTER TO A.getName := ADR(A.getName); + printName: POINTER TO A.printName := ADR(A.printName); + END_STRUCT +END_TYPE + +TYPE VirtualTableC: + STRUCT + getName: POINTER TO C.getName := ADR(C.getName); + printName: POINTER TO A.printName := ADR(A.printName); + END_STRUCT +END_TYPE + +FUNCTION_BLOCK A + VAR + vt: POINTER TO __VOID; + END_VAR + + METHOD getName: STRING + getName := 'A'; + END_METHOD + + METHOD printName + VAR + result: STRING; + END_VAR + + printf('name = %s$N', ADR(VirtualTableA#(vt^).getName^(THIS^))); + END_METHOD +END_FUNCTION_BLOCK + +// Inherits A as is +FUNCTION_BLOCK B EXTENDS A +END_FUNCTION_BLOCK + +// Overrides the `getName` method +FUNCTION_BLOCK C EXTENDS A + METHOD getName: STRING + getName := 'C'; + END_METHOD +END_FUNCTION_BLOCK + +FUNCTION main + VAR + instanceA: A; + instanceB: B; + instanceC: C; + END_VAR + + instanceA.vt := ADR(instanceVirtuaTableA); + instanceB.vt := ADR(instanceVirtuaTableB); + instanceC.vt := ADR(instanceVirtuaTableC); + + // The `printName` call will use the virtual table to call the correct `getName` method + // CHECK: name = A + // CHECK: name = A + // CHECK: name = C + instanceA.printName(); // base method, hence A + instanceB.printName(); // B inherits A without overriding any methods, hence A + instanceC.printName(); // C inherits A overriding the `getName` method, hence C +END_FUNCTION \ No newline at end of file diff --git a/tests/lit/single/polymorphism/fnptr/user_defined_polymorphism.st b/tests/lit/single/polymorphism/fnptr/user_defined_polymorphism.st new file mode 100644 index 0000000000..e217b3f840 --- /dev/null +++ b/tests/lit/single/polymorphism/fnptr/user_defined_polymorphism.st @@ -0,0 +1,90 @@ +// RUN: (%COMPILE %s && %RUN) | %CHECK %s + +// Virtual Table Definitions +TYPE UserVT_FbA: + STRUCT + printNumber: POINTER TO FbA.printNumber := ADR(FbA.printNumber); + END_STRUCT +END_TYPE + +TYPE UserVT_FbB: + STRUCT + // No override, hence the ADR(FbA.<...>) + printNumber: POINTER TO FbA.printNumber := ADR(FbA.printNumber); + END_STRUCT +END_TYPE + +TYPE UserVT_FbC: + STRUCT + // override, hence the ADR(FbC.<...>) + printNumber: POINTER TO FbA.printNumber := ADR(FbC.printNumber); + END_STRUCT +END_TYPE + +// Virtual Table Instances +VAR_GLOBAL + UserVT_FbA_instance: UserVT_FbA; + UserVT_FbB_instance: UserVT_FbB; + UserVT_FbC_instance: UserVT_FbC; +END_VAR + +FUNCTION_BLOCK FbA + VAR + vt: POINTER TO __VOID; + localStateA: DINT := 5; + END_VAR + + METHOD printNumber + VAR_INPUT + in: DINT; + END_VAR + + printf('FbA::printNumber: localStateA = %d, in = %d$N', localStateA, in); + END_METHOD +END_FUNCTION_BLOCK + +FUNCTION_BLOCK FbB EXTENDS FbA + VAR + localStateB: DINT := 10; + END_VAR +END_FUNCTION_BLOCK + +FUNCTION_BLOCK FbC EXTENDS FbA + VAR + localStateC: DINT := 15; + END_VAR + + METHOD printNumber + VAR_INPUT + in: DINT; + END_VAR + + printf('FbC::printNumber: localStateA = %d, localStateC = %d, in = %d$N', localStateA, localStateC, in); + END_METHOD +END_FUNCTION_BLOCK + +FUNCTION main + VAR + instanceFbA: FbA; + instanceFbB: FbB; + instanceFbC: FbC; + refInstanceFbA: POINTER TO FbA; + END_VAR + + // Virtual Table Initialization + instanceFbA.vt := ADR(UserVT_FbA_instance); + instanceFbB.vt := ADR(UserVT_FbB_instance); + instanceFbC.vt := ADR(UserVT_FbC_instance); + + // CHECK: FbA::printNumber: localStateA = 5, in = 5 + refInstanceFbA := ADR(instanceFbA); + UserVT_FbA#(refInstanceFbA^.vt^).printNumber^(refInstanceFbA^, 5); + + // CHECK: FbA::printNumber: localStateA = 5, in = 10 + refInstanceFbA := ADR(instanceFbB); + UserVT_FbA#(refInstanceFbA^.vt^).printNumber^(refInstanceFbA^, 10); + + // CHECK: FbC::printNumber: localStateA = 5, localStateC = 15, in = 15 + refInstanceFbA := ADR(instanceFbC); + UserVT_FbA#(refInstanceFbA^.vt^).printNumber^(refInstanceFbA^, 15); +END_FUNCTION \ No newline at end of file