वास्तव में PHI निर्देश क्या करता है और LLVM में इसका उपयोग कैसे करें


87

एलएलवीएम में काफी अजीब स्पष्टीकरण के साथ फी निर्देश है:

'Phi' निर्देश का उपयोग एसएसए ग्राफ में function नोड को लागू करने के लिए किया जाता है जो फ़ंक्शन का प्रतिनिधित्व करता है।

आमतौर पर इसका उपयोग ब्रांचिंग को लागू करने के लिए किया जाता है। अगर मुझे सही तरीके से समझ में आता है, तो निर्भरता विश्लेषण को संभव बनाना आवश्यक है और कुछ मामलों में यह अनावश्यक लोडिंग से बचने में मदद कर सकता है। हालाँकि यह समझना अभी भी कठिन है कि यह वास्तव में क्या करता है।

बहुरूपदर्शक उदाहरण इसे ifमामले के लिए काफी अच्छी तरह से समझाता है । लेकिन यह है कि स्पष्ट कैसे की तरह तार्किक आपरेशन लागू करने के लिए नहीं है &&और ||। यदि मैं ऑनलाइन llvm संकलक के लिए निम्नलिखित टाइप करता हूं :

void main1(bool r, bool y) {
    bool l = y || r;
}

पिछली कई लाइनें मुझे पूरी तरह से भ्रमित करती हैं:

; <label>:10                                      ; preds = %7, %0
%11 = phi i1 [ true, %0 ], [ %9, %7 ]
%12 = zext i1 %11 to i8

ऐसा लगता है कि फी नोड एक परिणाम उत्पन्न करता है जिसका उपयोग किया जा सकता है। और मुझे यह आभास हो रहा था कि फी नोड सिर्फ उन रास्तों को परिभाषित करता है जो आने वाले मूल्यों से हैं।

क्या कोई समझा सकता है कि फी नोड क्या है, और ||इसके साथ कैसे लागू किया जाए?


1
phiनोड compilers में समस्या का एक समाधान "स्टैटिक एकल काम" के रूप में परिवर्तित करने के लिए आईआर है। समाधान को बेहतर ढंग से समझने के लिए मैं बेहतर समस्या को समझने का सुझाव दूंगा। तो मैं आपको " क्यों phiनोड है " एक कर दूंगा ।
व्रज पंड्या

जवाबों:


76

फी नोड एक निर्देश है जिसका उपयोग वर्तमान ब्लॉक के पूर्ववर्ती के आधार पर एक मूल्य का चयन करने के लिए किया जाता है ( पूर्ण पदानुक्रम को देखने के लिए यहां देखें - यह एक मूल्य के रूप में भी उपयोग किया जाता है, जो कि उन वर्गों में से एक है जो इसे विरासत में मिला है)।

LLVM कोड की SSA (स्टैटिक सिंगल असाइनमेंट) शैली की संरचना के कारण फी नोड्स आवश्यक हैं - उदाहरण के लिए, निम्नलिखित C ++ फ़ंक्शन

void m(bool r, bool y){
    bool l = y || r ;
}

निम्नलिखित IR में अनुवादित किया जाता है: (के माध्यम से बनाया clang -c -emit-llvm file.c -o out.bc- और फिर के माध्यम से देखा llvm-dis)

define void @_Z1mbb(i1 zeroext %r, i1 zeroext %y) nounwind {
entry:
  %r.addr = alloca i8, align 1
  %y.addr = alloca i8, align 1
  %l = alloca i8, align 1
  %frombool = zext i1 %r to i8
  store i8 %frombool, i8* %r.addr, align 1
  %frombool1 = zext i1 %y to i8
  store i8 %frombool1, i8* %y.addr, align 1
  %0 = load i8* %y.addr, align 1
  %tobool = trunc i8 %0 to i1
  br i1 %tobool, label %lor.end, label %lor.rhs

lor.rhs:                                          ; preds = %entry
  %1 = load i8* %r.addr, align 1
  %tobool2 = trunc i8 %1 to i1
  br label %lor.end

lor.end:                                          ; preds = %lor.rhs, %entry
  %2 = phi i1 [ true, %entry ], [ %tobool2, %lor.rhs ]
  %frombool3 = zext i1 %2 to i8
  store i8 %frombool3, i8* %l, align 1
  ret void
}

तो यहाँ क्या होता है? सी ++ कोड के विपरीत, जहां चर bool lया तो 0 या 1 हो सकता है, एलएलवीएम आईआर में इसे एक बार परिभाषित किया जाना है । इसलिए हम जाँचते हैं कि क्या %toboolयह सच है, और फिर कूदो lor.endया lor.rhs

lor.endअंत में हमारे पास मूल्य है || ऑपरेटर। यदि हम प्रवेश खंड से पहुंचे - तो यह सही है। अन्यथा, यह %tobool2- के मूल्य के बराबर है और यही हमें निम्नलिखित आईआर लाइन से मिलता है:

%2 = phi i1 [ true, %entry ], [ %tobool2, %lor.rhs ]

6
टीएल; डीआर; नोड एक तिर्यक अभिव्यक्ति है। कोई यह तर्क दे सकता है कि इसमें यह शर्त शामिल नहीं है, लेकिन वास्तव में, अंतिम कोड में परिवर्तित होने पर, आप यह निर्धारित नहीं कर सकते कि कौन सा तर्क लाइव है, इसलिए condition शर्त भी होनी चाहिए।
हाय-एंजेल

31

आपको फी का उपयोग करने की आवश्यकता नहीं है। बस अस्थायी चर का गुच्छा बनाएँ। LLVM ऑप्टिमाइज़ेशन पास अस्थायी चरों को दूर करने का ध्यान रखेगा और इसके लिए ph नोड का उपयोग स्वतः करेगा।

उदाहरण के लिए, यदि आप ऐसा करना चाहते हैं:

x = 4;
if (something) x = x + 2;
print(x);

आप इसके लिए फी नोड का उपयोग कर सकते हैं (छद्मकोड में):

  1. 4 से X1 असाइन करें
  2. अगर (कुछ) 4 को शाखा
  3. 2 को जोड़कर एक्स 1 से एक्स 2 की गणना करें
  4. X3 phi को X1 और x2 से असाइन करें
  5. x3 के साथ कॉल प्रिंट

लेकिन आप फी नोड के बिना कर सकते हैं (छद्मकोड में):

  1. स्टैक पर स्थानीय चर आवंटित करें जिसे एक्स कहा जाता है
  2. अस्थायी X1 मूल्य 4 में लोड
  3. स्टोर एक्स 1 से एक्स
  4. अगर (कुछ) 8 को शाखा
  5. लोड x को अस्थायी x2
  6. 4 को अस्थायी x3 के साथ x2 जोड़ें
  7. स्टोर एक्स 3 से एक्स
  8. लोड x को अस्थायी x4
  9. x4 के साथ कॉल प्रिंट

Llvm के साथ ऑप्टिमाइज़ेशन पास चलाने से यह दूसरा कोड पहले कोड के अनुकूल हो जाएगा।


4
मैंने जो पढ़ा है उससे ऐसा लगता है जैसे यहाँ ध्यान रखने के लिए कुछ प्रतिबंध हैं। mem2reg प्रश्न में ऑप्टिमाइज़ेशन पास है, और इसकी कुछ सीमाएँ हैं जो कि कालिडोस्कोप उदाहरण में इंगित की गई हैं । ऐसा लगता है, हालांकि, समस्या को संभालने का पसंदीदा तरीका है और इसका उपयोग क्लैंग द्वारा किया जाता है।
मैथ्यू सैंडर्स
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.