Skip to content

Commit 62eb7f9

Browse files
gh-46: Make MAP and TNS atomic.
1 parent 717a2b8 commit 62eb7f9

File tree

5 files changed

+73
-4
lines changed

5 files changed

+73
-4
lines changed

docs/SPECIFICATION.html

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -211,9 +211,11 @@
211211
212212
Additionally, the symbol `*` MAY be used in an index position to denote a full-dimension slice selecting every element along that axis (for example, `tensor[*,1]` selects all elements of the first dimension at index `1` of the second dimension).
213213
214-
All other tensor operations are non-mutating: tensor literals and tensor-valued built-ins produce new tensor values rather than mutating existing ones. Because indexed assignment mutates a tensor object, if the same tensor value is aliased (bound to multiple identifiers, passed as an argument, or stored inside another tensor), all aliases observe the mutation.
214+
All other tensor operations are non-mutating: tensor literals and tensor-valued built-ins produce new tensor values rather than mutating existing ones. Because indexed assignment mutates a tensor object, if the same tensor value is explicitly aliased, all aliases observe the mutation.
215215
216-
MAP and `TNS` values are reference (aliasing) types: assigning a `MAP` or `TNS` to another identifier copies a reference to the same underlying container rather than performing an implicit deep copy. Mutating a map (via `map<...> = ...`, `DEL`, or other mutating operators) or mutating a tensor element through indexed assignment will be observed through any other identifier that references the same container. Use the built-in `COPY` (shallow copy) or `DEEPCOPY` (recursive deep copy) operators when a non-aliased duplicate is REQUIRED.
216+
MAP and `TNS` values are atomic (de-referenced) types: assigning a `MAP` or `TNS` to another identifier copies (duplicates) the container object so that subsequent mutations to one binding do not affect the other by default. The runtime provides explicit copy semantics to control sharing:
217+
218+
When shared, aliased containers are required, developers should use pointer references (with `@`).
217219
218220
Every runtime value has a static type: `INT`, `FLT`, `STR`, `TNS`, `MAP`, `THR`, or `FUNC`. Integers are conceptually unbounded mathematical integers. Floats are IEEE754 binary floating-point numbers. Strings are sequences of characters (source text is ASCII, but escape codes MAY denote non-ASCII code points). Tensors are non-scalar aggregates whose elements MAY be `INT`, `FLT`, `STR`, `MAP`, `THR`, `FUNC`, or `TNS`. Maps are associative containers mapping scalar keys (`INT`, `FLT`, or `STR`) to values of a single static type. Threads are handles to parallel code blocks. Functions are user-defined code blocks with lexical closures.
219221

