대여는 변수가 아니라 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
+6 -4
View File
@@ -251,18 +251,20 @@ fn read_all(path: str) -> !^[]u8 { // 표준 io는 core.Error로 통일
**R6 (배타성).** `&mut x`가 살아있는 동안 `x`에 대한 다른 참조 생성, 직접 읽기/쓰기, 이동이 금지된다. `&x`(공유)는 여러 개 동시 가능하지만 그동안 `x`에 쓰기/이동 금지.
v0.1의 대여 상태는 **root local/parameter 단위**로 추적한다. projection은 place의 root를 찾는 데만 쓰며 field-sensitive/index-sensitive 독립성을 증명하지 않는다. 따라서 `&mut p.a``p` 전체를 배타 대여하고 그동안 `p.b`의 읽기·쓰기·대여도 금지한다. `&mut xs[0]``&mut xs[1]`도 서로 다른 index라는 이유로 분리하지 않고 같은 root `xs`의 충돌 대여로 본다. 이는 compiler A/B의 함수-local 상태 기계를 작고 결정적으로 유지하기 위한 v0.1의 의도적인 보수성이다.
여기서 `x`는 변수가 아니라 **place**다. `p.a``p.b`는 서로 다른 place이므로 한쪽을 대여해도 다른 쪽은 그대로 읽고 쓸 수 있다. 대여가 필드 단위로 갈라지는 것은 루트 변수의 직속 필드 한 겹까지이며, 그 아래(`p.a.b`)와 인덱스(`arr[i]`)·역참조(`p.^`)는 전체를 대여한 것으로 본다. 전체를 대여하면 모든 필드와 충돌하고, 필드를 대여하면 전체 및 같은 필드와 충돌한다. 한 값에서 동시에 갈라둘 수 있는 필드 수에는 구현 상한이 있고, 넘으면 전체 대여로 되돌아간다 — 더 많이 보고할 뿐 놓치지는 않는다.
대여 상태는 **root local/parameter 와 그 직속 필드** 단위로 추적한다. `&mut xs[0]``&mut xs[1]`은 서로 다른 index라는 이유로 분리하지 않고 같은 root `xs`의 충돌 대여로 본다 — index는 상수가 아닐 수 있고, 그것을 따지는 것은 함수-local 상태 기계가 감당할 일이 아니다. 필드 이름은 상수라 그 문제가 없으므로 갈라진다.
```fe
var p = Pair{ a: 1, b: 2 };
let r = &mut p.a;
p.b = 3; // 에러: root p가 배타 대여 중
p.b = 3; // ok: p.b는 다른 place
p.a = 3; // 에러: p.a가 배타 대여 중
take(p); // 에러: 전체는 대여된 필드를 포함한다
r.^ = 4;
let a = &mut xs[0];
let b = &mut xs[1]; // 에러: 둘 다 root xs를 대여
a.^ = 1;
b.^ = 2;
```
참조의 생존 구간은 **참조 변수의 마지막 사용 지점까지**다. 그 이후에는 원본에 대한 접근·이동이 다시 허용된다. 조건부 흐름에서는 모든 경로의 마지막 사용 중 가장 나중 지점을 취한다. 임시 참조(`f(&x)`)는 그 문장 끝까지다. `defer` 블록에서 사용한 참조와 그 원본의 대여는 해당 defer가 실행되는 스코프 끝까지 연장한다.
+3 -2
View File
@@ -24,6 +24,7 @@ uv run python tests/exec.py 31/31 컴파일된 프로그램이 실제로
| **Ferro 렉서를 Ferro 로** | 강제 함수. 돌아간다 |
| `std.map` | 키를 맵이 소유한 버퍼에 복사하고 슬롯은 위치만 든다 |
| **Ferro 파서를 Ferro 로** | 노드 배열 하나 + 인덱스. `1 + 2 * 3``(+ 1 (* 2 3))` 로 묶인다 |
| 필드 단위 대여 | `p.a``p.b` 는 다른 place 다. `std.map``keep` 이 다시 함수 하나가 됐다 |
파서가 알려준 것: **자기 참조 자료구조는 인덱스로 짓는다.** 노드는 `^Node`
들 수 없고(자식이 여럿이며 한 번씩 소유하지 않는다) `&Node` 도 들 수 없다(R4).
@@ -38,8 +39,7 @@ uv run python tests/exec.py 31/31 컴파일된 프로그램이 실제로
| 1 | 심볼 표와 이름 해석을 Ferro 로 | 중 | `std.map` 이 준비됐다 |
| 2 | `io.read` 로 줄 단위 읽기 | 소 | 지금은 버퍼 하나로 통째로 읽는다 |
| 3 | 여러 반환값 또는 out 파라미터 | 중 | `&mut` 재대여로 되지만 장황하다 |
| 4 | 필드 단위 대여 | | 지금은 루트 단위라 `self` 의 한 필드에 쓰는 동안 다른 필드를 못 읽는다 |
| 5 | `fec` 을 Ferro 로 | 대 | 여기까지 오면 언어가 자기 무게를 견딘다 |
| 4 | `fec` 을 Ferro 로 | | 여기까지 오면 언어가 자기 무게를 견딘다 |
## 언어에 남은 구멍
@@ -69,6 +69,7 @@ uv run python tests/exec.py 31/31 컴파일된 프로그램이 실제로
| 해제 | 소유자를 놓으면 그것이 가진 것도 놓는다 (R1). `drop` 을 부른 뒤 필드로 내려간다 — 그래서 `List`/`Arena`/`Map``drop` 이 없다 |
| 배타 대여를 호출에 넘기기 | **이동이 아니라 호출 동안의 재대여** (SPEC §4.2, §5 R6). 없으면 배타 파라미터를 함수당 한 번만 넘길 수 있어 재귀 하강 파서를 못 쓴다 |
| 자기 `drop` 안의 부분 이동 | **허용** (SPEC §5 R7 예외). 객체가 사라지는 중이라 뒤에 남아 읽힐 것이 없다 |
| 대여 단위 | **place 단위** (SPEC §5 R6). 루트 변수의 직속 필드 한 겹까지 갈라지고, 그 아래와 인덱스·역참조는 전체 대여다. 한 값당 4 필드까지, 넘으면 전체로 되돌아간다 |
---
+56 -5
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,7 +560,7 @@ 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,
fe_own_access_field(s->c->diags,&root->own,field,
mutable ? FE_OWN_BORROW_MUT : FE_OWN_BORROW_SHARED,
expr->loc);
}
@@ -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,
+8 -19
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; }
/// 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;
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;
let filled: usize = self.used_bytes;
while k < filled {
while k < at {
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.
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);
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;
}