From 3be682bac9b768ae6481ed2a07844ee8ba87a896 Mon Sep 17 00:00:00 2001 From: Evan Haas Date: Sun, 27 Jun 2021 02:25:32 -0700 Subject: [PATCH] translate-c: Add documentation for `zig translate-c` --- doc/docgen.zig | 13 ++++ doc/langref.html.in | 160 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 172 insertions(+), 1 deletion(-) diff --git a/doc/docgen.zig b/doc/docgen.zig index f58c94f15c..ad13275a4c 100644 --- a/doc/docgen.zig +++ b/doc/docgen.zig @@ -286,6 +286,7 @@ const Code = struct { link_libc: bool, link_mode: ?std.builtin.LinkMode, disable_cache: bool, + verbose_cimport: bool, const Id = union(enum) { Test, @@ -536,6 +537,7 @@ fn genToc(allocator: *mem.Allocator, tokenizer: *Tokenizer) !Toc { var link_libc = false; var link_mode: ?std.builtin.LinkMode = null; var disable_cache = false; + var verbose_cimport = false; const source_token = while (true) { const content_tok = try eatToken(tokenizer, Token.Id.Content); @@ -548,6 +550,8 @@ fn genToc(allocator: *mem.Allocator, tokenizer: *Tokenizer) !Toc { mode = .ReleaseSafe; } else if (mem.eql(u8, end_tag_name, "code_disable_cache")) { disable_cache = true; + } else if (mem.eql(u8, end_tag_name, "code_verbose_cimport")) { + verbose_cimport = true; } else if (mem.eql(u8, end_tag_name, "code_link_object")) { _ = try eatToken(tokenizer, Token.Id.Separator); const obj_tok = try eatToken(tokenizer, Token.Id.TagContent); @@ -591,6 +595,7 @@ fn genToc(allocator: *mem.Allocator, tokenizer: *Tokenizer) !Toc { .link_libc = link_libc, .link_mode = link_mode, .disable_cache = disable_cache, + .verbose_cimport = verbose_cimport, }, }); tokenizer.code_node_count += 1; @@ -1127,6 +1132,10 @@ fn genHtml(allocator: *mem.Allocator, tokenizer: *Tokenizer, toc: *Toc, out: any try out.print(" -target {s}", .{triple}); } } + if (code.verbose_cimport) { + try build_args.append("--verbose-cimport"); + try out.print(" --verbose-cimport", .{}); + } if (expected_outcome == .BuildFail) { const result = try ChildProcess.exec(.{ .allocator = allocator, @@ -1213,6 +1222,10 @@ fn genHtml(allocator: *mem.Allocator, tokenizer: *Tokenizer, toc: *Toc, out: any const colored_stderr = try termColor(allocator, escaped_stderr); const colored_stdout = try termColor(allocator, escaped_stdout); + if (code.verbose_cimport) { + const escaped_build_stderr = try escapeHtml(allocator, exec_result.stderr); + try out.print("\n{s}", .{escaped_build_stderr}); + } try out.print("\n$ ./{s}\n{s}{s}", .{ code.name, colored_stdout, colored_stderr }); if (exited_with_signal) { try out.print("(process terminated by signal)", .{}); diff --git a/doc/langref.html.in b/doc/langref.html.in index b447a49ad4..09e4533d18 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -9914,7 +9914,6 @@ lib.addCSourceFile("src/lib.c", &[_][]const u8{ {#see_also|Primitive Types#} {#header_close#} - {#header_open|Import from C Header File#}

The {#syntax#}@cImport{#endsyntax#} builtin function can be used @@ -9954,6 +9953,165 @@ const c = @cImport({ {#see_also|@cImport|@cInclude|@cDefine|@cUndef|@import#} {#header_close#} + {#header_open|C Translation CLI#} + Zig's C translation capability is available as a CLI tool via zig translate-c. + It requires a single filename as an argument. It may also take a set of optional flags that are + forwarded to clang. It writes the translated file to stdout. + {#header_open|Command line flags#} +

+ {#header_close#} + {#header_open|Using -target and -cflags#} +

+ Important! When translating C code with zig translate-c, + you must use the same -target triple that you will use when compiling + the translated code. In addition, you must ensure that the -cflags used, + if any, match the cflags used by code on the target system. Using the incorrect -target + or -cflags could result in clang or Zig parse failures, or subtle ABI incompatibilities + when linking with C code. +

+

varytarget.h

+
long FOO = __LONG_MAX__;
+
$ zig translate-c -target thumb-freestanding-gnueabihf varytarget.h|grep FOO
+pub export var FOO: c_long = 2147483647;
+
$ zig translate-c -target x86_64-macos-gnu varytarget.h|grep FOO
+pub export var FOO: c_long = 9223372036854775807;
+

varycflags.h

+
enum FOO { BAR };
+int do_something(enum FOO foo);
+
$ zig translate-c varycflags.h|grep -B1 do_something
+pub const enum_FOO = c_uint;
+pub extern fn do_something(foo: enum_FOO) c_int;
+
$ zig translate-c -cflags -fshort-enums -- varycflags.h|grep -B1 do_something
+pub const enum_FOO = u8;
+pub extern fn do_something(foo: enum_FOO) c_int;
+ {#header_close#} + {#header_open|@cImport vs translate-c#} +

{#syntax#}@cImport{#endsyntax#} and zig translate-c use the same underlying + C translation functionality, so on a technical level they are equivalent. In practice, + {#syntax#}@cImport{#endsyntax#} is useful as a way to quickly and easily access numeric constants, typedefs, + and record types without needing any extra setup. If you need to pass {#link|cflags|Using -target and -cflags#} + to clang, or if you would like to edit the translated code, it is recommended to use + zig translate-c and save the results to a file. Common reasons for editing + the generated code include: changing {#syntax#}anytype{#endsyntax#} parameters in function-like macros to more + specific types; changing {#syntax#}[*c]T{#endsyntax#} pointers to {#syntax#}[*]T{#endsyntax#} or + {#syntax#}*T{#endsyntax#} pointers for improved type safety; and + {#link|enabling or disabling runtime safety|@setRuntimeSafety#} within specific functions. +

+ {#header_close#} + {#see_also|Targets|C Type Primitives|Pointers|C Pointers|Import from C Header File|@cInclude|@cImport|@setRuntimeSafety#} + {#header_close#} + {#header_open|C Translation Caching#} +

+ The C translation feature (whether used via zig translate-c or + {#syntax#}@cImport{#endsyntax#}) integrates with the Zig caching system. Subsequent runs with + the same source file, target, and cflags will use the cache instead of repeatedly translating + the same code. +

+

+ To see where the cached files are stored when compiling code that uses {#syntax#}@cImport{#endsyntax#}, + use the --verbose-cimport flag: +

+ {#code_begin|exe|verbose#} + {#link_libc#} + {#code_verbose_cimport#} +const c = @cImport({ + @cDefine("_NO_CRT_STDIO_INLINE", "1"); + @cInclude("stdio.h"); +}); +pub fn main() void { + _ = c; +} + {#code_end#} +

+ cimport.h contains the file to translate (constructed from calls to + {#syntax#}@cInclude{#endsyntax#}, {#syntax#}@cDefine{#endsyntax#}, and {#syntax#}@cUndef{#endsyntax#}), + cimport.h.d is the list of file dependencies, and + cimport.zig contains the translated output. +

+ {#see_also|Import from C Header File|C Translation CLI|@cInclude|@cImport#} + {#header_close#} + {#header_open|Translation failures#} +

+ Some C constructs cannot be translated to Zig - for example, goto, + structs with bitfields, and token-pasting macros. Zig employs demotion to allow translation + to continue in the face of non-translateable entities. +

+

+ Demotion comes in three varieties - {#link|opaque#}, extern, and + {#syntax#}@compileError{#endsyntax#}. + + C structs and unions that cannot be translated correctly will be translated as {#syntax#}opaque{}{#endsyntax#}. + Functions that contain opaque types or code constructs that cannot be translated will be demoted + to {#syntax#}extern{#endsyntax#} declarations. + + Thus, non-translateable types can still be used as pointers, and non-translateable functions + can be called so long as the linker is aware of the compiled function. +

+

+ {#syntax#}@compileError{#endsyntax#} is used when top-level definitions (global variables, + function prototypes, macros) cannot be translated or demoted. Since Zig uses lazy analysis for + top-level declarations, untranslateable entities will not cause a compile error in your code unless + you actually use them. +

+ {#see_also|opaque|extern|@compileError#} + {#header_close#} + {#header_open|C Macros#} +

+ C Translation makes a best-effort attempt to translate function-like macros into equivalent + Zig functions. Since C macros operate at the level of lexical tokens, not all C macros + can be translated to Zig. Macros that cannot be translated will be be demoted to + {#syntax#}@compileError{#endsyntax#}. Note that C code which uses macros will be + translated without any additional issues (since Zig operates on the pre-processed source + with macros expanded). It is merely the macros themselves which may not be translateable to + Zig. +

+

Consider the following example:

+

macro.c

+
#define MAKELOCAL(NAME, INIT) int NAME = INIT
+int foo(void) {
+   MAKELOCAL(a, 1);
+   MAKELOCAL(b, 2);
+   return a + b;
+}
+
$ zig translate-c macro.c > macro.zig
+
+

macro.zig

+
{#syntax#}pub export fn foo() c_int {
+    var a: c_int = 1;
+    var b: c_int = 2;
+    return a + b;
+}
+pub const MAKELOCAL = @compileError("unable to translate C expr: unexpected token .Equal"); // macro.c:1:9{#endsyntax#}
+

Note that {#syntax#}foo{#endsyntax#} was translated correctly despite using a non-translateable + macro. {#syntax#}MAKELOCAL{#endsyntax#} was demoted to {#syntax#}@compileError{#endsyntax#} since + it cannot be expressed as a Zig function; this simply means that you cannot directly use + {#syntax#}MAKELOCAL{#endsyntax#} from Zig. +

+ {#see_also|@compileError#} + {#header_close#} + {#header_open|C Pointers#}

This type is to be avoided whenever possible. The only valid reason for using a C pointer is in