Browse Source

ncd: optimize replacing placeholders in statement arguments by preparing replacement instructions in advance

ambrop7 13 năm trước cách đây
mục cha
commit
d25c2e8746
5 tập tin đã thay đổi với 255 bổ sung160 xóa
  1. 21 13
      ncd/NCDInterpBlock.c
  2. 2 2
      ncd/NCDInterpBlock.h
  3. 162 113
      ncd/NCDVal.c
  4. 65 25
      ncd/NCDVal.h
  5. 5 7
      ncd/ncd.c

+ 21 - 13
ncd/NCDInterpBlock.c

@@ -68,7 +68,7 @@ static int compute_prealloc (NCDInterpBlock *o)
     return 1;
     return 1;
 }
 }
 
 
-int convert_value_recurser (NCDPlaceholderDb *pdb, NCDValue *value, NCDValMem *mem, NCDValRef *out, int *out_has_placeholders)
+int convert_value_recurser (NCDPlaceholderDb *pdb, NCDValue *value, NCDValMem *mem, NCDValRef *out)
 {
 {
     ASSERT(pdb)
     ASSERT(pdb)
     ASSERT((NCDValue_Type(value), 1))
     ASSERT((NCDValue_Type(value), 1))
@@ -91,7 +91,7 @@ int convert_value_recurser (NCDPlaceholderDb *pdb, NCDValue *value, NCDValMem *m
             
             
             for (NCDValue *e = NCDValue_ListFirst(value); e; e = NCDValue_ListNext(value, e)) {
             for (NCDValue *e = NCDValue_ListFirst(value); e; e = NCDValue_ListNext(value, e)) {
                 NCDValRef vval;
                 NCDValRef vval;
-                if (!convert_value_recurser(pdb, e, mem, &vval, out_has_placeholders)) {
+                if (!convert_value_recurser(pdb, e, mem, &vval)) {
                     goto fail;
                     goto fail;
                 }
                 }
                 
                 
@@ -110,8 +110,8 @@ int convert_value_recurser (NCDPlaceholderDb *pdb, NCDValue *value, NCDValMem *m
                 
                 
                 NCDValRef vkey;
                 NCDValRef vkey;
                 NCDValRef vval;
                 NCDValRef vval;
-                if (!convert_value_recurser(pdb, ekey, mem, &vkey, out_has_placeholders) ||
-                    !convert_value_recurser(pdb, eval, mem, &vval, out_has_placeholders)
+                if (!convert_value_recurser(pdb, ekey, mem, &vkey) ||
+                    !convert_value_recurser(pdb, eval, mem, &vval)
                 ) {
                 ) {
                     goto fail;
                     goto fail;
                 }
                 }
@@ -132,7 +132,6 @@ int convert_value_recurser (NCDPlaceholderDb *pdb, NCDValue *value, NCDValMem *m
             }
             }
             
             
             *out = NCDVal_NewPlaceholder(mem, plid);
             *out = NCDVal_NewPlaceholder(mem, plid);
-            *out_has_placeholders = 1;
         } break;
         } break;
         
         
         default:
         default:
@@ -184,8 +183,7 @@ int NCDInterpBlock_Init (NCDInterpBlock *o, NCDBlock *block, NCDProcess *process
         NCDValMem_Init(&mem);
         NCDValMem_Init(&mem);
         
         
         NCDValRef val;
         NCDValRef val;
-        e->arg_has_placeholders = 0;
-        if (!convert_value_recurser(pdb, NCDStatement_RegArgs(s), &mem, &val, &e->arg_has_placeholders)) {
+        if (!convert_value_recurser(pdb, NCDStatement_RegArgs(s), &mem, &val)) {
             BLog(BLOG_ERROR, "convert_value_recurser failed");
             BLog(BLOG_ERROR, "convert_value_recurser failed");
             NCDValMem_Free(&mem);
             NCDValMem_Free(&mem);
             goto loop_fail0;
             goto loop_fail0;
@@ -193,15 +191,21 @@ int NCDInterpBlock_Init (NCDInterpBlock *o, NCDBlock *block, NCDProcess *process
         
         
         e->arg_ref = NCDVal_ToSafe(val);
         e->arg_ref = NCDVal_ToSafe(val);
         
         
+        if (!NCDValReplaceProg_Init(&e->arg_prog, val)) {
+            BLog(BLOG_ERROR, "NCDValReplaceProg_Init failed");
+            NCDValMem_Free(&mem);
+            goto loop_fail0;
+        }
+        
         if (!NCDValMem_FreeExport(&mem, &e->arg_data, &e->arg_len)) {
         if (!NCDValMem_FreeExport(&mem, &e->arg_data, &e->arg_len)) {
             BLog(BLOG_ERROR, "NCDValMem_FreeExport failed");
             BLog(BLOG_ERROR, "NCDValMem_FreeExport failed");
             NCDValMem_Free(&mem);
             NCDValMem_Free(&mem);
-            goto loop_fail0;
+            goto loop_fail1;
         }
         }
         
         
         if (NCDStatement_RegObjName(s) && !(e->objnames = split_string(NCDStatement_RegObjName(s), '.'))) {
         if (NCDStatement_RegObjName(s) && !(e->objnames = split_string(NCDStatement_RegObjName(s), '.'))) {
             BLog(BLOG_ERROR, "split_string failed");
             BLog(BLOG_ERROR, "split_string failed");
-            goto loop_fail1;
+            goto loop_fail2;
         }
         }
         
         
         if (e->name) {
         if (e->name) {
@@ -212,8 +216,10 @@ int NCDInterpBlock_Init (NCDInterpBlock *o, NCDBlock *block, NCDProcess *process
         o->num_stmts++;
         o->num_stmts++;
         continue;
         continue;
         
         
-    loop_fail1:
+    loop_fail2:
         BFree(e->arg_data);
         BFree(e->arg_data);
+    loop_fail1:
+        NCDValReplaceProg_Free(&e->arg_prog);
     loop_fail0:
     loop_fail0:
         goto fail2;
         goto fail2;
     }
     }
@@ -230,6 +236,7 @@ fail2:
             free_strings(e->objnames);
             free_strings(e->objnames);
         }
         }
         BFree(e->arg_data);
         BFree(e->arg_data);
+        NCDValReplaceProg_Free(&e->arg_prog);
     }
     }
 fail1:
 fail1:
     BFree(o->stmts);
     BFree(o->stmts);
@@ -247,6 +254,7 @@ void NCDInterpBlock_Free (NCDInterpBlock *o)
             free_strings(e->objnames);
             free_strings(e->objnames);
         }
         }
         BFree(e->arg_data);
         BFree(e->arg_data);
+        NCDValReplaceProg_Free(&e->arg_prog);
     }
     }
     
     
     NCDInterpBlock__Hash_Free(&o->hash);
     NCDInterpBlock__Hash_Free(&o->hash);
