Skip to content

Commit 8a07316

Browse files
authored
feat: support referencing local variables in pointer initialization (#1346)
Adds support to initialize pointer, alias and reference-to variables with the address of local POU members. The only exception is that stateful members cannot be initialized with addresses of temporary values, since 1) they do not exist at the time of initialization and 2) would lead to dangling pointers after going out of scope.
1 parent cbe548d commit 8a07316

File tree

12 files changed

+629
-55
lines changed

12 files changed

+629
-55
lines changed

compiler/plc_diagnostics/src/diagnostics/diagnostics_registry.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ lazy_static! {
210210
E106, Warning, include_str!("./error_codes/E106.md"), // VAR_EXTERNAL have no effect
211211
E107, Error, include_str!("./error_codes/E107.md"), // Missing configuration for template variable
212212
E108, Error, include_str!("./error_codes/E108.md"), // Template variable is configured multiple times
213+
E109, Error, include_str!("./error_codes/E109.md"), // Stateful pointer variable initialized with temporary value
213214
);
214215
}
215216

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Stateful member variable initialized with temporary reference
2+
3+
Stack-local variables do not yet exist at the time of initialization. Additionally, pointing to a temporary variable will lead to a dangling pointer
4+
as soon as it goes out of scope - potential use after free.
5+
6+
Erroneous code example:
7+
```
8+
FUNCTION_BLOCK foo
9+
VAR
10+
a : REF_TO BOOL := REF(b);
11+
END_VAR
12+
VAR_TEMP
13+
b : BOOL;
14+
END_VAR
15+
END_FUNCTION_BLOCK
16+
```

src/codegen/generators/expression_generator.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -684,7 +684,7 @@ impl<'ink, 'b> ExpressionCodeGenerator<'ink, 'b> {
684684
} else {
685685
// TODO: using the global context we could get a slice here; currently not possible because the
686686
// global context isn't passed into codegen
687-
Err(Diagnostic::new(&format!("{element:?} not a direct access"))
687+
Err(Diagnostic::new(format!("{element:?} not a direct access"))
688688
.with_error_code("E055")
689689
.with_location(element.get_location()))
690690
}?;

src/codegen/generators/pou_generator.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -772,6 +772,20 @@ impl<'ink, 'cg> PouGenerator<'ink, 'cg> {
772772
variable.source_location.get_column(),
773773
);
774774
}
775+
776+
if self
777+
.index
778+
.find_effective_type_by_name(variable.get_type_name())
779+
.map(|it| it.get_type_information())
780+
.is_some_and(|it| it.is_reference_to() || it.is_alias())
781+
{
782+
// aliases and reference to variables have special handling for initialization. initialize with a nullpointer
783+
self.llvm.builder.build_store(
784+
left,
785+
left.get_type().get_element_type().into_pointer_type().const_null(),
786+
);
787+
continue;
788+
};
775789
let right_stmt =
776790
self.index.get_const_expressions().maybe_get_constant_statement(&variable.initial_value);
777791
self.generate_variable_initializer(variable, left, right_stmt, &exp_gen)?;

src/codegen/tests/initialization_test/complex_initializers.rs

Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1368,3 +1368,275 @@ fn var_external_blocks_are_ignored_in_init_functions() {
13681368
}
13691369
"###)
13701370
}
1371+
1372+
#[test]
1373+
fn ref_to_local_member() {
1374+
let res = codegen(
1375+
r"
1376+
FUNCTION_BLOCK foo
1377+
VAR
1378+
s : STRING;
1379+
ptr : REF_TO STRING := REF(s);
1380+
alias AT s : STRING;
1381+
reference_to : REFERENCE TO STRING REF= s;
1382+
END_VAR
1383+
END_FUNCTION_BLOCK
1384+
",
1385+
);
1386+
1387+
insta::assert_snapshot!(res, @r#"
1388+
; ModuleID = '<internal>'
1389+
source_filename = "<internal>"
1390+
1391+
%foo = type { [81 x i8], [81 x i8]*, [81 x i8]*, [81 x i8]* }
1392+
1393+
@__foo__init = unnamed_addr constant %foo zeroinitializer
1394+
1395+
define void @foo(%foo* %0) {
1396+
entry:
1397+
%s = getelementptr inbounds %foo, %foo* %0, i32 0, i32 0
1398+
%ptr = getelementptr inbounds %foo, %foo* %0, i32 0, i32 1
1399+
%alias = getelementptr inbounds %foo, %foo* %0, i32 0, i32 2
1400+
%reference_to = getelementptr inbounds %foo, %foo* %0, i32 0, i32 3
1401+
ret void
1402+
}
1403+
; ModuleID = '__initializers'
1404+
source_filename = "__initializers"
1405+
1406+
%foo = type { [81 x i8], [81 x i8]*, [81 x i8]*, [81 x i8]* }
1407+
1408+
@__foo__init = external global %foo
1409+
1410+
define void @__init_foo(%foo* %0) {
1411+
entry:
1412+
%self = alloca %foo*, align 8
1413+
store %foo* %0, %foo** %self, align 8
1414+
%deref = load %foo*, %foo** %self, align 8
1415+
%ptr = getelementptr inbounds %foo, %foo* %deref, i32 0, i32 1
1416+
%deref1 = load %foo*, %foo** %self, align 8
1417+
%s = getelementptr inbounds %foo, %foo* %deref1, i32 0, i32 0
1418+
store [81 x i8]* %s, [81 x i8]** %ptr, align 8
1419+
%deref2 = load %foo*, %foo** %self, align 8
1420+
%alias = getelementptr inbounds %foo, %foo* %deref2, i32 0, i32 2
1421+
%deref3 = load %foo*, %foo** %self, align 8
1422+
%s4 = getelementptr inbounds %foo, %foo* %deref3, i32 0, i32 0
1423+
store [81 x i8]* %s4, [81 x i8]** %alias, align 8
1424+
%deref5 = load %foo*, %foo** %self, align 8
1425+
%reference_to = getelementptr inbounds %foo, %foo* %deref5, i32 0, i32 3
1426+
%deref6 = load %foo*, %foo** %self, align 8
1427+
%s7 = getelementptr inbounds %foo, %foo* %deref6, i32 0, i32 0
1428+
store [81 x i8]* %s7, [81 x i8]** %reference_to, align 8
1429+
ret void
1430+
}
1431+
1432+
declare void @foo(%foo*)
1433+
; ModuleID = '__init___testproject'
1434+
source_filename = "__init___testproject"
1435+
1436+
@llvm.global_ctors = appending global [1 x { i32, void ()*, i8* }] [{ i32, void ()*, i8* } { i32 0, void ()* @__init___testproject, i8* null }]
1437+
1438+
define void @__init___testproject() {
1439+
entry:
1440+
ret void
1441+
}
1442+
"#)
1443+
}
1444+
1445+
#[test]
1446+
fn ref_to_local_member_shadows_global() {
1447+
let res = codegen(
1448+
r"
1449+
VAR_GLOBAL
1450+
s : STRING;
1451+
END_VAR
1452+
1453+
FUNCTION_BLOCK foo
1454+
VAR
1455+
s : STRING;
1456+
ptr : REF_TO STRING := REF(s);
1457+
alias AT s : STRING;
1458+
reference_to : REFERENCE TO STRING REF= s;
1459+
END_VAR
1460+
END_FUNCTION_BLOCK
1461+
",
1462+
);
1463+
1464+
insta::assert_snapshot!(res, @r#"
1465+
; ModuleID = '<internal>'
1466+
source_filename = "<internal>"
1467+
1468+
%foo = type { [81 x i8], [81 x i8]*, [81 x i8]*, [81 x i8]* }
1469+
1470+
@s = global [81 x i8] zeroinitializer
1471+
@__foo__init = unnamed_addr constant %foo zeroinitializer
1472+
1473+
define void @foo(%foo* %0) {
1474+
entry:
1475+
%s = getelementptr inbounds %foo, %foo* %0, i32 0, i32 0
1476+
%ptr = getelementptr inbounds %foo, %foo* %0, i32 0, i32 1
1477+
%alias = getelementptr inbounds %foo, %foo* %0, i32 0, i32 2
1478+
%reference_to = getelementptr inbounds %foo, %foo* %0, i32 0, i32 3
1479+
ret void
1480+
}
1481+
; ModuleID = '__initializers'
1482+
source_filename = "__initializers"
1483+
1484+
%foo = type { [81 x i8], [81 x i8]*, [81 x i8]*, [81 x i8]* }
1485+
1486+
@__foo__init = external global %foo
1487+
1488+
define void @__init_foo(%foo* %0) {
1489+
entry:
1490+
%self = alloca %foo*, align 8
1491+
store %foo* %0, %foo** %self, align 8
1492+
%deref = load %foo*, %foo** %self, align 8
1493+
%ptr = getelementptr inbounds %foo, %foo* %deref, i32 0, i32 1
1494+
%deref1 = load %foo*, %foo** %self, align 8
1495+
%s = getelementptr inbounds %foo, %foo* %deref1, i32 0, i32 0
1496+
store [81 x i8]* %s, [81 x i8]** %ptr, align 8
1497+
%deref2 = load %foo*, %foo** %self, align 8
1498+
%alias = getelementptr inbounds %foo, %foo* %deref2, i32 0, i32 2
1499+
%deref3 = load %foo*, %foo** %self, align 8
1500+
%s4 = getelementptr inbounds %foo, %foo* %deref3, i32 0, i32 0
1501+
store [81 x i8]* %s4, [81 x i8]** %alias, align 8
1502+
%deref5 = load %foo*, %foo** %self, align 8
1503+
%reference_to = getelementptr inbounds %foo, %foo* %deref5, i32 0, i32 3
1504+
%deref6 = load %foo*, %foo** %self, align 8
1505+
%s7 = getelementptr inbounds %foo, %foo* %deref6, i32 0, i32 0
1506+
store [81 x i8]* %s7, [81 x i8]** %reference_to, align 8
1507+
ret void
1508+
}
1509+
1510+
declare void @foo(%foo*)
1511+
; ModuleID = '__init___testproject'
1512+
source_filename = "__init___testproject"
1513+
1514+
@llvm.global_ctors = appending global [1 x { i32, void ()*, i8* }] [{ i32, void ()*, i8* } { i32 0, void ()* @__init___testproject, i8* null }]
1515+
1516+
define void @__init___testproject() {
1517+
entry:
1518+
ret void
1519+
}
1520+
"#)
1521+
}
1522+
1523+
#[test]
1524+
fn temporary_variable_ref_to_local_member() {
1525+
let res = codegen(
1526+
r"
1527+
FUNCTION_BLOCK foo
1528+
VAR
1529+
s : STRING;
1530+
END_VAR
1531+
VAR_TEMP
1532+
ptr : REF_TO STRING := REF(s);
1533+
alias AT s : STRING;
1534+
reference_to : REFERENCE TO STRING REF= s;
1535+
END_VAR
1536+
END_FUNCTION_BLOCK
1537+
",
1538+
);
1539+
1540+
insta::assert_snapshot!(res, @r#"
1541+
; ModuleID = '<internal>'
1542+
source_filename = "<internal>"
1543+
1544+
%foo = type { [81 x i8] }
1545+
1546+
@__foo__init = unnamed_addr constant %foo zeroinitializer
1547+
1548+
define void @foo(%foo* %0) {
1549+
entry:
1550+
%s = getelementptr inbounds %foo, %foo* %0, i32 0, i32 0
1551+
%ptr = alloca [81 x i8]*, align 8
1552+
%alias = alloca [81 x i8]*, align 8
1553+
%reference_to = alloca [81 x i8]*, align 8
1554+
store [81 x i8]* %s, [81 x i8]** %ptr, align 8
1555+
store [81 x i8]* null, [81 x i8]** %alias, align 8
1556+
store [81 x i8]* null, [81 x i8]** %reference_to, align 8
1557+
store [81 x i8]* %s, [81 x i8]** %ptr, align 8
1558+
store [81 x i8]* %s, [81 x i8]** %alias, align 8
1559+
store [81 x i8]* %s, [81 x i8]** %reference_to, align 8
1560+
ret void
1561+
}
1562+
; ModuleID = '__initializers'
1563+
source_filename = "__initializers"
1564+
1565+
%foo = type { [81 x i8] }
1566+
1567+
@__foo__init = external global %foo
1568+
1569+
define void @__init_foo(%foo* %0) {
1570+
entry:
1571+
%self = alloca %foo*, align 8
1572+
store %foo* %0, %foo** %self, align 8
1573+
ret void
1574+
}
1575+
1576+
declare void @foo(%foo*)
1577+
; ModuleID = '__init___testproject'
1578+
source_filename = "__init___testproject"
1579+
1580+
@llvm.global_ctors = appending global [1 x { i32, void ()*, i8* }] [{ i32, void ()*, i8* } { i32 0, void ()* @__init___testproject, i8* null }]
1581+
1582+
define void @__init___testproject() {
1583+
entry:
1584+
ret void
1585+
}
1586+
"#)
1587+
}
1588+
1589+
#[test]
1590+
fn temporary_variable_ref_to_temporary_variable() {
1591+
let res = codegen(
1592+
r"
1593+
FUNCTION foo
1594+
VAR
1595+
ptr : REF_TO STRING := REF(s);
1596+
alias AT s : STRING;
1597+
END_VAR
1598+
VAR_TEMP
1599+
s : STRING;
1600+
reference_to : REFERENCE TO STRING REF= alias;
1601+
END_VAR
1602+
FUNCTION
1603+
",
1604+
);
1605+
1606+
insta::assert_snapshot!(res, @r##"
1607+
; ModuleID = '<internal>'
1608+
source_filename = "<internal>"
1609+
1610+
define void @foo() {
1611+
entry:
1612+
%ptr = alloca [81 x i8]*, align 8
1613+
%alias = alloca [81 x i8]*, align 8
1614+
%s = alloca [81 x i8], align 1
1615+
%reference_to = alloca [81 x i8]*, align 8
1616+
store [81 x i8]* %s, [81 x i8]** %ptr, align 8
1617+
store [81 x i8]* null, [81 x i8]** %alias, align 8
1618+
%0 = bitcast [81 x i8]* %s to i8*
1619+
call void @llvm.memset.p0i8.i64(i8* align 1 %0, i8 0, i64 ptrtoint ([81 x i8]* getelementptr ([81 x i8], [81 x i8]* null, i32 1) to i64), i1 false)
1620+
store [81 x i8]* null, [81 x i8]** %reference_to, align 8
1621+
store [81 x i8]* %s, [81 x i8]** %ptr, align 8
1622+
store [81 x i8]* %s, [81 x i8]** %alias, align 8
1623+
%deref = load [81 x i8]*, [81 x i8]** %alias, align 8
1624+
store [81 x i8]* %deref, [81 x i8]** %reference_to, align 8
1625+
ret void
1626+
}
1627+
1628+
; Function Attrs: argmemonly nofree nounwind willreturn writeonly
1629+
declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i1 immarg) #0
1630+
1631+
attributes #0 = { argmemonly nofree nounwind willreturn writeonly }
1632+
; ModuleID = '__init___testproject'
1633+
source_filename = "__init___testproject"
1634+
1635+
@llvm.global_ctors = appending global [1 x { i32, void ()*, i8* }] [{ i32, void ()*, i8* } { i32 0, void ()* @__init___testproject, i8* null }]
1636+
1637+
define void @__init___testproject() {
1638+
entry:
1639+
ret void
1640+
}
1641+
"##)
1642+
}

src/codegen/tests/statement_codegen_test.rs

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,6 @@ fn reference_to_string_assignment() {
297297
VAR
298298
a : REF_TO STRING;
299299
END_VAR
300-
301300
a^ := 'hello';
302301
END_FUNCTION
303302
"#,
@@ -351,16 +350,15 @@ fn local_alias() {
351350
"#,
352351
);
353352

354-
assert_snapshot!(content, @r###"
353+
assert_snapshot!(content, @r#"
355354
; ModuleID = '<internal>'
356355
source_filename = "<internal>"
357356
358357
define void @main() {
359358
entry:
360359
%foo = alloca i32*, align 8
361360
%bar = alloca i32, align 4
362-
%load_bar = load i32, i32* %bar, align 4
363-
store i32 %load_bar, i32** %foo, align 4
361+
store i32* null, i32** %foo, align 8
364362
store i32 0, i32* %bar, align 4
365363
store i32* %bar, i32** %foo, align 8
366364
ret void
@@ -374,7 +372,7 @@ fn local_alias() {
374372
entry:
375373
ret void
376374
}
377-
"###);
375+
"#);
378376
}
379377

380378
#[test]
@@ -390,16 +388,15 @@ fn local_string_alias() {
390388
"#,
391389
);
392390

393-
assert_snapshot!(content, @r###"
391+
assert_snapshot!(content, @r##"
394392
; ModuleID = '<internal>'
395393
source_filename = "<internal>"
396394
397395
define void @main() {
398396
entry:
399397
%foo = alloca [81 x i8]*, align 8
400398
%bar = alloca [81 x i8], align 1
401-
%load_bar = load [81 x i8], [81 x i8]* %bar, align 1
402-
store [81 x i8] %load_bar, [81 x i8]** %foo, align 1
399+
store [81 x i8]* null, [81 x i8]** %foo, align 8
403400
%0 = bitcast [81 x i8]* %bar to i8*
404401
call void @llvm.memset.p0i8.i64(i8* align 1 %0, i8 0, i64 ptrtoint ([81 x i8]* getelementptr ([81 x i8], [81 x i8]* null, i32 1) to i64), i1 false)
405402
store [81 x i8]* %bar, [81 x i8]** %foo, align 8
@@ -419,7 +416,7 @@ fn local_string_alias() {
419416
entry:
420417
ret void
421418
}
422-
"###);
419+
"##);
423420
}
424421

425422
#[test]

0 commit comments

Comments
 (0)