LLVM ከ Go እይታ

ኮምፕሌተርን ማዘጋጀት በጣም ከባድ ስራ ነው. ግን እንደ እድል ሆኖ ፣ እንደ ኤልኤልቪኤም ያሉ ፕሮጄክቶች ሲፈጠሩ ፣ የዚህ ችግር መፍትሄ በጣም ቀላል ነው ፣ ይህም አንድ ፕሮግራመር እንኳን እንኳን ለ C አፈፃፀም ቅርብ የሆነ አዲስ ቋንቋ ለመፍጠር ያስችላል። ስርዓቱ በትንሽ ሰነዶች የታጠቁ እጅግ በጣም ብዙ በሆነ ኮድ ይወከላል ። ይህንን ጉድለቱን ለማረም የጽሁፉ አዘጋጅ ዛሬ የምናተምነው የትርጉም ስራ በ Go ውስጥ የተፃፉ የኮድ ምሳሌዎችን ለማሳየት እና እንዴት ወደ መጀመሪያው እንደተተረጎመ ለማሳየት ነው። ኤስኤስኤ ይሂዱ, እና ከዚያም በ LLVM IR ውስጥ ማጠናከሪያውን በመጠቀም ቲኒጎጎ. የ Go SSA እና LLVM IR ኮድ እዚህ ከተሰጡት ማብራሪያዎች ጋር ተያያዥነት የሌላቸውን ነገሮች ለማስወገድ በትንሹ ተስተካክሏል፣ ይህም ማብራሪያውን የበለጠ ለመረዳት ያስችላል።

LLVM ከ Go እይታ

የመጀመሪያው ምሳሌ

እዚህ የምመለከተው የመጀመሪያው ተግባር ቁጥሮችን ለመጨመር ቀላል ዘዴ ነው.

func myAdd(a, b int) int{
    return a + b
}

ይህ ተግባር በጣም ቀላል ነው, እና ምናልባት, ምንም ቀላል ሊሆን አይችልም. ወደሚከተለው Go SSA ኮድ ይተረጎማል፡-

func myAdd(a int, b int) int:
entry:
    t0 = a + b                                                    int
    return t0

በዚህ እይታ, የውሂብ አይነት ፍንጮች በቀኝ በኩል ይቀመጣሉ እና በአብዛኛዎቹ ሁኔታዎች ችላ ሊባሉ ይችላሉ.

ይህ ትንሽ ምሳሌ ቀደም ሲል የኤስኤስኤውን አንድ ገጽታ ምንነት እንዲመለከቱ ያስችልዎታል። ይኸውም፣ ኮድን ወደ SSA ቅጽ ሲቀይሩ፣ እያንዳንዱ አገላለጽ ወደ ተዘጋጀባቸው በጣም የመጀመሪያ ደረጃ ክፍሎች ይከፋፈላል። በእኛ ሁኔታ, ትዕዛዙ return a + b, በእውነቱ, ሁለት ስራዎችን ይወክላል-ሁለት ቁጥሮች መጨመር እና ውጤቱን መመለስ.

በተጨማሪም, እዚህ የፕሮግራሙን መሰረታዊ ብሎኮች ማየት ይችላሉ, በዚህ ኮድ ውስጥ አንድ እገዳ ብቻ ነው - የመግቢያ እገዳ. ከዚህ በታች ስለ ብሎኮች የበለጠ እንነጋገራለን ።

የ Go SSA ኮድ በቀላሉ ወደ LLVM IR ይቀየራል፡-

define i64 @myAdd(i64 %a, i64 %b) {
entry:
  %0 = add i64 %a, %b
  ret i64 %0
}

እርስዎ ሊገነዘቡት የሚችሉት ምንም እንኳን የተለያዩ የአገባብ አወቃቀሮች እዚህ ጥቅም ላይ ቢውሉም, የተግባሩ መዋቅር በመሠረቱ አልተለወጠም. የ LLVM IR ኮድ ከ Go SSA ኮድ ትንሽ ጠንከር ያለ ነው, ከ C ጋር ተመሳሳይ ነው እዚህ, በተግባሩ መግለጫ ውስጥ, በመጀመሪያ የሚመለሰው የውሂብ አይነት መግለጫ አለ, የክርክር አይነት ከክርክሩ ስም በፊት ይጠቁማል. በተጨማሪም, IR ን መተንተንን ለማቃለል, የአለምአቀፍ አካላት ስሞች ከምልክቱ በፊት ናቸው @, እና ከአካባቢ ስሞች በፊት ምልክት አለ % (አንድ ተግባር እንደ ዓለም አቀፋዊ አካል ይቆጠራል).

በዚህ ኮድ ላይ አንድ ልብ ሊባል የሚገባው ነገር የGo አይነት የውክልና ውሳኔ ነው። intእንደ 32-ቢት ወይም 64-ቢት እሴት ሊወከል የሚችለው፣ እንደ ማቀናበሪያው እና እንደ የተጠናቀረው ዒላማ፣ LLVM የ IR ኮድ ሲያመነጭ ተቀባይነት ይኖረዋል። ይህ LLVM IR ኮድ ብዙ ሰዎች እንደሚያስቡት ከመድረክ ነጻ ካልሆነ ከብዙ ምክንያቶች አንዱ ነው። ለአንድ መድረክ የተፈጠረ እንዲህ ዓይነቱ ኮድ በቀላሉ ለሌላ መድረክ ሊወሰድ እና ሊጠናቀር አይችልም (ይህን ችግር ለመፍታት ተስማሚ ካልሆኑ በስተቀር) በከፍተኛ ጥንቃቄ).

ሌላው ትኩረት የሚስብ ነጥብ ደግሞ ዓይነቱ ነው i64 የተፈረመ ኢንቲጀር አይደለም፡ የቁጥሩን ምልክት ከመወከል አንፃር ገለልተኛ ነው። እንደ መመሪያው፣ ሁለቱንም የተፈረሙ እና ያልተፈረሙ ቁጥሮችን ሊወክል ይችላል። የመደመር ክዋኔው ውክልና ከሆነ, ይህ ምንም አይደለም, ስለዚህ ከተፈረሙ ወይም ካልተፈረሙ ቁጥሮች ጋር ለመስራት ምንም ልዩነት የለም. እዚህ ላይ በC ቋንቋ፣ የተፈረመ የኢንቲጀር ተለዋዋጭ ሞልቶ ወደ ማይገለጽ ባህሪ እንደሚመራ ማስተዋል እፈልጋለሁ፣ ስለዚህ Clang frontend በኦፕሬሽኑ ላይ ባንዲራ ይጨምራል። nsw (ምንም የተፈረመ ጥቅል የለም)፣ ይህም ለኤልኤልቪኤም የሚናገረው መደመር በጭራሽ እንደማይፈስ መገመት ይችላል።

ይህ ለአንዳንድ ማመቻቸት አስፈላጊ ሊሆን ይችላል. ለምሳሌ, ሁለት እሴቶችን መጨመር i16 በ 32 ቢት መድረክ ላይ (ከ 32 ቢት መዝገቦች ጋር) ከተጨማሪ በኋላ በክልል ውስጥ ለመቆየት የምልክት ማስፋፊያ ስራ ያስፈልገዋል i16. በዚህ ምክንያት የማሽን መመዝገቢያ መጠኖችን መሰረት በማድረግ የኢንቲጀር ስራዎችን ለማከናወን ብዙ ጊዜ የበለጠ ውጤታማ ነው.

ከዚህ IR ኮድ ቀጥሎ የሚሆነው ነገር አሁን ለእኛ ልዩ ትኩረት የሚስብ አይደለም። ኮዱ ተመቻችቷል (ነገር ግን እንደ እኛ ያለ ቀላል ምሳሌ ምንም ነገር አልተመቻቸም) እና ከዚያ ወደ ማሽን ኮድ ይቀየራል።

ሁለተኛ ምሳሌ

የሚቀጥለው ምሳሌ ትንሽ የበለጠ የተወሳሰበ ይሆናል። ይኸውም፣ እየተነጋገርን ያለነው የአንድን ኢንቲጀር ቁራጭ የሚያጠቃልል ተግባር ነው።

func sum(numbers []int) int {
    n := 0
    for i := 0; i < len(numbers); i++ {
        n += numbers[i]
    }
    return n
}

ይህ ኮድ ወደሚከተለው የGo SSA ኮድ ይቀየራል።

func sum(numbers []int) int:
entry:
    jump for.loop
for.loop:
    t0 = phi [entry: 0:int, for.body: t6] #n                       int
    t1 = phi [entry: 0:int, for.body: t7] #i                       int
    t2 = len(numbers)                                              int
    t3 = t1 < t2                                                  bool
    if t3 goto for.body else for.done
for.body:
    t4 = &numbers[t1]                                             *int
    t5 = *t4                                                       int
    t6 = t0 + t5                                                   int
    t7 = t1 + 1:int                                                int
    jump for.loop
for.done:
    return t0

እዚህ በኤስኤስኤ ቅጽ ውስጥ ኮድን ለመወከል የተለመዱ ተጨማሪ ግንባታዎችን ማየት ይችላሉ። ምናልባት የዚህ ኮድ በጣም ግልፅ ባህሪ ምንም የተዋቀሩ የፍሰት መቆጣጠሪያ ትዕዛዞች አለመኖሩ ነው. የስሌቶችን ፍሰት ለመቆጣጠር, ሁኔታዊ እና ቅድመ ሁኔታ የሌላቸው መዝለሎች ብቻ ናቸው, እና ይህን ትዕዛዝ እንደ ፍሰቱን ለመቆጣጠር እንደ ትዕዛዝ ከተመለከትን, የመመለሻ ትዕዛዝ.

በእውነቱ ፣ እዚህ ላይ ፕሮግራሙ የተጠማዘዘ ማሰሪያዎችን በመጠቀም ወደ ብሎኮች አለመከፋፈሉን ትኩረት መስጠት ይችላሉ (እንደ የቋንቋዎች C ቤተሰብ)። የመሰብሰቢያ ቋንቋዎችን የሚያስታውስ በመለያዎች የተከፋፈለ እና በመሠረታዊ ብሎኮች መልክ ቀርቧል። በኤስኤስኤ፣ መሰረታዊ ብሎኮች በመሰየሚያ የሚጀምሩ እና በመሰረታዊ የማጠናቀቂያ መመሪያዎች የሚጨርሱ ተከታታይ የኮድ ቅደም ተከተሎች ተደርገው ይወሰዳሉ፣ ለምሳሌ - return и jump.

የዚህ ኮድ ሌላ አስደሳች ዝርዝር በመመሪያው ይወከላል phi. መመሪያዎቹ በጣም ያልተለመዱ ናቸው እና ለመረዳት የተወሰነ ጊዜ ሊወስድ ይችላል። አስታውስ, ያንን SSA ለስታቲክ ነጠላ ምደባ አጭር ነው። ይህ በአቀነባባሪዎች ጥቅም ላይ የሚውለው ኮድ መካከለኛ ውክልና ሲሆን እያንዳንዱ ተለዋዋጭ እሴት አንድ ጊዜ ብቻ ይመደባል. ይህ እንደ ተግባራችን ያሉ ቀላል ተግባራትን ለመግለጽ በጣም ጥሩ ነው myAddከላይ የሚታየው ነገር ግን በዚህ ክፍል ውስጥ የተብራራውን ተግባር ላሉ ውስብስብ ተግባራት ተስማሚ አይደለም sum. በተለይም በ loop አፈፃፀም ወቅት ተለዋዋጮች ይለወጣሉ i и n.

ኤስኤስኤ መመሪያ የሚባለውን አንድ ጊዜ ተለዋዋጭ እሴቶችን በመመደብ ላይ ያለውን ገደብ አልፏል phi (ስሙ ከግሪክ ፊደል የተወሰደ ነው)። እውነታው ግን የኤስኤስኤ ውክልና ኮድ እንደ ሲ ላሉ ቋንቋዎች እንዲፈጠር አንዳንድ ዘዴዎችን መጠቀም አለብዎት። ይህንን መመሪያ የመጥራት ውጤት የተለዋዋጭ የአሁኑ ዋጋ ነው (i ወይም n), እና የመሠረታዊ እገዳዎች ዝርዝር እንደ መመዘኛዎቹ ጥቅም ላይ ይውላል. ለምሳሌ ይህንን መመሪያ አስቡበት፡-

t0 = phi [entry: 0:int, for.body: t6] #n

ትርጉሙ እንደሚከተለው ነው-የቀድሞው መሰረታዊ እገዳ እገዳ ከሆነ entry (ግቤት) ፣ ከዚያ t0 ቋሚ ነው 0, እና የቀደመው መሰረታዊ እገዳ ከሆነ for.body, ከዚያ ዋጋውን መውሰድ ያስፈልግዎታል t6 ከዚህ እገዳ. ይህ ሁሉ በጣም ሚስጥራዊ ሊመስል ይችላል፣ ግን ይህ ዘዴ SSA እንዲሰራ የሚያደርገው ነው። ከሰብአዊ እይታ ይህ ሁሉ ኮዱን ለመረዳት አስቸጋሪ ያደርገዋል, ነገር ግን እያንዳንዱ እሴት አንድ ጊዜ ብቻ መሰጠቱ ብዙ ማመቻቸትን በጣም ቀላል ያደርገዋል.

የእራስዎን ማቀናበሪያ ከጻፉ, ብዙውን ጊዜ እንደዚህ አይነት ነገሮችን መቋቋም እንደማይችሉ ልብ ይበሉ. ክላንግ እንኳን እነዚህን ሁሉ መመሪያዎች አያመነጭም። phi፣ ዘዴን ይጠቀማል alloca (ከተራ የአካባቢ ተለዋዋጮች ጋር አብሮ መሥራትን ይመስላል)። ከዚያ የኤልኤልቪኤም ማበልጸጊያ ማለፊያ ሲሰራ mem2reg, መመሪያዎች alloca ወደ SSA ቅፅ ተቀይሯል። TinyGo፣ነገር ግን፣ከGo SSA ግብአት ይቀበላል፣ይህም፣በተመች ሁኔታ፣ወደ SSA ቅጽ ተቀይሯል።

ሌላው ከግምት ውስጥ ያለው የመካከለኛ ኮድ ስብርባሪ አዲስ ነገር በመረጃ ጠቋሚ የተቆራረጡ ንጥረ ነገሮችን ማግኘት አድራሻውን በማስላት እና የተገኘውን ጠቋሚ ወደ ሌላ ቦታ የማስገባት አሰራር ነው። እዚህ የቋሚዎች ቀጥታ ወደ IR ኮድ መጨመር ማየት ይችላሉ (ለምሳሌ - 1:int). ተግባር ጋር ምሳሌ ውስጥ myAdd ይህ ጥቅም ላይ አልዋለም. አሁን እነዚያን ባህሪያት ከመንገድ ውጪ ስላደረግን፣ ይህ ኮድ ወደ LLVM IR ቅጽ ሲቀየር ምን እንደሚሆን እንይ፡

define i64 @sum(i64* %ptr, i64 %len, i64 %cap) {
entry:
  br label %for.loop

for.loop:                                         ; preds = %for.body, %entry
  %0 = phi i64 [ 0, %entry ], [ %5, %deref.next ]
  %1 = phi i64 [ 0, %entry ], [ %6, %deref.next ]
  %2 = icmp slt i64 %1, %len
  br i1 %2, label %for.body, label %for.done

for.body:                                         ; preds = %for.loop
  %3 = getelementptr i64, i64* %ptr, i64 %1
  %4 = load i64, i64* %3
  %5 = add i64 %0, %4
  %6 = add i64 %1, 1
  br label %for.loop

for.done:                                         ; preds = %for.loop
  ret i64 %0
}

እዚህ, ልክ እንደበፊቱ, ሌሎች የተዋሃዱ አወቃቀሮችን ያካተተ ተመሳሳይ መዋቅር ማየት እንችላለን. ለምሳሌ, በጥሪዎች ውስጥ phi እሴቶች እና መለያዎች ተለዋወጡ። ይሁን እንጂ ለየት ያለ ትኩረት ሊሰጠው የሚገባ ነገር እዚህ አለ.

ለመጀመር፣ እዚህ ፍጹም የተለየ የተግባር ፊርማ ማየት ይችላሉ። LLVM ቁርጥራጭን አይደግፍም፣ በውጤቱም፣ እንደ ማመቻቸት፣ ይህን መካከለኛ ኮድ ያመነጨው የTinyGo አጠናቃሪ የዚህን የውሂብ መዋቅር መግለጫ ወደ ክፍሎች ይከፍላል። እሱ ሶስት አካላትን ሊወክል ይችላል (ptr, len и cap) እንደ መዋቅር (መዋቅር), ነገር ግን እነሱን እንደ ሶስት የተለያዩ አካላት መወከል አንዳንድ ማመቻቸትን ይፈቅዳል. በዒላማው መድረክ ተግባራት የጥሪ ስምምነቶች ላይ በመመስረት ሌሎች አቀናባሪዎች ቁርጥራጮቹን በሌሎች መንገዶች ሊወክሉ ይችላሉ።

የዚህ ኮድ ሌላ አስደሳች ገጽታ የመመሪያው አጠቃቀም ነው getelementptr (ብዙውን ጊዜ ጂኢፒ ተብሎ ይጠራል)።

ይህ መመሪያ በጠቋሚዎች ይሰራል እና ወደ ቁርጥራጭ አካል ጠቋሚ ለማግኘት ይጠቅማል። ለምሳሌ፣ በC ከተጻፈው ከሚከተለው ኮድ ጋር እናወዳድረው፡-

int* sliceptr(int *ptr, int index) {
    return &ptr[index];
}

ወይም ከዚህ ጋር ከሚከተለው ጋር

int* sliceptr(int *ptr, int index) {
    return ptr + index;
}

እዚህ በጣም አስፈላጊው ነገር መመሪያው ነው getelementptr የማሰናከል ስራዎችን አያከናውንም. አሁን ባለው ላይ በመመስረት አዲስ ጠቋሚን ብቻ ያሰላል. እንደ መመሪያ ሊወሰድ ይችላል mul и add በሃርድዌር ደረጃ. ስለ GEP መመሪያዎች የበለጠ ማንበብ ይችላሉ። እዚህ.

የዚህ መካከለኛ ኮድ ሌላ አስደሳች ገጽታ የመመሪያው አጠቃቀም ነው icmp. ይህ የኢንቲጀር ንጽጽሮችን ለመተግበር የሚያገለግል አጠቃላይ ዓላማ መመሪያ ነው። የዚህ መመሪያ ውጤት ሁል ጊዜ የአይነት እሴት ነው። i1 - ምክንያታዊ እሴት. በዚህ አጋጣሚ ንጽጽር የሚደረገው ቁልፍ ቃሉን በመጠቀም ነው። slt (ከዚህ ያነሰ የተፈረመ)፣ ቀደም ሲል በአይነቱ የተወከሉትን ሁለት ቁጥሮች እያወዳደርን ነው። int. ሁለት ያልተፈረሙ ኢንቲጀር ብናወዳድር እንጠቀም ነበር። icmp, እና በንፅፅር ውስጥ ጥቅም ላይ የዋለው ቁልፍ ቃል ይሆናል ult. ተንሳፋፊ ነጥብ ቁጥሮችን ለማነፃፀር ፣ ሌላ መመሪያ ጥቅም ላይ ይውላል ፣ fcmp, እሱም በተመሳሳይ መንገድ ይሰራል.

ውጤቶች

በዚህ ጽሑፍ ውስጥ በጣም አስፈላጊ የሆኑትን የኤልኤልቪኤም አይአር ባህሪያት እንደሸፈነሁ አምናለሁ። እርግጥ ነው, እዚህ ብዙ ተጨማሪ ነገሮች አሉ. በተለይም የኮዱ መካከለኛ ውክልና የማመቻቸት ማለፊያዎች በ IR ውስጥ ሊገለጹ የማይችሉትን አንዳንድ የኮድ ማጠናቀሪያ ባህሪያትን ከግምት ውስጥ ለማስገባት የሚያስችሉ ብዙ ማብራሪያዎችን ሊይዝ ይችላል። ለምሳሌ ይህ ባንዲራ ነው። inbounds የጂኢፒ መመሪያዎች፣ ወይም ባንዲራዎች nsw и nuw, ይህም ወደ መመሪያው ሊጨመር ይችላል add. ለቁልፍ ቃልም ተመሳሳይ ነው private, የሚያመለክተው ተግባር አሁን ካለው የማጠናቀር ክፍል ውጭ እንደማይጠቀስ ለአመቻቹ ያሳያል። ይህ እንደ ጥቅም ላይ ያልዋሉ ክርክሮችን ማስወገድ ያሉ ብዙ አስደሳች የእርስ በርስ ማመቻቸት ያስችላል።

ስለ LLVM ተጨማሪ ማንበብ ይችላሉ። ሰነድየእራስዎን LLVM-ተኮር ማቀናበሪያ ሲገነቡ ብዙ ጊዜ የሚጠቅሱት። እዚህ መመሪያ, እሱም በጣም ቀላል ለሆነ ቋንቋ ማጠናከሪያን ማዳበርን ይመለከታል. የእራስዎን ማጠናከሪያ ሲፈጥሩ እነዚህ ሁለቱም የመረጃ ምንጮች ለእርስዎ ጠቃሚ ይሆናሉ.

ውድ አንባቢዎች! LLVM እየተጠቀሙ ነው?

LLVM ከ Go እይታ

ምንጭ: hab.com

አስተያየት ያክሉ