Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions src/codegen/generators/expression_generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
63 changes: 63 additions & 0 deletions src/codegen/tests/typesystem_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 = '<internal>'
source_filename = "<internal>"
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 }
"#);
}
10 changes: 10 additions & 0 deletions tests/lit/single/variadics/string_passed_as_pointer.st
Original file line number Diff line number Diff line change
@@ -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