CC.tcl

CC.tcl is a compiler driver derived from critcl which is intended to convert C.tcl's output into loadable extensions. There is code here for cross-compilation, but that is completely untested. All I can claim is it works on my machine.

# CC - compile C code intertexted in Tcl
#
# slavishly derived and simplified from the excellent critcl
# Colin McCormack, Steve Landers, Jean-Claude Wippler other parties

package require platform
package provide CC 1.0

# CC - compiler driver
namespace eval CC {
    proc param {name content} {
        variable param
        foreach line [split $content \n] {
            set line [string trim $line]
            if {![regexp {(\S+)\s+(.*)} $line -> n v]} {
                dict set param $name $line ""
            } else {
                dict set param $name $n $v
            }
        }
    }

    #   platform        sets the platform (defaults to platform::generic)
    #   compile         compile a C source file to an object file
    #   version         print the compiler version number
    #   link            link one or more object files and create a shared library
    #   preproc_define  preprocess C source file (for critcl::cdefines)
    #   preproc_enum    ditto
    #   tclstubs        cflag to set USE_TCL_STUBS
    #   tkstubs         cflag to set USE_TK_STUBS
    #   debug_memory    cflag to enable memory debugging
    #   debug_symbols   cflag to add symbols to resulting library
    #   object          file extension for object files
    #   output          flag to set output file
    #   strip           cflag to tell linker to strip symbols
    #   optimize        cflag to specify optimization level
    #   include         cflag to add an include directory
    #   noassert        cflag to turn off assertions in Tcl code
    #   threadflags     cflags to enable threaded build
    #   target          indicates that this is a cross-compile target
    #   sharedlibext    the platform shared library extension
    #
    #   (Support for Fortran)
    #   fcompile        compile a Fortran source file to an object file
    #   fversion        print the Fortran compiler version number
    #   flink           link one or more object files and create a shared library,
    #                   if at least one object file comes from Fortran
    #   foutput         Fortran flag to set output file
    #   finclude        Fortran flag to add an include directory
    #   fextra_cflags   Extra C flags for indicating type of Fortran compiler

    param * {
        compile         gcc -c -fPIC
        version         gcc -v
        link            gcc -shared -Wl,--export-dynamic -fPIC
        include         -I
        preproc_define  gcc -E -dM
        preproc_enum    gcc -E
        tclstubs        -DUSE_TCL_STUBS
        tkstubs         -DUSE_TK_STUBS
        debug_memory    -DTCL_MEM_DEBUG
        debug_symbols   -ggdb3
        object          .o
        output          -o %OUTFILE%
        ldoutput
        link_debug        -ggdb3
        link_release
        link_preload    --unresolved-symbols=ignore-in-shared-libs
        _strip           -Wl,-s
        strip
        optimize        -O2
        noassert        -DNDEBUG
        threadflags     -DUSE_THREAD_ALLOC=1 -D_REENTRANT=1 -D_THREAD_SAFE=1 -DHAVE_PTHREAD_ATTR_SETSTACKSIZE=1 -DHAVE_READDIR_R=1 -DTCL_THREADS=1
    }
    param linux-* {
        sharedlibext .so
    }

    # default on OSX ppc is universal containing ppc and x86 32 bit
    param macosx-powerpc {
        compile      gcc -c -arch ppc
        link         gcc -bundle -arch ppc
        link_preload -undefined dynamic_lookup -mmacosx-version-min=10.3
        strip
    }

    # default on OSX intel is universal containing x86 32 and 64 bit
    param macosx-ix86 {
        compile         gcc -c -arch i386 -arch x86_64
        link            gcc -bundle -arch i386 -arch x86_64
        link_preload    -undefined dynamic_lookup -mmacosx-version-min=10.3
        strip
    }

    # target for most common macosx architectures
    param macosx-most {
        compile         gcc -c -arch i386 -arch x86_64 -arch ppc
        link            gcc -bundle -arch i386 -arch x86_64 -arch ppc
        link_preload    -undefined dynamic_lookup -mmacosx-version-min=10.3
        strip
    }