@@ -299,14 +307,14 @@ char ** NCDInterpBlock_StatementObjNames (NCDInterpBlock *o, int i)
     return o->stmts[i].objnames;
     return o->stmts[i].objnames;
 }
 }
 
 
-int NCDInterpBlock_CopyStatementArgs (NCDInterpBlock *o, int i, NCDValMem *out_valmem, NCDValRef *out_val, int *out_has_placeholders)
+int NCDInterpBlock_CopyStatementArgs (NCDInterpBlock *o, int i, NCDValMem *out_valmem, NCDValRef *out_val, NCDValReplaceProg **out_prog)
 {
 {
     DebugObject_Access(&o->d_obj);
     DebugObject_Access(&o->d_obj);
     ASSERT(i >= 0)
     ASSERT(i >= 0)
     ASSERT(i < o->num_stmts)
     ASSERT(i < o->num_stmts)
     ASSERT(out_valmem)
     ASSERT(out_valmem)
     ASSERT(out_val)
     ASSERT(out_val)
-    ASSERT(out_has_placeholders)
+    ASSERT(out_prog)
     
     
     struct NCDInterpBlock__stmt *e = &o->stmts[i];
     struct NCDInterpBlock__stmt *e = &o->stmts[i];
     
     
@@ -315,7 +323,7 @@ int NCDInterpBlock_CopyStatementArgs (NCDInterpBlock *o, int i, NCDValMem *out_v
     }
     }
     
     
     *out_val = NCDVal_FromSafe(out_valmem, e->arg_ref);
     *out_val = NCDVal_FromSafe(out_valmem, e->arg_ref);
-    *out_has_placeholders = e->arg_has_placeholders;
+    *out_prog = &e->arg_prog;
     return 1;
     return 1;
 }
 }
 
 