src/builtins.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5771,7 +5771,8 @@ static Value builtin_match(Interpreter* interp, Value* args, int argc, Expr** ar
57715771
// COPY (shallow copy for scalars)
57725772
static Value builtin_copy(Interpreter* interp, Value* args, int argc, Expr** arg_nodes, Env* env, int line, int col) {
57735773
(void)arg_nodes; (void)env; (void)interp; (void)line; (void)col;
5774-
return value_copy(args[0]);
5774+
/* Preserve existing COPY operator behavior (shallow/aliasing). */
5775+
return value_alias(args[0]);
57755776
}
57765777

57775778
// DEEPCOPY: return a recursive deep copy of the argument

src/interpreter.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1959,6 +1959,13 @@ ExecResult assign_index_chain(Interpreter* interp, Env* env, Expr* idx_expr, Val
19591959

19601960
cleanup:
19611961
free(nodes);
1962+
// If assignment succeeded, write back the possibly-modified base value
1963+
// into the environment so that atomic container semantics persist.
1964+
if (out.status == EXEC_OK) {
1965+
if (!env_assign(env, base_name, base_val, TYPE_UNKNOWN, false)) {
1966+
out = make_error("Cannot write back to identifier (frozen?)", stmt_line, stmt_col);
1967+
}
1968+
}
19621969
value_free(base_val);
19631970
return out;
19641971
}

src/value.c

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -382,9 +382,66 @@ Value* value_tns_get_ptr(Value v, const size_t* idxs, size_t nidxs) {
382382

383383
// Shallow copy semantics: increment refcount for MAP/TNS and return aliasing Value.
384384
Value value_copy(Value v) {
385+
// New semantics: value_copy produces an atomic (de-referenced) copy for
386+
// container types (TNS and MAP): the container object is duplicated so
387+
// mutations to the returned value won't affect the original. Element
388+
// values inside the container remain as aliases (not deep-copied).
389+
Value out = v;
390+
if (v.type == VAL_STR && v.as.s) {
391+
out.as.s = strdup(v.as.s);
392+
} else if (v.type == VAL_TNS && v.as.tns) {
393+
Tensor* t = v.as.tns;
394+
// allocate new tensor structure and copy shape/strides
395+
Tensor* t2 = malloc(sizeof(Tensor));
396+
if (!t2) { fprintf(stderr, "Out of memory\n"); exit(1); }
397+
t2->elem_type = t->elem_type;
398+
t2->ndim = t->ndim;
399+
t2->shape = malloc(sizeof(size_t) * t2->ndim);
400+
t2->strides = malloc(sizeof(size_t) * t2->ndim);
401+
for (size_t i = 0; i < t2->ndim; i++) { t2->shape[i] = t->shape[i]; t2->strides[i] = t->strides[i]; }
402+
t2->length = t->length;
403+
t2->data = malloc(sizeof(Value) * t2->length);
404+
for (size_t i = 0; i < t2->length; i++) {
405+
// keep element references (shallow): use value_alias to preserve
406+
// previous element-sharing semantics for nested containers
407+
extern Value value_alias(Value v);
408+
t2->data[i] = value_alias(t->data[i]);
409+
}
410+
t2->refcount = 1;
411+
mtx_init(&t2->lock, 0);
412+
out.as.tns = t2;
413+
} else if (v.type == VAL_MAP && v.as.map) {
414+
Map* m = v.as.map;
415+
Map* m2 = malloc(sizeof(Map));
416+
if (!m2) { fprintf(stderr, "Out of memory\n"); exit(1); }
417+
m2->count = m->count;
418+
m2->capacity = m->count;
419+
m2->items = malloc(sizeof(MapEntry) * (m2->capacity ? m2->capacity : 1));
420+
for (size_t i = 0; i < m->count; i++) {
421+
extern Value value_alias(Value v);
422+
m2->items[i].key = value_alias(m->items[i].key);
423+
m2->items[i].value = value_alias(m->items[i].value);
424+
}
425+
m2->refcount = 1;
426+
mtx_init(&m2->lock, 0);
427+
out.as.map = m2;
428+
} else if (v.type == VAL_THR && v.as.thr) {
429+
// threads remain shared handles
430+
Thr* th = v.as.thr;
431+
mtx_lock(&th->state_lock);
432+
th->refcount++;
433+
mtx_unlock(&th->state_lock);
434+
out.as.thr = th;
435+
}
436+
return out;
437+
}
438+
439+
// Alias/shallow-copy helper: preserve previous semantics where MAP/TNS
440+
// were reference types. This function performs the former behavior of
441+
// `value_copy` (increment refcounts and return aliasing container).
442+
Value value_alias(Value v) {
385443
Value out = v;
386444
if (v.type == VAL_STR && v.as.s) {
387-
// Duplicate strings to preserve independent ownership semantics for STR
388445
out.as.s = strdup(v.as.s);
389446
} else if (v.type == VAL_TNS && v.as.tns) {
390447
Tensor* t = v.as.tns;
@@ -406,6 +463,7 @@ Value value_copy(Value v) {
406463
out.as.thr = th;
407464
}
408465
return out;
466+
409467
}
410468

411469
// Deep-copy helper: recursively duplicate container contents.

src/value.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ int value_thr_get_started(Value v);
110110
// Note: pointer semantics are implemented at the EnvEntry (alias) level; no PTR Value type.
111111

112112
Value value_copy(Value v);
113+
Value value_alias(Value v);
113114
Value value_deep_copy(Value v);
114115
void value_free(Value v);
115116

0 commit comments

Comments
 (0)