@@ -7,6 +7,7 @@ use std::{
7
7
8
8
use colored:: Colorize ;
9
9
use miette:: Result ;
10
+ use regex:: Regex ;
10
11
11
12
use crate :: {
12
13
bench:: Benchmark ,
@@ -37,6 +38,9 @@ pub(crate) struct Cfg {
37
38
/// Whether to run comparative tests against GnuCobol's `cobc`.
38
39
pub run_comparative : bool ,
39
40
41
+ /// Whether to run comparative tests against `cobc` -> `clang`.
42
+ pub run_clang : bool ,
43
+
40
44
/// Whether to only build and not execute benchmarks.
41
45
pub build_only : bool ,
42
46
@@ -72,13 +76,18 @@ pub(crate) fn run_single(cfg: &Cfg, benchmark: &Benchmark) -> Result<BenchmarkEx
72
76
. run_comparative
73
77
. then ( || run_cobc ( cfg, benchmark) )
74
78
. transpose ( ) ?;
79
+ let clang_results = cfg
80
+ . run_clang
81
+ . then ( || run_clang ( cfg, benchmark) )
82
+ . transpose ( ) ?;
75
83
76
84
Ok ( BenchmarkExecution {
77
85
benchmark : benchmark. clone ( ) ,
78
86
started_at,
79
87
ended_at : chrono:: offset:: Local :: now ( ) . to_utc ( ) ,
80
88
cobalt_results,
81
89
cobc_results,
90
+ clang_results,
82
91
} )
83
92
}
84
93
@@ -182,6 +191,122 @@ fn run_cobc(cfg: &Cfg, benchmark: &Benchmark) -> Result<BenchmarkResult> {
182
191
} )
183
192
}
184
193
194
+ /// Executes a single benchmark using GnuCobol's `cobc`'s C output followed by
195
+ /// binary generation using `clang`.
196
+ fn run_clang ( cfg : & Cfg , benchmark : & Benchmark ) -> Result < BenchmarkResult > {
197
+ // Calculate output file locations for benchmarking binary, C file.
198
+ // We also need a bootstrapping file for `main` since GnuCobol doesn't create
199
+ // one for us.
200
+ let mut bench_bin_path = cfg. output_dir . clone ( ) ;
201
+ bench_bin_path. push ( BENCH_BIN_NAME ) ;
202
+ let mut bench_c_path = cfg. output_dir . clone ( ) ;
203
+ bench_c_path. push ( format ! ( "{}.c" , & benchmark. name) ) ;
204
+ let mut bootstrap_path = cfg. output_dir . clone ( ) ;
205
+ bootstrap_path. push ( "bootstrap.c" ) ;
206
+
207
+ // Set up commands for transpiling then compiling from C.
208
+ let mut cobc = Command :: new ( "cobc" ) ;
209
+ cobc. args ( [ "-C" , & format ! ( "-O{}" , cfg. cobc_opt_level) , "-free" ] )
210
+ // Required to generate `cob_init()` in the transpiled C.
211
+ . arg ( "-fimplicit-init" )
212
+ . args ( [ "-o" , bench_c_path. to_str ( ) . unwrap ( ) ] )
213
+ . arg ( & benchmark. source_file ) ;
214
+ let mut clang = Command :: new ( "clang" ) ;
215
+ clang
216
+ . args ( [ "-lcob" , & format ! ( "-O{}" , cfg. cobc_opt_level) ] )
217
+ . args ( [ "-o" , bench_bin_path. to_str ( ) . unwrap ( ) ] )
218
+ . arg ( bootstrap_path. to_str ( ) . unwrap ( ) ) ;
219
+
220
+ // We require the program ID of the COBOL file to generate the bootstrapper.
221
+ // Attempt to grep for that from the source.
222
+ let bench_prog_func = fetch_program_func ( benchmark) ?;
223
+
224
+ // Generate the bootstrapping file.
225
+ let bootstrapper = format ! (
226
+ "
227
+ #include \" {}.c\"
228
+ int main(void) {{
229
+ {}();
230
+ }}
231
+ " ,
232
+ & benchmark. name, bench_prog_func
233
+ ) ;
234
+ std:: fs:: write ( bootstrap_path. clone ( ) , bootstrapper)
235
+ . map_err ( |e| miette:: diagnostic!( "Failed to write bootstrap file for `clang`: {e}" ) ) ?;
236
+
237
+ let before = Instant :: now ( ) ;
238
+ for _ in 0 ..100 {
239
+ // First, transpile to C.
240
+ let out = cobc
241
+ . output ( )
242
+ . map_err ( |e| miette:: diagnostic!( "Failed to execute `cobc`: {e}" ) ) ?;
243
+ if !out. status . success ( ) {
244
+ miette:: bail!(
245
+ "Failed benchmark for '{}' with `cobc` compiler error: {}" ,
246
+ benchmark. source_file,
247
+ String :: from_utf8_lossy( & out. stderr)
248
+ ) ;
249
+ }
250
+
251
+ // Finally, perform compilation & linkage with `clang`.
252
+ let out = clang
253
+ . output ( )
254
+ . map_err ( |e| miette:: diagnostic!( "Failed to execute `clang`: {e}" ) ) ?;
255
+ if !out. status . success ( ) {
256
+ miette:: bail!(
257
+ "Failed benchmark for '{}' with `clang` compiler error: {}" ,
258
+ benchmark. source_file,
259
+ String :: from_utf8_lossy( & out. stderr)
260
+ ) ;
261
+ }
262
+ }
263
+ let elapsed = before. elapsed ( ) ;
264
+ println ! (
265
+ "clang(compile): Total time {:.2?}, average/run of {:.6?}." ,
266
+ elapsed,
267
+ elapsed / 100
268
+ ) ;
269
+
270
+ // Run the target program.
271
+ let ( execute_time_total, execute_time_avg) = if !cfg. build_only {
272
+ let ( x, y) = run_bench_bin ( cfg, benchmark) ?;
273
+ ( Some ( x) , Some ( y) )
274
+ } else {
275
+ ( None , None )
276
+ } ;
277
+
278
+ Ok ( BenchmarkResult {
279
+ compile_time_total : elapsed,
280
+ compile_time_avg : elapsed / 100 ,
281
+ execute_time_total,
282
+ execute_time_avg,
283
+ } )
284
+ }
285
+
286
+ /// Attempts to fetch the output C function name of the given benchmark COBOL file.
287
+ /// If not found, throws an error.
288
+ fn fetch_program_func ( benchmark : & Benchmark ) -> Result < String > {
289
+ // First, read in source of benchmark.
290
+ let source = std:: fs:: read_to_string ( & benchmark. source_file ) . map_err ( |e| {
291
+ miette:: diagnostic!(
292
+ "Failed to read COBOL source for benchmark '{}': {e}" ,
293
+ benchmark. name
294
+ )
295
+ } ) ?;
296
+
297
+ // Search for a pattern matching "PROGRAM-ID ...".
298
+ let prog_id_pat = Regex :: new ( r"PROGRAM-ID\. [A-Z0-9a-z\-]+" ) . unwrap ( ) ;
299
+ let prog_id_str = prog_id_pat. find ( & source) . ok_or ( miette:: diagnostic!(
300
+ "Could not find program ID in sources for benchmark '{}'." ,
301
+ & benchmark. name
302
+ ) ) ?;
303
+
304
+ // Extract the program ID, format into final function name.
305
+ let mut prog_id = prog_id_str. as_str ( ) [ "PROGRAM-ID " . len ( ) ..] . to_string ( ) ;
306
+ prog_id = prog_id. replace ( "-" , "__" ) ;
307
+ Ok ( prog_id)
308
+ }
309
+
185
310
/// Executes a single generated benchmarking binary.
186
311
/// Returns the total execution time and average execution time per iteration.
187
312
fn run_bench_bin ( cfg : & Cfg , benchmark : & Benchmark ) -> Result < ( Duration , Duration ) > {
0 commit comments