@@ -56,20 +56,11 @@ fn main() -> Result<()> {
56
56
return Err ( "Duplicate filenames" . into ( ) ) ;
57
57
}
58
58
59
- let cpp_filename = souffle_generate ( & ruleset) ?;
59
+ let cpp_filename = souffle_generate ( & ruleset, stem ) ?;
60
60
cpp_filenames. push ( cpp_filename) ;
61
61
}
62
62
63
- for stem in known_stems {
64
- // HACK: Souffle adds datalog programs to the registry in the initializer of a global
65
- // variable (whose name begins with `__factory_Sf`). Since that global variable is never used
66
- // by the Rust program, it is occasionally removed by the linker, its initializer is never
67
- // run (!!!), and the program is never registered.
68
- //
69
- // `-u` marks the symbol as undefined, so that it will not be optimized out.
70
- let prog_symbol = format ! ( "__factory_Sf_{}_instance" , stem) ;
71
- println ! ( "cargo:rustc-link-arg=-u{}" , prog_symbol) ;
72
- }
63
+ odr_use_generate ( & known_stems) ?;
73
64
74
65
let mut cc = cxx_build:: bridge ( CXX_BRIDGE ) ;
75
66
@@ -88,8 +79,15 @@ fn main() -> Result<()> {
88
79
Ok ( ( ) )
89
80
}
90
81
82
+ fn odr_use_func_name ( stem : & str ) -> String {
83
+ format ! ( "odr_use_{}_global" , stem)
84
+ }
85
+
91
86
/// Uses Souffle to generate a C++ file for evaluating the given datalog program.
92
- fn souffle_generate ( datalog_filename : & Path ) -> Result < PathBuf > {
87
+ ///
88
+ /// Returns the filename for the generated C code, as well as the name of a generated function that
89
+ /// will trigger the global initializers in that translation unit.
90
+ fn souffle_generate ( datalog_filename : & Path , stem : & str ) -> Result < PathBuf > {
93
91
let mut cpp_filename = PathBuf :: from ( std:: env:: var ( "OUT_DIR" ) . unwrap ( ) ) ;
94
92
cpp_filename. push ( datalog_filename. with_extension ( "cpp" ) . file_name ( ) . unwrap ( ) ) ;
95
93
@@ -105,5 +103,43 @@ fn souffle_generate(datalog_filename: &Path) -> Result<PathBuf> {
105
103
return Err ( "Invalid datalog" . into ( ) ) ;
106
104
}
107
105
106
+ let mut generated_cpp = fs:: OpenOptions :: new ( ) . append ( true ) . open ( & cpp_filename) ?;
107
+ writeln ! (
108
+ generated_cpp,
109
+ r#"
110
+ extern "C"
111
+ void {}() {{}}"# ,
112
+ odr_use_func_name( stem)
113
+ ) ?;
114
+
108
115
Ok ( cpp_filename)
109
116
}
117
+
118
+ // HACK: Souffle adds datalog programs to the registry in the initializer of a global
119
+ // variable (whose name begins with `__factory_Sf`). That global variable is eligible for
120
+ // deferred initialization, so we need to force its initializer to run before we do a lookup in
121
+ // the registry (which happens in a different translation unit from the generated code).
122
+ //
123
+ // We accomplish this by defining a single, no-op function in each generated C++ file, and calling
124
+ // it on the Rust side before doing any meaningful work. By the C++ standard, this forces global
125
+ // initializers for anything in the that translation unit to run, since calling the function is an
126
+ // ODR-use of something in the same translation unit. We also define a helper function,
127
+ // `odr_use_all`, which calls the no-op function in every known module.
128
+ fn odr_use_generate ( known_stems : & HashSet < String > ) -> Result < ( ) > {
129
+ let mut odr_use_filename = PathBuf :: from ( std:: env:: var ( "OUT_DIR" ) . unwrap ( ) ) ;
130
+ odr_use_filename. push ( "odr_use.rs" ) ;
131
+
132
+ let mut odr_use = BufWriter :: new ( fs:: File :: create ( odr_use_filename) ?) ;
133
+ writeln ! ( odr_use, r#"extern "C" {{"# ) ?;
134
+ for stem in known_stems {
135
+ writeln ! ( odr_use, "fn {}();" , odr_use_func_name( stem) ) ?;
136
+ }
137
+ writeln ! ( odr_use, r#"}}"# ) ?;
138
+
139
+ writeln ! ( odr_use, "fn odr_use_all() {{" ) ?;
140
+ for stem in known_stems {
141
+ writeln ! ( odr_use, "unsafe {{ {}(); }}" , odr_use_func_name( stem) ) ?;
142
+ }
143
+ writeln ! ( odr_use, "}}" ) ?;
144
+ Ok ( ( ) )
145
+ }
0 commit comments