Index: /trunk/src/kmk/expand.c
===================================================================
--- /trunk/src/kmk/expand.c	(revision 2768)
+++ /trunk/src/kmk/expand.c	(revision 2769)
@@ -212,9 +212,9 @@
 
 #ifdef CONFIG_WITH_VALUE_LENGTH
-/* Static worker for reference_variable() that expands the recursive
+/* Worker for reference_variable() and kmk_exec_* that expands the recursive
    variable V. The main difference between this and
    recursively_expand[_for_file] is that this worker avoids the temporary
    buffer and outputs directly into the current variable buffer (O).  */
-static char *
+char *
 reference_recursive_variable (char *o, struct variable *v)
 {
@@ -1218,6 +1218,40 @@
 }
 
-/* Restore a previously-saved variable_buffer setting (free the current one).
- */
+#ifdef CONFIG_WITH_COMPILER
+/* Same as install_variable_buffer, except we supply a size hint.  */
+
+char *
+install_variable_buffer_with_hint (char **bufp, unsigned int *lenp, unsigned int size_hint)
+{
+  struct recycled_buffer *recycled;
+  char *buf;
+
+  *bufp = variable_buffer;
+  *lenp = variable_buffer_length;
+
+  recycled = recycled_head;
+  if (recycled)
+    {
+      recycled_head = recycled->next;
+      variable_buffer_length = recycled->length;
+      variable_buffer = buf = (char *)recycled;
+    }
+  else
+    {
+      if (size_hint < 512)
+        variable_buffer_length = (size_hint + 1 + 63) & ~(unsigned int)63;
+      else if (size_hint < 4096)
+        variable_buffer_length = (size_hint + 1 + 1023) & ~(unsigned int)1023;
+      else
+        variable_buffer_length = (size_hint + 1 + 4095) & ~(unsigned int)4095;
+      variable_buffer = buf = xmalloc (variable_buffer_length);
+    }
+  buf[0] = '\0';
+  return buf;
+}
+#endif /* CONFIG_WITH_COMPILER */
+
+/* Restore a previously-saved variable_buffer setting (free the
+   current one). */
 
 void
@@ -1234,2 +1268,23 @@
   variable_buffer_length = len;
 }
+
+
+/* Used to make sure there is at least SIZE bytes of buffer space
+   available starting at PTR.  */
+char *
+ensure_variable_buffer_space(char *ptr, unsigned int size)
+{
+  unsigned int offset = (unsigned int)(ptr - variable_buffer);
+  assert(offset <= variable_buffer_length);
+  if (variable_buffer_length - offset < size)
+    {
+      unsigned minlen = size + offset;
+      variable_buffer_length *= 2;
+      if (variable_buffer_length < minlen + 100)
+        variable_buffer_length = (minlen + 100 + 63) & ~(unsigned int)63;
+      variable_buffer = xrealloc (variable_buffer, variable_buffer_length);
+      ptr = variable_buffer + offset;
+    }
+  return ptr;
+}
+
Index: /trunk/src/kmk/kmk_cc_exec.c
===================================================================
--- /trunk/src/kmk/kmk_cc_exec.c	(revision 2768)
+++ /trunk/src/kmk/kmk_cc_exec.c	(revision 2769)
@@ -65,5 +65,19 @@
 typedef KMKCCBLOCK *PKMKCCBLOCK;
 
