Version 0 of A Tcl_Obj Command Machine Code Generator

Updated 2007-11-02 10:46:24 by GPS

George Peter Staplin Nov 2, 2007 - I needed a break from working on NexTk, to avoid going insane. I decided it would be fun to try to generate some simple Tcl_Obj commands at runtime, without invoking gcc. This project was a success -- I'm still sane :) And the commands work too!


 /*
 A Tcl_Obj Command Machine Code Generator

 By George Peter Staplin

 gcc -shared codeproc.c -I/usr/local/include /usr/local/lib/libtclstub8.5.a -o codeproc.so

 gcc -Wall -shared codeproc.c -I/usr/local/include /usr/local/lib/libtclstub8.5.a -o codeproc.so 

 */

 #include <tcl.h>
 #include <string.h>
 #include <stdlib.h>
 #include <stdint.h>
 #include <sys/mman.h>
 #include <unistd.h>

 struct code {
   int localoffset;
   unsigned char *start;
   unsigned char *pc;
   size_t length;
 };

 static struct code *current_code = NULL;

 #define OBJ_CMD_ARGS ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]
 #define defcmd(func,name) \
  Tcl_CreateObjCommand (interp, name, func, (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL)


 static void
 dump_eax (struct code *code) {
   static char *fmt = "dump: 0x%x\n";
   uintptr_t fun = (uintptr_t)printf;
   uintptr_t fun2 = (uintptr_t)abort;
   unsigned char docall[] = {0xff, 0xd0};

   /*
7f
50 push %eax
80
8b 44 24 08 mov 0x8(%esp),%eax
84
50 push %eax
85
ff 75 0c pushl 0xc(%ebp)
88
b8 dd ee ff 00 mov $0xffeedd,%eax
8d
ff d0 call *%eax
   */

   *(code->pc) = 0x50; code->pc++; /*pushl*/

   *(code->pc) = 0xb8; code->pc++; /*mov*/
   memcpy (code->pc, &fmt, sizeof (char *));
   code->pc += sizeof (char *);

   *(code->pc) = 0x50; code->pc++; /*push*/

   *(code->pc) = 0xb8; code->pc++; /*mov*/
   memcpy (code->pc, &fun, sizeof fun);
   code->pc += sizeof fun;

   memcpy (code->pc, docall, sizeof docall);
   code->pc += sizeof docall;

   /*now abort()*/
   *(code->pc) = 0xb8; code->pc++; /*mov*/
   memcpy (code->pc, &fun2, sizeof fun2);
   code->pc+= sizeof fun2;

   memcpy (code->pc, docall, sizeof docall);
   code->pc += sizeof docall;
 }


 static int 
 long_cmd (OBJ_CMD_ARGS) {
   struct code *code = current_code;
   int i;

   for (i = 1; i < objc; ++i) {
     Tcl_Obj *obj = Tcl_NewIntObj (code->localoffset);
     Tcl_IncrRefCount (obj);
     if (NULL == Tcl_ObjSetVar2 (interp, objv[i], NULL, obj, TCL_LEAVE_ERR_MSG)) {
       Tcl_DecrRefCount (obj);
       return TCL_ERROR;
     }
     Tcl_DecrRefCount (obj);
     code->localoffset += sizeof (long);
   }

   return TCL_OK;
 }

 static int
 pointer_cmd (OBJ_CMD_ARGS) {
   struct code *code = current_code;
   int i;

   for (i = 1; i < objc; ++i) {
     Tcl_Obj *obj = Tcl_NewIntObj (code->localoffset);
     Tcl_IncrRefCount (obj);
     if (NULL == Tcl_ObjSetVar2 (interp, objv[i], NULL, obj, TCL_LEAVE_ERR_MSG)) {
       Tcl_DecrRefCount (obj);
       return TCL_ERROR;
     }
     Tcl_DecrRefCount (obj);
     code->localoffset += sizeof (void *);
   }

   return TCL_OK;
 }


 static int
 get_long_cmd (OBJ_CMD_ARGS) {
   struct code *code = current_code;
   uintptr_t fun = (uintptr_t)Tcl_GetLongFromObj;
   int objoffset, longoffset;
   unsigned char leal[] = {0x8d, 0x44, 0x24, /*modify*/ 0x00};
   unsigned char movl[] = {0x8b, 0x44, 0x24, /*modify*/ 0x00};
   unsigned char pushinterp[] = {0xff, 0x75, 0xc};

   if (3 != objc) {
     Tcl_WrongNumArgs (interp, 1, objv, "obj-local long-local");
     return TCL_ERROR;
   }

   if (TCL_OK != Tcl_GetIntFromObj (interp, objv[1], &objoffset))
     return TCL_ERROR;

   if (TCL_OK != Tcl_GetIntFromObj (interp, objv[2], &longoffset))
     return TCL_ERROR;

   /*
     leal long-local,%eax
     pushl %eax
     movl  obj-local,%eax
     pushl %eax
     pushl 12(%ebp)
7b
8d 44 24 04 lea 0x4(%esp),%eax
7f
50 push %eax
80
8b 44 24 08 mov 0x8(%esp),%eax
84
50 push %eax
85
ff 75 0c pushl 0xc(%ebp)
88
b8 dd ee ff 00 mov $0xffeedd,%eax
8d
ff d0 call *%eax
8f
83 c4 0c add $0xc,%esp
   */

   leal[3] = longoffset;
   memcpy (code->pc, leal, sizeof leal);
   code->pc += sizeof leal;
   *(code->pc) = 0x50; code->pc++; /*pushl*/

   printf ("longoffset %d\n", longoffset);

   movl[3] = objoffset + 4;
   memcpy (code->pc, movl, sizeof movl);
   code->pc += sizeof movl;
   *(code->pc) = 0x50; code->pc++; /*pushl*/

   printf ("objoffset %d\n", objoffset);

   memcpy (code->pc, pushinterp, sizeof pushinterp);
   code->pc += sizeof pushinterp;

   *(code->pc) = 0xb8; code->pc++; /*movl (literal)*/
   memcpy (code->pc, &fun, sizeof fun);
   code->pc += sizeof fun;

   *(code->pc) = 0xff; code->pc++; /*call fun*/
   *(code->pc) = 0xd0; code->pc++;

   *(code->pc) = 0x83; code->pc++; /*cleanup after the pushes*/
   *(code->pc) = 0xc4; code->pc++;
   *(code->pc) = 12; code->pc++;

   return TCL_OK;
 }

 static void
 code_free (struct code *code) {
   munmap (code->start, code->length);
   free (code);
 }

 static int
 code_init (Tcl_Interp *interp, struct code **code) {
   struct code *r;
   size_t s;

   r = malloc (sizeof *r);
   if (NULL == r) {
     const char *err = Tcl_PosixError (interp);
     Tcl_SetResult (interp, "unable to malloc", TCL_STATIC);
     Tcl_AddErrorInfo (interp, err);
     return TCL_ERROR;
   }

   s = sysconf (_SC_PAGESIZE);

   r->pc = r->start = mmap (NULL, s, PROT_EXEC | PROT_READ | PROT_WRITE, 
                                 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
   if (((void *) -1) == r->pc) {
     const char *err = Tcl_PosixError (interp);
     Tcl_SetResult (interp, "unable to mmap", TCL_STATIC);
     Tcl_AddErrorInfo (interp, err);
     free (r);
     return TCL_ERROR;
   }

   r->localoffset = sizeof (void *);
   r->length = s;

   *code = r;

   return TCL_OK;
 }

 static void
 enter_function (struct code *code) {
   *(code->pc) = 0x55; code->pc++; /* push %ebp */
   *(code->pc) = 0x89; code->pc++; 
   *(code->pc) = 0xe5; code->pc++; /* mov %esp,%ebp */
 }

 static void
 delete_compiled (ClientData cdata) {
   struct code *code = (void *)cdata;
   code_free (code);
 }

 static int 
 compile_cmd (OBJ_CMD_ARGS) {
   struct code *code;
   int (*fun) (OBJ_CMD_ARGS);

   if (TCL_OK != code_init (interp, &code))
     return TCL_ERROR;

   current_code = code;

   fun = (Tcl_ObjCmdProc *) code->start;

   printf ("code->start %p\n", (void *)code->start);

   enter_function (code);

   if (TCL_ERROR == Tcl_EvalObjEx (interp, objv[2], TCL_EVAL_DIRECT)) {
     code_free (code);
     return TCL_ERROR;
   }

   Tcl_CreateObjCommand (interp, Tcl_GetString (objv[1]), fun, (ClientData)code, delete_compiled);

   return TCL_OK;
 }

 static int
 begin_code_cmd (OBJ_CMD_ARGS) {
   struct code *code = current_code;

   /*
     83 ec 08                sub    $0x8,%esp
   */
   *code->pc = 0x83; code->pc++;
   *code->pc = 0xec; code->pc++;
   *code->pc = code->localoffset; code->pc++;


   return TCL_OK;
 }

 static int
 leave_cmd (OBJ_CMD_ARGS) {
   struct code *code = current_code;
   /*
      c9                      leave  
      c3                      ret 
   */

   *(code->pc) = 0xc9; code->pc++;
   *(code->pc) = 0xc3; code->pc++;

   return TCL_OK;
 }

 static int
 result_cmd (OBJ_CMD_ARGS) {
   struct code *code = current_code;
   char *s;
   int r;
   /*
     b8 00 00 00 00          mov    $0x0,%eax
   */
   if (2 != objc) {
     Tcl_WrongNumArgs (interp, 1, objv, "result");
     return TCL_ERROR;
   }

   s = Tcl_GetString (objv[1]);
   if (!strcmp ("ok", s)) {
     r = TCL_OK;
   } else if (!strcmp ("error", s)) {
     r = TCL_ERROR;
   }

   *(code->pc) = 0xb8; code->pc++;
   memcpy (code->pc, &r, 4);
   code->pc += 4;

   return TCL_OK;
 }

 static int
 get_arg_cmd (OBJ_CMD_ARGS) {
   struct code *code = current_code;
   int argvoffset, localoffset;
   unsigned char m[] = {
     0x8b, 0x4d, 0x14,
     0x83, 0xc1, /*argvoffset*/ 0x00,
     0x8b, 0x01,
     0x89, 0x44, 0x24, /*localoffset*/ 0x00
   };

   if (3 != objc) {
     Tcl_WrongNumArgs (interp, 1, objv, "argv-offset local");
     return TCL_ERROR;
   }

   if (TCL_OK != Tcl_GetIntFromObj (interp, objv[1], &argvoffset))
     return TCL_ERROR;

   if (TCL_OK != Tcl_GetIntFromObj (interp, objv[2], &localoffset))
     return TCL_ERROR;

   /*
7b
8b 4d 14 mov 0x14(%ebp),%ecx
7e
83 c1 04 add $0x4,%ecx
81
8b 01 mov (%ecx),%eax
83
89 44 24 04 mov %eax,0x4(%esp)
      (0x4 is objv[1])
      (0x8 is objv[2])
   */

   m[5] = argvoffset * 4;
   m[11] = localoffset;

   memcpy (code->pc, m, sizeof m);
   code->pc += sizeof m;

   return TCL_OK;
 }

 static int
 long_plus_cmd (OBJ_CMD_ARGS) {
   struct code *code = current_code;

   unsigned char movleax[] = {0x8b, 0x44, 0x24, /*modify*/ 0x00};
   unsigned char movlecx[] = {0x8b, 0x4c, 0x24, /*modify*/ 0x00};
   unsigned char addl[] = {0x01, 0xc8};
   unsigned char movlresult[] = {0x89, 0x44, 0x24, /*modify*/ 0x00};
   int a, b, r; /* these are local variable offsets */

   /*
7b
8b 44 24 04 mov 0x4(%esp),%eax
7f
8b 4c 24 08 mov 0x8(%esp),%ecx
83
01 c8 add %ecx,%eax
85
89 44 24 12 mov %eax,0x12(%esp)
   */

   if (4 != objc) {
     Tcl_WrongNumArgs (interp, 1, objv, "a b result");
     return TCL_ERROR;
   }

   if (TCL_OK != Tcl_GetIntFromObj (interp, objv[1], &a))
     return TCL_ERROR;

   if (TCL_OK != Tcl_GetIntFromObj (interp, objv[2], &b))
     return TCL_ERROR;

   if (TCL_OK != Tcl_GetIntFromObj (interp, objv[3], &r))
     return TCL_ERROR;

   movleax[3] = a;
   memcpy (code->pc, movleax, sizeof movleax);
   code->pc += sizeof movleax;

   movlecx[3] = b;
   memcpy (code->pc, movlecx, sizeof movlecx);
   code->pc += sizeof movlecx;

   memcpy (code->pc, addl, sizeof addl);
   code->pc += sizeof addl;

   movlresult[3] = r;
   memcpy (code->pc, movlresult, sizeof movlresult);
   code->pc += sizeof movlresult;

   return TCL_OK;
 }

 static int
 long_obj_cmd (OBJ_CMD_ARGS) {
   struct code *code = current_code;
   uintptr_t fun = (uintptr_t)Tcl_NewLongObj;
   unsigned char docall[] = {0xff, 0xd0};
   unsigned char movlocal[] = {0x8b, 0x44, 0x24, /*modify*/ 0x00};
   unsigned char movlresult[] = {0x89, 0x44, 0x24, /*modify*/ 0x00};
   int longoffset, objoffset;

   /*
7f
50 push %eax
80
8b 44 24 08 mov 0x8(%esp),%eax
84
50 push %eax
85
ff 75 0c pushl 0xc(%ebp)
88
b8 dd ee ff 00 mov $0xffeedd,%eax
8d
ff d0 call *%eax
   */

   if (3 != objc) {
     Tcl_WrongNumArgs (interp, 1, objv, "longoffset objoffset");
     return TCL_ERROR;    
   }

   if (TCL_OK != Tcl_GetIntFromObj (interp, objv[1], &longoffset))
     return TCL_ERROR;

   if (TCL_OK != Tcl_GetIntFromObj (interp, objv[2], &objoffset))
     return TCL_ERROR;

   movlocal[3] = longoffset;
   memcpy (code->pc, movlocal, sizeof movlocal);
   code->pc += sizeof movlocal;

   *(code->pc) = 0x50; code->pc++; /*push*/

   *(code->pc) = 0xb8; code->pc++; /*mov*/
   memcpy (code->pc, &fun, sizeof fun);
   code->pc += sizeof fun;

   memcpy (code->pc, docall, sizeof docall);
   code->pc += sizeof docall;

   *(code->pc) = 0x83; code->pc++; /*cleanup after the push*/
   *(code->pc) = 0xc4; code->pc++;
   *(code->pc) = 4; code->pc++;

   movlresult[3] = objoffset;
   memcpy (code->pc, movlresult, sizeof movlresult);
   code->pc += sizeof movlresult;

   return TCL_OK; 
 }

 static int
 set_result_cmd (OBJ_CMD_ARGS) {
   struct code *code = current_code;
   uintptr_t fun = (uintptr_t)Tcl_SetObjResult;
   unsigned char docall[] = {0xff, 0xd0};
   unsigned char movlocal[] = {0x8b, 0x44, 0x24, /*modify*/ 0x00};
   unsigned char pushinterp[] = {0xff, 0x75, 0xc};
   int objoffset;

   if (2 != objc) {
     Tcl_WrongNumArgs (interp, 1, objv, "objoffset");
     return TCL_ERROR;
   }

   if (TCL_OK != Tcl_GetIntFromObj (interp, objv[1], &objoffset))
     return TCL_ERROR;

   movlocal[3] = objoffset;
   memcpy (code->pc, movlocal, sizeof movlocal);
   code->pc += sizeof movlocal;

   *(code->pc) = 0x50; code->pc++; /*push*/

   /*push the interp*/
   memcpy (code->pc, pushinterp, sizeof pushinterp);
   code->pc += sizeof pushinterp;

   *(code->pc) = 0xb8; code->pc++; /*mov*/
   memcpy (code->pc, &fun, sizeof fun);
   code->pc += sizeof fun;

   memcpy (code->pc, docall, sizeof docall);
   code->pc += sizeof docall;

   /*cleanup the stack*/
   *(code->pc) = 0x83; code->pc++; /*cleanup after the push*/
   *(code->pc) = 0xc4; code->pc++;
   *(code->pc) = 4; code->pc++;

   return TCL_OK;
 }

 int Codeproc_Init (Tcl_Interp *interp) {
   if (NULL == Tcl_InitStubs (interp, TCL_VERSION, 0))
     return TCL_ERROR;

   if (TCL_ERROR == Tcl_PkgProvide (interp, "codeproc", "1.0"))
     return TCL_ERROR;

   defcmd (compile_cmd, "compile");
   defcmd (long_cmd, "long");
   defcmd (pointer_cmd, "pointer");
   defcmd (get_long_cmd, "get-long");
   defcmd (begin_code_cmd, "begin-code");
   defcmd (leave_cmd, "leave");
   defcmd (result_cmd, "result");
   defcmd (get_arg_cmd, "get-arg");
   defcmd (long_plus_cmd, "long+");
   defcmd (long_obj_cmd, "long-obj");
   defcmd (set_result_cmd, "set-result");

   return TCL_OK;
 }


 /*
  TEST CODE
  proc container {} {
   compile testadd {
    long a b c
    begin-code
    result ok
    leave
   }
   puts "$a $b $c"
  }


 load ./codeproc.so

 proc container {} {
   compile testadd {
       pointer aobj bobj robj
       long a b r
       begin-code
       get-arg 1 $aobj  
       get-long $aobj $a
       get-arg 2 $bobj
       get-long $bobj $b
       long+ $a $b $r
       long-obj $r $robj
       set-result $robj
       result ok
       leave
   }
   puts $aobj
 }

 container
 puts RESULT:[testadd 2 3]

 */