@@ -56,20 +56,11 @@ fn main() -> Result<()> {
5656 return Err ( "Duplicate filenames" . into ( ) ) ;
5757 }
5858
59- let cpp_filename = souffle_generate ( & ruleset) ?;
59+ let cpp_filename = souffle_generate ( & ruleset, stem ) ?;
6060 cpp_filenames. push ( cpp_filename) ;
6161 }
6262
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) ?;
7364
7465 let mut cc = cxx_build:: bridge ( CXX_BRIDGE ) ;
7566
@@ -88,8 +79,15 @@ fn main() -> Result<()> {
8879 Ok ( ( ) )
8980}
9081
82+ fn odr_use_func_name ( stem : & str ) -> String {
83+ format ! ( "odr_use_{}_global" , stem)
84+ }
85+
9186/// 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 > {
9391 let mut cpp_filename = PathBuf :: from ( std:: env:: var ( "OUT_DIR" ) . unwrap ( ) ) ;
9492 cpp_filename. push ( datalog_filename. with_extension ( "cpp" ) . file_name ( ) . unwrap ( ) ) ;
9593
@@ -105,5 +103,43 @@ fn souffle_generate(datalog_filename: &Path) -> Result<PathBuf> {
105103 return Err ( "Invalid datalog" . into ( ) ) ;
106104 }
107105
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+
108115 Ok ( cpp_filename)
109116}
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