    # target for all macosx architectures
    param macosx-all {
        compile         gcc -c -arch i386 -arch x86_64 -arch ppc -arch ppc64
        link            gcc -bundle -arch i386 -arch x86_64 -arch ppc -arch ppc64
        link_preload    -undefined dynamic_lookup -mmacosx-version-min=10.3
        strip
    }

    # OSX ppc 32 bit
    param macosx-ppc32 {
        compile        gcc -c -arch ppc
        link           gcc -bundle -arch ppc
        link_preload   -undefined dynamic_lookup
        strip
    }

    # OSX ppc 64 bit
    param macosx-ppc64 {
        compile        gcc -c -arch ppc64
        link           gcc -bundle -arch ppc64
        link_preload   -undefined dynamic_lookup
        strip
    }

    # OSX x86 32 bit
    param macosx-x86_32 {
        compile       gcc -c -arch i386
        link          gcc -bundle -arch i386
        link_preload  -undefined dynamic_lookup
        strip
    }

    # OSX x86 64 bit
    param macosx-x86_64 {
        compile       gcc -c -arch x86_64
        link          gcc -bundle -arch x86_64
        link_preload  -undefined dynamic_lookup
        strip
    }

    # Linux - 32 bit or 64 bit build - select using "-target" if you don't want
    #         the platform default (32 on 32, 64 on 64)
    param linux-32-*  {
        compile   gcc -c -m32
    }
    param linux-64-*  {
        compile   gcc -c -m64
    }

    # Windows - using MSVC++ 6.0
    #
    # Note: the language option for cl is -TC for c and -TP for c++ or
    #       it can treat single files -Tc<filename>
    #
    param win32-ix86-cl {
        compile         cl -nologo -c
        link            link -nologo
        preproc_define  cl -nologo -E
        preproc_enum    cl -nologo -E
        object          .obj
        debug_symbols   -W3 -Od -Zi -GZ -MDd -D_DEBUG
        optimize        -W3 -O2 -Op -Gs -MD
        output          -Fo%OUTFILE%
        ldoutput        -dll -out:%OUTFILE%
        link_debug      -debug:full -debugtype:cv
        link_release    -release -opt:ref -opt:icf,3 -ws:aggressive
        link_preload
        strip
        version         cl
    }

    # Cross-compile for Windows using Xmingwin
    param mingw32 {
        compile        gcc -c -nostdlib
        link           gcc -shared
        link_preload
        sharedlibext  .dll
        tcl_platform(byteOrder)  littleEndian
        tcl_platform(machine)    intel
        tcl_platform(os)         Windows NT
        tcl_platform(osVersion)  5.0
        tcl_platform(platform)   windows
        tcl_platform(wordSize)   4
    }

    # Cross-compile for ARM (n770/Zaurus/etc) using Scratchbox et al
    param linux-arm {
        tcl_platform(byteOrder)  littleEndian
        tcl_platform(machine)    arm
        tcl_platform(os)         Linux
        tcl_platform(osVersion)  2.6
        tcl_platform(platform)   unix
        tcl_platform(wordSize)   4
    }

    # hpux itanium, native cc, 32 and 64bit builds.
    # +z <=> -fPIC on hpux.
    # +DD64 invokes the 64bit mode.
    param hpux-ia64_32 {
        compile         cc -c +z
        link            ld -b
        preproc_define  cc -E
        preproc_enum    cc -E
    }

    param hpux-ia64 {
        compile         cc -c +z +DD64
        link            ld -b
        preproc_define  cc -E
        preproc_enum    cc -E
    }

    # hpux, itanium, gcc
    # This works only if the -lgcc for 64bit is somewhere reachable.
    # hpux-ia64        gcc -c -fPIC -mlp64

    # aix, rs6000/powerpc, native cc, 32bit build
    # The link line is pretty much copied from Tcl.
    # NOTE: ldAix was copied from Tcl into a directory in the PATH.
    # It might make sense to stuff this file into critcl and then copy it out when
    # needed, either into a fixed place, or tempdir. In the latter case the link
    # line needs some way of getting the value substituted into it. I have no
    # idea of the critcl config allows that, and if yes, nor how.

    # cc_r = something with thread-enabled. better use it than cc and have things
    # fail.

