Skip to content

Commit 104c483

Browse files
lucic71nunoplopes
andauthored
[unsafe] Fix non-const global initializers and [unsafe, refcount] order of global initialization (#366)
Rust requires initializers of global variables to be const. C++ does not require that. To fix this tension, wrap global variables in `std::cell::LazyCell` so that initialization does not depend on the constness of the initializer. Another problem that this PR fixes is the moment when global variables are initialized in both refcount and unsafe. `thread_local` in refcount and the new `LazyCell` in unsafe initialize the variable on the first usage. This is wrong if the constructors of the global variables have side effects that must be visible in the program, for example: ```cpp #include <cassert> static int total = 0; struct S { S(int x) { total += x; } }; // there is no explicit usage of a and b inside the program other than the initialization // however the constructor has side effects that are visible on the total variable // so the initialization has to happen before entering main, not lazily as thread_local // and LazyCell normally do static S a(1); static S b(10); int main() { assert(total == 11); return 0; } ``` To fix this, I added a new function that runs before main and forces the initialization of global variables: `__cpp2rust_init_globals()`. --------- Co-authored-by: Nuno Lopes <nuno.lopes@tecnico.ulisboa.pt>
1 parent f60bd33 commit 104c483

938 files changed

Lines changed: 3271 additions & 391 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎cpp2rust/converter/converter.cpp‎

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ namespace cpp2rust {
2626
std::unordered_map<std::string, std::string> Converter::inner_structs_;
2727
std::unordered_set<std::string> Converter::decl_ids_;
2828
std::unordered_set<std::string> Converter::globals_;
29+
std::vector<std::string> Converter::global_inits_;
2930
std::unordered_set<std::string> Converter::abstract_structs_;
3031
Converter::RecordIndex Converter::record_decls_;
3132
std::map<std::string, Converter::MethodsOnPtr> Converter::methods_on_ptr_;
@@ -59,8 +60,7 @@ use std::rc::Rc;
5960
)");
6061
}
6162

62-
std::string Converter::EmitMethodsOnPtr() {
63-
std::string out;
63+
void Converter::EmitMethodsOnPtr(std::string &out) {
6464
for (const auto &[name, methods] : methods_on_ptr_) {
6565
out += methods.trait_header;
6666
out += " {\n";
@@ -71,18 +71,30 @@ std::string Converter::EmitMethodsOnPtr() {
7171
out += methods.impl_body;
7272
out += "}\n";
7373
}
74-
return out;
7574
}
7675

77-
std::string Converter::EmitOpaqueRecords() {
78-
std::string out;
76+
std::string Converter::ForceGlobalInit(const clang::VarDecl *decl) {
77+
return std::format("std::cell::LazyCell::force(&*&raw const {});",
78+
GetNamedDeclAsString(decl));
79+
}
80+
81+
void Converter::EmitGlobalInits(Model model, std::string &out) {
82+
out += model == Model::kUnsafe ? "pub unsafe fn __cpp2rust_init_globals() {\n"
83+
: "pub fn __cpp2rust_init_globals() {\n";
84+
for (const auto &line : global_inits_) {
85+
out += line;
86+
out += '\n';
87+
}
88+
out += "}\n";
89+
}
90+
91+
void Converter::EmitOpaqueRecords(std::string &out) {
7992
record_decls_.ForEachUndefined([&](const std::string &name) {
8093
out += "#[derive(Clone, Copy, Default, ByteRepr)]";
8194
out += "pub struct ";
8295
out += name;
8396
out += ";\n";
8497
});
85-
return out;
8698
}
8799

88100
bool Converter::VisitRecoveryExpr(clang::RecoveryExpr *expr) {
@@ -515,6 +527,7 @@ bool Converter::ConvertVarDeclSkipInit(clang::VarDecl *decl) {
515527
StrCat(AccessSpecifierAsString(decl->getAccess()), keyword::kStatic,
516528
keyword_mut_);
517529
ENSURE(decl_ids_.insert(GetID(decl)).second);
530+
global_inits_.push_back(ForceGlobalInit(decl));
518531
} else if (decl->isStaticLocal()) {
519532
StrCat(keyword::kStatic, keyword_mut_);
520533
} else if (decl->isLocalVarDecl()) {
@@ -535,7 +548,10 @@ bool Converter::ConvertVarDeclSkipInit(clang::VarDecl *decl) {
535548
if (is_parm_with_default_value) {
536549
StrCat("Option<");
537550
}
538-
Convert(qual_type);
551+
{
552+
PushLazyType lazy(*this, IsGlobalVar(decl) && LazyStaticInit());
553+
Convert(qual_type);
554+
}
539555
if (is_parm_with_default_value) {
540556
StrCat('>');
541557
}
@@ -600,8 +616,9 @@ void Converter::ConvertGlobalVarDecl(clang::VarDecl *decl) {
600616
PushConstInitializer static_init(*this, decl->isFileVarDecl() ||
601617
decl->isStaticLocal());
602618
StrCat(token::kAssign);
603-
StrCat(keyword_unsafe_);
604619
{
620+
PushLazyInit lazy(*this, LazyStaticInit());
621+
StrCat(keyword_unsafe_);
605622
PushBrace push(*this);
606623
ConvertVarDeclInitializer(decl);
607624
}
@@ -2925,7 +2942,11 @@ std::string Converter::ConvertDeclRefExpr(clang::DeclRefExpr *expr) {
29252942
}
29262943

29272944
if (IsGlobalVar(expr)) {
2928-
return GetNamedDeclAsString(expr->getDecl());
2945+
if (LazyStaticInit()) {
2946+
return std::format("(*std::cell::LazyCell::force_mut(&mut *&raw mut {}))",
2947+
GetNamedDeclAsString(decl));
2948+
}
2949+
return GetNamedDeclAsString(decl);
29292950
}
29302951

29312952
return GetNamedDeclAsString(decl);
@@ -4279,14 +4300,15 @@ pub fn main() {{
42794300
let mut argv: Vec<*mut libc::c_char> = args.iter().map(|arg| arg.as_ptr() as *mut libc::c_char).collect();
42804301
argv.push(::std::ptr::null_mut());
42814302
unsafe {{
4303+
__cpp2rust_init_globals();
42824304
::std::process::exit(main_0((argv.len() - 1) as i32, argv.as_mut_ptr()) as i32)
42834305
}}
42844306
}})",
42854307
main_function_name));
42864308
} else {
4287-
StrCat(std::format(
4288-
"pub fn main() {{ unsafe {{ std::process::exit({}() as i32); }} }}",
4289-
main_function_name));
4309+
StrCat(std::format("pub fn main() {{ unsafe {{ __cpp2rust_init_globals(); "
4310+
"std::process::exit({}() as i32); }} }}",
4311+
main_function_name));
42904312
}
42914313
}
42924314

‎cpp2rust/converter/converter.h‎

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include <vector>
1919

2020
#include "converter/converter_lib.h"
21+
#include "converter/factory.h"
2122
#include "converter/lex.h"
2223
#include "converter/translation_rule.h"
2324
#include "logging.h"
@@ -51,9 +52,10 @@ class Converter : public clang::RecursiveASTVisitor<Converter> {
5152

5253
virtual void EmitFilePreamble();
5354

54-
static std::string EmitOpaqueRecords();
55+
static void EmitOpaqueRecords(std::string &out);
56+
static void EmitGlobalInits(Model model, std::string &out);
5557

56-
static std::string EmitMethodsOnPtr();
58+
static void EmitMethodsOnPtr(std::string &out);
5759

5860
virtual bool VisitBuiltinType(clang::BuiltinType *type);
5961

@@ -98,6 +100,8 @@ class Converter : public clang::RecursiveASTVisitor<Converter> {
98100
virtual bool VisitFunctionTemplateDecl(clang::FunctionTemplateDecl *decl);
99101

100102
virtual bool VisitVarDecl(clang::VarDecl *decl);
103+
virtual bool LazyStaticInit() const { return true; }
104+
virtual std::string ForceGlobalInit(const clang::VarDecl *decl);
101105

102106
void ConvertVarDecl(clang::VarDecl *decl);
103107

@@ -481,7 +485,7 @@ class Converter : public clang::RecursiveASTVisitor<Converter> {
481485
std::string str() && { return std::move(partial_code); }
482486
};
483487

484-
template <char kOpen, char kClose> class PushDelim {
488+
template <auto kOpen, auto kClose> class PushDelim {
485489
Converter &c;
486490
bool enabled;
487491

@@ -506,6 +510,8 @@ class Converter : public clang::RecursiveASTVisitor<Converter> {
506510
PushDelim<token::kOpenCurlyBracket, token::kCloseCurlyBracket>;
507511
using PushParen = PushDelim<token::kOpenParen, token::kCloseParen>;
508512
using PushBracket = PushDelim<token::kOpenBracket, token::kCloseBracket>;
513+
using PushLazyType = PushDelim<token::kLazyCellType, token::kGt>;
514+
using PushLazyInit = PushDelim<token::kLazyCellNew, token::kCloseParen>;
509515

510516
template <typename T>
511517
inline std::string
@@ -1038,6 +1044,7 @@ class Converter : public clang::RecursiveASTVisitor<Converter> {
10381044
std::vector<ExprKind> curr_expr_kind_;
10391045
static std::unordered_map<std::string, std::string> inner_structs_;
10401046
static std::unordered_set<std::string> globals_;
1047+
static std::vector<std::string> global_inits_;
10411048
clang::Sema *sema_ = nullptr;
10421049
};
10431050
} // namespace cpp2rust

‎cpp2rust/converter/lex.h‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ inline constexpr char kDiv = '/';
3030
inline constexpr char kLt = '<';
3131
inline constexpr char kGt = '>';
3232
inline constexpr char kNewLine = '\n';
33+
inline constexpr const char kLazyCellType[] = "std::cell::LazyCell<";
34+
inline constexpr const char kLazyCellNew[] = "std::cell::LazyCell::new(||";
3335
} // namespace token
3436

3537
namespace keyword {

‎cpp2rust/converter/models/converter_refcount.cpp‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2448,6 +2448,10 @@ void ConverterRefCount::ConvertPointerSubscript(
24482448
}
24492449
}
24502450

2451+
std::string ConverterRefCount::ForceGlobalInit(const clang::VarDecl *decl) {
2452+
return std::format("let _ = {}.with(|_| ());", GetNamedDeclAsString(decl));
2453+
}
2454+
24512455
void ConverterRefCount::ConvertFunctionMain(
24522456
const clang::FunctionDecl *decl,
24532457
const std::string_view main_function_name) {
@@ -2461,12 +2465,14 @@ pub fn main() {{
24612465
argv.iter().map(|x| {{ x.borrow_mut().push(0); x.as_pointer() }}).collect(),
24622466
));
24632467
(*argv.borrow_mut()).push(Ptr::null());
2468+
__cpp2rust_init_globals();
24642469
::std::process::exit({}(::std::env::args().len() as i32,
24652470
argv.as_pointer()));
24662471
}})",
24672472
main_function_name));
24682473
} else {
2469-
StrCat(std::format("pub fn main() {{ std::process::exit({}()); }}",
2474+
StrCat(std::format("pub fn main() {{ __cpp2rust_init_globals(); "
2475+
"std::process::exit({}()); }}",
24702476
main_function_name));
24712477
}
24722478
}

‎cpp2rust/converter/models/converter_refcount.h‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ class ConverterRefCount final : public Converter {
7979
void EmitFunctionPreamble(clang::FunctionDecl *decl) override;
8080

8181
bool VisitVarDecl(clang::VarDecl *decl) override;
82+
bool LazyStaticInit() const override { return false; }
83+
std::string ForceGlobalInit(const clang::VarDecl *decl) override;
8284

8385
void ConvertGlobalVarDecl(clang::VarDecl *decl) override;
8486

‎cpp2rust/cpp2rust_lib.cpp‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,9 @@ std::string TranspileSrc(std::string_view cc_code, Model model,
3030
rules_dir),
3131
cc_code, tool_args, std::filesystem::path(filename).filename().string(),
3232
filename.ends_with(".c") ? CLANG_C_COMPILER : CLANG_CXX_COMPILER);
33-
rs_code += Converter::EmitOpaqueRecords();
34-
rs_code += Converter::EmitMethodsOnPtr();
33+
Converter::EmitOpaqueRecords(rs_code);
34+
Converter::EmitMethodsOnPtr(rs_code);
35+
Converter::EmitGlobalInits(model, rs_code);
3536
return rs_code;
3637
}
3738

