ಗೋ ದೃಷ್ಟಿಕೋನದಿಂದ LLVM

ಕಂಪೈಲರ್ ಅನ್ನು ಅಭಿವೃದ್ಧಿಪಡಿಸುವುದು ತುಂಬಾ ಕಷ್ಟದ ಕೆಲಸ. ಆದರೆ, ಅದೃಷ್ಟವಶಾತ್, LLVM ನಂತಹ ಯೋಜನೆಗಳ ಅಭಿವೃದ್ಧಿಯೊಂದಿಗೆ, ಈ ಸಮಸ್ಯೆಗೆ ಪರಿಹಾರವನ್ನು ಬಹಳ ಸರಳಗೊಳಿಸಲಾಗಿದೆ, ಇದು ಒಬ್ಬ ಪ್ರೋಗ್ರಾಮರ್ ಕೂಡ C ಗೆ ಕಾರ್ಯಕ್ಷಮತೆಗೆ ಹತ್ತಿರವಿರುವ ಹೊಸ ಭಾಷೆಯನ್ನು ರಚಿಸಲು ಅನುಮತಿಸುತ್ತದೆ. LLVM ನೊಂದಿಗೆ ಕೆಲಸ ಮಾಡುವುದು ಜಟಿಲವಾಗಿದೆ. ಸಿಸ್ಟಮ್ ಅನ್ನು ದೊಡ್ಡ ಪ್ರಮಾಣದ ಕೋಡ್‌ನಿಂದ ಪ್ರತಿನಿಧಿಸಲಾಗುತ್ತದೆ, ಕಡಿಮೆ ದಾಖಲಾತಿಗಳನ್ನು ಹೊಂದಿದೆ. ಈ ನ್ಯೂನತೆಯನ್ನು ಸರಿಪಡಿಸಲು ಪ್ರಯತ್ನಿಸುವ ಸಲುವಾಗಿ, ನಾವು ಇಂದು ಪ್ರಕಟಿಸುತ್ತಿರುವ ವಸ್ತುವಿನ ಲೇಖಕರು, ನಾವು ಇಂದು ಪ್ರಕಟಿಸುತ್ತಿರುವ ಅನುವಾದವು, Go ನಲ್ಲಿ ಬರೆದ ಕೋಡ್‌ನ ಉದಾಹರಣೆಗಳನ್ನು ಪ್ರದರ್ಶಿಸಲು ಮತ್ತು ಅವುಗಳನ್ನು ಮೊದಲು ಹೇಗೆ ಅನುವಾದಿಸಲಾಗಿದೆ ಎಂಬುದನ್ನು ತೋರಿಸುತ್ತದೆ. ಹೋಗಿ SSA, ಮತ್ತು ನಂತರ LLVM IR ನಲ್ಲಿ ಕಂಪೈಲರ್ ಅನ್ನು ಬಳಸಿ ಟೈನಿಗೋ. ವಿವರಣೆಗಳನ್ನು ಹೆಚ್ಚು ಅರ್ಥವಾಗುವಂತೆ ಮಾಡಲು, ಇಲ್ಲಿ ನೀಡಿರುವ ವಿವರಣೆಗಳಿಗೆ ಸಂಬಂಧಿಸದ ವಿಷಯಗಳನ್ನು ತೆಗೆದುಹಾಕಲು 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 ಗೆ ಹೋಲುತ್ತದೆ. ಇಲ್ಲಿ, ಫಂಕ್ಷನ್ ಡಿಕ್ಲರೇಶನ್‌ನಲ್ಲಿ, ಮೊದಲು ಅದು ಹಿಂದಿರುಗಿಸುವ ಡೇಟಾ ಪ್ರಕಾರದ ವಿವರಣೆಯಿದೆ, ಆರ್ಗ್ಯುಮೆಂಟ್ ಹೆಸರಿನ ಮೊದಲು ಆರ್ಗ್ಯುಮೆಂಟ್ ಪ್ರಕಾರವನ್ನು ಸೂಚಿಸಲಾಗುತ್ತದೆ. ಹೆಚ್ಚುವರಿಯಾಗಿ, ಐಆರ್ ಪಾರ್ಸಿಂಗ್ ಅನ್ನು ಸರಳಗೊಳಿಸಲು, ಜಾಗತಿಕ ಘಟಕಗಳ ಹೆಸರುಗಳು ಚಿಹ್ನೆಯಿಂದ ಮುಂಚಿತವಾಗಿರುತ್ತವೆ @, ಮತ್ತು ಸ್ಥಳೀಯ ಹೆಸರುಗಳ ಮೊದಲು ಒಂದು ಚಿಹ್ನೆ ಇದೆ % (ಒಂದು ಕಾರ್ಯವನ್ನು ಜಾಗತಿಕ ಘಟಕವೆಂದು ಪರಿಗಣಿಸಲಾಗುತ್ತದೆ).

