대여는 변수가 아니라 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:
@@ -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가 실행되는 스코프 끝까지 연장한다.
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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 &&
|
||||
|
||||
@@ -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
@@ -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];
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user