Khi nào nó có ý nghĩa để biên dịch ngôn ngữ của riêng tôi thành mã C trước?


34

Khi thiết kế một ngôn ngữ lập trình riêng, khi nào thì nên viết một trình chuyển đổi lấy mã nguồn và chuyển đổi nó thành mã C hoặc C ++ để tôi có thể sử dụng một trình biên dịch hiện có như gcc để kết thúc với mã máy? Có dự án sử dụng phương pháp này?



4
Nếu bạn nhìn qua C, bạn sẽ thấy C # và Java cũng biên dịch sang các ngôn ngữ trung gian. Bạn đã được cứu khỏi việc phải làm lại rất nhiều công việc mà người khác đã thực hiện bằng cách nhắm mục tiêu một ngôn ngữ trung gian thay vì đi thẳng vào lắp ráp.
Casey

1
@emodendroket Tuy nhiên, C # và Java biên dịch thành IL được thiết kế để trở thành một IL nói chung và cụ thể là C # / Java, do đó, theo nhiều cách, CIL và JVM bytecode hợp lý và thuận tiện hơn như IL so với C. Đó không phải là về việc có nên sử dụng bất kỳ ngôn ngữ trung gian nào không, đó là về việc sử dụng ngôn ngữ trung gian nào.

1
Nhìn vào một số triển khai phần mềm miễn phí tạo mã C. Và tôi hy vọng bạn sẽ làm cho ngôn ngữ của bạn thực hiện phần mềm miễn phí.
Basile Starynkevitch

2
Đây là liên kết được cập nhật từ bình luận của @ RobertHarvey: yosefk.com/blog/c-as-an-inter liền-lingu.html .
Christian Dean

Câu trả lời:


52

Chuyển sang mã C là một thói quen được thiết lập rất tốt. C ban đầu với các lớp (và các triển khai C ++ đầu tiên, sau đó được gọi là Cfront ) đã thực hiện thành công. Một số triển khai của Lisp hoặc Scheme đang làm điều đó, ví dụ Chicken Scheme , Scheme48 , Bigloo . Một số người dịch Prolog với C . Và một số phiên bản của Mozart cũng vậy (và đã có những nỗ lực biên dịch mã byte Ocaml thành C ). Hệ thống CAIA trí tuệ nhân tạo của J.Pitrat cũng được khởi động và tạo ra tất cả mã C của nó. Vala cũng dịch sang C, cho mã liên quan đến GTK. Cuốn sách của Queinnec Lisp In Pieces có một số chương về dịch sang C.

Một trong những vấn đề khi dịch sang C là các cuộc gọi đệ quy đuôi . Chuẩn C không đảm bảo rằng trình biên dịch C đang dịch chúng đúng (sang "nhảy với đối số", tức là không ăn ngăn xếp cuộc gọi), ngay cả trong một số trường hợp, các phiên bản gần đây của GCC (hoặc Clang / LLVM) thực hiện tối ưu hóa đó .

Một vấn đề khác là thu gom rác . Một số triển khai chỉ sử dụng trình thu gom rác bảo thủ Boehm ( thân thiện với C ...). Nếu bạn muốn dọn rác thu thập mã (như một số triển khai Lisp làm, ví dụ SBCL) có thể là một cơn ác mộng (bạn muốn dlclosetrên Posix).

Tuy nhiên, một vấn đề khác là xử lý các phần tiếp theo hạng nhất và cuộc gọi / cc . Nhưng thủ thuật thông minh là có thể (nhìn vào bên trong Lược đồ gà). Truy cập ngăn xếp cuộc gọi có thể đòi hỏi rất nhiều thủ thuật (nhưng xem phần sau của GNU , v.v ....). Sự tồn tại trực giao của các phần tiếp theo (nghĩa là ngăn xếp hoặc luồng) sẽ khó khăn trong C.

Xử lý ngoại lệ thường là một vấn đề để phát ra các cuộc gọi thông minh đến longjmp, v.v ...

Bạn có thể muốn tạo (trong mã C được phát ra) của mình #line. Điều này thật nhàm chán và tốn nhiều công sức (bạn sẽ muốn điều đó ví dụ như sản xuất gdbmã dễ dàng hơn ).