ಈ ಕೋಡ್‌ನಲ್ಲಿ ಗಮನಿಸಬೇಕಾದ ಒಂದು ವಿಷಯವೆಂದರೆ ಗೋ ಪ್ರಕಾರದ ಪ್ರಾತಿನಿಧ್ಯ ನಿರ್ಧಾರ int, 32-ಬಿಟ್ ಅಥವಾ 64-ಬಿಟ್ ಮೌಲ್ಯವಾಗಿ ಪ್ರತಿನಿಧಿಸಬಹುದು, ಕಂಪೈಲರ್ ಮತ್ತು ಸಂಕಲನದ ಗುರಿಯನ್ನು ಅವಲಂಬಿಸಿ, LLVM IR ಕೋಡ್ ಅನ್ನು ರಚಿಸಿದಾಗ ಸ್ವೀಕರಿಸಲಾಗುತ್ತದೆ. ಎಲ್‌ಎಲ್‌ವಿಎಂ ಐಆರ್ ಕೋಡ್ ಅನೇಕ ಜನರು ಯೋಚಿಸಿದಂತೆ ವೇದಿಕೆ ಸ್ವತಂತ್ರವಾಗಿರದಿರಲು ಇದು ಹಲವು ಕಾರಣಗಳಲ್ಲಿ ಒಂದಾಗಿದೆ. ಒಂದು ಪ್ಲಾಟ್‌ಫಾರ್ಮ್‌ಗಾಗಿ ರಚಿಸಲಾದ ಅಂತಹ ಕೋಡ್ ಅನ್ನು ಮತ್ತೊಂದು ಪ್ಲಾಟ್‌ಫಾರ್ಮ್‌ಗೆ ಸರಳವಾಗಿ ತೆಗೆದುಕೊಂಡು ಕಂಪೈಲ್ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ (ಈ ಸಮಸ್ಯೆಯನ್ನು ಪರಿಹರಿಸಲು ನೀವು ಸೂಕ್ತವಲ್ಲದಿದ್ದರೆ ತೀವ್ರ ಕಾಳಜಿಯೊಂದಿಗೆ).

ಗಮನಿಸಬೇಕಾದ ಮತ್ತೊಂದು ಆಸಕ್ತಿದಾಯಕ ಅಂಶವೆಂದರೆ ಪ್ರಕಾರ i64 ಸಹಿ ಮಾಡಿದ ಪೂರ್ಣಾಂಕವಲ್ಲ: ಇದು ಸಂಖ್ಯೆಯ ಚಿಹ್ನೆಯನ್ನು ಪ್ರತಿನಿಧಿಸುವ ವಿಷಯದಲ್ಲಿ ತಟಸ್ಥವಾಗಿದೆ. ಸೂಚನೆಯನ್ನು ಅವಲಂಬಿಸಿ, ಇದು ಸಹಿ ಮತ್ತು ಸಹಿ ಮಾಡದ ಸಂಖ್ಯೆಗಳನ್ನು ಪ್ರತಿನಿಧಿಸಬಹುದು. ಸೇರ್ಪಡೆ ಕಾರ್ಯಾಚರಣೆಯ ಪ್ರಾತಿನಿಧ್ಯದ ಸಂದರ್ಭದಲ್ಲಿ, ಇದು ಅಪ್ರಸ್ತುತವಾಗುತ್ತದೆ, ಆದ್ದರಿಂದ ಸಹಿ ಅಥವಾ ಸಹಿ ಮಾಡದ ಸಂಖ್ಯೆಗಳೊಂದಿಗೆ ಕೆಲಸ ಮಾಡುವಲ್ಲಿ ಯಾವುದೇ ವ್ಯತ್ಯಾಸವಿಲ್ಲ. ಇಲ್ಲಿ ನಾನು C ಭಾಷೆಯಲ್ಲಿ, ಸಹಿ ಮಾಡಲಾದ ಪೂರ್ಣಾಂಕ ವೇರಿಯೇಬಲ್ ಅನ್ನು ಉಕ್ಕಿಹರಿಯುವುದು ವ್ಯಾಖ್ಯಾನಿಸದ ನಡವಳಿಕೆಗೆ ಕಾರಣವಾಗುತ್ತದೆ ಎಂದು ನಾನು ಗಮನಿಸಲು ಬಯಸುತ್ತೇನೆ, ಆದ್ದರಿಂದ ಕ್ಲಾಂಗ್ ಮುಂಭಾಗವು ಕಾರ್ಯಾಚರಣೆಗೆ ಫ್ಲ್ಯಾಗ್ ಅನ್ನು ಸೇರಿಸುತ್ತದೆ 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

