대여는 변수가 아니라 place 단위다

p.a 와 p.b 는 서로 다른 자리인데 한쪽을 대여하면 다른 쪽까지 잠겼다. 메서드가
하는 일의 대부분이 한 필드에 쓰면서 다른 필드를 읽는 것이라, std.map 의 keep 은
그것 때문에 함수 둘로 갈라져 있었고 파서도 같은 자리에서 걸렸다.

FeOwnState 가 필드별 칸을 넷 갖는다. 값으로 복사되는 구조체라 흐름 병합과
스냅샷은 손댈 것이 없었다. 전체를 대여하면 모든 필드와 충돌하고, 필드를
대여하면 전체 및 같은 필드와 충돌한다. 칸이 모자라면 전체 대여로 되돌아가
더 많이 보고할 뿐 놓치지 않는다.

읽기는 식별자에서 일어나는데 그 자리에서는 자기가 무엇의 밑동인지 알 수 없다.
그래서 투영이 내려가는 길에 어느 필드인지 적어두고 식별자가 그것을 집는다.
인덱스는 갈라지지 않는다 -- xs[i] 의 i 는 상수가 아닐 수 있고, 필드 이름은
상수다.

길에서 나온 것: mem.replace 가 목적지 대여를 가져가고 돌려주지 않았다. 일반
호출 인자는 문장 끝에 돌려주는데 intrinsic 경로에만 그것이 없었다. 전에는
그 자리가 어차피 거부돼서 드러나지 않았다.

  var p = Pair{ a: 1, b: 2 };
  let r = &mut p.a;
  p.b = 3;      // ok -- 전에는 에러
  p.a = 3;      // 에러
  take(p);      // 에러

SPEC §5 R6 을 고쳤고, 옛 규칙을 그대로 적어둔 문단과 예제를 갈아치웠다.
own/badrfld 는 이제 허용되는 코드였으므로 같은 필드를 건드리도록 다시 겨눴고
okrfld·badrall·badrsame·exec/fieldbrw 를 더했다.

