From c1b7028fb07eff8aea189fa04bb3a2e3b339965f Mon Sep 17 00:00:00 2001 From: Angus Bethke Date: Thu, 1 Oct 2026 07:52:44 +0000 Subject: [PATCH 1/9] fix!(codegen): reset function outputs and default omitted arguments --- book/technical/pipeline/05-codegen.md | 6 +- book/user/language/functions.md | 2 +- .../generators/expression_generator.rs | 50 +++--- src/codegen/generators/pou_generator.rs | 54 +++++++ src/codegen/tests/directaccess_test.rs | 6 +- src/codegen/tests/fnptr.rs | 2 + src/codegen/tests/function_tests.rs | 2 + src/codegen/tests/oop_tests.rs | 14 +- ..._date_and_time_addition_in_var_output.snap | 4 + ...s__code_gen_tests__methods_var_output.snap | 14 +- ...ion_tests__structs_in_function_return.snap | 9 ++ ...ction_tests__member_variables_in_body.snap | 26 ++-- ..._output_should_be_passed_as_a_pointer.snap | 2 + ...on_tests__var_output_in_function_call.snap | 2 + ...nerics_test__generic_output_parameter.snap | 2 + ...ccepts_empty_statement_as_input_param.snap | 5 +- ...cepts_empty_statement_as_output_param.snap | 4 + ...sts__function_all_parameters_assigned.snap | 2 + ...function_default_value_parameter_type.snap | 4 + ...ests__function_empty_inout_assignment.snap | 2 + ...ests__function_empty_input_assignment.snap | 6 +- ...sts__function_empty_output_assignment.snap | 2 + ...empty_output_default_value_assignment.snap | 2 + ...ts__function_missing_inout_assignment.snap | 3 + ...ts__function_missing_input_assignment.snap | 6 +- ...issing_input_default_value_assignment.snap | 2 + ...s__function_missing_output_assignment.snap | 3 + ...ssing_output_default_value_assignment.snap | 2 + ...tests__program_empty_inout_assignment.snap | 2 + src/tests/adr/pou_adr.rs | 5 + .../function_blocks/no_keyword_error.st | 142 ++++++++++++++++++ .../interfaces/type_array_arguments.st | 4 +- .../interfaces/type_string_arguments.st | 4 +- .../interfaces/type_struct_arguments.st | 4 +- 34 files changed, 329 insertions(+), 70 deletions(-) create mode 100644 tests/lit/single/function_blocks/no_keyword_error.st diff --git a/book/technical/pipeline/05-codegen.md b/book/technical/pipeline/05-codegen.md index 101eda118d6..9591fa2ca40 100644 --- a/book/technical/pipeline/05-codegen.md +++ b/book/technical/pipeline/05-codegen.md @@ -207,7 +207,7 @@ In a function, `VAR_IN_OUT` and `VAR_OUTPUT` parameters are pointers, and an agg Functions are created in two passes, like structs: first a declaration for every POU the unit depends on, including POUs from other units, then the bodies of the POUs declared in this unit. If `scale` were in a second file, the module of `main` would contain `declare i32 @scale(i32, i16, ptr, ptr)` with no body. -A body starts by making every variable addressable, so the statements can treat both kinds alike. A function copies each argument into a stack slot and starts its return variable at zero; a stateful POU computes one pointer per member into the instance: +A body starts by making every variable addressable, so the statements can treat both kinds alike. A function copies each argument into a stack slot, starts its return variable at zero, and starts each output at its initial value, or zero, through the address the caller passed; a stateful POU computes one pointer per member into the instance: ```llvm define i32 @scale(i32 %0, i16 %1, ptr %2, ptr %3) { @@ -222,6 +222,8 @@ entry: %overflow = alloca ptr ; output: same store ptr %3, ptr %overflow store i32 0, ptr %scale ; the return variable starts at zero + %4 = load ptr, ptr %overflow ; the output starts at zero, in the caller's variable + store i8 0, ptr %4 ... %scale_ret = load i32, ptr %scale ; read the return variable ret i32 %scale_ret ; and return it @@ -316,7 +318,7 @@ call void @llvm.memcpy.p0.p0.i32(ptr align 1 %text, ptr align 1 @utf08_literal_1 ### Calls to functions -Codegen places named and positional arguments in parameter order and fills omitted parameters with defaults. It passes scalar inputs by value and `VAR_IN_OUT` and `VAR_OUTPUT` arguments by address. The callee writes through these addresses. For `i := scale(i, factor := 3, total := counter, overflow => flag)`: +Codegen places named and positional arguments in parameter order and fills omitted parameters with their defaults, or with zero when the parameter declares none; an omitted `VAR_OUTPUT` or `VAR_IN_OUT` gets the address of a zeroed temporary. It passes scalar inputs by value and `VAR_IN_OUT` and `VAR_OUTPUT` arguments by address. The callee writes through these addresses. For `i := scale(i, factor := 3, total := counter, overflow => flag)`: ```llvm %call = call i32 @scale(i32 %load_i, i16 3, ptr @counter, ptr %flag) ; value, factor, address of total, address of overflow diff --git a/book/user/language/functions.md b/book/user/language/functions.md index 9bbb11be66e..7c724797d94 100644 --- a/book/user/language/functions.md +++ b/book/user/language/functions.md @@ -65,7 +65,7 @@ END_FUNCTION `VAR_IN_OUT` is for data that the function reads and writes. It is always the caller's variable. -`VAR_OUTPUT` carries a second result out of the call. +`VAR_OUTPUT` carries a second result out of the call. Every call starts the output at its initial value, or at zero when it has none, so the caller never reads a stale value from a path that does not assign it. ## Calling diff --git a/src/codegen/generators/expression_generator.rs b/src/codegen/generators/expression_generator.rs index 747fd1f96eb..73ec4c795fa 100644 --- a/src/codegen/generators/expression_generator.rs +++ b/src/codegen/generators/expression_generator.rs @@ -1585,9 +1585,14 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { .find_effective_type_info(parameter.get_type_name()) .is_some_and(|it| it.is_reference_to()); + // a missing argument gets its declared initial value, or zero + let initial_value = self.get_initial_value(¶meter.initial_value, ¶meter_type_name); + match declaration_type { ArgumentType::ByVal(..) if parameter_is_reference_to => { + // REFERENCE TO parameters expect an address; pass the address of a null reference let ptr_value = self.llvm.builder.build_alloca(parameter_type, "")?; + self.llvm.builder.build_store(ptr_value, parameter_type.const_zero())?; Ok(ptr_value.as_basic_value_enum()) } ArgumentType::ByVal(..) @@ -1595,36 +1600,25 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { { // Aggregate VAR_INPUT defaults are passed by reference in function signatures. let ptr_value = self.llvm.builder.build_alloca(parameter_type, "")?; - if let Some(initial_value) = - self.get_initial_value(¶meter.initial_value, ¶meter_type_name) - { - let value = self.generate_expression(initial_value)?; - self.llvm.builder.build_store(ptr_value, value)?; - } + let value = match initial_value { + Some(initial_value) => self.generate_expression(initial_value)?, + None => parameter_type.const_zero(), + }; + self.llvm.builder.build_store(ptr_value, value)?; Ok(ptr_value.as_basic_value_enum()) } - ArgumentType::ByVal(..) => { - if let Some(initial_value) = - self.get_initial_value(¶meter.initial_value, ¶meter_type_name) - { - self.generate_expression(initial_value) - } else { - let ptr_value = self.llvm.builder.build_alloca(parameter_type, "")?; - let pointee = parameter_type; - Ok(self.llvm.load_pointer(pointee, &ptr_value, "")?) - } - } + ArgumentType::ByVal(..) => match initial_value { + Some(initial_value) => self.generate_expression(initial_value), + None => Ok(parameter_type.const_zero()), + }, _ => { + // by-ref parameters receive the address of an initialized temporary let ptr_value = self.llvm.builder.build_alloca(parameter_type, "")?; - - // if default value is given for an output - // we need to initialize the pointer value before returning - if let Some(initial_value) = - self.get_initial_value(¶meter.initial_value, ¶meter_type_name) - { - let value = self.generate_expression(initial_value)?; - self.llvm.builder.build_store(ptr_value, value)?; - } + let value = match initial_value { + Some(initial_value) => self.generate_expression(initial_value)?, + None => parameter_type.const_zero(), + }; + self.llvm.builder.build_store(ptr_value, value)?; Ok(ptr_value.as_basic_value_enum()) } } @@ -1704,7 +1698,9 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { .llvm_index .find_associated_type(inner_type_name) .ok_or_else(|| Diagnostic::unknown_type(parameter.get_name(), expression))?; - builder.build_alloca(temp_type, "empty_varinout")?.as_basic_value_enum() + let temp = builder.build_alloca(temp_type, "empty_varinout")?; + builder.build_store(temp, temp_type.const_zero())?; + temp.as_basic_value_enum() } else { self.generate_lvalue(expression)?.as_basic_value_enum() }; diff --git a/src/codegen/generators/pou_generator.rs b/src/codegen/generators/pou_generator.rs index 74c47e49c49..2b45b7089c8 100644 --- a/src/codegen/generators/pou_generator.rs +++ b/src/codegen/generators/pou_generator.rs @@ -623,6 +623,7 @@ impl<'ink, 'cg> PouGenerator<'ink, 'cg> { &function_context, debug, )?; + self.generate_initialization_of_output_params(&pou_members, &local_index)?; } else { //Generate temp variables let members = pou_members.into_iter().filter(|it| it.is_temp()).collect::>(); @@ -937,6 +938,59 @@ impl<'ink, 'cg> PouGenerator<'ink, 'cg> { Ok(()) } + /// resets every by-ref output parameter of a function or method to its initial value, so the + /// caller never observes a stale value through an output the body does not assign + fn generate_initialization_of_output_params( + &self, + variables: &[&VariableIndexEntry], + local_llvm_index: &LlvmTypedIndex, + ) -> Result<(), CodegenError> { + let exp_gen = ExpressionCodeGenerator::new_context_free( + &self.llvm, + self.index, + self.annotations, + local_llvm_index, + ); + let outputs = variables.iter().filter(|it| it.is_output() && it.get_declaration_type().is_by_ref()); + + for variable in outputs { + let Some(inner_type_name) = self + .index + .find_effective_type_info(variable.get_type_name()) + .and_then(|it| it.get_inner_pointer_type_name()) + else { + continue; + }; + // reference and alias outputs are bound by the body, not reset + if self + .index + .find_effective_type_info(inner_type_name) + .is_some_and(|it| it.is_reference_to() || it.is_alias()) + { + continue; + } + let Some(pointer_slot) = + local_llvm_index.find_loaded_associated_variable_value(variable.get_qualified_name()) + else { + continue; + }; + + let ptr_type = self.llvm.context.ptr_type(AddressSpace::from(ADDRESS_SPACE_GENERIC)); + let output = self.llvm.builder.build_load(ptr_type, pointer_slot, "")?.into_pointer_value(); + let initializer = + self.index.get_const_expressions().maybe_get_constant_statement(&variable.initial_value); + self.llvm.generate_variable_initializer( + self.llvm_index, + self.index, + (variable.get_qualified_name(), inner_type_name, &variable.source_location), + output, + initializer, + &exp_gen, + )?; + } + Ok(()) + } + /// initializes the variable represented by `variable` by storing into the given `variable_to_initialize` pointer using either /// the optional `initializer_statement` (hence code like: `variable : type := initializer_statement`), or determine the initial /// value with the help of the `variable`'s index entry by e.g. looking for a default value of the variable's type diff --git a/src/codegen/tests/directaccess_test.rs b/src/codegen/tests/directaccess_test.rs index f018b94129c..ca6cf87fba9 100644 --- a/src/codegen/tests/directaccess_test.rs +++ b/src/codegen/tests/directaccess_test.rs @@ -551,9 +551,11 @@ fn direct_access_in_output_assignment_of_function() { store i8 %0, ptr %in, align [filtered] %out = alloca ptr, align [filtered] store ptr %1, ptr %out, align [filtered] + %2 = load ptr, ptr %out, align [filtered] + store i8 0, ptr %2, align [filtered] %deref = load ptr, ptr %out, align [filtered] - %2 = load i8, ptr %in, align [filtered] - %tmpVar = xor i8 %2, -1 + %3 = load i8, ptr %in, align [filtered] + %tmpVar = xor i8 %3, -1 store i8 %tmpVar, ptr %deref, align [filtered] ret void } diff --git a/src/codegen/tests/fnptr.rs b/src/codegen/tests/fnptr.rs index b1b45d0018e..90aa17b4ae7 100644 --- a/src/codegen/tests/fnptr.rs +++ b/src/codegen/tests/fnptr.rs @@ -300,6 +300,8 @@ fn function_pointer_method_with_all_variable_parameter_types() { %inout = alloca ptr, align [filtered] store ptr %3, ptr %inout, align [filtered] store i32 0, ptr %A.foo, align [filtered] + %4 = load ptr, ptr %out, align [filtered] + call void @llvm.memset.p0.i64(ptr align [filtered] %4, i8 0, i64 ptrtoint (ptr getelementptr ([81 x i8], ptr null, i32 1) to i64), i1 false) %A__foo_ret = load i32, ptr %A.foo, align [filtered] ret i32 %A__foo_ret } diff --git a/src/codegen/tests/function_tests.rs b/src/codegen/tests/function_tests.rs index daab0671d50..2202d7d7d84 100644 --- a/src/codegen/tests/function_tests.rs +++ b/src/codegen/tests/function_tests.rs @@ -544,6 +544,8 @@ fn function_output_should_be_cast_if_needed() { %result = alloca ptr, align [filtered] store ptr %2, ptr %result, align [filtered] store i16 0, ptr %libFunction, align [filtered] + %3 = load ptr, ptr %result, align [filtered] + store float 0.000000e+00, ptr %3, align [filtered] %libFunction_ret = load i16, ptr %libFunction, align [filtered] ret i16 %libFunction_ret } diff --git a/src/codegen/tests/oop_tests.rs b/src/codegen/tests/oop_tests.rs index b121a63058a..d4800e84f6e 100644 --- a/src/codegen/tests/oop_tests.rs +++ b/src/codegen/tests/oop_tests.rs @@ -2524,6 +2524,8 @@ fn fb_extension_with_output() { %outputValue = alloca ptr, align [filtered] store ptr %3, ptr %outputValue, align [filtered] store i16 0, ptr %foo.met1, align [filtered] + %4 = load ptr, ptr %outputValue, align [filtered] + store i16 0, ptr %4, align [filtered] %foo__met1_ret = load i16, ptr %foo.met1, align [filtered] ret i16 %foo__met1_ret } @@ -2726,14 +2728,16 @@ fn function_with_output_used_in_main_by_extension() { %outputValue = alloca ptr, align [filtered] store ptr %3, ptr %outputValue, align [filtered] store i16 0, ptr %foo.met1, align [filtered] + %4 = load ptr, ptr %outputValue, align [filtered] + store i16 0, ptr %4, align [filtered] %deref = load ptr, ptr %outputValue, align [filtered] %load_mandatoryInput = load i16, ptr %mandatoryInput, align [filtered] - %4 = sext i16 %load_mandatoryInput to i32 + %5 = sext i16 %load_mandatoryInput to i32 %load_optionalInput = load i16, ptr %optionalInput, align [filtered] - %5 = sext i16 %load_optionalInput to i32 - %tmpVar = add i32 %4, %5 - %6 = trunc i32 %tmpVar to i16 - store i16 %6, ptr %deref, align [filtered] + %6 = sext i16 %load_optionalInput to i32 + %tmpVar = add i32 %5, %6 + %7 = trunc i32 %tmpVar to i16 + store i16 %7, ptr %deref, align [filtered] %foo__met1_ret = load i16, ptr %foo.met1, align [filtered] ret i16 %foo__met1_ret } diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__date_and_time_addition_in_var_output.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__date_and_time_addition_in_var_output.snap index e44bab7135d..ece7d174015 100644 --- a/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__date_and_time_addition_in_var_output.snap +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__date_and_time_addition_in_var_output.snap @@ -15,6 +15,10 @@ entry: %time_var = alloca ptr, align [filtered] store ptr %1, ptr %time_var, align [filtered] store i32 0, ptr %func, align [filtered] + %2 = load ptr, ptr %d_and_t, align [filtered] + store i32 0, ptr %2, align [filtered] + %3 = load ptr, ptr %time_var, align [filtered] + store i32 0, ptr %3, align [filtered] %deref = load ptr, ptr %d_and_t, align [filtered] %deref1 = load ptr, ptr %d_and_t, align [filtered] %load_d_and_t = load i32, ptr %deref1, align [filtered] diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__methods_var_output.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__methods_var_output.snap index 26b59a40803..e9e0e6d43c0 100644 --- a/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__methods_var_output.snap +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__code_gen_tests__methods_var_output.snap @@ -24,6 +24,8 @@ entry: store ptr %0, ptr %this, align [filtered] %out = alloca ptr, align [filtered] store ptr %1, ptr %out, align [filtered] + %2 = load ptr, ptr %out, align [filtered] + call void @llvm.memset.p0.i64(ptr align [filtered] %2, i8 0, i64 ptrtoint (ptr getelementptr ([81 x i8], ptr null, i32 1) to i64), i1 false) %deref = load ptr, ptr %out, align [filtered] call void @llvm.memcpy.p0.p0.i32(ptr align [filtered] %deref, ptr align [filtered] @utf08_literal_0, i32 6, i1 false) ret void @@ -40,11 +42,11 @@ entry: ret void } -; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: readwrite) -declare void @llvm.memcpy.p0.p0.i32(ptr noalias writeonly captures(none), ptr noalias readonly captures(none), i32, i1 immarg) #0 - ; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: write) -declare void @llvm.memset.p0.i64(ptr writeonly captures(none), i8, i64, i1 immarg) #1 +declare void @llvm.memset.p0.i64(ptr writeonly captures(none), i8, i64, i1 immarg) #0 + +; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: readwrite) +declare void @llvm.memcpy.p0.p0.i32(ptr noalias writeonly captures(none), ptr noalias readonly captures(none), i32, i1 immarg) #1 -attributes #0 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) } -attributes #1 = { nocallback nofree nounwind willreturn memory(argmem: write) } +attributes #0 = { nocallback nofree nounwind willreturn memory(argmem: write) } +attributes #1 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) } diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__structs_in_function_return.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__structs_in_function_return.snap index 502194a6f91..148f2c3eaf0 100644 --- a/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__structs_in_function_return.snap +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__structs_in_function_return.snap @@ -7,11 +7,20 @@ source_filename = "" target datalayout = "[filtered]" target triple = "[filtered]" +%myStruct = type { i16 } + define void @func(ptr %0, ptr %1) { entry: %func = alloca ptr, align [filtered] store ptr %0, ptr %func, align [filtered] %xxx = alloca ptr, align [filtered] store ptr %1, ptr %xxx, align [filtered] + %2 = load ptr, ptr %xxx, align [filtered] + call void @llvm.memset.p0.i64(ptr align [filtered] %2, i8 0, i64 ptrtoint (ptr getelementptr (%myStruct, ptr null, i32 1) to i64), i1 false) ret void } + +; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: write) +declare void @llvm.memset.p0.i64(ptr writeonly captures(none), i8, i64, i1 immarg) #0 + +attributes #0 = { nocallback nofree nounwind willreturn memory(argmem: write) } diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__function_tests__member_variables_in_body.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__function_tests__member_variables_in_body.snap index 35aa7038031..8d93a148005 100644 --- a/src/codegen/tests/snapshots/rusty__codegen__tests__function_tests__member_variables_in_body.snap +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__function_tests__member_variables_in_body.snap @@ -21,25 +21,27 @@ entry: store i16 1, ptr %v, align [filtered] store i16 2, ptr %vt, align [filtered] store i32 0, ptr %func, align [filtered] + %3 = load ptr, ptr %o, align [filtered] + store i64 0, ptr %3, align [filtered] %load_i = load i16, ptr %i, align [filtered] - %3 = sext i16 %load_i to i32 + %4 = sext i16 %load_i to i32 %deref = load ptr, ptr %io, align [filtered] %load_io = load i8, ptr %deref, align [filtered] - %4 = sext i8 %load_io to i32 - %tmpVar = mul i32 %3, %4 - %5 = sext i32 %tmpVar to i64 + %5 = sext i8 %load_io to i32 + %tmpVar = mul i32 %4, %5 + %6 = sext i32 %tmpVar to i64 %deref1 = load ptr, ptr %o, align [filtered] %load_o = load i64, ptr %deref1, align [filtered] - %tmpVar2 = sub i64 %5, %load_o + %tmpVar2 = sub i64 %6, %load_o %load_v = load i16, ptr %v, align [filtered] - %6 = sext i16 %load_v to i32 + %7 = sext i16 %load_v to i32 %load_vt = load i16, ptr %vt, align [filtered] - %7 = sext i16 %load_vt to i32 - %tmpVar3 = mul i32 %6, %7 - %8 = sext i32 %tmpVar3 to i64 - %tmpVar4 = add i64 %tmpVar2, %8 - %9 = trunc i64 %tmpVar4 to i32 - store i32 %9, ptr %func, align [filtered] + %8 = sext i16 %load_vt to i32 + %tmpVar3 = mul i32 %7, %8 + %9 = sext i32 %tmpVar3 to i64 + %tmpVar4 = add i64 %tmpVar2, %9 + %10 = trunc i64 %tmpVar4 to i32 + store i32 %10, ptr %func, align [filtered] %func_ret = load i32, ptr %func, align [filtered] ret i32 %func_ret } diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__function_tests__on_functions_var_output_should_be_passed_as_a_pointer.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__function_tests__on_functions_var_output_should_be_passed_as_a_pointer.snap index d0d804b5ec5..9e0d8b40000 100644 --- a/src/codegen/tests/snapshots/rusty__codegen__tests__function_tests__on_functions_var_output_should_be_passed_as_a_pointer.snap +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__function_tests__on_functions_var_output_should_be_passed_as_a_pointer.snap @@ -13,6 +13,8 @@ entry: %v = alloca ptr, align [filtered] store ptr %0, ptr %v, align [filtered] store i32 0, ptr %bump, align [filtered] + %1 = load ptr, ptr %v, align [filtered] + store i8 0, ptr %1, align [filtered] store i32 1, ptr %bump, align [filtered] %deref = load ptr, ptr %v, align [filtered] store i8 2, ptr %deref, align [filtered] diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__function_tests__var_output_in_function_call.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__function_tests__var_output_in_function_call.snap index da5ec0fce48..8e075de4e61 100644 --- a/src/codegen/tests/snapshots/rusty__codegen__tests__function_tests__var_output_in_function_call.snap +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__function_tests__var_output_in_function_call.snap @@ -17,6 +17,8 @@ entry: %o = alloca ptr, align [filtered] store ptr %0, ptr %o, align [filtered] store i32 0, ptr %func, align [filtered] + %1 = load ptr, ptr %o, align [filtered] + store i16 0, ptr %1, align [filtered] %deref = load ptr, ptr %o, align [filtered] store i16 6, ptr %deref, align [filtered] store i32 4, ptr %func, align [filtered] diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__generics_test__generic_output_parameter.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__generics_test__generic_output_parameter.snap index f510e6b2463..ccb6cc523ab 100644 --- a/src/codegen/tests/snapshots/rusty__codegen__tests__generics_test__generic_output_parameter.snap +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__generics_test__generic_output_parameter.snap @@ -19,6 +19,8 @@ entry: %out1 = alloca ptr, align [filtered] store ptr %1, ptr %out1, align [filtered] store i16 0, ptr %foo__INT, align [filtered] + %2 = load ptr, ptr %out1, align [filtered] + store i16 0, ptr %2, align [filtered] %foo__INT_ret = load i16, ptr %foo__INT, align [filtered] ret i16 %foo__INT_ret } diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__function_accepts_empty_statement_as_input_param.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__function_accepts_empty_statement_as_input_param.snap index 60fc0623e0e..e88877437ca 100644 --- a/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__function_accepts_empty_statement_as_input_param.snap +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__function_accepts_empty_statement_as_input_param.snap @@ -1,5 +1,6 @@ --- source: src/codegen/tests/parameters_tests.rs +assertion_line: 593 expression: result --- ; ModuleID = '' @@ -22,8 +23,6 @@ entry: define void @main(ptr %0) { entry: - %1 = alloca i32, align [filtered] - %2 = load i32, ptr %1, align [filtered] - call void @foo(i32 1, i32 %2) + call void @foo(i32 1, i32 0) ret void } diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__function_accepts_empty_statement_as_output_param.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__function_accepts_empty_statement_as_output_param.snap index 2c236132486..02533514233 100644 --- a/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__function_accepts_empty_statement_as_output_param.snap +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__function_accepts_empty_statement_as_output_param.snap @@ -17,6 +17,10 @@ entry: store ptr %0, ptr %out1, align [filtered] %out2 = alloca ptr, align [filtered] store ptr %1, ptr %out2, align [filtered] + %2 = load ptr, ptr %out1, align [filtered] + store i32 0, ptr %2, align [filtered] + %3 = load ptr, ptr %out2, align [filtered] + store i32 0, ptr %3, align [filtered] ret void } diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__function_all_parameters_assigned.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__function_all_parameters_assigned.snap index a57d4c4491a..fce0a0f4102 100644 --- a/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__function_all_parameters_assigned.snap +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__function_all_parameters_assigned.snap @@ -21,6 +21,8 @@ entry: %inout1 = alloca ptr, align [filtered] store ptr %2, ptr %inout1, align [filtered] store i32 0, ptr %foo, align [filtered] + %3 = load ptr, ptr %output1, align [filtered] + store i32 0, ptr %3, align [filtered] %foo_ret = load i32, ptr %foo, align [filtered] ret i32 %foo_ret } diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__function_default_value_parameter_type.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__function_default_value_parameter_type.snap index 58ff2af2176..435ab9b55a2 100644 --- a/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__function_default_value_parameter_type.snap +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__function_default_value_parameter_type.snap @@ -23,6 +23,10 @@ entry: %inout1 = alloca ptr, align [filtered] store ptr %3, ptr %inout1, align [filtered] store i32 0, ptr %foo, align [filtered] + %4 = load ptr, ptr %output1, align [filtered] + store i32 20, ptr %4, align [filtered] + %5 = load ptr, ptr %output2, align [filtered] + store i32 20, ptr %5, align [filtered] %foo_ret = load i32, ptr %foo, align [filtered] ret i32 %foo_ret } diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__function_empty_inout_assignment.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__function_empty_inout_assignment.snap index cc7dc0fda17..435d5871047 100644 --- a/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__function_empty_inout_assignment.snap +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__function_empty_inout_assignment.snap @@ -21,6 +21,8 @@ entry: %inout1 = alloca ptr, align [filtered] store ptr %2, ptr %inout1, align [filtered] store i32 0, ptr %foo, align [filtered] + %3 = load ptr, ptr %output1, align [filtered] + store i32 0, ptr %3, align [filtered] %foo_ret = load i32, ptr %foo, align [filtered] ret i32 %foo_ret } diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__function_empty_input_assignment.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__function_empty_input_assignment.snap index 60513805564..08adbdffd65 100644 --- a/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__function_empty_input_assignment.snap +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__function_empty_input_assignment.snap @@ -21,6 +21,8 @@ entry: %inout1 = alloca ptr, align [filtered] store ptr %2, ptr %inout1, align [filtered] store i32 0, ptr %foo, align [filtered] + %3 = load ptr, ptr %output1, align [filtered] + store i32 0, ptr %3, align [filtered] %foo_ret = load i32, ptr %foo, align [filtered] ret i32 %foo_ret } @@ -30,8 +32,6 @@ entry: %var1 = getelementptr inbounds nuw %main, ptr %0, i32 0, i32 0 %var2 = getelementptr inbounds nuw %main, ptr %0, i32 0, i32 1 %var3 = getelementptr inbounds nuw %main, ptr %0, i32 0, i32 2 - %1 = alloca i32, align [filtered] - %2 = load i32, ptr %1, align [filtered] - %call = call i32 @foo(i32 %2, ptr %var2, ptr %var3) + %call = call i32 @foo(i32 0, ptr %var2, ptr %var3) ret void } diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__function_empty_output_assignment.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__function_empty_output_assignment.snap index b6fed1805ee..9c1ec884f18 100644 --- a/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__function_empty_output_assignment.snap +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__function_empty_output_assignment.snap @@ -21,6 +21,8 @@ entry: %inout1 = alloca ptr, align [filtered] store ptr %2, ptr %inout1, align [filtered] store i32 0, ptr %foo, align [filtered] + %3 = load ptr, ptr %output1, align [filtered] + store i32 0, ptr %3, align [filtered] %foo_ret = load i32, ptr %foo, align [filtered] ret i32 %foo_ret } diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__function_empty_output_default_value_assignment.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__function_empty_output_default_value_assignment.snap index 5cdc2d527d8..af740c5b3d7 100644 --- a/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__function_empty_output_default_value_assignment.snap +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__function_empty_output_default_value_assignment.snap @@ -21,6 +21,8 @@ entry: %inout1 = alloca ptr, align [filtered] store ptr %2, ptr %inout1, align [filtered] store i32 0, ptr %foo, align [filtered] + %3 = load ptr, ptr %output1, align [filtered] + store i32 3, ptr %3, align [filtered] %foo_ret = load i32, ptr %foo, align [filtered] ret i32 %foo_ret } diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__function_missing_inout_assignment.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__function_missing_inout_assignment.snap index cc7dc0fda17..7ae7a353360 100644 --- a/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__function_missing_inout_assignment.snap +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__function_missing_inout_assignment.snap @@ -21,6 +21,8 @@ entry: %inout1 = alloca ptr, align [filtered] store ptr %2, ptr %inout1, align [filtered] store i32 0, ptr %foo, align [filtered] + %3 = load ptr, ptr %output1, align [filtered] + store i32 0, ptr %3, align [filtered] %foo_ret = load i32, ptr %foo, align [filtered] ret i32 %foo_ret } @@ -32,6 +34,7 @@ entry: %var3 = getelementptr inbounds nuw %main, ptr %0, i32 0, i32 2 %load_var1 = load i32, ptr %var1, align [filtered] %1 = alloca i32, align [filtered] + store i32 0, ptr %1, align [filtered] %call = call i32 @foo(i32 %load_var1, ptr %var2, ptr %1) ret void } diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__function_missing_input_assignment.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__function_missing_input_assignment.snap index 60513805564..08adbdffd65 100644 --- a/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__function_missing_input_assignment.snap +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__function_missing_input_assignment.snap @@ -21,6 +21,8 @@ entry: %inout1 = alloca ptr, align [filtered] store ptr %2, ptr %inout1, align [filtered] store i32 0, ptr %foo, align [filtered] + %3 = load ptr, ptr %output1, align [filtered] + store i32 0, ptr %3, align [filtered] %foo_ret = load i32, ptr %foo, align [filtered] ret i32 %foo_ret } @@ -30,8 +32,6 @@ entry: %var1 = getelementptr inbounds nuw %main, ptr %0, i32 0, i32 0 %var2 = getelementptr inbounds nuw %main, ptr %0, i32 0, i32 1 %var3 = getelementptr inbounds nuw %main, ptr %0, i32 0, i32 2 - %1 = alloca i32, align [filtered] - %2 = load i32, ptr %1, align [filtered] - %call = call i32 @foo(i32 %2, ptr %var2, ptr %var3) + %call = call i32 @foo(i32 0, ptr %var2, ptr %var3) ret void } diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__function_missing_input_default_value_assignment.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__function_missing_input_default_value_assignment.snap index 26c8f83050e..59f6cf4bbbc 100644 --- a/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__function_missing_input_default_value_assignment.snap +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__function_missing_input_default_value_assignment.snap @@ -21,6 +21,8 @@ entry: %inout1 = alloca ptr, align [filtered] store ptr %2, ptr %inout1, align [filtered] store i32 0, ptr %foo, align [filtered] + %3 = load ptr, ptr %output1, align [filtered] + store i32 0, ptr %3, align [filtered] %foo_ret = load i32, ptr %foo, align [filtered] ret i32 %foo_ret } diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__function_missing_output_assignment.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__function_missing_output_assignment.snap index b6fed1805ee..290288e64c8 100644 --- a/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__function_missing_output_assignment.snap +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__function_missing_output_assignment.snap @@ -21,6 +21,8 @@ entry: %inout1 = alloca ptr, align [filtered] store ptr %2, ptr %inout1, align [filtered] store i32 0, ptr %foo, align [filtered] + %3 = load ptr, ptr %output1, align [filtered] + store i32 0, ptr %3, align [filtered] %foo_ret = load i32, ptr %foo, align [filtered] ret i32 %foo_ret } @@ -32,6 +34,7 @@ entry: %var3 = getelementptr inbounds nuw %main, ptr %0, i32 0, i32 2 %load_var1 = load i32, ptr %var1, align [filtered] %1 = alloca i32, align [filtered] + store i32 0, ptr %1, align [filtered] %call = call i32 @foo(i32 %load_var1, ptr %1, ptr %var3) ret void } diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__function_missing_output_default_value_assignment.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__function_missing_output_default_value_assignment.snap index 5cdc2d527d8..af740c5b3d7 100644 --- a/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__function_missing_output_default_value_assignment.snap +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__function_missing_output_default_value_assignment.snap @@ -21,6 +21,8 @@ entry: %inout1 = alloca ptr, align [filtered] store ptr %2, ptr %inout1, align [filtered] store i32 0, ptr %foo, align [filtered] + %3 = load ptr, ptr %output1, align [filtered] + store i32 3, ptr %3, align [filtered] %foo_ret = load i32, ptr %foo, align [filtered] ret i32 %foo_ret } diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__program_empty_inout_assignment.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__program_empty_inout_assignment.snap index c593ef44176..5eb61a0d13e 100644 --- a/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__program_empty_inout_assignment.snap +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__program_empty_inout_assignment.snap @@ -1,5 +1,6 @@ --- source: src/codegen/tests/parameters_tests.rs +assertion_line: 413 expression: result --- ; ModuleID = '' @@ -29,6 +30,7 @@ entry: %load_var1 = load i32, ptr %var1, align [filtered] store i32 %load_var1, ptr @prog_instance, align [filtered] %empty_varinout = alloca i32, align [filtered] + store i32 0, ptr %empty_varinout, align [filtered] store ptr %empty_varinout, ptr getelementptr inbounds (%prog, ptr @prog_instance, i32 0, i32 2), align [filtered] call void @prog(ptr @prog_instance) %1 = load i32, ptr getelementptr inbounds (%prog, ptr @prog_instance, i32 0, i32 1), align [filtered] diff --git a/src/tests/adr/pou_adr.rs b/src/tests/adr/pou_adr.rs index 8a5310d6340..7b53780c7d9 100644 --- a/src/tests/adr/pou_adr.rs +++ b/src/tests/adr/pou_adr.rs @@ -422,6 +422,8 @@ fn function_get_a_method_with_by_ref_parameters() { store i16 1, ptr %v, align [filtered] store i16 2, ptr %vt, align [filtered] store i32 0, ptr %main_fun, align [filtered] + %3 = load ptr, ptr %o, align [filtered] + store i64 0, ptr %3, align [filtered] %main_fun_ret = load i32, ptr %main_fun, align [filtered] ret i32 %main_fun_ret } @@ -462,6 +464,7 @@ fn calling_a_function() { %z = getelementptr inbounds nuw %prg, ptr %0, i32 0, i32 1 %load_x = load i16, ptr %x, align [filtered] %1 = alloca i64, align [filtered] + store i64 0, ptr %1, align [filtered] %call = call i32 @main_fun(i16 %load_x, ptr %z, ptr %1) ret void } @@ -480,6 +483,8 @@ fn calling_a_function() { store i16 1, ptr %v, align [filtered] store i16 2, ptr %vt, align [filtered] store i32 0, ptr %main_fun, align [filtered] + %3 = load ptr, ptr %o, align [filtered] + store i64 0, ptr %3, align [filtered] %main_fun_ret = load i32, ptr %main_fun, align [filtered] ret i32 %main_fun_ret } diff --git a/tests/lit/single/function_blocks/no_keyword_error.st b/tests/lit/single/function_blocks/no_keyword_error.st new file mode 100644 index 00000000000..6448822968f --- /dev/null +++ b/tests/lit/single/function_blocks/no_keyword_error.st @@ -0,0 +1,142 @@ +// RUN: %COMPILE %s && %RUN | %CHECK %s +FUNCTION_BLOCK fb + VAR + return_value : INT; + return_value_2 : INT; + END_VAR + + METHOD PUBLIC public_method + VAR_INPUT + input_var : INT; + input_var_2 : INT; + END_VAR + VAR_OUTPUT + output_var : INT; + output_var_2 : INT; + output_return_sum : INT; + END_VAR + output_return_sum := private_method(input_var, input_var_2, output_var, output_var_2); + PRINTF('PUBLIC-METHOD: private_method.output_var: %d, output_var_2: %d, output_return_sum: %d$N', output_var, output_var_2, output_return_sum); + END_METHOD + METHOD PRIVATE private_method : INT + VAR_INPUT + input_var : INT; + input_var_2 : INT; + END_VAR + VAR_OUTPUT + output_var : INT; + output_var_2 : INT; + END_VAR + output_var := input_var; + output_var_2 := input_var_2; + private_method := output_var + output_var_2; + END_METHOD + + METHOD INTERNAL internal_method + VAR_INPUT + input_var : INT; + input_var_2 : INT; + END_VAR + VAR_OUTPUT + output_var : INT; + output_var_2 : INT; + END_VAR + protected_method(input_var := input_var, input_var_2 := input_var_2, output_var => output_var, output_var_2 => output_var_2); + PRINTF('INTERNAL-METHOD: protected_method.output_var: %d, output_var_2: %d$N', output_var, output_var_2); + END_METHOD + METHOD PROTECTED protected_method + VAR_INPUT + input_var : INT; + input_var_2 : INT; + END_VAR + VAR_OUTPUT + output_var : INT; + output_var_2 : INT; + END_VAR + output_var := input_var; + output_var_2 := input_var_2; + END_METHOD + + METHOD no_keyword_method + VAR_INPUT + input_var : INT; + input_var_2 : INT; + END_VAR + VAR_OUTPUT + output_var : INT; + output_var_2 : INT; + END_VAR + final_method(output_var_2 => output_var_2, input_var_2 := input_var_2); + PRINTF('NO-KEYWORD-METHOD: final_method.output_var: %d, output_var_2: %d$N', output_var, output_var_2); + output_var := input_var; + END_METHOD + METHOD FINAL final_method + VAR_INPUT + input_var : INT; + input_var_2 : INT; + END_VAR + VAR_OUTPUT + output_var : INT; + output_var_2 : INT; + END_VAR + output_var := input_var; + output_var_2 := input_var_2; + END_METHOD +END_FUNCTION_BLOCK + +FUNCTION_BLOCK fb_extended EXTENDS fb + VAR_OUTPUT + output_var : INT; + output_var_2 : INT; + END_VAR + METHOD extended_method : INT + VAR_OUTPUT + m_output_var : INT; + m_output_var_2 : INT; + END_VAR + public_method(100, 101, m_output_var, m_output_var_2, extended_method); + PRINTF('EXTENDED-METHOD: public_method.m_output_var: %d, m_output_var_2: %d, extended_method.return_value: %d$N', m_output_var, m_output_var_2, extended_method); + END_METHOD + + internal_method(input_var := 102, input_var_2 := 103, output_var => output_var, output_var_2 => output_var_2); + PRINTF('FB-EXTENDED: internal_method.output_var: %d, output_var_2: %d$N', output_var, output_var_2); + no_keyword_method(input_var := 104, output_var => output_var); + PRINTF('FB-EXTENDED: no_keyword_method.output_var: %d, output_var_2: %d$N', output_var, output_var_2); + + SUPER^.public_method(105, 106, output_var, output_var_2, return_value); + PRINTF('FB-EXTENDED: SUPER^.public_method.output_var: %d, output_var_2: %d, return_value: %d$N', output_var, output_var_2, return_value); + + THIS^.public_method(200, 205, output_var, output_var_2, return_value); + PRINTF('FB-EXTENDED: THIS^.public_method.output_var: %d, output_var_2: %d, return_value: %d$N', output_var, output_var_2, return_value); + + THIS^.internal_method(input_var_2 := 301, output_var_2 => output_var_2); + PRINTF('FB-EXTENDED: THIS^.internal_method.output_var_2: %d$N', output_var_2); + + SUPER^.no_keyword_method(400, 450, output_var, output_var_2); + PRINTF('FB-EXTENDED: SUPER^.no_keyword_method.output_var: %d, output_var_2: %d$N', output_var, output_var_2); +END_FUNCTION_BLOCK + +FUNCTION main + VAR + fb_extended_instance : fb_extended; + return_value : INT; + END_VAR + + return_value := fb_extended_instance.extended_method(); + fb_extended_instance(); + + //CHECK:PUBLIC-METHOD: private_method.output_var: 100, output_var_2: 101, output_return_sum: 201 + //CHECK-NEXT:EXTENDED-METHOD: public_method.m_output_var: 100, m_output_var_2: 101, extended_method.return_value: 201 + //CHECK-NEXT:INTERNAL-METHOD: protected_method.output_var: 102, output_var_2: 103 + //CHECK-NEXT:FB-EXTENDED: internal_method.output_var: 102, output_var_2: 103 + //CHECK-NEXT:NO-KEYWORD-METHOD: final_method.output_var: 0, output_var_2: 0 + //CHECK-NEXT:FB-EXTENDED: no_keyword_method.output_var: 104, output_var_2: 103 + //CHECK-NEXT:PUBLIC-METHOD: private_method.output_var: 105, output_var_2: 106, output_return_sum: 211 + //CHECK-NEXT:FB-EXTENDED: SUPER^.public_method.output_var: 105, output_var_2: 106, return_value: 211 + //CHECK-NEXT:PUBLIC-METHOD: private_method.output_var: 200, output_var_2: 205, output_return_sum: 405 + //CHECK-NEXT:FB-EXTENDED: THIS^.public_method.output_var: 200, output_var_2: 205, return_value: 405 + //CHECK-NEXT:INTERNAL-METHOD: protected_method.output_var: 0, output_var_2: 301 + //CHECK-NEXT:FB-EXTENDED: THIS^.internal_method.output_var_2: 301 + //CHECK-NEXT:NO-KEYWORD-METHOD: final_method.output_var: 0, output_var_2: 450 + //CHECK-NEXT:FB-EXTENDED: SUPER^.no_keyword_method.output_var: 400, output_var_2: 450 +END_FUNCTION \ No newline at end of file diff --git a/tests/lit/single/polymorphism/interfaces/type_array_arguments.st b/tests/lit/single/polymorphism/interfaces/type_array_arguments.st index ab7abf29dee..84f31391744 100644 --- a/tests/lit/single/polymorphism/interfaces/type_array_arguments.st +++ b/tests/lit/single/polymorphism/interfaces/type_array_arguments.st @@ -161,7 +161,7 @@ FUNCTION main // CHECK: FbA::input, in = [21, 22, 23, 24, 25] reference.input(localInputArray); - // CHECK-NEXT: FbA::output, out = [31, 32, 33, 34, 35] + // CHECK-NEXT: FbA::output, out = [0, 0, 0, 0, 0] // CHECK-NEXT: main::output = [90, 91, 92, 93, 94] // CHECK-NEXT: main::output = [31, 32, 33, 34, 35] reference.output(localOutputArray); @@ -202,7 +202,7 @@ FUNCTION main ); // CHECK-NEXT: FbA::in_out_and_inout, in = [21, 22, 23, 24, 25] - // CHECK-NEXT: FbA::in_out_and_inout, out = [31, 32, 33, 34, 35] + // CHECK-NEXT: FbA::in_out_and_inout, out = [0, 0, 0, 0, 0] // CHECK-NEXT: FbA::in_out_and_inout, inout = [41, 42, 43, 44, 45] // CHECK-NEXT: main::out_after_mix = [70, 71, 72, 73, 74] // CHECK-NEXT: main::inout_after_mix = [60, 61, 62, 63, 64] diff --git a/tests/lit/single/polymorphism/interfaces/type_string_arguments.st b/tests/lit/single/polymorphism/interfaces/type_string_arguments.st index a75aebda5a7..65f8223f520 100644 --- a/tests/lit/single/polymorphism/interfaces/type_string_arguments.st +++ b/tests/lit/single/polymorphism/interfaces/type_string_arguments.st @@ -107,7 +107,7 @@ FUNCTION main // CHECK-NEXT: FbA::input, in_variable = main, clean reference.input('input, literal', localInputVariable); - // CHECK-NEXT: FbA::output, out = main, clean + // CHECK-NEXT: FbA::output, out = // CHECK-NEXT: FbA::output, dirty // CHECK-NEXT: main, clean reference.output(localOutputVariable); @@ -125,7 +125,7 @@ FUNCTION main // CHECK-NEXT: FbA::in_out_and_inout, in_literal = in_out_and_inout, literal // CHECK-NEXT: FbA::in_out_and_inout, in_variable = main, clean - // CHECK-NEXT: FbA::in_out_and_inout, out = main, clean + // CHECK-NEXT: FbA::in_out_and_inout, out = // CHECK-NEXT: FbA::in_out_and_inout, inout = main, clean // CHECK-NEXT: out = FbA::in_out_and_inout, out dirty // CHECK-NEXT: inout = FbA::in_out_and_inout, inout dirty diff --git a/tests/lit/single/polymorphism/interfaces/type_struct_arguments.st b/tests/lit/single/polymorphism/interfaces/type_struct_arguments.st index 103cdc74f05..c4ecad27caa 100644 --- a/tests/lit/single/polymorphism/interfaces/type_struct_arguments.st +++ b/tests/lit/single/polymorphism/interfaces/type_struct_arguments.st @@ -187,7 +187,7 @@ FUNCTION main // CHECK: FbA::input, in = (id=201, name=input, fp=[201, 202, 203, 204, 205]) reference.input(localInputUser); - // CHECK-NEXT: FbA::output, out = (id=301, name=output_clean, fp=[301, 302, 303, 304, 305]) + // CHECK-NEXT: FbA::output, out = (id=0, name=, fp=[0, 0, 0, 0, 0]) // CHECK-NEXT: main::output = (id=900, name=output_dirty, fp=[901, 902, 903, 904, 905]) // CHECK-NEXT: main::output = (id=301, name=output_clean, fp=[301, 302, 303, 304, 305]) reference.output(localOutputUser); @@ -236,7 +236,7 @@ FUNCTION main ); // CHECK-NEXT: FbA::in_out_and_inout, in = (id=201, name=input, fp=[201, 202, 203, 204, 205]) - // CHECK-NEXT: FbA::in_out_and_inout, out = (id=301, name=output_clean, fp=[301, 302, 303, 304, 305]) + // CHECK-NEXT: FbA::in_out_and_inout, out = (id=0, name=, fp=[0, 0, 0, 0, 0]) // CHECK-NEXT: FbA::in_out_and_inout, inout = (id=401, name=inout_clean, fp=[401, 402, 403, 404, 405]) // CHECK-NEXT: main::out_after_mix = (id=700, name=out_dirty, fp=[701, 702, 703, 704, 705]) // CHECK-NEXT: main::inout_after_mix = (id=600, name=inout_dirty_2, fp=[601, 602, 603, 604, 605]) From abc05d13942afd5d8801faf1fe1bbc2768e04410 Mon Sep 17 00:00:00 2001 From: Angus Bethke Date: Thu, 1 Oct 2026 09:00:49 +0000 Subject: [PATCH 2/9] fix(codegen): construct function outputs like locals and skip VLA outputs --- book/technical/participants/06-init.md | 2 +- book/technical/pipeline/05-codegen.md | 4 ++-- book/user/language/functions.md | 2 +- compiler/plc_ast/src/ast.rs | 4 ++++ compiler/plc_lowering/src/initializer.rs | 21 ++++++++++++------- .../generators/expression_generator.rs | 18 ++++++---------- src/codegen/generators/pou_generator.rs | 5 +++-- src/codegen/tests/oop_tests.rs | 1 + ...ccepts_empty_statement_as_input_param.snap | 1 - ...cepts_empty_statement_as_output_param.snap | 1 + ...ests__function_empty_inout_assignment.snap | 1 + ...sts__function_empty_output_assignment.snap | 1 + ...tests__program_empty_inout_assignment.snap | 1 - ...od_access_keywords_and_output_defaults.st} | 2 +- 14 files changed, 36 insertions(+), 28 deletions(-) rename tests/lit/single/function_blocks/{no_keyword_error.st => method_access_keywords_and_output_defaults.st} (99%) diff --git a/book/technical/participants/06-init.md b/book/technical/participants/06-init.md index 677a7c7e843..88802ea943e 100644 --- a/book/technical/participants/06-init.md +++ b/book/technical/participants/06-init.md @@ -141,7 +141,7 @@ An array of a built-in type gets an empty constructor. An array literal that is ### Stack variables -Function and method locals, and `VAR_TEMP` variables in any POU, are initialized at the start of the POU body. These statements use no `self.` prefix. A function also constructs its return value when needed: `FUNCTION useLine: Point` with `localLine: Line` starts with `Line__ctor(localLine);` and `Point__ctor(useLine);`. `VAR_IN_OUT` variables use caller-owned storage and get no constructor call. +Function and method locals and outputs, and `VAR_TEMP` variables in any POU, are initialized at the start of the POU body. These statements use no `self.` prefix. A function also constructs its return value when needed: `FUNCTION useLine: Point` with `localLine: Line` starts with `Line__ctor(localLine);` and `Point__ctor(useLine);`. An output of a function or method is caller-owned storage like a return value, and gets the same treatment: codegen zero-fills it on entry, then the constructor call and the initializer assignment run. `VAR_IN_OUT` variables and variable length array outputs use caller-owned storage that the body must not reset, and get no constructor call. ### Linkage diff --git a/book/technical/pipeline/05-codegen.md b/book/technical/pipeline/05-codegen.md index 9591fa2ca40..2ab4b0d4809 100644 --- a/book/technical/pipeline/05-codegen.md +++ b/book/technical/pipeline/05-codegen.md @@ -207,7 +207,7 @@ In a function, `VAR_IN_OUT` and `VAR_OUTPUT` parameters are pointers, and an agg Functions are created in two passes, like structs: first a declaration for every POU the unit depends on, including POUs from other units, then the bodies of the POUs declared in this unit. If `scale` were in a second file, the module of `main` would contain `declare i32 @scale(i32, i16, ptr, ptr)` with no body. -A body starts by making every variable addressable, so the statements can treat both kinds alike. A function copies each argument into a stack slot, starts its return variable at zero, and starts each output at its initial value, or zero, through the address the caller passed; a stateful POU computes one pointer per member into the instance: +A body starts by making every variable addressable, so the statements can treat both kinds alike. A function copies each argument into a stack slot, starts its return variable at zero, and starts each output at its initial value, or zero, through the address the caller passed (a variable length array output keeps the caller's bounds and is not reset); a stateful POU computes one pointer per member into the instance: ```llvm define i32 @scale(i32 %0, i16 %1, ptr %2, ptr %3) { @@ -318,7 +318,7 @@ call void @llvm.memcpy.p0.p0.i32(ptr align 1 %text, ptr align 1 @utf08_literal_1 ### Calls to functions -Codegen places named and positional arguments in parameter order and fills omitted parameters with their defaults, or with zero when the parameter declares none; an omitted `VAR_OUTPUT` or `VAR_IN_OUT` gets the address of a zeroed temporary. It passes scalar inputs by value and `VAR_IN_OUT` and `VAR_OUTPUT` arguments by address. The callee writes through these addresses. For `i := scale(i, factor := 3, total := counter, overflow => flag)`: +Codegen places named and positional arguments in parameter order. A parameter without an argument, which validation allows for a trailing input with a default and for any parameter of a method, and a parameter whose argument is written empty (`overflow =>`), gets its default, or zero; a by-address parameter then receives a temporary holding that value. It passes scalar inputs by value and `VAR_IN_OUT` and `VAR_OUTPUT` arguments by address. The callee writes through these addresses. For `i := scale(i, factor := 3, total := counter, overflow => flag)`: ```llvm %call = call i32 @scale(i32 %load_i, i16 3, ptr @counter, ptr %flag) ; value, factor, address of total, address of overflow diff --git a/book/user/language/functions.md b/book/user/language/functions.md index 7c724797d94..dc422161c7b 100644 --- a/book/user/language/functions.md +++ b/book/user/language/functions.md @@ -65,7 +65,7 @@ END_FUNCTION `VAR_IN_OUT` is for data that the function reads and writes. It is always the caller's variable. -`VAR_OUTPUT` carries a second result out of the call. Every call starts the output at its initial value, or at zero when it has none, so the caller never reads a stale value from a path that does not assign it. +`VAR_OUTPUT` carries a second result out of the call. Every call starts the output at its initial value, or at zero when it has none, exactly like a local variable, so the caller never reads a stale value from a path that does not assign it. The function does this through the caller's variable when it starts, so do not pass one variable as an output and also as a `VAR_IN_OUT` or `VAR_INPUT {ref}` of the same call: the function would read it as zero. ## Calling diff --git a/compiler/plc_ast/src/ast.rs b/compiler/plc_ast/src/ast.rs index 84e53a680fd..2eda58cf0b1 100644 --- a/compiler/plc_ast/src/ast.rs +++ b/compiler/plc_ast/src/ast.rs @@ -556,6 +556,10 @@ impl VariableBlockType { pub fn is_inout(&self) -> bool { matches!(self, VariableBlockType::InOut) } + + pub fn is_output(&self) -> bool { + matches!(self, VariableBlockType::Output) + } } impl Display for VariableBlockType { diff --git a/compiler/plc_lowering/src/initializer.rs b/compiler/plc_lowering/src/initializer.rs index 3171ecc0cac..446da3ba1a8 100644 --- a/compiler/plc_lowering/src/initializer.rs +++ b/compiler/plc_lowering/src/initializer.rs @@ -246,7 +246,11 @@ impl AstVisitor for Initializer { }; let mut stmts = vec![]; let base = Self::get_base_ident(&variable_block_type, is_stateful); - if !variable_block_type.is_inout() { + let is_vla = variable + .data_type_declaration + .get_referenced_type() + .is_some_and(|it| index.get_type_information_or_void(it).is_vla()); + if !variable_block_type.is_inout() && !is_vla { if let Some(constructor) = variable .data_type_declaration .get_referenced_type() @@ -331,10 +335,7 @@ impl AstVisitor for Initializer { } } } - if variable_block_type.is_temp() - || (variable_block_type.is_local() && !is_stateful) - || variable_block_type.is_inout() - { + if Self::is_stack_variable(&variable_block_type, is_stateful) || variable_block_type.is_inout() { self.add_to_current_stack_constructor(stmts); } else { self.add_to_current_constructor(stmts); @@ -1032,9 +1033,15 @@ impl Initializer { None } + /// Temps, and locals and outputs of a stateless POU, live on the stack and are initialized on + /// every call; an output of a function or method is reset through the caller's address. + fn is_stack_variable(variable_block_type: &VariableBlockType, is_stateful: bool) -> bool { + variable_block_type.is_temp() + || ((variable_block_type.is_local() || variable_block_type.is_output()) && !is_stateful) + } + fn get_base_ident(variable_block_type: &VariableBlockType, is_stateful: bool) -> Option<&str> { - if variable_block_type.is_temp() - || (variable_block_type.is_local() && !is_stateful) + if Self::is_stack_variable(variable_block_type, is_stateful) || variable_block_type.is_inout() || variable_block_type.is_global() { diff --git a/src/codegen/generators/expression_generator.rs b/src/codegen/generators/expression_generator.rs index 73ec4c795fa..a5b2ad8f5d9 100644 --- a/src/codegen/generators/expression_generator.rs +++ b/src/codegen/generators/expression_generator.rs @@ -1313,22 +1313,16 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { declared_parameter: Option<&VariableIndexEntry>, ) -> Result, CodegenError> { if argument.is_empty_statement() { - // Uninitialized var_output / var_in_out + // an explicitly empty by-ref argument gets an initialized temporary, like an omitted one + if let Some(parameter) = declared_parameter { + return self.generate_empty_expression(parameter); + } let v_type = self .llvm_index .find_associated_type(type_name) .ok_or_else(|| Diagnostic::unknown_type(type_name, argument))?; - let ptr_value = self.llvm.builder.build_alloca(v_type, "")?; - if let Some(p) = declared_parameter { - if let Some(initial_value) = - self.get_initial_value(&p.initial_value, &self.get_parameter_type(p)) - { - let value = self.generate_expression(initial_value)?; - self.llvm.builder.build_store(ptr_value, value)?; - } - } - + self.llvm.builder.build_store(ptr_value, v_type.const_zero())?; return Ok(ptr_value.into()); } @@ -1590,7 +1584,7 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { match declaration_type { ArgumentType::ByVal(..) if parameter_is_reference_to => { - // REFERENCE TO parameters expect an address; pass the address of a null reference + // REFERENCE TO parameters expect an address; pass the address of a zeroed temporary let ptr_value = self.llvm.builder.build_alloca(parameter_type, "")?; self.llvm.builder.build_store(ptr_value, parameter_type.const_zero())?; Ok(ptr_value.as_basic_value_enum()) diff --git a/src/codegen/generators/pou_generator.rs b/src/codegen/generators/pou_generator.rs index 2b45b7089c8..2378dd8d542 100644 --- a/src/codegen/generators/pou_generator.rs +++ b/src/codegen/generators/pou_generator.rs @@ -961,11 +961,12 @@ impl<'ink, 'cg> PouGenerator<'ink, 'cg> { else { continue; }; - // reference and alias outputs are bound by the body, not reset + // reference and alias outputs are bound by the body, and a variable length array + // output holds the caller's bounds and data pointer, so neither is reset if self .index .find_effective_type_info(inner_type_name) - .is_some_and(|it| it.is_reference_to() || it.is_alias()) + .is_some_and(|it| it.is_reference_to() || it.is_alias() || it.is_vla()) { continue; } diff --git a/src/codegen/tests/oop_tests.rs b/src/codegen/tests/oop_tests.rs index d4800e84f6e..b6a0a7b0926 100644 --- a/src/codegen/tests/oop_tests.rs +++ b/src/codegen/tests/oop_tests.rs @@ -2543,6 +2543,7 @@ fn fb_extension_with_output() { %1 = load ptr, ptr %met1, align [filtered] %deref3 = load ptr, ptr %this, align [filtered] %2 = alloca i16, align [filtered] + store i16 0, ptr %2, align [filtered] %fnptr_call = call i16 %1(ptr %deref3, i16 0, i16 0, ptr %2) ret void } diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__function_accepts_empty_statement_as_input_param.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__function_accepts_empty_statement_as_input_param.snap index e88877437ca..c062ff81a07 100644 --- a/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__function_accepts_empty_statement_as_input_param.snap +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__function_accepts_empty_statement_as_input_param.snap @@ -1,6 +1,5 @@ --- source: src/codegen/tests/parameters_tests.rs -assertion_line: 593 expression: result --- ; ModuleID = '' diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__function_accepts_empty_statement_as_output_param.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__function_accepts_empty_statement_as_output_param.snap index 02533514233..b33e382a8f0 100644 --- a/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__function_accepts_empty_statement_as_output_param.snap +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__function_accepts_empty_statement_as_output_param.snap @@ -28,6 +28,7 @@ define void @main(ptr %0) { entry: %x = getelementptr inbounds nuw %main, ptr %0, i32 0, i32 0 %1 = alloca i32, align [filtered] + store i32 0, ptr %1, align [filtered] call void @foo(ptr %x, ptr %1) ret void } diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__function_empty_inout_assignment.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__function_empty_inout_assignment.snap index 435d5871047..7ae7a353360 100644 --- a/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__function_empty_inout_assignment.snap +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__function_empty_inout_assignment.snap @@ -34,6 +34,7 @@ entry: %var3 = getelementptr inbounds nuw %main, ptr %0, i32 0, i32 2 %load_var1 = load i32, ptr %var1, align [filtered] %1 = alloca i32, align [filtered] + store i32 0, ptr %1, align [filtered] %call = call i32 @foo(i32 %load_var1, ptr %var2, ptr %1) ret void } diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__function_empty_output_assignment.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__function_empty_output_assignment.snap index 9c1ec884f18..290288e64c8 100644 --- a/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__function_empty_output_assignment.snap +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__function_empty_output_assignment.snap @@ -34,6 +34,7 @@ entry: %var3 = getelementptr inbounds nuw %main, ptr %0, i32 0, i32 2 %load_var1 = load i32, ptr %var1, align [filtered] %1 = alloca i32, align [filtered] + store i32 0, ptr %1, align [filtered] %call = call i32 @foo(i32 %load_var1, ptr %1, ptr %var3) ret void } diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__program_empty_inout_assignment.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__program_empty_inout_assignment.snap index 5eb61a0d13e..94505484031 100644 --- a/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__program_empty_inout_assignment.snap +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__program_empty_inout_assignment.snap @@ -1,6 +1,5 @@ --- source: src/codegen/tests/parameters_tests.rs -assertion_line: 413 expression: result --- ; ModuleID = '' diff --git a/tests/lit/single/function_blocks/no_keyword_error.st b/tests/lit/single/function_blocks/method_access_keywords_and_output_defaults.st similarity index 99% rename from tests/lit/single/function_blocks/no_keyword_error.st rename to tests/lit/single/function_blocks/method_access_keywords_and_output_defaults.st index 6448822968f..8fa3d760c39 100644 --- a/tests/lit/single/function_blocks/no_keyword_error.st +++ b/tests/lit/single/function_blocks/method_access_keywords_and_output_defaults.st @@ -139,4 +139,4 @@ FUNCTION main //CHECK-NEXT:FB-EXTENDED: THIS^.internal_method.output_var_2: 301 //CHECK-NEXT:NO-KEYWORD-METHOD: final_method.output_var: 0, output_var_2: 450 //CHECK-NEXT:FB-EXTENDED: SUPER^.no_keyword_method.output_var: 400, output_var_2: 450 -END_FUNCTION \ No newline at end of file +END_FUNCTION From 9bb9b6b4315e3bbaf745bdd604f727905b7c3e93 Mon Sep 17 00:00:00 2001 From: Angus Bethke Date: Thu, 1 Oct 2026 10:20:50 +0000 Subject: [PATCH 3/9] fix: pr comments addressed --- book/user/language/functions.md | 2 +- book/user/language/methods-and-properties.md | 2 +- src/codegen/generators/pou_generator.rs | 8 ++++-- .../method_output_initializer_uses_this.st | 27 +++++++++++++++++++ 4 files changed, 35 insertions(+), 4 deletions(-) create mode 100644 tests/lit/single/function_blocks/method_output_initializer_uses_this.st diff --git a/book/user/language/functions.md b/book/user/language/functions.md index dc422161c7b..e09e7f4583c 100644 --- a/book/user/language/functions.md +++ b/book/user/language/functions.md @@ -65,7 +65,7 @@ END_FUNCTION `VAR_IN_OUT` is for data that the function reads and writes. It is always the caller's variable. -`VAR_OUTPUT` carries a second result out of the call. Every call starts the output at its initial value, or at zero when it has none, exactly like a local variable, so the caller never reads a stale value from a path that does not assign it. The function does this through the caller's variable when it starts, so do not pass one variable as an output and also as a `VAR_IN_OUT` or `VAR_INPUT {ref}` of the same call: the function would read it as zero. +`VAR_OUTPUT` carries a second result out of the call. Every call starts the output at its initial value, or at zero when it has none, exactly like a local variable, so the caller never reads a stale value from a path that does not assign it. An output that is a variable length array, a `REFERENCE TO`, or an alias (`AT`) is the exception: it keeps what the caller passed. The function does this through the caller's variable when it starts, so do not pass one variable as an output and also as a `VAR_IN_OUT` or `VAR_INPUT {ref}` of the same call: the function would read it as zero. ## Calling diff --git a/book/user/language/methods-and-properties.md b/book/user/language/methods-and-properties.md index d9b2ca9fc0c..c98211dd6b3 100644 --- a/book/user/language/methods-and-properties.md +++ b/book/user/language/methods-and-properties.md @@ -35,7 +35,7 @@ END_VAR value := inlet.Fill(amount := 3); ``` -`value` is now `3`, the new level. A method is called like a [function](functions.md#calling): the arguments come by position, in the order of the declaration, or by name. Give every parameter a value, because a parameter that the call leaves out takes the default value of its declaration, and a parameter without a default holds no defined value. The body of the block and its methods share the data of the instance, but a local variable of a method does not survive the call. +`value` is now `3`, the new level. A method is called like a [function](functions.md#calling): the arguments come by position, in the order of the declaration, or by name. A method call may leave out any parameter. The parameter then takes the default value of its declaration, or zero when it has none, and an output that the call leaves out is written to a temporary that the caller never sees. The body of the block and its methods share the data of the instance, but a local variable of a method does not survive the call. Inside a method, `THIS^` names the instance itself. You need it when a parameter and a member have the same name, because the parameter hides the member: diff --git a/src/codegen/generators/pou_generator.rs b/src/codegen/generators/pou_generator.rs index 2378dd8d542..e3ecc21e025 100644 --- a/src/codegen/generators/pou_generator.rs +++ b/src/codegen/generators/pou_generator.rs @@ -978,8 +978,12 @@ impl<'ink, 'cg> PouGenerator<'ink, 'cg> { let ptr_type = self.llvm.context.ptr_type(AddressSpace::from(ADDRESS_SPACE_GENERIC)); let output = self.llvm.builder.build_load(ptr_type, pointer_slot, "")?.into_pointer_value(); - let initializer = - self.index.get_const_expressions().maybe_get_constant_statement(&variable.initial_value); + // only a resolved constant is applied here; any other initializer is assigned by the + // lowered stack initializer, which runs with the instance in scope + let initializer = variable + .initial_value + .as_ref() + .and_then(|id| self.index.get_const_expressions().get_resolved_constant_statement(id)); self.llvm.generate_variable_initializer( self.llvm_index, self.index, diff --git a/tests/lit/single/function_blocks/method_output_initializer_uses_this.st b/tests/lit/single/function_blocks/method_output_initializer_uses_this.st new file mode 100644 index 00000000000..da60e98ea2e --- /dev/null +++ b/tests/lit/single/function_blocks/method_output_initializer_uses_this.st @@ -0,0 +1,27 @@ +// RUN: %COMPILE %s && %RUN | %CHECK %s +// An output initializer that needs the instance, such as ADR(THIS^), is applied by the lowered +// stack initializer of the method; the entry reset must not try to evaluate it. +FUNCTION_BLOCK Fb + VAR + x : INT := 5; + END_VAR + + METHOD m + VAR_OUTPUT + self_ptr : POINTER TO Fb := ADR(THIS^); + n : INT := 9; + END_VAR + END_METHOD +END_FUNCTION_BLOCK + +FUNCTION main + VAR + fb : Fb; + p : POINTER TO Fb; + k : INT := 1; + END_VAR + + fb.m(self_ptr => p, n => k); + printf('p^.x = %d, p = ADR(fb): %d, k = %d$N', p^.x, p = ADR(fb), k); + // CHECK: p^.x = 5, p = ADR(fb): 1, k = 9 +END_FUNCTION From 321b3c7204d167ee99ea56e4f7f343007c423cae Mon Sep 17 00:00:00 2001 From: Angus Bethke Date: Mon, 5 Oct 2026 06:44:33 +0000 Subject: [PATCH 4/9] fix: addressed PR comments --- book/technical/internals/00-pous.md | 6 ++- .../generators/expression_generator.rs | 25 ++++++---- src/codegen/tests/parameters_tests.rs | 48 +++++++++++++++++++ ...tests__program_empty_inout_assignment.snap | 10 ++-- 4 files changed, 72 insertions(+), 17 deletions(-) diff --git a/book/technical/internals/00-pous.md b/book/technical/internals/00-pous.md index 4d5204ad85a..28ed7dd39da 100644 --- a/book/technical/internals/00-pous.md +++ b/book/technical/internals/00-pous.md @@ -273,7 +273,7 @@ entry: The `%this` slot provides the instance pointer for `THIS^` in function blocks and their methods and actions. Programs and classes do not get this slot. A method or action of `Counter` computes the same member pointers, so `reset` writes directly to the instance. An action also gets a fresh stack slot for each `VAR_TEMP` of its owner, so a temp never carries a value from one call to the next. A method cannot reach a `VAR_TEMP` of its owner at all. -A function allocates stack slots for its parameters, locals, and return variable. It stores the incoming arguments in those slots, gives each local its initial value, and zeroes the return variable: +A function allocates stack slots for its parameters, locals, and return variable. It stores the incoming arguments in those slots, gives each local its initial value, zeroes the return variable, and starts each output at its initial value, or zero, through the address the caller passed: ```llvm define i32 @scale(i32 %0, i16 %1, ptr %2, ptr %3) { @@ -290,13 +290,15 @@ entry: %tmp = alloca i32, align 4 store i32 0, ptr %tmp, align 4 store i32 0, ptr %scale, align 4 + %4 = load ptr, ptr %overflow, align 8 + store i8 0, ptr %4, align 1 ... %scale_ret = load i32, ptr %scale, align 4 ret i32 %scale_ret } ``` -`count` and `overflow` hold pointers, and every access loads the pointer first and then the value behind it; that is the auto-dereference the resolver marked. Writing `overflow` writes the caller's `flag` directly. +`count` and `overflow` hold pointers, and every access loads the pointer first and then the value behind it; that is the auto-dereference the resolver marked. Writing `overflow` writes the caller's `flag` directly, and the reset at entry has already written zero there, so `flag` holds the output's default even on a path that never assigns it. ### Calls diff --git a/src/codegen/generators/expression_generator.rs b/src/codegen/generators/expression_generator.rs index d974acc9755..79a84b77305 100644 --- a/src/codegen/generators/expression_generator.rs +++ b/src/codegen/generators/expression_generator.rs @@ -1687,9 +1687,8 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { .expect("POU type for parameter struct must exist"); let pointer_to_param = self.build_parameter_struct_gep(pointee, param_context); - let parameter = self - .index - .find_parameter(param_context.declaring_pou, index) + let parameter_entry = self.index.find_parameter(param_context.declaring_pou, index); + let parameter = parameter_entry .and_then(|var| self.index.find_effective_type_by_name(var.get_type_name())) .map(|var| var.get_type_information()) .unwrap_or_else(|| self.index.get_void_type().get_type_information()); @@ -1698,13 +1697,19 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { //this is a VAR_IN_OUT assignment, so don't load the value, assign the pointer //expression may be empty -> generate a local variable for it let generated_exp = if expression.is_empty_statement() { - let temp_type = self - .llvm_index - .find_associated_type(inner_type_name) - .ok_or_else(|| Diagnostic::unknown_type(parameter.get_name(), expression))?; - let temp = builder.build_alloca(temp_type, "empty_varinout")?; - builder.build_store(temp, temp_type.const_zero())?; - temp.as_basic_value_enum() + match parameter_entry { + // the temporary starts at the parameter's default, or zero + Some(parameter_entry) => self.generate_empty_expression(parameter_entry)?, + None => { + let temp_type = self + .llvm_index + .find_associated_type(inner_type_name) + .ok_or_else(|| Diagnostic::unknown_type(parameter.get_name(), expression))?; + let temp = builder.build_alloca(temp_type, "empty_varinout")?; + builder.build_store(temp, temp_type.const_zero())?; + temp.as_basic_value_enum() + } + } } else { self.generate_lvalue(expression)?.as_basic_value_enum() }; diff --git a/src/codegen/tests/parameters_tests.rs b/src/codegen/tests/parameters_tests.rs index c6219ff512e..5970142505f 100644 --- a/src/codegen/tests/parameters_tests.rs +++ b/src/codegen/tests/parameters_tests.rs @@ -413,6 +413,54 @@ fn program_empty_inout_assignment() { filtered_assert_snapshot!(result); } +#[test] +fn program_empty_inout_assignment_takes_type_default() { + // GIVEN + let result = codegen( + " + TYPE Preset : DINT := 20; END_TYPE + + PROGRAM prog + VAR_IN_OUT + inout1 : Preset; + END_VAR + END_PROGRAM + + PROGRAM main + prog(inout1 := ); + END_PROGRAM + ", + ); + // THEN + filtered_assert_snapshot!(result, @r#" + ; ModuleID = '' + source_filename = "" + target datalayout = "[filtered]" + target triple = "[filtered]" + + %prog = type { ptr } + %main = type {} + + @prog_instance = global %prog zeroinitializer + @main_instance = global %main zeroinitializer + + define void @prog(ptr %0) { + entry: + %inout1 = getelementptr inbounds nuw %prog, ptr %0, i32 0, i32 0 + ret void + } + + define void @main(ptr %0) { + entry: + %1 = alloca i32, align [filtered] + store i32 20, ptr %1, align [filtered] + store ptr %1, ptr @prog_instance, align [filtered] + call void @prog(ptr @prog_instance) + ret void + } + "#); +} + #[test] fn program_missing_input_assignment() { // GIVEN diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__program_empty_inout_assignment.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__program_empty_inout_assignment.snap index 94505484031..5463231bd32 100644 --- a/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__program_empty_inout_assignment.snap +++ b/src/codegen/tests/snapshots/rusty__codegen__tests__parameters_tests__program_empty_inout_assignment.snap @@ -28,11 +28,11 @@ entry: %var3 = getelementptr inbounds nuw %main, ptr %0, i32 0, i32 2 %load_var1 = load i32, ptr %var1, align [filtered] store i32 %load_var1, ptr @prog_instance, align [filtered] - %empty_varinout = alloca i32, align [filtered] - store i32 0, ptr %empty_varinout, align [filtered] - store ptr %empty_varinout, ptr getelementptr inbounds (%prog, ptr @prog_instance, i32 0, i32 2), align [filtered] + %1 = alloca i32, align [filtered] + store i32 0, ptr %1, align [filtered] + store ptr %1, ptr getelementptr inbounds (%prog, ptr @prog_instance, i32 0, i32 2), align [filtered] call void @prog(ptr @prog_instance) - %1 = load i32, ptr getelementptr inbounds (%prog, ptr @prog_instance, i32 0, i32 1), align [filtered] - store i32 %1, ptr %var2, align [filtered] + %2 = load i32, ptr getelementptr inbounds (%prog, ptr @prog_instance, i32 0, i32 1), align [filtered] + store i32 %2, ptr %var2, align [filtered] ret void } From b54c8bd8cabf647ac7d7a082187ca3816ab49941 Mon Sep 17 00:00:00 2001 From: Angus Bethke Date: Mon, 5 Oct 2026 06:59:22 +0000 Subject: [PATCH 5/9] fix(codegen): leave aggregate output initializers to the lowering --- book/technical/pipeline/05-codegen.md | 2 +- book/user/language/functions.md | 4 ++- book/user/language/methods-and-properties.md | 2 +- src/codegen/generators/pou_generator.rs | 8 ++++-- .../output_aggregate_initializers.st | 27 +++++++++++++++++++ 5 files changed, 38 insertions(+), 5 deletions(-) create mode 100644 tests/lit/single/functions/output_aggregate_initializers.st diff --git a/book/technical/pipeline/05-codegen.md b/book/technical/pipeline/05-codegen.md index 1d9c32d5730..c6863fa5ddb 100644 --- a/book/technical/pipeline/05-codegen.md +++ b/book/technical/pipeline/05-codegen.md @@ -318,7 +318,7 @@ call void @llvm.memcpy.p0.p0.i32(ptr align 1 %text, ptr align 1 @utf08_literal_1 ### Calls to functions -Codegen places named and positional arguments in parameter order. A parameter without an argument, which validation allows for a trailing input with a default and for any parameter of a method, and a parameter whose argument is written empty (`overflow =>`), gets its default, or zero; a by-address parameter then receives a temporary holding that value. It passes scalar inputs by value and `VAR_IN_OUT` and `VAR_OUTPUT` arguments by address. The callee writes through these addresses. For `i := scale(i, factor := 3, total := counter, overflow => flag)`: +Codegen places named and positional arguments in parameter order. A parameter without an argument, which validation allows for a trailing input with a default of a function and for any input or output of a method but never for a `VAR_IN_OUT` or a `REFERENCE TO` input, and an output whose argument is written empty (`overflow =>`), gets its default, or zero; a by-address parameter then receives a temporary holding that value. It passes scalar inputs by value and `VAR_IN_OUT` and `VAR_OUTPUT` arguments by address. The callee writes through these addresses. For `i := scale(i, factor := 3, total := counter, overflow => flag)`: ```llvm %call = call i32 @scale(i32 %load_i, i16 3, ptr @counter, ptr %flag) ; value, factor, address of total, address of overflow diff --git a/book/user/language/functions.md b/book/user/language/functions.md index e09e7f4583c..9b51860d7cf 100644 --- a/book/user/language/functions.md +++ b/book/user/language/functions.md @@ -65,7 +65,7 @@ END_FUNCTION `VAR_IN_OUT` is for data that the function reads and writes. It is always the caller's variable. -`VAR_OUTPUT` carries a second result out of the call. Every call starts the output at its initial value, or at zero when it has none, exactly like a local variable, so the caller never reads a stale value from a path that does not assign it. An output that is a variable length array, a `REFERENCE TO`, or an alias (`AT`) is the exception: it keeps what the caller passed. The function does this through the caller's variable when it starts, so do not pass one variable as an output and also as a `VAR_IN_OUT` or `VAR_INPUT {ref}` of the same call: the function would read it as zero. +`VAR_OUTPUT` carries a second result out of the call. Every call starts the output at its initial value, or at zero when it has none, exactly like a local variable, so the caller never reads a stale value from a path that does not assign it. An output that is a variable length array, a `REFERENCE TO`, or an alias (`AT`) is the exception: it keeps what the caller passed. The function does this through the caller's variable when it starts, so do not pass one variable as an output and also as a `VAR_IN_OUT` or `VAR_INPUT {ref}` of the same call: the function would read it already reset to the output's initial value, or zero. ## Calling @@ -89,6 +89,8 @@ error[E031]: Expected a reference for parameter total because their type is InOu and it can accept a value where the parameter needs a variable, which then binds a temporary that the caller never sees. +An output argument may also be left empty, as in `scale(5, overflow => )`. The call then binds a temporary that starts at the parameter's initial value, or zero, and the function writes there instead of into a variable of the caller. A `VAR_IN_OUT` or `REFERENCE TO` argument cannot be empty. + A missing argument is an error, in both forms: ``` diff --git a/book/user/language/methods-and-properties.md b/book/user/language/methods-and-properties.md index c98211dd6b3..c1784ff519a 100644 --- a/book/user/language/methods-and-properties.md +++ b/book/user/language/methods-and-properties.md @@ -35,7 +35,7 @@ END_VAR value := inlet.Fill(amount := 3); ``` -`value` is now `3`, the new level. A method is called like a [function](functions.md#calling): the arguments come by position, in the order of the declaration, or by name. A method call may leave out any parameter. The parameter then takes the default value of its declaration, or zero when it has none, and an output that the call leaves out is written to a temporary that the caller never sees. The body of the block and its methods share the data of the instance, but a local variable of a method does not survive the call. +`value` is now `3`, the new level. A method is called like a [function](functions.md#calling): the arguments come by position, in the order of the declaration, or by name. A method call may leave out an input or an output, but it must supply every `VAR_IN_OUT` and every `REFERENCE TO` input. A parameter that the call leaves out takes the default value of its declaration, or zero when it has none, and an output that the call leaves out is written to a temporary that the caller never sees. The outputs that the call does supply start every call at their initial value, or zero, exactly as the outputs of a [function](functions.md#parameters) do, so the caller's variable never keeps a stale value from a path that does not assign it. The body of the block and its methods share the data of the instance, but a local variable of a method does not survive the call. Inside a method, `THIS^` names the instance itself. You need it when a parameter and a member have the same name, because the parameter hides the member: diff --git a/src/codegen/generators/pou_generator.rs b/src/codegen/generators/pou_generator.rs index a3841a5a7db..c3745165aed 100644 --- a/src/codegen/generators/pou_generator.rs +++ b/src/codegen/generators/pou_generator.rs @@ -935,11 +935,15 @@ impl<'ink, 'cg> PouGenerator<'ink, 'cg> { let ptr_type = self.llvm.context.ptr_type(AddressSpace::from(ADDRESS_SPACE_GENERIC)); let output = self.llvm.builder.build_load(ptr_type, pointer_slot, "")?.into_pointer_value(); - // only a resolved constant is applied here; any other initializer is assigned by the - // lowered stack initializer, which runs with the instance in scope + // only a resolved scalar constant is applied here; an aggregate or non-constant + // initializer is assigned by the lowered stack initializer, which runs with the + // instance in scope + let is_aggregate = + self.index.get_effective_type_or_void_by_name(inner_type_name).is_aggregate_type(); let initializer = variable .initial_value .as_ref() + .filter(|_| !is_aggregate) .and_then(|id| self.index.get_const_expressions().get_resolved_constant_statement(id)); self.llvm.generate_variable_initializer( self.llvm_index, diff --git a/tests/lit/single/functions/output_aggregate_initializers.st b/tests/lit/single/functions/output_aggregate_initializers.st new file mode 100644 index 00000000000..a9554526bb9 --- /dev/null +++ b/tests/lit/single/functions/output_aggregate_initializers.st @@ -0,0 +1,27 @@ +// RUN: %COMPILE %s && %RUN | %CHECK %s +// A struct literal, an array literal, and a string on a VAR_OUTPUT are applied by the lowered +// stack initializer of the function; the entry reset only zero-fills aggregates. +TYPE S : STRUCT + x : INT; + y : INT; +END_STRUCT END_TYPE + +FUNCTION f + VAR_OUTPUT + out : S := (x := 7); + arr : ARRAY[0..1] OF INT := [1, 2]; + s : STRING := 'abc'; + END_VAR +END_FUNCTION + +FUNCTION main + VAR + v : S; + a : ARRAY[0..1] OF INT; + str : STRING; + END_VAR + v.y := 5; + f(out => v, arr => a, s => str); + printf('v = (%d, %d), a = [%d, %d], s = %s$N', v.x, v.y, a[0], a[1], str); + // CHECK: v = (7, 0), a = [1, 2], s = abc +END_FUNCTION From 15b7f841135a03a940a7e4d12102adcb996eb0de Mon Sep 17 00:00:00 2001 From: Angus Bethke Date: Mon, 5 Oct 2026 12:10:16 +0000 Subject: [PATCH 6/9] fix: added some additional lit tests --- .../method_omitted_inputs_take_default.st | 33 +++++++++++++++++ .../method_output_reset_on_early_return.st | 36 +++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 tests/lit/single/function_blocks/method_omitted_inputs_take_default.st create mode 100644 tests/lit/single/function_blocks/method_output_reset_on_early_return.st diff --git a/tests/lit/single/function_blocks/method_omitted_inputs_take_default.st b/tests/lit/single/function_blocks/method_omitted_inputs_take_default.st new file mode 100644 index 00000000000..20822f8eeb7 --- /dev/null +++ b/tests/lit/single/function_blocks/method_omitted_inputs_take_default.st @@ -0,0 +1,33 @@ +// RUN: %COMPILE %s && %RUN | %CHECK %s +FUNCTION_BLOCK fb + VAR + idx : INT; + END_VAR + METHOD if_statement1 + VAR_INPUT + in : BOOL; + in2 : BOOL; + END_VAR + VAR_OUTPUT + out : INT; + END_VAR + + IF in THEN + out := 1; + ELSIF in2 THEN + out := 2; + ELSE + out := 3; + END_IF + END_METHOD +END_FUNCTION_BLOCK + +FUNCTION main + VAR + fb_instance : fb; + out : INT; + END_VAR + fb_instance.if_statement1(out=>out); + PRINTF('Out with no in selection: %d$N', out); + //CHECK:Out with no in selection: 3 +END_FUNCTION diff --git a/tests/lit/single/function_blocks/method_output_reset_on_early_return.st b/tests/lit/single/function_blocks/method_output_reset_on_early_return.st new file mode 100644 index 00000000000..e2deed4aadd --- /dev/null +++ b/tests/lit/single/function_blocks/method_output_reset_on_early_return.st @@ -0,0 +1,36 @@ +// RUN: %COMPILE %s && %RUN | %CHECK %s +FUNCTION_BLOCK fb + VAR + fb_var : DINT; + END_VAR + METHOD early_return + VAR_INPUT + early_exit : BOOL; + in_value : DINT; + END_VAR + VAR_OUTPUT + out_value : DINT; + END_VAR + + IF early_exit THEN + RETURN; + END_IF; + fb_var := in_value; + out_value := in_value; + END_METHOD +END_FUNCTION_BLOCK + +FUNCTION main + VAR + fb_instance : fb; + value : DINT; + END_VAR + + fb_instance.early_return(FALSE, 42, value); + PRINTF('Early exit: %d - out: %d - fb_var: %d$N', FALSE, value, fb_instance.fb_var); + //CHECK:Early exit: 0 - out: 42 - fb_var: 42 + + fb_instance.early_return(TRUE, 99, value); + PRINTF('Early exit: %d - out: %d - fb_var: %d$N', TRUE, value, fb_instance.fb_var); + //CHECK-NEXT:Early exit: 1 - out: 0 - fb_var: 42 +END_FUNCTION From f7aad8150ea52d008e7b2c0eaa0b2df7258d5840 Mon Sep 17 00:00:00 2001 From: Angus Bethke Date: Tue, 6 Oct 2026 07:04:16 +0000 Subject: [PATCH 7/9] fix: addressed further PR comments --- book/user/language/functions.md | 2 +- book/user/language/methods-and-properties.md | 2 +- compiler/plc_lowering/src/initializer.rs | 54 ++++++++++++++++++- .../generators/expression_generator.rs | 20 ++----- .../method_empty_body_output_default.st | 22 ++++++++ .../method_empty_statement_output_default.st | 23 ++++++++ .../method_nested_calls_output_defaults.st | 53 ++++++++++++++++++ .../functions/output_reset_on_every_call.st | 34 ++++++++++++ .../functions/output_scalar_type_defaults.st | 53 ++++++++++++++++++ .../single/vla/output_keeps_caller_bounds.st | 23 ++++++++ 10 files changed, 266 insertions(+), 20 deletions(-) create mode 100644 tests/lit/single/function_blocks/method_empty_body_output_default.st create mode 100644 tests/lit/single/function_blocks/method_empty_statement_output_default.st create mode 100644 tests/lit/single/function_blocks/method_nested_calls_output_defaults.st create mode 100644 tests/lit/single/functions/output_reset_on_every_call.st create mode 100644 tests/lit/single/functions/output_scalar_type_defaults.st create mode 100644 tests/lit/single/vla/output_keeps_caller_bounds.st diff --git a/book/user/language/functions.md b/book/user/language/functions.md index 9b51860d7cf..8dbc00a60ec 100644 --- a/book/user/language/functions.md +++ b/book/user/language/functions.md @@ -65,7 +65,7 @@ END_FUNCTION `VAR_IN_OUT` is for data that the function reads and writes. It is always the caller's variable. -`VAR_OUTPUT` carries a second result out of the call. Every call starts the output at its initial value, or at zero when it has none, exactly like a local variable, so the caller never reads a stale value from a path that does not assign it. An output that is a variable length array, a `REFERENCE TO`, or an alias (`AT`) is the exception: it keeps what the caller passed. The function does this through the caller's variable when it starts, so do not pass one variable as an output and also as a `VAR_IN_OUT` or `VAR_INPUT {ref}` of the same call: the function would read it already reset to the output's initial value, or zero. +`VAR_OUTPUT` carries a second result out of the call. Every call starts the output at its initial value, or at zero when it has none, exactly like a local variable, so the caller never reads a stale value from a path that does not assign it. An output that is a variable length array, a `REFERENCE TO`, or an alias (`AT`) is the exception: it keeps what the caller passed. The function does this through the caller's variable when it starts, so a variable that the body can reach in another way is already reset when the body reads it. That is the case when the caller passes one variable as an output and also as a `VAR_IN_OUT` or `VAR_INPUT {ref}` of the same call, and when the output target is a global variable, or an instance member in a method, that the body reads. `bump(o => g)` with the body `o := g + 1` therefore yields the initial value of `o` plus one, whatever `g` held before the call. Pass such a value in through an input instead. ## Calling diff --git a/book/user/language/methods-and-properties.md b/book/user/language/methods-and-properties.md index c1784ff519a..e932fd83646 100644 --- a/book/user/language/methods-and-properties.md +++ b/book/user/language/methods-and-properties.md @@ -35,7 +35,7 @@ END_VAR value := inlet.Fill(amount := 3); ``` -`value` is now `3`, the new level. A method is called like a [function](functions.md#calling): the arguments come by position, in the order of the declaration, or by name. A method call may leave out an input or an output, but it must supply every `VAR_IN_OUT` and every `REFERENCE TO` input. A parameter that the call leaves out takes the default value of its declaration, or zero when it has none, and an output that the call leaves out is written to a temporary that the caller never sees. The outputs that the call does supply start every call at their initial value, or zero, exactly as the outputs of a [function](functions.md#parameters) do, so the caller's variable never keeps a stale value from a path that does not assign it. The body of the block and its methods share the data of the instance, but a local variable of a method does not survive the call. +`value` is now `3`, the new level. A method is called like a [function](functions.md#calling): the arguments come by position, in the order of the declaration, or by name. A method call may leave out an input or an output, but it must supply every `VAR_IN_OUT` and every `REFERENCE TO` input. A parameter that the call leaves out takes the default value of its declaration, or zero when it has none, and an output that the call leaves out is written to a temporary that the caller never sees. The outputs that the call does supply start every call at their initial value, or zero, exactly as the outputs of a [function](functions.md#parameters) do, so the caller's variable never keeps a stale value from a path that does not assign it. The reset reaches the caller's variable before the body runs, so a member of the instance that the call passes as an output is already at its default when the body reads it. The body of the block and its methods share the data of the instance, but a local variable of a method does not survive the call. Inside a method, `THIS^` names the instance itself. You need it when a parameter and a member have the same name, because the parameter hides the member: diff --git a/compiler/plc_lowering/src/initializer.rs b/compiler/plc_lowering/src/initializer.rs index 446da3ba1a8..a366500183a 100644 --- a/compiler/plc_lowering/src/initializer.rs +++ b/compiler/plc_lowering/src/initializer.rs @@ -250,7 +250,17 @@ impl AstVisitor for Initializer { .data_type_declaration .get_referenced_type() .is_some_and(|it| index.get_type_information_or_void(it).is_vla()); - if !variable_block_type.is_inout() && !is_vla { + // a scalar output of a function or method needs no constructor: codegen resets it to the + // default of its type, and a constructor call on it would fail the by-ref type check + // for alias types + let is_scalar_output = variable_block_type.is_output() + && !is_stateful + && !variable + .data_type_declaration + .get_referenced_type() + .and_then(|it| index.find_effective_type_by_name(it)) + .is_some_and(|it| it.is_aggregate_type()); + if !variable_block_type.is_inout() && !is_vla && !is_scalar_output { if let Some(constructor) = variable .data_type_declaration .get_referenced_type() @@ -1642,6 +1652,48 @@ mod tests { "); } + #[test] + fn function_and_method_outputs_are_in_stack_constructor() { + let src = r#" + TYPE MyStruct : STRUCT + a : INT := 5; + END_STRUCT + END_TYPE + TYPE MyInt : INT; END_TYPE + + FUNCTION MyFunction + VAR_OUTPUT + outStruct : MyStruct; + outAlias : MyInt; + outDefault : INT := 3; + END_VAR + END_FUNCTION + + FUNCTION_BLOCK MyFb + METHOD MyMethod + VAR_OUTPUT + outStruct : MyStruct; + outDefault : INT := 3; + END_VAR + END_METHOD + END_FUNCTION_BLOCK + "#; + + let initializer = parse_and_init(src); + // Outputs of a function are stack variables: the struct gets a constructor call, the + // default an assignment, and the scalar alias nothing (codegen resets it) + insta::assert_snapshot!(print_body_to_string(initializer.stack_constructor.get("MyFunction").unwrap()), @" + intern: + MyStruct__ctor(outStruct) + outDefault := 3 + "); + insta::assert_snapshot!(print_body_to_string(initializer.stack_constructor.get("MyFb.MyMethod").unwrap()), @" + intern: + MyStruct__ctor(outStruct) + outDefault := 3 + "); + } + #[test] fn program_temp_variables_are_in_stack_constructor() { let src = r#" diff --git a/src/codegen/generators/expression_generator.rs b/src/codegen/generators/expression_generator.rs index 79a84b77305..1978a14eb49 100644 --- a/src/codegen/generators/expression_generator.rs +++ b/src/codegen/generators/expression_generator.rs @@ -1351,10 +1351,9 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { } }; - // ...check if we can bitcast a reference to their hinted type + // ...wrap it into a fat pointer when the parameter is a variable length array if let Some(hint) = self.annotations.get_type_hint(argument, self.index) { let actual_type = self.annotations.get_type_or_void(argument, self.index); - let actual_type_info = self.index.find_elementary_pointer_type(&actual_type.information); let target_type_info = self.index.find_elementary_pointer_type(&hint.information); if target_type_info.is_vla() { @@ -1369,23 +1368,10 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { self.annotations.get(argument) ); }; - - // From https://llvm.org/docs/LangRef.html#bitcast-to-instruction: The ‘bitcast’ instruction takes - // a value to cast, which must be a **non-aggregate** first class value, and a type to cast it to, - // which must also be a non-aggregate first class type. - if !actual_type_info.is_aggregate() - && !target_type_info.is_aggregate() - && actual_type_info != target_type_info - { - return Ok(self.llvm.builder.build_bit_cast( - value, - self.llvm_index.get_associated_type(hint.get_name())?, - "", - )?); - } } - // ...otherwise no bitcasting was needed, thus return the generated element pointer as is + // ...otherwise the element pointer is passed as is: with opaque pointers a cast between + // pointer types is a no-op, and a cast to the value type is invalid Ok(value.into()) } diff --git a/tests/lit/single/function_blocks/method_empty_body_output_default.st b/tests/lit/single/function_blocks/method_empty_body_output_default.st new file mode 100644 index 00000000000..d078bfef866 --- /dev/null +++ b/tests/lit/single/function_blocks/method_empty_body_output_default.st @@ -0,0 +1,22 @@ +// RUN: %COMPILE %s && %RUN | %CHECK %s +FUNCTION_BLOCK fb + METHOD empty_method + VAR_OUTPUT + out : BOOL := TRUE; + END_VAR + END_METHOD +END_FUNCTION_BLOCK + +FUNCTION main + VAR + fb_instance : fb; + out : BOOL; + END_VAR + + fb_instance.empty_method(out); + + IF out THEN + PRINTF('Method with empty body called successfully!$N'); + END_IF + //CHECK: Method with empty body called successfully! +END_FUNCTION diff --git a/tests/lit/single/function_blocks/method_empty_statement_output_default.st b/tests/lit/single/function_blocks/method_empty_statement_output_default.st new file mode 100644 index 00000000000..3f01d0b1b90 --- /dev/null +++ b/tests/lit/single/function_blocks/method_empty_statement_output_default.st @@ -0,0 +1,23 @@ +// RUN: %COMPILE %s && %RUN | %CHECK %s +FUNCTION_BLOCK fb + METHOD empty_statement_method + VAR_OUTPUT + out : BOOL := TRUE; + END_VAR + ; + END_METHOD +END_FUNCTION_BLOCK + +FUNCTION main + VAR + fb_instance : fb; + out : BOOL; + END_VAR + + fb_instance.empty_statement_method(out); + + IF out THEN + PRINTF('Method with empty statement called successfully!$N'); + END_IF + //CHECK: Method with empty statement called successfully! +END_FUNCTION diff --git a/tests/lit/single/function_blocks/method_nested_calls_output_defaults.st b/tests/lit/single/function_blocks/method_nested_calls_output_defaults.st new file mode 100644 index 00000000000..74c165dfb20 --- /dev/null +++ b/tests/lit/single/function_blocks/method_nested_calls_output_defaults.st @@ -0,0 +1,53 @@ +// RUN: %COMPILE %s && %RUN | %CHECK %s +FUNCTION_BLOCK fb + METHOD myMethod + VAR_OUTPUT + out_method : BOOL; + out_fb : BOOL; + out_func : BOOL; + END_VAR + VAR + fb_instance : fb2; + END_VAR + fb_instance.myMethod(out_method); + fb_instance(out_fb); + out_func := func(); + END_METHOD +END_FUNCTION_BLOCK +FUNCTION_BLOCK fb2 + VAR_OUTPUT + fb_out : BOOL := TRUE; + END_VAR + METHOD myMethod + VAR_OUTPUT + m_out : BOOL := TRUE; + END_VAR + END_METHOD +END_FUNCTION_BLOCK +FUNCTION func : BOOL + func := TRUE; +END_FUNCTION + +FUNCTION main + VAR + fb_instance : fb; + out_method : BOOL; + out_fb : BOOL; + out_func : BOOL; + END_VAR + + fb_instance.myMethod(out_method, out_fb, out_func); + + IF out_method THEN + PRINTF('Method within a method called successfully!$N'); + END_IF + //CHECK:Method within a method called successfully! + IF out_fb THEN + PRINTF('Function block within a method called successfully!$N'); + END_IF + //CHECK-NEXT:Function block within a method called successfully! + IF out_func THEN + PRINTF('Function within a method called successfully!$N'); + END_IF + //CHECK-NEXT:Function within a method called successfully! +END_FUNCTION diff --git a/tests/lit/single/functions/output_reset_on_every_call.st b/tests/lit/single/functions/output_reset_on_every_call.st new file mode 100644 index 00000000000..ae4f85414fe --- /dev/null +++ b/tests/lit/single/functions/output_reset_on_every_call.st @@ -0,0 +1,34 @@ +// RUN: %COMPILE %s && %RUN | %CHECK %s +// A function output that a path does not assign comes back at its default on every call, not at +// the value a previous call left in the caller's variable. +FUNCTION clamp_flag + VAR_INPUT + value : INT; + END_VAR + VAR_OUTPUT + too_big : BOOL; + END_VAR + IF value > 2 THEN + too_big := TRUE; + END_IF +END_FUNCTION + +FUNCTION main + VAR + i : INT; + flag : BOOL; + END_VAR + FOR i := 1 TO 5 DO + clamp_flag(i, flag); + printf('%d: %d$N', i, flag); + END_FOR + // CHECK: 1: 0 + // CHECK-NEXT: 2: 0 + // CHECK-NEXT: 3: 1 + // CHECK-NEXT: 4: 1 + // CHECK-NEXT: 5: 1 + flag := TRUE; + clamp_flag(1, flag); + printf('after: %d$N', flag); + // CHECK-NEXT: after: 0 +END_FUNCTION diff --git a/tests/lit/single/functions/output_scalar_type_defaults.st b/tests/lit/single/functions/output_scalar_type_defaults.st new file mode 100644 index 00000000000..cfba910c7d1 --- /dev/null +++ b/tests/lit/single/functions/output_scalar_type_defaults.st @@ -0,0 +1,53 @@ +// RUN: %COMPILE %s && %RUN | %CHECK %s +// Outputs of an alias, subrange, enum, or struct type start every call at the default of their +// type, in a function and in a method alike. A scalar output needs no constructor call, so an +// alias of an elementary type must compile as an output. +TYPE MyInt : INT; END_TYPE +TYPE Preset : DINT := 20; END_TYPE +TYPE Pct : INT(0..100) := 50; END_TYPE +TYPE Color : (Red, Green := 5, Blue) := Green; END_TYPE +TYPE S : STRUCT + a : INT := 7; +END_STRUCT END_TYPE + +FUNCTION_BLOCK Fb + METHOD m + VAR_OUTPUT + p : MyInt; + q : Preset; + r : Pct; + c : Color; + st : S; + END_VAR + END_METHOD +END_FUNCTION_BLOCK + +FUNCTION f + VAR_OUTPUT + p : MyInt; + q : Preset; + r : Pct; + c : Color; + st : S; + END_VAR +END_FUNCTION + +FUNCTION main + VAR + fb : Fb; + a : MyInt; + b : Preset; + r : Pct; + c : Color; + st : S; + END_VAR + a := 1; b := 1; r := 1; c := Blue; st.a := 1; + f(p => a, q => b, r => r, c => c, st => st); + printf('function: a = %d, b = %d, r = %d, c = %d, st.a = %d$N', a, b, r, c, st.a); + // CHECK: function: a = 0, b = 20, r = 50, c = 5, st.a = 7 + + a := 1; b := 1; r := 1; c := Blue; st.a := 1; + fb.m(p => a, q => b, r => r, c => c, st => st); + printf('method: a = %d, b = %d, r = %d, c = %d, st.a = %d$N', a, b, r, c, st.a); + // CHECK-NEXT: method: a = 0, b = 20, r = 50, c = 5, st.a = 7 +END_FUNCTION diff --git a/tests/lit/single/vla/output_keeps_caller_bounds.st b/tests/lit/single/vla/output_keeps_caller_bounds.st new file mode 100644 index 00000000000..7b69a09ae1d --- /dev/null +++ b/tests/lit/single/vla/output_keeps_caller_bounds.st @@ -0,0 +1,23 @@ +// RUN: %COMPILE %s && %RUN | %CHECK %s +// A variable length array output is not reset at entry: it keeps the bounds and the data +// pointer the caller passed, so the function can write into the caller's array. +FUNCTION fill + VAR_OUTPUT + arr : ARRAY[*] OF INT; + END_VAR + VAR + i : DINT; + END_VAR + FOR i := LOWER_BOUND(arr, 1) TO UPPER_BOUND(arr, 1) DO + arr[i] := i * 10; + END_FOR +END_FUNCTION + +FUNCTION main + VAR + data : ARRAY[2..4] OF INT; + END_VAR + fill(arr => data); + printf('%d %d %d$N', data[2], data[3], data[4]); + // CHECK: 20 30 40 +END_FUNCTION From 8c6aa495abf60e84a3c541bcf75adc3956842cd4 Mon Sep 17 00:00:00 2001 From: Angus Bethke Date: Tue, 6 Oct 2026 08:57:08 +0000 Subject: [PATCH 8/9] fix: skipping every scalar output constructor loses address-valued type defaults --- book/technical/internals/00-pous.md | 2 +- book/technical/participants/06-init.md | 2 +- book/technical/pipeline/05-codegen.md | 2 +- compiler/plc_lowering/src/initializer.rs | 19 +- .../method_body_declaration_var_usage.st | 1591 +++++++++++++++++ .../functions/output_pointer_type_default.st | 38 + 6 files changed, 1645 insertions(+), 9 deletions(-) create mode 100644 tests/lit/single/function_blocks/method_body_declaration_var_usage.st create mode 100644 tests/lit/single/functions/output_pointer_type_default.st diff --git a/book/technical/internals/00-pous.md b/book/technical/internals/00-pous.md index 28ed7dd39da..2b0d26fbaf3 100644 --- a/book/technical/internals/00-pous.md +++ b/book/technical/internals/00-pous.md @@ -273,7 +273,7 @@ entry: The `%this` slot provides the instance pointer for `THIS^` in function blocks and their methods and actions. Programs and classes do not get this slot. A method or action of `Counter` computes the same member pointers, so `reset` writes directly to the instance. An action also gets a fresh stack slot for each `VAR_TEMP` of its owner, so a temp never carries a value from one call to the next. A method cannot reach a `VAR_TEMP` of its owner at all. -A function allocates stack slots for its parameters, locals, and return variable. It stores the incoming arguments in those slots, gives each local its initial value, zeroes the return variable, and starts each output at its initial value, or zero, through the address the caller passed: +A function allocates stack slots for its parameters, locals, and return variable. It stores the incoming arguments in those slots, gives each local its initial value, zeroes the return variable, and starts each output at its initial value, or zero, through the address the caller passed, except a variable length array, `REFERENCE TO`, or alias output, which keeps what the caller passed: ```llvm define i32 @scale(i32 %0, i16 %1, ptr %2, ptr %3) { diff --git a/book/technical/participants/06-init.md b/book/technical/participants/06-init.md index 88802ea943e..42603d72e5e 100644 --- a/book/technical/participants/06-init.md +++ b/book/technical/participants/06-init.md @@ -141,7 +141,7 @@ An array of a built-in type gets an empty constructor. An array literal that is ### Stack variables -Function and method locals and outputs, and `VAR_TEMP` variables in any POU, are initialized at the start of the POU body. These statements use no `self.` prefix. A function also constructs its return value when needed: `FUNCTION useLine: Point` with `localLine: Line` starts with `Line__ctor(localLine);` and `Point__ctor(useLine);`. An output of a function or method is caller-owned storage like a return value, and gets the same treatment: codegen zero-fills it on entry, then the constructor call and the initializer assignment run. `VAR_IN_OUT` variables and variable length array outputs use caller-owned storage that the body must not reset, and get no constructor call. +Function and method locals and outputs, and `VAR_TEMP` variables in any POU, are initialized at the start of the POU body. These statements use no `self.` prefix. A function also constructs its return value when needed: `FUNCTION useLine: Point` with `localLine: Line` starts with `Line__ctor(localLine);` and `Point__ctor(useLine);`. An output of a function or method is caller-owned storage like a return value, and gets the same treatment: codegen zero-fills it on entry, then the constructor call and the initializer assignment run. `VAR_IN_OUT` variables and variable length array outputs use caller-owned storage that the body must not reset, and get no constructor call; a `REFERENCE TO` or alias output is bound by the body and is not reset either. ### Linkage diff --git a/book/technical/pipeline/05-codegen.md b/book/technical/pipeline/05-codegen.md index c6863fa5ddb..8db4ae6d700 100644 --- a/book/technical/pipeline/05-codegen.md +++ b/book/technical/pipeline/05-codegen.md @@ -207,7 +207,7 @@ In a function, `VAR_IN_OUT` and `VAR_OUTPUT` parameters are pointers, and an agg Functions are created in two passes, like structs: first a declaration for every POU the unit depends on, including POUs from other units, then the bodies of the POUs declared in this unit. If `scale` were in a second file, the module of `main` would contain `declare i32 @scale(i32, i16, ptr, ptr)` with no body. -A body starts by making every variable addressable, so the statements can treat both kinds alike. A function copies each argument into a stack slot, starts its return variable at zero, and starts each output at its initial value, or zero, through the address the caller passed (a variable length array output keeps the caller's bounds and is not reset); a stateful POU computes one pointer per member into the instance: +A body starts by making every variable addressable, so the statements can treat both kinds alike. A function copies each argument into a stack slot, starts its return variable at zero, and starts each output at its initial value, or zero, through the address the caller passed (a variable length array output keeps the caller's bounds, and a `REFERENCE TO` or alias output is bound by the body, so neither is reset); a stateful POU computes one pointer per member into the instance: ```llvm define i32 @scale(i32 %0, i16 %1, ptr %2, ptr %3) { diff --git a/compiler/plc_lowering/src/initializer.rs b/compiler/plc_lowering/src/initializer.rs index a366500183a..32af429d87b 100644 --- a/compiler/plc_lowering/src/initializer.rs +++ b/compiler/plc_lowering/src/initializer.rs @@ -250,16 +250,17 @@ impl AstVisitor for Initializer { .data_type_declaration .get_referenced_type() .is_some_and(|it| index.get_type_information_or_void(it).is_vla()); - // a scalar output of a function or method needs no constructor: codegen resets it to the - // default of its type, and a constructor call on it would fail the by-ref type check - // for alias types + // a non-pointer scalar output of a function or method needs no constructor: codegen + // resets it to the constant default of its type, and a constructor call on it would fail + // the by-ref type check for alias types. A pointer output keeps its constructor, which + // applies an address-valued default such as ADR(g). let is_scalar_output = variable_block_type.is_output() && !is_stateful && !variable .data_type_declaration .get_referenced_type() .and_then(|it| index.find_effective_type_by_name(it)) - .is_some_and(|it| it.is_aggregate_type()); + .is_some_and(|it| it.is_aggregate_type() || it.get_type_information().is_pointer()); if !variable_block_type.is_inout() && !is_vla && !is_scalar_output { if let Some(constructor) = variable .data_type_declaration @@ -1660,11 +1661,16 @@ mod tests { END_STRUCT END_TYPE TYPE MyInt : INT; END_TYPE + TYPE MyPtr : POINTER TO INT := ADR(gVar); END_TYPE + VAR_GLOBAL + gVar : INT; + END_VAR FUNCTION MyFunction VAR_OUTPUT outStruct : MyStruct; outAlias : MyInt; + outPointer : MyPtr; outDefault : INT := 3; END_VAR END_FUNCTION @@ -1680,11 +1686,12 @@ mod tests { "#; let initializer = parse_and_init(src); - // Outputs of a function are stack variables: the struct gets a constructor call, the - // default an assignment, and the scalar alias nothing (codegen resets it) + // Outputs of a function are stack variables: the struct and the pointer get a constructor + // call, the default an assignment, and the scalar alias nothing (codegen resets it) insta::assert_snapshot!(print_body_to_string(initializer.stack_constructor.get("MyFunction").unwrap()), @" intern: MyStruct__ctor(outStruct) + MyPtr__ctor(outPointer) outDefault := 3 "); insta::assert_snapshot!(print_body_to_string(initializer.stack_constructor.get("MyFb.MyMethod").unwrap()), @" diff --git a/tests/lit/single/function_blocks/method_body_declaration_var_usage.st b/tests/lit/single/function_blocks/method_body_declaration_var_usage.st new file mode 100644 index 00000000000..2c52ac0117d --- /dev/null +++ b/tests/lit/single/function_blocks/method_body_declaration_var_usage.st @@ -0,0 +1,1591 @@ +// RUN: %COMPILE %s && %RUN | %CHECK %s +FUNCTION_BLOCK fb + VAR_INPUT + fb_input_sint : SINT; + fb_input_int : INT; + fb_input_dint : DINT; + fb_input_lint : LINT := 4; + fb_input_usint : USINT; + fb_input_uint : UINT; + fb_input_udint : UDINT := 7; + fb_input_ulint : ULINT; + fb_input_word : WORD := 16#9; + fb_input_dword : DWORD; + fb_input_lword : LWORD; + fb_input_bool : BOOL := TRUE; + fb_input_time : TIME; + fb_input_ltime : LTIME := LT#13s; + END_VAR + VAR_OUTPUT + fb_output_sint : SINT := 1; + fb_output_int : INT; + fb_output_dint : DINT; + fb_output_lint : LINT; + fb_output_usint : USINT; + fb_output_uint : UINT := 6; + fb_output_udint : UDINT; + fb_output_ulint : ULINT; + fb_output_word : WORD; + fb_output_dword : DWORD; + fb_output_lword : LWORD := 16#B; + fb_output_bool : BOOL; + fb_output_time : TIME := T#12s; + fb_output_ltime : LTIME; + END_VAR + VAR PUBLIC + fb_public_sint : SINT; + fb_public_int : INT := 2; + fb_public_dint : DINT; + fb_public_lint : LINT; + fb_public_usint : USINT := 5; + fb_public_uint : UINT; + fb_public_udint : UDINT; + fb_public_ulint : ULINT; + fb_public_word : WORD; + fb_public_dword : DWORD := 16#A; + fb_public_lword : LWORD; + fb_public_bool : BOOL := TRUE; + fb_public_time : TIME; + fb_public_ltime : LTIME; + END_VAR + VAR PROTECTED + fb_protected_sint : SINT; + fb_protected_int : INT; + fb_protected_dint : DINT := 3; + fb_protected_lint : LINT; + fb_protected_usint : USINT; + fb_protected_uint : UINT; + fb_protected_udint : UDINT; + fb_protected_ulint : ULINT := 8; + fb_protected_word : WORD; + fb_protected_dword : DWORD; + fb_protected_lword : LWORD; + fb_protected_bool : BOOL; + fb_protected_time : TIME := T#42ms; + fb_protected_ltime : LTIME; + END_VAR + VAR INTERNAL + fb_internal_sint : SINT := 11; + fb_internal_int : INT; + fb_internal_dint : DINT; + fb_internal_lint : LINT; + fb_internal_usint : USINT := 12; + fb_internal_uint : UINT; + fb_internal_udint : UDINT; + fb_internal_ulint : ULINT; + fb_internal_word : WORD := 16#D; + fb_internal_dword : DWORD; + fb_internal_lword : LWORD; + fb_internal_bool : BOOL := TRUE; + fb_internal_time : TIME; + fb_internal_ltime : LTIME := LT#1s; + END_VAR + VAR PRIVATE + fb_private_sint : SINT; + fb_private_int : INT := 21; + fb_private_dint : DINT; + fb_private_lint : LINT; + fb_private_usint : USINT; + fb_private_uint : UINT := 22; + fb_private_udint : UDINT; + fb_private_ulint : ULINT; + fb_private_word : WORD; + fb_private_dword : DWORD := 16#17; + fb_private_lword : LWORD; + fb_private_bool : BOOL; + fb_private_time : TIME; + fb_private_ltime : LTIME; + END_VAR + VAR CONSTANT + fb_constant_sint : SINT := 1; + fb_constant_int : INT := 2; + fb_constant_dint : DINT := 3; + fb_constant_lint : LINT := 4; + fb_constant_usint : USINT := 5; + fb_constant_uint : UINT := 6; + fb_constant_udint : UDINT := 7; + fb_constant_ulint : ULINT := 8; + fb_constant_word : WORD := 16#9; + fb_constant_dword : DWORD := 16#A; + fb_constant_lword : LWORD := 16#B; + fb_constant_bool : BOOL := TRUE; + fb_constant_time : TIME := T#13ms; + fb_constant_ltime : LTIME := LT#14us; + END_VAR + METHOD myMethod + VAR_INPUT + m_input_sint : SINT; + m_input_int : INT := 2; + m_input_dint : DINT; + m_input_lint : LINT; + m_input_usint : USINT; + m_input_uint : UINT := 6; + m_input_udint : UDINT; + m_input_ulint : ULINT; + m_input_word : WORD; + m_input_dword : DWORD := 16#A; + m_input_lword : LWORD; + m_input_bool : BOOL; + m_input_time : TIME := T#12s; + m_input_ltime : LTIME; + END_VAR + VAR_OUTPUT + m_output_sint : SINT; + m_output_int : INT; + m_output_dint : DINT := 3; + m_output_lint : LINT; + m_output_usint : USINT; + m_output_uint : UINT; + m_output_udint : UDINT; + m_output_ulint : ULINT := 8; + m_output_word : WORD; + m_output_dword : DWORD; + m_output_lword : LWORD := 16#B; + m_output_bool : BOOL := TRUE; + m_output_time : TIME; + m_output_ltime : LTIME := LT#13us; + END_VAR + VAR + m_var_sint : SINT := 1; + m_var_int : INT; + m_var_dint : DINT; + m_var_lint : LINT; + m_var_usint : USINT := 5; + m_var_uint : UINT; + m_var_udint : UDINT; + m_var_ulint : ULINT; + m_var_word : WORD := 16#9; + m_var_dword : DWORD; + m_var_lword : LWORD; + m_var_bool : BOOL := TRUE; + m_var_time : TIME := T#12ms; + m_var_ltime : LTIME := LT#13ns; + END_VAR + + PRINTF('Checking fb variable after call of method:$N'); + PRINTF('- fb_input_sint: %hhd$N', fb_input_sint); + PRINTF('- fb_input_int: %hd$N', fb_input_int); + PRINTF('- fb_input_dint: %d$N', fb_input_dint); + PRINTF('- fb_input_lint: %lld$N', fb_input_lint); + PRINTF('- fb_input_usint: %hhu$N', fb_input_usint); + PRINTF('- fb_input_uint: %hu$N', fb_input_uint); + PRINTF('- fb_input_udint: %u$N', fb_input_udint); + PRINTF('- fb_input_ulint: %llu$N', fb_input_ulint); + PRINTF('- fb_input_word: %hu$N', fb_input_word); + PRINTF('- fb_input_dword: %u$N', fb_input_dword); + PRINTF('- fb_input_lword: %llu$N', fb_input_lword); + PRINTF('- fb_input_bool: %hhu$N', fb_input_bool); + PRINTF('- fb_input_time: %u$N', fb_input_time); + PRINTF('- fb_input_ltime: %llu$N', fb_input_ltime); + + PRINTF('- fb_output_sint: %hhd$N', fb_output_sint); + PRINTF('- fb_output_int: %hd$N', fb_output_int); + PRINTF('- fb_output_dint: %d$N', fb_output_dint); + PRINTF('- fb_output_lint: %lld$N', fb_output_lint); + PRINTF('- fb_output_usint: %hhu$N', fb_output_usint); + PRINTF('- fb_output_uint: %hu$N', fb_output_uint); + PRINTF('- fb_output_udint: %u$N', fb_output_udint); + PRINTF('- fb_output_ulint: %llu$N', fb_output_ulint); + PRINTF('- fb_output_word: %hu$N', fb_output_word); + PRINTF('- fb_output_dword: %u$N', fb_output_dword); + PRINTF('- fb_output_lword: %llu$N', fb_output_lword); + PRINTF('- fb_output_bool: %hhu$N', fb_output_bool); + PRINTF('- fb_output_time: %u$N', fb_output_time); + PRINTF('- fb_output_ltime: %llu$N', fb_output_ltime); + + PRINTF('- fb_public_sint: %hhd$N', fb_public_sint); + PRINTF('- fb_public_int: %hd$N', fb_public_int); + PRINTF('- fb_public_dint: %d$N', fb_public_dint); + PRINTF('- fb_public_lint: %lld$N', fb_public_lint); + PRINTF('- fb_public_usint: %hhu$N', fb_public_usint); + PRINTF('- fb_public_uint: %hu$N', fb_public_uint); + PRINTF('- fb_public_udint: %u$N', fb_public_udint); + PRINTF('- fb_public_ulint: %llu$N', fb_public_ulint); + PRINTF('- fb_public_word: %hu$N', fb_public_word); + PRINTF('- fb_public_dword: %u$N', fb_public_dword); + PRINTF('- fb_public_lword: %llu$N', fb_public_lword); + PRINTF('- fb_public_bool: %hhu$N', fb_public_bool); + PRINTF('- fb_public_time: %u$N', fb_public_time); + PRINTF('- fb_public_ltime: %llu$N', fb_public_ltime); + + PRINTF('- fb_protected_sint: %hhd$N', fb_protected_sint); + PRINTF('- fb_protected_int: %hd$N', fb_protected_int); + PRINTF('- fb_protected_dint: %d$N', fb_protected_dint); + PRINTF('- fb_protected_lint: %lld$N', fb_protected_lint); + PRINTF('- fb_protected_usint: %hhu$N', fb_protected_usint); + PRINTF('- fb_protected_uint: %hu$N', fb_protected_uint); + PRINTF('- fb_protected_udint: %u$N', fb_protected_udint); + PRINTF('- fb_protected_ulint: %llu$N', fb_protected_ulint); + PRINTF('- fb_protected_word: %hu$N', fb_protected_word); + PRINTF('- fb_protected_dword: %u$N', fb_protected_dword); + PRINTF('- fb_protected_lword: %llu$N', fb_protected_lword); + PRINTF('- fb_protected_bool: %hhu$N', fb_protected_bool); + PRINTF('- fb_protected_time: %u$N', fb_protected_time); + PRINTF('- fb_protected_ltime: %llu$N', fb_protected_ltime); + + PRINTF('- fb_internal_sint: %hhd$N', fb_internal_sint); + PRINTF('- fb_internal_int: %hd$N', fb_internal_int); + PRINTF('- fb_internal_dint: %d$N', fb_internal_dint); + PRINTF('- fb_internal_lint: %lld$N', fb_internal_lint); + PRINTF('- fb_internal_usint: %hhu$N', fb_internal_usint); + PRINTF('- fb_internal_uint: %hu$N', fb_internal_uint); + PRINTF('- fb_internal_udint: %u$N', fb_internal_udint); + PRINTF('- fb_internal_ulint: %llu$N', fb_internal_ulint); + PRINTF('- fb_internal_word: %hu$N', fb_internal_word); + PRINTF('- fb_internal_dword: %u$N', fb_internal_dword); + PRINTF('- fb_internal_lword: %llu$N', fb_internal_lword); + PRINTF('- fb_internal_bool: %hhu$N', fb_internal_bool); + PRINTF('- fb_internal_time: %u$N', fb_internal_time); + PRINTF('- fb_internal_ltime: %llu$N', fb_internal_ltime); + + PRINTF('- fb_private_sint: %hhd$N', fb_private_sint); + PRINTF('- fb_private_int: %hd$N', fb_private_int); + PRINTF('- fb_private_dint: %d$N', fb_private_dint); + PRINTF('- fb_private_lint: %lld$N', fb_private_lint); + PRINTF('- fb_private_usint: %hhu$N', fb_private_usint); + PRINTF('- fb_private_uint: %hu$N', fb_private_uint); + PRINTF('- fb_private_udint: %u$N', fb_private_udint); + PRINTF('- fb_private_ulint: %llu$N', fb_private_ulint); + PRINTF('- fb_private_word: %hu$N', fb_private_word); + PRINTF('- fb_private_dword: %u$N', fb_private_dword); + PRINTF('- fb_private_lword: %llu$N', fb_private_lword); + PRINTF('- fb_private_bool: %hhu$N', fb_private_bool); + PRINTF('- fb_private_time: %u$N', fb_private_time); + PRINTF('- fb_private_ltime: %llu$N', fb_private_ltime); + + PRINTF('- fb_constant_sint: %hhd$N', fb_constant_sint); + PRINTF('- fb_constant_int: %hd$N', fb_constant_int); + PRINTF('- fb_constant_dint: %d$N', fb_constant_dint); + PRINTF('- fb_constant_lint: %lld$N', fb_constant_lint); + PRINTF('- fb_constant_usint: %hhu$N', fb_constant_usint); + PRINTF('- fb_constant_uint: %hu$N', fb_constant_uint); + PRINTF('- fb_constant_udint: %u$N', fb_constant_udint); + PRINTF('- fb_constant_ulint: %llu$N', fb_constant_ulint); + PRINTF('- fb_constant_word: %hu$N', fb_constant_word); + PRINTF('- fb_constant_dword: %u$N', fb_constant_dword); + PRINTF('- fb_constant_lword: %llu$N', fb_constant_lword); + PRINTF('- fb_constant_bool: %hhu$N', fb_constant_bool); + PRINTF('- fb_constant_time: %u$N', fb_constant_time); + PRINTF('- fb_constant_ltime: %llu$N', fb_constant_ltime); + + PRINTF('Checking method variable after call:$N'); + PRINTF('- m_input_sint: %hhd$N', m_input_sint); + PRINTF('- m_input_int: %hd$N', m_input_int); + PRINTF('- m_input_dint: %d$N', m_input_dint); + PRINTF('- m_input_lint: %lld$N', m_input_lint); + PRINTF('- m_input_usint: %hhu$N', m_input_usint); + PRINTF('- m_input_uint: %hu$N', m_input_uint); + PRINTF('- m_input_udint: %u$N', m_input_udint); + PRINTF('- m_input_ulint: %llu$N', m_input_ulint); + PRINTF('- m_input_word: %hu$N', m_input_word); + PRINTF('- m_input_dword: %u$N', m_input_dword); + PRINTF('- m_input_lword: %llu$N', m_input_lword); + PRINTF('- m_input_bool: %hhu$N', m_input_bool); + PRINTF('- m_input_time: %u$N', m_input_time); + PRINTF('- m_input_ltime: %llu$N', m_input_ltime); + + PRINTF('- m_output_sint: %hhd$N', m_output_sint); + PRINTF('- m_output_int: %hd$N', m_output_int); + PRINTF('- m_output_dint: %d$N', m_output_dint); + PRINTF('- m_output_lint: %lld$N', m_output_lint); + PRINTF('- m_output_usint: %hhu$N', m_output_usint); + PRINTF('- m_output_uint: %hu$N', m_output_uint); + PRINTF('- m_output_udint: %u$N', m_output_udint); + PRINTF('- m_output_ulint: %llu$N', m_output_ulint); + PRINTF('- m_output_word: %hu$N', m_output_word); + PRINTF('- m_output_dword: %u$N', m_output_dword); + PRINTF('- m_output_lword: %llu$N', m_output_lword); + PRINTF('- m_output_bool: %hhu$N', m_output_bool); + PRINTF('- m_output_time: %u$N', m_output_time); + PRINTF('- m_output_ltime: %llu$N', m_output_ltime); + + PRINTF('- m_var_sint: %hhd$N', m_var_sint); + PRINTF('- m_var_int: %hd$N', m_var_int); + PRINTF('- m_var_dint: %d$N', m_var_dint); + PRINTF('- m_var_lint: %lld$N', m_var_lint); + PRINTF('- m_var_usint: %hhu$N', m_var_usint); + PRINTF('- m_var_uint: %hu$N', m_var_uint); + PRINTF('- m_var_udint: %u$N', m_var_udint); + PRINTF('- m_var_ulint: %llu$N', m_var_ulint); + PRINTF('- m_var_word: %hu$N', m_var_word); + PRINTF('- m_var_dword: %u$N', m_var_dword); + PRINTF('- m_var_lword: %llu$N', m_var_lword); + PRINTF('- m_var_bool: %hhu$N', m_var_bool); + PRINTF('- m_var_time: %u$N', m_var_time); + PRINTF('- m_var_ltime: %llu$N', m_var_ltime); + + fb_output_sint := -1; + fb_output_int := -2; + fb_output_dint := -3; + fb_output_lint := -4; + fb_output_usint := 1; + fb_output_uint := 2; + fb_output_udint := 3; + fb_output_ulint := 4; + fb_output_word := 16#1; + fb_output_dword := 16#2; + fb_output_lword := 16#3; + fb_output_bool := TRUE; + fb_output_time := t#1ms; + fb_output_ltime := lt#1ns; + + fb_public_sint := -1; + fb_public_int := -2; + fb_public_dint := -3; + fb_public_lint := -4; + fb_public_usint := 1; + fb_public_uint := 2; + fb_public_udint := 3; + fb_public_ulint := 4; + fb_public_word := 16#1; + fb_public_dword := 16#2; + fb_public_lword := 16#3; + fb_public_bool := TRUE; + fb_public_time := t#1ms; + fb_public_ltime := lt#1ns; + + fb_protected_sint := -1; + fb_protected_int := -2; + fb_protected_dint := -3; + fb_protected_lint := -4; + fb_protected_usint := 1; + fb_protected_uint := 2; + fb_protected_udint := 3; + fb_protected_ulint := 4; + fb_protected_word := 16#1; + fb_protected_dword := 16#2; + fb_protected_lword := 16#3; + fb_protected_bool := TRUE; + fb_protected_time := t#1ms; + fb_protected_ltime := lt#1ns; + + fb_internal_sint := -1; + fb_internal_int := -2; + fb_internal_dint := -3; + fb_internal_lint := -4; + fb_internal_usint := 1; + fb_internal_uint := 2; + fb_internal_udint := 3; + fb_internal_ulint := 4; + fb_internal_word := 16#1; + fb_internal_dword := 16#2; + fb_internal_lword := 16#3; + fb_internal_bool := TRUE; + fb_internal_time := t#1ms; + fb_internal_ltime := lt#1ns; + + fb_private_sint := -1; + fb_private_int := -2; + fb_private_dint := -3; + fb_private_lint := -4; + fb_private_usint := 1; + fb_private_uint := 2; + fb_private_udint := 3; + fb_private_ulint := 4; + fb_private_word := 16#1; + fb_private_dword := 16#2; + fb_private_lword := 16#3; + fb_private_bool := TRUE; + fb_private_time := t#1ms; + fb_private_ltime := lt#1ns; + + m_var_sint := m_input_sint; + m_var_int := m_input_int; + m_var_dint := m_input_dint; + m_var_lint := m_input_lint; + m_var_usint := m_input_usint; + m_var_uint := m_input_uint; + m_var_udint := m_input_udint; + m_var_ulint := m_input_ulint; + m_var_word := m_input_word; + m_var_dword := m_input_dword; + m_var_lword := m_input_lword; + m_var_bool := m_input_bool; + m_var_time := m_input_time; + m_var_ltime := m_input_ltime; + + m_output_sint := m_var_sint; + m_output_int := m_var_int; + m_output_dint := m_var_dint; + m_output_lint := m_var_lint; + m_output_usint := m_var_usint; + m_output_uint := m_var_uint; + m_output_udint := m_var_udint; + m_output_ulint := m_var_ulint; + m_output_word := m_var_word; + m_output_dword := m_var_dword; + m_output_lword := m_var_lword; + m_output_bool := m_var_bool; + m_output_time := m_var_time; + m_output_ltime := m_var_ltime; + END_METHOD + METHOD myMethod_sint : SINT + VAR_INPUT + m_input_sint : SINT; + END_VAR + myMethod_sint := m_input_sint; + END_METHOD + METHOD myMethod_int : INT + VAR_INPUT + m_input_int : INT; + END_VAR + myMethod_int := m_input_int; + END_METHOD + METHOD myMethod_dint : DINT + VAR_INPUT + m_input_dint : DINT; + END_VAR + myMethod_dint := m_input_dint; + END_METHOD + METHOD myMethod_lint : LINT + VAR_INPUT + m_input_lint : LINT; + END_VAR + myMethod_lint := m_input_lint; + END_METHOD + METHOD myMethod_usint : USINT + VAR_INPUT + m_input_usint : USINT; + END_VAR + myMethod_usint := m_input_usint; + END_METHOD + METHOD myMethod_uint : UINT + VAR_INPUT + m_input_uint : UINT; + END_VAR + myMethod_uint := m_input_uint; + END_METHOD + METHOD myMethod_udint : UDINT + VAR_INPUT + m_input_udint : UDINT; + END_VAR + myMethod_udint := m_input_udint; + END_METHOD + METHOD myMethod_ulint : ULINT + VAR_INPUT + m_input_ulint : ULINT; + END_VAR + myMethod_ulint := m_input_ulint; + END_METHOD + METHOD myMethod_word : WORD + VAR_INPUT + m_input_word : WORD; + END_VAR + myMethod_word := m_input_word; + END_METHOD + METHOD myMethod_dword : DWORD + VAR_INPUT + m_input_dword : DWORD; + END_VAR + myMethod_dword := m_input_dword; + END_METHOD + METHOD myMethod_lword : LWORD + VAR_INPUT + m_input_lword : LWORD; + END_VAR + myMethod_lword := m_input_lword; + END_METHOD + METHOD myMethod_bool : BOOL + VAR_INPUT + m_input_bool : BOOL; + END_VAR + myMethod_bool := m_input_bool; + END_METHOD + METHOD myMethod_time : TIME + VAR_INPUT + m_input_time : TIME; + END_VAR + myMethod_time := m_input_time; + END_METHOD + METHOD myMethod_ltime : LTIME + VAR_INPUT + m_input_ltime : LTIME; + END_VAR + myMethod_ltime := m_input_ltime; + END_METHOD + METHOD myMethod_arr_int : ARRAY[1..5] OF INT + VAR_INPUT + m_input_arr_idx1 : INT; + m_input_arr_idx3 : INT; + END_VAR + myMethod_arr_int[1] := m_input_arr_idx1; + myMethod_arr_int[3] := m_input_arr_idx3; + END_METHOD + METHOD myMethod_bits + VAR_INPUT + m_input_byte : BYTE; + END_VAR + VAR_OUTPUT + m_output_bits : ARRAY[1..8] OF BOOL; + END_VAR + m_output_bits[1] := m_input_byte.0; + m_output_bits[2] := m_input_byte.1; + m_output_bits[3] := m_input_byte.2; + m_output_bits[4] := m_input_byte.3; + m_output_bits[5] := m_input_byte.4; + m_output_bits[6] := m_input_byte.5; + m_output_bits[7] := m_input_byte.6; + m_output_bits[8] := m_input_byte.7; + END_METHOD +END_FUNCTION_BLOCK + +FUNCTION_BLOCK fb2 extends fb + METHOD myMethod2 + VAR_INPUT + m_input_sint : SINT; + m_input_int : INT := 2; + m_input_dint : DINT; + m_input_lint : LINT; + m_input_usint : USINT; + m_input_uint : UINT := 6; + m_input_udint : UDINT; + m_input_ulint : ULINT; + m_input_word : WORD; + m_input_dword : DWORD := 16#A; + m_input_lword : LWORD; + m_input_bool : BOOL; + m_input_time : TIME := T#12s; + m_input_ltime : LTIME; + END_VAR + VAR_OUTPUT + m_output_sint : SINT; + m_output_int : INT; + m_output_dint : DINT := 3; + m_output_lint : LINT; + m_output_usint : USINT; + m_output_uint : UINT; + m_output_udint : UDINT; + m_output_ulint : ULINT := 8; + m_output_word : WORD; + m_output_dword : DWORD; + m_output_lword : LWORD := 16#B; + m_output_bool : BOOL := TRUE; + m_output_time : TIME; + m_output_ltime : LTIME := LT#13us; + END_VAR + VAR + m_var_sint : SINT := 1; + m_var_int : INT; + m_var_dint : DINT; + m_var_lint : LINT; + m_var_usint : USINT := 5; + m_var_uint : UINT; + m_var_udint : UDINT; + m_var_ulint : ULINT; + m_var_word : WORD := 16#9; + m_var_dword : DWORD; + m_var_lword : LWORD; + m_var_bool : BOOL := TRUE; + m_var_time : TIME := T#12ms; + m_var_ltime : LTIME := LT#13ns; + END_VAR + + PRINTF('Checking fb variable after call of method2:$N'); + PRINTF('- fb_input_sint: %hhd$N', fb_input_sint); + PRINTF('- fb_input_int: %hd$N', fb_input_int); + PRINTF('- fb_input_dint: %d$N', fb_input_dint); + PRINTF('- fb_input_lint: %lld$N', fb_input_lint); + PRINTF('- fb_input_usint: %hhu$N', fb_input_usint); + PRINTF('- fb_input_uint: %hu$N', fb_input_uint); + PRINTF('- fb_input_udint: %u$N', fb_input_udint); + PRINTF('- fb_input_ulint: %llu$N', fb_input_ulint); + PRINTF('- fb_input_word: %hu$N', fb_input_word); + PRINTF('- fb_input_dword: %u$N', fb_input_dword); + PRINTF('- fb_input_lword: %llu$N', fb_input_lword); + PRINTF('- fb_input_bool: %hhu$N', fb_input_bool); + PRINTF('- fb_input_time: %u$N', fb_input_time); + PRINTF('- fb_input_ltime: %llu$N', fb_input_ltime); + + PRINTF('- fb_output_sint: %hhd$N', fb_output_sint); + PRINTF('- fb_output_int: %hd$N', fb_output_int); + PRINTF('- fb_output_dint: %d$N', fb_output_dint); + PRINTF('- fb_output_lint: %lld$N', fb_output_lint); + PRINTF('- fb_output_usint: %hhu$N', fb_output_usint); + PRINTF('- fb_output_uint: %hu$N', fb_output_uint); + PRINTF('- fb_output_udint: %u$N', fb_output_udint); + PRINTF('- fb_output_ulint: %llu$N', fb_output_ulint); + PRINTF('- fb_output_word: %hu$N', fb_output_word); + PRINTF('- fb_output_dword: %u$N', fb_output_dword); + PRINTF('- fb_output_lword: %llu$N', fb_output_lword); + PRINTF('- fb_output_bool: %hhu$N', fb_output_bool); + PRINTF('- fb_output_time: %u$N', fb_output_time); + PRINTF('- fb_output_ltime: %llu$N', fb_output_ltime); + + PRINTF('- fb_public_sint: %hhd$N', fb_public_sint); + PRINTF('- fb_public_int: %hd$N', fb_public_int); + PRINTF('- fb_public_dint: %d$N', fb_public_dint); + PRINTF('- fb_public_lint: %lld$N', fb_public_lint); + PRINTF('- fb_public_usint: %hhu$N', fb_public_usint); + PRINTF('- fb_public_uint: %hu$N', fb_public_uint); + PRINTF('- fb_public_udint: %u$N', fb_public_udint); + PRINTF('- fb_public_ulint: %llu$N', fb_public_ulint); + PRINTF('- fb_public_word: %hu$N', fb_public_word); + PRINTF('- fb_public_dword: %u$N', fb_public_dword); + PRINTF('- fb_public_lword: %llu$N', fb_public_lword); + PRINTF('- fb_public_bool: %hhu$N', fb_public_bool); + PRINTF('- fb_public_time: %u$N', fb_public_time); + PRINTF('- fb_public_ltime: %llu$N', fb_public_ltime); + + PRINTF('- fb_protected_sint: %hhd$N', fb_protected_sint); + PRINTF('- fb_protected_int: %hd$N', fb_protected_int); + PRINTF('- fb_protected_dint: %d$N', fb_protected_dint); + PRINTF('- fb_protected_lint: %lld$N', fb_protected_lint); + PRINTF('- fb_protected_usint: %hhu$N', fb_protected_usint); + PRINTF('- fb_protected_uint: %hu$N', fb_protected_uint); + PRINTF('- fb_protected_udint: %u$N', fb_protected_udint); + PRINTF('- fb_protected_ulint: %llu$N', fb_protected_ulint); + PRINTF('- fb_protected_word: %hu$N', fb_protected_word); + PRINTF('- fb_protected_dword: %u$N', fb_protected_dword); + PRINTF('- fb_protected_lword: %llu$N', fb_protected_lword); + PRINTF('- fb_protected_bool: %hhu$N', fb_protected_bool); + PRINTF('- fb_protected_time: %u$N', fb_protected_time); + PRINTF('- fb_protected_ltime: %llu$N', fb_protected_ltime); + + PRINTF('- fb_internal_sint: %hhd$N', fb_internal_sint); + PRINTF('- fb_internal_int: %hd$N', fb_internal_int); + PRINTF('- fb_internal_dint: %d$N', fb_internal_dint); + PRINTF('- fb_internal_lint: %lld$N', fb_internal_lint); + PRINTF('- fb_internal_usint: %hhu$N', fb_internal_usint); + PRINTF('- fb_internal_uint: %hu$N', fb_internal_uint); + PRINTF('- fb_internal_udint: %u$N', fb_internal_udint); + PRINTF('- fb_internal_ulint: %llu$N', fb_internal_ulint); + PRINTF('- fb_internal_word: %hu$N', fb_internal_word); + PRINTF('- fb_internal_dword: %u$N', fb_internal_dword); + PRINTF('- fb_internal_lword: %llu$N', fb_internal_lword); + PRINTF('- fb_internal_bool: %hhu$N', fb_internal_bool); + PRINTF('- fb_internal_time: %u$N', fb_internal_time); + PRINTF('- fb_internal_ltime: %llu$N', fb_internal_ltime); + + PRINTF('- fb_constant_sint: %hhd$N', fb_constant_sint); + PRINTF('- fb_constant_int: %hd$N', fb_constant_int); + PRINTF('- fb_constant_dint: %d$N', fb_constant_dint); + PRINTF('- fb_constant_lint: %lld$N', fb_constant_lint); + PRINTF('- fb_constant_usint: %hhu$N', fb_constant_usint); + PRINTF('- fb_constant_uint: %hu$N', fb_constant_uint); + PRINTF('- fb_constant_udint: %u$N', fb_constant_udint); + PRINTF('- fb_constant_ulint: %llu$N', fb_constant_ulint); + PRINTF('- fb_constant_word: %hu$N', fb_constant_word); + PRINTF('- fb_constant_dword: %u$N', fb_constant_dword); + PRINTF('- fb_constant_lword: %llu$N', fb_constant_lword); + PRINTF('- fb_constant_bool: %hhu$N', fb_constant_bool); + PRINTF('- fb_constant_time: %u$N', fb_constant_time); + PRINTF('- fb_constant_ltime: %llu$N', fb_constant_ltime); + + PRINTF('Checking method2 variable after call:$N'); + PRINTF('- m_input_sint: %hhd$N', m_input_sint); + PRINTF('- m_input_int: %hd$N', m_input_int); + PRINTF('- m_input_dint: %d$N', m_input_dint); + PRINTF('- m_input_lint: %lld$N', m_input_lint); + PRINTF('- m_input_usint: %hhu$N', m_input_usint); + PRINTF('- m_input_uint: %hu$N', m_input_uint); + PRINTF('- m_input_udint: %u$N', m_input_udint); + PRINTF('- m_input_ulint: %llu$N', m_input_ulint); + PRINTF('- m_input_word: %hu$N', m_input_word); + PRINTF('- m_input_dword: %u$N', m_input_dword); + PRINTF('- m_input_lword: %llu$N', m_input_lword); + PRINTF('- m_input_bool: %hhu$N', m_input_bool); + PRINTF('- m_input_time: %u$N', m_input_time); + PRINTF('- m_input_ltime: %llu$N', m_input_ltime); + + PRINTF('- m_output_sint: %hhd$N', m_output_sint); + PRINTF('- m_output_int: %hd$N', m_output_int); + PRINTF('- m_output_dint: %d$N', m_output_dint); + PRINTF('- m_output_lint: %lld$N', m_output_lint); + PRINTF('- m_output_usint: %hhu$N', m_output_usint); + PRINTF('- m_output_uint: %hu$N', m_output_uint); + PRINTF('- m_output_udint: %u$N', m_output_udint); + PRINTF('- m_output_ulint: %llu$N', m_output_ulint); + PRINTF('- m_output_word: %hu$N', m_output_word); + PRINTF('- m_output_dword: %u$N', m_output_dword); + PRINTF('- m_output_lword: %llu$N', m_output_lword); + PRINTF('- m_output_bool: %hhu$N', m_output_bool); + PRINTF('- m_output_time: %u$N', m_output_time); + PRINTF('- m_output_ltime: %llu$N', m_output_ltime); + + PRINTF('- m_var_sint: %hhd$N', m_var_sint); + PRINTF('- m_var_int: %hd$N', m_var_int); + PRINTF('- m_var_dint: %d$N', m_var_dint); + PRINTF('- m_var_lint: %lld$N', m_var_lint); + PRINTF('- m_var_usint: %hhu$N', m_var_usint); + PRINTF('- m_var_uint: %hu$N', m_var_uint); + PRINTF('- m_var_udint: %u$N', m_var_udint); + PRINTF('- m_var_ulint: %llu$N', m_var_ulint); + PRINTF('- m_var_word: %hu$N', m_var_word); + PRINTF('- m_var_dword: %u$N', m_var_dword); + PRINTF('- m_var_lword: %llu$N', m_var_lword); + PRINTF('- m_var_bool: %hhu$N', m_var_bool); + PRINTF('- m_var_time: %u$N', m_var_time); + PRINTF('- m_var_ltime: %llu$N', m_var_ltime); + + fb_output_sint := -1; + fb_output_int := -2; + fb_output_dint := -3; + fb_output_lint := -4; + fb_output_usint := 1; + fb_output_uint := 2; + fb_output_udint := 3; + fb_output_ulint := 4; + fb_output_word := 16#1; + fb_output_dword := 16#2; + fb_output_lword := 16#3; + fb_output_bool := TRUE; + fb_output_time := t#1ms; + fb_output_ltime := lt#1ns; + + fb_public_sint := -1; + fb_public_int := -2; + fb_public_dint := -3; + fb_public_lint := -4; + fb_public_usint := 1; + fb_public_uint := 2; + fb_public_udint := 3; + fb_public_ulint := 4; + fb_public_word := 16#1; + fb_public_dword := 16#2; + fb_public_lword := 16#3; + fb_public_bool := TRUE; + fb_public_time := t#1ms; + fb_public_ltime := lt#1ns; + + fb_protected_sint := -1; + fb_protected_int := -2; + fb_protected_dint := -3; + fb_protected_lint := -4; + fb_protected_usint := 1; + fb_protected_uint := 2; + fb_protected_udint := 3; + fb_protected_ulint := 4; + fb_protected_word := 16#1; + fb_protected_dword := 16#2; + fb_protected_lword := 16#3; + fb_protected_bool := TRUE; + fb_protected_time := t#1ms; + fb_protected_ltime := lt#1ns; + + fb_internal_sint := -1; + fb_internal_int := -2; + fb_internal_dint := -3; + fb_internal_lint := -4; + fb_internal_usint := 1; + fb_internal_uint := 2; + fb_internal_udint := 3; + fb_internal_ulint := 4; + fb_internal_word := 16#1; + fb_internal_dword := 16#2; + fb_internal_lword := 16#3; + fb_internal_bool := TRUE; + fb_internal_time := t#1ms; + fb_internal_ltime := lt#1ns; + + m_var_sint := m_input_sint; + m_var_int := m_input_int; + m_var_dint := m_input_dint; + m_var_lint := m_input_lint; + m_var_usint := m_input_usint; + m_var_uint := m_input_uint; + m_var_udint := m_input_udint; + m_var_ulint := m_input_ulint; + m_var_word := m_input_word; + m_var_dword := m_input_dword; + m_var_lword := m_input_lword; + m_var_bool := m_input_bool; + m_var_time := m_input_time; + m_var_ltime := m_input_ltime; + + m_output_sint := m_var_sint; + m_output_int := m_var_int; + m_output_dint := m_var_dint; + m_output_lint := m_var_lint; + m_output_usint := m_var_usint; + m_output_uint := m_var_uint; + m_output_udint := m_var_udint; + m_output_ulint := m_var_ulint; + m_output_word := m_var_word; + m_output_dword := m_var_dword; + m_output_lword := m_var_lword; + m_output_bool := m_var_bool; + m_output_time := m_var_time; + m_output_ltime := m_var_ltime; + END_METHOD +END_FUNCTION_BLOCK +FUNCTION main + VAR + fb_instance : fb; + fb2_instance : fb2; + var_sint : SINT; + var_int : INT; + var_dint : DINT; + var_lint : LINT; + var_usint : USINT; + var_uint : UINT; + var_udint : UDINT; + var_ulint : ULINT; + var_word : WORD; + var_dword : DWORD; + var_lword : LWORD; + var_bool : BOOL; + var_time : TIME; + var_ltime : LTIME; + var_arr_int : ARRAY[1..5] OF INT; + var_bits : ARRAY[1..8] OF BOOL; + END_VAR + + fb_instance.myMethod(); + + //CHECK:Checking fb variable after call of method: + //CHECK-NEXT:- fb_input_sint: 0 + //CHECK-NEXT:- fb_input_int: 0 + //CHECK-NEXT:- fb_input_dint: 0 + //CHECK-NEXT:- fb_input_lint: 4 + //CHECK-NEXT:- fb_input_usint: 0 + //CHECK-NEXT:- fb_input_uint: 0 + //CHECK-NEXT:- fb_input_udint: 7 + //CHECK-NEXT:- fb_input_ulint: 0 + //CHECK-NEXT:- fb_input_word: 9 + //CHECK-NEXT:- fb_input_dword: 0 + //CHECK-NEXT:- fb_input_lword: 0 + //CHECK-NEXT:- fb_input_bool: 1 + //CHECK-NEXT:- fb_input_time: 0 + //CHECK-NEXT:- fb_input_ltime: 13000000000 + + //CHECK-NEXT:- fb_output_sint: 1 + //CHECK-NEXT:- fb_output_int: 0 + //CHECK-NEXT:- fb_output_dint: 0 + //CHECK-NEXT:- fb_output_lint: 0 + //CHECK-NEXT:- fb_output_usint: 0 + //CHECK-NEXT:- fb_output_uint: 6 + //CHECK-NEXT:- fb_output_udint: 0 + //CHECK-NEXT:- fb_output_ulint: 0 + //CHECK-NEXT:- fb_output_word: 0 + //CHECK-NEXT:- fb_output_dword: 0 + //CHECK-NEXT:- fb_output_lword: 11 + //CHECK-NEXT:- fb_output_bool: 0 + //CHECK-NEXT:- fb_output_time: 12000 + //CHECK-NEXT:- fb_output_ltime: 0 + + //CHECK-NEXT:- fb_public_sint: 0 + //CHECK-NEXT:- fb_public_int: 2 + //CHECK-NEXT:- fb_public_dint: 0 + //CHECK-NEXT:- fb_public_lint: 0 + //CHECK-NEXT:- fb_public_usint: 5 + //CHECK-NEXT:- fb_public_uint: 0 + //CHECK-NEXT:- fb_public_udint: 0 + //CHECK-NEXT:- fb_public_ulint: 0 + //CHECK-NEXT:- fb_public_word: 0 + //CHECK-NEXT:- fb_public_dword: 10 + //CHECK-NEXT:- fb_public_lword: 0 + //CHECK-NEXT:- fb_public_bool: 1 + //CHECK-NEXT:- fb_public_time: 0 + //CHECK-NEXT:- fb_public_ltime: 0 + + //CHECK-NEXT:- fb_protected_sint: 0 + //CHECK-NEXT:- fb_protected_int: 0 + //CHECK-NEXT:- fb_protected_dint: 3 + //CHECK-NEXT:- fb_protected_lint: 0 + //CHECK-NEXT:- fb_protected_usint: 0 + //CHECK-NEXT:- fb_protected_uint: 0 + //CHECK-NEXT:- fb_protected_udint: 0 + //CHECK-NEXT:- fb_protected_ulint: 8 + //CHECK-NEXT:- fb_protected_word: 0 + //CHECK-NEXT:- fb_protected_dword: 0 + //CHECK-NEXT:- fb_protected_lword: 0 + //CHECK-NEXT:- fb_protected_bool: 0 + //CHECK-NEXT:- fb_protected_time: 42 + //CHECK-NEXT:- fb_protected_ltime: 0 + + //CHECK-NEXT:- fb_internal_sint: 11 + //CHECK-NEXT:- fb_internal_int: 0 + //CHECK-NEXT:- fb_internal_dint: 0 + //CHECK-NEXT:- fb_internal_lint: 0 + //CHECK-NEXT:- fb_internal_usint: 12 + //CHECK-NEXT:- fb_internal_uint: 0 + //CHECK-NEXT:- fb_internal_udint: 0 + //CHECK-NEXT:- fb_internal_ulint: 0 + //CHECK-NEXT:- fb_internal_word: 13 + //CHECK-NEXT:- fb_internal_dword: 0 + //CHECK-NEXT:- fb_internal_lword: 0 + //CHECK-NEXT:- fb_internal_bool: 1 + //CHECK-NEXT:- fb_internal_time: 0 + //CHECK-NEXT:- fb_internal_ltime: 1000000000 + + //CHECK-NEXT:- fb_private_sint: 0 + //CHECK-NEXT:- fb_private_int: 21 + //CHECK-NEXT:- fb_private_dint: 0 + //CHECK-NEXT:- fb_private_lint: 0 + //CHECK-NEXT:- fb_private_usint: 0 + //CHECK-NEXT:- fb_private_uint: 22 + //CHECK-NEXT:- fb_private_udint: 0 + //CHECK-NEXT:- fb_private_ulint: 0 + //CHECK-NEXT:- fb_private_word: 0 + //CHECK-NEXT:- fb_private_dword: 23 + //CHECK-NEXT:- fb_private_lword: 0 + //CHECK-NEXT:- fb_private_bool: 0 + //CHECK-NEXT:- fb_private_time: 0 + //CHECK-NEXT:- fb_private_ltime: 0 + + //CHECK-NEXT:- fb_constant_sint: 1 + //CHECK-NEXT:- fb_constant_int: 2 + //CHECK-NEXT:- fb_constant_dint: 3 + //CHECK-NEXT:- fb_constant_lint: 4 + //CHECK-NEXT:- fb_constant_usint: 5 + //CHECK-NEXT:- fb_constant_uint: 6 + //CHECK-NEXT:- fb_constant_udint: 7 + //CHECK-NEXT:- fb_constant_ulint: 8 + //CHECK-NEXT:- fb_constant_word: 9 + //CHECK-NEXT:- fb_constant_dword: 10 + //CHECK-NEXT:- fb_constant_lword: 11 + //CHECK-NEXT:- fb_constant_bool: 1 + //CHECK-NEXT:- fb_constant_time: 13 + //CHECK-NEXT:- fb_constant_ltime: 14000 + + //CHECK-NEXT:Checking method variable after call: + //CHECK-NEXT:- m_input_sint: 0 + //CHECK-NEXT:- m_input_int: 2 + //CHECK-NEXT:- m_input_dint: 0 + //CHECK-NEXT:- m_input_lint: 0 + //CHECK-NEXT:- m_input_usint: 0 + //CHECK-NEXT:- m_input_uint: 6 + //CHECK-NEXT:- m_input_udint: 0 + //CHECK-NEXT:- m_input_ulint: 0 + //CHECK-NEXT:- m_input_word: 0 + //CHECK-NEXT:- m_input_dword: 10 + //CHECK-NEXT:- m_input_lword: 0 + //CHECK-NEXT:- m_input_bool: 0 + //CHECK-NEXT:- m_input_time: 12000 + //CHECK-NEXT:- m_input_ltime: 0 + + //CHECK-NEXT:- m_output_sint: 0 + //CHECK-NEXT:- m_output_int: 0 + //CHECK-NEXT:- m_output_dint: 3 + //CHECK-NEXT:- m_output_lint: 0 + //CHECK-NEXT:- m_output_usint: 0 + //CHECK-NEXT:- m_output_uint: 0 + //CHECK-NEXT:- m_output_udint: 0 + //CHECK-NEXT:- m_output_ulint: 8 + //CHECK-NEXT:- m_output_word: 0 + //CHECK-NEXT:- m_output_dword: 0 + //CHECK-NEXT:- m_output_lword: 11 + //CHECK-NEXT:- m_output_bool: 1 + //CHECK-NEXT:- m_output_time: 0 + //CHECK-NEXT:- m_output_ltime: 13000 + + //CHECK-NEXT:- m_var_sint: 1 + //CHECK-NEXT:- m_var_int: 0 + //CHECK-NEXT:- m_var_dint: 0 + //CHECK-NEXT:- m_var_lint: 0 + //CHECK-NEXT:- m_var_usint: 5 + //CHECK-NEXT:- m_var_uint: 0 + //CHECK-NEXT:- m_var_udint: 0 + //CHECK-NEXT:- m_var_ulint: 0 + //CHECK-NEXT:- m_var_word: 9 + //CHECK-NEXT:- m_var_dword: 0 + //CHECK-NEXT:- m_var_lword: 0 + //CHECK-NEXT:- m_var_bool: 1 + //CHECK-NEXT:- m_var_time: 12 + //CHECK-NEXT:- m_var_ltime: 13 + + fb_instance.myMethod(61,62,63,64,65,66,67,68,69,70,71,TRUE,t#73ms,lt#74ns,var_sint,var_int,var_dint,var_lint,var_usint,var_uint,var_udint,var_ulint,var_word,var_dword,var_lword,var_bool,var_time,var_ltime); + + //CHECK-NEXT:Checking fb variable after call of method: + //CHECK-NEXT:- fb_input_sint: 0 + //CHECK-NEXT:- fb_input_int: 0 + //CHECK-NEXT:- fb_input_dint: 0 + //CHECK-NEXT:- fb_input_lint: 4 + //CHECK-NEXT:- fb_input_usint: 0 + //CHECK-NEXT:- fb_input_uint: 0 + //CHECK-NEXT:- fb_input_udint: 7 + //CHECK-NEXT:- fb_input_ulint: 0 + //CHECK-NEXT:- fb_input_word: 9 + //CHECK-NEXT:- fb_input_dword: 0 + //CHECK-NEXT:- fb_input_lword: 0 + //CHECK-NEXT:- fb_input_bool: 1 + //CHECK-NEXT:- fb_input_time: 0 + //CHECK-NEXT:- fb_input_ltime: 13000000000 + + //CHECK-NEXT:- fb_output_sint: -1 + //CHECK-NEXT:- fb_output_int: -2 + //CHECK-NEXT:- fb_output_dint: -3 + //CHECK-NEXT:- fb_output_lint: -4 + //CHECK-NEXT:- fb_output_usint: 1 + //CHECK-NEXT:- fb_output_uint: 2 + //CHECK-NEXT:- fb_output_udint: 3 + //CHECK-NEXT:- fb_output_ulint: 4 + //CHECK-NEXT:- fb_output_word: 1 + //CHECK-NEXT:- fb_output_dword: 2 + //CHECK-NEXT:- fb_output_lword: 3 + //CHECK-NEXT:- fb_output_bool: 1 + //CHECK-NEXT:- fb_output_time: 1 + //CHECK-NEXT:- fb_output_ltime: 1 + + //CHECK-NEXT:- fb_public_sint: -1 + //CHECK-NEXT:- fb_public_int: -2 + //CHECK-NEXT:- fb_public_dint: -3 + //CHECK-NEXT:- fb_public_lint: -4 + //CHECK-NEXT:- fb_public_usint: 1 + //CHECK-NEXT:- fb_public_uint: 2 + //CHECK-NEXT:- fb_public_udint: 3 + //CHECK-NEXT:- fb_public_ulint: 4 + //CHECK-NEXT:- fb_public_word: 1 + //CHECK-NEXT:- fb_public_dword: 2 + //CHECK-NEXT:- fb_public_lword: 3 + //CHECK-NEXT:- fb_public_bool: 1 + //CHECK-NEXT:- fb_public_time: 1 + //CHECK-NEXT:- fb_public_ltime: 1 + + //CHECK-NEXT:- fb_protected_sint: -1 + //CHECK-NEXT:- fb_protected_int: -2 + //CHECK-NEXT:- fb_protected_dint: -3 + //CHECK-NEXT:- fb_protected_lint: -4 + //CHECK-NEXT:- fb_protected_usint: 1 + //CHECK-NEXT:- fb_protected_uint: 2 + //CHECK-NEXT:- fb_protected_udint: 3 + //CHECK-NEXT:- fb_protected_ulint: 4 + //CHECK-NEXT:- fb_protected_word: 1 + //CHECK-NEXT:- fb_protected_dword: 2 + //CHECK-NEXT:- fb_protected_lword: 3 + //CHECK-NEXT:- fb_protected_bool: 1 + //CHECK-NEXT:- fb_protected_time: 1 + //CHECK-NEXT:- fb_protected_ltime: 1 + + //CHECK-NEXT:- fb_internal_sint: -1 + //CHECK-NEXT:- fb_internal_int: -2 + //CHECK-NEXT:- fb_internal_dint: -3 + //CHECK-NEXT:- fb_internal_lint: -4 + //CHECK-NEXT:- fb_internal_usint: 1 + //CHECK-NEXT:- fb_internal_uint: 2 + //CHECK-NEXT:- fb_internal_udint: 3 + //CHECK-NEXT:- fb_internal_ulint: 4 + //CHECK-NEXT:- fb_internal_word: 1 + //CHECK-NEXT:- fb_internal_dword: 2 + //CHECK-NEXT:- fb_internal_lword: 3 + //CHECK-NEXT:- fb_internal_bool: 1 + //CHECK-NEXT:- fb_internal_time: 1 + //CHECK-NEXT:- fb_internal_ltime: 1 + + //CHECK-NEXT:- fb_private_sint: -1 + //CHECK-NEXT:- fb_private_int: -2 + //CHECK-NEXT:- fb_private_dint: -3 + //CHECK-NEXT:- fb_private_lint: -4 + //CHECK-NEXT:- fb_private_usint: 1 + //CHECK-NEXT:- fb_private_uint: 2 + //CHECK-NEXT:- fb_private_udint: 3 + //CHECK-NEXT:- fb_private_ulint: 4 + //CHECK-NEXT:- fb_private_word: 1 + //CHECK-NEXT:- fb_private_dword: 2 + //CHECK-NEXT:- fb_private_lword: 3 + //CHECK-NEXT:- fb_private_bool: 1 + //CHECK-NEXT:- fb_private_time: 1 + //CHECK-NEXT:- fb_private_ltime: 1 + + //CHECK-NEXT:- fb_constant_sint: 1 + //CHECK-NEXT:- fb_constant_int: 2 + //CHECK-NEXT:- fb_constant_dint: 3 + //CHECK-NEXT:- fb_constant_lint: 4 + //CHECK-NEXT:- fb_constant_usint: 5 + //CHECK-NEXT:- fb_constant_uint: 6 + //CHECK-NEXT:- fb_constant_udint: 7 + //CHECK-NEXT:- fb_constant_ulint: 8 + //CHECK-NEXT:- fb_constant_word: 9 + //CHECK-NEXT:- fb_constant_dword: 10 + //CHECK-NEXT:- fb_constant_lword: 11 + //CHECK-NEXT:- fb_constant_bool: 1 + //CHECK-NEXT:- fb_constant_time: 13 + //CHECK-NEXT:- fb_constant_ltime: 14000 + + //CHECK-NEXT:Checking method variable after call: + //CHECK-NEXT:- m_input_sint: 61 + //CHECK-NEXT:- m_input_int: 62 + //CHECK-NEXT:- m_input_dint: 63 + //CHECK-NEXT:- m_input_lint: 64 + //CHECK-NEXT:- m_input_usint: 65 + //CHECK-NEXT:- m_input_uint: 66 + //CHECK-NEXT:- m_input_udint: 67 + //CHECK-NEXT:- m_input_ulint: 68 + //CHECK-NEXT:- m_input_word: 69 + //CHECK-NEXT:- m_input_dword: 70 + //CHECK-NEXT:- m_input_lword: 71 + //CHECK-NEXT:- m_input_bool: 1 + //CHECK-NEXT:- m_input_time: 73 + //CHECK-NEXT:- m_input_ltime: 74 + + //CHECK-NEXT:- m_output_sint: 0 + //CHECK-NEXT:- m_output_int: 0 + //CHECK-NEXT:- m_output_dint: 3 + //CHECK-NEXT:- m_output_lint: 0 + //CHECK-NEXT:- m_output_usint: 0 + //CHECK-NEXT:- m_output_uint: 0 + //CHECK-NEXT:- m_output_udint: 0 + //CHECK-NEXT:- m_output_ulint: 8 + //CHECK-NEXT:- m_output_word: 0 + //CHECK-NEXT:- m_output_dword: 0 + //CHECK-NEXT:- m_output_lword: 11 + //CHECK-NEXT:- m_output_bool: 1 + //CHECK-NEXT:- m_output_time: 0 + //CHECK-NEXT:- m_output_ltime: 13000 + + //CHECK-NEXT:- m_var_sint: 1 + //CHECK-NEXT:- m_var_int: 0 + //CHECK-NEXT:- m_var_dint: 0 + //CHECK-NEXT:- m_var_lint: 0 + //CHECK-NEXT:- m_var_usint: 5 + //CHECK-NEXT:- m_var_uint: 0 + //CHECK-NEXT:- m_var_udint: 0 + //CHECK-NEXT:- m_var_ulint: 0 + //CHECK-NEXT:- m_var_word: 9 + //CHECK-NEXT:- m_var_dword: 0 + //CHECK-NEXT:- m_var_lword: 0 + //CHECK-NEXT:- m_var_bool: 1 + //CHECK-NEXT:- m_var_time: 12 + //CHECK-NEXT:- m_var_ltime: 13 + + PRINTF('Checking changes of variable:$N'); + PRINTF('- var_sint: %d$N', var_sint); + PRINTF('- var_int: %d$N', var_int); + PRINTF('- var_dint: %d$N', var_dint); + PRINTF('- var_lint: %d$N', var_lint); + PRINTF('- var_usint: %d$N', var_usint); + PRINTF('- var_uint: %d$N', var_uint); + PRINTF('- var_udint: %d$N', var_udint); + PRINTF('- var_ulint: %d$N', var_ulint); + PRINTF('- var_word: %d$N', var_word); + PRINTF('- var_dword: %d$N', var_dword); + PRINTF('- var_lword: %d$N', var_lword); + PRINTF('- var_bool: %d$N', var_bool); + PRINTF('- var_time: %d$N', var_time); + PRINTF('- var_ltime: %d$N', var_ltime); + + //CHECK-NEXT:Checking changes of variable: + //CHECK-NEXT:- var_sint: 61 + //CHECK-NEXT:- var_int: 62 + //CHECK-NEXT:- var_dint: 63 + //CHECK-NEXT:- var_lint: 64 + //CHECK-NEXT:- var_usint: 65 + //CHECK-NEXT:- var_uint: 66 + //CHECK-NEXT:- var_udint: 67 + //CHECK-NEXT:- var_ulint: 68 + //CHECK-NEXT:- var_word: 69 + //CHECK-NEXT:- var_dword: 70 + //CHECK-NEXT:- var_lword: 71 + //CHECK-NEXT:- var_bool: 1 + //CHECK-NEXT:- var_time: 73 + //CHECK-NEXT:- var_ltime: 74 + + fb2_instance.myMethod2(); + //CHECK-NEXT:Checking fb variable after call of method2: + //CHECK-NEXT:- fb_input_sint: 0 + //CHECK-NEXT:- fb_input_int: 0 + //CHECK-NEXT:- fb_input_dint: 0 + //CHECK-NEXT:- fb_input_lint: 4 + //CHECK-NEXT:- fb_input_usint: 0 + //CHECK-NEXT:- fb_input_uint: 0 + //CHECK-NEXT:- fb_input_udint: 7 + //CHECK-NEXT:- fb_input_ulint: 0 + //CHECK-NEXT:- fb_input_word: 9 + //CHECK-NEXT:- fb_input_dword: 0 + //CHECK-NEXT:- fb_input_lword: 0 + //CHECK-NEXT:- fb_input_bool: 1 + //CHECK-NEXT:- fb_input_time: 0 + //CHECK-NEXT:- fb_input_ltime: 13000000000 + + //CHECK-NEXT:- fb_output_sint: 1 + //CHECK-NEXT:- fb_output_int: 0 + //CHECK-NEXT:- fb_output_dint: 0 + //CHECK-NEXT:- fb_output_lint: 0 + //CHECK-NEXT:- fb_output_usint: 0 + //CHECK-NEXT:- fb_output_uint: 6 + //CHECK-NEXT:- fb_output_udint: 0 + //CHECK-NEXT:- fb_output_ulint: 0 + //CHECK-NEXT:- fb_output_word: 0 + //CHECK-NEXT:- fb_output_dword: 0 + //CHECK-NEXT:- fb_output_lword: 11 + //CHECK-NEXT:- fb_output_bool: 0 + //CHECK-NEXT:- fb_output_time: 12000 + //CHECK-NEXT:- fb_output_ltime: 0 + + //CHECK-NEXT:- fb_public_sint: 0 + //CHECK-NEXT:- fb_public_int: 2 + //CHECK-NEXT:- fb_public_dint: 0 + //CHECK-NEXT:- fb_public_lint: 0 + //CHECK-NEXT:- fb_public_usint: 5 + //CHECK-NEXT:- fb_public_uint: 0 + //CHECK-NEXT:- fb_public_udint: 0 + //CHECK-NEXT:- fb_public_ulint: 0 + //CHECK-NEXT:- fb_public_word: 0 + //CHECK-NEXT:- fb_public_dword: 10 + //CHECK-NEXT:- fb_public_lword: 0 + //CHECK-NEXT:- fb_public_bool: 1 + //CHECK-NEXT:- fb_public_time: 0 + //CHECK-NEXT:- fb_public_ltime: 0 + + //CHECK-NEXT:- fb_protected_sint: 0 + //CHECK-NEXT:- fb_protected_int: 0 + //CHECK-NEXT:- fb_protected_dint: 3 + //CHECK-NEXT:- fb_protected_lint: 0 + //CHECK-NEXT:- fb_protected_usint: 0 + //CHECK-NEXT:- fb_protected_uint: 0 + //CHECK-NEXT:- fb_protected_udint: 0 + //CHECK-NEXT:- fb_protected_ulint: 8 + //CHECK-NEXT:- fb_protected_word: 0 + //CHECK-NEXT:- fb_protected_dword: 0 + //CHECK-NEXT:- fb_protected_lword: 0 + //CHECK-NEXT:- fb_protected_bool: 0 + //CHECK-NEXT:- fb_protected_time: 42 + //CHECK-NEXT:- fb_protected_ltime: 0 + + //CHECK-NEXT:- fb_internal_sint: 11 + //CHECK-NEXT:- fb_internal_int: 0 + //CHECK-NEXT:- fb_internal_dint: 0 + //CHECK-NEXT:- fb_internal_lint: 0 + //CHECK-NEXT:- fb_internal_usint: 12 + //CHECK-NEXT:- fb_internal_uint: 0 + //CHECK-NEXT:- fb_internal_udint: 0 + //CHECK-NEXT:- fb_internal_ulint: 0 + //CHECK-NEXT:- fb_internal_word: 13 + //CHECK-NEXT:- fb_internal_dword: 0 + //CHECK-NEXT:- fb_internal_lword: 0 + //CHECK-NEXT:- fb_internal_bool: 1 + //CHECK-NEXT:- fb_internal_time: 0 + //CHECK-NEXT:- fb_internal_ltime: 1000000000 + + //CHECK-NEXT:- fb_constant_sint: 1 + //CHECK-NEXT:- fb_constant_int: 2 + //CHECK-NEXT:- fb_constant_dint: 3 + //CHECK-NEXT:- fb_constant_lint: 4 + //CHECK-NEXT:- fb_constant_usint: 5 + //CHECK-NEXT:- fb_constant_uint: 6 + //CHECK-NEXT:- fb_constant_udint: 7 + //CHECK-NEXT:- fb_constant_ulint: 8 + //CHECK-NEXT:- fb_constant_word: 9 + //CHECK-NEXT:- fb_constant_dword: 10 + //CHECK-NEXT:- fb_constant_lword: 11 + //CHECK-NEXT:- fb_constant_bool: 1 + //CHECK-NEXT:- fb_constant_time: 13 + //CHECK-NEXT:- fb_constant_ltime: 14000 + + //CHECK-NEXT:Checking method2 variable after call: + //CHECK-NEXT:- m_input_sint: 0 + //CHECK-NEXT:- m_input_int: 2 + //CHECK-NEXT:- m_input_dint: 0 + //CHECK-NEXT:- m_input_lint: 0 + //CHECK-NEXT:- m_input_usint: 0 + //CHECK-NEXT:- m_input_uint: 6 + //CHECK-NEXT:- m_input_udint: 0 + //CHECK-NEXT:- m_input_ulint: 0 + //CHECK-NEXT:- m_input_word: 0 + //CHECK-NEXT:- m_input_dword: 10 + //CHECK-NEXT:- m_input_lword: 0 + //CHECK-NEXT:- m_input_bool: 0 + //CHECK-NEXT:- m_input_time: 12000 + //CHECK-NEXT:- m_input_ltime: 0 + + //CHECK-NEXT:- m_output_sint: 0 + //CHECK-NEXT:- m_output_int: 0 + //CHECK-NEXT:- m_output_dint: 3 + //CHECK-NEXT:- m_output_lint: 0 + //CHECK-NEXT:- m_output_usint: 0 + //CHECK-NEXT:- m_output_uint: 0 + //CHECK-NEXT:- m_output_udint: 0 + //CHECK-NEXT:- m_output_ulint: 8 + //CHECK-NEXT:- m_output_word: 0 + //CHECK-NEXT:- m_output_dword: 0 + //CHECK-NEXT:- m_output_lword: 11 + //CHECK-NEXT:- m_output_bool: 1 + //CHECK-NEXT:- m_output_time: 0 + //CHECK-NEXT:- m_output_ltime: 13000 + + //CHECK-NEXT:- m_var_sint: 1 + //CHECK-NEXT:- m_var_int: 0 + //CHECK-NEXT:- m_var_dint: 0 + //CHECK-NEXT:- m_var_lint: 0 + //CHECK-NEXT:- m_var_usint: 5 + //CHECK-NEXT:- m_var_uint: 0 + //CHECK-NEXT:- m_var_udint: 0 + //CHECK-NEXT:- m_var_ulint: 0 + //CHECK-NEXT:- m_var_word: 9 + //CHECK-NEXT:- m_var_dword: 0 + //CHECK-NEXT:- m_var_lword: 0 + //CHECK-NEXT:- m_var_bool: 1 + //CHECK-NEXT:- m_var_time: 12 + //CHECK-NEXT:- m_var_ltime: 13 + + fb2_instance.myMethod2(91,92,93,94,95,96,97,98,99,100,101,TRUE,t#103ms,lt#104ns,var_sint,var_int,var_dint,var_lint,var_usint,var_uint,var_udint,var_ulint,var_word,var_dword,var_lword,var_bool,var_time,var_ltime); + + //CHECK-NEXT:Checking fb variable after call of method2: + //CHECK-NEXT:- fb_input_sint: 0 + //CHECK-NEXT:- fb_input_int: 0 + //CHECK-NEXT:- fb_input_dint: 0 + //CHECK-NEXT:- fb_input_lint: 4 + //CHECK-NEXT:- fb_input_usint: 0 + //CHECK-NEXT:- fb_input_uint: 0 + //CHECK-NEXT:- fb_input_udint: 7 + //CHECK-NEXT:- fb_input_ulint: 0 + //CHECK-NEXT:- fb_input_word: 9 + //CHECK-NEXT:- fb_input_dword: 0 + //CHECK-NEXT:- fb_input_lword: 0 + //CHECK-NEXT:- fb_input_bool: 1 + //CHECK-NEXT:- fb_input_time: 0 + //CHECK-NEXT:- fb_input_ltime: 13000000000 + + //CHECK-NEXT:- fb_output_sint: -1 + //CHECK-NEXT:- fb_output_int: -2 + //CHECK-NEXT:- fb_output_dint: -3 + //CHECK-NEXT:- fb_output_lint: -4 + //CHECK-NEXT:- fb_output_usint: 1 + //CHECK-NEXT:- fb_output_uint: 2 + //CHECK-NEXT:- fb_output_udint: 3 + //CHECK-NEXT:- fb_output_ulint: 4 + //CHECK-NEXT:- fb_output_word: 1 + //CHECK-NEXT:- fb_output_dword: 2 + //CHECK-NEXT:- fb_output_lword: 3 + //CHECK-NEXT:- fb_output_bool: 1 + //CHECK-NEXT:- fb_output_time: 1 + //CHECK-NEXT:- fb_output_ltime: 1 + + //CHECK-NEXT:- fb_public_sint: -1 + //CHECK-NEXT:- fb_public_int: -2 + //CHECK-NEXT:- fb_public_dint: -3 + //CHECK-NEXT:- fb_public_lint: -4 + //CHECK-NEXT:- fb_public_usint: 1 + //CHECK-NEXT:- fb_public_uint: 2 + //CHECK-NEXT:- fb_public_udint: 3 + //CHECK-NEXT:- fb_public_ulint: 4 + //CHECK-NEXT:- fb_public_word: 1 + //CHECK-NEXT:- fb_public_dword: 2 + //CHECK-NEXT:- fb_public_lword: 3 + //CHECK-NEXT:- fb_public_bool: 1 + //CHECK-NEXT:- fb_public_time: 1 + //CHECK-NEXT:- fb_public_ltime: 1 + + //CHECK-NEXT:- fb_protected_sint: -1 + //CHECK-NEXT:- fb_protected_int: -2 + //CHECK-NEXT:- fb_protected_dint: -3 + //CHECK-NEXT:- fb_protected_lint: -4 + //CHECK-NEXT:- fb_protected_usint: 1 + //CHECK-NEXT:- fb_protected_uint: 2 + //CHECK-NEXT:- fb_protected_udint: 3 + //CHECK-NEXT:- fb_protected_ulint: 4 + //CHECK-NEXT:- fb_protected_word: 1 + //CHECK-NEXT:- fb_protected_dword: 2 + //CHECK-NEXT:- fb_protected_lword: 3 + //CHECK-NEXT:- fb_protected_bool: 1 + //CHECK-NEXT:- fb_protected_time: 1 + //CHECK-NEXT:- fb_protected_ltime: 1 + + //CHECK-NEXT:- fb_internal_sint: -1 + //CHECK-NEXT:- fb_internal_int: -2 + //CHECK-NEXT:- fb_internal_dint: -3 + //CHECK-NEXT:- fb_internal_lint: -4 + //CHECK-NEXT:- fb_internal_usint: 1 + //CHECK-NEXT:- fb_internal_uint: 2 + //CHECK-NEXT:- fb_internal_udint: 3 + //CHECK-NEXT:- fb_internal_ulint: 4 + //CHECK-NEXT:- fb_internal_word: 1 + //CHECK-NEXT:- fb_internal_dword: 2 + //CHECK-NEXT:- fb_internal_lword: 3 + //CHECK-NEXT:- fb_internal_bool: 1 + //CHECK-NEXT:- fb_internal_time: 1 + //CHECK-NEXT:- fb_internal_ltime: 1 + + //CHECK-NEXT:- fb_constant_sint: 1 + //CHECK-NEXT:- fb_constant_int: 2 + //CHECK-NEXT:- fb_constant_dint: 3 + //CHECK-NEXT:- fb_constant_lint: 4 + //CHECK-NEXT:- fb_constant_usint: 5 + //CHECK-NEXT:- fb_constant_uint: 6 + //CHECK-NEXT:- fb_constant_udint: 7 + //CHECK-NEXT:- fb_constant_ulint: 8 + //CHECK-NEXT:- fb_constant_word: 9 + //CHECK-NEXT:- fb_constant_dword: 10 + //CHECK-NEXT:- fb_constant_lword: 11 + //CHECK-NEXT:- fb_constant_bool: 1 + //CHECK-NEXT:- fb_constant_time: 13 + //CHECK-NEXT:- fb_constant_ltime: 14000 + + //CHECK-NEXT:Checking method2 variable after call: + //CHECK-NEXT:- m_input_sint: 91 + //CHECK-NEXT:- m_input_int: 92 + //CHECK-NEXT:- m_input_dint: 93 + //CHECK-NEXT:- m_input_lint: 94 + //CHECK-NEXT:- m_input_usint: 95 + //CHECK-NEXT:- m_input_uint: 96 + //CHECK-NEXT:- m_input_udint: 97 + //CHECK-NEXT:- m_input_ulint: 98 + //CHECK-NEXT:- m_input_word: 99 + //CHECK-NEXT:- m_input_dword: 100 + //CHECK-NEXT:- m_input_lword: 101 + //CHECK-NEXT:- m_input_bool: 1 + //CHECK-NEXT:- m_input_time: 103 + //CHECK-NEXT:- m_input_ltime: 104 + + //CHECK-NEXT:- m_output_sint: 0 + //CHECK-NEXT:- m_output_int: 0 + //CHECK-NEXT:- m_output_dint: 3 + //CHECK-NEXT:- m_output_lint: 0 + //CHECK-NEXT:- m_output_usint: 0 + //CHECK-NEXT:- m_output_uint: 0 + //CHECK-NEXT:- m_output_udint: 0 + //CHECK-NEXT:- m_output_ulint: 8 + //CHECK-NEXT:- m_output_word: 0 + //CHECK-NEXT:- m_output_dword: 0 + //CHECK-NEXT:- m_output_lword: 11 + //CHECK-NEXT:- m_output_bool: 1 + //CHECK-NEXT:- m_output_time: 0 + //CHECK-NEXT:- m_output_ltime: 13000 + + //CHECK-NEXT:- m_var_sint: 1 + //CHECK-NEXT:- m_var_int: 0 + //CHECK-NEXT:- m_var_dint: 0 + //CHECK-NEXT:- m_var_lint: 0 + //CHECK-NEXT:- m_var_usint: 5 + //CHECK-NEXT:- m_var_uint: 0 + //CHECK-NEXT:- m_var_udint: 0 + //CHECK-NEXT:- m_var_ulint: 0 + //CHECK-NEXT:- m_var_word: 9 + //CHECK-NEXT:- m_var_dword: 0 + //CHECK-NEXT:- m_var_lword: 0 + //CHECK-NEXT:- m_var_bool: 1 + //CHECK-NEXT:- m_var_time: 12 + //CHECK-NEXT:- m_var_ltime: 13 + + PRINTF('Checking changes of variable:$N'); + PRINTF('- var_sint: %d$N', var_sint); + PRINTF('- var_int: %d$N', var_int); + PRINTF('- var_dint: %d$N', var_dint); + PRINTF('- var_lint: %d$N', var_lint); + PRINTF('- var_usint: %d$N', var_usint); + PRINTF('- var_uint: %d$N', var_uint); + PRINTF('- var_udint: %d$N', var_udint); + PRINTF('- var_ulint: %d$N', var_ulint); + PRINTF('- var_word: %d$N', var_word); + PRINTF('- var_dword: %d$N', var_dword); + PRINTF('- var_lword: %d$N', var_lword); + PRINTF('- var_bool: %d$N', var_bool); + PRINTF('- var_time: %d$N', var_time); + PRINTF('- var_ltime: %d$N', var_ltime); + + //CHECK-NEXT:Checking changes of variable: + //CHECK-NEXT:- var_sint: 91 + //CHECK-NEXT:- var_int: 92 + //CHECK-NEXT:- var_dint: 93 + //CHECK-NEXT:- var_lint: 94 + //CHECK-NEXT:- var_usint: 95 + //CHECK-NEXT:- var_uint: 96 + //CHECK-NEXT:- var_udint: 97 + //CHECK-NEXT:- var_ulint: 98 + //CHECK-NEXT:- var_word: 99 + //CHECK-NEXT:- var_dword: 100 + //CHECK-NEXT:- var_lword: 101 + //CHECK-NEXT:- var_bool: 1 + //CHECK-NEXT:- var_time: 103 + //CHECK-NEXT:- var_ltime: 104 + + PRINTF('Return value of myMethod_sint: %hhd$N', fb2_instance.myMethod_sint(var_sint)); + //CHECK-NEXT:Return value of myMethod_sint: 91 + PRINTF('Return value of myMethod_int: %hd$N', fb2_instance.myMethod_int(var_int)); + //CHECK-NEXT:Return value of myMethod_int: 92 + PRINTF('Return value of myMethod_dint: %d$N', fb2_instance.myMethod_dint(var_dint)); + //CHECK-NEXT:Return value of myMethod_dint: 93 + PRINTF('Return value of myMethod_lint: %lld$N', fb2_instance.myMethod_lint(var_lint)); + //CHECK-NEXT:Return value of myMethod_lint: 94 + PRINTF('Return value of myMethod_usint: %hhu$N', fb2_instance.myMethod_usint(var_usint)); + //CHECK-NEXT:Return value of myMethod_usint: 95 + PRINTF('Return value of myMethod_uint: %hu$N', fb2_instance.myMethod_uint(var_uint)); + //CHECK-NEXT:Return value of myMethod_uint: 96 + PRINTF('Return value of myMethod_udint: %u$N', fb2_instance.myMethod_udint(var_udint)); + //CHECK-NEXT:Return value of myMethod_udint: 97 + PRINTF('Return value of myMethod_ulint: %llu$N', fb2_instance.myMethod_ulint(var_ulint)); + //CHECK-NEXT:Return value of myMethod_ulint: 98 + PRINTF('Return value of myMethod_word: %hu$N', fb2_instance.myMethod_word(var_word)); + //CHECK-NEXT:Return value of myMethod_word: 99 + PRINTF('Return value of myMethod_dword: %u$N', fb2_instance.myMethod_dword(var_dword)); + //CHECK-NEXT:Return value of myMethod_dword: 100 + PRINTF('Return value of myMethod_lword: %llu$N', fb2_instance.myMethod_lword(var_lword)); + //CHECK-NEXT:Return value of myMethod_lword: 101 + PRINTF('Return value of myMethod_bool: %d$N', fb2_instance.myMethod_bool(var_bool)); + //CHECK-NEXT:Return value of myMethod_bool: 1 + PRINTF('Return value of myMethod_time: %d$N', fb2_instance.myMethod_time(var_time)); + //CHECK-NEXT:Return value of myMethod_time: 103 + PRINTF('Return value of myMethod_ltime: %lld$N', fb2_instance.myMethod_ltime(var_ltime)); + //CHECK-NEXT:Return value of myMethod_ltime: 104 + var_arr_int := fb2_instance.myMethod_arr_int(1,3); + PRINTF('Return value of myMethod_arr_int[1]: %hd, myMethod_arr_int[2]: %hd, myMethod_arr_int[3]: %hd, myMethod_arr_int[4]: %hd, myMethod_arr_int[5]: %hd$N', var_arr_int[1], var_arr_int[2], var_arr_int[3], var_arr_int[4], var_arr_int[5]); + //CHECK-NEXT:Return value of myMethod_arr_int[1]: 1, myMethod_arr_int[2]: 0, myMethod_arr_int[3]: 3, myMethod_arr_int[4]: 0, myMethod_arr_int[5]: 0 + fb_instance.myMethod_bits(23, var_bits); + PRINTF('Return value of myMethod_byte: %d, %d, %d, %d, %d, %d, %d, %d$N', var_bits[1], var_bits[2], var_bits[3], var_bits[4], var_bits[5], var_bits[6], var_bits[7], var_bits[8]); + //CHECK-NEXT:Return value of myMethod_byte: 1, 1, 1, 0, 1, 0, 0, 0 + + if (fb2_instance.myMethod_bool(var_bool)) THEN + PRINTF('Return value of myMethod_bool inside IF: %d$N', fb2_instance.myMethod_bool(var_bool)); + END_IF + //CHECK-NEXT:Return value of myMethod_bool inside IF: 1 + + if (fb2_instance.myMethod_int(1) = fb_instance.myMethod_int(1)) THEN + PRINTF('Return value of two methods from different instances are compared successfully with each other. %hd = %hd$N', fb2_instance.myMethod_int(1), fb_instance.myMethod_int(1)); + END_IF + //CHECK-NEXT:Return value of two methods from different instances are compared successfully with each other. 1 = 1 + + if (fb2_instance.myMethod_usint(3) = 0) THEN + ; + ELSIF (fb_instance.myMethod_udint(fb2_instance.myMethod_udint(3)) = 3) THEN + PRINTF('Return value of myMethod_udint inside ELSIF: %u$N', fb_instance.myMethod_udint(fb2_instance.myMethod_udint(3))); + END_IF + //CHECK-NEXT:Return value of myMethod_udint inside ELSIF: 3 + + CASE fb2_instance.myMethod_lint(fb_instance.myMethod_lint(32)) OF + 0:; + 32: + PRINTF('Return value of myMethod_lint inside CASE: %lld$N', fb2_instance.myMethod_lint(32)); + 1:; + END_CASE + //CHECK-NEXT:Return value of myMethod_lint inside CASE: 32 + + FOR var_int := 1 TO 10 DO + PRINTF('Return value of var_int inside FOR loop: %hd$N', fb_instance.myMethod_int(var_int)); + END_FOR + //CHECK-NEXT:Return value of var_int inside FOR loop: 1 + //CHECK-NEXT:Return value of var_int inside FOR loop: 2 + //CHECK-NEXT:Return value of var_int inside FOR loop: 3 + //CHECK-NEXT:Return value of var_int inside FOR loop: 4 + //CHECK-NEXT:Return value of var_int inside FOR loop: 5 + //CHECK-NEXT:Return value of var_int inside FOR loop: 6 + //CHECK-NEXT:Return value of var_int inside FOR loop: 7 + //CHECK-NEXT:Return value of var_int inside FOR loop: 8 + //CHECK-NEXT:Return value of var_int inside FOR loop: 9 + //CHECK-NEXT:Return value of var_int inside FOR loop: 10 + + var_lint := 0; + REPEAT + var_lint := fb_instance.myMethod_lint(var_lint + 1); + PRINTF('Return value of var_lint inside REPEAT loop: %lld$N', var_lint); + UNTIL var_lint >= fb2_instance.myMethod_lint(10) + END_REPEAT + //CHECK-NEXT:Return value of var_lint inside REPEAT loop: 1 + //CHECK-NEXT:Return value of var_lint inside REPEAT loop: 2 + //CHECK-NEXT:Return value of var_lint inside REPEAT loop: 3 + //CHECK-NEXT:Return value of var_lint inside REPEAT loop: 4 + //CHECK-NEXT:Return value of var_lint inside REPEAT loop: 5 + //CHECK-NEXT:Return value of var_lint inside REPEAT loop: 6 + //CHECK-NEXT:Return value of var_lint inside REPEAT loop: 7 + //CHECK-NEXT:Return value of var_lint inside REPEAT loop: 8 + //CHECK-NEXT:Return value of var_lint inside REPEAT loop: 9 + //CHECK-NEXT:Return value of var_lint inside REPEAT loop: 10 + + WHILE var_lint < fb2_instance.myMethod_lint(20) DO + var_lint := fb_instance.myMethod_lint(var_lint + 1); + PRINTF('Return value of var_lint inside WHILE loop: %lld$N', var_lint); + END_WHILE + //CHECK-NEXT:Return value of var_lint inside WHILE loop: 11 + //CHECK-NEXT:Return value of var_lint inside WHILE loop: 12 + //CHECK-NEXT:Return value of var_lint inside WHILE loop: 13 + //CHECK-NEXT:Return value of var_lint inside WHILE loop: 14 + //CHECK-NEXT:Return value of var_lint inside WHILE loop: 15 + //CHECK-NEXT:Return value of var_lint inside WHILE loop: 16 + //CHECK-NEXT:Return value of var_lint inside WHILE loop: 17 + //CHECK-NEXT:Return value of var_lint inside WHILE loop: 18 + //CHECK-NEXT:Return value of var_lint inside WHILE loop: 19 + //CHECK-NEXT:Return value of var_lint inside WHILE loop: 20 +END_FUNCTION + +// UNSUPPORTED: rusty +// error: failed test with no error information +// PRG-4979 \ No newline at end of file diff --git a/tests/lit/single/functions/output_pointer_type_default.st b/tests/lit/single/functions/output_pointer_type_default.st new file mode 100644 index 00000000000..1c61624e8a9 --- /dev/null +++ b/tests/lit/single/functions/output_pointer_type_default.st @@ -0,0 +1,38 @@ +// RUN: %COMPILE %s && %RUN | %CHECK %s +// A pointer-typed output keeps its constructor call, so an address-valued default of its type, +// which codegen cannot store as a constant, is applied on every call. +VAR_GLOBAL + g : DINT := 99; +END_VAR + +TYPE P : POINTER TO DINT := ADR(g); END_TYPE + +FUNCTION_BLOCK Fb + METHOD m + VAR_OUTPUT + out : P; + END_VAR + END_METHOD +END_FUNCTION_BLOCK + +FUNCTION f + VAR_OUTPUT + out : P; + END_VAR +END_FUNCTION + +FUNCTION main + VAR + fb : Fb; + p : P; + END_VAR + p := ADR(p); + f(out => p); + printf('function: out^ = %d, out = ADR(g): %d$N', p^, p = ADR(g)); + // CHECK: function: out^ = 99, out = ADR(g): 1 + + p := ADR(p); + fb.m(out => p); + printf('method: out^ = %d, out = ADR(g): %d$N', p^, p = ADR(g)); + // CHECK-NEXT: method: out^ = 99, out = ADR(g): 1 +END_FUNCTION From b19b656b1a510cf43c258391c2dc5af259aadd1b Mon Sep 17 00:00:00 2001 From: Angus Bethke Date: Tue, 6 Oct 2026 09:31:25 +0000 Subject: [PATCH 9/9] fix(validation): resolve aliases for by-ref outputs so they keep defaults --- compiler/plc_lowering/src/initializer.rs | 18 ++------- src/validation/statement.rs | 5 ++- .../output_scalar_address_default.st | 38 +++++++++++++++++++ 3 files changed, 45 insertions(+), 16 deletions(-) create mode 100644 tests/lit/single/functions/output_scalar_address_default.st diff --git a/compiler/plc_lowering/src/initializer.rs b/compiler/plc_lowering/src/initializer.rs index 32af429d87b..87950c9409b 100644 --- a/compiler/plc_lowering/src/initializer.rs +++ b/compiler/plc_lowering/src/initializer.rs @@ -250,18 +250,7 @@ impl AstVisitor for Initializer { .data_type_declaration .get_referenced_type() .is_some_and(|it| index.get_type_information_or_void(it).is_vla()); - // a non-pointer scalar output of a function or method needs no constructor: codegen - // resets it to the constant default of its type, and a constructor call on it would fail - // the by-ref type check for alias types. A pointer output keeps its constructor, which - // applies an address-valued default such as ADR(g). - let is_scalar_output = variable_block_type.is_output() - && !is_stateful - && !variable - .data_type_declaration - .get_referenced_type() - .and_then(|it| index.find_effective_type_by_name(it)) - .is_some_and(|it| it.is_aggregate_type() || it.get_type_information().is_pointer()); - if !variable_block_type.is_inout() && !is_vla && !is_scalar_output { + if !variable_block_type.is_inout() && !is_vla { if let Some(constructor) = variable .data_type_declaration .get_referenced_type() @@ -1686,11 +1675,12 @@ mod tests { "#; let initializer = parse_and_init(src); - // Outputs of a function are stack variables: the struct and the pointer get a constructor - // call, the default an assignment, and the scalar alias nothing (codegen resets it) + // Outputs of a function are stack variables: every user type gets a constructor call and a + // declared default an assignment, exactly like a local insta::assert_snapshot!(print_body_to_string(initializer.stack_constructor.get("MyFunction").unwrap()), @" intern: MyStruct__ctor(outStruct) + MyInt__ctor(outAlias) MyPtr__ctor(outPointer) outDefault := 3 "); diff --git a/src/validation/statement.rs b/src/validation/statement.rs index d901d610ddb..4005bac1b4b 100644 --- a/src/validation/statement.rs +++ b/src/validation/statement.rs @@ -1320,14 +1320,15 @@ fn validate_by_ref_argument_type( context.index.get_effective_type_or_void_by_name(param.get_type_name()) }; - // Reference arguments are transparent: compare their target type. + // Reference arguments are transparent: compare their target type. An auto-deref variable is + // annotated with the raw name of its target type, so resolve aliases as for any other variable. let arg_type = context.annotations.get_type_or_void(arg, context.index); let arg_type = if let DataTypeInformation::Pointer { inner_type_name, auto_deref: Some(_), .. } = arg_type.get_type_information() { context.index.get_effective_type_or_void_by_name(inner_type_name) } else { - arg_type + context.index.find_effective_type(arg_type).unwrap_or(arg_type) }; if arg_type.get_type_information().is_pointer() { diff --git a/tests/lit/single/functions/output_scalar_address_default.st b/tests/lit/single/functions/output_scalar_address_default.st new file mode 100644 index 00000000000..1b6efba757a --- /dev/null +++ b/tests/lit/single/functions/output_scalar_address_default.st @@ -0,0 +1,38 @@ +// RUN: %COMPILE %s && %RUN | %CHECK %s +// A scalar type whose default is an address, which codegen cannot store as a constant, is applied +// to an output of that type by the type's constructor on every call, in a function and a method. +VAR_GLOBAL + g : DINT := 99; +END_VAR + +TYPE A : LWORD := ADR(g); END_TYPE + +FUNCTION_BLOCK Fb + METHOD m + VAR_OUTPUT + out : A; + END_VAR + END_METHOD +END_FUNCTION_BLOCK + +FUNCTION f + VAR_OUTPUT + out : A; + END_VAR +END_FUNCTION + +FUNCTION main + VAR + fb : Fb; + a : A; + END_VAR + a := 1; + f(out => a); + printf('function: out = ADR(g): %d$N', a = ADR(g)); + // CHECK: function: out = ADR(g): 1 + + a := 1; + fb.m(out => a); + printf('method: out = ADR(g): %d$N', a = ADR(g)); + // CHECK-NEXT: method: out = ADR(g): 1 +END_FUNCTION