This extension embeds tcc inside tcl by means of a loadable extension.
The [tcc] command generates a command which encapsulates a tcc C compiler environment - you can feed it C code, generate .so or .dll files, even compile C into memory and run it there. It's like a prototypic critcl without the external dependencies.
tcc is only around 100Kb long, and it's fast as blazes, so this is a realistic technique. Sadly, though, it only works for i386 architectures.
- CMcC 20041028 (I am so happy :)
RS2007-10-14: This later evolved into Odyce (credited at [L1 ])
lm 2009/12/07 : As Odyce is not distributed as a Tcl extension (only available in the eTcl distribution), maybe it could be a good idea to restart the TclTcc development ?
- PWQ 28 Oct 2004 Feedback:
(not a great endorcement for tcc) (but see below, it could be glibc/ld.so's bug)
tclsh: x¶ÿ¿}7*@ð: Bad font file format
and aborts tcl on executing:
catch {$tcc {moop}} result
Linux tcl8.4.3 gcc 3.3
Yes, I've noticed that Elf .so files produced by tcc can cause segfaults - it can happen when you dlopen() a file and use dlsym() on it. I'm not entirely sure that it's tcc's fault, and not the fault of ld.so (which is where the segfault occurs.)
I have absolutely no idea why it'd refer to font file formats ... I'll try this myself, thanks for the feedback. In the meanwhile, gcc compiles it properly. -- CMcC
Installation
tcltcc.c
/* tcltcc.c -- tcc extension for tcl * * Colin McCormack 28th October 2004 */ #include <tcl.h> #include "libtcc.h" #include <dlfcn.h> /* clean up tcc state */ static void tcc_del (ClientData clientData) { tcc_delete((TCCState *)clientData); } /* struct to contain tcc environment-interp association */ struct tcc_augmented { TCCState *tccp; Tcl_Interp *interp; Tcl_Obj *result; }; /* record a tcc error in the interpreter result */ void tcc_err(void *opaque, const char *msg) { struct tcc_augmented *state = (struct tcc_augmented *)opaque; /*fprintf(stderr, "err: %s\n", msg);*/ Tcl_ListObjAppendElement( state->interp, state->result, Tcl_NewStringObj(msg, -1)); } /* command to manipulate a tcc environment */ static int tcc(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) { char *tcc_tmp[20]; Tcl_Obj *result = Tcl_NewListObj(0,NULL); TCCState *tccp = (TCCState *)clientData; int cnt = 1; int err = 0; char *file = NULL; int disposition = 0; int type = -1; struct tcc_augmented state = {tccp, interp, result}; char *command = NULL; char *cmdsym = NULL; static CONST char *optionStrings[] = { "--", "-output", "-run", "-relocate", "-file", "-symbol", "-library", "-type", "-libpath", "-include", "-sysinclude", "-define", "-undefine", "-value", "-command", NULL }; enum options { TCC_DONE, TCC_OUTPUT, TCC_RUN, TCC_RELOCATE, TCC_FILE, TCC_SYMBOL, TCC_LIBRARY, TCC_TYPE, TCC_LIBPATH, TCC_INCLUDE, TCC_SYSINCLUDE, TCC_DEFINE, TCC_UNDEFINE, TCC_VALUE, TCC_COMMAND }; /* set error/warning display callback */ tcc_set_error_func(tccp, (void *)&state, tcc_err); while (!err && cnt < objc) { int index; /*fprintf(stderr, "tcc %d %s\n", cnt, Tcl_GetString(objv[cnt]));*/ if (Tcl_GetIndexFromObj(interp, objv[cnt], optionStrings, "option", 0, &index) != TCL_OK) { /* we're at the end of options */ break; } if ((enum options)index == TCC_DONE) { /* -- signals end of options */ break; } switch ((enum options) index) { case TCC_OUTPUT: { if (cnt == objc) { Tcl_WrongNumArgs(interp, cnt, objv+cnt, "filename"); return TCL_ERROR; } //err = tcc_output_file(tccp, Tcl_GetString(objv[cnt+1])); file = Tcl_GetString(objv[cnt+1]); disposition = TCC_OUTPUT; cnt += 2; break; } case TCC_RUN: { disposition = TCC_RUN; cnt ++; break; } case TCC_RELOCATE: { disposition = TCC_RELOCATE; cnt ++; break; } case TCC_VALUE: { unsigned long value; Tcl_Obj *val; if (cnt == objc) { Tcl_WrongNumArgs(interp, cnt, objv+cnt, "name"); return TCL_ERROR; } err = tcc_get_symbol(tccp, &value, Tcl_GetString(objv[cnt+1])); if (err) { /*fprintf(stderr, "get symbol err: %d\n", err);*/ val = Tcl_NewStringObj("No such symbol", -1); } else { val = Tcl_NewIntObj(value); } /* append name/value pair to result */ Tcl_ListObjAppendElement(interp, result, objv[cnt+1]); Tcl_ListObjAppendElement(interp, result, val); cnt += 2; break; } case TCC_COMMAND: { if (cnt +2 >= objc) { Tcl_WrongNumArgs(interp, cnt, objv+cnt, "command symbol"); return TCL_ERROR; } disposition = TCC_COMMAND; command = Tcl_GetString(objv[cnt+1]); cmdsym = Tcl_GetString(objv[cnt+2]); cnt += 3; break; } case TCC_FILE: { if (cnt == objc) { Tcl_WrongNumArgs(interp, cnt, objv+cnt, "name"); return TCL_ERROR; } err = tcc_add_file(tccp, Tcl_GetString(objv[cnt+1])); cnt += 2; break; } case TCC_SYMBOL: { int i; if (cnt+2 >= objc) { Tcl_WrongNumArgs(interp, cnt, objv+cnt, "symbol value"); return TCL_ERROR; } if (Tcl_GetIntFromObj(interp, objv[cnt+2], &i) != TCL_OK) { Tcl_Obj *objPtr = Tcl_NewObj(); Tcl_SetObjResult(interp, objPtr); Tcl_AppendToObj(objPtr, "argument to -symbol must be an integer", -1); return TCL_ERROR; } err = tcc_add_symbol(tccp, Tcl_GetString(objv[cnt+1]), i); cnt += 3; break; } case TCC_LIBRARY: { if (cnt == objc) { Tcl_WrongNumArgs(interp, cnt, objv, "file"); return TCL_ERROR; } err = tcc_add_library(tccp, Tcl_GetString(objv[cnt+1])); if (err) { tcc_err((void *)&state,"can't find library."); } cnt += 2; break; } case TCC_TYPE: { static CONST char *typeStrings[] = { "memory", "exe", "dll", "obj" }; if (cnt == objc) { Tcl_WrongNumArgs(interp, cnt, objv+cnt, "type"); return TCL_ERROR; } if (Tcl_GetIndexFromObj(interp, objv[cnt+1], typeStrings, "type", 0, &type) != TCL_OK) { return TCL_ERROR; } tcc_set_output_type(tccp, type); cnt += 2; break; } case TCC_LIBPATH: { if (cnt == objc) { Tcl_WrongNumArgs(interp, cnt, objv, "filename"); return TCL_ERROR; } err = tcc_add_library_path(tccp, Tcl_GetString(objv[cnt+1])); cnt += 2; break; } case TCC_INCLUDE: { if (cnt == objc) { Tcl_WrongNumArgs(interp, cnt, objv, "filename"); return TCL_ERROR; } err = tcc_add_include_path(tccp, Tcl_GetString(objv[cnt+1])); cnt += 2; break; } case TCC_SYSINCLUDE: { if (cnt == objc) { Tcl_WrongNumArgs(interp, cnt, objv, "filename"); return TCL_ERROR; } err = tcc_add_sysinclude_path(tccp, Tcl_GetString(objv[cnt+1])); cnt += 2; break; } case TCC_DEFINE: { if (cnt+2 > objc) { Tcl_WrongNumArgs(interp, cnt, objv, "filename"); return TCL_ERROR; } tcc_define_symbol( tccp, Tcl_GetString(objv[cnt+1]), Tcl_GetString(objv[cnt+2])); cnt += 3; break; } case TCC_UNDEFINE: { if (cnt == objc) { Tcl_WrongNumArgs(interp, cnt, objv, "filename"); return TCL_ERROR; } tcc_undefine_symbol(tccp, Tcl_GetString(objv[cnt+1])); cnt += 2; break; } default: { return TCL_ERROR; } } } /* now compile whatever remains */ while (!err && cnt < objc) { /*fprintf(stderr, "Compiling: %d - %s\n", cnt, Tcl_GetString(objv[cnt]));*/ err = tcc_compile_string(tccp, Tcl_GetString(objv[cnt])); cnt++; } if (!err) { /* decide what we want to do with the code */ switch (disposition) { case TCC_COMMAND: { long cmdval; /*fprintf(stderr, "Command\n");*/ err = tcc_relocate(tccp); if (err) { /*fprintf(stderr, "relocate err: %d\n", err);*/ Tcl_ListObjAppendElement( interp, result, Tcl_NewStringObj("No such command symbol", -1)); break; } /*fprintf(stderr, "getting symbol: %s\n", cmdsym);*/ err = tcc_get_symbol(tccp, &cmdval, cmdsym); /*fprintf(stderr, "got symbol: %s - %d\n", cmdsym, cmdval);*/ if (err) { /*fprintf(stderr, "command err: %d\n", err);*/ Tcl_ListObjAppendElement( interp, result, Tcl_NewStringObj("No such command symbol", -1)); break; } else { /* construct the command */ /*fprintf(stderr, "command sym: %s\n", command);*/ Tcl_CreateObjCommand( interp, command, (void*)cmdval, (ClientData) tccp, NULL); } break; } case TCC_RUN: err = tcc_run(tccp, 0, NULL); break; case TCC_OUTPUT: { void *elf; void *sym; if (type == -1) { err = 1; Tcl_ListObjAppendElement( interp, result, Tcl_NewStringObj("-type must be specified with -output", -1)); } else { err = tcc_output_file(tccp, file); } break; } case TCC_RELOCATE: { err = tcc_relocate(tccp); if (err) { Tcl_ListObjAppendElement( interp, result, Tcl_NewStringObj("relocation failed", -1)); } break; } default: break; } } Tcl_SetObjResult(interp, result); if (err) { return TCL_ERROR; } else { return TCL_OK; } } static int tcc_create(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) { Tcl_Obj *result; char tcc_tmp[20]; TCCState *tccp = tcc_new(); static int counter = 0; tcc_set_output_type(tccp, TCC_OUTPUT_MEMORY); sprintf(tcc_tmp, "tcc_%d", counter++); result = Tcl_NewStringObj(tcc_tmp, -1); /* construct the command */ Tcl_CreateObjCommand(interp, tcc_tmp, tcc, (ClientData) tccp, (Tcl_CmdDeleteProc *) tcc_del); /* return the command name */ Tcl_SetObjResult(interp, result); return TCL_OK; } int Tcc_Init(Tcl_Interp *interp) { Tcl_PkgProvide(interp,"tcc","1.0"); Tcl_CreateObjCommand(interp, "tcc", tcc_create, (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL); return TCL_OK; }
Makefile.tcltcc - create the extension
CFLAGS += -fPIC -DLIBTCC -ggdb3 -Derror=tcc_error libtcc.so: libtcc.o tcltcc.o gcc -ggdb3 -pipe -shared -o $@ $^ libtcc.o: tcc.c i386-gen.c $(CC) $(CFLAGS) -DLIBTCC -c -o $@ $<
tcc.test - initial test for the extension
load ./libtcc.so set code1 { int fib(int n) { if (n <= 2) return 1; else return fib(n-1) + fib(n-2); } int foo(int n) { printf("Hello World!\n"); printf("fib(%d) = %d\n", n, fib(n)); return 0; } } set tcc [tcc] $tcc -type memory -relocate $code1 rename $tcc "" set tcc [tcc] $tcc -type exe -output fred { #include <stdio.h> void main () {printf("Hello World\n");} } rename $tcc "" set code { #include <tcl.h> int fred(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) { Tcl_SetObjResult(interp, Tcl_NewStringObj("moop", -1)); return TCL_OK; } int Fred_Init(Tcl_Interp *interp) { Tcl_CreateObjCommand(interp, "fred", fred, (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL); return TCL_OK; } int Fred_SafeInit(Tcl_Interp *interp) { Tcl_CreateObjCommand(interp, "fred", fred, (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL); return TCL_OK; } int Fred_Unload(Tcl_Interp *interp) {} int Fred_SafeUnload(Tcl_Interp *interp) {} } set tcc [tcc] $tcc -libpath /usr/lib -library tcl8.5 -command dofred fred $code puts stderr [dofred] puts stderr "DONE" rename $tcc "" set tcc [tcc] catch {$tcc {moop}} result puts stderr "Err: $result" rename $tcc "" set tcc [tcc] $tcc -type dll -libpath /usr/lib -library tcl8.5 -output fred.so $code load ./fred.so puts stderr "Loaded ./fred.so" puts [fred] rename $tcc ""
Wow! Phenometastic! Unbemazing! Congrats Colin, really neat! -jcw