    param aix-powerpc {
        compile         cc_r -c -O
        link            ldAix /bin/ld -bhalt:4 -bM:SRE -bE:lib.exp -H512 -T512 -bnoentry -lm -lc
        preproc_define  cc -E
        preproc_enum    cc -E
    }
    variable preload {}        ;# libs to preload
    variable libfile {}        ;# libs

    proc compile {ns args} {
        set copts ""
        set cflags ""
        set libs ""
        set ldflags ""
        if {![info exists platform] || $platform eq ""} {
            set platform [::platform::generic]
        }

        # find matching parameter sets
        variable param
        set matches {}
        dict for {n v} $param {
            #puts stderr "pmatch $n $platform: [string match $n $platform]"
            if {[string match $n $platform]} {
                lappend matches [string map {* \xff} $n]
            }
        }

        # roughly sort the matches into ascending order
        set matches [lsort -dictionary $matches]
        set matches [string map {\xff *} $matches]

        # combine matching parameter sets
        set p {}
        foreach n $matches {
            set p [dict merge $p [dict get $param $n]]
        }

        # get the C source and populate the command line
        set source [$ns generate]
        set sfd [file tempfile src]; puts $sfd $source; close $sfd
        set obj $src; file rename $src $src.c; append src .c

        dict with p {
            set cmdline "$compile $cflags $threadflags $tclstubs $copts"
            set obj $obj$object
            append cmdline " " [string map [list %OUTFILE% $obj] $output]

            # add the Tk stuff
            if {[dict exists $args tk] && [dict get $args tk]} {
                append cmdline " $tkstubs"
            }
            if {![dict exists $args optimize] || ![dict get $args optimize]} {
                append cmdline " $optimize"
            }
            if {![dict exists $args symbols] || [dict get $args symbols]} {
                append cmdline " $debug_symbols"
            }

            append cmdline " " [list $src]        ;# record the source file name in the cmdline
        }

        puts stderr "Compiling: $cmdline"

        if {[catch {
            exec {*}$cmdline
        } result eo]} {
            error "Compilation '$cmdline' failed: '$result' ($eo)"
        }

        # link the resultant .o file into an .so file
        lappend objs $obj
        set target [$ns package]
        set libs {-L/usr/lib -ltclstub8.5}
        # -ltcl8.6
        dict with p {
            set cmdline $link
            variable preload
            if {[llength $preload]} {
                append cmdline " $link_preload"
            }
            set outfile $target$sharedlibext
            set ldout [string map [list %OUTFILE% $outfile] $ldoutput]
            if {$ldout eq ""} {
                set ldout [string map [list %OUTFILE% $outfile] $output]
            }
            if {![dict exists $args symbols] || [dict get $args symbols]} {
                append cmdline " $link_debug $ldout"
            } else {
                append cmdline " $strip $link_release $ldout"
            }

            variable libfile
            append cmdline " " $libfile
            append cmdline " " $objs
            append cmdline " $libs $ldflags"
        }

        puts stderr "linking: $cmdline"
        if {[catch {
            exec {*}$cmdline
        } result eo]} {
            error "Linking '$cmdline' failed: '$result' ($eo)"
        }

        return $obj
    }

    ::namespace export -clear *
    ::namespace ensemble create -subcommands {}
}

if {[info exists argv0] && ($argv0 eq [info script])} {
    package require C
    C proc add {int x int y} int {
        return x + y;
    }

    C proc cube {int x} int {
        return x * x * x;
    }
    
    C package Junk

    puts [CC compile [C namespace]]

    load Junk.so
    puts [add 345 123]
    puts [cube 99]
}

AMG: To get this to work with Tcl 8.6 CVS HEAd compiled with --enable-symbols in MinGW, I had to make the following changes:

  • Remove both instances of "-fPIC" from param * {...}
  • Replace "--export-dynamic" with "--export-all-symbols" in param * {...}
  • Add "sharedlibext .dll" to param * {...}
  • Replace "set libs {-L/usr/lib -ltclstub8.5}" with "{-Lc:/msys/local/lib -ltclstub86g}"

I don't know the "right" way to make any of these changes; obviously my patched CC.tcl will only work in my exact configuration. I tried adding new "param win32" and "param win32-ix86" blocks, but -fPIC still got passed to the compiler.

Also. What, if anything, has to be done to get Tk support?