My MELT lispy ngôn ngữ miền cụ thể (để tùy chỉnh hoặc mở rộng GCC ) được phiên dịch sang C (trên thực tế để C ++ nghèo bây giờ). Nó có bộ thu gom rác sao chép thế hệ riêng. (Bạn có thể quan tâm bởi Qish hoặc Ravenbrook MPS ). Trên thực tế, GC thế hệ dễ dàng hơn trong mã C được tạo bằng máy so với mã C viết tay (vì bạn sẽ điều chỉnh trình tạo mã C cho rào cản ghi và máy móc của bạn).

Tôi không biết bất kỳ triển khai ngôn ngữ nào dịch sang mã C ++ chính hãng , tức là sử dụng một số kỹ thuật "thu gom rác thời gian biên dịch" để phát ra mã C ++ bằng cách sử dụng nhiều mẫu STL và tôn trọng thành ngữ RAII . (xin vui lòng cho biết nếu bạn biết một).

Điều thú vị ngày nay là (trên máy tính để bàn Linux hiện tại) Trình biên dịch C có thể đủ nhanh để thực hiện một vòng lặp đọc-in-in-vòng-in tương tác được dịch sang C: bạn sẽ phát ra mã C (vài trăm dòng) cho mỗi người dùng tương tác, bạn sẽ forkbiên dịch nó thành một đối tượng chia sẻ, sau đó bạn sẽ thực hiện dlopen. (MELT đang làm điều đó tất cả đã sẵn sàng, và nó thường đủ nhanh). Tất cả điều này có thể mất vài phần mười giây và được người dùng cuối chấp nhận.

Khi có thể, tôi khuyên bạn nên dịch sang C, chứ không phải C ++, đặc biệt vì quá trình biên dịch C ++ chậm.

Nếu bạn đang triển khai ngôn ngữ của mình, bạn cũng có thể xem xét (thay vì phát ra mã C) một số thư viện JIT như libjit , GNU sét , asmjit hoặc thậm chí LLVM hoặc GCCJIT . Nếu bạn muốn dịch sang C, đôi khi bạn có thể sử dụng tinycc : nó biên dịch rất nhanh mã C được tạo (ngay cả trong bộ nhớ) để làm chậm mã máy. Nhưng nói chung, bạn muốn tận dụng tối ưu hóa được thực hiện bởi trình biên dịch C thực sự như GCC

Nếu bạn dịch sang C ngôn ngữ của bạn, trước tiên hãy chắc chắn xây dựng toàn bộ AST của mã C được tạo trong bộ nhớ (điều này cũng giúp tạo ra tất cả các khai báo trước, sau đó là tất cả các định nghĩa và mã chức năng). Bạn sẽ có thể thực hiện một số tối ưu hóa / chuẩn hóa theo cách này. Ngoài ra, bạn có thể quan tâm đến một số tiện ích mở rộng GCC (ví dụ: gotos được tính toán). Có lẽ bạn sẽ muốn tránh tạo ra các hàm C khổng lồ - ví dụ như một trăm ngàn dòng C được tạo - (tốt hơn là bạn nên chia chúng thành các phần nhỏ hơn) vì tối ưu hóa trình biên dịch C rất không hài lòng với các hàm C rất lớn (trong thực tế và thực nghiệmgcc -Othời gian biên dịch của các hàm lớn tỷ lệ với bình phương kích thước mã hàm). Vì vậy, giới hạn kích thước của các hàm C được tạo của bạn ở mức vài nghìn dòng mỗi hàm.

Lưu ý rằng cả trình biên dịch C & C ++ của Clang (thru LLVM ) và GCC (thru libgccjit ) cung cấp một số cách để phát ra một số biểu diễn bên trong phù hợp với các trình biên dịch này, nhưng làm như vậy có thể (hoặc không) khó hơn phát ra mã C (hoặc C ++), và là cụ thể cho từng trình biên dịch.

Nếu thiết kế một ngôn ngữ được dịch sang C, có lẽ bạn muốn có một số thủ thuật (hoặc cấu trúc) để tạo ra một hỗn hợp C với ngôn ngữ của bạn. Giấy DSL2011 của tôi MELT : Ngôn ngữ cụ thể miền được dịch được nhúng trong Trình biên dịch GCC sẽ cung cấp cho bạn các gợi ý hữu ích.


Bạn đang đề cập đến "Đề án gà?"
Robert Harvey

1
Có. Tôi đã cho URL.
Basile Starynkevitch

Có phải là tương đối thực tế để tạo một máy ảo, như Java hoặc một cái gì đó, biên dịch mã byte thành C, sau đó sử dụng gcc để biên dịch JIT? Hay họ chỉ nên đi thẳng từ mã byte để lắp ráp?
Panzercrisis

1
@Panzercrisis Hầu hết các trình biên dịch JIT yêu cầu phụ trợ mã máy của họ để hỗ trợ những thứ như thay thế một chức năng và vá mã hiện có bằng một cửa nhảy / bẫy. Bên cạnh đó, gcc đặc biệt là ... về mặt kiến ​​trúc ít phù hợp với quá trình biên dịch JIT và các trường hợp sử dụng khác. Hãy xem libgccjit mặc dù: gcc.gnu.org/ml/gcc-patches/2013-10/msg00228.htmlgcc.gnu.org/wiki/JIT

1
Vật liệu định hướng tuyệt vời. Cảm ơn!
capr

7

Thật có ý nghĩa khi thời gian để tạo mã máy đầy đủ vượt xa sự bất tiện khi có một bước trung gian biên dịch "IL" của bạn thành mã máy bằng trình biên dịch C.

Thông thường các ngôn ngữ dành riêng cho tên miền được viết theo cách này, một hệ thống cấp độ rất cao được sử dụng để xác định hoặc mô tả một quy trình sau đó được biên dịch thành một tệp thực thi hoặc dll. Thời gian để tạo ra công việc / lắp ráp tốt lớn hơn nhiều so với việc tạo C và C khá gần với mã lắp ráp để thực hiện, do đó, việc tạo C và sử dụng lại các kỹ năng của các trình biên dịch C là rất hợp lý. Lưu ý rằng nó không chỉ là biên dịch, mà còn tối ưu hóa - những người viết gcc hoặc llvm đã dành rất nhiều thời gian để tạo mã máy được tối ưu hóa, sẽ rất cố gắng để phát minh lại tất cả công việc khó khăn của họ.

Có thể sẽ dễ chấp nhận hơn khi sử dụng lại phụ trợ trình biên dịch của LLVM mà IIRC là ngôn ngữ trung lập, do đó bạn tạo các lệnh LLVM thay vì mã C.


Có vẻ như các thư viện là một lý do khá thuyết phục để xem xét nó quá.
Casey

Khi bạn nói "IL 'của bạn", bạn đang đề cập đến điều gì? Một cây cú pháp trừu tượng?
Robert Harvey

@RobertHarvey không, ý tôi là mã C. Trong trường hợp OP, đây là một ngôn ngữ trung gian nằm giữa ngôn ngữ cấp cao và mã máy của chính mình. Tôi đặt nó trong dấu ngoặc kép để thử và truyền đạt ý tưởng này rằng nó không phải là IL được sử dụng bởi nhiều người (ví dụ như .NET IL của Microsoft)
gbjbaanb

2

Viết một trình biên dịch để tạo mã máy có thể không khó hơn nhiều so với viết một trình biên dịch tạo ra C (trong một số trường hợp có thể dễ dàng hơn), nhưng một trình biên dịch tạo mã máy sẽ chỉ có thể tạo các chương trình có thể chạy trên nền tảng cụ thể nó được viết; một trình biên dịch tạo mã C, ngược lại, có thể tạo chương trình cho bất kỳ nền tảng nào sử dụng phương ngữ C mà mã được tạo được thiết kế để hỗ trợ. Lưu ý rằng trong nhiều trường hợp, có thể viết mã C hoàn toàn di động và sẽ hoạt động như mong muốn mà không sử dụng bất kỳ hành vi nào không được đảm bảo theo tiêu chuẩn C, nhưng mã dựa trên các hành vi được bảo đảm nền tảng có thể chạy nhanh hơn nhiều trên các nền tảng tạo ra những đảm bảo hơn mã không.

Ví dụ: giả sử một ngôn ngữ hỗ trợ một tính năng để tạo ra một UInt32từ bốn byte liên tiếp của một liên kết tùy ý UInt8[], được diễn giải theo kiểu cuối lớn. Trên một số trình biên dịch, người ta có thể viết mã dưới dạng:

uint32_t dat = *(__packed uint32_t*)p;
return (dat >> 24) | (dat >> 8) | ((uint32_t)dat << 8) | ((uint32_t)dat << 24));

và có trình biên dịch tạo ra một hoạt động tải từ theo sau là một lệnh đảo ngược byte-in-word. Tuy nhiên, một số trình biên dịch sẽ không hỗ trợ công cụ sửa đổi __packed và nếu không có nó sẽ tạo ra mã không hoạt động.

Ngoài ra, người ta có thể viết mã dưới dạng:

return dat[3] | ((uint16_t)dat[2] << 8) | ((uint32_t)dat[1] << 16) | ((uint32_t)dat[0] << 24);

một mã như vậy sẽ hoạt động trên bất kỳ nền tảng nào, ngay cả những mã CHAR_BITSkhông 8 (giả sử rằng mỗi octet dữ liệu nguồn kết thúc trong một phần tử mảng riêng biệt), nhưng mã đó có thể không chạy nhanh như tốc độ không di động phiên bản trên nền tảng hỗ trợ trước đây.

Lưu ý rằng tính di động thường yêu cầu mã phải cực kỳ tự do với các kiểu chữ và cấu trúc tương tự. Ví dụ, mã muốn nhân hai số nguyên không dấu 32 bit và mang lại 32 bit thấp hơn của kết quả phải có tính di động được viết là:

uint32_t result = 1u*x*y;

Không có điều đó 1u, trình biên dịch trên hệ thống có INT_BITS nằm trong khoảng từ 33 đến 64 có thể làm bất cứ điều gì nó muốn nếu sản phẩm của x và y lớn hơn 2.147.483.647 và một số trình biên dịch có xu hướng tận dụng các cơ hội đó.


1

Bạn có một số câu trả lời xuất sắc ở trên nhưng trong một nhận xét, bạn đã trả lời câu hỏi "Tại sao bạn muốn tạo một ngôn ngữ lập trình của riêng bạn ngay từ đầu?" Với "Nó chủ yếu dành cho mục đích học tập," Tôi ' m sẽ trả lời từ một góc độ khác nhau.

Thật hợp lý khi viết một trình chuyển đổi lấy mã nguồn và chuyển đổi nó thành mã C hoặc C ++, để bạn có thể sử dụng một trình biên dịch hiện có như gcc để kết thúc với mã máy, nếu bạn quan tâm hơn đến việc tìm hiểu về từ vựng, cú pháp và phân tích ngữ nghĩa hơn bạn đang tìm hiểu về việc tạo và tối ưu hóa mã!

Viết trình tạo mã máy của riêng bạn là một công việc khá quan trọng mà bạn có thể tránh bằng cách biên dịch thành mã C, nếu đó không phải là điều bạn chủ yếu quan tâm!

Tuy nhiên, nếu bạn đang tham gia chương trình lắp ráp và bị mê hoặc bởi những thách thức của việc tối ưu hóa mã ở mức thấp nhất, thì bằng mọi cách, hãy tự mình viết một trình tạo mã cho trải nghiệm học tập!


-7

Nó phụ thuộc vào Hệ điều hành bạn đang sử dụng nếu bạn đang sử dụng Windows, có Microsoft IL (Ngôn ngữ trung gian) Chuyển đổi mã của bạn thành ngôn ngữ trung gian để không mất thời gian để biên dịch thành mã máy. Hoặc nếu bạn đang sử dụng Linux, có một trình biên dịch riêng cho việc đó

Quay trở lại câu hỏi của bạn là khi bạn thiết kế ngôn ngữ của riêng mình, bạn nên có một trình biên dịch hoặc trình thông dịch riêng cho điều đó vì máy không biết ngôn ngữ cấp cao. Mã của bạn nên được biên dịch thành mã máy để làm cho nó hữu ích cho máy


2
Your code should be compiled into machine code to make it useful for machine- Nếu trình biên dịch của bạn tạo mã c làm đầu ra, bạn có thể đặt mã c vào trình biên dịch ac để tạo mã máy, phải không?
Robert Harvey

Vâng. bởi vì máy không phải là ngôn ngữ c
Tayyab Gulsher Vohra

2
Đúng. Vì vậy, câu hỏi là "Khi nào thì có nghĩa là phát ra c và sử dụng trình biên dịch ac, thay vì phát trực tiếp ngôn ngữ máy hoặc mã byte?"
Robert Harvey

thực ra anh ta yêu cầu thiết kế ngôn ngữ lập trình của mình, trong đó anh ta yêu cầu "chuyển đổi nó thành mã C hoặc C ++". Vì vậy, tôi đang giải thích điều này nếu bạn đang thiết kế ngôn ngữ lập trình của riêng bạn tại sao bạn nên sử dụng trình biên dịch c hoặc c ++. Nếu bạn đủ thông minh, bạn nên tự thiết kế
Tayyab Gulsher Vohra

8
Tôi không nghĩ bạn hiểu câu hỏi. Xem yosefk.com/blog/c-as-an-interantly-lingu.html
Robert Harvey
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.