+ 2 - 2
ncd/NCDInterpBlock.h

@@ -46,7 +46,7 @@ struct NCDInterpBlock__stmt {
     char *arg_data;
     char *arg_data;
     size_t arg_len;
     size_t arg_len;
     NCDValSafeRef arg_ref;
     NCDValSafeRef arg_ref;
-    int arg_has_placeholders;
+    NCDValReplaceProg arg_prog;
     int alloc_size;
     int alloc_size;
     int prealloc_offset;
     int prealloc_offset;
     int hash_next;
     int hash_next;
@@ -73,7 +73,7 @@ void NCDInterpBlock_Free (NCDInterpBlock *o);
 int NCDInterpBlock_FindStatement (NCDInterpBlock *o, int from_index, const char *name);
 int NCDInterpBlock_FindStatement (NCDInterpBlock *o, int from_index, const char *name);
 const char * NCDInterpBlock_StatementCmdName (NCDInterpBlock *o, int i);
 const char * NCDInterpBlock_StatementCmdName (NCDInterpBlock *o, int i);
 char ** NCDInterpBlock_StatementObjNames (NCDInterpBlock *o, int i);
 char ** NCDInterpBlock_StatementObjNames (NCDInterpBlock *o, int i);
-int NCDInterpBlock_CopyStatementArgs (NCDInterpBlock *o, int i, NCDValMem *out_valmem, NCDValRef *out_val, int *out_has_placeholders) WARN_UNUSED;
+int NCDInterpBlock_CopyStatementArgs (NCDInterpBlock *o, int i, NCDValMem *out_valmem, NCDValRef *out_val, NCDValReplaceProg **out_prog) WARN_UNUSED;
 void NCDInterpBlock_StatementBumpAllocSize (NCDInterpBlock *o, int i, int alloc_size);
 void NCDInterpBlock_StatementBumpAllocSize (NCDInterpBlock *o, int i, int alloc_size);
 int NCDInterpBlock_StatementPreallocSize (NCDInterpBlock *o, int i);
 int NCDInterpBlock_StatementPreallocSize (NCDInterpBlock *o, int i);
 int NCDInterpBlock_PreallocSize (NCDInterpBlock *o);
 int NCDInterpBlock_PreallocSize (NCDInterpBlock *o);

+ 162 - 113
ncd/NCDVal.c

@@ -397,119 +397,6 @@ fail:
     return NCDVal_NewInvalid();
     return NCDVal_NewInvalid();
 }
 }
 
 
