Mở bài

Mình đang quá trình học C++, tò mò nên tìm hiểu lại về các step khi chạy 1 chương trình thì có các steps: - Preprocess - Compile - Translate to assembly code - Translate to machine code - Linking

Mình thử viết 1 chương trình hello world để test các bước trên. Thay vì sử dụng g++ -o index.o index.cpp thì mình sử dụng make file như dưới đây để tìm hiểu về từng bước ở trên.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
all: run

run: link
	./index

link: convert_to_machine_code
	ld -arch x86_64 -macosx_version_min 10.14 -o index index.o

convert_to_machine_code: compile
	as -o index.o index.s

compile: preprocess
	g++ -S index.i -o index.s

preprocess: index.cpp
	c++ -E index.cpp > index.i

clean:
	rm index.i index.s index.o index

Ngoài trừ bước link, các bước khác đều hoạt động như expected. Đến bước link thì bắn ra lỗi sau:

1
2
3
4
5
Undefined symbols for architecture x86_64:
  "__Unwind_Resume", referenced from:
      __ZNSt3__124__put_character_sequenceIcNS_11char_traitsIcEEEERNS_13basic_ostreamIT_T0_EES7_PKS4_m in index.o
      __ZNSt3__116__pad_and_outputIcNS_11char_traitsIcEEEENS_19ostreambuf_iteratorIT_T0_EES6_PKS4_S8_S8_RNS_8ios_baseES4_ in index.o
      __ZNKSt3__19basic_iosIcNS_11char_traitsIcEEE5widenEc in index.o

Clang

Vấn đề

Như thương lệ, search lỗi trên bác Google thì mình đổi phần link trong make file thành như sau thì chạy thành công

1
2
link: convert_to_machine_code
	clang -std=c++11 -stdlib=libc++ -lc++ -m64 -o index index.o

Đến đây mình mới tò mò, là tại sao đổi từ ld sang clang thì lại chạy. ld thì theo mình hiểu là tool để linking rồi, thế thì clang là cái giống gì? Nó khác gì với ld?

Google nhanh với từ khoá clang vs ld. -> Không ra kết quả gì khả thi. Chứng tỏ giả định của mình rằng 2 clang để thay thế cho ld có vẻ không đúng 100% và không bao quát toàn bộ vấn đề.

Tiếp tục 1 hồi lần mò trên internet, từ khái niệm này sang khái niệm khác. Thì cuối cùng dựa vào 2 link ở dưới, mình đã có thể trả lời được câu hỏi Clang là gì?

Đáp án

Ref1 Ref2

Trả lời ngắn gọn thì: Clang là một compiler, tương tự như gcc compiler. Do đó không ngạc nhiên khi mình chuyển từ ld sang clang thì lại build được. Do câu lệnh clang nó đã bao gồm cả quá trình linking rồi.

Điểm khác biệt giữa clanggcc đó chính là clang thì sử dụng LLVM IR (intermediate representation), trong khi đó gcc thì sử dụng GIMPLE Ref. Do đó clang có thể tận dụng được các lợi thế của LLVM để compile ra các platform khác nhau 1 cách dễ dàng. Thậm chí có thể sử dụng LLVM IR để chạy code C/C++ với nearly native perfomance trên web bằng cách sử dụng Emscripten convert LLVM IR sang ngôn ngữ Webassembly và sau đó nhúng vào web.

Minh hoạ vai trò của LLVM

Bởi vì clang sinh ra để thay thế gcc, do đó nó cũng có đủ hết các step của compiler như:

1
2
3
4
5
6
Preprocessor: This performs the actions of the C preprocessor: expanding #includes and #defines. The -E flag instructs Clang to stop after this step.
Parsing: This parses and semantically analyzes the source language and builds a source-level intermediate representation (“AST”), producing a precompiled header (PCH), preamble, or precompiled module file (PCM), depending on the input. The -precompile flag instructs Clang to stop after this step. This is the default when the input is a header file.
IR generation: This converts the source-level intermediate representation into an optimizer-specific intermediate representation (IR); for Clang, this is LLVM IR. The -emit-llvm flag instructs Clang to stop after this step. If combined with -S, Clang will produce textual LLVM IR; otherwise, it will produce LLVM IR bitcode.
Compiler backend: This converts the intermediate representation into target-specific assembly code. The -S flag instructs Clang to stop after this step.
Assembler: This converts target-specific assembly code into target-specific machine code object files. The -c flag instructs Clang to stop after this step.
Linker: This combines multiple object files into a single image (either a shared object or an executable).

