From 0f8717248cd890c92a5a70d620f36a4c3bbb944a Mon Sep 17 00:00:00 2001 From: Michael Haselberger Date: Fri, 5 Dec 2025 08:44:27 +0000 Subject: [PATCH] fix: array var-args decay to pointers --- .../generators/expression_generator.rs | 11 +++- src/codegen/tests/typesystem_test.rs | 63 +++++++++++++++++++ .../variadics/string_passed_as_pointer.st | 10 +++ 3 files changed, 82 insertions(+), 2 deletions(-) create mode 100644 tests/lit/single/variadics/string_passed_as_pointer.st diff --git a/src/codegen/generators/expression_generator.rs b/src/codegen/generators/expression_generator.rs index 7dfbe04c9f5..6f9ee9e4315 100644 --- a/src/codegen/generators/expression_generator.rs +++ b/src/codegen/generators/expression_generator.rs @@ -1194,12 +1194,19 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> { .get_variadic_member(pou.get_name()) .and_then(|it| it.get_varargs().zip(Some(it.get_declaration_type()))) { + // For unsized variadics, we need to follow C ABI rules + let is_unsized = matches!(var_args, VarArgs::Unsized(_)); + let generated_params = variadic_params .iter() .map(|param_statement| { self.get_type_hint_for(param_statement).map(|it| it.get_name()).and_then(|type_name| { - // if the variadic is defined in a by_ref block, we need to pass the argument as reference - if argument_type.is_by_ref() { + // Check if we need to pass by reference: + // 1. If the variadic is defined in a by_ref block + // 2. For unsized variadics: arrays and strings decay to pointers per C ABI + let type_info = self.index.get_effective_type_or_void_by_name(type_name); + let is_array_or_string = type_info.is_array() || type_info.is_string(); + if argument_type.is_by_ref() || (is_unsized && is_array_or_string) { self.generate_argument_by_ref( param_statement, type_name, diff --git a/src/codegen/tests/typesystem_test.rs b/src/codegen/tests/typesystem_test.rs index 1b532edcd6f..fa6c2e92f74 100644 --- a/src/codegen/tests/typesystem_test.rs +++ b/src/codegen/tests/typesystem_test.rs @@ -419,3 +419,66 @@ fn self_referential_struct_via_reference_codegen() { } "#); } + +#[test] +fn arrays_and_strings_passed_as_pointers_in_unsized_variadics() { + // Test that arrays and strings are passed as pointers to unsized variadic functions + // following C ABI conventions (array/string decay to pointers) + let src = r#" + {external} + FUNCTION printf : DINT + VAR_INPUT + format: STRING; + args: ...; + END_VAR + END_FUNCTION + + PROGRAM main + VAR_TEMP + myString: STRING := 'hello'; + myArray: ARRAY[0..2] OF INT := [1, 2, 3]; + END_VAR + // Both STRING and ARRAY should be passed as pointers (i8*) + printf('String: %s', myString); + printf('Array: %p', myArray); + END_PROGRAM + "#; + + let result = codegen(src); + filtered_assert_snapshot!(result, @r#" + ; ModuleID = '' + source_filename = "" + target datalayout = "[filtered]" + target triple = "[filtered]" + + %main = type {} + + @main_instance = global %main zeroinitializer + @__main.myString__init = unnamed_addr constant [81 x i8] c"hello\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00" + @__main.myArray__init = unnamed_addr constant [3 x i16] [i16 1, i16 2, i16 3] + @utf08_literal_0 = private unnamed_addr constant [10 x i8] c"Array: %p\00" + @utf08_literal_1 = private unnamed_addr constant [11 x i8] c"String: %s\00" + + declare i32 @printf(i8*, ...) + + define void @main(%main* %0) { + entry: + %myString = alloca [81 x i8], align 1 + %myArray = alloca [3 x i16], align 2 + %1 = bitcast [81 x i8]* %myString to i8* + call void @llvm.memcpy.p0i8.p0i8.i64(i8* align 1 %1, i8* align 1 getelementptr inbounds ([81 x i8], [81 x i8]* @__main.myString__init, i32 0, i32 0), i64 ptrtoint ([81 x i8]* getelementptr ([81 x i8], [81 x i8]* null, i32 1) to i64), i1 false) + %2 = bitcast [3 x i16]* %myArray to i8* + call void @llvm.memcpy.p0i8.p0i8.i64(i8* align 1 %2, i8* align 1 bitcast ([3 x i16]* @__main.myArray__init to i8*), i64 ptrtoint ([3 x i16]* getelementptr ([3 x i16], [3 x i16]* null, i32 1) to i64), i1 false) + %3 = bitcast [81 x i8]* %myString to i8* + %call = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([11 x i8], [11 x i8]* @utf08_literal_1, i32 0, i32 0), i8* %3) + %4 = bitcast [3 x i16]* %myArray to i16* + %call1 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([10 x i8], [10 x i8]* @utf08_literal_0, i32 0, i32 0), i16* %4) + ret void + } + + ; Function Attrs: argmemonly nofree nounwind willreturn + declare void @llvm.memcpy.p0i8.p0i8.i64(i8* noalias nocapture writeonly, i8* noalias nocapture readonly, i64, i1 immarg) #0 + + attributes #0 = { argmemonly nofree nounwind willreturn } + "#); +} diff --git a/tests/lit/single/variadics/string_passed_as_pointer.st b/tests/lit/single/variadics/string_passed_as_pointer.st new file mode 100644 index 00000000000..bec7a95e25d --- /dev/null +++ b/tests/lit/single/variadics/string_passed_as_pointer.st @@ -0,0 +1,10 @@ +// RUN: (%COMPILE %s && %RUN) | %CHECK %s +// Test that STRING types are correctly passed as pointers to variadic functions +// per C spec (strings decay to char* pointers) +// CHECK: My string is: hello +FUNCTION main : DINT +VAR + myString : STRING := 'hello'; +END_VAR + printf('My string is: %s$N', myString); +END_FUNCTION