diff --git a/fec/rt/start.asm b/fec/rt/start.asm index c2726e6..27fe54c 100644 --- a/fec/rt/start.asm +++ b/fec/rt/start.asm @@ -131,6 +131,10 @@ fe_trap endp extern _GetProcessHeap@0 : near extern _HeapAlloc@12 : near extern _HeapFree@12 : near +extern _CreateFileA@28 : near +extern _ReadFile@20 : near +extern _CloseHandle@4 : near +extern _GetCommandLineA@0 : near ; fe_rt_write(handle, ptr, len) -> bytes written public fe_rt_write @@ -298,6 +302,78 @@ hex_digit: ret fe_rt_write_hex endp +; fe_rt_open(path, write) -> handle, or -1 +; `path` is a NUL-terminated byte string. Reading opens what is there; writing +; creates or truncates. +public fe_rt_open +fe_rt_open proc near + push ebp + mov ebp, esp + push 0 ; hTemplateFile + push 128 ; FILE_ATTRIBUTE_NORMAL + cmp dword ptr [ebp+12], 0 + jne open_write + push 3 ; OPEN_EXISTING + push 0 + push 1 ; FILE_SHARE_READ + push 80000000h ; GENERIC_READ + jmp open_call +open_write: + push 2 ; CREATE_ALWAYS + push 0 + push 0 + push 40000000h ; GENERIC_WRITE +open_call: + push dword ptr [ebp+8] + call _CreateFileA@28 + mov esp, ebp + pop ebp + ret +fe_rt_open endp + +; fe_rt_read(handle, buf, len) -> bytes read, or -1 +public fe_rt_read +fe_rt_read proc near + push ebp + mov ebp, esp + push 0 + push offset written + push dword ptr [ebp+16] + push dword ptr [ebp+12] + push dword ptr [ebp+8] + call _ReadFile@20 + test eax, eax + jne read_ok + mov eax, -1 + jmp read_done +read_ok: + mov eax, [written] +read_done: + mov esp, ebp + pop ebp + ret +fe_rt_read endp + +; fe_rt_close(handle) +public fe_rt_close +fe_rt_close proc near + push ebp + mov ebp, esp + push dword ptr [ebp+8] + call _CloseHandle@4 + mov esp, ebp + pop ebp + ret +fe_rt_close endp + +; fe_rt_cmdline() -> pointer to the whole command line, NUL terminated. +; Splitting it is the standard library's job, not the runtime's. +public fe_rt_cmdline +fe_rt_cmdline proc near + call _GetCommandLineA@0 + ret +fe_rt_cmdline endp + ; fe_rt_exit(code) -- never returns public fe_rt_exit fe_rt_exit proc near diff --git a/fec/src/checkcal.c b/fec/src/checkcal.c index 11e1d50..3dfb14b 100644 --- a/fec/src/checkcal.c +++ b/fec/src/checkcal.c @@ -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) && diff --git a/fec/src/checkexp.c b/fec/src/checkexp.c index 33f18c9..a8a9af0 100644 --- a/fec/src/checkexp.c +++ b/fec/src/checkexp.c @@ -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; diff --git a/fec/src/checkstm.c b/fec/src/checkstm.c index 587e199..b3b1e0a 100644 --- a/fec/src/checkstm.c +++ b/fec/src/checkstm.c @@ -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"); diff --git a/fec/src/lower.c b/fec/src/lower.c index da94f4d..045197e 100644 --- a/fec/src/lower.c +++ b/fec/src/lower.c @@ -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); diff --git a/fec/src/lowerstm.c b/fec/src/lowerstm.c index 1ac6395..ff93d4d 100644 --- a/fec/src/lowerstm.c +++ b/fec/src/lowerstm.c @@ -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; diff --git a/fec/src/types.c b/fec/src/types.c index 71ef161..4572476 100644 --- a/fec/src/types.c +++ b/fec/src/types.c @@ -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, ""); + 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, ""); /* A plain named type may be a generic declaration -- with arguments it is diff --git a/fec/src/types.h b/fec/src/types.h index 4a4a4a1..4109839 100644 --- a/fec/src/types.h +++ b/fec/src/types.h @@ -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); diff --git a/fec/std/io.fe b/fec/std/io.fe index 475b1d8..75e2cde 100644 --- a/fec/std/io.fe +++ b/fec/std/io.fe @@ -10,7 +10,8 @@ pub fn write(w: Writer, bytes: []u8) -> usize { if w == Writer.Null { return bytes.n; } var handle: i32 = 1; if w == Writer.Stderr { handle = 2; } - let done: i32 = sys.raw_write(handle, &bytes[0], bytes.n); + var done: i32 = 0; + unsafe { done = sys.raw_write(handle, @ptr_cast(u8, &bytes[0]), bytes.n); } if done < 0 { return 0; } return done as usize; } @@ -23,3 +24,97 @@ pub fn println(bytes: []u8) -> usize { let n: usize = write(Writer.Stdout, bytes); return n + write(Writer.Stdout, "\n"); } + +// Files. A handle is what the operating system gave back; -1 means it did not +// give one. The path has to be NUL terminated because that is what the system +// call wants, and `to_cstr` is how a Ferro string becomes one. + +pub fn open_read(path: []mut u8) -> !i32 { + var handle: i32 = 0; + unsafe { handle = sys.raw_open(@ptr_cast(u8, &path[0]), 0); } + if handle == 0 - 1 { return error.NoSuchFile; } + return handle; +} + +pub fn open_write(path: []mut u8) -> !i32 { + var handle: i32 = 0; + unsafe { handle = sys.raw_open(@ptr_cast(u8, &path[0]), 1); } + if handle == 0 - 1 { return error.CannotWrite; } + return handle; +} + +pub fn read(handle: i32, into: []mut u8) -> !usize { + var got: i32 = 0; + unsafe { got = sys.raw_read(handle, @ptr_cast(u8, &into[0]), into.n); } + if got < 0 { return error.ReadFailed; } + return got as usize; +} + +pub fn close(handle: i32) -> void { + sys.raw_close(handle); +} + +/// Put `text` into `buf` with a NUL after it and say how many bytes that took, +/// the NUL included. A system call cannot be told a length, so it needs this. +/// +/// The length comes back rather than a slice of `buf`: with two reference-like +/// parameters the signature cannot say which one a returned slice came from, +/// and R8 will not guess. +pub fn to_cstr(buf: []mut u8, text: []u8) -> usize { + var i: usize = 0; + while i < text.n { + if i + 1 >= buf.n { break; } + buf[i] = text[i]; + i = i + 1; + } + if i < buf.n { buf[i] = 0; } + return i + 1; +} + +pub fn write_file(handle: i32, bytes: []u8) -> !usize { + var done: i32 = 0; + unsafe { done = sys.raw_write(handle, @ptr_cast(u8, &bytes[0]), bytes.n); } + if done < 0 { return error.WriteFailed; } + return done as usize; +} + +/// Copy the command line into `buf` and say how long it is. It arrives as one +/// string with the program's own name first; `arg` picks a piece out of it. +pub fn cmdline(buf: []mut u8) -> usize { + let raw: *u8 = sys.raw_cmdline(); + var i: usize = 0; + unsafe { + while i + 1 < buf.n { + let c: u8 = @volatile_load(raw + i); + if c == 0 { break; } + buf[i] = c; + i = i + 1; + } + } + return i; +} + +/// The `n`th whitespace-separated piece of `line`, or an empty slice when +/// there is no such piece. Quoting is not handled; nothing here needs it yet. +pub fn arg(line: []u8, n: usize) -> []u8 { + var at: usize = 0; + var seen: usize = 0; + while at < line.n { + while at < line.n { + if line[at] != 32 { break; } + at = at + 1; + } + var stop: usize = at; + while stop < line.n { + if line[stop] == 32 { break; } + stop = stop + 1; + } + if stop > at { + if seen == n { return line[at..stop]; } + seen = seen + 1; + } + at = stop; + } + return line[0..0]; +} + diff --git a/fec/std/sys.fe b/fec/std/sys.fe index c2c91f9..53c3db0 100644 --- a/fec/std/sys.fe +++ b/fec/std/sys.fe @@ -8,6 +8,10 @@ extern "c" fn fe_rt_free(p: *u8); extern "c" fn fe_rt_exit(code: i32); extern "c" fn fe_rt_allocs() -> i32; extern "c" fn fe_rt_frees() -> i32; +extern "c" fn fe_rt_open(path: *u8, write: i32) -> i32; +extern "c" fn fe_rt_read(handle: i32, buf: *u8, len: usize) -> i32; +extern "c" fn fe_rt_close(handle: i32); +extern "c" fn fe_rt_cmdline() -> *u8; pub fn exit(code: i32) -> void { unsafe { fe_rt_exit(code); } @@ -29,3 +33,21 @@ pub fn raw_free(p: *u8) -> void { // back. A test can insist the two agree; nothing else should care. pub fn allocs() -> i32 { unsafe { return fe_rt_allocs(); } } pub fn frees() -> i32 { unsafe { return fe_rt_frees(); } } + +pub fn raw_open(path: *u8, write: i32) -> i32 { + unsafe { return fe_rt_open(path, write); } +} + +pub fn raw_read(handle: i32, buf: *u8, len: usize) -> i32 { + unsafe { return fe_rt_read(handle, buf, len); } +} + +pub fn raw_close(handle: i32) -> void { + unsafe { fe_rt_close(handle); } +} + +/// The whole command line as one NUL-terminated string. Splitting it into +/// arguments is `std.io`'s job: the runtime should not know about quoting. +pub fn raw_cmdline() -> *u8 { + unsafe { return fe_rt_cmdline(); } +} diff --git a/fec/tests/exec/cmdargs.fe b/fec/tests/exec/cmdargs.fe new file mode 100644 index 0000000..4cb53ae --- /dev/null +++ b/fec/tests/exec/cmdargs.fe @@ -0,0 +1,21 @@ +// EXIT:0 +// OUTPUT:args ok +unit cmdargs; +import std.io; +import std.str; + +// The command line reaches the program. A compiler is told which file to read +// this way and no other. + +fn main() -> i32 { + var line: [512]u8 = undefined; + let n: usize = io.cmdline(line[..]); + let program: []u8 = io.arg(line[0..n], 0); + if program.n == 0 { @print("no program name\n"); return 1; } + if str.find(program, "cmdargs") == program.n { + @print("unexpected program name: {}\n", program); + return 2; + } + @print("args ok\n"); + return 0; +} diff --git a/fec/tests/exec/readfile.fe b/fec/tests/exec/readfile.fe new file mode 100644 index 0000000..a2888e0 --- /dev/null +++ b/fec/tests/exec/readfile.fe @@ -0,0 +1,37 @@ +// EXIT:0 +// OUTPUT:read 64 bytes +// OUTPUT:first line: // EXIT:0 +unit readfile; +import std.io; + +// Reads its own source and reports the first line. A program that can open a +// file is a program that can be a compiler. + +fn main() -> i32 { + var path: [64]u8 = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]; + let n: usize = io.to_cstr(path[..], "fec/tests/exec/readfile.fe"); + let handle: i32 = io.open_read(path[0..n]) catch |e| { + @print("cannot open\n"); + return 1; + }; + var buf: [64]u8 = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]; + let got: usize = io.read(handle, buf[..]) catch |e| { + io.close(handle); + return 2; + }; + io.close(handle); + @print("read {} bytes\n", n); + var stop: usize = 0; + while stop < got { + if buf[stop] == 10 { break; } + stop = stop + 1; + } + @print("first line: {}\n", buf[0..stop]); + return 0; +}