فرآیند گردآوری – گردآوری – جامعه dev

عکس پوشش توسط برنارد هرمان در Unsplash
در آخرین پست وبلاگ ، ما مرحله پیش پردازش را پوشش دادیم. اکنون وقت آن است که وارد مرحله تدوین شوید.
تدوین چیست؟
تدوین فرآیند تبدیل کد منبع از پیش پردازش شده (در مورد ما ، کد C یا C ++) به کد مونتاژ است. این مرحله توسط کامپایلر انجام می شود ، که قبل از تولید کد سطح پایین ، چک ها و بهینه سازی های مختلفی را انجام می دهد.
بر خلاف مرحله پیش پردازش ، که صرفاً کد را از نظر متنی اصلاح می کند ، تدوین شامل تجزیه و تحلیل عمیق تر و تحول است.
توجه: برای متوقف کردن فرآیند تدوین بعد از مرحله تدوین ، از پرچم -s استفاده کنید:
gcc/clang -S main.c -o main.s
بیایید این را با یک مثال کشف کنیم:
foo.h
int foo(void);
دست
#include "foo.h"
int square(int i) { return i * i; }
int main() {
int a = 5;
int f = foo();
return f + square(a);
}
مراحل اصلی تدوین
1. تجزیه و تحلیل واژگانی (توکینیزاسیون)
کد از پیش پردازش شده به نشانه ها تقسیم می شود. به عنوان مثال ، خط:
int a = 5;
در: int
با a
با =
با 5
وت ;
این مرحله فضای سفید را از بین می برد و نمادهای ناشناخته را تشخیص می دهد. به عنوان مثال:
int a = 5 ;
هنوز هم با همان نشانه ها به پایان می رسد int
با a
با =
با 5
وت ;
با این حال ، این باعث خطایی خواهد شد:int a = 5 @ 3;
error: expected ';' at end of declaration
6 | int a = 5 @3;
| ^
|
2. تجزیه و تحلیل نحو (تجزیه)
-
توکن ها بر اساس قوانین دستور زبان برای تشکیل یک درخت نحوی انتزاعی (AST) گروه بندی می شوند.
-
AST نمایانگر ساختار نحوی سلسله مراتبی کد است ، و جزئیات غیر ضروری مانند قسمتهای نیمه یا پرانتز را انتزاع می کند.
-
این مرحله تضمین می کند که ساختار کد از نحو زبان پیروی می کند.
مثال:int a = 5 // Missing the ;
خروجی:
error: expected ';' at end of declaration
6 | int a = 5 // Missing the ;
| ^
|
3. تجزیه و تحلیل معنایی
این فاز صحت منطقی را بررسی می کند:
- تضمین می کند که متغیرها قبل از استفاده اعلام می شوند.
int main() {
// int a = 5;
int f = foo();
return f + square(a);
}
error: use of undeclared identifier 'a'
8 | return f + square(a);
| ^
- سازگاری نوع را تضمین می کند (به عنوان مثال ، هیچ رشته ای به یک عدد صحیح اختصاص نمی دهد).
int a = "hello";
خطا:
error: incompatible pointer to integer conversion initializing 'int' with an expression of type 'char[6]' [-Wint-conversion]
6 | int a = "hello";
| ^ ~~~~~~~
4. IR – نمایندگی متوسط
-
کامپایلر AST را به یک بازنمایی میانی (IR) تبدیل می کند که مستقل از سکوی است.
-
IR به عنوان پلی بین کد منبع سطح بالا و کد دستگاه سطح پایین عمل می کند و بهینه سازی را امکان پذیر می کند.
در پست های آینده ، ما این موضوع را به طور عمیق و همراه با بهینه سازی های مختلف پوشش خواهیم داد. در حال حاضر ، بیایید ببینیم که چگونه به کامپایلر دستور می دهیم این فرم را منتشر کند.
برای تولید LLVM IR با استفاده از clang:
clang main.c -S -emit-llvm -o main.ll`
نسخه بهینه شده:
; ModuleID = 'main.c'
source_filename = "main.c"
target datalayout = "e-m:o-i64:64-i128:128-n32:64-S128-Fn32"
target triple = "arm64-apple-macosx14.0.0"
; Function Attrs: noinline nounwind optnone ssp uwtable(sync)
define i32 @square(i32 noundef %0) #0 {
%2 = alloca i32, align 4
store i32 %0, ptr %2, align 4
%3 = load i32, ptr %2, align 4
%4 = load i32, ptr %2, align 4
%5 = mul nsw i32 %3, %4
ret i32 %5
}
; Function Attrs: noinline nounwind optnone ssp uwtable(sync)
define i32 @main() #0 {
%1 = alloca i32, align 4
%2 = alloca i32, align 4
%3 = alloca i32, align 4
store i32 0, ptr %1, align 4
store i32 5, ptr %2, align 4
%4 = call i32 @foo()
store i32 %4, ptr %3, align 4
%5 = load i32, ptr %3, align 4
%6 = load i32, ptr %2, align 4
%7 = call i32 @square(i32 noundef %6)
%8 = add nsw i32 %5, %7
ret i32 %8
}
declare i32 @foo() #1
attributes #0 = {...}
attributes #1 = {...}
...
...
بهینه سازی کامپایلر
کامپایلر قبل از تولید کد مونتاژ IR را بهینه می کند. بهینه سازی های مشترک شامل موارد زیر است:
- عملکردی
- حذف کد مرده
- حلقه دور زدن
- حرکت کد ثابت حلقه
- توابع ادغام
- و غیره …
ما این موارد را در پست های آینده کشف خواهیم کرد.
نتیجه نهایی ، مونتاژ ARM64
لینوکس
.text
.file "main.c"
.globl square // -- Begin function square
.p2align 2
.type square,@function
square: // @square
.cfi_startproc
// %bb.0:
sub sp, sp, #16
.cfi_def_cfa_offset 16
str w0, [sp, #12]
ldr w8, [sp, #12]
ldr w9, [sp, #12]
mul w0, w8, w9
add sp, sp, #16
.cfi_def_cfa_offset 0
ret
.Lfunc_end0:
.size square, .Lfunc_end0-square
.cfi_endproc
// -- End function
.globl main // -- Begin function main
.p2align 2
.type main,@function
main: // @main
.cfi_startproc
// %bb.0:
sub sp, sp, #32
.cfi_def_cfa_offset 32
stp x29, x30, [sp, #16] // 16-byte Folded Spill
add x29, sp, #16
.cfi_def_cfa w29, 16
.cfi_offset w30, -8
.cfi_offset w29, -16
stur wzr, [x29, #-4]
mov w8, #5 // =0x5
str w8, [sp, #8]
bl foo
str w0, [sp, #4]
ldr w8, [sp, #4]
str w8, [sp] // 4-byte Folded Spill
ldr w0, [sp, #8]
bl square
ldr w8, [sp] // 4-byte Folded Reload
add w0, w8, w0
.cfi_def_cfa wsp, 32
ldp x29, x30, [sp, #16] // 16-byte Folded Reload
add sp, sp, #32
.cfi_def_cfa_offset 0
.cfi_restore w30
.cfi_restore w29
ret
.Lfunc_end1:
.size main, .Lfunc_end1-main
.cfi_endproc
// -- End function
.ident "Ubuntu clang version 18.1.3 (1ubuntu1)"
.section ".note.GNU-stack","",@progbits
.addrsig
.addrsig_sym square
.addrsig_sym foo
MACOS:
.section __TEXT,__text,regular,pure_instructions
.build_version macos, 14, 0 sdk_version 14, 4
.globl _square ; -- Begin function square
.p2align 2
_square: ; @square
.cfi_startproc
; %bb.0:
sub sp, sp, #16
.cfi_def_cfa_offset 16
str w0, [sp, #12]
ldr w8, [sp, #12]
ldr w9, [sp, #12]
mul w0, w8, w9
add sp, sp, #16
ret
.cfi_endproc
; -- End function
.globl _main ; -- Begin function main
.p2align 2
_main: ; @main
.cfi_startproc
; %bb.0:
sub sp, sp, #32
stp x29, x30, [sp, #16] ; 16-byte Folded Spill
add x29, sp, #16
.cfi_def_cfa w29, 16
.cfi_offset w30, -8
.cfi_offset w29, -16
stur wzr, [x29, #-4]
mov w8, #5 ; =0x5
str w8, [sp, #8]
bl _foo
str w0, [sp, #4]
ldr w8, [sp, #4]
str w8, [sp] ; 4-byte Folded Spill
ldr w0, [sp, #8]
bl _square
ldr w8, [sp] ; 4-byte Folded Reload
add w0, w8, w0
ldp x29, x30, [sp, #16] ; 16-byte Folded Reload
add sp, sp, #32
ret
.cfi_endproc
; -- End function
.subsections_via_symbols
این برای این پست است! در مورد بعدی ، ما آن را پوشش خواهیم داد مونتاژ کننده فاز