SSA ರೂಪದಲ್ಲಿ ಕೋಡ್ ಅನ್ನು ಪ್ರತಿನಿಧಿಸುವ ವಿಶಿಷ್ಟವಾದ ಹೆಚ್ಚಿನ ನಿರ್ಮಾಣಗಳನ್ನು ನೀವು ಈಗಾಗಲೇ ಇಲ್ಲಿ ನೋಡಬಹುದು. ಬಹುಶಃ ಈ ಕೋಡ್‌ನ ಅತ್ಯಂತ ಸ್ಪಷ್ಟವಾದ ವೈಶಿಷ್ಟ್ಯವೆಂದರೆ ಯಾವುದೇ ರಚನಾತ್ಮಕ ಹರಿವಿನ ನಿಯಂತ್ರಣ ಆಜ್ಞೆಗಳಿಲ್ಲ. ಲೆಕ್ಕಾಚಾರಗಳ ಹರಿವನ್ನು ನಿಯಂತ್ರಿಸಲು, ಷರತ್ತುಬದ್ಧ ಮತ್ತು ಬೇಷರತ್ತಾದ ಜಿಗಿತಗಳು ಮಾತ್ರ ಇವೆ, ಮತ್ತು, ನಾವು ಈ ಆಜ್ಞೆಯನ್ನು ಹರಿವನ್ನು ನಿಯಂತ್ರಿಸುವ ಆಜ್ಞೆಯಂತೆ ಪರಿಗಣಿಸಿದರೆ, ರಿಟರ್ನ್ ಆಜ್ಞೆ.

ವಾಸ್ತವವಾಗಿ, ಕರ್ಲಿ ಬ್ರೇಸ್‌ಗಳನ್ನು ಬಳಸಿಕೊಂಡು ಪ್ರೋಗ್ರಾಂ ಅನ್ನು ಬ್ಲಾಕ್‌ಗಳಾಗಿ ವಿಂಗಡಿಸಲಾಗಿಲ್ಲ (ಭಾಷೆಗಳ ಸಿ ಕುಟುಂಬದಲ್ಲಿರುವಂತೆ) ಇಲ್ಲಿ ನೀವು ಗಮನ ಹರಿಸಬಹುದು. ಇದನ್ನು ಲೇಬಲ್‌ಗಳಿಂದ ವಿಂಗಡಿಸಲಾಗಿದೆ, ಅಸೆಂಬ್ಲಿ ಭಾಷೆಗಳನ್ನು ನೆನಪಿಸುತ್ತದೆ ಮತ್ತು ಮೂಲ ಬ್ಲಾಕ್‌ಗಳ ರೂಪದಲ್ಲಿ ಪ್ರಸ್ತುತಪಡಿಸಲಾಗುತ್ತದೆ. SSA ಯಲ್ಲಿ, ಮೂಲಭೂತ ಬ್ಲಾಕ್‌ಗಳನ್ನು ಲೇಬಲ್‌ನಿಂದ ಪ್ರಾರಂಭಿಸಿ ಮತ್ತು ಮೂಲಭೂತ ಬ್ಲಾಕ್ ಪೂರ್ಣಗೊಳಿಸುವಿಕೆ ಸೂಚನೆಗಳೊಂದಿಗೆ ಕೊನೆಗೊಳ್ಳುವ ಕೋಡ್‌ನ ಪಕ್ಕದ ಅನುಕ್ರಮಗಳಾಗಿ ವ್ಯಾಖ್ಯಾನಿಸಲಾಗಿದೆ, ಉದಾಹರಣೆಗೆ - 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 ಈ ಬ್ಲಾಕ್ನಿಂದ. ಇದೆಲ್ಲವೂ ನಿಗೂಢವಾಗಿ ಕಾಣಿಸಬಹುದು, ಆದರೆ ಈ ಕಾರ್ಯವಿಧಾನವೇ SSA ಕೆಲಸ ಮಾಡುತ್ತದೆ. ಮಾನವ ದೃಷ್ಟಿಕೋನದಿಂದ, ಇವೆಲ್ಲವೂ ಕೋಡ್ ಅನ್ನು ಅರ್ಥಮಾಡಿಕೊಳ್ಳಲು ಕಷ್ಟಕರವಾಗಿಸುತ್ತದೆ, ಆದರೆ ಪ್ರತಿ ಮೌಲ್ಯವನ್ನು ಒಮ್ಮೆ ಮಾತ್ರ ನಿಗದಿಪಡಿಸಲಾಗಿದೆ ಎಂಬ ಅಂಶವು ಅನೇಕ ಆಪ್ಟಿಮೈಸೇಶನ್‌ಗಳನ್ನು ಹೆಚ್ಚು ಸುಲಭಗೊಳಿಸುತ್ತದೆ.

