GOAL P0-1: 니치 옵셔널이 포인터 대신 포인터가 든 자리를 넘겼다
?T 의 페이로드가 null 이 될 수 없으면 태그를 따로 두지 않고 그 불가능한 값을 null 로 쓴다. ?^T 와 ?&T 가 그렇다. 그런데 if let 이 그것을 풀 때 두 경우를 한 갈래로 처리하고 있었다. 바인딩이 참조인 이유가 둘이다. 페이로드가 값이면 바인딩은 그것이 래퍼 안에 앉은 자리를 가리켜야 하고(주소), 페이로드가 이미 포인터면 바인딩은 그 포인터여야 한다(값). 후자에 주소를 쓰면 포인터의 포인터가 되고, 프로그램은 값이 있어야 할 자리에서 주소를 읽는다. 컴파일도 되고 실행도 됐다. ?i32 5 (맞았음 -- 태그가 있어서 다른 길로 갔다) ?^i32 6125480 → 5 ?&i32 6125496 → 5 그리고 옵셔널을 null 과 비교하는 것이 lowering 되지 않았다 -- 래퍼 전체를 값으로 읽으려 해서 'cannot lower an aggregate as a value' 였다. 태그만 보면 되는 질문이다. optional/oknull.fe 가 검사만 하는 fixture 라 드러나지 않았다. exec/optref.fe 가 세 모양을 전부 고정한다: if let, orelse, .?, == null, 그리고 R7 관용구인 mem.replace(&mut box, null).? 로 소유자를 꺼내 놓는 것까지. 229/229, 33/33.
This commit is contained in:
@@ -78,6 +78,28 @@ Slot lower_expr_core(Lower *L, FeNode *n)
|
|||||||
if (n->text && (!strcmp(n->text, "and") || !strcmp(n->text, "or")))
|
if (n->text && (!strcmp(n->text, "and") || !strcmp(n->text, "or")))
|
||||||
return lower_logical(L, n, !strcmp(n->text, "and"));
|
return lower_logical(L, n, !strcmp(n->text, "and"));
|
||||||
op = binary_op(n->text, &is_cmp);
|
op = binary_op(n->text, &is_cmp);
|
||||||
|
/* Comparing an optional with `null` asks about its tag, not about the
|
||||||
|
bytes of the whole wrapper -- which has no value form at all. */
|
||||||
|
if ((op == FE_IR_EQ || op == FE_IR_NE) && n->a && n->b) {
|
||||||
|
FeNode *w = fe_m7_is_null(n->b) ? n->a :
|
||||||
|
(fe_m7_is_null(n->a) ? n->b : 0);
|
||||||
|
FeType *wt = w ? w->sem_type : 0;
|
||||||
|
if (wt && wt->kind == FE_TYPE_OPTIONAL) {
|
||||||
|
Slot s = lower_expr(L, w);
|
||||||
|
unsigned t0;
|
||||||
|
unsigned z;
|
||||||
|
if (!s.is_place) {
|
||||||
|
fail(L, "an optional with no place", w);
|
||||||
|
return slot_void();
|
||||||
|
}
|
||||||
|
t0 = wrapper_tag(L, s, wt, w);
|
||||||
|
z = fe_ir_const(L->m, L->b,
|
||||||
|
uses_niche(wt) ? FE_IR_PTR : FE_IR_I8, 0);
|
||||||
|
return slot_value(fe_ir_binary(L->m, L->b, op,
|
||||||
|
uses_niche(wt) ? FE_IR_PTR : FE_IR_I8, t0, z, 1),
|
||||||
|
FE_IR_I8);
|
||||||
|
}
|
||||||
|
}
|
||||||
operand = ir_type(n->a ? n->a->sem_type : 0);
|
operand = ir_type(n->a ? n->a->sem_type : 0);
|
||||||
if (operand == FE_IR_VOID || operand == FE_IR_MEM) operand = FE_IR_I32;
|
if (operand == FE_IR_VOID || operand == FE_IR_MEM) operand = FE_IR_I32;
|
||||||
a = as_value(L, lower_expr(L, n->a), n->a);
|
a = as_value(L, lower_expr(L, n->a), n->a);
|
||||||
|
|||||||
+14
-4
@@ -344,10 +344,20 @@ void lower_if_let(Lower *L, FeNode *n)
|
|||||||
FeType *bt = binding->sem_type;
|
FeType *bt = binding->sem_type;
|
||||||
Slot payload = wrapper_payload(L, value, opt);
|
Slot payload = wrapper_payload(L, value, opt);
|
||||||
unsigned local = declare_var(L, binding->cname, bt, binding->text);
|
unsigned local = declare_var(L, binding->cname, bt, binding->text);
|
||||||
if (bt && (bt->kind == FE_TYPE_REF || bt->kind == FE_TYPE_RAW))
|
if (bt && (bt->kind == FE_TYPE_REF || bt->kind == FE_TYPE_RAW)) {
|
||||||
fe_ir_store(L->m, L->b, fe_ir_at_local(local, 0),
|
/* The binding is a reference either way, but for two different
|
||||||
as_address(L, payload, n), FE_IR_PTR);
|
reasons. When the payload is itself a single pointer (`^T`,
|
||||||
else
|
`&T`) the binding *is* that pointer, so it has to be read out.
|
||||||
|
When the payload is a value the binding points at where it sits
|
||||||
|
inside the wrapper, so the address is what is wanted. Taking the
|
||||||
|
address in the first case gives a pointer to the pointer, and
|
||||||
|
the program reads an address where it expects a value. */
|
||||||
|
FeType *pl = opt ? (opt->kind == FE_TYPE_ERROR_UNION
|
||||||
|
? opt->error_value : opt->elem) : 0;
|
||||||
|
unsigned p = pl && ir_type(pl) == FE_IR_PTR
|
||||||
|
? as_value(L, payload, n) : as_address(L, payload, n);
|
||||||
|
fe_ir_store(L->m, L->b, fe_ir_at_local(local, 0), p, FE_IR_PTR);
|
||||||
|
} else
|
||||||
store_into(L, fe_ir_at_local(local, 0), payload, n, ir_size(bt));
|
store_into(L, fe_ir_at_local(local, 0), payload, n, ir_size(bt));
|
||||||
}
|
}
|
||||||
lower_stmt(L, n->b);
|
lower_stmt(L, n->b);
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
// EXIT:0
|
||||||
|
// OUTPUT:int 5 none 0
|
||||||
|
// OUTPUT:own 5 absent -1
|
||||||
|
// OUTPUT:ref 5 absent yes
|
||||||
|
// OUTPUT:cmp yes no
|
||||||
|
// OUTPUT:unwrap 5
|
||||||
|
// OUTPUT:balanced
|
||||||
|
unit optref;
|
||||||
|
|
||||||
|
import std.io;
|
||||||
|
import std.sys;
|
||||||
|
import std.mem;
|
||||||
|
|
||||||
|
// An optional whose payload cannot be null keeps no separate tag: the payload's
|
||||||
|
// own impossible value is `null`. `?i32` carries a tag; `?^T` and `?&T` do not.
|
||||||
|
//
|
||||||
|
// Both shapes have to answer the same questions, and the pointer-shaped ones
|
||||||
|
// were answering with the address of the slot the pointer sits in rather than
|
||||||
|
// with the pointer -- one level of indirection too many, in code that compiled
|
||||||
|
// and ran.
|
||||||
|
|
||||||
|
fn opt_int(on: bool) -> ?i32 {
|
||||||
|
if on { return 5; }
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A niche optional over an owner. The `if let` binding is `&i32` -- the
|
||||||
|
/// pointer itself, not where it is kept -- and `.?` moves the owner out so it
|
||||||
|
/// can be let go of.
|
||||||
|
fn take(o: ?^i32) -> i32 {
|
||||||
|
var box: ?^i32 = o;
|
||||||
|
var v: i32 = -1;
|
||||||
|
if let Some(p) = box { v = p.^; }
|
||||||
|
if box == null { return v; }
|
||||||
|
// SPEC 5 R7: a non-Copy value leaves a projection through `mem.replace`,
|
||||||
|
// which puts something valid back where it was.
|
||||||
|
let owner: ^i32 = mem.replace(&mut box, null).?;
|
||||||
|
mem.destroy(owner);
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make(v: i32) -> ?^i32 {
|
||||||
|
let p: ^i32 = mem.create(v) catch |e| { return null; };
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Bag {
|
||||||
|
items: ^[]mut i32,
|
||||||
|
|
||||||
|
/// SPEC 5 R8(a): derived from `self`, so the borrow is the caller's.
|
||||||
|
fn at(self: &Self, i: usize) -> ?&i32 {
|
||||||
|
if i >= self.items.^.n { return null; }
|
||||||
|
return &self.items.^[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run() -> !void {
|
||||||
|
// A tag in front of the payload.
|
||||||
|
var got: i32 = 0;
|
||||||
|
if let Some(a) = opt_int(true) { got = a; }
|
||||||
|
let none: i32 = opt_int(false) orelse 0;
|
||||||
|
@print("int {} none {}\n", got, none);
|
||||||
|
|
||||||
|
// A niche over `^i32`.
|
||||||
|
@print("own {} absent {}\n", take(make(5)), take(null));
|
||||||
|
|
||||||
|
// A niche over `&i32`.
|
||||||
|
let room: ^[]mut i32 = try mem.alloc_slice(i32, 2);
|
||||||
|
room.^[0] = 5;
|
||||||
|
let b: Bag = Bag{ items: room };
|
||||||
|
var seen: i32 = 0;
|
||||||
|
if let Some(r) = b.at(0) { seen = r.^; }
|
||||||
|
var past: bool = false;
|
||||||
|
if let Some(r) = b.at(9) { seen = seen; } else { past = true; }
|
||||||
|
@print("ref {} absent {}\n", seen, yesno(past));
|
||||||
|
|
||||||
|
// Comparing against null reads the tag, not the bytes of the wrapper.
|
||||||
|
@print("cmp {} {}\n", yesno(b.at(9) == null), yesno(b.at(0) == null));
|
||||||
|
@print("unwrap {}\n", b.at(0).?.^);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn yesno(b: bool) -> []u8 {
|
||||||
|
if b { return "yes"; }
|
||||||
|
return "no";
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user