-#define V_TYPE(ptr) ((int *)(ptr))
-#define V_LIST(ptr) ((struct NCDVal__list *)(ptr))
-#define V_MAP(ptr) ((struct NCDVal__map *)(ptr))
-
-static int replace_placeholders_recurser (NCDValMem *mem, NCDVal__idx idx, NCDVal_replace_func replace, void *arg)
-{
-    ASSERT(idx >= 0)
-    NCDVal__AssertValOnly(mem, idx);
-    
-    int res;
-    NCDValRef repval;
-    
-    int changed = 0;
-    void *ptr = NCDValMem__BufAt(mem, idx);
-    
-    switch (*V_TYPE(ptr)) {
-        case NCDVAL_STRING: {
-        } break;
-        
-        case NCDVAL_LIST: {
-            NCDVal__idx count = V_LIST(ptr)->count;
-            
-            for (NCDVal__idx i = 0; i < count; i++) {
-                if (V_LIST(ptr)->elem_indices[i] < -1) {
-                    int plid = V_LIST(ptr)->elem_indices[i] - NCDVAL_MINIDX;
-                    if (!replace(arg, plid, mem, &repval) || NCDVal_IsInvalid(repval)) {
-                        return -1;
-                    }
-                    ASSERT(repval.mem == mem)
-                    
-                    ptr = NCDValMem__BufAt(mem, idx);
-                    V_LIST(ptr)->elem_indices[i] = repval.idx;
-                    changed = 1;
-                } else {
-                    if ((res = replace_placeholders_recurser(mem, V_LIST(ptr)->elem_indices[i], replace, arg)) < 0) {
-                        return res;
-                    }
-                    ptr = NCDValMem__BufAt(mem, idx);
-                    changed |= res;
-                }
-            }
-        } break;
-        
-        case NCDVAL_MAP: {
-            NCDVal__idx count = V_MAP(ptr)->count;
-            
-            for (NCDVal__idx i = 0; i < count; i++) {
-                int key_changed = 0;
-                
-                if (V_MAP(ptr)->elems[i].key_idx < -1) {
-                    int plid = V_MAP(ptr)->elems[i].key_idx - NCDVAL_MINIDX;
-                    if (!replace(arg, plid, mem, &repval) || NCDVal_IsInvalid(repval)) {
-                        return -1;
-                    }
-                    ASSERT(repval.mem == mem)
-                    
-                    ptr = NCDValMem__BufAt(mem, idx);
-                    V_MAP(ptr)->elems[i].key_idx = repval.idx;
-                    changed = 1;
-                    key_changed = 1;
-                } else {
-                    if ((res = replace_placeholders_recurser(mem, V_MAP(ptr)->elems[i].key_idx, replace, arg)) < 0) {
-                        return res;
-                    }
-                    ptr = NCDValMem__BufAt(mem, idx);
-                    changed |= res;
-                    key_changed |= res;
-                }
-                
-                if (V_MAP(ptr)->elems[i].val_idx < -1) {
-                    int plid = V_MAP(ptr)->elems[i].val_idx - NCDVAL_MINIDX;
-                    if (!replace(arg, plid, mem, &repval) || NCDVal_IsInvalid(repval)) {
-                        return -1;
-                    }
-                    ASSERT(repval.mem == mem)
-                    
-                    ptr = NCDValMem__BufAt(mem, idx);
-                    V_MAP(ptr)->elems[i].val_idx = repval.idx;
-                    changed = 1;
-                } else {
-                    if ((res = replace_placeholders_recurser(mem, V_MAP(ptr)->elems[i].val_idx, replace, arg)) < 0) {
-                        return res;
-                    }
-                    ptr = NCDValMem__BufAt(mem, idx);
-                    changed |= res;
-                }
-                
-                if (key_changed) {
-                    NCDVal__MapTreeRef ref = {&V_MAP(ptr)->elems[i], NCDVal__MapElemIdx(idx, i)};
-                    NCDVal__MapTree_Remove(&V_MAP(ptr)->tree, mem, ref);
-                    if (!NCDVal__MapTree_Insert(&V_MAP(ptr)->tree, mem, ref, NULL)) {
-                        BLog(BLOG_ERROR, "duplicate key in map");
-                        return -1;
-                    }
-                }
-            }
-        } break;
-        
-        default: ASSERT(0);
-    }
-    
-    return changed;
-}
-
-int NCDVal_ReplacePlaceholders (NCDValRef val, NCDVal_replace_func replace, void *arg)
-{
-    NCDVal__AssertVal(val);
-    ASSERT(!NCDVal_IsPlaceholder(val))
-    ASSERT(replace)
-    
-    return (replace_placeholders_recurser(val.mem, val.idx, replace, arg) >= 0);
-}
-
 int NCDVal_Compare (NCDValRef val1, NCDValRef val2)
 int NCDVal_Compare (NCDValRef val1, NCDValRef val2)
 {
 {
     NCDVal__AssertVal(val1);
     NCDVal__AssertVal(val1);
@@ -1027,3 +914,165 @@ NCDValMapElem NCDVal_MapFindKey (NCDValRef map, NCDValRef key)
     
     
     return NCDVal__MapElem(ref.link);
     return NCDVal__MapElem(ref.link);
 }
 }
