tcc4tcl (Tiny C Compiler for Tcl) is a Tcl extension that provides an interface to TCC. It is a fork of tcltcc by Mark Janssen.
Tcc4tcl makes extending Tcl with C and creating compiled package extensions a simple and interactive experience. It incorporates TinyCC , a small yet featureful C compiler that works fast enough to be used as JIT compiler.
In addition to the TinyCC binding, tcc4tcl allows quick wrapping of C functions into Tcl procedures, and saving these procedures in libraries loadable as package extensions using the load command. It is similar to Critcl, except that the compiler is included.
Latest release (0.40.0) 7/18/2022. Tested on Linux and Windows.
MiR 2023-09-22 -- Meanwhile, tcc is entering release cycle for 0.9.28. When this version is finalized, I will make a new release of tcc4tcl, the dev-repo on github is actualized to 0.9.28rc [L1 ]. Happy testing!
MiR 2024-06-20 -- Still no new release of tcc 0.9.28 is waiting for finalization... sigh! But I keep updating mob-builds with the latest dev-repo on a regular basis.
License: LGPL v2.1 (or later).
Example: Add a simple C function as a Tcl procedure:
set handle [tcc4tcl::new] $handle cproc add {int a int b} long { return(a+b); } $handle go puts [add 1 2]
Enable invocation of existing mkdir syscall:
tcc4tcl::cproc mkdir {Tcl_Interp* interp char* dir} ok { int mkdir_ret; mkdir_ret = mkdir(dir); if (mkdir_ret != 0) { Tcl_SetObjResult(interp, Tcl_NewStringObj("failed", -1)); return(TCL_ERROR); }; return(TCL_OK); }
MiR 2022-11-02 -- I tested a new way of patching tcc for building tcc4tcl. Since the patch generally seems to be stable since some months and now works from tcc 0.9.27 up to the latest mob version I decided to open the repo to the public. You can find the patch, tcc4tcl source and binary packages here [L2 ]. Would be glad to hear some opinions. The new versions identify themselves as 0.41 and set a global variable $TCC_VERSION that reflects the used version of tcc. Contains also some small patches/features to tcc4tcl.c and tcc4tcl.tcl, undocumented for now.
MiR 2022-07-18 -- New Beta relase, docs updated, bumped version to 0.40.0 [L3 ]
Binary distributions are availiable on the tccide github [L4 ].
MiR 2022-07-06 -- Beta Version of tcc4tcl (0927) released :-)
Some important changenotes:
Please test and report back! If someone want's to take care of the makefile issues... I'm really bad at makefiles and some of the automake files got lost long time ago it seems.
AMG: gcc versions 5.1.0 and newer compress debug sections. tcc 0.9.26 does not know what to do with this. If libtcc1.a was produced with debug symbols using gcc 5.1.0 or newer, when tcc tries to initialize, it corrupts memory when applying symbol relocations to the debug sections in libtcc1.a. This is because the relocation table contains offsets into the uncompressed debug section. About half of these offsets will exceed the total size of the compressed debug section, which tcc operates on directly, not knowing it needs to decompress it first. tcc blindly trusts the symbol offsets and will happily scribble all over memory if it thinks that's what the relocation table says to do. The consequences are unpredictable. I had to use valgrind to see that this was the origin of my program's intermittent misbehavior, then it took a few days of high-powered sleuthing to track down the root cause.
The solution I've adopted is to ensure -gz=none is in CFLAGS when compiling tcc4tcl.
The solution recommended by the tcc team (for a different problem also documented on this page, but it would solve this problem too) is to change tcc4tcl to compile libtcc1.a using tcc rather than gcc.
AMG: The current version of tcc4tcl (0.30) directly includes a patched copy of tcc 0.9.26 from early 2013. The most recent version of tcc is 0.9.27 from late 2017. I would very much like to upgrade to 0.9.27 because of the many improvements, but doing so breaks virtually all the tcc patches found in [L5 ].
AMG: The C code has access to Tcl and Tk symbols just fine, but other than that, the only available symbols are: printf, fprintf, fopen, and fclose. These are listed in tcc_syms in tccrun.c. It's clear that this list is present as only an example of how to add symbols.
I'm very much wishing for the ability to extend this list without editing the tcc source. In fact, many of the symbols I want to access may not even be available during the compilation and linking of tcc4tcl since they'll come from other dynamically loaded extension libraries. Mostly SQLite.
To do this, I extended the add_symbol command to allow dynamic symbol lookups, implemented using Tcl_LoadFile() or dlsym(), depending on context.
Tcl_LoadFile() is used when the symbol comes from a library. dlsym() is used when the symbol is baked into the interpreter (or is in a library loaded using [load -global]); however, this doesn't work in Windows.
Here's the patch against tcc4tcl version c1a5de894b [L6 ]. Warning: this patch contains tabs and end-of-line whitespace!
Patchdiff -ur tcc4tcl-0.30~/tcc4tcl.c tcc4tcl-0.30/tcc4tcl.c --- tcc4tcl-0.30~/tcc4tcl.c 2019-06-11 12:25:16.849343862 -0500 +++ tcc4tcl-0.30/tcc4tcl.c 2019-06-11 19:34:35.721853441 -0500 @@ -19,6 +19,10 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#ifndef _WIN32 +# include <dlfcn.h> +#endif + #include <tcl.h> #include <stdlib.h> #include "tcc.h" @@ -28,6 +32,9 @@ int relocated; }; +static Tcl_HashTable Tcc4tclHandles; +static int Tcc4tclHandlesInitialized; + static void Tcc4tclErrorFunc(Tcl_Interp * interp, char * msg) { Tcl_AppendResult(interp, msg, "\n", NULL); } @@ -58,6 +65,7 @@ static int Tcc4tclHandleCmd ( ClientData cdata, Tcl_Interp *interp, int objc, Tcl_Obj * CONST objv[]){ Tcl_WideInt val; + Tcl_Obj *expr_o; Tcl_Obj *val_o; void *val_p; int index; @@ -127,27 +135,110 @@ tcc_add_library_path(s, Tcl_GetString(objv[2])); return TCL_OK; } - case TCC4TCL_ADD_SYMBOL: - if (objc != 4) { - Tcl_WrongNumArgs(interp, 2, objv, "symbol value"); + case TCC4TCL_ADD_SYMBOL: { + Tcl_LoadHandle handle; + Tcl_HashEntry *entry; + static const char *const switches[] = {"-library", "-lookup", NULL}; + enum {LIBRARY, LOOKUP} match; + Tcl_Obj *library = NULL; + const char *lookup[2] = {NULL, NULL}; + int i; + int new; + + if (objc < 3) { + Tcl_WrongNumArgs(interp, 2, objv, "symbol ?value? " + "?-library libraryName? ?-lookup lookupName?"); return TCL_ERROR; } - rv = Tcl_ExprObj(interp, Tcl_ObjPrintf("wide(%s)", Tcl_GetString(objv[3])), &val_o); - if (rv != TCL_OK) { - return TCL_ERROR; + /* + * [tcc add_symbol symbol value]. The value is the symbol address. + */ + if (objc == 4) { + expr_o = Tcl_ObjPrintf("wide(%s)", Tcl_GetString(objv[3])); + rv = Tcl_ExprObj(interp, expr_o, &val_o); + Tcl_DecrRefCount(expr_o); + if (rv != TCL_OK) { + return TCL_ERROR; + } + rv = Tcl_GetWideIntFromObj(interp, val_o, &val); + Tcl_DecrRefCount(val_o); + if (rv != TCL_OK) { + return TCL_ERROR; + } + tcc_add_symbol(s, Tcl_GetString(objv[2]), (void *)val); + return TCL_OK; } - rv = Tcl_GetWideIntFromObj(interp, val_o, &val); - if (rv != TCL_OK) { - return TCL_ERROR; + /* + * [tcc add_symbol symbol ?-switches ...?]. Parse switches to + * obtain the library and lookup symbol names, if given. + */ + lookup[0] = Tcl_GetString(objv[2]); + for (i = 3; i < objc; i += 2) { + if (Tcl_GetIndexFromObj(interp, objv[i], switches, "switch", 0, + (int *)&match) != TCL_OK) { + return TCL_ERROR; + } else if (i == objc - 1) { + Tcl_Obj *error = Tcl_NewStringObj( + "missing value for switch: ", -1); + Tcl_AppendObjToObj(error, objv[i]); + Tcl_SetObjResult(interp, error); + return TCL_ERROR; + } else if (match == LIBRARY) { + library = objv[i + 1]; + } else if (match == LOOKUP) { + lookup[0] = Tcl_GetString(objv[i + 1]); + } } - val_p = (void *) val; + if (library) { + /* + * -library was specified. First, search the handle table for + * the already-opened library. If not found, open the library + * and add it to the table, looking up the symbol as a side + * effect. If found, look up the symbol using the existing + * handle. In either case, fail if the symbol is missing. + */ + entry = Tcl_CreateHashEntry(&Tcc4tclHandles, library, &new); + if (new) { + if (Tcl_LoadFile(interp, library, lookup, 0, &val_p, + &handle) != TCL_OK) { + Tcl_DeleteHashEntry(entry); + return TCL_ERROR; + } + Tcl_SetHashValue(entry, handle); + } else if (!(val_p = Tcl_FindSymbol(interp, + (Tcl_LoadHandle)Tcl_GetHashValue(entry), *lookup))) { + return TCL_ERROR; + } + } else { + /* + * -library was not specified. If dlsym() is available, use + * dlsym() to look up the symbol in the current process image. + * This includes symbols statically and dynamically linked into + * the current process (assuming they weren't marked "static"; + * be mindful of the double meaning of that word!), plus symbols + * in libraries loaded by [load -global] or Tcl_LoadFile() using + * the TCL_LOAD_GLOBAL flag. + */ +#ifndef _WIN32 + dlerror(); + val_p = dlsym(RTLD_DEFAULT, Tcl_GetString(objv[objc - 1])); + if (!val_p && (str = dlerror())) { + Tcl_SetResult(interp, str, TCL_VOLATILE); + return TCL_ERROR; + } +#else + Tcl_SetResult(interp, "-library required on this platform", + TCL_STATIC); + return TCL_ERROR; +#endif + } - tcc_add_symbol(s,Tcl_GetString(objv[2]), val_p); + tcc_add_symbol(s, Tcl_GetString(objv[2]), val_p); return TCL_OK; - case TCC4TCL_COMMAND: + } case TCC4TCL_COMMAND: if (objc != 4 && objc != 5) { Tcl_WrongNumArgs(interp, 2, objv, "tclname cname ?clientData?"); return TCL_ERROR; @@ -319,7 +410,29 @@ } #endif + if (!Tcc4tclHandlesInitialized) { + Tcc4tclHandlesInitialized = 1; + Tcl_InitHashTable(&Tcc4tclHandles, TCL_STRING_KEYS); + } + Tcl_CreateObjCommand(interp, "tcc4tcl", Tcc4tclCreateCmd, NULL, NULL); return TCL_OK; } + +int Tcc4tcl_Unload(Tcl_Interp *interp, int flags) { + if (flags & TCL_UNLOAD_DETACH_FROM_PROCESS) { + Tcl_HashSearch search; + Tcl_HashEntry *entry; + + entry = Tcl_FirstHashEntry(&Tcc4tclHandles, &search); + while (entry) { + Tcl_FSUnloadFile(interp, (Tcl_LoadHandle)Tcl_GetHashValue(entry)); + entry = Tcl_NextHashEntry(&search); + } + Tcl_DeleteHashTable(&Tcc4tclHandles); + Tcc4tclHandlesInitialized = 0; + } + + return TCL_OK; +} diff -ur tcc4tcl-0.30~/tcc4tcl.tcl tcc4tcl-0.30/tcc4tcl.tcl --- tcc4tcl-0.30~/tcc4tcl.tcl 2019-06-11 12:25:16.850344362 -0500 +++ tcc4tcl-0.30/tcc4tcl.tcl 2019-06-11 13:54:07.764711760 -0500 @@ -46,7 +46,7 @@ } } - array set $handle [list code "" type $type filename $output package $pkgName add_inc_path "" add_lib_path "" add_lib "" add_macros "" add_files ""] + array set $handle [list code "" type $type filename $output package $pkgName add_inc_path "" add_lib_path "" add_lib "" add_symbol "" add_macros "" add_files ""] proc $handle {cmd args} [string map [list @@HANDLE@@ $handle] { set handle {@@HANDLE@@} @@ -63,7 +63,7 @@ set callcmd ::tcc4tcl::_$cmd if {[info command $callcmd] == ""} { - return -code error "unknown or ambiguous subcommand \"$cmd\": must be cwrap, ccode, cproc, ccommand, delete, linktclcommand, code, tk, add_include_path, add_library_path, add_library, process_command_line, or go" + return -code error "unknown or ambiguous subcommand \"$cmd\": must be cwrap, ccode, cproc, ccommand, delete, linktclcommand, code, tk, add_include_path, add_library_path, add_library, add_symbol, process_command_line, or go" } uplevel 1 [list $callcmd $handle {*}$args] @@ -118,6 +118,12 @@ lappend state(add_lib) {*}$args } + proc _add_symbol {handle args} { + upvar #0 $handle state + + lappend state(add_symbol) $args + } + proc _add_file {handle args} { upvar #0 $handle state @@ -618,6 +624,10 @@ tcc add_file $addFile } + foreach addSymbol $state(add_symbol) { + tcc add_symbol {*}$addSymbol + } + switch -- $state(type) { "memory" { tcc compile $code
package require tcc4tcl set tcc [tcc4tcl::new] $tcc add_symbol sin $tcc add_symbol cos $tcc ccode {#include <math.h>} $tcc cproc sin {double x} double {return sin(x);} $tcc cproc cos {double x} double {return cos(x);} $tcc go sin [expr {asin(0.123)}] cos [expr {acos(0.456)}]
package require tcc4tcl package require sqlite3 set tcc [tcc4tcl::new] $tcc ccode { #include "sqlite3.h" int testCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { sqlite3 *db; sqlite3_stmt *stmt; int rc; if ((rc = sqlite3_open_v2(NULL, &db, SQLITE_OPEN_READWRITE, NULL)) != SQLITE_OK) { Tcl_SetResult(interp, sqlite3_errstr(rc), TCL_VOLATILE); return TCL_ERROR; } sqlite3_prepare_v2(db, "SELECT sqlite_version()", -1, &stmt, NULL); if (!stmt) { Tcl_SetResult(interp, sqlite3_errcode(db), TCL_VOLATILE); sqlite3_close(db); return TCL_ERROR; } if (sqlite3_step(stmt) != SQLITE_ROW) { Tcl_SetResult(interp, sqlite3_errcode(db), TCL_VOLATILE); sqlite3_close(db); return TCL_ERROR; } Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3_column_text(stmt, 0), sqlite3_column_bytes(stmt, 0))); if (sqlite3_finalize(stmt) != SQLITE_OK) { Tcl_SetResult(interp, sqlite3_errcode(db), TCL_VOLATILE); sqlite3_close(db); return TCL_ERROR; } if ((rc = sqlite3_close(db)) != SQLITE_OK) { Tcl_SetResult(interp, sqlite3_errstr(rc), TCL_VOLATILE); return TCL_ERROR; } return TCL_OK; } } set lib [file join [info nameofexecutable] lib/sqlite3.28.0/libsqlite3.28.0.so] $tcc add_symbol sqlite3_open_v2 -library $lib $tcc add_symbol sqlite3_prepare_v2 -library $lib $tcc add_symbol sqlite3_step -library $lib $tcc add_symbol sqlite3_column_text -library $lib $tcc add_symbol sqlite3_column_bytes -library $lib $tcc add_symbol sqlite3_finalize -library $lib $tcc add_symbol sqlite3_close -library $lib $tcc add_symbol sqlite3_errstr -library $lib $tcc add_symbol sqlite3_errcode -library $lib $tcc linktclcommand test testCmd $tcc go test
This doesn't quite work on Windows. Not only is dlsym() missing, meaning that I can't find symbols already linked into the process, but the Tcl_LoadFile() approach is failing as well.
If I try to add symbols for a package that's already loaded, I get a permissions error. I'm guessing that Tcl_LoadFile() is trying to read the file a second time, yet Windows is locking it. Shouldn't Tcl_LoadFile() just point to the already-open library?
If I try to add symbols before loading the package, I get farther, but nevertheless all my symbol lookups fail. Maybe there need to be leading and/or trailing underscores. (That's what the -lookup switch is for, to allow the looked-up name to not match the name referenced within the C program.) However, these underscores don't show up in the output of nm, so I'm not sure that's really what's going on.
There's trouble on Linux too, at least in combination with VFS (tclkit) and sharing a database connection between Tcl and C . [package require sqlite3] invokes [load libsqlite3.*.so] which invokes Tcl_LoadFile() which is forced to copy libsqlite3.*.so to a temporary file to pass to dlopen(). Later on, [$tcc add_symbol sqlite3_* -library .../libsqlite3.*.so] calls Tcl_LoadFile() as well, not having access to the handle produced by the first call to dlopen(). Thus, SQLite is loaded twice, and the function pointers associated with the second are for code linked to a separate copy of all the static and global variables. This prevents sharing SQLite connections between Tcl and C, since Tcl and C are linked to separate copies of SQLite.
tclLoad.c actually does keep track of handles associated with already-loaded libraries, which can be looked up by package name ("Sqlite3" in this case). However, there is no API to access this table other than the unload command which has obvious and undesirable side effects.
The three solutions I can think of are:
I've tried (3) and it works. The trouble with (1) is it's a change to the way my Tclkit is built, and office security procedures make it hard for me to publish new binaries (which by the way is a part of my motivation to integrate tcc). Both (1) and (2) seem to be fundamentally incompatible with Windows due to requiring dlsym(RTLD_DEFAULT).
AMG: With tcc4tcl 0.30 (tcc 0.9.26) compiled with 32-bit MXE GCC 5.4.0, or with the official build [L7 ] (tried with [L8 ]), I get the following error:
% tcc4tcl::cproc test {Tcl_WideInt a int b} Tcl_WideInt {return a << b;} tcc: error: undefined symbol '__ashldi3' relocating failed
This is only a problem in Windows, not Linux. The cause is twofold. (1) gcc is prefixing all the symbol names in libtcc1.a with an extra underscore that tcc is not expecting, and (2) gcc is producing PE-format object files, and tcc only knows how to link ELF object files.
The solution is to apply this patch:
Patchdiff -ur tcc4tcl-0.30-old/Makefile.in tcc4tcl-0.30-new/Makefile.in --- tcc4tcl-0.30-old/Makefile.in 2017-10-13 15:37:05.000000000 -0500 +++ tcc4tcl-0.30-new/Makefile.in 2019-03-13 15:04:27.510092464 -0500 @@ -8,6 +8,8 @@ CPP = @CPP@ AR = @AR@ RANLIB = @RANLIB@ +OBJCOPY = @OBJCOPY@ +OBJDUMP = @OBJDUMP@ CFLAGS = @CFLAGS@ @SHOBJFLAGS@ CPPFLAGS = @CPPFLAGS@ -I$(shell cd @srcdir@ && pwd) -I$(shell cd @srcdir@ && pwd)/tcc -I$(shell pwd)/tcc @DEFS@ @SHOBJCPPFLAGS@ LDFLAGS = @LDFLAGS@ @@ -29,7 +31,7 @@ host_os = @host_os@ @SET_MAKE@ -all: $(TARGET) tcc/libtcc1.a +all: $(TARGET) tcc/libtcc1-elf.a tcc/config.h: if [ "$(srcdir)" = "." ]; then \ @@ -46,6 +48,17 @@ -$(MAKE) -C tcc tcc@EXEEXT@ $(MAKE) -C tcc libtcc1.a +# tcc supports dynamically loading object code from ELF, not from PE, so on some +# platforms it is necessary to convert its runtime support library to ELF. +tcc/libtcc1-elf.a: tcc/libtcc1.a + if $(OBJDUMP) -a $< | grep -q ' file format pei\?-x86-64$$'; then \ + $(OBJCOPY) --remove-leading-char -O elf64-x86-64 $< $@; \ + elif $(OBJDUMP) -a $< | grep -q ' file format pei\?-i386$$'; then \ + $(OBJCOPY) --remove-leading-char -O elf32-i386 $< $@; \ + else \ + cp -f $< $@; \ + fi + tcc4tcl.o: $(srcdir)/tcc4tcl.c $(srcdir)/tcc/tcc.h $(srcdir)/tcc/libtcc.h tcc/config.h $(CC) $(CPPFLAGS) $(CFLAGS) -o tcc4tcl.o -c $(srcdir)/tcc4tcl.c @@ -60,7 +73,7 @@ -$(RANLIB) tcc4tcl-static.new.a mv tcc4tcl-static.new.a tcc4tcl-static.a -install: $(TARGET) pkgIndex.tcl $(srcdir)/tcc4tcl.tcl $(srcdir)/tcc4critcl.tcl tcc/libtcc1.a $(shell echo $(srcdir)/tcc/include/*) $(shell echo $(srcdir)/tcc/win32/lib/*.c) $(srcdir)/headers.awk $(srcdir)/patch-headers.sh +install: $(TARGET) pkgIndex.tcl $(srcdir)/tcc4tcl.tcl $(srcdir)/tcc4critcl.tcl tcc/libtcc1-elf.a $(shell echo $(srcdir)/tcc/include/*) $(shell echo $(srcdir)/tcc/win32/lib/*.c) $(srcdir)/headers.awk $(srcdir)/patch-headers.sh $(INSTALL) -d "$(DESTDIR)$(PACKAGE_INSTALL_DIR)" $(INSTALL) -d "$(DESTDIR)$(PACKAGE_INSTALL_DIR)/lib" $(INSTALL) -d "$(DESTDIR)$(PACKAGE_INSTALL_DIR)/include" @@ -68,7 +81,7 @@ $(INSTALL) -m 0644 pkgIndex.tcl "$(DESTDIR)$(PACKAGE_INSTALL_DIR)" $(INSTALL) -m 0644 $(srcdir)/tcc4tcl.tcl "$(DESTDIR)$(PACKAGE_INSTALL_DIR)" $(INSTALL) -m 0644 $(srcdir)/tcc4critcl.tcl "$(DESTDIR)$(PACKAGE_INSTALL_DIR)" - $(INSTALL) -m 0644 tcc/libtcc1.a "$(DESTDIR)$(PACKAGE_INSTALL_DIR)/lib" + $(INSTALL) -m 0644 tcc/libtcc1-elf.a "$(DESTDIR)$(PACKAGE_INSTALL_DIR)/lib/libtcc1.a" $(INSTALL) -m 0644 $(shell echo $(srcdir)/tcc/win32/lib/*.c) "$(DESTDIR)$(PACKAGE_INSTALL_DIR)/lib" $(INSTALL) -m 0644 $(shell echo $(srcdir)/tcc/include/*) "$(DESTDIR)$(PACKAGE_INSTALL_DIR)/include" @if ! echo "_WIN32" | $(CPP) $(CPPFLAGS) - | grep '^_WIN32$$' >/dev/null; then \ diff -ur tcc4tcl-0.30-old/configure.ac tcc4tcl-0.30-new/configure.ac --- tcc4tcl-0.30-old/configure.ac 2017-10-13 15:37:16.000000000 -0500 +++ tcc4tcl-0.30-new/configure.ac 2019-03-13 15:06:59.156101031 -0500 @@ -39,6 +39,7 @@ TARGET="tcc4tcl-static.a" fi +AC_CHECK_TOOL([OBJDUMP], [objdump]) AC_SUBST(TARGET) AC_SUBST(TCC4TCL_TARGET) AC_SUBST(TCC_EXTRA_CFLAGS) diff -ur tcc4tcl-0.30-old/test.tcl tcc4tcl-0.30-new/test.tcl --- tcc4tcl-0.30-old/test.tcl 2017-10-13 15:37:05.000000000 -0500 +++ tcc4tcl-0.30-new/test.tcl 2019-03-13 15:09:06.231108210 -0500 @@ -15,6 +15,9 @@ # This should work tcc4tcl::cproc test3 {int i} int { return(i+42); } +# Check for libtcc1 functionality +tcc4tcl::cproc testlibtcc1 {double x} int { return(x); } + # Multiple arguments tcc4tcl::cproc add {int a int b} int { return(a+b); }
Be cautious with this patch since it intentionally contains tabs and end-of-line whitespace, all of which have been stripped off by the wiki. Let me know if you want the original file with correct formatting.
After applying the patch, run "autoconf" to regenerate the configure script.
I have confirmed that the above patch fixes 32-bit Windows and does not break 64-bit Windows, 32-bit Linux, or 64-bit Linux.
Note: Even though I say that the error only happens on 32-bit Windows, as far as I can tell, this is only because libtcc1 is not needed as much on 64-bit Windows. If I dug deeper, I could probably find a test case that would break 64-bit Windows too, i.e. a case where libtcc1 is used on both 32- and 64-bit CPUs.
AMG: Is there any facility to access a cproc command's full objv argument list?
Answer: No. Bypass cproc and instead use ccode directly to create functions matching the signature shown below. Then use linktclcommand to arrange for Tcl_CreateObjCommand to be called on those functions. Note that the created Tcl commands are in the global namespace by default, not in the current namespace eval namespace, unless the namespace is explicitly given in the command name.
typedef int Tcl_ObjCmdProc( ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]);
dbohdan 2015-03-16: As of version 0.23 you can compile tcc4tcl on Linux thus:
#!/bin/sh set -e version=0.23 release="tcc4tcl-$version" url="http://rkeene.org/devel/tcc4tcl/${release}.tar.gz" curl "$url" -o "${release}.tar.gz" # -O may not be unavailable. tar zxvf "$release.tar.gz" cd $release ./configure make
dbohdan: Updated the script once more to hammer Roy Keene's server less.