1 /++ Idiomatic D API for libtcc.h 2 See: https://briancallahan.net/blog/20220406.html 3 4 TODO: Wrap newly added `tcc_set_realloc` if available. Availability might be 5 checkable via a trait on ImportC declarations. 6 TODO: Wrap newly added `tcc_setjmp`. `tcc_set_backtrace_func`. 7 +/ 8 module tcc; 9 10 import nxt.path : FilePath, DirPath; 11 12 @safe: 13 14 /++ Tiny C Compiler (TCC). 15 See: https://briancallahan.net/blog/20220406.html 16 +/ 17 struct TCC { 18 import std.string : toStringz; 19 import deimos.libtcc; 20 21 /++ Output type. 22 +/ 23 enum OutputType { 24 memory = TCC_OUTPUT_MEMORY, /** Output will be run const memory (default) */ 25 exe = TCC_OUTPUT_EXE, /** Executable file */ 26 dll = TCC_OUTPUT_DLL, /** Dynamic library */ 27 obj = TCC_OUTPUT_OBJ, /** Object file */ 28 preprocess = TCC_OUTPUT_PREPROCESS, /** Only preprocess (used internally) */ 29 } 30 31 // Disable copying for now until I've figured out the preferred behavior. 32 this(this) @disable; 33 34 /** Make sure `_state` is initialized. */ 35 private void assertInitialized() @trusted pure nothrow @nogc { 36 if (_state is null) 37 _state = tcc_new(); 38 } 39 40 ~this() @trusted pure nothrow @nogc { 41 if (_state !is null) { 42 tcc_delete(_state); 43 _state = null; 44 } 45 } 46 47 /** Set CONFIG_TCCDIR at runtime. */ 48 void setLibraryPath(in char[] path) pure scope @trusted { 49 assertInitialized(); 50 return tcc_set_lib_path(_state, path.toStringz); 51 } 52 /// ditto 53 void setLibraryPath(in DirPath path) pure scope => setLibraryPath(path.str); 54 55 alias ErrorFunc = extern(C) void function(void* opaque, const char* msg); 56 57 /** Set error/warning display callback. */ 58 void setErrorFunction(void* error_opaque, ErrorFunc error_func) pure scope @trusted @nogc { 59 assertInitialized(); 60 return tcc_set_error_func(_state, error_opaque, error_func); 61 } 62 63 /** Set options `opts` as from command line (multiple supported). 64 Important options are `"-bt"` `"-b"`. 65 */ 66 int setOptions(in char[] opts) pure scope @trusted { 67 assertInitialized(); 68 return tcc_set_options(_state, opts.toStringz); 69 } 70 71 /*****************************/ 72 /** Preprocessor. */ 73 74 /** Add include path `pathname`. */ 75 int addIncludePath(in char[] pathname) pure scope @trusted { 76 assertInitialized(); 77 return tcc_add_include_path(_state, pathname.toStringz); 78 } 79 /// ditto 80 int addIncludePath(in DirPath path) pure scope => addIncludePath(path.str); 81 82 /** Add system include path `pathname`. */ 83 int addSystemIncludePath(in char[] pathname) pure scope @trusted { 84 assertInitialized(); 85 return tcc_add_sysinclude_path(_state, pathname.toStringz); 86 } 87 /// ditto 88 int addSystemIncludePath(in DirPath path) pure scope => addSystemIncludePath(path.str); 89 90 /** Define preprocessor symbol named `sym`. Can put optional value. */ 91 void defineSymbol(in char[] sym, in char[] value) pure scope @trusted { 92 assertInitialized(); 93 return tcc_define_symbol(_state, sym.toStringz, value.toStringz); 94 } 95 96 /** Undefine preprocess symbol named `sym`. */ 97 void undefineSymbol(in char[] sym) pure scope @trusted { 98 assertInitialized(); 99 return tcc_undefine_symbol(_state, sym.toStringz); 100 } 101 102 /*****************************/ 103 /** Compiling. */ 104 105 /** Add a file (C file, dll, object, library, ld script). 106 Return -1 if error. 107 */ 108 int addFile(in char[] filename) pure scope @trusted { 109 assertInitialized(); 110 return tcc_add_file(_state, filename.toStringz); 111 } 112 /// ditto 113 int addFile(in FilePath path) pure scope => addFile(path.str); 114 115 /** Compile `source` containing a C source. 116 Return -1 if error. 117 */ 118 int compile(in char[] source) pure scope @trusted { 119 assertInitialized(); 120 /+ TODO: avoid `toStringz` when possible +/ 121 return tcc_compile_string(_state, source.toStringz); 122 } 123 124 /*****************************/ 125 /** Linking commands. */ 126 127 /** Set output type. MUST BE CALLED before any compilation. */ 128 int setOutputType(in OutputType output_type) pure scope @trusted @nogc { 129 assertInitialized(); 130 return tcc_set_output_type(_state, output_type); 131 } 132 133 /** Equivalent to -Lpath option. */ 134 int addLibraryPath(in char[] pathname) pure scope @trusted { 135 assertInitialized(); 136 return tcc_add_library_path(_state, pathname.toStringz); 137 } 138 int addLibraryPath(in DirPath path) pure scope => addLibraryPath(path.str); 139 140 /** The library name is the same as the argument of the '-l' option. */ 141 int addLibrary(in char[] libraryname) pure scope @trusted { 142 assertInitialized(); 143 return tcc_add_library(_state, libraryname.toStringz); 144 } 145 146 /** Add a symbol to the compiled program. */ 147 int addSymbol(in char[] name, const void* val) pure scope @trusted { 148 assertInitialized(); 149 return tcc_add_symbol(_state, name.toStringz, val); 150 } 151 152 /** Output an executable, library or object file. 153 DO NOT call `relocate()` before. 154 */ 155 int outputFile(in char[] filename) scope @trusted { 156 assertInitialized(); 157 return tcc_output_file(_state, filename.toStringz); 158 } 159 /// ditto 160 int outputFile(in FilePath filename) scope @trusted { 161 assertInitialized(); 162 return tcc_output_file(_state, filename.str.toStringz); 163 } 164 165 /** Link and run `main()` function and return its value. 166 DO NOT call `relocate()` before. 167 */ 168 int run(int argc, char** argv) scope @trusted @nogc { 169 assertInitialized(); 170 return tcc_run(_state, argc, argv); 171 } 172 173 /** Evaluate (Compile, link and run) `source` by running its `main()` and returning its value. 174 DO NOT call `relocate()` before. 175 */ 176 int eval(in char[] source, int argc, char** argv) scope @trusted { 177 typeof(return) ret; 178 ret = setOutputType(TCC.OutputType.exe); 179 if (ret != 0) 180 return ret; 181 ret = compile(source); 182 if (ret != 0) 183 return ret; 184 return run(argc, argv); 185 } 186 alias compileAndRun = eval; 187 188 /** Do all relocations (needed before using `get_symbol()`) */ 189 int relocate(void* ptr); // TOOD: define 190 191 /** Possible values for 'ptr': 192 - RELOCATE_AUTO : Allocate and manage memory internally 193 - NULL : return required memory size for the step below 194 - memory address : copy code to memory passed by the caller 195 Returns -1 if error. 196 */ 197 enum RELOCATE_AUTO = cast(void*) 1; 198 199 /** Return value of symbol named `name` or `null` if not found. */ 200 void* getSymbolMaybe(in char[] name) pure scope @trusted { 201 assertInitialized(); 202 return tcc_get_symbol(_state, name.toStringz); 203 } 204 205 TCCState* _state; 206 } 207 208 /// 209 pure @safe unittest { 210 TCC tcc; 211 assert(tcc.compile(`int f(int x) { return x; }`) == 0); 212 } 213 214 /// 215 pure @safe unittest { 216 TCC tcc; 217 static extern(C) void error_func_ignore(void* opaque, const char* msg) {} 218 tcc.setErrorFunction((void*).init, &error_func_ignore); 219 assert(tcc.compile(`int f(int x) { return; }`) == 0); /+ TODO: check warning output +/ 220 assert(tcc.compile(`int f(int x) { return }`) == -1); /+ TODO: check error output +/ 221 } 222 223 /// Compile main(). 224 pure @safe unittest { 225 TCC tcc; 226 assert(tcc.compile(`int main(int argc, char** argv) { return 0; }`) == 0); 227 } 228 229 /// Run main(). 230 @safe unittest { 231 version (linux) { 232 TCC tcc; 233 tcc.setOutputType(TCC.OutputType.exe); 234 const src = `int main(int argc, char** argv) { return 42; }`; 235 assert(tcc.eval(src, 0, null) == 42); 236 } 237 } 238 239 /// Use stdio.h. 240 @safe unittest { 241 version (linux) { 242 TCC tcc; 243 tcc.setOutputType(TCC.OutputType.exe); 244 const src = ` 245 #include <stdio.h> 246 int main(int argc, char** argv) { 247 printf("Hello world!\n"); 248 return 0; } 249 `; 250 assert(tcc.eval(src, 0, null) == 0); 251 // assert(tcc.addLibraryPath(DirPath("/usr/lib/x86_64-linux-gnu/")) == 0); 252 // assert(tcc.addLibrary("c") == 0); // C library 253 } 254 } 255 256 /// Use gmp.h. 257 @safe unittest { 258 version (linux) { 259 TCC tcc; 260 tcc.setOutputType(TCC.OutputType.exe); 261 const src = ` 262 #include <gmp.h> 263 int main(int argc, char** argv) { 264 mpz_t x; 265 mpz_init_set_ui(x, 42); 266 const ret = mpz_get_ui(x); 267 mpz_clear(x); 268 return ret; 269 } 270 `; 271 assert(tcc.addLibrary("gmp") == 0); // GNU MP 272 assert(tcc.eval(src, 0, null) == 42); 273 } 274 } 275 276 /// Set system include paths. 277 pure @safe unittest { 278 TCC tcc; 279 tcc.addSystemIncludePath(DirPath(`/usr/include/`)); 280 tcc.addSystemIncludePath(DirPath(`/usr/lib/x86_64-linux-gnu/tcc/`)); 281 tcc.addSystemIncludePath(DirPath(`/usr/include/linux/`)); 282 tcc.addSystemIncludePath(DirPath(`/usr/include/x86_64-linux-gnu/`)); 283 } 284 285 /++ In-memory compilation. 286 See: https://gist.github.com/llandsmeer/3489603e5070ee8dbe75cdf1865a5ca9 287 +/ 288 @safe unittest { 289 TCC tcc; 290 291 static extern(C) void error_func_ignore(void* opaque, const char* msg) @trusted { 292 import core.stdc.string : strlen; 293 import std.stdio; 294 printf("opaque:%p\nmsg:\n%s\n", opaque, msg); 295 } 296 tcc.setErrorFunction((void*).init, &error_func_ignore); 297 298 tcc.setOutputType(TCC.OutputType.memory); 299 300 import std.datetime.stopwatch : StopWatch, AutoStart; 301 auto sw = StopWatch(AutoStart.yes); 302 static extern(C) int add1(int x) { return x+1; } 303 tcc.addSymbol("add1", &add1); 304 tcc.compile("int add1(int); int main() { return add1(41); }"); 305 import std.stdio; 306 writeln("tcc.compile took ", sw.peek); 307 308 sw.reset(); 309 const exitStatus = tcc.run(0, null); 310 writeln("tcc.run took:", sw.peek); 311 312 assert(exitStatus == 42); 313 }