+
+static void replaceprog_build_recurser (NCDValMem *mem, NCDVal__idx idx, size_t *out_num_instr, NCDValReplaceProg *prog)
+{
+    ASSERT(idx >= 0)
+    NCDVal__AssertValOnly(mem, idx);
+    ASSERT(out_num_instr)
+    
+    *out_num_instr = 0;
+    
+    void *ptr = NCDValMem__BufAt(mem, idx);
+    
+    struct NCDVal__instr instr;
+    
+    switch (*((int *)(ptr))) {
+        case NCDVAL_STRING: {
+        } break;
+        
+        case NCDVAL_LIST: {
+            struct NCDVal__list *list_e = ptr;
+            
+            for (NCDVal__idx i = 0; i < list_e->count; i++) {
+                if (list_e->elem_indices[i] < -1) {
+                    if (prog) {
+                        instr.type = NCDVAL_INSTR_PLACEHOLDER;
+                        instr.placeholder.plid = list_e->elem_indices[i] - NCDVAL_MINIDX;
+                        instr.placeholder.plidx = idx + offsetof(struct NCDVal__list, elem_indices) + i * sizeof(NCDVal__idx);
+                        prog->instrs[prog->num_instrs++] = instr;
+                    }
+                    (*out_num_instr)++;
+                } else {
+                    size_t elem_num_instr;
+                    replaceprog_build_recurser(mem, list_e->elem_indices[i], &elem_num_instr, prog);
+                    (*out_num_instr) += elem_num_instr;
+                }
+            }
+        } break;
+        
+        case NCDVAL_MAP: {
+            struct NCDVal__map *map_e = ptr;
+            
+            for (NCDVal__idx i = 0; i < map_e->count; i++) {
+                int need_reinsert = 0;
+                
+                if (map_e->elems[i].key_idx < -1) {
+                    if (prog) {
+                        instr.type = NCDVAL_INSTR_PLACEHOLDER;
+                        instr.placeholder.plid = map_e->elems[i].key_idx - NCDVAL_MINIDX;
+                        instr.placeholder.plidx = idx + offsetof(struct NCDVal__map, elems) + i * sizeof(struct NCDVal__mapelem) + offsetof(struct NCDVal__mapelem, key_idx);
+                        prog->instrs[prog->num_instrs++] = instr;
+                    }
+                    (*out_num_instr)++;
+                    need_reinsert = 1;
+                } else {
+                    size_t key_num_instr;
+                    replaceprog_build_recurser(mem, map_e->elems[i].key_idx, &key_num_instr, prog);
+                    (*out_num_instr) += key_num_instr;
+                    if (key_num_instr > 0) {
+                        need_reinsert = 1;
+                    }
+                }
+                
+                if (map_e->elems[i].val_idx < -1) {
+                    if (prog) {
+                        instr.type = NCDVAL_INSTR_PLACEHOLDER;
+                        instr.placeholder.plid = map_e->elems[i].val_idx - NCDVAL_MINIDX;
+                        instr.placeholder.plidx = idx + offsetof(struct NCDVal__map, elems) + i * sizeof(struct NCDVal__mapelem) + offsetof(struct NCDVal__mapelem, val_idx);
+                        prog->instrs[prog->num_instrs++] = instr;
+                    }
+                    (*out_num_instr)++;
+                } else {
+                    size_t val_num_instr;
+                    replaceprog_build_recurser(mem, map_e->elems[i].val_idx, &val_num_instr, prog);
+                    (*out_num_instr) += val_num_instr;
+                }
+                
+                if (need_reinsert) {
+                    if (prog) {
+                        instr.type = NCDVAL_INSTR_REINSERT;
+                        instr.reinsert.mapidx = idx;
+                        instr.reinsert.elempos = i;
+                        prog->instrs[prog->num_instrs++] = instr;
+                    }
+                    (*out_num_instr)++;
+                }
+            }
+        } break;
+        
+        default: ASSERT(0);
+    }
+}
+
+int NCDValReplaceProg_Init (NCDValReplaceProg *o, NCDValRef val)
+{
+    NCDVal__AssertVal(val);
+    ASSERT(!NCDVal_IsPlaceholder(val))
+    
+    size_t num_instrs;
+    replaceprog_build_recurser(val.mem, val.idx, &num_instrs, NULL);
+    
+    if (!(o->instrs = BAllocArray(num_instrs, sizeof(o->instrs[0])))) {
+        BLog(BLOG_ERROR, "BAllocArray failed");
+        return 0;
+    }
+    
+    o->num_instrs = 0;
+    
+    size_t num_instrs2;
+    replaceprog_build_recurser(val.mem, val.idx, &num_instrs2, o);
+    
+    ASSERT(num_instrs2 == num_instrs)
+    ASSERT(o->num_instrs == num_instrs)
+    
+    return 1;
+}
+
+void NCDValReplaceProg_Free (NCDValReplaceProg *o)
+{
+    BFree(o->instrs);
+}
+
+int NCDValReplaceProg_Execute (NCDValReplaceProg *o, NCDValMem *mem, NCDVal_replace_func replace, void *arg)
+{
+    NCDVal__AssertMem(mem);
+    ASSERT(replace)
+    
+    for (size_t i = 0; i < o->num_instrs; i++) {
+        struct NCDVal__instr instr = o->instrs[i];
+        
+        if (instr.type == NCDVAL_INSTR_PLACEHOLDER) {
+#ifndef NDEBUG
+            NCDVal__idx *check_plptr = NCDValMem__BufAt(mem, instr.placeholder.plidx);
+            ASSERT(*check_plptr < -1)
+            ASSERT(*check_plptr - NCDVAL_MINIDX == instr.placeholder.plid)
+#endif
+            NCDValRef repval;
+            if (!replace(arg, instr.placeholder.plid, mem, &repval) || NCDVal_IsInvalid(repval)) {
+                return 0;
+            }
+            ASSERT(repval.mem == mem)
+            
+            NCDVal__idx *plptr = NCDValMem__BufAt(mem, instr.placeholder.plidx);
+            *plptr = repval.idx;
+        } else {
+            ASSERT(instr.type == NCDVAL_INSTR_REINSERT)
+            
+            NCDVal__AssertValOnly(mem, instr.reinsert.mapidx);
+            struct NCDVal__map *map_e = NCDValMem__BufAt(mem, instr.reinsert.mapidx);
+            ASSERT(map_e->type == NCDVAL_MAP)
+            ASSERT(instr.reinsert.elempos >= 0)
+            ASSERT(instr.reinsert.elempos < map_e->count)
+            
+            NCDVal__MapTreeRef ref = {&map_e->elems[instr.reinsert.elempos], NCDVal__MapElemIdx(instr.reinsert.mapidx, instr.reinsert.elempos)};
+            NCDVal__MapTree_Remove(&map_e->tree, mem, ref);
+            if (!NCDVal__MapTree_Insert(&map_e->tree, mem, ref, NULL)) {
+                BLog(BLOG_ERROR, "duplicate key in map");
+                return 0;
+            }
+        }
+    }
+    
+    return 1;
+}

