feat(lang): L7 memory is safe unless it says unsafe
The typed buffers are slices: words/floats/fixeds/doubles/pointers(n) make
zeroed, bounds-checked []int/[]float/... and the type names mean them. buffer(n)
is a []byte, with text_of, Fs.read_bytes/write_bytes and view(xs, start, n).
bytes(), indexing a raw pointer or bytes, free, resize, Memory.*, raw file calls,
data_of and C externs are refused outside unsafe { } / unsafe function, and a
project's own files may write unsafe only with --unsafe; the runtime and packages
are the platform. A slice passed to an extern goes as its data.
What the change found: Sync's atomics on a slice header, words(n) uninitialised,
input's fixed axes in ints, truetype's fixed outlines as ints, skin matrices
typed int, gl_shader's source table made from raw bytes. render3d gets safe
entry points (safe_api.ludic). Rendering is byte-identical; a frame costs the same.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
9259808f80
commit
b0b0b62bce
70 changed files with 69189 additions and 64569 deletions
|
|
@ -37,6 +37,7 @@ const E_TRY: int = 52 # try EXPR else { ... } — recover a fallible
|
|||
# a=the fallible (result-typed) expression b=else block (its
|
||||
# trailing expression is the fallback) line=source line
|
||||
const E_LIST: int = 54 # [a, b, c] — a slice literal; kids=the elements, all of one type
|
||||
const S_UNSAFE: int = 56 # unsafe { ... } — raw memory allowed inside (L7); a = the block
|
||||
const E_FNREF: int = 55 # fn name — a top-level function as a value (s=the name); a worker entry point
|
||||
# statements
|
||||
const S_LET: int = 10
|
||||
|
|
@ -87,6 +88,7 @@ property Node {
|
|||
file: pointer = null # the source file the node was parsed from (for diagnostics)
|
||||
vis: int = 0 # L3: 1 when the declaration is `export`ed from its module
|
||||
tps: pointer = null # L5: a generic declaration's type parameters, "T|U"; null when not generic
|
||||
uns: int = 0 # L7: 1 on an `unsafe function`
|
||||
}
|
||||
|
||||
# every node remembers where it was parsed (file + the line of the token the
|
||||
|
|
|
|||
|
|
@ -17,6 +17,21 @@ var g_float_files: []pointer = new []pointer
|
|||
# L3 modules: `module NAME` at the top of a file names the module it and everything it imports
|
||||
# belong to, until an import names its own; `friend module NAME` may see every module's private
|
||||
# names (a test harness). A file in no module - the runtime, a program's root - is public.
|
||||
# L7: the files that may write `unsafe` - the runtime, a package from the toolchain or
|
||||
# ludic_modules, and what those import from beside them. A project's own files may only
|
||||
# with --unsafe (g_unsafe_all).
|
||||
var g_uses_bytes: bool = false # text_of / Fs.read_bytes / Fs.write_bytes: splice bytes.ludic
|
||||
var g_trusted_files: []pointer = new []pointer
|
||||
var g_unsafe_all: bool = false
|
||||
function unsafe_trusted(f: pointer) -> bool {
|
||||
if g_unsafe_all { return true }
|
||||
var i = 0
|
||||
while i < len(g_trusted_files) {
|
||||
if (g_trusted_files[i] == f) { return true }
|
||||
i += 1
|
||||
}
|
||||
return false
|
||||
}
|
||||
var g_mod_file: []pointer = new []pointer
|
||||
var g_mod_name: []pointer = new []pointer
|
||||
var g_mod_friends: []pointer = new []pointer
|
||||
|
|
@ -92,6 +107,16 @@ function eat_id() -> pointer {
|
|||
function skipnl() -> void { while toks[pi].kind == TK_NL { pi += 1 } }
|
||||
|
||||
# a type: `[]T` slice, `fn(T, U) -> R` function, or a plain name (int/ptr/str/bool/struct)
|
||||
# L7: the typed buffers are slices - a length, a bounds check on every index, and a place in the
|
||||
# checker - so `floats` is `[]float`, `words` is `[]int`, and so on. `bytes()` stays raw.
|
||||
function buffer_slice_ty(t: pointer) -> pointer {
|
||||
if (t == "floats") { return "[]float" }
|
||||
if (t == "words") { return "[]int" }
|
||||
if (t == "fixeds") { return "[]fixed" }
|
||||
if (t == "doubles") { return "[]double" }
|
||||
if (t == "pointers") { return "[]pointer" }
|
||||
return t
|
||||
}
|
||||
function ptype() -> pointer {
|
||||
if (toks[pi].text == "fn") and (toks[pi + 1].text == "(") {
|
||||
pi += 1
|
||||
|
|
@ -125,7 +150,7 @@ function ptype() -> pointer {
|
|||
}
|
||||
let tn = eat_id()
|
||||
if is_op("<") { return gen_type_args(tn) }
|
||||
return tn
|
||||
return buffer_slice_ty(tn)
|
||||
}
|
||||
|
||||
# ---- expressions -----------------------------------------------------------
|
||||
|
|
@ -267,6 +292,7 @@ function p_primary() -> Node {
|
|||
n.b = block()
|
||||
return n
|
||||
}
|
||||
if (t.text == "text_of") { g_uses_bytes = true }
|
||||
let n = node(E_ID); n.s = t.text; pi += 1; return n
|
||||
}
|
||||
if is_op("(") { pi += 1; skipnl(); let e = expr(); skipnl(); eat_op(")"); return e }
|
||||
|
|
@ -279,6 +305,7 @@ function p_postfix() -> Node {
|
|||
while true {
|
||||
if is_op(".") { pi += 1; let m = node(E_MEMBER); m.a = e; m.s = eat_id(); e = m
|
||||
if e.a.kind == E_ID and e.a.s == "Regex" { g_uses_regex = true } # splice the regex runtime on demand
|
||||
if e.a.kind == E_ID and e.a.s == "Fs" and (e.s == "read_bytes" or e.s == "write_bytes") { g_uses_bytes = true }
|
||||
if e.a.kind == E_ID and (e.a.s == "BigInt" or e.a.s == "Decimal") { g_uses_bignum = true } # splice the bignum runtime on demand
|
||||
if e.a.kind == E_ID and (e.a.s == "Dict" or e.a.s == "Set") { g_uses_dict = true } # splice the hash-table runtime on demand
|
||||
if e.a.kind == E_ID and (e.a.s == "Huge" or e.a.s == "Angle" or e.a.s == "Percent") { g_uses_numeric = true } # splice the huge/angle/percent runtime on demand
|
||||
|
|
@ -488,6 +515,12 @@ function stmt_body() -> Node {
|
|||
pi += 1; n.a = expr()
|
||||
return n
|
||||
}
|
||||
if (t.text == "unsafe") and (toks[pi + 1].text == "{") { # L7: raw memory allowed inside
|
||||
let un = node(S_UNSAFE)
|
||||
pi += 1
|
||||
un.a = block()
|
||||
return un
|
||||
}
|
||||
if (t.text == "break") { pi += 1; return node(S_BREAK) }
|
||||
if (t.text == "continue") { pi += 1; return node(S_CONTINUE) }
|
||||
if (t.text == "cancel") { pi += 1; return node(S_CANCEL) } # veto a cancellable event
|
||||
|
|
@ -980,8 +1013,14 @@ function parse_one_decl() -> void {
|
|||
if is_id("namespace") { parse_namespace(); return } # #76 namespace block
|
||||
if is_id("var") { push(prog, parse_var()); return }
|
||||
if is_id("const") { push(prog, parse_const()); return }
|
||||
var is_unsafe = false
|
||||
if is_id("unsafe") and (toks[pi + 1].text == "function") { # L7: an unsafe function
|
||||
pi += 1
|
||||
is_unsafe = true
|
||||
}
|
||||
if is_id("function") {
|
||||
let f = parse_fn()
|
||||
if is_unsafe { f.uns = 1 }
|
||||
if is_det { push(g_det_names, f.s) }
|
||||
if is_export { f.ival = 1 }
|
||||
if (sys_phase != null) { push(g_mod_sys_fn, f.s); push(g_mod_sys_phase, sys_phase) } # #64: register at load
|
||||
|
|
@ -1111,6 +1150,7 @@ function do_import(rel: pointer) -> void {
|
|||
# a module reaches as far as its own files: a package found through $LUDIC_HOME or
|
||||
# ludic_modules is not beside its importer and keeps its own module (or none)
|
||||
let beside = full == join_path(cur_dir, rel)
|
||||
if is_runtime_path(rel) or not beside or (beside and unsafe_trusted(g_parse_file)) { push(g_trusted_files, full) }
|
||||
if beside and not is_runtime_path(rel) and not (module_of(g_parse_file) == "") { module_set(full, module_of(g_parse_file)) }
|
||||
if (src == null) { perr(`cannot open import {full}`) }
|
||||
# the audio runtime can arrive through atlas.ludic's own import or the Assets
|
||||
|
|
@ -1138,6 +1178,9 @@ function maybe_splice_runtime() -> void {
|
|||
# L6: the engine's namespaces that are aliases of runtime functions, declared in Ludic
|
||||
cur_dir = ""
|
||||
do_import("runtime/native/namespaces.ludic")
|
||||
# L7: the byte buffer's API, when a program reads or writes bytes (it opens files through
|
||||
# the asset pack, and a program that does not should not carry the pack's machinery)
|
||||
if g_uses_bytes { do_import("runtime/native/bytes.ludic") }
|
||||
cur_dir = saved
|
||||
# a game (has systems/components) links the Ludic runtime.
|
||||
if has_ecs() {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue