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
|
|
@ -65,10 +65,10 @@ function atlas_init() -> void {
|
|||
at_spr_sy = words(ATLAS_MAX_SPR)
|
||||
at_spr_w = words(ATLAS_MAX_SPR)
|
||||
at_spr_h = words(ATLAS_MAX_SPR)
|
||||
at_name_str = bytes(ATLAS_MAX_NAME * 8) # a pointer (8 bytes) per name slot
|
||||
at_name_str = pointers(ATLAS_MAX_NAME) # a pointer (8 bytes) per name slot
|
||||
at_name_id = words(ATLAS_MAX_NAME)
|
||||
at_q_name = bytes(ATLAS_MAX_QUEUE * 8)
|
||||
at_q_path = bytes(ATLAS_MAX_QUEUE * 8)
|
||||
at_q_name = pointers(ATLAS_MAX_QUEUE)
|
||||
at_q_path = pointers(ATLAS_MAX_QUEUE)
|
||||
}
|
||||
|
||||
# register (name -> atlas id) in the name table so Assets.get / Sprite.named find it.
|
||||
|
|
@ -376,7 +376,7 @@ function assets_load_one(name: pointer, path: pointer) -> void {
|
|||
return
|
||||
}
|
||||
if path_has_suffix(path, ".ttf") or path_has_suffix(path, ".ttc") {
|
||||
if at_font_name == null { at_font_name = bytes(ATLAS_MAX_FONT * 8); at_font_id = words(ATLAS_MAX_FONT) }
|
||||
if at_font_name == null { at_font_name = pointers(ATLAS_MAX_FONT); at_font_id = words(ATLAS_MAX_FONT) }
|
||||
if at_font_n < ATLAS_MAX_FONT {
|
||||
at_font_name[at_font_n] = name
|
||||
at_font_id[at_font_n] = rt_font_load(path)
|
||||
|
|
|
|||
|
|
@ -31,9 +31,9 @@ var snd_bank_n: int = 0
|
|||
|
||||
function audio_init() -> void {
|
||||
if snd_ready { return }
|
||||
snd_tab = bytes(AUDIO_CAP * 8) # one 8-byte pointer slot per handle
|
||||
snd_tab = pointers(AUDIO_CAP) # one 8-byte pointer slot per handle
|
||||
fill(snd_tab, 0, AUDIO_CAP * 8) # malloc does not zero; empty slots must read null
|
||||
snd_bank_name = bytes(AUDIO_CAP * 8)
|
||||
snd_bank_name = pointers(AUDIO_CAP)
|
||||
snd_bank_id = words(AUDIO_CAP)
|
||||
snd_ready = true
|
||||
}
|
||||
|
|
|
|||
43
runtime/native/bytes.ludic
Normal file
43
runtime/native/bytes.ludic
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
# bytes.ludic — L7: bytes a program can hold without raw memory. A `[]byte` from buffer(n) is
|
||||
# bounds-checked like any slice; these turn one into text and move one to and from a file. The
|
||||
# runtime is the platform, so the raw calls underneath (file_read, the NUL-terminated copy) are
|
||||
# its business, not the caller's.
|
||||
|
||||
# the first n bytes of b as text (it stops at b's end, and the text ends at a zero byte if one comes first)
|
||||
function text_of(b: []byte, n: int) -> string {
|
||||
var k = n
|
||||
if b == null { k = 0 }
|
||||
if k > len(b) { k = len(b) }
|
||||
if k < 0 { k = 0 }
|
||||
let out = bytes(k + 1)
|
||||
var i = 0
|
||||
while i < k {
|
||||
out[i] = b[i]
|
||||
i += 1
|
||||
}
|
||||
out[k] = 0
|
||||
return out
|
||||
}
|
||||
# a whole file as bytes - through the asset pack when the file is in one - or null
|
||||
function fs_read_bytes(path: string) -> []byte {
|
||||
let f = file_open(path, "rb")
|
||||
if f == null { return null }
|
||||
file_seek(f, 0, 2)
|
||||
let n = file_tell(f)
|
||||
file_seek(f, 0, 0)
|
||||
let b = buffer(n)
|
||||
let got = file_read(f, b, n)
|
||||
file_close(f)
|
||||
if got < n { return view(b, 0, got) }
|
||||
return b
|
||||
}
|
||||
# the first `count` bytes of b to a file, replacing it; false when it cannot be written whole
|
||||
function fs_write_bytes(path: string, b: []byte, count: int) -> bool {
|
||||
var k = count
|
||||
if k > len(b) { k = len(b) }
|
||||
let f = file_open(path, "wb")
|
||||
if f == null { return false }
|
||||
let put = file_write(f, b, k)
|
||||
file_close(f)
|
||||
return put == k
|
||||
}
|
||||
|
|
@ -522,7 +522,7 @@ function rt_running() -> bool {
|
|||
if is_windowed() {
|
||||
return win_running()
|
||||
}
|
||||
return rt_alive
|
||||
return rt_alive != 0
|
||||
}
|
||||
|
||||
# ---- writing the frame out ------------------------------------------------
|
||||
|
|
@ -792,7 +792,7 @@ function rt_status_text() -> pointer {
|
|||
# only it knows their shape. Everything below belongs to the runtime, so the
|
||||
# runtime writes it — same order both ways.
|
||||
function rt_save_state(f: pointer) -> void {
|
||||
let w: words = bytes(16)
|
||||
let w: words = words(4)
|
||||
w[0] = rt_rng
|
||||
w[1] = rt_mapw
|
||||
w[2] = rt_maph
|
||||
|
|
@ -805,7 +805,7 @@ function rt_save_state(f: pointer) -> void {
|
|||
}
|
||||
|
||||
function rt_load_state(f: pointer) -> void {
|
||||
let w: words = bytes(16)
|
||||
let w: words = words(4)
|
||||
file_read(f, w, 16)
|
||||
rt_rng = w[0]
|
||||
rt_mapw = w[1]
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ property Dict { keys: pointers, vals: words, used: words, cap: int = 0, count: i
|
|||
# (re)allocate the three parallel arrays to `cap` slots, all cleared
|
||||
function dict_init(d: Dict, cap: int) -> void {
|
||||
d.cap = cap; d.count = 0
|
||||
d.keys = bytes(cap * 8); fill(d.keys, 0, cap * 8) # null slots
|
||||
d.keys = pointers(cap); fill(d.keys, 0, cap * 8) # null slots
|
||||
d.vals = words(cap); fill(d.vals, 0, cap * 4)
|
||||
d.used = words(cap); fill(d.used, 0, cap * 4)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -203,7 +203,7 @@ var gl_log_buf: string = null
|
|||
# Compile one shader stage from source; 0 (and the info log on stdout) on failure.
|
||||
function gl_shader(kind: int, src: pointer) -> int {
|
||||
let id = gl_create_shader(kind)
|
||||
var srcs: pointers = bytes(8)
|
||||
var srcs: pointers = pointers(1)
|
||||
srcs[0] = src
|
||||
gl_shader_source(id, 1, srcs, null)
|
||||
gl_compile_shader(id)
|
||||
|
|
@ -286,3 +286,8 @@ function gl_framebuffer() -> int {
|
|||
gl_gen_framebuffers(1, ids)
|
||||
return ids[0]
|
||||
}
|
||||
|
||||
# Time.now_us / Time.sleep_us (runtime/native/namespaces.ludic): the platform's clock, as an API a
|
||||
# program reaches without calling C (L7)
|
||||
function time_now_us() -> long { return gl_now_us() }
|
||||
function time_sleep_us(us: long) -> void { gl_sleep_us(us) }
|
||||
|
|
|
|||
|
|
@ -48,9 +48,9 @@ function http_init() -> void {
|
|||
h_res = words(HTTP_SLOTS)
|
||||
h_status = words(HTTP_SLOTS)
|
||||
h_blen = words(HTTP_SLOTS)
|
||||
h_req = bytes(HTTP_SLOTS * 8)
|
||||
h_body = bytes(HTTP_SLOTS * 8)
|
||||
h_hdr = bytes(HTTP_SLOTS * 8)
|
||||
h_req = pointers(HTTP_SLOTS)
|
||||
h_body = pointers(HTTP_SLOTS)
|
||||
h_hdr = pointers(HTTP_SLOTS)
|
||||
h_save = words(HTTP_SLOTS)
|
||||
var i = 0
|
||||
while i < HTTP_SLOTS {
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ const IMG_MAX: int = 32
|
|||
const SPR_MAX: int = 160
|
||||
const SPR_SZ: int = 16
|
||||
|
||||
var img_px: pointers = null # IMG_MAX pointers to RGBA buffers
|
||||
var img_px: [][]int = null # IMG_MAX RGBA buffers
|
||||
var img_w: words = null
|
||||
var img_h: words = null
|
||||
var img_n: int = 0
|
||||
|
|
@ -22,7 +22,12 @@ var spr_px: words = null # SPR_MAX * 16 * 16 RGBA pixels, one block
|
|||
var spr_n: int = 0
|
||||
|
||||
function rt_image_init() -> void {
|
||||
img_px = bytes(IMG_MAX * 8)
|
||||
img_px = new [][]int
|
||||
var pi0 = 0
|
||||
while pi0 < IMG_MAX {
|
||||
push(img_px, null)
|
||||
pi0 += 1
|
||||
}
|
||||
img_w = words(IMG_MAX)
|
||||
img_h = words(IMG_MAX)
|
||||
spr_px = words(SPR_MAX * SPR_SZ * SPR_SZ)
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ const Z_FAST: int = 10
|
|||
const Z_FASTSZ: int = 1024 # 1 << Z_FAST
|
||||
const Z_SYMS: int = 1040 # 16 + Z_FASTSZ: where the symbols start
|
||||
|
||||
function z_table_new(nsym: int) -> pointer {
|
||||
function z_table_new(nsym: int) -> words {
|
||||
return words((Z_SYMS + nsym))
|
||||
}
|
||||
|
||||
|
|
@ -238,7 +238,7 @@ function z_stored(out: pointer, at: int, cap: int) -> int {
|
|||
return w
|
||||
}
|
||||
|
||||
function z_codes(out: pointer, at: int, cap: int, lit: pointer, dist: pointer) -> int {
|
||||
function z_codes(out: pointer, at: int, cap: int, lit: words, dist: words) -> int {
|
||||
var w = at
|
||||
var sym = z_decode(lit)
|
||||
while sym != 256 {
|
||||
|
|
@ -273,7 +273,7 @@ function z_codes(out: pointer, at: int, cap: int, lit: pointer, dist: pointer) -
|
|||
return w
|
||||
}
|
||||
|
||||
function z_fixed_tables(lit: pointer, dist: pointer) -> void {
|
||||
function z_fixed_tables(lit: words, dist: words) -> void {
|
||||
let lengths: words = words(288)
|
||||
for i in 0 .. 144 { lengths[i] = 8 }
|
||||
for i in 144 .. 256 { lengths[i] = 9 }
|
||||
|
|
@ -285,7 +285,7 @@ function z_fixed_tables(lit: pointer, dist: pointer) -> void {
|
|||
free(lengths)
|
||||
}
|
||||
|
||||
function z_dynamic_tables(lit: pointer, dist: pointer) -> int {
|
||||
function z_dynamic_tables(lit: words, dist: words) -> int {
|
||||
let nlen = z_bits(5) + 257
|
||||
let ndist = z_bits(5) + 1
|
||||
let ncode = z_bits(4) + 4
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ var input_pos: int = 0 # replay / record cursor
|
|||
|
||||
function input_init() -> void {
|
||||
if input_names == null {
|
||||
input_names = bytes(INPUT_MAX_ACT * 8) # a pointer (8 bytes) per action slot
|
||||
input_names = pointers(INPUT_MAX_ACT) # a pointer (8 bytes) per action slot
|
||||
input_keys = words(INPUT_MAX_ACT * INPUT_MAX_KEYS)
|
||||
input_pads = words(INPUT_MAX_ACT * INPUT_MAX_KEYS) # #83 pad buttons per action (+1 encoded)
|
||||
}
|
||||
|
|
@ -236,7 +236,7 @@ var in_wheel: int = 0 # wheel delta this frame
|
|||
var in_pad_conn: words = null # IN_PADS
|
||||
var in_pad_btn: words = null # IN_PADS
|
||||
var in_pad_btn0: words = null # IN_PADS — pad button mask last frame (edges) — #83
|
||||
var in_pad_axis: words = null # IN_PADS * IN_AXES (fixed)
|
||||
var in_pad_axis: fixeds = null # IN_PADS * IN_AXES
|
||||
# touch points: active flag, x, y each
|
||||
var in_touch_on: words = null # IN_TOUCH
|
||||
var in_touch_x: words = null # IN_TOUCH
|
||||
|
|
@ -253,7 +253,7 @@ function in_init() -> void {
|
|||
in_pad_conn = words(IN_PADS)
|
||||
in_pad_btn = words(IN_PADS)
|
||||
in_pad_btn0 = words(IN_PADS)
|
||||
in_pad_axis = words(IN_PADS * IN_AXES)
|
||||
in_pad_axis = fixeds(IN_PADS * IN_AXES)
|
||||
in_touch_on = words(IN_TOUCH)
|
||||
in_touch_x = words(IN_TOUCH)
|
||||
in_touch_y = words(IN_TOUCH)
|
||||
|
|
|
|||
|
|
@ -415,14 +415,14 @@ var ch_lock: pointers = null # a mutex per channel
|
|||
function sy_init() -> void {
|
||||
if sy_ready { return }
|
||||
mx_used = words(SYNC_MUTEX); fill(mx_used, 0, SYNC_MUTEX * 4)
|
||||
mx_obj = bytes(SYNC_MUTEX * 8); fill(mx_obj, 0, SYNC_MUTEX * 8)
|
||||
mx_obj = pointers(SYNC_MUTEX); fill(mx_obj, 0, SYNC_MUTEX * 8)
|
||||
at_used = words(SYNC_ATOMIC); fill(at_used, 0, SYNC_ATOMIC * 4)
|
||||
at_cell = bytes(SYNC_ATOMIC * 8); fill(at_cell, 0, SYNC_ATOMIC * 8)
|
||||
at_cell = pointers(SYNC_ATOMIC); fill(at_cell, 0, SYNC_ATOMIC * 8)
|
||||
ch_used = words(SYNC_CHAN); fill(ch_used, 0, SYNC_CHAN * 4)
|
||||
ch_head = words(SYNC_CHAN); fill(ch_head, 0, SYNC_CHAN * 4)
|
||||
ch_count = words(SYNC_CHAN); fill(ch_count, 0, SYNC_CHAN * 4)
|
||||
ch_buf = words(SYNC_CHAN * CHAN_CAP); fill(ch_buf, 0, SYNC_CHAN * CHAN_CAP * 4)
|
||||
ch_lock = bytes(SYNC_CHAN * 8); fill(ch_lock, 0, SYNC_CHAN * 8)
|
||||
ch_lock = pointers(SYNC_CHAN); fill(ch_lock, 0, SYNC_CHAN * 8)
|
||||
sy_ready = true
|
||||
}
|
||||
|
||||
|
|
@ -467,7 +467,7 @@ function sync_atomic() -> int {
|
|||
while i < SYNC_ATOMIC {
|
||||
if at_used[i] == 0 {
|
||||
at_used[i] = 1
|
||||
if at_cell[i] == null { at_cell[i] = words(1) }
|
||||
if at_cell[i] == null { at_cell[i] = data_of(words(1)) } # the atomics take the cell's address, not its slice
|
||||
thr_store(at_cell[i], 0)
|
||||
return i + 1
|
||||
}
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ function light_isqrt(n: int) -> int {
|
|||
# sqrt(v)*2^8. Used by the normal-map N·L (unit vectors), where inputs are <= 1.
|
||||
function light_fsqrt(v: fixed) -> fixed {
|
||||
if v <= 0 { return fixed(0) }
|
||||
return light_isqrt(v) << 8
|
||||
return light_isqrt(as_int(v)) << 8
|
||||
}
|
||||
|
||||
# ---- tier controls (globals) ----------------------------------------------
|
||||
|
|
|
|||
|
|
@ -542,3 +542,11 @@ namespace Tiled {
|
|||
alias world_count(world) = tiled_world_count
|
||||
alias world_map(world, index) = tiled_world_map
|
||||
}
|
||||
namespace Time {
|
||||
alias now_us() = time_now_us # a microsecond clock, for measuring - not the game's time
|
||||
alias sleep_us(us) = time_sleep_us
|
||||
}
|
||||
namespace Fs {
|
||||
alias read_bytes(path) = fs_read_bytes # the whole file as a []byte, or null
|
||||
alias write_bytes(path, data, count) = fs_write_bytes
|
||||
}
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@ function proc_cmdline(path: pointer, args: []pointer) -> pointer {
|
|||
function process_spawn(path: pointer, args: []pointer) -> int {
|
||||
if (path == null) or (path[0] == 0) { return -1 }
|
||||
let n = len(args)
|
||||
var argv: pointers = bytes((n + 2) * 8)
|
||||
var argv: pointers = pointers(n + 2)
|
||||
argv[0] = path
|
||||
var i = 0
|
||||
while i < n {
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ var anim_nclips: int = 0
|
|||
|
||||
function anim_clip_init() -> void {
|
||||
if anim_clip_names == null {
|
||||
anim_clip_names = bytes(ANIM_MAX_CLIPS * 8) # a pointer (8 bytes) per slot
|
||||
anim_clip_names = pointers(ANIM_MAX_CLIPS) # a pointer (8 bytes) per slot
|
||||
anim_clip_fps = words(ANIM_MAX_CLIPS)
|
||||
anim_clip_frames = words(ANIM_MAX_CLIPS)
|
||||
anim_clip_mode = words(ANIM_MAX_CLIPS)
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ var gc_adv: words = null
|
|||
var gc_bmp: pointers = null
|
||||
|
||||
function rt_tt_init() -> void {
|
||||
tt_data = bytes(TT_MAX * 8)
|
||||
tt_data = pointers(TT_MAX)
|
||||
tt_size = words(TT_MAX)
|
||||
tt_upem = words(TT_MAX)
|
||||
tt_nglyf = words(TT_MAX)
|
||||
|
|
@ -79,16 +79,16 @@ function rt_tt_init() -> void {
|
|||
tt_cmap = words(TT_MAX)
|
||||
tt_cfmt = words(TT_MAX)
|
||||
|
||||
ol_x = words(TT_PTS)
|
||||
ol_y = words(TT_PTS)
|
||||
ol_x = fixeds(TT_PTS)
|
||||
ol_y = fixeds(TT_PTS)
|
||||
ol_on = words(TT_PTS)
|
||||
ol_ends = words(256)
|
||||
|
||||
ed_x0 = words(TT_EDGES)
|
||||
ed_y0 = words(TT_EDGES)
|
||||
ed_x1 = words(TT_EDGES)
|
||||
ed_y1 = words(TT_EDGES)
|
||||
sc_x = words(TT_EDGES)
|
||||
ed_x0 = fixeds(TT_EDGES)
|
||||
ed_y0 = fixeds(TT_EDGES)
|
||||
ed_x1 = fixeds(TT_EDGES)
|
||||
ed_y1 = fixeds(TT_EDGES)
|
||||
sc_x = fixeds(TT_EDGES)
|
||||
sc_d = words(TT_EDGES)
|
||||
|
||||
gc_used = words(TT_GC)
|
||||
|
|
@ -100,7 +100,7 @@ function rt_tt_init() -> void {
|
|||
gc_ox = words(TT_GC)
|
||||
gc_oy = words(TT_GC)
|
||||
gc_adv = words(TT_GC)
|
||||
gc_bmp = bytes(TT_GC * 8)
|
||||
gc_bmp = pointers(TT_GC)
|
||||
fill(gc_used, 0, TT_GC * 4)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -55,13 +55,15 @@ function udp_open(port: int) -> int {
|
|||
# the port a socket is bound to (the one the system picked, for port 0)
|
||||
function udp_port(h: int) -> int { return lu_udp_port(h) }
|
||||
# one datagram of `n` bytes from `buf` to ip:port: the bytes sent, or -1
|
||||
function udp_send(h: int, ip: int, port: int, buf: pointer, n: int) -> int {
|
||||
if (buf == null) or (n <= 0) { return -1 }
|
||||
function udp_send(h: int, ip: int, port: int, buf: []byte, n: int) -> int {
|
||||
if (buf == null) or (n <= 0) or (n > len(buf)) { return -1 }
|
||||
return lu_udp_send(h, ip, port, buf, n)
|
||||
}
|
||||
# the next waiting datagram, up to `cap` bytes, into `buf`: its length, or 0 when none waits
|
||||
function udp_recv(h: int, buf: pointer, cap: int) -> int {
|
||||
function udp_recv(h: int, buf: []byte, cap0: int) -> int {
|
||||
udp_init()
|
||||
var cap = cap0
|
||||
if buf != null and cap > len(buf) { cap = len(buf) }
|
||||
if (buf == null) or (cap <= 0) { return 0 }
|
||||
let n = lu_udp_recv(h, buf, cap, u_ipport)
|
||||
if (n > 0) and (h >= 1) and (h <= UDP_HANDLES) {
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ function rt_ui_init() -> void {
|
|||
ui_fired = words(UI_MAX)
|
||||
ui_hasdyn = words(UI_MAX)
|
||||
ui_dyn = bytes(UI_MAX * 96)
|
||||
ui_text = bytes(UI_MAX * 8)
|
||||
ui_text = pointers(UI_MAX)
|
||||
}
|
||||
|
||||
# ---- build-time interface (called by compiler-emitted code) ---------------
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue