ഒരു ഗോ വീക്ഷണകോണിൽ നിന്നുള്ള LLVM

ഒരു കംപൈലർ വികസിപ്പിക്കുന്നത് വളരെ ബുദ്ധിമുട്ടുള്ള കാര്യമാണ്. പക്ഷേ, ഭാഗ്യവശാൽ, LLVM പോലുള്ള പ്രോജക്‌ടുകളുടെ വികസനത്തോടെ, ഈ പ്രശ്‌നത്തിനുള്ള പരിഹാരം വളരെ ലളിതമാക്കിയിരിക്കുന്നു, ഇത് ഒരു പ്രോഗ്രാമറെപ്പോലും ഒരു പുതിയ ഭാഷ സൃഷ്ടിക്കാൻ അനുവദിക്കുന്നു, ഇത് C- യുടെ പ്രകടനത്തിന് അടുത്താണ്. LLVM-നൊപ്പം പ്രവർത്തിക്കുന്നത് സങ്കീർണ്ണമാണ് ചെറിയ ഡോക്യുമെന്റേഷൻ സജ്ജീകരിച്ചിരിക്കുന്ന ഒരു വലിയ അളവിലുള്ള കോഡാണ് സിസ്റ്റത്തെ പ്രതിനിധീകരിക്കുന്നത്. ഈ പോരായ്മ പരിഹരിക്കാൻ ശ്രമിക്കുന്നതിന്, മെറ്റീരിയലിന്റെ രചയിതാവ്, ഞങ്ങൾ ഇന്ന് പ്രസിദ്ധീകരിക്കുന്ന വിവർത്തനം, Go- ൽ എഴുതിയ കോഡിന്റെ ഉദാഹരണങ്ങൾ പ്രദർശിപ്പിക്കുകയും അവ ആദ്യം എങ്ങനെ വിവർത്തനം ചെയ്യപ്പെടുകയും ചെയ്യുന്നു എന്ന് കാണിക്കാൻ പോകുന്നു. പോകൂ SSA, തുടർന്ന് എൽഎൽവിഎം ഐആർ കംപൈലർ ഉപയോഗിച്ച് ടിനിഗോ. Go SSA, LLVM IR കോഡ്, ഇവിടെ നൽകിയിരിക്കുന്ന വിശദീകരണങ്ങൾക്ക് പ്രസക്തമല്ലാത്ത കാര്യങ്ങൾ നീക്കം ചെയ്യുന്നതിനായി, വിശദീകരണങ്ങൾ കൂടുതൽ മനസ്സിലാക്കാൻ വേണ്ടി ചെറുതായി എഡിറ്റ് ചെയ്തിട്ടുണ്ട്.

ഒരു ഗോ വീക്ഷണകോണിൽ നിന്നുള്ള LLVM

ആദ്യ ഉദാഹരണം

ഞാൻ ഇവിടെ നോക്കാൻ പോകുന്ന ആദ്യത്തെ ഫംഗ്‌ഷൻ നമ്പറുകൾ ചേർക്കുന്നതിനുള്ള ഒരു ലളിതമായ സംവിധാനമാണ്:

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 ഫ്രണ്ട്‌എൻഡ് പ്രവർത്തനത്തിലേക്ക് ഒരു ഫ്ലാഗ് ചേർക്കുന്നു nsw (ഒപ്പിട്ട പൊതിയൊന്നുമില്ല), കൂട്ടിച്ചേർക്കൽ ഒരിക്കലും കവിഞ്ഞൊഴുകുന്നില്ലെന്ന് LLVM-നോട് പറയുന്നു.

ചില ഒപ്റ്റിമൈസേഷനുകൾക്ക് ഇത് പ്രധാനമായിരിക്കാം. ഉദാഹരണത്തിന്, രണ്ട് മൂല്യങ്ങൾ ചേർക്കുന്നു i16 ഒരു 32-ബിറ്റ് പ്ലാറ്റ്‌ഫോമിൽ (32-ബിറ്റ് രജിസ്റ്ററുകളുള്ള) പരിധിയിൽ തുടരുന്നതിന്, കൂട്ടിച്ചേർക്കലിനുശേഷം, ഒരു സൈൻ വിപുലീകരണ പ്രവർത്തനം ആവശ്യമാണ് i16. ഇക്കാരണത്താൽ, മെഷീൻ രജിസ്റ്റർ വലുപ്പങ്ങളെ അടിസ്ഥാനമാക്കി പൂർണ്ണസംഖ്യ പ്രവർത്തനങ്ങൾ നടത്തുന്നത് പലപ്പോഴും കൂടുതൽ കാര്യക്ഷമമാണ്.

ഈ ഐആർ കോഡ് ഉപയോഗിച്ച് അടുത്തതായി എന്ത് സംഭവിക്കും എന്നത് ഇപ്പോൾ ഞങ്ങൾക്ക് പ്രത്യേക താൽപ്പര്യമുള്ള കാര്യമല്ല. കോഡ് ഒപ്റ്റിമൈസ് ചെയ്‌തിരിക്കുന്നു (എന്നാൽ നമ്മുടേത് പോലെയുള്ള ഒരു ലളിതമായ ഉദാഹരണത്തിന്റെ കാര്യത്തിൽ, ഒന്നും ഒപ്റ്റിമൈസ് ചെയ്തിട്ടില്ല) തുടർന്ന് മെഷീൻ കോഡായി പരിവർത്തനം ചെയ്യുന്നു.

രണ്ടാമത്തെ ഉദാഹരണം

നമ്മൾ നോക്കുന്ന അടുത്ത ഉദാഹരണം കുറച്ചുകൂടി സങ്കീർണ്ണമായിരിക്കും. അതായത്, പൂർണ്ണസംഖ്യകളുടെ ഒരു സ്ലൈസ് സംഗ്രഹിക്കുന്ന ഒരു ഫംഗ്ഷനെക്കുറിച്ചാണ് നമ്മൾ സംസാരിക്കുന്നത്:

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

എസ്എസ്എ ഫോമിൽ കോഡിനെ പ്രതിനിധീകരിക്കുന്നതിനുള്ള സാധാരണ കൂടുതൽ നിർമ്മാണങ്ങൾ നിങ്ങൾക്ക് ഇതിനകം തന്നെ ഇവിടെ കാണാൻ കഴിയും. ഘടനാപരമായ ഫ്ലോ കൺട്രോൾ കമാൻഡുകൾ ഇല്ലെന്നതാണ് ഈ കോഡിന്റെ ഏറ്റവും വ്യക്തമായ സവിശേഷത. കണക്കുകൂട്ടലുകളുടെ ഒഴുക്ക് നിയന്ത്രിക്കുന്നതിന്, സോപാധികവും നിരുപാധികവുമായ ജമ്പുകൾ മാത്രമേ ഉള്ളൂ, കൂടാതെ, ഈ കമാൻഡ് ഒഴുക്ക് നിയന്ത്രിക്കുന്നതിനുള്ള ഒരു കമാൻഡായി ഞങ്ങൾ പരിഗണിക്കുകയാണെങ്കിൽ, ഒരു റിട്ടേൺ കമാൻഡ്.

വാസ്തവത്തിൽ, ചുരുണ്ട ബ്രേസുകൾ (ഭാഷകളുടെ സി കുടുംബത്തിലെന്നപോലെ) ഉപയോഗിച്ച് പ്രോഗ്രാം ബ്ലോക്കുകളായി വിഭജിച്ചിട്ടില്ല എന്ന വസ്തുത ഇവിടെ നിങ്ങൾക്ക് ശ്രദ്ധിക്കാം. ഇത് അസംബ്ലി ഭാഷകളെ അനുസ്മരിപ്പിക്കുന്ന ലേബലുകളാൽ വിഭജിക്കുകയും അടിസ്ഥാന ബ്ലോക്കുകളുടെ രൂപത്തിൽ അവതരിപ്പിക്കുകയും ചെയ്യുന്നു. എസ്‌എസ്‌എയിൽ, അടിസ്ഥാന ബ്ലോക്കുകളെ ഒരു ലേബലിൽ ആരംഭിച്ച് അടിസ്ഥാന ബ്ലോക്ക് പൂർത്തീകരണ നിർദ്ദേശങ്ങളിൽ അവസാനിക്കുന്ന കോഡിന്റെ തുടർച്ചയായ ശ്രേണികളായി നിർവചിക്കപ്പെടുന്നു, ഉദാഹരണത്തിന് - return и jump.