228/228, 32/32.
This commit is contained in:
2026-08-17 15:29:12 +09:00
parent 120a36eef5
commit f7e667652e
15 changed files with 510 additions and 40 deletions
+58 -7
View File
@@ -260,6 +260,7 @@ FeSym *add_symbol(FeCheckerState *s, FeScope *scope,
sym->decl = decl;
fe_own_state_init(&sym->own, initialized);
sym->borrow_root = 0;
sym->borrow_field = 0;
sym->borrow_mut = 0;
sym->borrow_defer = 0;
sym->owner = scope;
@@ -506,9 +507,47 @@ int own_is_global(FeCheckerState *s, FeSym *sym)
return 0;
}
/* Strip the `&`/`&mut` off an expression; the place underneath is what is
being reached. */
static FeNode *own_strip_ref(FeNode *e)
{
while (e && e->kind==FE_N_UNARY && e->text &&
(strcmp(e->text,"&")==0 || strcmp(e->text,"&mut")==0))
e=e->a;
return e;
}
/* The first field projected off the root of `expr`, and that root.
`self.bytes.^[i]` projects `bytes` off `self`. An index (`arr[i]`) and a
dereference (`p.^`) name no field, so they answer for the whole value --
which is what the checker did for everything before. */
const char *own_projected_field(FeNode *expr, FeNode **root_out)
{
FeNode *inner;
FeNode *outer=0;
if (root_out) *root_out=0;
inner=own_strip_ref(expr);
while (inner && (inner->kind==FE_N_MEMBER || inner->kind==FE_N_INDEX)) {
outer=inner;
inner=own_strip_ref(inner->a);
}
if (!inner || inner->kind!=FE_N_IDENT || !outer) return 0;
if (outer->kind!=FE_N_MEMBER) return 0;
/* A member node's own text is the token the postfix chain started at, not
the operator, so the spelling of the projection is what to look at:
`.?` carries nothing on the right and `.^` carries a caret. */
if (outer->text && (strcmp(outer->text,".?")==0 ||
strcmp(outer->text,".^")==0)) return 0;
if (!outer->b || !outer->b->text) return 0;
if (strcmp(outer->b->text,"^")==0) return 0;
if (root_out) *root_out=inner;
return outer->b->text;
}
void own_borrow_expr(FeCheckerState *s, FeNode *expr, int mutable)
{
FeSym *root=own_root_symbol(s,expr);
const char *field=own_projected_field(expr,0);
if (!root) return;
if (mutable && root->type && root->type->kind==FE_TYPE_REF &&
!root->type->ref_mut) {
@@ -521,9 +560,9 @@ void own_borrow_expr(FeCheckerState *s, FeNode *expr, int mutable)
err(s->c,expr->loc,"cannot borrow a mutable global");
return;
}
fe_own_access(s->c->diags,&root->own,
mutable ? FE_OWN_BORROW_MUT : FE_OWN_BORROW_SHARED,
expr->loc);
fe_own_access_field(s->c->diags,&root->own,field,
mutable ? FE_OWN_BORROW_MUT : FE_OWN_BORROW_SHARED,
expr->loc);
}
void own_release_temporary_borrow(FeCheckerState *s, FeNode *expr)
@@ -533,8 +572,12 @@ void own_release_temporary_borrow(FeCheckerState *s, FeNode *expr)
if (strcmp(expr->text,"&")!=0 && strcmp(expr->text,"&mut")!=0) return;
root=own_root_symbol(s,expr->a);
if (!root) return;
if (strcmp(expr->text,"&mut")==0) fe_own_release_exclusive(&root->own);
else fe_own_release_shared(&root->own);
{
const char *field=own_projected_field(expr->a,0);
if (strcmp(expr->text,"&mut")==0)
fe_own_release_exclusive_field(&root->own,field);
else fe_own_release_shared_field(&root->own,field);
}
}
/* Return-reference provenance is represented at call sites by retaining a
@@ -577,6 +620,7 @@ void own_bind_derived_call(FeCheckerState *s, FeSym *binding,
else
fe_own_access(s->c->diags,&root->own,FE_OWN_BORROW_SHARED,value->loc);
binding->borrow_root=root;
binding->borrow_field=0;
binding->borrow_mut=value->sem_type->kind==FE_TYPE_REF && value->sem_type->ref_mut;
}
@@ -631,9 +675,13 @@ void own_release_after_stmt(FeCheckerState *s, FeScope *scope,
ref->decl && ref->decl->text ? ref->decl->text : ref->name);
if (!scope_end && (ref->borrow_defer || !last || last->defer_extended ||
!own_contains_node(stmt,last->last_node))) continue;
if (ref->borrow_mut) fe_own_release_exclusive(&ref->borrow_root->own);
else fe_own_release_shared(&ref->borrow_root->own);
if (ref->borrow_mut)
fe_own_release_exclusive_field(&ref->borrow_root->own,
ref->borrow_field);
else fe_own_release_shared_field(&ref->borrow_root->own,
ref->borrow_field);
ref->borrow_root=0;
ref->borrow_field=0;
}
}
@@ -687,6 +735,7 @@ void flow_borrow_capture(FeFlowSlot *slots, FeFlowBorrow *states,
if (!states) return;
for (i=0;i<count;++i) {
states[i].root=slots[i].sym->borrow_root;
states[i].field=slots[i].sym->borrow_field;
states[i].mutable=slots[i].sym->borrow_mut;
}
}
@@ -698,6 +747,7 @@ void flow_borrow_restore(FeFlowSlot *slots, FeFlowBorrow *states,
if (!states) return;
for (i=0;i<count;++i) {
slots[i].sym->borrow_root=states[i].root;
slots[i].sym->borrow_field=states[i].field;
slots[i].sym->borrow_mut=states[i].mutable;
}
}
@@ -709,6 +759,7 @@ void flow_borrow_merge(FeFlowSlot *slots, FeFlowBorrow *left,
if (!left || !right) return;
for (i=0;i<count;++i) {
slots[i].sym->borrow_root=left[i].root ? left[i].root : right[i].root;
slots[i].sym->borrow_field=left[i].root ? left[i].field : right[i].field;
slots[i].sym->borrow_mut=left[i].mutable || right[i].mutable;
}
}
+36
View File
@@ -139,6 +139,7 @@ FeType *check_call(FeCheckerState *s, FeNode *n)
!m7_actual_compatible(expected,b,value))
err(c,value->loc,"mem.replace value type mismatch");
mark_moved(s,value,value->sem_type ? value->sem_type : b);
own_release_temporary_borrow(s,arg);
n->sem_type=expected ? expected : unknown(c);
fe_type_require_replace(&c->types,n->sem_type);
return n->sem_type;
@@ -351,7 +352,24 @@ FeType *m7_check_lazy(FeCheckerState *s, FeNode *n,
return payload;
}
static FeType *check_expr_dispatch(FeCheckerState *s, FeNode *n);
/* Every expression goes through here, which is where a projection can tell
the identifier underneath it which field is actually being reached. */
FeType *check_expr(FeCheckerState *s, FeNode *n)
{
const char *save_field=s->proj_field;
FeNode *save_base=s->proj_base;
FeType *t;
if (n && (n->kind==FE_N_MEMBER || n->kind==FE_N_INDEX))
s->proj_field=own_projected_field(n,&s->proj_base);
t=check_expr_dispatch(s,n);
s->proj_field=save_field;
s->proj_base=save_base;
return t;
}
static FeType *check_expr_dispatch(FeCheckerState *s, FeNode *n)
{
FeType *a;
FeType *b;
@@ -486,7 +504,24 @@ FeType *check_expr(FeCheckerState *s, FeNode *n)
return check_expr_core(s,n);
}
static FeType *check_lvalue_dispatch(FeCheckerState *s, FeNode *n, int read);
/* An assignment target is a projection too, so it leaves the same word for the
identifier underneath: `self.room = x` reaches `room` and nothing else. */
FeType *check_lvalue(FeCheckerState *s, FeNode *n, int read)
{
const char *save_field=s->proj_field;
FeNode *save_base=s->proj_base;
FeType *t;
if (n && (n->kind==FE_N_MEMBER || n->kind==FE_N_INDEX))
s->proj_field=own_projected_field(n,&s->proj_base);
t=check_lvalue_dispatch(s,n,read);
s->proj_field=save_field;
s->proj_base=save_base;
return t;
}
static FeType *check_lvalue_dispatch(FeCheckerState *s, FeNode *n, int read)
{
FeType *base=0;
FeFieldType *field;
@@ -786,6 +821,7 @@ void m7_check_decl_stmt(FeCheckerState *s, FeNode *n, int mutable)
if (sym && n->b && n->b->kind==FE_N_UNARY && n->b->text &&
(strcmp(n->b->text,"&")==0 || strcmp(n->b->text,"&mut")==0)) {
sym->borrow_root=own_root_symbol(s,n->b->a);
sym->borrow_field=own_projected_field(n->b->a,0);
sym->borrow_mut=strcmp(n->b->text,"&mut")==0;
sym->borrow_defer=s->defer_depth!=0 ||
own_defer_uses(s->fn_node ? s->fn_node->c : 0,n->text);
+8 -1
View File
@@ -308,7 +308,10 @@ FeType *check_identifier(FeCheckerState *s, FeNode *n)
n->cname = sym->cname;
n->sem_type = sym->type;
if (!sym->fn) {
fe_own_access(s->c->diags,&sym->own,FE_OWN_READ,n->loc);
/* When this identifier is the base of a projection, the read reaches
one field and not the whole value. The chain above left word. */
const char *field = s->proj_base==n ? s->proj_field : 0;
fe_own_access_field(s->c->diags,&sym->own,field,FE_OWN_READ,n->loc);
sym->moved=sym->own.move;
}
return sym->type;
@@ -482,6 +485,10 @@ FeType *check_expr_core(FeCheckerState *s, FeNode *n)
if(a && a->kind==FE_TYPE_REF && !compatible(a->elem,b,value))
err(c,value->loc,"mem.replace value type mismatch");
if(value) mark_moved(s,value,b);
/* The destination is lent for the length of the call, the same
as any other argument. Without this the borrow stays live to
the end of the function and the place can never be read. */
own_release_temporary_borrow(s,arg);
n->sem_type=a && a->kind==FE_TYPE_REF ? a->elem : unknown(c);
fe_type_require_replace(&c->types,n->sem_type);
return n->sem_type;
+9
View File
@@ -30,6 +30,8 @@ struct FeSym {
release the root borrow without a separate alias engine. */
FeOwnState own;
FeSym *borrow_root;
/* Which field of the root this binding borrowed, or null for all of it. */
const char *borrow_field;
int borrow_mut;
int borrow_defer;
FeScope *owner;
@@ -52,6 +54,11 @@ typedef struct FeCheckerState {
unsigned defer_depth;
FeOwnLiveness liveness;
FeNode *fn_node;
/* While a projection is being checked, which field of which base it
reaches. The read happens down at the identifier, which cannot see the
chain above it, so the chain leaves word here on the way down. */
const char *proj_field;
FeNode *proj_base;
} FeCheckerState;
/* The type bindings in force, saved across a nested instantiation. */
@@ -70,6 +77,7 @@ typedef struct FeFlowSlot {
typedef struct FeFlowBorrow {
FeSym *root;
const char *field;
int mutable;
} FeFlowBorrow;
@@ -114,6 +122,7 @@ void flow_restore(FeFlowSlot *slots, unsigned count);
void flow_merge(FeFlowSlot *base, FeFlowSlot *left, FeFlowSlot *right,
unsigned count);
FeSym *own_root_symbol(FeCheckerState *s, FeNode *expr);
const char *own_projected_field(FeNode *expr, FeNode **root_out);
int own_is_global(FeCheckerState *s, FeSym *sym);
void own_borrow_expr(FeCheckerState *s, FeNode *expr, int mutable);
void own_release_temporary_borrow(FeCheckerState *s, FeNode *expr);
+2
View File
@@ -272,6 +272,7 @@ void check_stmt_core(FeCheckerState *s, FeNode *n)
if (sym && n->b && n->b->kind==FE_N_UNARY && n->b->text &&
(strcmp(n->b->text,"&")==0 || strcmp(n->b->text,"&mut")==0)) {
sym->borrow_root=own_root_symbol(s,n->b->a);
sym->borrow_field=own_projected_field(n->b->a,0);
sym->borrow_mut=strcmp(n->b->text,"&mut")==0;
sym->borrow_defer=s->defer_depth != 0 ||
own_defer_uses(s->fn_node ? s->fn_node->c : 0,n->text);
@@ -297,6 +298,7 @@ void check_stmt_core(FeCheckerState *s, FeNode *n)
if (sym && n->b && n->b->kind==FE_N_UNARY && n->b->text &&
(strcmp(n->b->text,"&")==0 || strcmp(n->b->text,"&mut")==0)) {
sym->borrow_root=own_root_symbol(s,n->b->a);
sym->borrow_field=own_projected_field(n->b->a,0);
sym->borrow_mut=strcmp(n->b->text,"&mut")==0;
sym->borrow_defer=s->defer_depth != 0 ||
own_defer_uses(s->fn_node ? s->fn_node->c : 0,n->text);
+226
View File
@@ -92,6 +92,7 @@ int fe_own_place_from_expr(FeNode *expr, FeOwnPlace *place)
void fe_own_state_init(FeOwnState *state, int initialized)
{
unsigned i;
if (!state) return;
state->move = FE_OWN_AVAILABLE;
state->initialized = initialized != 0;
@@ -100,6 +101,12 @@ void fe_own_state_init(FeOwnState *state, int initialized)
state->borrow_conflict = 0;
state->move_loc = fe_own_no_loc();
state->borrow_loc = fe_own_no_loc();
for (i = 0; i < FE_OWN_FIELD_MAX; ++i) {
state->fields[i].name = 0;
state->fields[i].shared = 0;
state->fields[i].exclusive = 0;
state->fields[i].loc = fe_own_no_loc();
}
}
static int fe_own_require_value(FeDiags *diags, FeOwnState *state, FeLoc loc)
@@ -130,8 +137,187 @@ static int fe_own_require_stable_borrow(FeDiags *diags, FeOwnState *state,
return 0;
}
/* Whole-value state only: what a field access has to get past before it looks
at its own entry. `check` reports and decides; `apply` also records. */
static int fe_own_access_whole(FeDiags *diags, FeOwnState *state,
FeOwnAccessKind access, FeLoc loc);
static int fe_own_access_whole_check(FeDiags *diags, FeOwnState *state,
FeOwnAccessKind access, FeLoc loc);
/* The entry for this field, or null. `make` asks for one to be created. */
static FeOwnField *fe_own_field_slot(FeOwnState *state, const char *field,
int make)
{
unsigned i;
unsigned free_slot = FE_OWN_FIELD_MAX;
if (!state || !field) return 0;
for (i = 0; i < FE_OWN_FIELD_MAX; ++i) {
if (state->fields[i].name &&
strcmp(state->fields[i].name, field) == 0) return &state->fields[i];
if (!state->fields[i].name && free_slot == FE_OWN_FIELD_MAX)
free_slot = i;
}
if (!make || free_slot == FE_OWN_FIELD_MAX) return 0;
state->fields[free_slot].name = field;
state->fields[free_slot].shared = 0;
state->fields[free_slot].exclusive = 0;
state->fields[free_slot].loc = fe_own_no_loc();
return &state->fields[free_slot];
}
/* A live borrow of some field, for the accesses that reach the whole value. */
static const FeOwnField *fe_own_field_live(const FeOwnState *state,
int mut_only)
{
unsigned i;
if (!state) return 0;
for (i = 0; i < FE_OWN_FIELD_MAX; ++i) {
const FeOwnField *f = &state->fields[i];
if (!f->name) continue;
if (f->exclusive) return f;
if (!mut_only && f->shared) return f;
}
return 0;
}
void fe_own_release_shared_field(FeOwnState *state, const char *field)
{
FeOwnField *f = fe_own_field_slot(state, field, 0);
if (!f || !f->shared) { fe_own_release_shared(state); return; }
--f->shared;
if (!f->shared && !f->exclusive) f->name = 0;
}
void fe_own_release_exclusive_field(FeOwnState *state, const char *field)
{
FeOwnField *f = fe_own_field_slot(state, field, 0);
if (!f || !f->exclusive) { fe_own_release_exclusive(state); return; }
f->exclusive = 0;
if (!f->shared) f->name = 0;
}
int fe_own_access(FeDiags *diags, FeOwnState *state,
FeOwnAccessKind access, FeLoc loc)
{
return fe_own_access_field(diags, state, 0, access, loc);
}
int fe_own_access_field(FeDiags *diags, FeOwnState *state, const char *field,
FeOwnAccessKind access, FeLoc loc)
{
FeOwnField *f;
const FeOwnField *other;
if (!state) return 0;
if (access == FE_OWN_PROJECTION) return 1;
if (!field) {
/* Reaching the whole value: a borrow of any part of it is in the way.
A shared borrow of a field still lets the whole be read. */
other = fe_own_field_live(state, access == FE_OWN_READ);
if (other) {
fe_own_error_note(diags, loc,
access == FE_OWN_WRITE ? "cannot write while value is borrowed" :
access == FE_OWN_MOVE ? "cannot move while value is borrowed" :
access == FE_OWN_READ ?
"cannot read directly while value is mutably borrowed" :
"cannot borrow while a field of the value is borrowed",
other->loc, "borrow originated here");
return 0;
}
return fe_own_access_whole(diags, state, access, loc);
}
/* Reaching one field: a borrow of the whole value is in the way, and so is
a borrow of this same field. A borrow of a different field is not. */
if (!fe_own_access_whole_check(diags, state, access, loc)) return 0;
f = fe_own_field_slot(state, field,
access == FE_OWN_BORROW_SHARED ||
access == FE_OWN_BORROW_MUT);
if (!f) {
/* No room left in the table, so this borrow covers the whole value.
That reports more than it has to and never less. */
if (access == FE_OWN_BORROW_SHARED || access == FE_OWN_BORROW_MUT)
return fe_own_access_whole(diags, state, access, loc);
return 1;
}
switch (access) {
case FE_OWN_READ:
if (f->exclusive) {
fe_own_error_note(diags, loc,
"cannot read directly while value is mutably borrowed",
f->loc, "mutable borrow originated here");
return 0;
}
return 1;
case FE_OWN_WRITE:
case FE_OWN_MOVE:
if (f->shared || f->exclusive) {
fe_own_error_note(diags, loc,
access == FE_OWN_WRITE ? "cannot write while value is borrowed"
: "cannot move while value is borrowed",
f->loc, "borrow originated here");
return 0;
}
return 1;
case FE_OWN_BORROW_SHARED:
if (f->exclusive) {
fe_own_error_note(diags, loc,
"cannot create shared borrow while mutable borrow is live",
f->loc, "mutable borrow originated here");
return 0;
}
if (!f->shared) f->loc = loc;
++f->shared;
return 1;
case FE_OWN_BORROW_MUT:
if (f->shared || f->exclusive) {
fe_own_error_note(diags, loc,
"cannot create mutable borrow while another borrow is live",
f->loc, "existing borrow originated here");
return 0;
}
f->exclusive = 1;
f->loc = loc;
return 1;
default:
break;
}
return 1;
}
static int fe_own_access_whole_check(FeDiags *diags, FeOwnState *state,
FeOwnAccessKind access, FeLoc loc)
{
if (!fe_own_require_stable_borrow(diags, state, loc)) return 0;
if (access == FE_OWN_WRITE) {
if (state->shared || state->exclusive) {
fe_own_error_note(diags, loc, "cannot write while value is borrowed",
state->borrow_loc, "borrow originated here");
return 0;
}
return 1;
}
if (!fe_own_require_value(diags, state, loc)) return 0;
if (access == FE_OWN_READ || access == FE_OWN_BORROW_SHARED) {
if (state->exclusive) {
fe_own_error_note(diags, loc, access == FE_OWN_READ ?
"cannot read directly while value is mutably borrowed" :
"cannot create shared borrow while mutable borrow is live",
state->borrow_loc, "mutable borrow originated here");
return 0;
}
return 1;
}
if (state->shared || state->exclusive) {
fe_own_error_note(diags, loc, access == FE_OWN_MOVE ?
"cannot move while value is borrowed" :
"cannot create mutable borrow while another borrow is live",
state->borrow_loc, "existing borrow originated here");
return 0;
}
return 1;
}
static int fe_own_access_whole(FeDiags *diags, FeOwnState *state,
FeOwnAccessKind access, FeLoc loc)
{
if (!state) return 0;
if (access == FE_OWN_PROJECTION) return 1;
@@ -225,9 +411,38 @@ void fe_own_release_exclusive(FeOwnState *state)
if (!state->shared) state->borrow_loc = fe_own_no_loc();
}
/* Merging two paths through the code: a borrow that is live on either side is
live after, because the checker cannot know which side ran. */
static void fe_own_merge_fields(FeOwnState *out, const FeOwnState *left,
const FeOwnState *right)
{
unsigned i;
unsigned j;
for (i = 0; i < FE_OWN_FIELD_MAX; ++i) out->fields[i] = left->fields[i];
for (i = 0; i < FE_OWN_FIELD_MAX; ++i) {
const FeOwnField *r = &right->fields[i];
if (!r->name) continue;
for (j = 0; j < FE_OWN_FIELD_MAX; ++j) {
if (out->fields[j].name &&
strcmp(out->fields[j].name, r->name) != 0) continue;
if (!out->fields[j].name) out->fields[j] = *r;
else {
if (r->shared > out->fields[j].shared)
out->fields[j].shared = r->shared;
if (r->exclusive && !out->fields[j].exclusive) {
out->fields[j].exclusive = 1;
out->fields[j].loc = r->loc;
}
}
break;
}
}
}
FeOwnState fe_own_merge_state(FeOwnState left, FeOwnState right)
{
FeOwnState out;
fe_own_merge_fields(&out, &left, &right);
out.move = left.move == right.move ? left.move :
fe_own_merge_move(left.move, right.move);
out.initialized = left.initialized && right.initialized;
@@ -246,6 +461,17 @@ FeOwnState fe_own_merge_state(FeOwnState left, FeOwnState right)
int fe_own_state_equal(const FeOwnState *left, const FeOwnState *right)
{
if (!left || !right) return 0;
{
unsigned i;
for (i = 0; i < FE_OWN_FIELD_MAX; ++i) {
const FeOwnField *a = &left->fields[i];
const FeOwnField *b = &right->fields[i];
if (!a->name != !b->name) return 0;
if (a->name && strcmp(a->name, b->name) != 0) return 0;
if (a->shared != b->shared || a->exclusive != b->exclusive)
return 0;
}
}
return left->move == right->move &&
left->initialized == right->initialized &&
left->shared == right->shared &&
+26
View File
@@ -34,6 +34,22 @@ typedef struct FeOwnPlace {
int projected;
} FeOwnPlace;
/* How many distinct fields of one value can be borrowed at once. Past this
a borrow falls back to covering the whole value, which reports more than it
has to but never less. */
#define FE_OWN_FIELD_MAX 4
/* A borrow of one field rather than of the whole value. `self.bytes` and
`self.used_bytes` are different places, so borrowing one has to leave the
other readable -- otherwise a method cannot write through one field while
reading another, which is most of what a method does. */
typedef struct FeOwnField {
const char *name;
unsigned shared;
int exclusive;
FeLoc loc;
} FeOwnField;
typedef struct FeOwnState {
int move;
int initialized;
@@ -42,6 +58,10 @@ typedef struct FeOwnState {
int borrow_conflict;
FeLoc move_loc;
FeLoc borrow_loc;
/* Whole-value state is above; these cover one field each. A whole-value
borrow conflicts with every field, and a field borrow conflicts with
the whole value and with itself. */
FeOwnField fields[FE_OWN_FIELD_MAX];
} FeOwnState;
typedef struct FeOwnProvenance {
@@ -72,9 +92,15 @@ int fe_own_place_from_expr(FeNode *expr, FeOwnPlace *place);
void fe_own_state_init(FeOwnState *state, int initialized);
int fe_own_access(FeDiags *diags, FeOwnState *state,
FeOwnAccessKind access, FeLoc loc);
/* The same, but reaching only one field of the value. A null `field` is the
whole value and behaves exactly as `fe_own_access`. */
int fe_own_access_field(FeDiags *diags, FeOwnState *state, const char *field,
FeOwnAccessKind access, FeLoc loc);
int fe_own_call_shared_view(FeDiags *diags, FeOwnState *state, FeLoc loc);
void fe_own_release_shared(FeOwnState *state);
void fe_own_release_exclusive(FeOwnState *state);
void fe_own_release_shared_field(FeOwnState *state, const char *field);
void fe_own_release_exclusive_field(FeOwnState *state, const char *field);
FeOwnState fe_own_merge_state(FeOwnState left, FeOwnState right);
int fe_own_state_equal(const FeOwnState *left, const FeOwnState *right);
int fe_own_loop_merge_state(FeOwnState entry, FeOwnState backedge,
+14 -25
View File
@@ -92,33 +92,22 @@ pub struct Map(V) {
return;
}
/// Make sure `need` bytes fit, moving to a bigger buffer if they do not.
///
/// Kept apart from `keep` because the borrow that swaps the buffer in is
/// a borrow of the whole of `self` -- borrowing is tracked at the root --
/// and it has to be over before any field is read again.
fn ensure_room(self: &mut Self, need: usize) -> !void {
let have: usize = self.bytes.^.n;
if need <= have { return; }
var room: usize = have;
while room < need { room = room * 2; }
let bigger: ^[]mut u8 = try mem.alloc_slice(u8, room);
var k: usize = 0;
let filled: usize = self.used_bytes;
while k < filled {
bigger.^[k] = self.bytes.^[k];
k = k + 1;
}
let old: ^[]mut u8 = mem.replace(&mut self.bytes, bigger);
mem.destroy(old);
return;
}
/// Copy `key` into the byte buffer and point the slot at it.
/// Copy `key` into the byte buffer and point the slot at it, moving to a
/// bigger buffer first if it does not fit.
fn keep(self: &mut Self, key: []u8, slot: usize) -> !void {
let at: usize = self.used_bytes;
let need: usize = at + key.n;
try self.ensure_room(need);
if at + key.n > self.bytes.^.n {
var room: usize = self.bytes.^.n;
while room < at + key.n { room = room * 2; }
let bigger: ^[]mut u8 = try mem.alloc_slice(u8, room);
var k: usize = 0;
while k < at {
bigger.^[k] = self.bytes.^[k];
k = k + 1;
}
let old: ^[]mut u8 = mem.replace(&mut self.bytes, bigger);
mem.destroy(old);
}
var i: usize = 0;
while i < key.n {
self.bytes.^[at + i] = key[i];
+81
View File
@@ -0,0 +1,81 @@
// EXIT:0
// OUTPUT:a 40 b 3
// OUTPUT:used 5 room 8
// OUTPUT:first 9
// OUTPUT:balanced
unit fieldbrw;
import std.io;
import std.sys;
import std.mem;
// Borrowing is per field. `p.a` and `p.b` are different places, so lending one
// out has to leave the other readable -- otherwise a method cannot write
// through one field while reading another, which is most of what a method
// does.
struct Pair { a: i32, b: i32, }
fn scale(v: &mut i32, by: i32) -> void {
v.^ = v.^ * by;
return;
}
struct Box {
bytes: ^[]mut u8,
used: usize,
room: usize,
fn with_capacity(n: usize) -> !Self {
let room: ^[]mut u8 = try mem.alloc_slice(u8, n);
return Self{ bytes: room, used: 0, room: n };
}
/// Move to a bigger buffer, then go on reading the other fields. The
/// borrow that hands over the buffer covers `bytes` and nothing else.
fn grow(self: &mut Self, want: usize) -> !void {
let bigger: ^[]mut u8 = try mem.alloc_slice(u8, want);
var i: usize = 0;
while i < self.used {
bigger.^[i] = self.bytes.^[i];
i = i + 1;
}
let old: ^[]mut u8 = mem.replace(&mut self.bytes, bigger);
mem.destroy(old);
self.room = want;
return;
}
fn push(self: &mut Self, v: u8) -> !void {
if self.used == self.room { try self.grow(self.room * 2); }
self.bytes.^[self.used] = v;
self.used = self.used + 1;
return;
}
}
fn run() -> !void {
var p: Pair = Pair{ a: 4, b: 2 };
let left: &mut i32 = &mut p.a;
// Writing another field while `a` is lent out.
p.b = 3;
scale(left, 10);
@print("a {} b {}\n", p.a, p.b);
var box: Box = try Box.with_capacity(4);
try box.push(9);
try box.push(8);
try box.push(7);
try box.push(6);
try box.push(5);
@print("used {} room {}\n", box.used, box.room);
@print("first {}\n", box.bytes.^[0]);
return;
}
fn main() -> i32 {
run() catch |e| { @print("failed\n"); return 1; };
if sys.allocs() == sys.frees() { @print("balanced\n"); }
else { @print("leaked\n"); }
return 0;
}
+14
View File
@@ -0,0 +1,14 @@
// ERROR:11:borrow
unit badrall;
struct Pair { a: i32, b: i32, }
fn take(q: Pair) -> i32 { return q.a; }
fn bad() -> i32 {
var p = Pair{ a: 1, b: 2 };
let left = &mut p.a;
let n = take(p);
left.^ = 4;
return n;
}
+1 -1
View File
@@ -6,6 +6,6 @@ struct Pair { a: i32, b: i32, }
fn bad() -> void {
var p = Pair{ a: 1, b: 2 };
let left = &mut p.a;
p.b = 3;
p.a = 3;
left.^ = 4;
}
+12
View File
@@ -0,0 +1,12 @@
// ERROR:9:borrow
unit badrsame;
struct Pair { a: i32, b: i32, }
fn bad() -> void {
var p = Pair{ a: 1, b: 2 };
let one = &mut p.a;
let two = &mut p.a;
one.^ = 1;
two.^ = 2;
}
+14
View File
@@ -0,0 +1,14 @@
unit okrfld;
// Borrowing one field leaves the others alone. `p.a` and `p.b` are different
// places, so a borrow of one says nothing about the other.
struct Pair { a: i32, b: i32, }
fn ok() -> i32 {
var p = Pair{ a: 1, b: 2 };
let left = &mut p.a;
p.b = 3;
left.^ = 4;
return p.a + p.b;
}