Và có thể sử dụng flag -v hoặc -### khi chạy clang để nhìn thấy câu lệnh của các steps trên khi chạy clang. Ví dụ như ở bước Linker, mình thêm -v vào thì sẽ được output như sau:

1
2
3
4
5
6
clang -std=c++11 -stdlib=libc++ -lc++ -m64 -o index index.o -v
Apple LLVM version 10.0.1 (clang-1001.0.46.4)
Target: x86_64-apple-darwin18.5.0
Thread model: posix
InstalledDir: /Library/Developer/CommandLineTools/usr/bin
 "/Library/Developer/CommandLineTools/usr/bin/ld" -demangle -lto_library /Library/Developer/CommandLineTools/usr/lib/libLTO.dylib -dynamic -arch x86_64 -macosx_version_min 10.14.0 -syslibroot /Library/Developer/CommandLineTools/SDKs/MacOSX10.14.sdk -o index -lc++ index.o -L/usr/local/lib -lSystem /Library/Developer/CommandLineTools/usr/lib/clang/10.0.1/lib/darwin/libclang_rt.osx.a

Có thể thấy rõ ràng là clang gọi đến ld behind the scene, nhưng với 1 loạt các tham số. Đây là lý do mà câu lệnh ld ở make file ban đầu của mình không chạy.

Với flag -###, có thể thấy các output của các step còn lại như sau:

1
2
3
4
5
6
7
clang -std=c++11 -stdlib=libc++ -lc++ -m64 -o index index.cpp -###
Apple LLVM version 10.0.1 (clang-1001.0.46.4)
Target: x86_64-apple-darwin18.5.0
Thread model: posix
InstalledDir: /Library/Developer/CommandLineTools/usr/bin
 "/Library/Developer/CommandLineTools/usr/bin/clang" "-cc1" "-triple" "x86_64-apple-macosx10.14.0" "-Wdeprecated-objc-isa-usage" "-Werror=deprecated-objc-isa-usage" "-emit-obj" "-mrelax-all" "-disable-free" "-disable-llvm-verifier" "-discard-value-names" "-main-file-name" "index.cpp" "-mrelocation-model" "pic" "-pic-level" "2" "-mthread-model" "posix" "-mdisable-fp-elim" "-fno-strict-return" "-masm-verbose" "-munwind-tables" "-target-sdk-version=10.14" "-target-cpu" "penryn" "-dwarf-column-info" "-debugger-tuning=lldb" "-target-linker-version" "450.3" "-resource-dir" "/Library/Developer/CommandLineTools/usr/lib/clang/10.0.1" "-isysroot" "/Library/Developer/CommandLineTools/SDKs/MacOSX10.14.sdk" "-I/usr/local/include" "-stdlib=libc++" "-Wno-atomic-implicit-seq-cst" "-Wno-framework-include-private-from-public" "-Wno-atimport-in-framework-header" "-Wno-quoted-include-in-framework-header" "-std=c++11" "-fdeprecated-macro" "-fdebug-compilation-dir" "/Users/hs/Documents/workspace/test" "-ferror-limit" "19" "-fmessage-length" "134" "-stack-protector" "1" "-fblocks" "-fencode-extended-block-signature" "-fregister-global-dtors-with-atexit" "-fobjc-runtime=macosx-10.14.0" "-fcxx-exceptions" "-fexceptions" "-fmax-type-align=16" "-fdiagnostics-show-option" "-fcolor-diagnostics" "-o" "/var/folders/_v/s1ss70n55433z7rhsk2tnd3w0000gn/T/index-3d2ce5.o" "-x" "c++" "index.cpp"
 "/Library/Developer/CommandLineTools/usr/bin/ld" "-demangle" "-lto_library" "/Library/Developer/CommandLineTools/usr/lib/libLTO.dylib" "-no_deduplicate" "-dynamic" "-arch" "x86_64" "-macosx_version_min" "10.14.0" "-syslibroot" "/Library/Developer/CommandLineTools/SDKs/MacOSX10.14.sdk" "-o" "index" "-lc++" "/var/folders/_v/s1ss70n55433z7rhsk2tnd3w0000gn/T/index-3d2ce5.o" "-L/usr/local/lib" "-lSystem" "/Library/Developer/CommandLineTools/usr/lib/clang/10.0.1/lib/darwin/libclang_rt.osx.a"

Kết luận

Thực ra ban đầu bài blog này mình định viết để tìm hiểu về LLVM, nhưng thông qua việc tìm hiểu về Clang, vai trò của nó trong hệ sinh thái của LLVM thì mình cũng hiểu được phần nào về LLVM. Mong là sẽ có 1 lúc nào đó có thời gian để đào sâu hơn và có thể viết ra 1 toy compiler dựa trên LLVM.

Ref