std: 프로그램이 바깥 세상과 이야기한다 -- 파일과 명령줄

런타임에 open/read/close 와 명령줄을 넣었다. std.io 가 그 위에 파일 열기,
읽기, 쓰기, 그리고 명령줄을 조각으로 나누는 것을 얹는다. 인용부호 처리는
런타임이 알 일이 아니라 라이브러리가 할 일이다.

길에서 고친 것들:

- *T 가 타입 시스템에 실체가 없어서 덩어리로 취급됐다. 이제 진짜 종류다 --
  주소일 뿐이고 추적할 대여도 실행할 drop 도 없는 Copy 타입. 그 결과 &u8 이
  *u8 에 자동으로 맞지 않게 됐는데, 그게 맞다: R9 는 그 변환을 unsafe 안의
  @ptr_cast 로만 허용한다.
- raw 포인터에 정수를 더하면 더 뒤의 주소다. 소유자나 대여에는 허용하지
  않는다 -- 자기 자리가 있는 것에서 걸어나가는 것이 *T 의 용도다.
- @volatile_load / @volatile_store / @ptr_cast 를 내린다.
- undefined 가 선언된 타입을 따른다. 없으면 손으로 타이핑할 수 있는 것보다
  큰 버퍼를 선언할 방법이 아예 없었다.

R8 이 정확히 동작하는 것도 확인했다: 참조성 파라미터가 둘인 함수는 슬라이스를
반환할 수 없다. 어디서 파생됐는지 시그니처가 말하지 않기 때문이다.