ഈ കോഡിന്റെ രസകരമായ മറ്റൊരു വിശദാംശം നിർദ്ദേശം പ്രതിനിധീകരിക്കുന്നു phi. നിർദ്ദേശങ്ങൾ തികച്ചും അസാധാരണമാണ്, മനസ്സിലാക്കാൻ കുറച്ച് സമയമെടുത്തേക്കാം. എന്ന് ഓർക്കണം എസ്എസ്എ സ്റ്റാറ്റിക് സിംഗിൾ അസൈൻമെന്റ് എന്നതിന്റെ ചുരുക്കമാണ്. കംപൈലറുകൾ ഉപയോഗിക്കുന്ന കോഡിന്റെ ഒരു ഇന്റർമീഡിയറ്റ് പ്രാതിനിധ്യമാണിത്, അതിൽ ഓരോ വേരിയബിളിനും ഒരു തവണ മാത്രമേ മൂല്യം നൽകൂ. ഞങ്ങളുടെ ഫംഗ്‌ഷൻ പോലുള്ള ലളിതമായ ഫംഗ്‌ഷനുകൾ പ്രകടിപ്പിക്കുന്നതിന് ഇത് മികച്ചതാണ് myAddമുകളിൽ കാണിച്ചിരിക്കുന്നു, എന്നാൽ ഈ വിഭാഗത്തിൽ ചർച്ച ചെയ്തിരിക്കുന്ന ഫംഗ്‌ഷൻ പോലുള്ള കൂടുതൽ സങ്കീർണ്ണമായ ഫംഗ്‌ഷനുകൾക്ക് അനുയോജ്യമല്ല sum. പ്രത്യേകിച്ച്, ലൂപ്പിന്റെ എക്സിക്യൂഷൻ സമയത്ത് വേരിയബിളുകൾ മാറുന്നു i и n.

നിർദ്ദേശം എന്ന് വിളിക്കപ്പെടുന്ന ഒരു പ്രാവശ്യം ഉപയോഗിച്ച് വേരിയബിൾ മൂല്യങ്ങൾ നൽകുന്നതിനുള്ള നിയന്ത്രണം SSA മറികടക്കുന്നു phi (അതിന്റെ പേര് ഗ്രീക്ക് അക്ഷരമാലയിൽ നിന്നാണ് എടുത്തത്). സി പോലുള്ള ഭാഷകൾക്കായി കോഡിന്റെ എസ്എസ്എ പ്രാതിനിധ്യം സൃഷ്ടിക്കുന്നതിന്, നിങ്ങൾ ചില തന്ത്രങ്ങൾ അവലംബിക്കേണ്ടതുണ്ട് എന്നതാണ് വസ്തുത. ഈ നിർദ്ദേശം വിളിക്കുന്നതിന്റെ ഫലം വേരിയബിളിന്റെ നിലവിലെ മൂല്യമാണ് (i അഥവാ n), കൂടാതെ അടിസ്ഥാന ബ്ലോക്കുകളുടെ ഒരു ലിസ്റ്റ് അതിന്റെ പാരാമീറ്ററുകളായി ഉപയോഗിക്കുന്നു. ഉദാഹരണത്തിന്, ഈ നിർദ്ദേശം പരിഗണിക്കുക:

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

അതിന്റെ അർത്ഥം ഇപ്രകാരമാണ്: മുമ്പത്തെ അടിസ്ഥാന ബ്ലോക്ക് ഒരു ബ്ലോക്ക് ആയിരുന്നെങ്കിൽ entry (ഇൻപുട്ട്), തുടർന്ന് t0 സ്ഥിരമാണ് 0, മുമ്പത്തെ അടിസ്ഥാന ബ്ലോക്ക് ആണെങ്കിൽ for.body, അപ്പോൾ നിങ്ങൾ മൂല്യം എടുക്കേണ്ടതുണ്ട് t6 ഈ ബ്ലോക്കിൽ നിന്ന്. ഇതെല്ലാം വളരെ നിഗൂഢമായി തോന്നിയേക്കാം, എന്നാൽ ഈ സംവിധാനമാണ് എസ്എസ്എയെ പ്രവർത്തിക്കുന്നത്. ഒരു മാനുഷിക വീക്ഷണകോണിൽ, ഇതെല്ലാം കോഡ് മനസ്സിലാക്കാൻ പ്രയാസകരമാക്കുന്നു, എന്നാൽ ഓരോ മൂല്യവും ഒരിക്കൽ മാത്രമേ നിയുക്തമാക്കിയിട്ടുള്ളൂ എന്നത് പല ഒപ്റ്റിമൈസേഷനുകളും വളരെ എളുപ്പമാക്കുന്നു.