-/** Expansion instructions. */
+/**
+ * String expansion statistics.
+ */
+typedef struct KMKCCEXPSTATS
+{
+    /** Max expanded size. */
+    uint32_t                    cchMax;
+    /** Recent average size. */
+    uint32_t                    cchAvg;
+} KMKCCEXPSTATS;
+typedef KMKCCEXPSTATS *PKMKCCEXPSTATS;
+
+/**
+ * Expansion instructions.
+ */
 typedef enum KMKCCEXPINSTR
 {
@@ -74,8 +88,11 @@
     /** Insert an expanded variable value, the name is dynamic (sub prog). */
     kKmkCcExpInstr_DynamicVariable,
+    /** Insert an expanded variable value, which name we already know, doing
+     * search an replace on a string. */
+    kKmkCcExpInstr_SearchAndReplacePlainVariable,
     /** Insert the output of function that requires no argument expansion. */
     kKmkCcExpInstr_PlainFunction,
     /** Insert the output of function that requires dynamic expansion of one ore
-     * more arguments. */
+     * more arguments.  (Dynamic is perhaps not such a great name, but whatever.) */
     kKmkCcExpInstr_DynamicFunction,
     /** Jump to a new instruction block. */
@@ -95,15 +112,19 @@
 typedef KMKCCEXPCORE *PKMKCCEXPCORE;
 
+/**
+ * String expansion sub program.
+ */
 typedef struct kmk_cc_exp_subprog
 {
     /** Pointer to the first instruction. */
     PKMKCCEXPCORE           pFirstInstr;
-    /** Max expanded size. */
-    uint32_t                cbMax;
-    /** Recent average size. */
-    uint32_t                cbAvg;
+    /** Statistics. */
+    KMKCCEXPSTATS           Stats;
 } KMKCCEXPSUBPROG;
 typedef KMKCCEXPSUBPROG *PKMKCCEXPSUBPROG;
 
+/**
+ * kKmkCcExpInstr_CopyString instruction format.
+ */
 typedef struct kmk_cc_exp_copy_string
 {
@@ -117,21 +138,25 @@
 typedef KMKCCEXPCOPYSTRING *PKMKCCEXPCOPYSTRING;
 
+/**
+ * kKmkCcExpInstr_PlainVariable instruction format.
+ */
 typedef struct kmk_cc_exp_plain_variable
 {
     /** The core instruction. */
     KMKCCEXPCORE            Core;
-    /** The variable strcache entry for this variable. */
-    struct strcache2_entry const *pNameEntry;
+    /** The name of the variable (points into variable_strcache). */
+    const char             *pszName;
 } KMKCCEXPPLAINVAR;
 typedef KMKCCEXPPLAINVAR *PKMKCCEXPPLAINVAR;
 
+/**
+ * kKmkCcExpInstr_DynamicVariable instruction format.
+ */
 typedef struct kmk_cc_exp_dynamic_variable
 {
     /** The core instruction. */
     KMKCCEXPCORE            Core;
-    /** Where to continue after this instruction.  This is necessary since the
-     * subprogram is allocated after us in the instruction block.  Since the sub
-     * program is of variable size, we don't even know if we're still in the same
-     * instruction block.  So, we include a jump here. */
+    /** Where to continue after this instruction.  (This is necessary since the
+     * instructions of the subprogram are emitted after this instruction.) */
     PKMKCCEXPCORE           pNext;
     /** The subprogram that will give us the variable name. */
@@ -140,4 +165,31 @@
 typedef KMKCCEXPDYNVAR *PKMKCCEXPDYNVAR;
 
+/**
+ * kKmkCcExpInstr_SearchAndReplacePlainVariable instruction format.
+ */
+typedef struct kmk_cc_exp_sr_plain_variable
+{
+    /** The core instruction. */
+    KMKCCEXPCORE            Core;
+    /** Where to continue after this instruction.  (This is necessary since the
+     * instruction contains string data of variable size.) */
+    PKMKCCEXPCORE           pNext;
+    /** The name of the variable (points into variable_strcache). */
+    const char             *pszName;
+    /** Search pattern.  */
+    const char             *pszSearchPattern;
+    /** Replacement pattern. */
+    const char             *pszReplacePattern;
+    /** Offset into pszSearchPattern of the significant '%' char. */
+    uint32_t                offPctSearchPattern;
+    /** Offset into pszReplacePattern of the significant '%' char. */
+    uint32_t                offPctReplacePattern;
+} KMKCCEXPSRPLAINVAR;
+typedef KMKCCEXPSRPLAINVAR *PKMKCCEXPSRPLAINVAR;
+
+/**
+ * Instruction format parts common to both kKmkCcExpInstr_PlainFunction and
+ * kKmkCcExpInstr_DynamicFunction.
+ */
 typedef struct kmk_cc_exp_function_core
 {
@@ -146,7 +198,6 @@
     /** Number of arguments. */
     uint32_t                cArgs;
-    /** Where to continue after this instruction.  This is necessary since the
-     * instruction is of variable size and we don't even know if we're still in the
-     * same instruction block.  So, we include a jump here. */
+    /** Where to continue after this instruction.  (This is necessary since the
+     * instructions are of variable size and may be followed by string data.) */
     PKMKCCEXPCORE           pNext;
     /**
@@ -164,4 +215,7 @@
 typedef KMKCCEXPFUNCCORE *PKMKCCEXPFUNCCORE;
 
+/**
+ * Instruction format for kKmkCcExpInstr_PlainFunction.
+ */
 typedef struct kmk_cc_exp_plain_function
 {
@@ -178,4 +232,7 @@
 #define KMKCCEXPPLAINFUNC_SIZE(a_cArgs)  (sizeof(KMKCCEXPFUNCCORE) + (a_cArgs + 1) * sizeof(const char *))
 
+/**
+ * Instruction format for kKmkCcExpInstr_DynamicFunction.
+ */
 typedef struct kmk_cc_exp_dyn_function
 {
@@ -209,4 +266,7 @@
                                           + (a_cArgs) * sizeof(((PKMKCCEXPDYNFUNC)(uintptr_t)42)->aArgs[0]) )
 
+/**
+ * Instruction format for kKmkCcExpInstr_Jump.
+ */
 typedef struct kmk_cc_exp_jump
 {
@@ -227,8 +287,6 @@
     /** List of blocks for this program (LIFO). */
     PKMKCCBLOCK             pBlockTail;
-    /** Max expanded size. */
-    uint32_t                cbMax;
-    /** Recent average size. */
-    uint32_t                cbAvg;
+    /** Statistics. */
+    KMKCCEXPSTATS           Stats;
 } KMKCCEXPPROG;
 /** Pointer to a string expansion program. */
@@ -245,4 +303,5 @@
 *******************************************************************************/
 static int kmk_cc_exp_compile_subprog(PKMKCCBLOCK *ppBlockTail, const char *pchStr, uint32_t cchStr, PKMKCCEXPSUBPROG pSubProg);
+static char *kmk_exec_expand_subprog_to_tmp(PKMKCCEXPSUBPROG pSubProg, uint32_t *pcch);
 
 
@@ -741,5 +800,6 @@
 
 /**
- * Emits a kKmkCcExpInstr_PlainVariable.
+ * Emits either a kKmkCcExpInstr_PlainVariable or
+ * kKmkCcExpInstr_SearchAndReplacePlainVariable instruction.
  *
  * @returns 0 on success, non-zero on failure.
@@ -750,11 +810,85 @@
  *                              nothing will be emitted.
  */
-static int kmk_cc_exp_emit_plain_variable(PKMKCCBLOCK *ppBlockTail, const char *pchName, uint32_t cchName)
+static int kmk_cc_exp_emit_plain_variable_maybe_sr(PKMKCCBLOCK *ppBlockTail, const char *pchName, uint32_t cchName)
 {
     if (cchName > 0)
     {
-        PKMKCCEXPPLAINVAR pInstr = (PKMKCCEXPPLAINVAR)kmk_cc_block_alloc_exp(ppBlockTail, sizeof(*pInstr));
-        pInstr->Core.enmOpCode = kKmkCcExpInstr_PlainVariable;
-        pInstr->pNameEntry = strcache2_get_entry(&variable_strcache, strcache2_add(&variable_strcache, pchName, cchName));
+        /*
+         * Hopefully, we're not expected to do any search and replace on the
+         * expanded variable string later...  Requires both ':' and '='.
+         */
+        const char *pchEqual;
+        const char *pchColon = (const char *)memchr(pchName, ':', cchName);
+        if (   pchColon == NULL
+            || (pchEqual = (const char *)memchr(pchColon + 1, ':', cchName - (pchColon - pchName - 1))) == NULL
+            || pchEqual == pchEqual + 1)
+        {
+            PKMKCCEXPPLAINVAR pInstr = (PKMKCCEXPPLAINVAR)kmk_cc_block_alloc_exp(ppBlockTail, sizeof(*pInstr));
+            pInstr->Core.enmOpCode = kKmkCcExpInstr_PlainVariable;
+            pInstr->pszName = strcache2_add(&variable_strcache, pchName, cchName);
+        }
+        else if (pchColon != pchName)
+        {
+            /*
+             * Okay, we need to do search and replace the variable value.
+             * This is performed by patsubst_expand_pat using '%' patterns.
+             */
+            uint32_t            cchName2   = (uint32_t)(pchColon - pchName);
+            uint32_t            cchSearch  = (uint32_t)(pchEqual - pchColon - 1);
+            uint32_t            cchReplace = cchName - cchName2 - cchSearch - 2;
+            const char         *pchPct;
+            char               *psz;
+            PKMKCCEXPSRPLAINVAR pInstr;
+
+            pInstr = (PKMKCCEXPSRPLAINVAR)kmk_cc_block_alloc_exp(ppBlockTail, sizeof(*pInstr));
+            pInstr->Core.enmOpCode = kKmkCcExpInstr_SearchAndReplacePlainVariable;
+            pInstr->pszName = strcache2_add(&variable_strcache, pchName, cchName2);
+
+            /* Figure out the search pattern, unquoting percent chars.. */
+            psz = (char *)kmk_cc_block_byte_alloc(ppBlockTail, cchSearch + 2);
+            psz[0] = '%';
+            memcpy(psz + 1, pchColon + 1, cchSearch);
+            psz[1 + cchSearch] = '\0';
+            pchPct = find_percent(psz + 1); /* also performs unquoting */
+            if (pchPct)
+            {
+                pInstr->pszSearchPattern    = psz + 1;
+                pInstr->offPctSearchPattern = (uint32_t)(pchPct - psz - 1);
+            }
+            else
+            {
+                pInstr->pszSearchPattern    = psz;
+                pInstr->offPctSearchPattern = 0;
+            }
+
+            /* Figure out the replacement pattern, unquoting percent chars.. */
+            if (cchReplace == 0)
+            {
+                pInstr->pszReplacePattern    = "%";
+                pInstr->offPctReplacePattern = 0;
+            }
+            else
+            {
+                psz = (char *)kmk_cc_block_byte_alloc(ppBlockTail, cchReplace + 2);
+                psz[0] = '%';
+                memcpy(psz + 1, pchEqual + 1, cchReplace);
+                psz[1 + cchReplace] = '\0';
+                pchPct = find_percent(psz + 1); /* also performs unquoting */
+                if (pchPct)
+                {
+                    pInstr->pszReplacePattern    = psz + 1;
+                    pInstr->offPctReplacePattern = (uint32_t)(pchPct - psz - 1);
+                }
+                else
+                {
+                    pInstr->pszReplacePattern    = psz;
+                    pInstr->offPctReplacePattern = 0;
+                }
+            }
+
+            /* Note down where the next instruction is after realigning the allocator. */
+            kmk_cc_block_realign(ppBlockTail);
+            pInstr->pNext = (PKMKCCEXPCORE)kmk_cc_block_get_next_ptr(*ppBlockTail);
+        }
     }
     return 0;
@@ -837,8 +971,19 @@
                     {
                         /* There are several alternative ways of finding the ending
-                           parenthesis / braces.  GNU make only consideres open &
-                           close chars of the one we're processing, and it does not
-                           matter whether the opening paren / braces are preceeded by
-                           any dollar char.  Simple and efficient.  */
+                           parenthesis / braces.
+
+                           GNU make does one thing for functions and variable containing
+                           any '$' chars before the first closing char.  While for
+                           variables where a closing char comes before any '$' char, a
+                           simplified approach is taken.  This means that for example:
+
+                                Given VAR=var, the expressions "$(var())" and
+                                "$($(VAR)())" would be expanded differently.
+                                In the first case the variable "var(" would be
+                                used and in the second "var()".
+
+                           This code will not duplicate this weird behavior, but work
+                           the same regardless of whether there is a '$' char before
+                           the first closing char. */
                         make_function_ptr_t pfnFunction;
                         const char         *pszFunction;
@@ -865,4 +1010,5 @@
                             cchName++;
                         }
+
                         if (   cchName >= MIN_FUNCTION_LENGTH
                             && cchName <= MAX_FUNCTION_LENGTH
@@ -1002,5 +1148,5 @@
                             }
                             if (cDollars == 0)
-                                rc = kmk_cc_exp_emit_plain_variable(ppBlockTail, pchStr, cchName);
+                                rc = kmk_cc_exp_emit_plain_variable_maybe_sr(ppBlockTail, pchStr, cchName);
                             else
                                 rc = kmk_cc_exp_emit_dyn_variable(ppBlockTail, pchStr, cchName);
@@ -1012,5 +1158,5 @@
                     {
                         /* Single character variable name. */
-                        rc = kmk_cc_exp_emit_plain_variable(ppBlockTail, pchStr, 1);
+                        rc = kmk_cc_exp_emit_plain_variable_maybe_sr(ppBlockTail, pchStr, 1);
                         pchStr++;
                         cchStr--;
@@ -1046,4 +1192,15 @@
 
 /**
+ * Initializes string expansion program statistics.
+ * @param   pStats              Pointer to the statistics structure to init.
+ */
+static void kmk_cc_exp_stats_init(PKMKCCEXPSTATS pStats)
+{
+    pStats->cchAvg = 0;
+    pStats->cchMax = 0;
+}
+
+
+/**
  * Compiles a string expansion sub program.
  *
@@ -1064,6 +1221,5 @@
     assert(cchStr);
     pSubProg->pFirstInstr = (PKMKCCEXPCORE)kmk_cc_block_get_next_ptr(*ppBlockTail);
-    pSubProg->cbMax = 0;
-    pSubProg->cbAvg = 0;
+    kmk_cc_exp_stats_init(&pSubProg->Stats);
     return kmk_cc_exp_compile_common(ppBlockTail, pchStr, cchStr);
 }
@@ -1095,6 +1251,5 @@
         pProg->pBlockTail   = pBlock;
         pProg->pFirstInstr  = (PKMKCCEXPCORE)kmk_cc_block_get_next_ptr(pBlock);
-        pProg->cbMax        = 0;
-        pProg->cbAvg        = 0;
+        kmk_cc_exp_stats_init(&pProg->Stats);
 
         /*
@@ -1145,4 +1300,353 @@
 
 
+#ifndef NDEBUG
+/**
+ * Used to check that function arguments are left alone.
+ * @returns Updated hash.
+ * @param   uHash       The current hash value.
+ * @param   psz         The string to hash.
+ */
+static uint32_t kmk_exec_debug_string_hash(uint32_t uHash, const char *psz)
+{
+    unsigned char ch;
+    while ((ch = *(unsigned char const *)psz++) != '\0')
+        uHash = (uHash << 6) + (uHash << 16) - uHash + (unsigned char)ch;
+    return uHash;
+}
+#endif
+
+
+/**
+ * String expansion execution worker for outputting a variable.
+ *
+ * @returns The new variable buffer position.
+ * @param   pVar        The variable to reference.
+ * @param   pchDst      The current variable buffer position.
+ */
+static char *kmk_exec_expand_worker_reference_variable(struct variable *pVar, char *pchDst)
+{
+    if (pVar->value_length > 0)
+    {
+        if (!pVar->recursive)
+            pchDst = variable_buffer_output(pchDst, pVar->value, pVar->value_length);
+        else
+            pchDst = reference_recursive_variable(pchDst, pVar);
+    }
+    else if (pVar->append)
+        pchDst = reference_recursive_variable(pchDst, pVar);
+    return pchDst;
+}
+
+
+/**
+ * Executes a stream string expansion instructions, outputting to the current
+ * varaible buffer.
+ *
+ * @returns The new variable buffer position.
+ * @param   pInstrCore      The instruction to start executing at.
+ * @param   pchDst          The current variable buffer position.
+ */
+static char *kmk_exec_expand_instruction_stream_to_var_buf(PKMKCCEXPCORE pInstrCore, char *pchDst)
+{
+    for (;;)
+    {
+        switch (pInstrCore->enmOpCode)
+        {
+            case kKmkCcExpInstr_CopyString:
+            {
+                PKMKCCEXPCOPYSTRING pInstr = (PKMKCCEXPCOPYSTRING)pInstrCore;
+                pchDst = variable_buffer_output(pchDst, pInstr->pachSrc, pInstr->cchCopy);
+
+                pInstrCore = &(pInstr + 1)->Core;
+                break;
+            }
+
+            case kKmkCcExpInstr_PlainVariable:
+            {
+                PKMKCCEXPPLAINVAR pInstr = (PKMKCCEXPPLAINVAR)pInstrCore;
+                struct variable  *pVar = lookup_variable_strcached(pInstr->pszName);
+                if (pVar)
+                    pchDst = kmk_exec_expand_worker_reference_variable(pVar, pchDst);
+                else
+                    warn_undefined(pInstr->pszName, strcache2_get_len(&variable_strcache, pInstr->pszName));
+
+                pInstrCore = &(pInstr + 1)->Core;
+                break;
+            }
+
+            case kKmkCcExpInstr_DynamicVariable:
+            {
+                PKMKCCEXPDYNVAR  pInstr = (PKMKCCEXPDYNVAR)pInstrCore;
+                struct variable *pVar;
+                uint32_t         cchName;
+                char            *pszName = kmk_exec_expand_subprog_to_tmp(&pInstr->SubProg, &cchName);
+                char            *pszColon = (char *)memchr(pszName, ':', cchName);
+                char            *pszEqual;
+                if (   pszColon == NULL
+                    || (pszEqual = (char *)memchr(pszColon + 1, '=', &pszName[cchName] - pszColon - 1)) == NULL
+                    || pszEqual == pszColon + 1)
+                {
+                    pVar = lookup_variable(pszName, cchName);
+                    if (pVar)
+                        pchDst = kmk_exec_expand_worker_reference_variable(pVar, pchDst);
+                    else
+                        warn_undefined(pszName, cchName);
+                }
+                else if (pszColon != pszName)
+                {
+                    /*
+                     * Oh, we have to do search and replace. How tedious.
+                     * Since the variable name is a temporary buffer, we can transform
+                     * the strings into proper search and replacement patterns directly.
+                     */
+                    pVar = lookup_variable(pszName, pszColon - pszName);
+                    if (pVar)
+                    {
+                        char const *pszExpandedVarValue = pVar->recursive ? recursively_expand(pVar) : pVar->value;
+                        char       *pszSearchPat  = pszColon + 1;
+                        char       *pszReplacePat = pszEqual + 1;
+                        const char *pchPctSearchPat;
+                        const char *pchPctReplacePat;
+
+                        *pszEqual = '\0';
+                        pchPctSearchPat = find_percent(pszSearchPat);
+                        pchPctReplacePat = find_percent(pszReplacePat);
+
+                        if (!pchPctReplacePat)
+                        {
+                            if (pszReplacePat[-2] != '\0') /* On the offchance that a pct was unquoted by find_percent. */
+                            {
+                                memmove(pszName + 1, pszSearchPat, pszReplacePat - pszSearchPat);
+                                if (pchPctSearchPat)
+                                    pchPctSearchPat -= pszSearchPat - &pszName[1];
+                                pszSearchPat = &pszName[1];
+                            }
+                            pchPctReplacePat = --pszReplacePat;
+                            *pszReplacePat = '%';
+                        }
+
+                        if (!pchPctSearchPat)
+                        {
+                            pchPctSearchPat = --pszSearchPat;
+                            *pszSearchPat = '%';
+                        }
+
+                        pchDst = patsubst_expand_pat(pchDst, pszExpandedVarValue,
+                                                     pszSearchPat, pszReplacePat,
+                                                     pchPctSearchPat, pchPctReplacePat);
+
+                        if (pVar->recursive)
+                            free((void *)pszExpandedVarValue);
+                    }
+                    else
+                        warn_undefined(pszName, pszColon - pszName);
+                }
+                free(pszName);
+
+                pInstrCore = pInstr->pNext;
+                break;
+            }
+
+
+            case kKmkCcExpInstr_SearchAndReplacePlainVariable:
+            {
+                PKMKCCEXPSRPLAINVAR pInstr = (PKMKCCEXPSRPLAINVAR)pInstrCore;
+                struct variable    *pVar = lookup_variable_strcached(pInstr->pszName);
+                if (pVar)
+                {
+                    char const *pszExpandedVarValue = pVar->recursive ? recursively_expand(pVar) : pVar->value;
+                    pchDst = patsubst_expand_pat(pchDst,
+                                                 pszExpandedVarValue,
+                                                 pInstr->pszSearchPattern,
+                                                 pInstr->pszReplacePattern,
+                                                 &pInstr->pszSearchPattern[pInstr->offPctSearchPattern],
+                                                 &pInstr->pszReplacePattern[pInstr->offPctReplacePattern]);
+                    if (pVar->recursive)
+                        free((void *)pszExpandedVarValue);
+                }
+                else
+                    warn_undefined(pInstr->pszName, strcache2_get_len(&variable_strcache, pInstr->pszName));
+
+                pInstrCore = pInstr->pNext;
+                break;
+            }
+
+            case kKmkCcExpInstr_PlainFunction:
+            {
+                PKMKCCEXPPLAINFUNC pInstr = (PKMKCCEXPPLAINFUNC)pInstrCore;
+#ifndef NDEBUG
+                uint32_t uCrcBefore = 0;
+                uint32_t uCrcAfter = 0;
+                uint32_t iArg = pInstr->Core.cArgs;
+                while (iArg-- > 0)
+                    uCrcBefore = kmk_exec_debug_string_hash(uCrcBefore, pInstr->apszArgs[iArg]);
+#endif
+
+                pchDst = pInstr->Core.pfnFunction(pchDst, (char **)&pInstr->apszArgs[0], pInstr->Core.pszFuncName);
+
+#ifndef NDEBUG
+                iArg = pInstr->Core.cArgs;
+                while (iArg-- > 0)
+                    uCrcAfter = kmk_exec_debug_string_hash(uCrcAfter, pInstr->apszArgs[iArg]);
+                assert(uCrcBefore == uCrcAfter);
+#endif
+
+                pInstrCore = pInstr->Core.pNext;
+                break;
+            }
+
+            case kKmkCcExpInstr_DynamicFunction:
+            {
+                PKMKCCEXPDYNFUNC pInstr = (PKMKCCEXPDYNFUNC)pInstrCore;
+                char           **papszArgsShadow = xmalloc( (pInstr->Core.cArgs * 2 + 1) * sizeof(char *));
+                char           **papszArgs = &papszArgsShadow[pInstr->Core.cArgs];
+                uint32_t         iArg;
+#ifndef NDEBUG
+                uint32_t         uCrcBefore = 0;
+                uint32_t         uCrcAfter = 0;
+#endif
+                iArg = pInstr->Core.cArgs;
+                papszArgs[iArg] = NULL;
+                while (iArg-- > 0)
+                {
+                    char *pszArg;
+                    if (pInstr->aArgs[iArg].fPlain)
+                        pszArg = (char *)pInstr->aArgs[iArg].u.Plain.pszArg;
+                    else
+                        pszArg = kmk_exec_expand_subprog_to_tmp(&pInstr->aArgs[iArg].u.SubProg, NULL);
+                    papszArgsShadow[iArg] = pszArg;
+                    papszArgs[iArg]       = pszArg;
+#ifndef NDEBUG
+                    uCrcBefore = kmk_exec_debug_string_hash(uCrcBefore, pszArg);
+#endif
+                }
+
+                pchDst = pInstr->Core.pfnFunction(pchDst, papszArgs, pInstr->Core.pszFuncName);
+
+                iArg = pInstr->Core.cArgs;
+                while (iArg-- > 0)
+                {
+#ifndef NDEBUG
+                    assert(papszArgsShadow[iArg] == papszArgs[iArg]);
+                    uCrcAfter = kmk_exec_debug_string_hash(uCrcAfter, papszArgsShadow[iArg]);
+#endif
+                    if (!pInstr->aArgs[iArg].fPlain)
+                        free(papszArgsShadow);
+                }
+                assert(uCrcBefore == uCrcAfter);
+                free(papszArgsShadow);
+
+                pInstrCore = pInstr->Core.pNext;
+                break;
+            }
+
+            case kKmkCcExpInstr_Jump:
+            {
+                PKMKCCEXPJUMP pInstr = (PKMKCCEXPJUMP)pInstrCore;
+                pInstrCore = pInstr->pNext;
+                break;
+            }
+
+            case kKmkCcExpInstr_Return:
+                return pchDst;
+
+            default:
+                fatal(NULL, _("Unknown string expansion opcode: %d (%#x)"),
+                      (int)pInstrCore->enmOpCode, (int)pInstrCore->enmOpCode);
+                return NULL;
+        }
+    }
+}
+
+
+/**
+ * Updates the string expansion statistics.
+ *
+ * @param   pStats              The statistics structure to update.
+ * @param   cchResult           The result lenght.
+ */
+void kmk_cc_exp_stats_update(PKMKCCEXPSTATS pStats, uint32_t cchResult)
+{
+    /*
+     * Keep statistics on output size.  The average is simplified and not an
+     * exact average for every expansion that has taken place.
+     */
+    if (cchResult > pStats->cchMax)
+    {
+        if (pStats->cchMax)
+            pStats->cchAvg = cchResult;
+        pStats->cchMax = cchResult;
+    }
+    pStats->cchAvg = (pStats->cchAvg * 7 + cchResult) / 8;
+}
+
+
+/**
+ * Execute a string expansion sub-program, outputting to a new heap buffer.
+ *
+ * @returns Pointer to the output buffer (hand to free when done).
+ * @param   pSubProg          The sub-program to execute.
+ * @param   pcchResult        Where to return the size of the result. Optional.
+ */
+static char *kmk_exec_expand_subprog_to_tmp(PKMKCCEXPSUBPROG pSubProg, uint32_t *pcchResult)
+{
+    char           *pchOldVarBuf;
+    unsigned int    cbOldVarBuf;
+    char           *pchDst;
+    char           *pszResult;
+    uint32_t        cchResult;
+
+    /*
+     * Temporarily replace the variable buffer while executing the instruction
+     * stream for this sub program.
+     */
+    pchDst = install_variable_buffer_with_hint(&pchOldVarBuf, &cbOldVarBuf,
+                                               pSubProg->Stats.cchAvg ? pSubProg->Stats.cchAvg + 32 : 256);
+
+    pchDst = kmk_exec_expand_instruction_stream_to_var_buf(pSubProg->pFirstInstr, pchDst);
+
+    /* Ensure that it's terminated. */
+    pchDst = variable_buffer_output(pchDst, "\0", 1) - 1;
+
+    /* Grab the result buffer before restoring the previous one. */
+    pszResult = variable_buffer;
+    cchResult = (uint32_t)(pchDst - pszResult);
+    if (pcchResult)
+        *pcchResult = cchResult;
+    kmk_cc_exp_stats_update(&pSubProg->Stats, cchResult);
+
+    restore_variable_buffer(pchOldVarBuf, cbOldVarBuf);
+
+    return pszResult;
+}
+
+
+/**
+ * Execute a string expansion program, outputting to the current variable
+ * buffer.
+ *
+ * @returns New variable buffer position.
+ * @param   pProg               The program to execute.
+ * @param   pchDst              The current varaible buffer position.
+ */
+static char *kmk_exec_expand_prog_to_var_buf(PKMKCCEXPPROG pProg, char *pchDst)
+{
+    uint32_t cchResult;
+    uint32_t offStart = (uint32_t)(pchDst - variable_buffer);
+
+    if (pProg->Stats.cchAvg >= variable_buffer_length - offStart)
+        pchDst = ensure_variable_buffer_space(pchDst, offStart + pProg->Stats.cchAvg + 32);
+
+    pchDst = kmk_exec_expand_instruction_stream_to_var_buf(pProg->pFirstInstr, pchDst);
+
+    cchResult = (uint32_t)(pchDst - variable_buffer);
+    assert(cchResult >= offStart);
+    cchResult -= offStart;
+    kmk_cc_exp_stats_update(&pProg->Stats, cchResult);
+
+    return pchDst;
+}
+
+
 /**
  * Equivalent of eval_buffer, only it's using the evalprog of the variable.
@@ -1167,6 +1671,5 @@
 {
     assert(pVar->expandprog);
-    assert(0);
-    return pchDst;
+    return kmk_exec_expand_prog_to_var_buf(pVar->expandprog, pchDst);
 }
 
@@ -1180,4 +1683,16 @@
 {
     assert(pVar->evalprog || pVar->expandprog);
+#if 0
+    if (pVar->evalprog)
+    {
+        kmk_cc_block_free_list(pVar->evalprog->pBlockTail);
+        pVar->evalprog = NULL;
+    }
+#endif
+    if (pVar->expandprog)
+    {
+        kmk_cc_block_free_list(pVar->expandprog->pBlockTail);
+        pVar->expandprog = NULL;
+    }
 }
 
@@ -1191,4 +1706,16 @@
 {
     assert(pVar->evalprog || pVar->expandprog);
+#if 0
+    if (pVar->evalprog)
+    {
+        kmk_cc_block_free_list(pVar->evalprog->pBlockTail);
+        pVar->evalprog = NULL;
+    }
+#endif
+    if (pVar->expandprog)
+    {
+        kmk_cc_block_free_list(pVar->expandprog->pBlockTail);
+        pVar->expandprog = NULL;
+    }
 }
 
Index: /trunk/src/kmk/variable.c
===================================================================
--- /trunk/src/kmk/variable.c	(revision 2768)
+++ /trunk/src/kmk/variable.c	(revision 2769)
@@ -1035,4 +1035,74 @@
   return 0;
 }
+
+#ifdef CONFIG_WITH_STRCACHE2
+/* Alternative version of lookup_variable that takes a name that's already in
+   the variable string cache. */
+struct variable *
+lookup_variable_strcached (const char *name)
+{
+  struct variable *v;
+#if 1 /*FIX THIS - ndef KMK*/
+  const struct variable_set_list *setlist;
+  struct variable var_key;
+#endif /* KMK */
+  int is_parent = 0;
+
+#ifndef NDEBUG
+  strcache2_verify_entry (&variable_strcache, name);
+#endif
+
+#ifdef KMK
+  /* Check for kBuild-define- local variable accesses and handle these first. */
+  if (strcache2_get_len(&variable_strcache, name) > 3 && name[0] == '[')
+    {
+      v = lookup_kbuild_object_variable_accessor(name, strcache2_get_len(&variable_strcache, name));
+      if (v != VAR_NOT_KBUILD_ACCESSOR)
+        {
+          MAKE_STATS_2 (v->references++);
+          return v;
+        }
+    }
+#endif
+
+#if 1  /*FIX THIS - ndef KMK */
+
+  var_key.name = (char *) name;
+  var_key.length = strcache2_get_len(&variable_strcache, name);
+
+  for (setlist = current_variable_set_list;
+       setlist != 0; setlist = setlist->next)
+    {
+      const struct variable_set *set = setlist->set;
+
+      v = (struct variable *) hash_find_item_strcached ((struct hash_table *) &set->table, &var_key);
+      if (v && (!is_parent || !v->private_var))
+        {
+# ifdef KMK
+          RESOLVE_ALIAS_VARIABLE(v);
+# endif
+          MAKE_STATS_2 (v->references++);
+	   return v->special ? lookup_special_var (v) : v;
+        }
+
+      is_parent |= setlist->next_is_parent;
+    }
+
+#else  /* KMK - need for speed */
+
+  v = lookup_cached_variable (name);
+  assert (lookup_variable_for_assert(name, length) == v);
+#ifdef VMS
+  if (v)
+#endif
+    return v;
+#endif /* KMK - need for speed */
+#ifdef VMS
+# error "Port me (split out the relevant code from lookup_varaible and call it)"
+#endif
+  return 0;
+}
+#endif
+
 
 
Index: /trunk/src/kmk/variable.h
===================================================================
--- /trunk/src/kmk/variable.h	(revision 2768)
+++ /trunk/src/kmk/variable.h	(revision 2769)
@@ -244,5 +244,7 @@
 #endif /* CONFIG_WITH_VALUE_LENGTH */
 void install_variable_buffer (char **bufp, unsigned int *lenp);
+char *install_variable_buffer_with_hint (char **bufp, unsigned int *lenp, unsigned int size_hint);
 void restore_variable_buffer (char *buf, unsigned int len);
+char *ensure_variable_buffer_space(char *ptr, unsigned int size);
 #ifdef CONFIG_WITH_VALUE_LENGTH
 void append_expanded_string_to_variable (struct variable *v, const char *value,
@@ -325,4 +327,7 @@
                                    unsigned int *value_lenp);
 #define recursively_expand(v)   recursively_expand_for_file (v, NULL, NULL)
+#endif
+#ifdef CONFIG_WITH_COMPILER
+char *reference_recursive_variable (char *o, struct variable *v);
 #endif
 
@@ -369,4 +374,7 @@
 struct variable *lookup_variable_in_set (const char *name, unsigned int length,
                                          const struct variable_set *set);
+#ifdef CONFIG_WITH_STRCACHE2
+struct variable *lookup_variable_strcached (const char *name);
+#endif
 
 #ifdef CONFIG_WITH_VALUE_LENGTH