exec.py 24/24.
This commit is contained in:
2026-08-17 07:11:56 +09:00
parent e014c95a75
commit 676fef88fb
12 changed files with 340 additions and 5 deletions
+8
View File
@@ -444,6 +444,14 @@ FeType *check_expr(FeCheckerState *s, FeNode *n)
n->sem_type=fe_type_intern(&s->c->types,"bool");
return n->sem_type;
}
/* A raw pointer plus a number is an address further along. Only raw
pointers: an owner or a borrow has a place it belongs to, and
walking away from it is what `*T` is for. */
if (known(a) && a->kind==FE_TYPE_RAW && known(b) &&
fe_type_is_integer(b) && op[0] && (op[0]=='+' || op[0]=='-')) {
n->sem_type=a;
return n->sem_type;
}
if ((known(a) && !fe_type_is_integer(a)) ||
(known(b) && !fe_type_is_integer(b)) ||
(known(a) && known(b) && !fe_type_equal(a,b) &&
+13
View File
@@ -495,6 +495,19 @@ FeType *check_expr_core(FeCheckerState *s, FeNode *n)
if(!target || !known(target)) err(c,n->loc,"size/align requires a known type");
n->sem_type=fe_type_intern(&c->types,"usize"); return n->sem_type;
}
if (!n->a && n->text && strcmp(n->text,"@ptr_cast")==0) {
/* `@ptr_cast(T, p)`: the first argument names the type the result
points at, the second is the address. R9 keeps it in `unsafe`. */
FeNode *type_arg=n->children;
FeNode *value=type_arg ? type_arg->next : 0;
FeType *target=type_arg && type_arg->kind==FE_N_IDENT ?
fe_type_intern(&c->types,type_arg->text) : unknown(c);
if (!type_arg || !value || value->next)
err(c,n->loc,"@ptr_cast requires a type and a pointer");
if (value) check_expr(s,value);
n->sem_type=fe_type_raw(&c->types,target);
return n->sem_type;
}
if (n->a && n->a->kind == FE_N_MEMBER) {
FeNode *method;
FeNode *self_param;
+8
View File
@@ -535,6 +535,14 @@ FeType *m7_check_expected(FeCheckerState *s, FeNode *value,
FeType *actual;
FeM7ContextKind context;
if (!value) return unknown(s->c);
/* `undefined` is not a value, it is the absence of one: it takes whatever
type was asked for, and says the storage starts out unset. Without this
there is no way to declare a buffer larger than you care to type out. */
if (value->kind==FE_N_LITERAL && value->text &&
!strcmp(value->text,"undefined") && expected) {
value->sem_type=expected;
return expected;
}
if (fe_m7_is_null(value)) {
if (!fe_m7_can_contextual_null(expected)) {
err(s->c,value->loc,"null requires a contextual optional type");
+32 -1
View File
@@ -25,7 +25,8 @@ FeIrType ir_type_of(const FeType *t)
if (t->bits <= 8U) return FE_IR_I8;
if (t->bits <= 16U) return FE_IR_I16;
return FE_IR_I32;
case FE_TYPE_REF: return FE_IR_PTR;
case FE_TYPE_REF:
case FE_TYPE_RAW: return FE_IR_PTR;
case FE_TYPE_OWNED:
/* An owned slice carries a length beside the pointer. */
return t->elem && t->elem->kind == FE_TYPE_SLICE ? FE_IR_MEM : FE_IR_PTR;
@@ -391,6 +392,36 @@ int lower_builtin(Lower *L, FeNode *n, Slot *out)
*out = slot_value(fe_ir_const(L->m, L->b, FE_IR_I32, v), FE_IR_I32);
return 1;
}
if (!strcmp(name, "@volatile_load")) {
/* Reading through a raw pointer. Nothing here reorders loads yet, so
volatile and ordinary read the same; the keyword is what marks the
access as deliberate, and the checker already required `unsafe`. */
FeNode *arg = n->children;
unsigned p = as_value(L, lower_expr(L, arg), arg);
FeIrType t = ir_type(n->sem_type);
if (t == FE_IR_VOID || t == FE_IR_MEM) t = FE_IR_I8;
*out = slot_place(fe_ir_at_temp(p, 0), t, ir_size(n->sem_type));
return 1;
}
if (!strcmp(name, "@volatile_store")) {
FeNode *arg = n->children;
FeNode *value = arg ? arg->next : 0;
unsigned p = as_value(L, lower_expr(L, arg), arg);
Slot v = lower_expr(L, value);
FeIrType t = value && value->sem_type ? ir_type(value->sem_type)
: FE_IR_I8;
fe_ir_store(L->m, L->b, fe_ir_at_temp(p, 0), as_value(L, v, value), t);
*out = slot_void();
return 1;
}
if (!strcmp(name, "@ptr_cast")) {
/* A pointer is a pointer; the type it is said to point at is the
checker's business and leaves no trace here. */
FeNode *arg = n->children;
FeNode *value = arg ? arg->next : 0;
*out = slot_value(as_value(L, lower_expr(L, value), value), FE_IR_PTR);
return 1;
}
if (!strcmp(name, "@line")) {
*out = slot_value(fe_ir_const(L->m, L->b, FE_IR_I32,
(long)n->loc.line), FE_IR_I32);
+4
View File
@@ -333,6 +333,10 @@ void lower_stmt(Lower *L, FeNode *n)
case FE_N_VAR:
case FE_N_CONST: {
unsigned local = declare_var(L, n->cname, n->sem_type, n->text);
/* `undefined` says the storage starts out unset, so there is nothing
to write into it. */
if (n->b && n->b->kind == FE_N_LITERAL && n->b->text &&
!strcmp(n->b->text, "undefined")) return;
if (n->b) {
Slot v = lower_expr(L, n->b);
unsigned flag;
+17 -2
View File
@@ -234,6 +234,21 @@ FeType *fe_type_ref(FeTypeCtx *ctx, FeType *elem, int mutable)
return t;
}
FeType *fe_type_raw(FeTypeCtx *ctx, FeType *elem)
{
char key[320];
FeType *t;
sprintf(key, "*%s", elem ? elem->name : "?");
t = fe_type_intern(ctx, key);
if (t->kind == FE_TYPE_UNKNOWN) {
t->kind = FE_TYPE_RAW;
t->elem = elem;
t->size = FE_PTR_SIZE;
t->align = FE_PTR_ALIGN;
}
return t;
}
FeType *fe_type_owned(FeTypeCtx *ctx, FeType *elem)
{
char key[320];
@@ -485,7 +500,7 @@ static void layout_type(FeTypeCtx *ctx, FeType *t)
if (t->size > 4UL) t->size = 4UL;
t->cycle_state = 2; return;
}
if (t->kind == FE_TYPE_REF) {
if (t->kind == FE_TYPE_REF || t->kind == FE_TYPE_RAW) {
t->size = FE_PTR_SIZE;
t->align = FE_PTR_ALIGN;
t->cycle_state = 2; return;
@@ -613,7 +628,7 @@ FeType *fe_type_from_ast(FeTypeCtx *ctx, const FeNode *node)
return fe_type_error_union(ctx,fe_type_from_ast(ctx,node->a));
}
if (node->text && strcmp(node->text, "*") == 0)
return fe_type_intern(ctx, "<unknown>");
return fe_type_raw(ctx, fe_type_from_ast(ctx, node->a));
if (node->text && strcmp(node->text, "fn") == 0)
return fe_type_intern(ctx, "<unknown>");
/* A plain named type may be a generic declaration -- with arguments it is
+6 -1
View File
@@ -7,7 +7,11 @@ typedef enum FeTypeKind {
FE_TYPE_ERROR, FE_TYPE_ERROR_UNION, FE_TYPE_OPTIONAL,
FE_TYPE_VOID, FE_TYPE_BOOL, FE_TYPE_CHAR, FE_TYPE_INT,
FE_TYPE_STRUCT, FE_TYPE_ENUM, FE_TYPE_ARRAY, FE_TYPE_SLICE, FE_TYPE_STR,
FE_TYPE_REF, FE_TYPE_OWNED, FE_TYPE_UNKNOWN
FE_TYPE_REF, FE_TYPE_OWNED,
/* `*T`. A machine address and nothing else: no borrow to track, no drop
to run, Copy. Everything it is good for is behind `unsafe`. */
FE_TYPE_RAW,
FE_TYPE_UNKNOWN
} FeTypeKind;
/* One target, one pointer width (SPEC 2). usize and isize are that width and
@@ -124,6 +128,7 @@ FeType *fe_type_slice(FeTypeCtx *ctx, FeType *elem);
FeType *fe_type_mut_slice(FeTypeCtx *ctx, FeType *elem);
FeType *fe_type_ref(FeTypeCtx *ctx, FeType *elem, int mutable);
FeType *fe_type_owned(FeTypeCtx *ctx, FeType *elem);
FeType *fe_type_raw(FeTypeCtx *ctx, FeType *elem);
FeType *fe_type_error_union(FeTypeCtx *ctx, FeType *value);
void fe_type_require_replace(FeTypeCtx *ctx, FeType *type);
FeType *fe_type_declare_struct(FeTypeCtx *ctx, const FeNode *node, int packed);