@@ -71,8 +72,9 @@ std::string TranspileDir(std::string_view build_dir, Model model,
7172
std::string rs_code;
7273
FrontendActionFactory factory(rs_code, model, rules_dir);
7374
Tool.run(&factory);
74-
rs_code += Converter::EmitOpaqueRecords();
75-
rs_code += Converter::EmitMethodsOnPtr();
75+
Converter::EmitOpaqueRecords(rs_code);
76+
Converter::EmitMethodsOnPtr(rs_code);
77+
Converter::EmitGlobalInits(model, rs_code);
7678
return rs_code;
7779
}
7880
} // namespace cpp2rust

‎tests/multi-file/cross_tu_anon_enum_collision/out/refcount/cross_tu_anon_enum_collision.rs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ pub fn a_value_1() -> i32 {
1414
return (*x.borrow());
1515
}
1616
pub fn main() {
17+
__cpp2rust_init_globals();
1718
std::process::exit(main_0());
1819
}
1920
fn main_0() -> i32 {
@@ -28,3 +29,4 @@ pub fn b_value_2() -> i32 {
2829
(*x.borrow_mut()) |= (anon_3_BETA as i32);
2930
return (*x.borrow());
3031
}
32+
pub fn __cpp2rust_init_globals() {}

‎tests/multi-file/cross_tu_anon_enum_collision/out/unsafe/cross_tu_anon_enum_collision.rs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ pub unsafe fn a_value_1() -> i32 {
1515
}
1616
pub fn main() {
1717
unsafe {
18+
__cpp2rust_init_globals();
1819
std::process::exit(main_0() as i32);
1920
}
2021
}
@@ -30,3 +31,4 @@ pub unsafe fn b_value_2() -> i32 {
3031
x |= (anon_3_BETA as i32);
3132
return x;
3233
}
34+
pub unsafe fn __cpp2rust_init_globals() {}

‎tests/multi-file/cross_tu_tag_collision/out/refcount/cross_tu_tag_collision.rs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ pub fn a_value_0() -> i32 {
3636
return (*(*w.borrow()).id.borrow());
3737
}
3838
pub fn main() {
39+
__cpp2rust_init_globals();
3940
std::process::exit(main_0());
4041
}
4142
fn main_0() -> i32 {
@@ -51,3 +52,4 @@ pub fn b_value_1() -> i32 {
5152
let w: Value<widget_enum> = Rc::new(RefCell::new(widget_enum_WIDGET_C));
5253
return ((*w.borrow()) as i32);
5354
}
55+
pub fn __cpp2rust_init_globals() {}

‎tests/multi-file/cross_tu_tag_collision/out/unsafe/cross_tu_tag_collision.rs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ pub unsafe fn a_value_0() -> i32 {
1818
}
1919
pub fn main() {
2020
unsafe {
21+
__cpp2rust_init_globals();
2122
std::process::exit(main_0() as i32);
2223
}
2324
}
@@ -34,3 +35,4 @@ pub unsafe fn b_value_1() -> i32 {
3435
let mut w: widget_enum = widget_enum_WIDGET_C;
3536
return (w as i32);
3637
}
38+
pub unsafe fn __cpp2rust_init_globals() {}

0 commit comments

Comments
 (0)