Skip to content

Commit 96d0492

Browse files
authored
fix: aggregate output assignments (PLC-lang#1234)
* fix: aggregate output assignments Renames `generate_string_store` to `build_memcpy` and adapts the logic to work for all aggregate types, not just strings.
1 parent b3b59d7 commit 96d0492

File tree

3 files changed

+179
-68
lines changed

3 files changed

+179
-68
lines changed

src/codegen/generators/expression_generator.rs

Lines changed: 41 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -572,12 +572,8 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> {
572572
let output_value_type =
573573
self.index.get_type_information_or_void(parameter.get_type_name());
574574

575-
//Special string handling
576-
if (assigned_output_type.is_string() && output_value_type.is_string())
577-
|| (assigned_output_type.is_struct() && output_value_type.is_struct())
578-
|| (assigned_output_type.is_array() && output_value_type.is_array())
579-
{
580-
self.generate_string_store(
575+
if assigned_output_type.is_aggregate() && output_value_type.is_aggregate() {
576+
self.build_memcpy(
581577
assigned_output,
582578
assigned_output_type,
583579
expression.get_location(),
@@ -2185,55 +2181,26 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> {
21852181
let right_type =
21862182
self.annotations.get_type_or_void(right_statement, self.index).get_type_information();
21872183

2188-
//Special string handling
2189-
if left_type.is_string() && right_type.is_string()
2190-
//string-literals are also generated as global constant variables so we can always assume that
2191-
//we have a pointer-value
2192-
{
2184+
// redirect aggregate types
2185+
if left_type.is_aggregate() && right_type.is_aggregate() {
21932186
let right =
2194-
self.generate_expression_value(right_statement).map(|expr_value| match expr_value {
2195-
ExpressionValue::LValue(ptr) => ptr,
2196-
ExpressionValue::RValue(_) => unreachable!(
2197-
"strings should be lvalues: {:?}, {:?}",
2198-
right_statement.get_location(),
2199-
right_statement
2200-
),
2201-
})?;
2202-
self.generate_string_store(
2187+
self.generate_expression_value(right_statement)?.get_basic_value_enum().into_pointer_value();
2188+
self.build_memcpy(
22032189
left,
22042190
left_type,
22052191
right_statement.get_location(),
22062192
right,
22072193
right_type,
22082194
right_statement.get_location(),
22092195
)?;
2210-
} else if (left_type.is_struct() && right_type.is_struct())
2211-
|| (left_type.is_array() && right_type.is_array())
2212-
{
2213-
//memcopy right_statement into left
2214-
let expression =
2215-
self.generate_expression_value(right_statement)?.get_basic_value_enum().into_pointer_value();
2216-
2217-
let size =
2218-
self.llvm_index.get_associated_type(right_type.get_name())?.size_of().ok_or_else(|| {
2219-
Diagnostic::codegen_error(
2220-
format!("Unknown size of type {}.", right_type.get_name()).as_str(),
2221-
right_statement.get_location(),
2222-
)
2223-
})?;
2224-
2225-
self.llvm
2226-
.builder
2227-
.build_memcpy(left, 1, expression, 1, size)
2228-
.map_err(|err| Diagnostic::codegen_error(err, right_statement.get_location()))?;
22292196
} else {
22302197
let expression = self.generate_expression(right_statement)?;
22312198
self.llvm.builder.build_store(left, expression);
22322199
}
22332200
Ok(())
22342201
}
22352202

2236-
pub fn generate_string_store(
2203+
fn build_memcpy(
22372204
&self,
22382205
left: inkwell::values::PointerValue<'ink>,
22392206
left_type: &DataTypeInformation,
@@ -2242,35 +2209,45 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> {
22422209
right_type: &DataTypeInformation,
22432210
right_location: SourceLocation,
22442211
) -> Result<PointerValue<'ink>, Diagnostic> {
2245-
let target_size = self.get_string_size(left_type, left_location.clone())?;
2246-
let value_size = self.get_string_size(right_type, right_location)?;
2247-
let size = std::cmp::min(target_size - 1, value_size);
2248-
let align_left = left_type.get_string_character_width(self.index).value();
2249-
let align_right = right_type.get_string_character_width(self.index).value();
2250-
//Multiply by the string alignment to copy enough for widestrings
2251-
//This is done at compile time to avoid generating an extra mul
2252-
let size = self.llvm.context.i32_type().const_int((size * align_left as i64) as u64, true);
2212+
let (size, alignment) = match (left_type, right_type) {
2213+
(
2214+
DataTypeInformation::String { size: lsize, .. },
2215+
DataTypeInformation::String { size: rsize, .. },
2216+
) => {
2217+
let target_size = lsize
2218+
.as_int_value(self.index)
2219+
.map_err(|err| Diagnostic::codegen_error(err.as_str(), left_location.clone()))?;
2220+
let value_size = rsize
2221+
.as_int_value(self.index)
2222+
.map_err(|err| Diagnostic::codegen_error(err.as_str(), right_location))?;
2223+
let size = std::cmp::min(target_size - 1, value_size);
2224+
let alignment = left_type.get_string_character_width(self.index).value();
2225+
//Multiply by the string alignment to copy enough for widestrings
2226+
//This is done at compile time to avoid generating an extra mul
2227+
let size = self.llvm.context.i32_type().const_int((size * alignment as i64) as u64, true);
2228+
(size, alignment)
2229+
}
2230+
(DataTypeInformation::Array { .. }, DataTypeInformation::Array { .. })
2231+
| (DataTypeInformation::Struct { .. }, DataTypeInformation::Struct { .. }) => {
2232+
let size = self.llvm_index.get_associated_type(right_type.get_name())?.size_of().ok_or_else(
2233+
|| {
2234+
Diagnostic::codegen_error(
2235+
format!("Unknown size of type {}.", right_type.get_name()).as_str(),
2236+
right_location,
2237+
)
2238+
},
2239+
)?;
2240+
(size, 1)
2241+
}
2242+
_ => unreachable!("memcpy is not used for non-aggregate types"),
2243+
};
2244+
22532245
self.llvm
22542246
.builder
2255-
.build_memcpy(left, align_left, right, align_right, size)
2247+
.build_memcpy(left, alignment, right, alignment, size)
22562248
.map_err(|err| Diagnostic::codegen_error(err, left_location))
22572249
}
22582250

2259-
fn get_string_size(
2260-
&self,
2261-
datatype: &DataTypeInformation,
2262-
location: SourceLocation,
2263-
) -> Result<i64, Diagnostic> {
2264-
if let DataTypeInformation::String { size, .. } = datatype {
2265-
size.as_int_value(self.index).map_err(|err| Diagnostic::codegen_error(err.as_str(), location))
2266-
} else {
2267-
Err(Diagnostic::codegen_error(
2268-
format!("{} is not a String", datatype.get_name()).as_str(),
2269-
location,
2270-
))
2271-
}
2272-
}
2273-
22742251
/// returns an optional name used for a temporary variable when loading a pointer represented by `expression`
22752252
fn get_load_name(&self, expression: &AstNode) -> Option<String> {
22762253
match expression.get_stmt() {

src/codegen/tests/parameters_tests.rs

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1079,3 +1079,100 @@ fn by_value_fb_arg_aggregates_are_memcopied() {
10791079
attributes #1 = { argmemonly nofree nounwind willreturn }
10801080
"###);
10811081
}
1082+
1083+
#[test]
1084+
fn var_output_aggregate_types_are_memcopied() {
1085+
let result = codegen(
1086+
r#"
1087+
TYPE OUT_TYPE : STRUCT
1088+
a : BYTE;
1089+
END_STRUCT;
1090+
END_TYPE
1091+
1092+
FUNCTION_BLOCK FB
1093+
VAR_OUTPUT
1094+
output : OUT_TYPE;
1095+
output2 : ARRAY[0..10] OF DINT;
1096+
output3 : ARRAY[0..10] OF OUT_TYPE;
1097+
output4 : STRING;
1098+
output5 : WSTRING;
1099+
END_VAR
1100+
END_FUNCTION_BLOCK
1101+
1102+
PROGRAM PRG
1103+
VAR
1104+
out: OUT_TYPE;
1105+
out2 : ARRAY[0..10] OF DINT;
1106+
out3 : ARRAY[0..10] OF OUT_TYPE;
1107+
out4 : STRING;
1108+
out5 : WSTRING;
1109+
station: FB;
1110+
END_VAR
1111+
station(output => out, output2 => out2, output3 => out3, output4 => out4, output5 => out5);
1112+
END_PROGRAM
1113+
"#,
1114+
);
1115+
1116+
assert_snapshot!(result, @r###"
1117+
; ModuleID = 'main'
1118+
source_filename = "main"
1119+
1120+
%FB = type { %OUT_TYPE, [11 x i32], [11 x %OUT_TYPE], [81 x i8], [81 x i16] }
1121+
%OUT_TYPE = type { i8 }
1122+
%PRG = type { %OUT_TYPE, [11 x i32], [11 x %OUT_TYPE], [81 x i8], [81 x i16], %FB }
1123+
1124+
@__FB__init = unnamed_addr constant %FB zeroinitializer
1125+
@__OUT_TYPE__init = unnamed_addr constant %OUT_TYPE zeroinitializer
1126+
@PRG_instance = global %PRG zeroinitializer
1127+
1128+
define void @FB(%FB* %0) section "fn-FB:v[v][v][v][s8u81][s16u81]" {
1129+
entry:
1130+
%output = getelementptr inbounds %FB, %FB* %0, i32 0, i32 0
1131+
%output2 = getelementptr inbounds %FB, %FB* %0, i32 0, i32 1
1132+
%output3 = getelementptr inbounds %FB, %FB* %0, i32 0, i32 2
1133+
%output4 = getelementptr inbounds %FB, %FB* %0, i32 0, i32 3
1134+
%output5 = getelementptr inbounds %FB, %FB* %0, i32 0, i32 4
1135+
ret void
1136+
}
1137+
1138+
define void @PRG(%PRG* %0) section "fn-PRG:v" {
1139+
entry:
1140+
%out = getelementptr inbounds %PRG, %PRG* %0, i32 0, i32 0
1141+
%out2 = getelementptr inbounds %PRG, %PRG* %0, i32 0, i32 1
1142+
%out3 = getelementptr inbounds %PRG, %PRG* %0, i32 0, i32 2
1143+
%out4 = getelementptr inbounds %PRG, %PRG* %0, i32 0, i32 3
1144+
%out5 = getelementptr inbounds %PRG, %PRG* %0, i32 0, i32 4
1145+
%station = getelementptr inbounds %PRG, %PRG* %0, i32 0, i32 5
1146+
call void @FB(%FB* %station)
1147+
%1 = getelementptr inbounds %FB, %FB* %station, i32 0, i32 0
1148+
%2 = bitcast %OUT_TYPE* %out to i8*
1149+
%3 = bitcast %OUT_TYPE* %1 to i8*
1150+
call void @llvm.memcpy.p0i8.p0i8.i64(i8* align 1 %2, i8* align 1 %3, i64 ptrtoint (%OUT_TYPE* getelementptr (%OUT_TYPE, %OUT_TYPE* null, i32 1) to i64), i1 false)
1151+
%4 = getelementptr inbounds %FB, %FB* %station, i32 0, i32 1
1152+
%5 = bitcast [11 x i32]* %out2 to i8*
1153+
%6 = bitcast [11 x i32]* %4 to i8*
1154+
call void @llvm.memcpy.p0i8.p0i8.i64(i8* align 1 %5, i8* align 1 %6, i64 ptrtoint ([11 x i32]* getelementptr ([11 x i32], [11 x i32]* null, i32 1) to i64), i1 false)
1155+
%7 = getelementptr inbounds %FB, %FB* %station, i32 0, i32 2
1156+
%8 = bitcast [11 x %OUT_TYPE]* %out3 to i8*
1157+
%9 = bitcast [11 x %OUT_TYPE]* %7 to i8*
1158+
call void @llvm.memcpy.p0i8.p0i8.i64(i8* align 1 %8, i8* align 1 %9, i64 ptrtoint ([11 x %OUT_TYPE]* getelementptr ([11 x %OUT_TYPE], [11 x %OUT_TYPE]* null, i32 1) to i64), i1 false)
1159+
%10 = getelementptr inbounds %FB, %FB* %station, i32 0, i32 3
1160+
%11 = bitcast [81 x i8]* %out4 to i8*
1161+
%12 = bitcast [81 x i8]* %10 to i8*
1162+
call void @llvm.memcpy.p0i8.p0i8.i32(i8* align 1 %11, i8* align 1 %12, i32 80, i1 false)
1163+
%13 = getelementptr inbounds %FB, %FB* %station, i32 0, i32 4
1164+
%14 = bitcast [81 x i16]* %out5 to i8*
1165+
%15 = bitcast [81 x i16]* %13 to i8*
1166+
call void @llvm.memcpy.p0i8.p0i8.i32(i8* align 2 %14, i8* align 2 %15, i32 160, i1 false)
1167+
ret void
1168+
}
1169+
1170+
; Function Attrs: argmemonly nofree nounwind willreturn
1171+
declare void @llvm.memcpy.p0i8.p0i8.i64(i8* noalias nocapture writeonly, i8* noalias nocapture readonly, i64, i1 immarg) #0
1172+
1173+
; Function Attrs: argmemonly nofree nounwind willreturn
1174+
declare void @llvm.memcpy.p0i8.p0i8.i32(i8* noalias nocapture writeonly, i8* noalias nocapture readonly, i32, i1 immarg) #0
1175+
1176+
attributes #0 = { argmemonly nofree nounwind willreturn }
1177+
"###);
1178+
}

tests/correctness/functions.rs

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::ffi::CStr;
2+
13
use rusty::codegen::CodegenContext;
24

35
// Copyright (c) 2020 Ghaith Hachem and Mathias Rieder
@@ -642,6 +644,41 @@ fn optional_output_assignment_in_functions() {
642644
assert_eq!(2, interface.var2);
643645
}
644646

647+
#[test]
648+
fn aggregate_var_output_assignment() {
649+
#[repr(C)]
650+
struct MainType {
651+
var1: [u8; 81],
652+
var2: [i32; 4],
653+
}
654+
655+
let main = r#"
656+
PROGRAM foo
657+
VAR_OUTPUT
658+
output1 : STRING;
659+
output2 : ARRAY[0..3] OF DINT;
660+
END_VAR
661+
output1 := 'Hello, world!';
662+
output2 := [5, 7, 11, 13];
663+
END_PROGRAM
664+
665+
PROGRAM main
666+
VAR
667+
var1 : STRING;
668+
var2 : ARRAY[0..3] OF DINT;
669+
END_VAR
670+
foo(output1 => var1, output2 => var2);
671+
END_PROGRAM
672+
"#;
673+
674+
let mut interface = MainType { var1: [0; 81], var2: [0; 4] };
675+
let _: i32 = compile_and_run(main.to_string(), &mut interface);
676+
677+
let str = CStr::from_bytes_until_nul(&interface.var1).unwrap().to_string_lossy();
678+
assert_eq!("Hello, world!", str);
679+
assert_eq!([5, 7, 11, 13], interface.var2);
680+
}
681+
645682
#[test]
646683
fn direct_call_on_function_block_array_access() {
647684
#[allow(dead_code)]
@@ -872,12 +909,12 @@ fn mux_struct_ref() {
872909
#[repr(C)]
873910
#[derive(Default)]
874911
struct MainType {
875-
res: myStruct,
912+
res: MyStruct,
876913
}
877914

878915
#[repr(C)]
879916
#[derive(Default)]
880-
struct myStruct {
917+
struct MyStruct {
881918
a: bool,
882919
b: bool,
883920
}
@@ -1026,12 +1063,12 @@ fn sel_struct_ref() {
10261063
#[repr(C)]
10271064
#[derive(Default)]
10281065
struct MainType {
1029-
res: myStruct,
1066+
res: MyStruct,
10301067
}
10311068

10321069
#[repr(C)]
10331070
#[derive(Default)]
1034-
struct myStruct {
1071+
struct MyStruct {
10351072
a: bool,
10361073
b: bool,
10371074
}

0 commit comments

Comments
 (0)