+ 65 - 25
ncd/NCDVal.h

@@ -101,6 +101,28 @@ typedef struct {
     NCDVal__idx elemidx;
     NCDVal__idx elemidx;
 } NCDValMapElem;
 } NCDValMapElem;
 
 
+#define NCDVAL_INSTR_PLACEHOLDER 0
+#define NCDVAL_INSTR_REINSERT 1
+
+struct NCDVal__instr {
+    int type;
+    union {
+        struct {
+            NCDVal__idx plid;
+            NCDVal__idx plidx;
+        } placeholder;
+        struct {
+            NCDVal__idx mapidx;
+            NCDVal__idx elempos;
+        } reinsert;
+    };
+};
+
+typedef struct {
+    struct NCDVal__instr *instrs;
+    size_t num_instrs;
+} NCDValReplaceProg;
+
 //
 //
 
 
 #define NCDVAL_STRING 1
 #define NCDVAL_STRING 1
@@ -220,31 +242,6 @@ int NCDVal_PlaceholderId (NCDValRef val);
  */
  */
 NCDValRef NCDVal_NewCopy (NCDValMem *mem, NCDValRef val);
 NCDValRef NCDVal_NewCopy (NCDValMem *mem, NCDValRef val);
 
 
-/**
- * Callback used by {@link NCDVal_ReplacePlaceholders} to allow the caller to produce
- * values of placeholders.
- * This function should build a new value within the memory object 'mem' (which is
- * the same as of the value reference whose placeholders are being replaced), and
- * return a value reference.
- * On success, it should return 1, writing a valid value reference to *out.
- * On failure, it can either return 0, or return 1 but write an invalid value reference.
- * This callback must not access the memory object in any other way than building
- * new values in it; it must not modify any values ther were already present at the
- * point it was called.
- */
-typedef int (*NCDVal_replace_func) (void *arg, int plid, NCDValMem *mem, NCDValRef *out);
-
-/**
- * Replaces placeholders in a value.
- * The value reference must point to a valid value, and not a placeholder value.
- * This will call the callback 'replace', which should build the values to replace
- * the placeholders.
- * Returns 1 on success and 0 on failure. On failure, the entire memory object enters
- * and inconsistent state and must be freed using {@link NCDValMem_Free} before
- * performing any other operation on it.
- */
-int NCDVal_ReplacePlaceholders (NCDValRef val, NCDVal_replace_func replace, void *arg);
-
 /**
 /**
  * Compares two values, both of which must not be invalid references.
  * Compares two values, both of which must not be invalid references.
  * Returns -1, 0 or 1.
  * Returns -1, 0 or 1.
@@ -503,4 +500,47 @@ NCDValRef NCDVal_MapElemVal (NCDValRef map, NCDValMapElem me);
  */
  */
 NCDValMapElem NCDVal_MapFindKey (NCDValRef map, NCDValRef key);
 NCDValMapElem NCDVal_MapFindKey (NCDValRef map, NCDValRef key);
 
 
