66#include " Jit/pyjit.h"
77#include " Jit/threaded_compile.h"
88#include " Jit/util.h"
9+ #include " pycore_ceval.h"
910
1011#include < elf.h>
1112#include < fcntl.h>
1920#include < cstdio>
2021#include < cstring>
2122#include < ctime>
23+ #include < iostream>
24+ #include < regex>
25+ #include < sstream>
26+ #include < tuple>
2227
2328#ifdef __x86_64__
2429// Use the cheaper rdtsc by default. If you disable this for some reason, or
@@ -242,6 +247,40 @@ void initFiles() {
242247 inited = true ;
243248}
244249
250+ // Parses a JIT entry and returns a tuple containing the
251+ // code address, code size, and entry name. An example of an entry is:
252+ // 7fa873c00148 360 __CINDER_JIT:__main__:foo2
253+ std::tuple<const void *, unsigned int , const char *> parseJitEntry (
254+ const char * entry) {
255+ std::string_view entry_view = entry;
256+ size_t space_pos_1 = entry_view.find (' ' );
257+
258+ // Extract the hexadecimal code address
259+ const char * code_addr_str = entry_view.substr (0 , space_pos_1).data ();
260+ unsigned long long code_addr_val = 0 ;
261+ std::from_chars (
262+ code_addr_str, code_addr_str + space_pos_1, code_addr_val, 16 );
263+ const void * code_addr = reinterpret_cast <const void *>(code_addr_val);
264+
265+ // Find the second space character
266+ size_t space_pos_2 = entry_view.find (' ' , space_pos_1 + 1 );
267+
268+ // Extract the hexadecimal code size
269+ const char * code_size_str =
270+ entry_view.substr (space_pos_1 + 1 , space_pos_2).data ();
271+ uint32_t code_size;
272+ std::from_chars (
273+ code_size_str,
274+ code_size_str + (space_pos_2 - space_pos_1 - 1 ),
275+ code_size,
276+ 16 );
277+
278+ // Extract the entry name
279+ const char * entry_name = entry_view.substr (space_pos_2 + 1 ).data ();
280+
281+ return std::make_tuple (code_addr, code_size, entry_name);
282+ }
283+
245284// Copy the contents of from_name to to_name. Returns a std::FILE* at the end
246285// of to_name on success, or nullptr on failure.
247286std::FILE* copyFile (const std::string& from_name, const std::string& to_name) {
@@ -277,6 +316,74 @@ std::FILE* copyFile(const std::string& from_name, const std::string& to_name) {
277316 }
278317}
279318
319+ // Copy the contents of the parent perf map file to the child perf map file.
320+ // Returns 1 on success and 0 on failure.
321+ int copyJitFile (const std::string& parent_filename) {
322+ auto parent_file = std::fopen (parent_filename.c_str (), " r" );
323+ if (parent_file == nullptr ) {
324+ JIT_LOG (
325+ " Couldn't open %s for reading (%s)" ,
326+ parent_filename,
327+ string_error (errno));
328+ return 0 ;
329+ }
330+
331+ char buf[1024 ];
332+ while (std::fgets (buf, sizeof (buf), parent_file) != nullptr ) {
333+ buf[strcspn (buf, " \n " )] = ' \0 ' ;
334+ auto jit_entry = parseJitEntry (buf);
335+ try {
336+ PyUnstable_WritePerfMapEntry (
337+ std::get<0 >(jit_entry),
338+ std::get<1 >(jit_entry),
339+ std::get<2 >(jit_entry));
340+ } catch (const std::invalid_argument& e) {
341+ JIT_LOG (" Error: Invalid JIT entry: %s \n " , buf);
342+ }
343+ }
344+ std::fclose (parent_file);
345+ return 1 ;
346+ }
347+
348+ // Copy the JIT entries from the parent perf map file to the child perf map
349+ // file. This is used when perf-trampoline is enabled, as the perf map file
350+ // will also include trampoline entries. We only want to copy the JIT entries.
351+ // Returns 1 on success, and 0 on failure.
352+ int copyJitEntries (const std::string& parent_filename) {
353+ auto parent_file = std::fopen (parent_filename.c_str (), " r" );
354+ if (parent_file == nullptr ) {
355+ JIT_LOG (
356+ " Couldn't open %s for reading (%s)" ,
357+ parent_filename,
358+ string_error (errno));
359+ return 0 ;
360+ }
361+
362+ char buf[1024 ];
363+ while (std::fgets (buf, sizeof (buf), parent_file) != nullptr ) {
364+ if (std::strstr (buf, " __CINDER_" ) != nullptr ) {
365+ buf[strcspn (buf, " \n " )] = ' \0 ' ;
366+ auto jit_entry = parseJitEntry (buf);
367+ try {
368+ PyUnstable_WritePerfMapEntry (
369+ std::get<0 >(jit_entry),
370+ std::get<1 >(jit_entry),
371+ std::get<2 >(jit_entry));
372+ } catch (const std::invalid_argument& e) {
373+ JIT_LOG (" Error: Invalid JIT entry: %s \n " , buf);
374+ }
375+ }
376+ }
377+ std::fclose (parent_file);
378+ return 1 ;
379+ }
380+
381+ bool isPerfTrampolineActive () {
382+ PyThreadState* tstate = PyThreadState_GET ();
383+ return tstate->interp ->eval_frame &&
384+ tstate->interp ->eval_frame != _PyEval_EvalFrameDefault;
385+ }
386+
280387// Copy the perf pid map from the parent process into a new file for this child
281388// process.
282389void copyFileInfo (FileInfo& info) {
@@ -290,33 +397,53 @@ void copyFileInfo(FileInfo& info) {
290397 fmt::format (fmt::runtime (info.filename_format ), getpid ());
291398 info = {};
292399
293- unlink (child_filename.c_str ());
294-
295- if (_PyJIT_IsEnabled ()) {
296- // The JIT is still enabled: copy the file to allow for more compilation in
297- // this process.
298- if (auto new_pid_map = copyFile (parent_filename, child_filename)) {
299- info.filename = child_filename;
300- info.file = new_pid_map;
400+ if (parent_filename.starts_with (" /tmp/perf-" ) &&
401+ parent_filename.ends_with (" .map" ) && isPerfTrampolineActive ()) {
402+ if (!copyJitEntries (parent_filename)) {
403+ JIT_LOG (
404+ " Failed to copy JIT entries from %s to %s" ,
405+ parent_filename,
406+ child_filename);
301407 }
302- } else {
303- // The JIT has been disabled: hard link the file to save disk space. Don't
304- // open it in this process, to avoid messing with the parent's file.
305- if (::link (parent_filename.c_str (), child_filename.c_str ()) != 0 ) {
408+ } else if (
409+ parent_filename.starts_with (" /tmp/perf-" ) &&
410+ parent_filename.ends_with (" .map" ) && _PyJIT_IsEnabled ()) {
411+ // The JIT is still enabled: copy the file to allow for more compilation
412+ // in this process.
413+ if (!copyJitFile (parent_filename)) {
306414 JIT_LOG (
307- " Failed to link %s to %s: %s" ,
308- child_filename,
415+ " Failed to copy perf map file from %s to %s" ,
309416 parent_filename,
310- string_error (errno));
417+ child_filename);
418+ }
419+ } else {
420+ unlink (child_filename.c_str ());
421+ if (_PyJIT_IsEnabled ()) {
422+ // The JIT is still enabled: copy the file to allow for more compilation
423+ // in this process.
424+ if (auto new_pid_map = copyFile (parent_filename, child_filename)) {
425+ info.filename = child_filename;
426+ info.file = new_pid_map;
427+ }
311428 } else {
312- // Poke the file's atime to keep tmpwatch at bay.
313- std::FILE* file = std::fopen (parent_filename.c_str (), " r" );
314- if (file != nullptr ) {
315- std::fclose (file);
429+ // The JIT has been disabled: hard link the file to save disk space. Don't
430+ // open it in this process, to avoid messing with the parent's file.
431+ if (::link (parent_filename.c_str (), child_filename.c_str ()) != 0 ) {
432+ JIT_LOG (
433+ " Failed to link %s to %s: %s" ,
434+ child_filename,
435+ parent_filename,
436+ string_error (errno));
437+ } else {
438+ // Poke the file's atime to keep tmpwatch at bay.
439+ std::FILE* file = std::fopen (parent_filename.c_str (), " r" );
440+ if (file != nullptr ) {
441+ std::fclose (file);
442+ }
316443 }
444+ info.file = nullptr ;
445+ info.filename = " " ;
317446 }
318- info.file = nullptr ;
319- info.filename = " " ;
320447 }
321448}
322449
@@ -353,19 +480,12 @@ void registerFunction(
353480
354481 initFiles ();
355482
356- if (auto file = g_pid_map.file ) {
357- for (auto & section_and_size : code_sections) {
358- void * code = section_and_size.first ;
359- std::size_t size = section_and_size.second ;
360- fmt::print (
361- file,
362- " {:x} {:x} {}:{}\n " ,
363- reinterpret_cast <uintptr_t >(code),
364- size,
365- prefix,
366- name);
367- std::fflush (file);
368- }
483+ for (auto & section_and_size : code_sections) {
484+ void * code = section_and_size.first ;
485+ std::size_t size = section_and_size.second ;
486+ auto jit_entry = prefix + " :" + name;
487+ PyUnstable_WritePerfMapEntry (
488+ static_cast <const void *>(code), size, jit_entry.c_str ());
369489 }
370490
371491 if (auto file = g_jitdump_file.file ) {
0 commit comments