ನಿಮ್ಮ ಸ್ವಂತ ಕಂಪೈಲರ್ ಅನ್ನು ನೀವು ಬರೆದರೆ, ನೀವು ಸಾಮಾನ್ಯವಾಗಿ ಈ ರೀತಿಯ ವಿಷಯವನ್ನು ಎದುರಿಸಬೇಕಾಗಿಲ್ಲ. ಖಣಿಲು ಕೂಡ ಈ ಎಲ್ಲಾ ಸೂಚನೆಗಳನ್ನು ರಚಿಸುವುದಿಲ್ಲ phi, ಇದು ಯಾಂತ್ರಿಕ ವ್ಯವಸ್ಥೆಯನ್ನು ಬಳಸುತ್ತದೆ alloca (ಇದು ಸಾಮಾನ್ಯ ಸ್ಥಳೀಯ ಅಸ್ಥಿರಗಳೊಂದಿಗೆ ಕೆಲಸ ಮಾಡಲು ಹೋಲುತ್ತದೆ). ನಂತರ, LLVM ಆಪ್ಟಿಮೈಸೇಶನ್ ಪಾಸ್ ಅನ್ನು ಚಾಲನೆ ಮಾಡುವಾಗ ಕರೆಯಲಾಗುತ್ತದೆ mem2reg, ಸೂಚನೆಗಳು alloca SSA ರೂಪಕ್ಕೆ ಪರಿವರ್ತಿಸಲಾಗಿದೆ. 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 ಎಂದು ಸಂಕ್ಷಿಪ್ತಗೊಳಿಸಲಾಗಿದೆ).

ಈ ಸೂಚನೆಯು ಪಾಯಿಂಟರ್‌ಗಳೊಂದಿಗೆ ಕಾರ್ಯನಿರ್ವಹಿಸುತ್ತದೆ ಮತ್ತು ಸ್ಲೈಸ್ ಅಂಶಕ್ಕೆ ಪಾಯಿಂಟರ್ ಅನ್ನು ಪಡೆಯಲು ಬಳಸಲಾಗುತ್ತದೆ. ಉದಾಹರಣೆಗೆ, ಇದನ್ನು 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, ಇದು ಇದೇ ರೀತಿಯಲ್ಲಿ ಕಾರ್ಯನಿರ್ವಹಿಸುತ್ತದೆ.

ಫಲಿತಾಂಶಗಳು