+/**
+ * Builds a placeholder replacement program, which is a list of instructions for
+ * efficiently replacing placeholders in identical values in identical memory
+ * objects.
+ * To actually perform replacements, make copies of the memory object of this value
+ * using {@link NCDValMem_FreeExport} and {@link NCDValMem_InitImport}, then call
+ * {@link NCDValReplaceProg_Execute} on the copies.
+ * The value passed must be a valid value, and not a placeholder.
+ * Returns 1 on success, 0 on failure.
+ */
+int NCDValReplaceProg_Init (NCDValReplaceProg *o, NCDValRef val);
+
+/**
+ * Frees the placeholder replacement program.
+ */
+void NCDValReplaceProg_Free (NCDValReplaceProg *o);
+
+/**
+ * Callback used by {@link NCDValReplaceProg_Execute} to allow the caller to produce
+ * values of placeholders.
+ * This function should build a new value within the memory object 'mem' (which is
+ * the same as of the memory object where placeholders are being replaced).
+ * On success, it should return 1, writing a valid value reference to *out.
+ * On failure, it can either return 0, or return 1 but write an invalid value reference.
+ * This callback must not access the memory object in any other way than building
+ * new values in it; it must not modify any values that were already present at the
+ * point it was called.
+ */
+typedef int (*NCDVal_replace_func) (void *arg, int plid, NCDValMem *mem, NCDValRef *out);
+
+/**
+ * Executes the replacement program, replacing placeholders in a value.
+ * The memory object must given be identical to the memory object which was used in
+ * {@link NCDValReplaceProg_Init}; see {@link NCDValMem_FreeExport} and
+ * {@link NCDValMem_InitImport}.
+ * This will call the callback 'replace', which should build the values to replace
+ * the placeholders.
+ * Returns 1 on success and 0 on failure. On failure, the entire memory object enters
+ * and inconsistent state and must be freed using {@link NCDValMem_Free} before
+ * performing any other operation on it.
+ */
+int NCDValReplaceProg_Execute (NCDValReplaceProg *o, NCDValMem *mem, NCDVal_replace_func replace, void *arg);
+
 #endif
 #endif

+ 5 - 7
ncd/ncd.c

@@ -1042,18 +1042,16 @@ void process_advance (struct process *p)
     
     
     // copy arguments
     // copy arguments
     NCDValRef args;
     NCDValRef args;
-    int has_placeholders;
-    if (!NCDInterpBlock_CopyStatementArgs(p->iblock, ps->i, &ps->args_mem, &args, &has_placeholders)) {
+    NCDValReplaceProg *prog;
+    if (!NCDInterpBlock_CopyStatementArgs(p->iblock, ps->i, &ps->args_mem, &args, &prog)) {
         statement_log(ps, BLOG_ERROR, "NCDInterpBlock_CopyStatementArgs failed");
         statement_log(ps, BLOG_ERROR, "NCDInterpBlock_CopyStatementArgs failed");
         goto fail0;
         goto fail0;
     }
     }
     
     
     // replace placeholders with values of variables
     // replace placeholders with values of variables
-    if (has_placeholders) {
-        if (!NCDVal_ReplacePlaceholders(args, replace_placeholders_callback, ps)) {
-            statement_log(ps, BLOG_ERROR, "failed to replace variables in arguments with values");
-            goto fail1;
-        }
+    if (!NCDValReplaceProg_Execute(prog, &ps->args_mem, replace_placeholders_callback, ps)) {
+        statement_log(ps, BLOG_ERROR, "failed to replace variables in arguments with values");
+        goto fail1;
     }
     }
     
     
     // allocate memory
     // allocate memory