നിങ്ങളുടേതായ കംപൈലർ എഴുതുകയാണെങ്കിൽ, സാധാരണയായി ഇത്തരം കാര്യങ്ങൾ കൈകാര്യം ചെയ്യേണ്ടതില്ലെന്ന കാര്യം ശ്രദ്ധിക്കുക. ക്ലാങ് പോലും ഈ നിർദ്ദേശങ്ങളെല്ലാം സൃഷ്ടിക്കുന്നില്ല phi, ഇത് ഒരു മെക്കാനിസം ഉപയോഗിക്കുന്നു alloca (ഇത് സാധാരണ പ്രാദേശിക വേരിയബിളുകളുമായി പ്രവർത്തിക്കുന്നതിന് സമാനമാണ്). തുടർന്ന്, ഒരു LLVM ഒപ്റ്റിമൈസേഷൻ പാസ് പ്രവർത്തിപ്പിക്കുമ്പോൾ വിളിക്കുന്നു mem2reg, നിർദ്ദേശങ്ങൾ alloca എസ്എസ്എ ഫോമിലേക്ക് പരിവർത്തനം ചെയ്തു. എന്നിരുന്നാലും, TinyGo, Go SSA-യിൽ നിന്ന് ഇൻപുട്ട് സ്വീകരിക്കുന്നു, അത് സൗകര്യപ്രദമായി, ഇതിനകം തന്നെ SSA ഫോമിലേക്ക് പരിവർത്തനം ചെയ്തിട്ടുണ്ട്.

പരിഗണനയിലുള്ള ഇന്റർമീഡിയറ്റ് കോഡിന്റെ ശകലത്തിന്റെ മറ്റൊരു നൂതനമായത്, സൂചിക വഴി സ്ലൈസ് ഘടകങ്ങളിലേക്കുള്ള ആക്‌സസ് വിലാസം കണക്കാക്കുന്ന പ്രവർത്തനത്തിന്റെ രൂപത്തിലും ഫലമായുണ്ടാകുന്ന പോയിന്റർ ഡിഫെറൻസ് ചെയ്യുന്ന പ്രവർത്തനത്തിന്റെ രൂപത്തിലും പ്രതിനിധീകരിക്കുന്നു എന്നതാണ്. ഐആർ കോഡിലേക്ക് സ്ഥിരാങ്കങ്ങൾ നേരിട്ട് ചേർക്കുന്നത് ഇവിടെ കാണാം (ഉദാഹരണത്തിന് - 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 (പലപ്പോഴും GEP എന്ന് ചുരുക്കി പറയാറുണ്ട്).

ഈ നിർദ്ദേശം പോയിന്ററുകളുമായി പ്രവർത്തിക്കുന്നു, ഒരു സ്ലൈസ് എലമെന്റിലേക്ക് ഒരു പോയിന്റർ ലഭിക്കാൻ ഇത് ഉപയോഗിക്കുന്നു. ഉദാഹരണത്തിന്, സിയിൽ എഴുതിയിരിക്കുന്ന ഇനിപ്പറയുന്ന കോഡുമായി താരതമ്യം ചെയ്യാം:

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

അല്ലെങ്കിൽ ഇതിന് തുല്യമായ ഇനിപ്പറയുന്നവ ഉപയോഗിച്ച്:

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

ഇവിടെ ഏറ്റവും പ്രധാനപ്പെട്ട കാര്യം നിർദ്ദേശങ്ങൾ ആണ് getelementptr dereferencing പ്രവർത്തനങ്ങൾ നടത്തുന്നില്ല. ഇത് നിലവിലുള്ളതിനെ അടിസ്ഥാനമാക്കി ഒരു പുതിയ പോയിന്റർ കണക്കാക്കുന്നു. ഇത് നിർദ്ദേശങ്ങളായി എടുക്കാം mul и add ഹാർഡ്‌വെയർ തലത്തിൽ. GEP നിർദ്ദേശങ്ങളെക്കുറിച്ച് നിങ്ങൾക്ക് കൂടുതൽ വായിക്കാം ഇവിടെ.

ഈ ഇന്റർമീഡിയറ്റ് കോഡിന്റെ മറ്റൊരു രസകരമായ സവിശേഷത നിർദ്ദേശത്തിന്റെ ഉപയോഗമാണ് icmp. പൂർണ്ണസംഖ്യ താരതമ്യങ്ങൾ നടപ്പിലാക്കാൻ ഉപയോഗിക്കുന്ന ഒരു പൊതു ഉദ്ദേശ്യ നിർദ്ദേശമാണിത്. ഈ നിർദ്ദേശം നടപ്പിലാക്കുന്നതിന്റെ ഫലം എല്ലായ്പ്പോഴും തരം മൂല്യമാണ് i1 - ലോജിക്കൽ മൂല്യം. ഈ സാഹചര്യത്തിൽ, കീവേഡ് ഉപയോഗിച്ച് ഒരു താരതമ്യം നടത്തുന്നു slt (ഇതിലും കുറവ് ഒപ്പിട്ടത്), കാരണം ഞങ്ങൾ മുമ്പ് പ്രതിനിധീകരിച്ച രണ്ട് സംഖ്യകളെ താരതമ്യം ചെയ്യുന്നു int. ഞങ്ങൾ ഒപ്പിടാത്ത രണ്ട് പൂർണ്ണസംഖ്യകളെ താരതമ്യം ചെയ്യുകയാണെങ്കിൽ, ഞങ്ങൾ ഉപയോഗിക്കും icmp, താരതമ്യത്തിൽ ഉപയോഗിക്കുന്ന കീവേഡ് ഇതായിരിക്കും ult. ഫ്ലോട്ടിംഗ് പോയിന്റ് നമ്പറുകൾ താരതമ്യം ചെയ്യാൻ, മറ്റൊരു നിർദ്ദേശം ഉപയോഗിക്കുന്നു, fcmp, സമാനമായ രീതിയിൽ പ്രവർത്തിക്കുന്നു.

ഫലങ്ങൾ

LLVM IR-ന്റെ ഏറ്റവും പ്രധാനപ്പെട്ട സവിശേഷതകൾ ഈ മെറ്റീരിയലിൽ ഞാൻ ഉൾപ്പെടുത്തിയിട്ടുണ്ട് എന്ന് ഞാൻ വിശ്വസിക്കുന്നു. തീർച്ചയായും, ഇവിടെ ധാരാളം ഉണ്ട്. പ്രത്യേകിച്ചും, കോഡിന്റെ ഇന്റർമീഡിയറ്റ് പ്രാതിനിധ്യത്തിൽ IR-ൽ പ്രകടിപ്പിക്കാൻ കഴിയാത്ത കംപൈലറിന് അറിയാവുന്ന കോഡിന്റെ ചില സവിശേഷതകൾ കണക്കിലെടുക്കാൻ ഒപ്റ്റിമൈസേഷൻ പാസുകളെ അനുവദിക്കുന്ന നിരവധി വ്യാഖ്യാനങ്ങൾ അടങ്ങിയിരിക്കാം. ഉദാഹരണത്തിന്, ഇതൊരു പതാകയാണ് inbounds GEP നിർദ്ദേശങ്ങൾ, അല്ലെങ്കിൽ പതാകകൾ nsw и nuw, ഇത് നിർദ്ദേശങ്ങളിൽ ചേർക്കാവുന്നതാണ് add. കീവേഡിന്റെ കാര്യവും അങ്ങനെ തന്നെ private, അത് അടയാളപ്പെടുത്തുന്ന ഫംഗ്‌ഷൻ നിലവിലെ കംപൈലേഷൻ യൂണിറ്റിന് പുറത്ത് നിന്ന് പരാമർശിക്കില്ലെന്ന് ഒപ്റ്റിമൈസർ സൂചിപ്പിക്കുന്നു. ഉപയോഗിക്കാത്ത ആർഗ്യുമെന്റുകൾ ഇല്ലാതാക്കുന്നത് പോലുള്ള രസകരമായ ഇന്റർപ്രൊസീജറൽ ഒപ്റ്റിമൈസേഷനുകൾ ഇത് അനുവദിക്കുന്നു.

നിങ്ങൾക്ക് LLVM-നെ കുറിച്ച് കൂടുതൽ വായിക്കാം പ്രമാണീകരണം, നിങ്ങളുടേതായ LLVM-അധിഷ്ഠിത കമ്പൈലർ വികസിപ്പിക്കുമ്പോൾ നിങ്ങൾ പലപ്പോഴും ഇത് പരാമർശിക്കും. ഇവിടെ നേതൃത്വം, വളരെ ലളിതമായ ഭാഷയ്ക്കായി ഒരു കമ്പൈലർ വികസിപ്പിക്കുന്നത് നോക്കുന്നു. നിങ്ങളുടെ സ്വന്തം കമ്പൈലർ സൃഷ്ടിക്കുമ്പോൾ ഈ രണ്ട് വിവര സ്രോതസ്സുകളും നിങ്ങൾക്ക് ഉപയോഗപ്രദമാകും.

പ്രിയ വായനക്കാർ! നിങ്ങൾ LLVM ഉപയോഗിക്കുന്നുണ്ടോ?

ഒരു ഗോ വീക്ഷണകോണിൽ നിന്നുള്ള LLVM

അവലംബം: www.habr.com

ഒരു അഭിപ്രായം ചേർക്കുക