@@ -155,37 +155,73 @@ def parse_all_grift_times(stdout_str):
155155 matches = re .findall (r"time \(sec\):\s*([\d\.]+)" , stdout_str )
156156 return [float (m ) for m in matches ]
157157
158- def run_benchmark (config_dir , source_filename , input_content_multiplied ):
159- # 1. ファイルの場所を「絶対パス(/app/.../fib.grift)」で確実に指定する
158+ def parse_grift_profiler (stdout_str ):
159+ """Griftの --cast-profiler 出力からキャスト関連の数値を抽出する"""
160+ cast_total = None
161+ longest_data = None
162+
163+ for line in stdout_str .split ('\n ' ):
164+ # "total casts:" の行を探す
165+ if "total casts:" in line :
166+ # "total casts:" 以降の文字列を取得して空白で分割
167+ parts = line .split ("total casts:" )[1 ].split ()
168+
169+ # 数値に変換できるもの(N/Aなどを除く)をすべて合計する
170+ total = 0
171+ for p in parts :
172+ if p .isdigit ():
173+ total += int (p )
174+ cast_total = total
175+
176+ # "longest proxy chain:" の行を探す
177+ elif "longest proxy chain:" in line :
178+ parts = line .split ("longest proxy chain:" )[1 ].split ()
179+ if parts and parts [0 ].isdigit ():
180+ longest_data = int (parts [0 ])
181+
182+ return cast_total , longest_data
183+
184+ def run_benchmark (config_dir , source_filename , input_content , enable_profiler = False ):
160185 import os
161186 abs_source_path = os .path .abspath (os .path .join (config_dir , source_filename ))
162- abs_output_path = os .path .abspath (os .path .join (config_dir , "bench" ))
163-
164- # 2. コマンドに絶対パスを渡す
165- compile_cmd = [
166- GRIFT_CMD , "-O" , "3" , "-o" , abs_output_path , abs_source_path
167- ]
187+ output_bin = "bench_prof" if enable_profiler else "bench_perf"
188+ abs_output_path = os .path .abspath (os .path .join (config_dir , output_bin ))
189+
190+ # コンパイルコマンドの構築
191+ compile_cmd = [GRIFT_CMD , "-O" , "3" ]
192+ if enable_profiler :
193+ compile_cmd .append ("--cast-profiler" )
194+ compile_cmd .extend (["-o" , abs_output_path , abs_source_path ])
168195
169196 try :
170- # cwd=config_dir を追加(Griftが中間ファイルなどを正しい場所に置けるようにするため)
171197 subprocess .run (compile_cmd , cwd = config_dir , check = True , capture_output = True , text = True )
172198 except subprocess .CalledProcessError as e :
173199 print (f"\n [Compile Error in { source_filename } ]\n { e .stderr } " )
174- return {"status" : "Compile Failed" , "times" : [], "stderr" : e .stderr }
200+ return {"status" : "Compile Failed" , "times" : [], "cast" : None , "longest" : None , " stderr" : e .stderr }
175201
176- # 3. 実行時も絶対パスでバイナリを叩く
177202 run_cmd = [abs_output_path ]
178203
179204 try :
180- # ここでも cwd=config_dir を追加します
181- proc = subprocess .run (run_cmd , input = input_content_multiplied , cwd = config_dir , check = True , capture_output = True , text = True )
205+ proc = subprocess .run (run_cmd , input = input_content , cwd = config_dir , check = True , capture_output = True , text = True )
182206 stdout_str = proc .stdout .strip ()
183- times = parse_all_grift_times (stdout_str )
184- status = "Success" if len (times ) > 0 else "No Output"
185- return {"status" : status , "times" : times , "stdout" : stdout_str }
207+
208+ result = {"status" : "Success" }
209+ if enable_profiler :
210+ # プロファイルON時はキャスト情報のみパース
211+ cast_data , longest_data = parse_grift_profiler (stdout_str )
212+ result ["cast" ] = cast_data
213+ result ["longest" ] = longest_data
214+ else :
215+ # プロファイルOFF時は実行時間のみパース
216+ times = parse_all_grift_times (stdout_str )
217+ result ["status" ] = "Success" if len (times ) > 0 else "No Output"
218+ result ["times" ] = times
219+
220+ return result
186221 except subprocess .CalledProcessError as e :
187222 print (f"\n [Runtime Error]\n { e .stderr } " )
188- return {"status" : "Run Failed" , "times" : [], "stderr" : e .stderr }
223+ return {"status" : "Run Failed" , "times" : [], "cast" : None , "longest" : None , "stderr" : e .stderr }
224+
189225
190226def get_latest_log_dir ():
191227 log_base = os .path .expanduser (LOG_BASE_DIR )
@@ -198,6 +234,34 @@ def get_latest_log_dir():
198234 raise FileNotFoundError (f"No timestamped directories found in { log_base } " )
199235 return os .path .join (log_base , valid_subdirs [- 1 ])
200236
237+ def generate_c_code (config_dir , source_filename , dest_dir , dest_filename ):
238+ """GriftでCコードを生成し、指定したディレクトリに保存する"""
239+ import os
240+ import shutil
241+
242+ # 修正: --keep-ir <出力ファイル名> <入力ファイル名> の順番に変更
243+ cmd = [GRIFT_CMD , "--backend" , "C" , "--keep-ir" , dest_filename , source_filename ]
244+
245+ try :
246+ # config_dir内でコマンドを実行し、Cコードを生成
247+ subprocess .run (cmd , cwd = config_dir , check = True , capture_output = True , text = True )
248+
249+ # config_dir内に生成されたCコードを、目的のログディレクトリへ移動
250+ src_c = os .path .join (config_dir , dest_filename )
251+ dest_c = os .path .join (dest_dir , dest_filename )
252+
253+ # 指定した名前で正しくファイルが生成されていれば移動
254+ if os .path .exists (src_c ):
255+ shutil .move (src_c , dest_c )
256+ else :
257+ # 万が一、Grift側が自動で別の名前(元のファイル名.cなど)をつけてしまった場合の保険
258+ c_files = [f for f in os .listdir (config_dir ) if f .endswith (".c" )]
259+ if c_files :
260+ shutil .move (os .path .join (config_dir , c_files [0 ]), dest_c )
261+
262+ except subprocess .CalledProcessError as e :
263+ print (f"\n [C Code Generation Error in { source_filename } ]\n { e .stderr } " )
264+
201265def main ():
202266 parser = argparse .ArgumentParser (description = "Run Grift Lattice Benchmark." )
203267 parser .add_argument ("grift_path" , help = "Path to the .grift file" )
@@ -222,6 +286,8 @@ def main():
222286 latest_log_dir = get_latest_log_dir ()
223287 suffix = "_fs" if args .static else ""
224288 output_jsonl_path = os .path .join (latest_log_dir , f"GRIFT_{ filename_no_ext } { suffix } .jsonl" )
289+ c_code_dir = os .path .join (latest_log_dir , "GRIFT" )
290+ os .makedirs (c_code_dir , exist_ok = True )
225291 except Exception as e :
226292 print (f"Error setting up log directory: { e } " )
227293 return
@@ -232,8 +298,8 @@ def main():
232298 if not base_input :
233299 print (f"\n [Warning] Input file { input_path } is empty!" )
234300
235- # args.iter ぴったりではなく、余裕を持たせて +10 回分連結する
236- multiplied_input = (base_input + "\n " ) * (args . iter + 10 )
301+ multiplied_input_perf = ( base_input + " \n " ) * ( args .iter + 10 )
302+ multiplied_input_prof = (base_input + "\n " ) * (1 + 10 )
237303
238304 # AST解析
239305 with open (grift_path , 'r' ) as f :
@@ -288,37 +354,65 @@ def main():
288354 node .is_mutable_type = is_dyn
289355
290356 base_code = "\n " .join ([serialize (c ) for c in ast_root .children ])
291- full_code = base_code + get_grift_driver_code (args .iter )
292357
293358 variant_dir = os .path .join (work_dir , f"config_{ i } " )
294359 os .makedirs (variant_dir , exist_ok = True )
295- with open (os .path .join (variant_dir , filename ), 'w' ) as f :
296- f .write (full_code )
360+ mutant_index = seq_idx + 1
297361
298- print (f"[{ seq_idx + 1 } /{ total_variants } ] { config_id } ... " , end = "" , flush = True )
299- result = run_benchmark (variant_dir , filename , multiplied_input )
300-
301- times = result .get ("times" , [])
362+ print (f"[{ mutant_index } /{ total_variants } ] { config_id } ... " , end = "" , flush = True )
363+
364+ # ---------------------------------------------------------------------
365+ # Phase 1: 実行時間の計測 (プロファイラ OFF, args.iter 回ループ)
366+ # ---------------------------------------------------------------------
367+ filename_perf = f"perf_{ filename } "
368+ full_code_perf = base_code + get_grift_driver_code (args .iter )
369+ with open (os .path .join (variant_dir , filename_perf ), 'w' ) as f :
370+ f .write (full_code_perf )
371+
372+ res_perf = run_benchmark (variant_dir , filename_perf , multiplied_input_perf , enable_profiler = False )
373+ times = res_perf .get ("times" , [])
302374 avg_time = sum (times ) / len (times ) if times else 0.0
303- print (f"{ result ['status' ]} (Avg: { avg_time :.4f} s)" )
375+
376+ # Cコード生成 (純粋な実行用コードを元に生成)
377+ dest_c_filename = f"{ filename_no_ext } { mutant_index } .c"
378+ generate_c_code (variant_dir , filename_perf , c_code_dir , dest_c_filename )
379+
380+ # ---------------------------------------------------------------------
381+ # Phase 2: キャストプロファイルの取得 (プロファイラ ON, 1回のみ実行)
382+ # ---------------------------------------------------------------------
383+ filename_prof = f"prof_{ filename } "
384+ full_code_prof = base_code + get_grift_driver_code (1 )
385+ with open (os .path .join (variant_dir , filename_prof ), 'w' ) as f :
386+ f .write (full_code_prof )
387+
388+ res_prof = run_benchmark (variant_dir , filename_prof , multiplied_input_prof , enable_profiler = True )
389+
390+ print (f"{ res_perf ['status' ]} (Avg: { avg_time :.4f} s)" )
304391
392+ # ---------------------------------------------------------------------
393+ # 結果の統合とJSON出力
394+ # ---------------------------------------------------------------------
305395 output_obj = {
306396 "mode" : "GRIFT" ,
307- "mutant_index" : seq_idx + 1 ,
397+ "mutant_index" : mutant_index ,
308398 "after_mutate" : base_code ,
309399 "after_insertion" : None ,
310400 "after_translation" : None ,
311401 "times_sec" : times ,
312402 "mem" : None ,
313- "cast" : None ,
403+ "cast" : res_prof . get ( "cast" ), # Phase 2 の結果を利用
314404 "inference" : None ,
315- "longest" : None
405+ "longest" : res_prof . get ( "longest" ) # Phase 2 の結果を利用
316406 }
317407
318408 with open (output_jsonl_path , 'a' ) as f :
319409 f .write (json .dumps (output_obj ) + "\n " )
320410
321411 print (f"Done! Saved to: { output_jsonl_path } " )
322412
413+ if os .path .exists (work_dir ):
414+ shutil .rmtree (work_dir )
415+ print (f"Cleaned up temporary directory: { work_dir } " )
416+
323417if __name__ == "__main__" :
324418 main ()
0 commit comments