ಈ ವಸ್ತುವಿನಲ್ಲಿ ನಾನು LLVM IR ನ ಪ್ರಮುಖ ವೈಶಿಷ್ಟ್ಯಗಳನ್ನು ಒಳಗೊಂಡಿದೆ ಎಂದು ನಾನು ನಂಬುತ್ತೇನೆ. ಸಹಜವಾಗಿ, ಇಲ್ಲಿ ಇನ್ನೂ ಬಹಳಷ್ಟು ಇದೆ. ನಿರ್ದಿಷ್ಟವಾಗಿ ಹೇಳುವುದಾದರೆ, ಕೋಡ್‌ನ ಮಧ್ಯಂತರ ಪ್ರಾತಿನಿಧ್ಯವು ಅನೇಕ ಟಿಪ್ಪಣಿಗಳನ್ನು ಹೊಂದಿರಬಹುದು, ಅದು ಆಪ್ಟಿಮೈಸೇಶನ್ ಪಾಸ್‌ಗಳನ್ನು ಕಂಪೈಲರ್‌ಗೆ ತಿಳಿದಿರುವ ಕೋಡ್‌ನ ಕೆಲವು ವೈಶಿಷ್ಟ್ಯಗಳನ್ನು ಗಣನೆಗೆ ತೆಗೆದುಕೊಳ್ಳಲು ಅನುವು ಮಾಡಿಕೊಡುತ್ತದೆ, ಅದನ್ನು IR ನಲ್ಲಿ ವ್ಯಕ್ತಪಡಿಸಲಾಗುವುದಿಲ್ಲ. ಉದಾಹರಣೆಗೆ, ಇದು ಧ್ವಜ inbounds GEP ಸೂಚನೆಗಳು, ಅಥವಾ ಧ್ವಜಗಳು nsw и nuw, ಇದನ್ನು ಸೂಚನೆಗಳಿಗೆ ಸೇರಿಸಬಹುದು add. ಕೀವರ್ಡ್‌ಗೆ ಅದೇ ಹೋಗುತ್ತದೆ private, ಇದು ಗುರುತಿಸುವ ಕಾರ್ಯವನ್ನು ಪ್ರಸ್ತುತ ಸಂಕಲನ ಘಟಕದ ಹೊರಗಿನಿಂದ ಉಲ್ಲೇಖಿಸಲಾಗುವುದಿಲ್ಲ ಎಂದು ಆಪ್ಟಿಮೈಜರ್‌ಗೆ ಸೂಚಿಸುತ್ತದೆ. ಬಳಕೆಯಾಗದ ಆರ್ಗ್ಯುಮೆಂಟ್‌ಗಳನ್ನು ತೆಗೆದುಹಾಕುವಂತಹ ಸಾಕಷ್ಟು ಆಸಕ್ತಿದಾಯಕ ಇಂಟರ್‌ಪ್ರೊಸೆಡರಲ್ ಆಪ್ಟಿಮೈಸೇಶನ್‌ಗಳಿಗೆ ಇದು ಅನುಮತಿಸುತ್ತದೆ.

ನೀವು LLVM ಬಗ್ಗೆ ಇನ್ನಷ್ಟು ಓದಬಹುದು ದಸ್ತಾವೇಜನ್ನು, ನಿಮ್ಮ ಸ್ವಂತ LLVM-ಆಧಾರಿತ ಕಂಪೈಲರ್ ಅನ್ನು ಅಭಿವೃದ್ಧಿಪಡಿಸುವಾಗ ನೀವು ಆಗಾಗ್ಗೆ ಉಲ್ಲೇಖಿಸುವಿರಿ. ಇಲ್ಲಿ ನಾಯಕತ್ವ, ಇದು ಅತ್ಯಂತ ಸರಳವಾದ ಭಾಷೆಗಾಗಿ ಕಂಪೈಲರ್ ಅನ್ನು ಅಭಿವೃದ್ಧಿಪಡಿಸುವುದನ್ನು ನೋಡುತ್ತದೆ. ನಿಮ್ಮ ಸ್ವಂತ ಕಂಪೈಲರ್ ಅನ್ನು ರಚಿಸುವಾಗ ಈ ಎರಡೂ ಮಾಹಿತಿಯ ಮೂಲಗಳು ನಿಮಗೆ ಉಪಯುಕ್ತವಾಗುತ್ತವೆ.

ಆತ್ಮೀಯ ಓದುಗರು! ನೀವು LLVM ಬಳಸುತ್ತಿರುವಿರಾ?

ಗೋ ದೃಷ್ಟಿಕೋನದಿಂದ LLVM

ಮೂಲ: www.habr.com

ಕಾಮೆಂಟ್ ಅನ್ನು ಸೇರಿಸಿ