(* * Copyright (c) 1997-1999 Massachusetts Institute of Technology * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * *) (* $Id: genfft.ml,v 1.75 1999/05/26 15:44:15 fftw Exp $ *) (* This file contains the entry point for the genfft program: it parses the command-line parameters and calls the rest of the program as needed to generate the requested code. *) open Util open To_c open Expr let optimize expr = let _ = info "simplifiying..." in let simple = Exprdag.simplify_to_alist expr in let _ = info "scheduling..." in let scheduled = Schedule.schedule simple in let _ = info "annotating..." in let annotated = Asched.annotate scheduled in let _ = info "unparsing..." in annotated let make_expr name = Var (Variable.make_named name) let iarray = "input" let oarray = "output" let istride = "istride" let ostride = "ostride" let twiddle_order = "twiddle_order" type codelet_type = TWIDDLE | NO_TWIDDLE | REAL2HC | HC2HC | HC2REAL | REALEVEN | REALODD | REALEVEN2 | REALODD2 | REALEVEN_TWIDDLE | REALODD_TWIDDLE let rec list_to_c = function [] -> "" | [a] -> (string_of_int a) | a :: b -> (string_of_int a) ^ ", " ^ (list_to_c b) let codelet_description n dir ty f = let Fcn (_, name, _, _) = f and (ctype, itype) = match ty with TWIDDLE -> "FFTW_TWIDDLE", 0 | NO_TWIDDLE -> "FFTW_NOTW", 1 | REAL2HC -> "FFTW_REAL2HC", 2 | HC2HC -> "FFTW_HC2HC", 3 | HC2REAL -> "FFTW_HC2REAL", 4 | REALEVEN -> "FFTW_REALEVEN", 5 | REALODD -> "FFTW_REALODD", 6 | REALEVEN2 -> "FFTW_REALEVEN2", 7 | REALODD2 -> "FFTW_REALODD2", 8 | REALEVEN_TWIDDLE -> "FFTW_REALEVEN_TWIDDLE", 9 | REALODD_TWIDDLE -> "FFTW_REALODD_TWIDDLE", 10 and (cdir, idir) = match dir with Fft.FORWARD -> "FFTW_FORWARD", 0 | Fft.BACKWARD -> "FFTW_BACKWARD", 1 and (_, num_twiddle, tw_o) = Twiddle.twiddle_policy () in let (declare_order, order, nt) = match ty with TWIDDLE -> ("static const int " ^ twiddle_order ^ "[] = {" ^ (list_to_c (tw_o n)) ^ "};\n"), twiddle_order, num_twiddle n | NO_TWIDDLE -> "", "(const int *) 0", 0 | REAL2HC -> "", "(const int *) 0", 0 | HC2HC -> ("static const int " ^ twiddle_order ^ "[] = {" ^ (list_to_c (tw_o n)) ^ "};\n"), twiddle_order, num_twiddle n | HC2REAL -> "", "(const int *) 0", 0 | REALEVEN -> "", "(const int *) 0", 0 | REALODD -> "", "(const int *) 0", 0 | REALEVEN2 -> "", "(const int *) 0", 0 | REALODD2 -> "", "(const int *) 0", 0 | REALEVEN_TWIDDLE -> ("static const int " ^ twiddle_order ^ "[] = {" ^ (list_to_c (tw_o n)) ^ "};\n"), twiddle_order, num_twiddle n | REALODD_TWIDDLE -> ("static const int " ^ twiddle_order ^ "[] = {" ^ (list_to_c (tw_o n)) ^ "};\n"), twiddle_order, num_twiddle n (* this should be replaced by CRC/MD5 of the codelet *) and signature = 11 * (2 * n + idir) + itype in "\n\n" ^ declare_order ^ "fftw_codelet_desc " ^ name ^ "_desc = {\n" ^ "\"" ^ name ^ "\",\n" ^ "(void (*)()) " ^ name ^ ",\n" ^ (string_of_int n) ^ ",\n" ^ cdir ^ ",\n" ^ ctype ^ ",\n" ^ (string_of_int signature) ^ ",\n" ^ (string_of_int nt) ^ ",\n" ^ order ^ ",\n" ^ "};\n" let fftw_no_twiddle_gen n dir = let _ = info "generating..." in let asch = optimize (Fft.no_twiddle_gen_expr n Symmetry.no_sym dir) and ns = string_of_int n and (name, sign) = match dir with Fft.FORWARD -> "fftw_no_twiddle_", (-1) | Fft.BACKWARD -> "fftwi_no_twiddle_", 1 and unparse_var = Variable.make_unparser (iarray, Some istride) (oarray, Some ostride) ("BUG", None) in let tree = Fcn ("void", name ^ ns, [Decl ("const fftw_complex *", iarray); Decl ("fftw_complex *", oarray); Decl ("int", istride); Decl ("int", ostride)], Asch asch) in let desc = codelet_description n dir NO_TWIDDLE tree in ((make_c_unparser unparse_var) tree) ^ desc let athena_no_twiddle_gen n dir = let _ = info "generating..." in let asch = optimize (Fft.no_twiddle_gen_expr n Symmetry.no_sym dir) and ns = string_of_int n and (name, sign) = match dir with Fft.FORWARD -> "athfft_", (-1) | Fft.BACKWARD -> "athffti_", 1 and unparse_var = Variable.make_unparser (iarray, None) (iarray, None) ("BUG", None) in let tree = Fcn ("void", name ^ ns, [Decl ("fftw_complex *", iarray)], Asch asch) and prologue = "#include \"athfft.h\"\n\n" ^ (if dir == Fft.FORWARD then "int ath_permutation_" ^ ns ^ "(int i) { return i; }\n\n\n" else "") in prologue ^ ((make_c_unparser unparse_var) tree) let no_twiddle_gen n dir = if (!Magic.athenafft) then athena_no_twiddle_gen n dir else fftw_no_twiddle_gen n dir let real_oarray = "real_output" let imag_oarray = "imag_output" let real_ostride = "real_ostride" let imag_ostride = "imag_ostride" let real2hc_gen n = let _ = info "generating..." in let dir = Fft.FORWARD in let asch = optimize (Fft.no_twiddle_gen_expr n Symmetry.real_sym dir) and ns = string_of_int n and (name, sign) = "fftw_real2hc_", (-1) and unparse_var = Variable.make_real2hc_unparser (iarray, Some istride) (real_oarray, Some real_ostride) (imag_oarray, Some imag_ostride) in let tree = Fcn ("void", name ^ ns, [Decl ("const fftw_real *", iarray); Decl ("fftw_real *", real_oarray); Decl ("fftw_real *", imag_oarray); Decl ("int", istride); Decl ("int", real_ostride); Decl ("int", imag_ostride)], Asch asch) in let desc = codelet_description n dir REAL2HC tree in ((make_c_unparser unparse_var) tree) ^ desc let real_iarray = "real_input" let imag_iarray = "imag_input" let real_istride = "real_istride" let imag_istride = "imag_istride" let hc2real_gen n = let _ = info "generating..." in let dir = Fft.BACKWARD in let asch = optimize (Fft.no_twiddle_gen_expr n Symmetry.hermitian_sym dir) and ns = string_of_int n and (name, sign) = "fftw_hc2real_", 1 and unparse_var = Variable.make_hc2real_unparser (real_iarray, Some real_istride) (imag_iarray, Some imag_istride) (oarray, Some ostride) in let tree = Fcn ("void", name ^ ns, [Decl ("const fftw_real *", real_iarray); Decl ("const fftw_real *", imag_iarray); Decl ("fftw_real *", oarray); Decl ("int", real_istride); Decl ("int", imag_istride); Decl ("int", ostride)], Asch asch) in let desc = codelet_description n dir HC2REAL tree in ((make_c_unparser unparse_var) tree) ^ desc let realeven_gen n = let _ = info "generating..." in let dir = Fft.FORWARD in let asch = optimize (Fft.no_twiddle_gen_expr n Symmetry.realeven_sym dir) and ns = string_of_int n and (name, sign) = "fftw_realeven_", (-1) and unparse_var = Variable.make_realeven_unparser (iarray, Some istride) (oarray, Some ostride) in let tree = Fcn ("void", name ^ ns, [Decl ("const fftw_real *", iarray); Decl ("fftw_real *", oarray); Decl ("int", istride); Decl ("int", ostride)], Asch asch) in let desc = codelet_description n dir REALEVEN tree in ((make_c_unparser unparse_var) tree) ^ desc let realodd_gen n = let _ = info "generating..." in let dir = Fft.FORWARD in let asch = optimize (Fft.no_twiddle_gen_expr n Symmetry.realodd_sym dir) and ns = string_of_int n and (name, sign) = "fftw_realodd_", (-1) and unparse_var = Variable.make_realodd_unparser (iarray, Some istride) (oarray, Some ostride) in let tree = Fcn ("void", name ^ ns, [Decl ("const fftw_real *", iarray); Decl ("fftw_real *", oarray); Decl ("int", istride); Decl ("int", ostride)], Asch asch) in let desc = codelet_description n dir REALODD tree in ((make_c_unparser unparse_var) tree) ^ desc let realeven2_gen n = let _ = info "generating..." in let dir = Fft.FORWARD in let asch = optimize (Fft.no_twiddle_gen_expr (2 * n) Symmetry.realeven2_input_sym dir) and ns = string_of_int n and (name, sign) = "fftw_realeven2_", (-1) and unparse_var = Variable.make_realeven_unparser (iarray, Some istride) (oarray, Some ostride) in let tree = Fcn ("void", name ^ ns, [Decl ("const fftw_real *", iarray); Decl ("fftw_real *", oarray); Decl ("int", istride); Decl ("int", ostride)], Asch asch) in let desc = codelet_description n dir REALEVEN2 tree in ((make_c_unparser unparse_var) tree) ^ desc let realodd2_gen n = let _ = info "generating..." in let dir = Fft.FORWARD in let asch = optimize (Fft.no_twiddle_gen_expr (2 * n) Symmetry.realodd2_input_sym dir) and ns = string_of_int n and (name, sign) = "fftw_realodd2_", (-1) and unparse_var = Variable.make_realodd2_unparser (iarray, Some istride) (oarray, Some ostride) in let tree = Fcn ("void", name ^ ns, [Decl ("const fftw_real *", iarray); Decl ("fftw_real *", oarray); Decl ("int", istride); Decl ("int", ostride)], Asch asch) in let desc = codelet_description n dir REALODD2 tree in ((make_c_unparser unparse_var) tree) ^ desc let ioarray = "inout" let iostride = "iostride" let twarray = "W" let fftw_twiddle_gen n dir = let _ = info "generating..." in let asch = optimize (Fft.twiddle_dit_gen_expr n Symmetry.no_sym Symmetry.no_sym dir) and ns = string_of_int n and m = "m" and dist = "dist" and a = "A" and i = "i" in let me = make_expr m and diste = make_expr dist and ae = make_expr a and ie = make_expr i and ioarraye = make_expr ioarray and twarraye = make_expr twarray and (name, sign) = match dir with Fft.FORWARD -> "fftw_twiddle_", (-1) | Fft.BACKWARD -> "fftwi_twiddle_", 1 and unparse_var = Variable.make_unparser (ioarray, Some iostride) (ioarray, Some iostride) (twarray, None) and (_, num_twiddles, _) = Twiddle.twiddle_policy () in let body = Block ( [Decl ("int", i); Decl ("fftw_complex *", ioarray)], [Stmt_assign (ioarraye, ae); For (Expr_assign (ie, me), Binop (" > ", ie, Integer 0), Comma (Expr_assign (ie, Plus [ie; Uminus (Integer 1)]), (Comma (Expr_assign (ioarraye, Plus [ioarraye; diste]), (Expr_assign (twarraye, Plus [twarraye; Integer (num_twiddles n)]))))), Asch asch)]) in let fcnname = name ^ ns in let tree = Fcn ("void", fcnname, [Decl ("fftw_complex *", a); Decl ("const fftw_complex *", twarray); Decl ("int", iostride); Decl ("int", m); Decl ("int", dist)], body) in let desc = codelet_description n dir TWIDDLE tree in ((make_c_unparser unparse_var) tree) ^ desc let athena_twiddle_gen n dir = let _ = info "generating..." and (name, sign, generator) = match dir with Fft.FORWARD -> "athfft_twiddle_", (-1), Fft.twiddle_dif_gen_expr | Fft.BACKWARD -> "athffti_twiddle_", 1, Fft.twiddle_dit_gen_expr in let asch = optimize (generator n Symmetry.no_sym Symmetry.no_sym dir) and ns = string_of_int n and a = "A" and i = "i" in let ae = make_expr a and ie = make_expr i and ioarraye = make_expr ioarray and twarraye = make_expr twarray and unparse_var = Variable.make_unparser (ioarray, Some iostride) (ioarray, Some iostride) (twarray, None) and (_, num_twiddles, _) = Twiddle.twiddle_policy () in let body = Block ( [Decl ("int", i); Decl ("fftw_complex *", ioarray)], [Stmt_assign (ioarraye, ae); For (Expr_assign (ie, make_expr iostride), Binop (" > ", ie, Integer 0), Comma (Expr_assign (ie, Plus [ie; Uminus (Integer 1)]), (Comma (Expr_assign (ioarraye, Plus [ioarraye; Integer 1]), (Expr_assign (twarraye, Plus [twarraye; Integer (num_twiddles n)]))))), Asch asch)]) in let fcnname = name ^ ns in let tree = Fcn ("void", fcnname, [Decl ("fftw_complex *", a); Decl ("const fftw_complex *", twarray); Decl ("int", iostride)], body) and prologue = "#include \"athfft.h\"\n\n" in prologue ^ ((make_c_unparser unparse_var) tree) let twiddle_gen n dir = if (!Magic.athenafft) then athena_twiddle_gen n dir else fftw_twiddle_gen n dir let arrayX = "X" let arrayY = "Y" let hc2hc_gen n dir = let _ = info "generating..." in let (zeroth_elements, middle_elements, final_elements, name, sign, make_variable_unparser) = match dir with Fft.FORWARD -> ( optimize (Fft.no_twiddle_gen_expr n Symmetry.real_sym dir), optimize (Fft.twiddle_dit_gen_expr n Symmetry.no_sym Symmetry.middle_hc2hc_forward_sym dir), optimize (Fft.no_twiddle_gen_expr (2 * n) Symmetry.final_hc2hc_forward_sym dir), "fftw_hc2hc_forward_", (-1), Variable.make_hc2hc_forward_unparser) | Fft.BACKWARD -> ( optimize (Fft.no_twiddle_gen_expr n Symmetry.hermitian_sym dir), optimize (Fft.twiddle_dif_gen_expr n Symmetry.middle_hc2hc_backward_sym Symmetry.no_sym dir), optimize (Fft.no_twiddle_gen_expr (2 * n) Symmetry.final_hc2hc_backward_sym dir), "fftw_hc2hc_backward_", 1, Variable.make_hc2hc_backward_unparser) and ns = string_of_int n and m = "m" and dist = "dist" and a = "A" and i = "i" in let me = make_expr m and diste = make_expr dist and iostridee = make_expr iostride and ae = make_expr a and ie = make_expr i and arrayXe = make_expr arrayX and arrayYe = make_expr arrayY and twarraye = make_expr twarray and unparse_var = make_variable_unparser n (arrayX, Some iostride) (arrayY, Some iostride) (twarray, None) and (_, num_twiddles, _) = Twiddle.twiddle_policy () in let body = Block ( [ Decl ("int", i); Decl ("fftw_real *", arrayX); Decl ("fftw_real *", arrayY)], [ Stmt_assign (arrayXe, ae); Stmt_assign (arrayYe, Plus [ae; Times (Integer n, iostridee)]); Asch zeroth_elements; Stmt_assign (arrayXe, Plus [arrayXe; diste]); Stmt_assign (arrayYe, Plus [arrayYe; Uminus diste]); For (Expr_assign (ie, Integer 2), Binop (" < ", ie, me), Comma (Expr_assign (ie, Plus [ie; Integer 2]), (Comma (Comma (Expr_assign (arrayXe, Plus [arrayXe; diste]), Expr_assign (arrayYe, Plus [arrayYe; Uminus diste])), (Expr_assign (twarraye, Plus [twarraye; Integer (num_twiddles n)]))))), Asch middle_elements); If (Binop (" == ", ie, me), Asch final_elements)]) in let fcnname = name ^ ns in let tree = Fcn ("void", fcnname, [Decl ("fftw_real *", a); Decl ("const fftw_complex *", twarray); Decl ("int", iostride); Decl ("int", m); Decl ("int", dist)], body) in let desc = codelet_description n dir HC2HC tree in ((make_c_unparser unparse_var) tree) ^ desc let usage = "Usage: genfft [-notwiddle | -notwiddleinv | -twiddle | -twiddleinv| -real2hc | -hc2hc-forward | -hc2hc-backward ] " type mode = | GEN_NOTHING | GEN_TWIDDLE of int | GEN_NOTWID of int | GEN_TWIDDLEI of int | GEN_NOTWIDI of int | GEN_REAL2HC of int | GEN_HC2REAL of int | GEN_HC2HC_FORWARD of int | GEN_HC2HC_BACKWARD of int | GEN_REAL_EVEN of int | GEN_REAL_ODD of int | GEN_REAL_EVEN2 of int | GEN_REAL_ODD2 of int | GEN_REAL_EVEN_TWIDDLE of int | GEN_REAL_ODD_TWIDDLE of int let mode = ref GEN_NOTHING let undocumented = " Undocumented voodoo parameter" let main () = Arg.parse [ "-notwiddle", Arg.Int(fun i -> mode := GEN_NOTWID i), " : Generate a no twiddle codelet of size "; "-notwiddleinv", Arg.Int(fun i -> mode := GEN_NOTWIDI i), " : Generate a no twiddle inverse codelet of size "; "-twiddle", Arg.Int(fun i -> mode := GEN_TWIDDLE i), " : Generate a twiddle codelet of size "; "-twiddleinv", Arg.Int(fun i -> mode := GEN_TWIDDLEI i), " : Generate a twiddle inverse codelet of size "; "-real2hc", Arg.Int(fun i -> mode := GEN_REAL2HC i), " : Generate a real to halfcomplex codelet of size "; "-hc2hc-forward", Arg.Int(fun i -> mode := GEN_HC2HC_FORWARD i), " : Generate a forward halfcomplex to halfcomplex codelet of size "; "-hc2hc-backward", Arg.Int(fun i -> mode := GEN_HC2HC_BACKWARD i), " : Generate a backward halfcomplex to halfcomplex codelet of size "; "-hc2real", Arg.Int(fun i -> mode := GEN_HC2REAL i), " : Generate a halfcomplex to real codelet of size "; "-realeven", Arg.Int(fun i -> mode := GEN_REAL_EVEN i), " : Generate a real even codelet of size "; "-realodd", Arg.Int(fun i -> mode := GEN_REAL_ODD i), " : Generate a real odd codelet of size "; "-realeven2", Arg.Int(fun i -> mode := GEN_REAL_EVEN2 i), " : Generate a real even-2 codelet of size "; "-realodd2", Arg.Int(fun i -> mode := GEN_REAL_ODD2 i), " : Generate a real odd-2 codelet of size "; "-realeventwiddle", Arg.Int(fun i -> mode := GEN_REAL_EVEN_TWIDDLE i), " : Generate a real even twiddle codelet of size "; "-realoddtwiddle", Arg.Int(fun i -> mode := GEN_REAL_ODD_TWIDDLE i), " : Generate a real odd twiddle codelet of size "; "-verbose", Arg.Unit(fun () -> Magic.verbose := true), " Enable verbose logging messages to stderr"; "-inline-konstants", Arg.Unit(fun () -> Magic.inline_konstants := true), " Inline floating point constants"; "-no-inline-konstants", Arg.Unit(fun () -> Magic.inline_konstants := false), " Do not inline floating point constants"; "-rader-min", Arg.Int(fun i -> Magic.rader_min := i), " : Use Rader's algorithm for prime sizes >= "; "-magic-alternate-convolution", Arg.Int(fun i -> Magic.alternate_convolution := i), undocumented; "-magic-athenafft", Arg.Unit(fun () -> Magic.athenafft := true), undocumented; "-magic-window", Arg.Int(fun i -> Magic.window := i), undocumented; "-magic-variables", Arg.Int(fun i -> Magic.number_of_variables := i), undocumented; "-magic-loopo", Arg.Unit(fun () -> Magic.loopo := true), undocumented; "-magic-loopi", Arg.Unit(fun () -> Magic.loopo := false), undocumented; "-magic-times-3-3", Arg.Unit(fun () -> Magic.times_3_3 := true), undocumented; "-magic-times-4-2", Arg.Unit(fun () -> Magic.times_3_3 := false), undocumented; "-magic-inline-single", Arg.Unit(fun () -> Magic.inline_single := true), undocumented; "-magic-no-inline-single", Arg.Unit(fun () -> Magic.inline_single := false), undocumented; "-magic-inline-loads", Arg.Unit(fun () -> Magic.inline_loads := true), undocumented; "-magic-no-inline-loads", Arg.Unit(fun () -> Magic.inline_loads := false), undocumented; "-magic-enable-fma", Arg.Unit(fun () -> Magic.enable_fma := true), undocumented; "-magic-disable-fma", Arg.Unit(fun () -> Magic.enable_fma := false), undocumented; "-magic-enable-fma-expansion", Arg.Unit(fun () -> Magic.enable_fma_expansion := true), undocumented; "-magic-disable-fma-expansion", Arg.Unit(fun () -> Magic.enable_fma_expansion := false), undocumented; "-magic-collect-common-twiddle", Arg.Unit(fun () -> Magic.collect_common_twiddle := true), undocumented; "-magic-no-collect-common-twiddle", Arg.Unit(fun () -> Magic.collect_common_twiddle := false), undocumented; "-magic-collect-common-inputs", Arg.Unit(fun () -> Magic.collect_common_inputs := true), undocumented; "-magic-no-collect-common-inputs", Arg.Unit(fun () -> Magic.collect_common_inputs := false), undocumented; "-magic-alignment-check", Arg.Unit(fun () -> Magic.alignment_check := true), undocumented; "-magic-use-wsquare", Arg.Unit(fun () -> Magic.use_wsquare := true), undocumented; "-magic-no-wsquare", Arg.Unit(fun () -> Magic.use_wsquare := false), undocumented; "-magic-twiddle-load-all", Arg.Unit(fun () -> Magic.twiddle_policy := Magic.TWIDDLE_LOAD_ALL), undocumented; "-magic-twiddle-iter", Arg.Unit(fun () -> Magic.twiddle_policy := Magic.TWIDDLE_ITER), undocumented; "-magic-twiddle-load-odd", Arg.Unit(fun () -> Magic.twiddle_policy := Magic.TWIDDLE_LOAD_ODD), undocumented; "-magic-twiddle-square1", Arg.Unit(fun () -> Magic.twiddle_policy := Magic.TWIDDLE_SQUARE1), undocumented; "-magic-twiddle-square2", Arg.Unit(fun () -> Magic.twiddle_policy := Magic.TWIDDLE_SQUARE2), undocumented; "-magic-twiddle-square3", Arg.Unit(fun () -> Magic.twiddle_policy := Magic.TWIDDLE_SQUARE3), undocumented; ] (fun _ -> failwith "too many arguments") usage; let out = match !mode with | GEN_TWIDDLE i -> (twiddle_gen i Fft.FORWARD) | GEN_TWIDDLEI i -> (twiddle_gen i Fft.BACKWARD) | GEN_NOTWID i -> (no_twiddle_gen i Fft.FORWARD) | GEN_NOTWIDI i -> (no_twiddle_gen i Fft.BACKWARD) | GEN_REAL2HC i -> (real2hc_gen i) | GEN_HC2HC_FORWARD i -> (hc2hc_gen i Fft.FORWARD) | GEN_HC2REAL i -> (hc2real_gen i) | GEN_HC2HC_BACKWARD i -> (hc2hc_gen i Fft.BACKWARD) | GEN_REAL_EVEN i -> (realeven_gen i) | GEN_REAL_ODD i -> (realodd_gen i) | GEN_REAL_EVEN2 i -> (realeven2_gen i) | GEN_REAL_ODD2 i -> (realodd2_gen i) | _ -> failwith "one of -notwiddle, -notwiddleinv, -twiddle, -twiddleinv, -real2hc, -hc2real, -hc2hc-forward, or -hc2hc-forward must be specified" in begin print_string out; exit 0; end let _ = main()