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
30
LANGUAGE.md
30
LANGUAGE.md
|
|
@ -215,6 +215,36 @@ way in its own files. What is still built into the compiler is the namespaces th
|
|||
`Math`, `Text`, `List`, `Vector`, `Color`, `Time`, `Date` - and the few methods that choose their
|
||||
target by an argument's type (`Audio.play` of a handle or a name).
|
||||
|
||||
### Memory is safe unless it says `unsafe`
|
||||
|
||||
The typed buffers are slices: `words(n)`, `floats(n)`, `fixeds(n)`, `doubles(n)` and
|
||||
`pointers(n)` make `n` zeroed elements of a `[]int`, `[]float`, `[]fixed`, `[]double` or
|
||||
`[]pointer`, and the type names `words`, `floats` and the rest mean those slices. Every index is
|
||||
checked against the length, so running off the end stops the program at that line instead of
|
||||
writing into whatever lies next. `buffer(n)` is `n` zeroed bytes, a `[]byte`; `text_of(b, n)` makes
|
||||
text of the first `n`; `Fs.read_bytes(path)` and `Fs.write_bytes(path, b, n)` move them to and from
|
||||
a file; `view(xs, start, count)` is part of a slice sharing its storage, checked once when it is
|
||||
made (make one where the buffer is made - each is a small allocation).
|
||||
|
||||
What is left is raw memory, and is refused outside `unsafe { }` or an `unsafe function`:
|
||||
`bytes(n)`, indexing a `pointer` or `bytes`, `free`, `resize`, `Memory.*`, `file_read` and
|
||||
`file_write`, `data_of(xs)` (a slice's first element, for C), and calling an `extern` C function.
|
||||
A slice passed to an extern goes as its elements' address, never its header.
|
||||
|
||||
`unsafe` itself is for the platform: the runtime, a package from the toolchain or
|
||||
`ludic_modules`, and what those import from beside them, all of which are unsafe throughout. A
|
||||
project's own files may write it only when the build says `--unsafe` (`ludic build --unsafe`) -
|
||||
the compiler and its tools build that way; a game is written against APIs and does not.
|
||||
|
||||
```ludic
|
||||
# doc-check: skip — a fragment
|
||||
let px = buffer(w * h * 3) # a []byte: bounds-checked
|
||||
px[0] = 255
|
||||
Fs.write_bytes("shot.raw", px, len(px))
|
||||
let hp = floats(3) # a []float
|
||||
hp[2] = 1.5
|
||||
```
|
||||
|
||||
## Models (entity kinds)
|
||||
|
||||
An `model` names a *kind* of entity and the fixed set of properties it
|
||||
|
|
|
|||
14
changes/unsafe.md
Normal file
14
changes/unsafe.md
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
bump: minor
|
||||
type: feature
|
||||
**Memory is safe unless it says `unsafe` (L7).** `words(n)`, `floats(n)`, `fixeds(n)`, `doubles(n)`
|
||||
and `pointers(n)` are bounds-checked, zero-filled slices now, and the type names mean those slices;
|
||||
`buffer(n)` is a `[]byte`, with `text_of`, `Fs.read_bytes`, `Fs.write_bytes` and `view(xs, start,
|
||||
count)` beside it. `bytes(n)`, indexing a raw pointer, `free`, `resize`, `Memory.*`, the raw file
|
||||
calls, `data_of` and calling a C `extern` 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 and are unsafe throughout. A slice passed to an extern goes as its data.
|
||||
|
||||
Making the buffers slices found, and this release fixes: `Sync`'s atomics operating on a slice's
|
||||
header rather than its cell; `words(n)` handing back uninitialised memory; `input.ludic` keeping a
|
||||
stick's fixed axes in an int buffer; truetype's fixed outlines allocated as ints; skinning's float
|
||||
matrices typed as ints. Rendering is byte-identical, and a frame costs the same.
|
||||
|
|
@ -7,6 +7,7 @@
|
|||
# 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
||||
program TiledP0 {
|
||||
entry {
|
||||
unsafe { # raw buffers on purpose: built with --unsafe (L7)
|
||||
# --- base64 decode: RFC 4648 test vectors (every padding remainder) ---
|
||||
if Base64.decode("") == "" { print(1) }
|
||||
if Base64.decode("Zg==") == "f" { print(2) }
|
||||
|
|
@ -58,4 +59,5 @@ program TiledP0 {
|
|||
zout[zn] = 0
|
||||
if zout == "Hello, Tiled gzip world! 0123456789 the quick brown fox." { print(21) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,11 +6,11 @@ program UdpDemo {
|
|||
let pa = Udp.port(a)
|
||||
let pb = Udp.port(b)
|
||||
let lo = Udp.ip("127.0.0.1")
|
||||
let out = Memory.bytes(16)
|
||||
Memory.poke(out, 0, 104)
|
||||
Memory.poke(out, 1, 105)
|
||||
let out = buffer(16)
|
||||
out[0] = 104
|
||||
out[1] = 105
|
||||
let sent = Udp.send(a, lo, pb, out, 2)
|
||||
let inb = Memory.bytes(64)
|
||||
let inb = buffer(64)
|
||||
var got = 0
|
||||
var tries = 0
|
||||
while got == 0 and tries < 200000 { got = Udp.recv(b, inb, 64); tries += 1 }
|
||||
|
|
@ -20,7 +20,7 @@ program UdpDemo {
|
|||
tries = 0
|
||||
while back == 0 and tries < 200000 { back = Udp.recv(a, out, 16); tries += 1 }
|
||||
let bad = Udp.ip("1.2.3") == 0 and Udp.ip("300.1.1.1") == 0
|
||||
print(`{sent} {got} {Memory.peek(inb, 0)} {Memory.peek(inb, 1)} {from_ok} {back} {Udp.ip_text(lo)} {bad} {Udp.recv(a, out, 16)} {pa > 0}`)
|
||||
print(`{sent} {got} {inb[0]} {inb[1]} {from_ok} {back} {Udp.ip_text(lo)} {bad} {Udp.recv(a, out, 16)} {pa > 0}`)
|
||||
Udp.close(a)
|
||||
Udp.close(b)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
# them back through the loopback: prints 4, then 10 20 30 42.
|
||||
program NetEcho {
|
||||
entry {
|
||||
unsafe { # raw buffers on purpose: built with --unsafe (L7)
|
||||
let out = bytes(4)
|
||||
out[0] = '\n'
|
||||
out[1] = 20
|
||||
|
|
@ -21,4 +22,5 @@ program NetEcho {
|
|||
var i = 0
|
||||
while i < n { print(inb[i]); i += 1 } # 10 20 30 42
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ program NetSnapshot {
|
|||
model Unit { Health }
|
||||
|
||||
entry {
|
||||
unsafe { # raw buffers on purpose: built with --unsafe (L7)
|
||||
spawn Unit { Health { hp: 50, max: 100 } }
|
||||
let buf = bytes(world_size())
|
||||
for (Health) in query [Health, {Unit}] {
|
||||
|
|
@ -26,4 +27,5 @@ program NetSnapshot {
|
|||
print(Health.hp) # 50 — restored from bytes
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,15 +22,15 @@ program VkCompute {
|
|||
|
||||
var dev: pointer = null
|
||||
|
||||
function fail(what: string, r: int) -> void { print(`vulkan: {what} failed, VkResult {r}`); quit() }
|
||||
function handle(out: bytes) -> long { return vk_get_i64(out, 0) }
|
||||
function has_ext(props: bytes, n: int, want: string) -> bool {
|
||||
unsafe function fail(what: string, r: int) -> void { print(`vulkan: {what} failed, VkResult {r}`); quit() }
|
||||
unsafe function handle(out: bytes) -> long { return vk_get_i64(out, 0) }
|
||||
unsafe function has_ext(props: bytes, n: int, want: string) -> bool {
|
||||
for i in 0 .. n {
|
||||
if string(vk_at(props, i * VkExtensionProperties_sizeof + VkExtensionProperties_extensionName)) == want { return true }
|
||||
}
|
||||
return false
|
||||
}
|
||||
function read_all(path: string) -> bytes {
|
||||
unsafe function read_all(path: string) -> bytes {
|
||||
let f = file_open(path, "rb")
|
||||
if f == null { return null }
|
||||
file_seek(f, 0, 2)
|
||||
|
|
|
|||
|
|
@ -16,11 +16,11 @@ program VkProbe {
|
|||
|
||||
const API_1_0: int = 4194304 # VK_MAKE_API_VERSION(0, 1, 0, 0)
|
||||
|
||||
function api_text(v: int) -> string { return `{(v >> 22) & 127}.{(v >> 12) & 1023}.{v & 4095}` }
|
||||
function yes(b: bool) -> string { if b { return "yes" }; return "no " }
|
||||
unsafe function api_text(v: int) -> string { return `{(v >> 22) & 127}.{(v >> 12) & 1023}.{v & 4095}` }
|
||||
unsafe function yes(b: bool) -> string { if b { return "yes" }; return "no " }
|
||||
|
||||
# a name in an array of VkExtensionProperties
|
||||
function has_ext(props: bytes, n: int, want: string) -> bool {
|
||||
unsafe function has_ext(props: bytes, n: int, want: string) -> bool {
|
||||
for i in 0 .. n {
|
||||
let at = vk_at(props, i * VkExtensionProperties_sizeof + VkExtensionProperties_extensionName)
|
||||
if string(at) == want { return true }
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ program VkResources {
|
|||
model Anchor { Marker }
|
||||
|
||||
var failures: int = 0
|
||||
function check(what: string, ok: bool) -> void {
|
||||
unsafe function check(what: string, ok: bool) -> void {
|
||||
if ok { print(`vulkan: {what} ok`) } else { print(`vulkan: {what} FAILED`); failures += 1 }
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,16 +21,16 @@ program VkTriangle {
|
|||
const H: int = 240
|
||||
const FMT: int = VK_FORMAT_R8G8B8A8_UNORM
|
||||
|
||||
function fail(what: string, r: int) -> void { print(`vulkan: {what} failed, VkResult {r}`); quit() }
|
||||
function handle(out: bytes) -> long { return vk_get_i64(out, 0) }
|
||||
function has_ext(props: bytes, n: int, want: string) -> bool {
|
||||
unsafe function fail(what: string, r: int) -> void { print(`vulkan: {what} failed, VkResult {r}`); quit() }
|
||||
unsafe function handle(out: bytes) -> long { return vk_get_i64(out, 0) }
|
||||
unsafe function has_ext(props: bytes, n: int, want: string) -> bool {
|
||||
for i in 0 .. n {
|
||||
if string(vk_at(props, i * VkExtensionProperties_sizeof + VkExtensionProperties_extensionName)) == want { return true }
|
||||
}
|
||||
return false
|
||||
}
|
||||
var spv_len: int = 0
|
||||
function read_all(path: string) -> bytes {
|
||||
unsafe function read_all(path: string) -> bytes {
|
||||
let f = file_open(path, "rb")
|
||||
if f == null { return null }
|
||||
file_seek(f, 0, 2)
|
||||
|
|
@ -43,7 +43,7 @@ program VkTriangle {
|
|||
return b
|
||||
}
|
||||
# the first memory type the resource allows that has every property wanted
|
||||
function mem_type(mp: bytes, allowed: int, want: int) -> int {
|
||||
unsafe function mem_type(mp: bytes, allowed: int, want: int) -> int {
|
||||
for t in 0 .. vk_get_i32(mp, VkPhysicalDeviceMemoryProperties_memoryTypeCount) {
|
||||
let pf = vk_get_i32(mp, VkPhysicalDeviceMemoryProperties_memoryTypes + t * VkMemoryType_sizeof + VkMemoryType_propertyFlags)
|
||||
if ((allowed >> t) & 1) == 1 and (pf & want) == want { return t }
|
||||
|
|
@ -51,14 +51,14 @@ program VkTriangle {
|
|||
return -1
|
||||
}
|
||||
# one colour image, one mip, one layer
|
||||
function color_range(at: bytes, off: int) -> void {
|
||||
unsafe function color_range(at: bytes, off: int) -> void {
|
||||
vk_put_i32(at, off + VkImageSubresourceRange_aspectMask, VK_IMAGE_ASPECT_COLOR_BIT)
|
||||
vk_put_i32(at, off + VkImageSubresourceRange_baseMipLevel, 0)
|
||||
vk_put_i32(at, off + VkImageSubresourceRange_levelCount, 1)
|
||||
vk_put_i32(at, off + VkImageSubresourceRange_baseArrayLayer, 0)
|
||||
vk_put_i32(at, off + VkImageSubresourceRange_layerCount, 1)
|
||||
}
|
||||
function barrier(cb: pointer, image: long, old_layout: int, new_layout: int, src_access: int, dst_access: int, src_stage: int, dst_stage: int) -> void {
|
||||
unsafe function barrier(cb: pointer, image: long, old_layout: int, new_layout: int, src_access: int, dst_access: int, src_stage: int, dst_stage: int) -> void {
|
||||
let b = bytes(VkImageMemoryBarrier_sizeof)
|
||||
vk_zero(b, VkImageMemoryBarrier_sizeof)
|
||||
vk_put_i32(b, VkImageMemoryBarrier_sType, VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER)
|
||||
|
|
@ -72,7 +72,7 @@ program VkTriangle {
|
|||
color_range(b, VkImageMemoryBarrier_subresourceRange)
|
||||
vk_cmd_pipeline_barrier(cb, src_stage, dst_stage, 0, 0, null, 0, null, 1, b)
|
||||
}
|
||||
function shader_module(dev: pointer, path: string) -> long {
|
||||
unsafe function shader_module(dev: pointer, path: string) -> long {
|
||||
let spv = read_all(path)
|
||||
if spv == null { print(`vulkan: no SPIR-V at {path} (compile vk_triangle.slang with slangc)`); quit() }
|
||||
let smci = bytes(VkShaderModuleCreateInfo_sizeof)
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ var prefs_n: int = 0
|
|||
namespace Prefs {
|
||||
internal function init() -> void {
|
||||
if prefs_names != null { return }
|
||||
prefs_names = bytes(PREFS_MAX * 8)
|
||||
prefs_names = pointers(PREFS_MAX)
|
||||
prefs_vals = words(PREFS_MAX)
|
||||
prefs_n = 0
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,17 @@ const F_HALF: int = 0x3F000000
|
|||
const PI: float = 3.1415927
|
||||
const F_PI: int = 0x40490FDB
|
||||
|
||||
# the n 4x4 matrices laid end to end in `m`, a view of each. Views are made once, where their
|
||||
# buffer is: a view is a small allocation, and one per matrix per frame is one nobody frees.
|
||||
function m4_views(m: floats, n: int) -> [][]float {
|
||||
let out = new [][]float
|
||||
var i = 0
|
||||
while i < n {
|
||||
push(out, view(m, i * 16, 16))
|
||||
i += 1
|
||||
}
|
||||
return out
|
||||
}
|
||||
function fl(x: fixed) -> float { return float(x) }
|
||||
function fi(n: int) -> float { return float(n) }
|
||||
function fr(n: int, d: int) -> float { return float(n) / float(d) }
|
||||
|
|
|
|||
|
|
@ -241,19 +241,19 @@ function gpu_screenshot(path: string) -> bool { if gpu_kind == GPU_VK { return g
|
|||
var gpu_u_tmp: words = null
|
||||
function gpu_tmp() -> words { if gpu_u_tmp == null { gpu_u_tmp = words(4) }; return gpu_u_tmp }
|
||||
# float bits (IEEE singles in an int), like every other number in the renderer
|
||||
function u_f(loc: int, v: float) -> void { if gpu_kind == GPU_VK { let t = gpu_tmp(); t[0] = float_bits(v); gvk_u_set(loc, t, 4, 1); return }; let t = gpu_tmp(); t[0] = float_bits(v); gl_uniform1fv(loc, 1, t) }
|
||||
function u_f2(loc: int, x: float, y: float) -> void { if gpu_kind == GPU_VK { let t = gpu_tmp(); t[0] = float_bits(x); t[1] = float_bits(y); gvk_u_set(loc, t, 8, 1); return }; let t = gpu_tmp(); t[0] = float_bits(x); t[1] = float_bits(y); gl_uniform2fv(loc, 1, t) }
|
||||
function u_f3(loc: int, x: float, y: float, z: float) -> void { if gpu_kind == GPU_VK { let t = gpu_tmp(); t[0] = float_bits(x); t[1] = float_bits(y); t[2] = float_bits(z); gvk_u_set(loc, t, 12, 1); return }; let t = gpu_tmp(); t[0] = float_bits(x); t[1] = float_bits(y); t[2] = float_bits(z); gl_uniform3fv(loc, 1, t) }
|
||||
function u_f4(loc: int, x: float, y: float, z: float, w: float) -> void { if gpu_kind == GPU_VK { let t = gpu_tmp(); t[0] = float_bits(x); t[1] = float_bits(y); t[2] = float_bits(z); t[3] = float_bits(w); gvk_u_set(loc, t, 16, 1); return }; let t = gpu_tmp(); t[0] = float_bits(x); t[1] = float_bits(y); t[2] = float_bits(z); t[3] = float_bits(w); gl_uniform4fv(loc, 1, t) }
|
||||
function u_v3(loc: int, v: floats) -> void { if gpu_kind == GPU_VK { gvk_u_set(loc, v, 12, 1); return }; gl_uniform3fv(loc, 1, v) }
|
||||
function u_fv(loc: int, n: int, v: floats) -> void { if gpu_kind == GPU_VK { gvk_u_set(loc, v, 4, n); return }; gl_uniform1fv(loc, n, v) }
|
||||
function u_f(loc: int, v: float) -> void { if gpu_kind == GPU_VK { let t = gpu_tmp(); t[0] = float_bits(v); gvk_u_set(loc, data_of(t), 4, 1); return }; let t = gpu_tmp(); t[0] = float_bits(v); gl_uniform1fv(loc, 1, t) }
|
||||
function u_f2(loc: int, x: float, y: float) -> void { if gpu_kind == GPU_VK { let t = gpu_tmp(); t[0] = float_bits(x); t[1] = float_bits(y); gvk_u_set(loc, data_of(t), 8, 1); return }; let t = gpu_tmp(); t[0] = float_bits(x); t[1] = float_bits(y); gl_uniform2fv(loc, 1, t) }
|
||||
function u_f3(loc: int, x: float, y: float, z: float) -> void { if gpu_kind == GPU_VK { let t = gpu_tmp(); t[0] = float_bits(x); t[1] = float_bits(y); t[2] = float_bits(z); gvk_u_set(loc, data_of(t), 12, 1); return }; let t = gpu_tmp(); t[0] = float_bits(x); t[1] = float_bits(y); t[2] = float_bits(z); gl_uniform3fv(loc, 1, t) }
|
||||
function u_f4(loc: int, x: float, y: float, z: float, w: float) -> void { if gpu_kind == GPU_VK { let t = gpu_tmp(); t[0] = float_bits(x); t[1] = float_bits(y); t[2] = float_bits(z); t[3] = float_bits(w); gvk_u_set(loc, data_of(t), 16, 1); return }; let t = gpu_tmp(); t[0] = float_bits(x); t[1] = float_bits(y); t[2] = float_bits(z); t[3] = float_bits(w); gl_uniform4fv(loc, 1, t) }
|
||||
function u_v3(loc: int, v: floats) -> void { if gpu_kind == GPU_VK { gvk_u_set(loc, data_of(v), 12, 1); return }; gl_uniform3fv(loc, 1, v) }
|
||||
function u_fv(loc: int, n: int, v: floats) -> void { if gpu_kind == GPU_VK { gvk_u_set(loc, data_of(v), 4, n); return }; gl_uniform1fv(loc, n, v) }
|
||||
# n vec4s from 4n float bits. Not u_fv with 4n: on Vulkan an array element is copied at the size
|
||||
# given and placed at the array's stride, so a vec4 array fed floats got one float per element -
|
||||
# which drew the chunked grass with every tile at a nonsense corner and zero blades a cell.
|
||||
function u_f4v(loc: int, n: int, v: floats) -> void { if gpu_kind == GPU_VK { gvk_u_set(loc, v, 16, n); return }; gl_uniform4fv(loc, n, v) }
|
||||
function u_mat4(loc: int, m: floats) -> void { if gpu_kind == GPU_VK { gvk_u_set(loc, m, 64, 1); return }; gl_uniform_matrix4fv(loc, 1, 0, m) }
|
||||
function u_mat4n(loc: int, n: int, m: floats) -> void { if gpu_kind == GPU_VK { gvk_u_set(loc, m, 64, n); return }; gl_uniform_matrix4fv(loc, n, 0, m) }
|
||||
function u_i(loc: int, v: int) -> void { if gpu_kind == GPU_VK { let t = gpu_tmp(); t[0] = v; gvk_u_set(loc, t, 4, 1); return }; gl_uniform1i(loc, v) }
|
||||
function u_f4v(loc: int, n: int, v: floats) -> void { if gpu_kind == GPU_VK { gvk_u_set(loc, data_of(v), 16, n); return }; gl_uniform4fv(loc, n, v) }
|
||||
function u_mat4(loc: int, m: floats) -> void { if gpu_kind == GPU_VK { gvk_u_set(loc, data_of(m), 64, 1); return }; gl_uniform_matrix4fv(loc, 1, 0, m) }
|
||||
function u_mat4n(loc: int, n: int, m: floats) -> void { if gpu_kind == GPU_VK { gvk_u_set(loc, data_of(m), 64, n); return }; gl_uniform_matrix4fv(loc, n, 0, m) }
|
||||
function u_i(loc: int, v: int) -> void { if gpu_kind == GPU_VK { let t = gpu_tmp(); t[0] = v; gvk_u_set(loc, data_of(t), 4, 1); return }; gl_uniform1i(loc, v) }
|
||||
|
||||
# ---- what this machine can do ----------------------------------------------------
|
||||
# The advanced graphics features are Windows features: the Vulkan renderer, ray tracing,
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ function grass_blade_mesh(rows: int) -> Mesh {
|
|||
idx[q * 6] = b; idx[q * 6 + 1] = b + 1; idx[q * 6 + 2] = b + 2
|
||||
idx[q * 6 + 3] = b + 1; idx[q * 6 + 4] = b + 3; idx[q * 6 + 5] = b + 2
|
||||
}
|
||||
gpu_mesh_indices(m, idx, nq * 6 * 4, 4)
|
||||
gpu_mesh_indices(m, data_of(idx), nq * 6 * 4, 4)
|
||||
free(idx)
|
||||
m.count = nq * 6
|
||||
gpu_mesh_done(m)
|
||||
|
|
@ -270,7 +270,7 @@ function grass_flush() -> void {
|
|||
if grass_n == 0 { return }
|
||||
var p = grass_prog
|
||||
let mesh = grass_mesh_live()
|
||||
if mesh { p = grass_mesh_prog } else { gpu_buffer_upload(grass_cmds, grass_n * 20, grass_rec, GPU_DYNAMIC) }
|
||||
if mesh { p = grass_mesh_prog } else { gpu_buffer_upload(grass_cmds, grass_n * 20, data_of(grass_rec), GPU_DYNAMIC) }
|
||||
for b in 0 .. grass_band_n {
|
||||
let s = grass_band_start[b]
|
||||
var e = grass_n
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ function mesh_grid(n: int, half: float) -> Mesh {
|
|||
k += 6
|
||||
}
|
||||
}
|
||||
gpu_mesh_indices(m, idx, ni * 4, 4)
|
||||
gpu_mesh_indices(m, data_of(idx), ni * 4, 4)
|
||||
free(idx)
|
||||
m.count = ni
|
||||
gpu_mesh_done(m)
|
||||
|
|
@ -81,7 +81,7 @@ function mesh_card() -> Mesh {
|
|||
free(v)
|
||||
let idx = words(6)
|
||||
idx[0] = 0; idx[1] = 1; idx[2] = 2; idx[3] = 0; idx[4] = 2; idx[5] = 3
|
||||
gpu_mesh_indices(m, idx, 24, 4)
|
||||
gpu_mesh_indices(m, data_of(idx), 24, 4)
|
||||
free(idx)
|
||||
m.count = 6
|
||||
gpu_mesh_done(m)
|
||||
|
|
|
|||
|
|
@ -58,9 +58,17 @@ function q_rotate(o: floats, q: floats, v: floats) -> void {
|
|||
}
|
||||
# pitch about X, yaw about Y, roll about Z, composed as yaw * pitch * roll
|
||||
var q_scratch: floats = null
|
||||
var q_sy: floats = null # views of q_scratch's second, third and fourth
|
||||
var q_sz: floats = null
|
||||
var q_st: floats = null
|
||||
function q_euler(o: floats, pitch: float, yaw: float, roll: float) -> void {
|
||||
if q_scratch == null { q_scratch = floats(16) }
|
||||
let qx = q_scratch; let qy = mem_off(q_scratch, 16); let qz = mem_off(q_scratch, 32); let t = mem_off(q_scratch, 48)
|
||||
if q_scratch == null {
|
||||
q_scratch = floats(16)
|
||||
q_sy = view(q_scratch, 4, 4)
|
||||
q_sz = view(q_scratch, 8, 4)
|
||||
q_st = view(q_scratch, 12, 4)
|
||||
}
|
||||
let qx = q_scratch; let qy = q_sy; let qz = q_sz; let t = q_st
|
||||
q_axis_angle(qx, 1.0, 0.0, 0.0, pitch)
|
||||
q_axis_angle(qy, 0.0, 1.0, 0.0, yaw)
|
||||
q_axis_angle(qz, 0.0, 0.0, 1.0, roll)
|
||||
|
|
|
|||
|
|
@ -35,3 +35,4 @@ import "grass.ludic"
|
|||
import "water.ludic"
|
||||
import "streamline.ludic"
|
||||
import "render.ludic"
|
||||
import "safe_api.ludic"
|
||||
|
|
|
|||
41
packages/ludic.render3d/safe_api.ludic
Normal file
41
packages/ludic.render3d/safe_api.ludic
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
# safe_api.ludic — the renderer's raw buffers, as a program may hold them (L7). The renderer reads
|
||||
# glTF accessors, the screen and PNG files into raw memory, which is its business; a game gets the
|
||||
# same data as slices, bounds-checked, and never frees anything. Each of these copies or hands over
|
||||
# at the boundary, so nothing raw crosses it.
|
||||
|
||||
# an accessor's float components (gltf_count elements of gltf_comps floats), as floats
|
||||
function gltf_accessor_floats(idx: int) -> floats {
|
||||
let p = gltf_accessor(idx)
|
||||
let n = gltf_count * gltf_comps
|
||||
let out = floats(n)
|
||||
for i in 0 .. n { out[i] = float_from_bits(mem_get_f32_bits(p, i)) }
|
||||
free(p)
|
||||
return out
|
||||
}
|
||||
# how many elements an accessor holds, without reading them
|
||||
function gltf_accessor_count(idx: int) -> int {
|
||||
let acc = value_at(value_get(gltf_doc, "accessors"), idx)
|
||||
return jint(acc, "count", 0)
|
||||
}
|
||||
# the presented frame as RGB8, bottom row first, into `out` (at least w * h * 3 bytes)
|
||||
function gpu_read_screen_bytes(w: int, h: int, out: []byte) -> bool {
|
||||
if out == null or len(out) < w * h * 3 { return false }
|
||||
gpu_read_screen(w, h, data_of(out))
|
||||
return true
|
||||
}
|
||||
# a PNG's samples (tex_w x tex_h x tex_channels, 8 or 16 bits): into `reuse` when it is large
|
||||
# enough - a map swap decodes the same size again - else into a new buffer. null if unreadable.
|
||||
function png_decode_bytes(path: string, reuse: []byte) -> []byte {
|
||||
let p = png_decode(path)
|
||||
if p == null { return null }
|
||||
let n = tex_w * tex_h * tex_channels * (tex_depth / 8)
|
||||
var out = reuse
|
||||
if out == null or len(out) < n { out = buffer(n) }
|
||||
for i in 0 .. n { out[i] = p[i] }
|
||||
free(p)
|
||||
return out
|
||||
}
|
||||
# upload samples laid out as the last decode left them (tex_w, tex_h, tex_channels, tex_depth)
|
||||
function tex_upload_bytes(px: []byte, srgb: bool, mips: bool) -> int {
|
||||
return tex_upload(data_of(px), srgb, mips)
|
||||
}
|
||||
|
|
@ -116,7 +116,7 @@ function model_cross_card() -> Model {
|
|||
let idx = words(12)
|
||||
idx[0] = 0; idx[1] = 1; idx[2] = 2; idx[3] = 0; idx[4] = 2; idx[5] = 3
|
||||
idx[6] = 4; idx[7] = 5; idx[8] = 6; idx[9] = 4; idx[10] = 6; idx[11] = 7
|
||||
gpu_mesh_indices(m, idx, 48, 4)
|
||||
gpu_mesh_indices(m, data_of(idx), 48, 4)
|
||||
free(idx)
|
||||
m.count = 12
|
||||
gpu_mesh_done(m)
|
||||
|
|
@ -194,7 +194,7 @@ function model_lupine() -> Model {
|
|||
gpu_mesh_vertices(m, v, gl_bytes_of(nq * 4 * 8), GPU_STATIC)
|
||||
sc_model_layout(m)
|
||||
free(v)
|
||||
gpu_mesh_indices(m, idx, nq * 6 * 4, 4)
|
||||
gpu_mesh_indices(m, data_of(idx), nq * 6 * 4, 4)
|
||||
free(idx)
|
||||
m.count = nq * 6
|
||||
gpu_mesh_done(m)
|
||||
|
|
@ -267,7 +267,7 @@ function model_lupine_dense() -> Model {
|
|||
gpu_mesh_vertices(m, v, gl_bytes_of(nq * 4 * 8), GPU_STATIC)
|
||||
sc_model_layout(m)
|
||||
free(v)
|
||||
gpu_mesh_indices(m, idx, nq * 6 * 4, 4)
|
||||
gpu_mesh_indices(m, data_of(idx), nq * 6 * 4, 4)
|
||||
free(idx)
|
||||
m.count = nq * 6
|
||||
gpu_mesh_done(m)
|
||||
|
|
@ -315,7 +315,7 @@ function model_blade() -> Model {
|
|||
idx[k + 3] = a + 1; idx[k + 4] = a + 3; idx[k + 5] = a + 2
|
||||
k += 6
|
||||
}
|
||||
gpu_mesh_indices(m, idx, ni * 4, 4)
|
||||
gpu_mesh_indices(m, data_of(idx), ni * 4, 4)
|
||||
free(idx)
|
||||
m.count = ni
|
||||
gpu_mesh_done(m)
|
||||
|
|
@ -667,7 +667,7 @@ function layer_gpu_prepare(l: Layer) -> bool {
|
|||
}
|
||||
let n = l.n_lods
|
||||
let cap = l.count
|
||||
gpu_buffer_upload(l.g_src, cap * INST_FLOATS * 4, l.inst, GPU_STATIC)
|
||||
gpu_buffer_upload(l.g_src, cap * INST_FLOATS * 4, data_of(l.inst), GPU_STATIC)
|
||||
gpu_buffer_upload(l.g_dst, (n + 1) * cap * INST_FLOATS * 4, null, GPU_DYNAMIC)
|
||||
if l.g_arena == null { layer_arena_build(l) }
|
||||
let n_mat = len(l.g_arena)
|
||||
|
|
@ -690,14 +690,14 @@ function layer_gpu_prepare(l: Layer) -> bool {
|
|||
}
|
||||
}
|
||||
}
|
||||
gpu_buffer_upload(l.g_cmds, SC_RECS * SC_REC_W, rec, GPU_DYNAMIC)
|
||||
gpu_buffer_upload(l.g_cmds, SC_RECS * SC_REC_W, data_of(rec), GPU_DYNAMIC)
|
||||
let zeros = words(5)
|
||||
for i in 0 .. 5 { zeros[i] = 0 }
|
||||
gpu_buffer_upload(l.g_counts, 20, zeros, GPU_DYNAMIC)
|
||||
gpu_buffer_upload(l.g_counts, 20, data_of(zeros), GPU_DYNAMIC)
|
||||
free(rec); free(zeros)
|
||||
# the card casts every instance, as on the CPU path (layer_grid_build uploads this there)
|
||||
l.n_sh = l.count
|
||||
gpu_buffer_upload(l.sh_buf, l.count * INST_FLOATS * 4, l.inst, GPU_STATIC)
|
||||
gpu_buffer_upload(l.sh_buf, l.count * INST_FLOATS * 4, data_of(l.inst), GPU_STATIC)
|
||||
l.g_n = l.count
|
||||
l.g_on = true
|
||||
return true
|
||||
|
|
@ -715,7 +715,7 @@ function layer_gpu_cull(l: Layer) -> void {
|
|||
pr[32] = float_bits(l.lods[0].height * 2.0); pr[33] = float_bits(4.0)
|
||||
let bufs = words(4)
|
||||
bufs[0] = l.g_src; bufs[1] = l.g_dst; bufs[2] = l.g_cmds; bufs[3] = l.g_counts
|
||||
gpu_dispatch(sc_cull_prog, pr, 144, bufs, 1)
|
||||
gpu_dispatch(sc_cull_prog, data_of(pr), 144, bufs, 1)
|
||||
free(pr); free(bufs)
|
||||
}
|
||||
|
||||
|
|
@ -762,7 +762,7 @@ function layer_grid_build(l: Layer, cell: float) -> void {
|
|||
free(cellof); free(fill)
|
||||
if l.vis == null { l.vis = floats(l.cap * INST_FLOATS) }
|
||||
l.n_sh = l.count
|
||||
gpu_buffer_upload(l.sh_buf, l.count * INST_FLOATS * 4, l.inst, GPU_STATIC)
|
||||
gpu_buffer_upload(l.sh_buf, l.count * INST_FLOATS * 4, data_of(l.inst), GPU_STATIC)
|
||||
}
|
||||
|
||||
# gather the instances of the cells the camera can see (and that are within cull)
|
||||
|
|
@ -848,7 +848,7 @@ function layer_partition_lods(l: Layer, src: floats, total: int) -> void {
|
|||
# casters: the whole (gathered) set from the shadow buffer, unless the impostor casts
|
||||
if l.gcell == 0.0 {
|
||||
l.n_sh = total
|
||||
if total > 0 { gpu_buffer_upload(l.sh_buf, total * INST_FLOATS * 4, src, GPU_DYNAMIC) }
|
||||
if total > 0 { gpu_buffer_upload(l.sh_buf, total * INST_FLOATS * 4, data_of(src), GPU_DYNAMIC) }
|
||||
}
|
||||
free(counts); free(start); free(fill)
|
||||
}
|
||||
|
|
@ -866,7 +866,7 @@ function layer_update(l: Layer) -> void {
|
|||
# per-instance loop — one upload, and the same buffer casts its shadows.
|
||||
if l.streamed and l.imp == null and l.near == 0.0 and l.n_lods <= 1 {
|
||||
l.n_near = l.count; l.n_far = 0; l.n_sh = l.count
|
||||
gpu_buffer_upload(l.buf, l.count * INST_FLOATS * 4, l.inst, GPU_DYNAMIC)
|
||||
gpu_buffer_upload(l.buf, l.count * INST_FLOATS * 4, data_of(l.inst), GPU_DYNAMIC)
|
||||
prof_layer_add(gl_now_us() - t_lu, l.count * INST_FLOATS * 4)
|
||||
return
|
||||
}
|
||||
|
|
@ -916,10 +916,10 @@ function layer_update(l: Layer) -> void {
|
|||
if l.gcell == 0.0 {
|
||||
l.n_sh = l.count
|
||||
if l.count > 0 {
|
||||
gpu_buffer_upload(l.sh_buf, l.count * INST_FLOATS * 4, l.inst, GPU_DYNAMIC)
|
||||
gpu_buffer_upload(l.sh_buf, l.count * INST_FLOATS * 4, data_of(l.inst), GPU_DYNAMIC)
|
||||
}
|
||||
}
|
||||
gpu_buffer_upload(l.buf, nn * INST_FLOATS * 4, tmp, GPU_DYNAMIC)
|
||||
gpu_buffer_upload(l.buf, nn * INST_FLOATS * 4, data_of(tmp), GPU_DYNAMIC)
|
||||
if nf > 0 {
|
||||
gpu_buffer_upload(l.imp_buf, nf * INST_FLOATS * 4, mem_off(tmp, (far_off - nf * INST_FLOATS) * 4), GPU_DYNAMIC)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,8 @@ const SHADOW_CASCADES: int = 5
|
|||
|
||||
var sh_tex: int = 0
|
||||
var sh_fbo: int = 0
|
||||
var sh_vp: floats = null # 4 x 16 float bits
|
||||
var sh_vp: floats = null # the cascades' view-projections, 16 floats each
|
||||
var sh_vp_v: [][]float = null # a view of each
|
||||
var sh_split: floats = null # view-space far distance of each cascade
|
||||
var sh_range: floats = null # 4 light-frustum depth extents (metres)
|
||||
var sh_texel: floats = null # 4 shadow texel sizes (metres)
|
||||
|
|
@ -29,6 +30,7 @@ function shadow_init() -> void {
|
|||
gpu_fb_no_color()
|
||||
gpu_fb_bind(0)
|
||||
sh_vp = floats(16 * SHADOW_CASCADES)
|
||||
sh_vp_v = m4_views(sh_vp, SHADOW_CASCADES)
|
||||
sh_split = floats(SHADOW_CASCADES)
|
||||
sh_range = floats(SHADOW_CASCADES)
|
||||
sh_texel = floats(SHADOW_CASCADES)
|
||||
|
|
@ -141,7 +143,7 @@ function shadow_fit(c: int, near: float, far: float) -> void {
|
|||
free(out); free(eye); free(up); free(center); free(corners)
|
||||
}
|
||||
|
||||
function shadow_cascade_vp(c: int) -> floats { return mem_off(sh_vp, c * 64) }
|
||||
function shadow_cascade_vp(c: int) -> floats { return sh_vp_v[c] }
|
||||
|
||||
|
||||
# render every cascade; `draw` happens through terrain_draw_shadow + the scene's casters
|
||||
|
|
@ -211,7 +213,7 @@ function shadow_dump() -> void {
|
|||
let buf = floats(n * SHADOW_CASCADES)
|
||||
gpu_tex_bind(GPU_TEX2D_ARRAY, sh_tex)
|
||||
gpu_tex_param(GPU_TEX2D_ARRAY, GL_TEXTURE_COMPARE_MODE, GL_NONE)
|
||||
gpu_tex_read(GPU_TEX2D_ARRAY, GL_DEPTH_COMPONENT, GL_FLOAT, buf)
|
||||
gpu_tex_read(GPU_TEX2D_ARRAY, GL_DEPTH_COMPONENT, GL_FLOAT, data_of(buf))
|
||||
gpu_tex_param(GPU_TEX2D_ARRAY, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE)
|
||||
if sh_probe_x != 0.0 {
|
||||
let vp = shadow_cascade_vp(2)
|
||||
|
|
|
|||
|
|
@ -26,11 +26,14 @@ property Skin {
|
|||
names: []string,
|
||||
pose_r: floats, # 4 per node: the pose rotation, model frame
|
||||
pose_t: floats, # 3 per node: the pose offset, model frame (metres)
|
||||
gmat: words, # 16 per node: global matrix this pose
|
||||
gmat: floats, # 16 per node: global matrix this pose
|
||||
gmat_v: [][]float, # a view of each node's matrix in gmat
|
||||
n_joints: int = 0,
|
||||
joints: words, # node index per joint
|
||||
inv_bind: words, # 16 per joint
|
||||
inv_bind: floats, # 16 per joint
|
||||
inv_v: [][]float, # a view of each joint's inverse bind matrix
|
||||
bones: floats, # 16 per joint: what the vertex shader skins with
|
||||
bones_v: [][]float, # a view of each joint's matrix in bones
|
||||
tmp_l: floats,
|
||||
tmp_q: floats,
|
||||
tmp_a: floats,
|
||||
|
|
@ -76,7 +79,8 @@ function skin_load(idx: int) -> Skin {
|
|||
sk.n_nodes = n
|
||||
sk.par = words(n); sk.walk = words(n)
|
||||
sk.rest_t = floats(n * 3); sk.rest_r = floats(n * 4); sk.rest_s = floats(n * 3); sk.rest_g = floats(n * 4)
|
||||
sk.pose_r = floats(n * 4); sk.pose_t = floats(n * 3); sk.gmat = words(n * 16)
|
||||
sk.pose_r = floats(n * 4); sk.pose_t = floats(n * 3); sk.gmat = floats(n * 16)
|
||||
sk.gmat_v = m4_views(sk.gmat, n)
|
||||
sk.names = new []string
|
||||
sk.tmp_l = m4_new(); sk.tmp_q = q_new(); sk.tmp_a = q_new(); sk.tmp_b = q_new(); sk.tmp_c = q_new(); sk.tmp_v = floats(3)
|
||||
for i in 0 .. n { sk.par[i] = -1 }
|
||||
|
|
@ -123,15 +127,17 @@ function skin_load(idx: int) -> Skin {
|
|||
if nj > SKIN_MAX_JOINTS { print(`skin: {nj} joints, only the first {SKIN_MAX_JOINTS} are used`); nj = SKIN_MAX_JOINTS }
|
||||
sk.n_joints = nj
|
||||
sk.joints = words(nj)
|
||||
sk.inv_bind = words(nj * 16)
|
||||
sk.inv_bind = floats(nj * 16)
|
||||
sk.inv_v = m4_views(sk.inv_bind, nj)
|
||||
sk.bones = floats(nj * 16)
|
||||
sk.bones_v = m4_views(sk.bones, nj)
|
||||
for j in 0 .. nj { sk.joints[j] = value_as_int(value_at(jl, j)) }
|
||||
if value_has(skv, "inverseBindMatrices") != 0 {
|
||||
let ib = gltf_accessor(value_as_int(value_get(skv, "inverseBindMatrices")))
|
||||
for i in 0 .. nj * 16 { sk.inv_bind[i] = mem_get_f32_bits(ib, i) }
|
||||
for i in 0 .. nj * 16 { sk.inv_bind[i] = float_from_bits(mem_get_f32_bits(ib, i)) }
|
||||
free(ib)
|
||||
} else {
|
||||
for j in 0 .. nj { m4_identity(mem_off(sk.inv_bind, j * 64)) }
|
||||
for j in 0 .. nj { m4_identity(sk.inv_v[j]) }
|
||||
}
|
||||
skin_reset(sk)
|
||||
skin_pose(sk)
|
||||
|
|
@ -144,7 +150,7 @@ function skin_find(sk: Skin, name: string) -> int {
|
|||
print(`skin: no node {name}`)
|
||||
return -1
|
||||
}
|
||||
function skin_mat(sk: Skin, node: int) -> floats { return mem_off(sk.gmat, node * 64) }
|
||||
function skin_mat(sk: Skin, node: int) -> floats { return sk.gmat_v[node] }
|
||||
|
||||
# back to the rest pose
|
||||
function skin_reset(sk: Skin) -> void {
|
||||
|
|
@ -192,7 +198,7 @@ function skin_pose(sk: Skin) -> void {
|
|||
else { m4_copy(skin_mat(sk, i), sk.tmp_l) }
|
||||
}
|
||||
for j in 0 .. sk.n_joints {
|
||||
m4_mul(mem_off(sk.bones, j * 64), skin_mat(sk, sk.joints[j]), mem_off(sk.inv_bind, j * 64))
|
||||
m4_mul(sk.bones_v[j], skin_mat(sk, sk.joints[j]), sk.inv_v[j])
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -209,10 +215,12 @@ function skin_clone(src: Skin) -> Skin {
|
|||
sk.n_nodes = src.n_nodes; sk.par = src.par; sk.walk = src.walk
|
||||
sk.rest_t = src.rest_t; sk.rest_r = src.rest_r; sk.rest_s = src.rest_s; sk.rest_g = src.rest_g
|
||||
sk.names = src.names
|
||||
sk.n_joints = src.n_joints; sk.joints = src.joints; sk.inv_bind = src.inv_bind
|
||||
sk.n_joints = src.n_joints; sk.joints = src.joints; sk.inv_bind = src.inv_bind; sk.inv_v = src.inv_v
|
||||
let n = src.n_nodes
|
||||
sk.pose_r = floats(n * 4); sk.pose_t = floats(n * 3); sk.gmat = words(n * 16)
|
||||
sk.pose_r = floats(n * 4); sk.pose_t = floats(n * 3); sk.gmat = floats(n * 16)
|
||||
sk.gmat_v = m4_views(sk.gmat, n)
|
||||
sk.bones = floats(src.n_joints * 16)
|
||||
sk.bones_v = m4_views(sk.bones, src.n_joints)
|
||||
sk.tmp_l = m4_new(); sk.tmp_q = q_new(); sk.tmp_a = q_new(); sk.tmp_b = q_new(); sk.tmp_c = q_new(); sk.tmp_v = floats(3)
|
||||
skin_reset(sk)
|
||||
skin_pose(sk)
|
||||
|
|
|
|||
|
|
@ -261,7 +261,7 @@ function stream_update(s: Stream, cam_x: float, cam_z: float) -> void {
|
|||
}
|
||||
if c != null and c.count > 0 and l.count + c.count <= l.cap and stream_chunk_visible(s, cx, cz, c) {
|
||||
let tg = gl_now_us()
|
||||
mem_copy(mem_off(l.inst, l.count * INST_FLOATS * 4), c.data, c.count * INST_FLOATS * 4)
|
||||
mem_copy(mem_off(l.inst, l.count * INST_FLOATS * 4), data_of(c.data), c.count * INST_FLOATS * 4)
|
||||
l.count += c.count
|
||||
stream_us_gather = stream_us_gather + (gl_now_us() - tg)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -270,7 +270,7 @@ function terrain_generate() -> void {
|
|||
ter_heights = floats(TERRAIN_RES * TERRAIN_RES)
|
||||
gpu_tex_bind(GPU_TEX2D, ter_height_tex)
|
||||
gpu_pixel_store(GL_PACK_ALIGNMENT, 4)
|
||||
gpu_tex_read(GPU_TEX2D, GL_RED, GL_FLOAT, ter_heights)
|
||||
gpu_tex_read(GPU_TEX2D, GL_RED, GL_FLOAT, data_of(ter_heights))
|
||||
gpu_fb_bind(0)
|
||||
gpu_fb_free(fbo)
|
||||
gpu_program_free(p)
|
||||
|
|
@ -663,7 +663,7 @@ function cdlod_init() -> void {
|
|||
k += 6
|
||||
}
|
||||
}
|
||||
gpu_mesh_indices(m, idx, ni * 4, 4)
|
||||
gpu_mesh_indices(m, data_of(idx), ni * 4, 4)
|
||||
free(idx)
|
||||
m.count = ni
|
||||
gpu_mesh_done(m)
|
||||
|
|
|
|||
|
|
@ -389,7 +389,7 @@ function tex_load_hdr(path: pointer) -> int {
|
|||
let id = gpu_tex_new()
|
||||
gpu_tex_bind(GPU_TEX2D, id)
|
||||
gpu_pixel_store(GL_UNPACK_ALIGNMENT, 4)
|
||||
gpu_tex_image2d(GL_RGB16F, tex_w, tex_h, GL_RGB, GL_FLOAT, px)
|
||||
gpu_tex_image2d(GL_RGB16F, tex_w, tex_h, GL_RGB, GL_FLOAT, data_of(px))
|
||||
gpu_tex_param(GPU_TEX2D, GL_TEXTURE_WRAP_S, GL_REPEAT)
|
||||
gpu_tex_param(GPU_TEX2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
|
||||
gpu_tex_param(GPU_TEX2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
|
||||
|
|
@ -417,7 +417,7 @@ function tex_max(tex: int, w: int, h: int, tag: pointer) -> void {
|
|||
let buf = floats(w * h * 4)
|
||||
gpu_tex_bind(GPU_TEX2D, tex)
|
||||
gpu_pixel_store(GL_PACK_ALIGNMENT, 4)
|
||||
gpu_tex_read(GPU_TEX2D, GL_RGBA, GL_FLOAT, buf)
|
||||
gpu_tex_read(GPU_TEX2D, GL_RGBA, GL_FLOAT, data_of(buf))
|
||||
var best = 0.0; var bx = 0; var by = 0
|
||||
var i = 0
|
||||
while i < w * h {
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ var wt_wade_s: float = 0.0
|
|||
var water_refl: Target = null # the world mirrored in the surface, half resolution
|
||||
var water_refl_div: int = 2 # R3D_REFLDIV overrides: 2 = half res, 4 = quarter
|
||||
var water_saved: floats = null # the real camera's matrices, restored after the pass
|
||||
var water_saved_vp: floats = null # views of its second and third matrices
|
||||
var water_saved_ivp: floats = null
|
||||
var water_dumped: bool = false
|
||||
# Several still-water planes, each at its own level over its own bounds: the sea round an
|
||||
# island and a lake a hundred metres above it cannot be one surface. Each draws the same way;
|
||||
|
|
@ -49,11 +51,13 @@ function water_reflection_pass() -> void {
|
|||
# rendered at the window's size would be paying for pixels the water never samples
|
||||
water_refl = target_new(post_w / water_refl_div, post_h / water_refl_div, GL_RGBA16F, GL_RGBA, GL_HALF_FLOAT, true, GL_LINEAR)
|
||||
water_saved = floats(16 * 4 + 3)
|
||||
water_saved_vp = view(water_saved, 16, 16)
|
||||
water_saved_ivp = view(water_saved, 32, 16)
|
||||
}
|
||||
# save the camera
|
||||
m4_copy(water_saved, cam_view)
|
||||
m4_copy(mem_off(water_saved, 64), cam_vp)
|
||||
m4_copy(mem_off(water_saved, 128), cam_inv_vp)
|
||||
m4_copy(water_saved_vp, cam_vp)
|
||||
m4_copy(water_saved_ivp, cam_inv_vp)
|
||||
let sx = cam_pos[0]; let sy = cam_pos[1]; let sz = cam_pos[2]
|
||||
# the mirrored camera: view' = view * R, R reflecting y about the surface (y' = 2L - y).
|
||||
# R has determinant -1, so the winding flips (front faces culled below) and the image
|
||||
|
|
@ -98,8 +102,8 @@ function water_reflection_pass() -> void {
|
|||
# restore
|
||||
r3d_clip_y = -2147483600.0
|
||||
m4_copy(cam_view, water_saved)
|
||||
m4_copy(cam_vp, mem_off(water_saved, 64))
|
||||
m4_copy(cam_inv_vp, mem_off(water_saved, 128))
|
||||
m4_copy(cam_vp, water_saved_vp)
|
||||
m4_copy(cam_inv_vp, water_saved_ivp)
|
||||
v3_set(cam_pos, sx, sy, sz)
|
||||
free(eye); free(fwd); free(up); free(at)
|
||||
gpu_fb_bind(0)
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@ var ing_qty: words = null
|
|||
function craft_ensure() -> void {
|
||||
if recipe_reg == null {
|
||||
recipe_reg = Dict.new()
|
||||
recipe_out = bytes(CRAFT_MAX * 8); recipe_outqty = words(CRAFT_MAX)
|
||||
ing_rid = words(CRAFT_ING_MAX); ing_name = bytes(CRAFT_ING_MAX * 8); ing_qty = words(CRAFT_ING_MAX)
|
||||
recipe_out = pointers(CRAFT_MAX); recipe_outqty = words(CRAFT_MAX)
|
||||
ing_rid = words(CRAFT_ING_MAX); ing_name = pointers(CRAFT_ING_MAX); ing_qty = words(CRAFT_ING_MAX)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,8 +23,8 @@ function dlg_ensure() -> void {
|
|||
if dlg_reg == null {
|
||||
dlg_reg = Dict.new()
|
||||
dlg_tree_start = words(DLG_TREE_MAX)
|
||||
dlg_node_tree = words(DLG_NODE_MAX); dlg_node_text = bytes(DLG_NODE_MAX * 8)
|
||||
dlg_choice_node = words(DLG_CHOICE_MAX); dlg_choice_label = bytes(DLG_CHOICE_MAX * 8); dlg_choice_next = words(DLG_CHOICE_MAX)
|
||||
dlg_node_tree = words(DLG_NODE_MAX); dlg_node_text = pointers(DLG_NODE_MAX)
|
||||
dlg_choice_node = words(DLG_CHOICE_MAX); dlg_choice_label = pointers(DLG_CHOICE_MAX); dlg_choice_next = words(DLG_CHOICE_MAX)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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) ---------------
|
||||
|
|
|
|||
|
|
@ -107,6 +107,13 @@ function first_arg_is_text(e: Node) -> bool {
|
|||
return t == "string"
|
||||
}
|
||||
|
||||
# a namespace that computes inline also takes the methods an `alias` gives it (L6): Time.now_us
|
||||
# is declared in runtime/native/namespaces.ludic beside Time.now, which the compiler computes
|
||||
function emit_alias_or_fail(ns: pointer, meth: pointer, e: Node) -> Val {
|
||||
let al = ns_alias_find(ns, meth)
|
||||
if al < 0 { perr(`unknown builtin {ns}.{meth}`) }
|
||||
return emit_alias_call(al, e)
|
||||
}
|
||||
function emit_ns_call(ns: pointer, meth: pointer, e: Node) -> Val {
|
||||
# `Prop.of(e)` / `Prop.has(e)` — typed access to one entity's component, the
|
||||
# same binding a query loop makes but for an entity handle held in a variable.
|
||||
|
|
@ -120,19 +127,19 @@ function emit_ns_call(ns: pointer, meth: pointer, e: Node) -> Val {
|
|||
# bare rt_ name — so `floor`/`round`/`lerp` never leak into the bare namespace.
|
||||
if (ns == "Math") {
|
||||
if is_math_ns(meth) { return emit_math_ns(meth, e) }
|
||||
perr(`unknown builtin Math.{meth}`)
|
||||
return emit_alias_or_fail("Math", meth, e)
|
||||
}
|
||||
if (ns == "Text") {
|
||||
if is_text_ns(meth) { return emit_text_ns(meth, e) }
|
||||
perr(`unknown builtin Text.{meth}`)
|
||||
return emit_alias_or_fail("Text", meth, e)
|
||||
}
|
||||
if (ns == "List") {
|
||||
if is_list_ns(meth) { return emit_list_ns(meth, e) }
|
||||
perr(`unknown builtin List.{meth}`)
|
||||
return emit_alias_or_fail("List", meth, e)
|
||||
}
|
||||
if (ns == "Ease") {
|
||||
if is_ease_ns(meth) { return emit_ease_ns(meth, e) }
|
||||
perr(`unknown builtin Ease.{meth}`)
|
||||
return emit_alias_or_fail("Ease", meth, e)
|
||||
}
|
||||
if (ns == "Anim") {
|
||||
if is_anim_ns(meth) { return emit_anim_ns(meth, e) }
|
||||
|
|
@ -148,87 +155,87 @@ function emit_ns_call(ns: pointer, meth: pointer, e: Node) -> Val {
|
|||
}
|
||||
if (ns == "Collision") {
|
||||
if is_collide_ns(meth) { return emit_collide_ns(meth, e) }
|
||||
perr(`unknown builtin Collision.{meth}`)
|
||||
return emit_alias_or_fail("Collision", meth, e)
|
||||
}
|
||||
if (ns == "Memory") {
|
||||
if is_mem_ns(meth) { return emit_mem_ns(meth, e) }
|
||||
perr(`unknown builtin Memory.{meth}`)
|
||||
return emit_alias_or_fail("Memory", meth, e)
|
||||
}
|
||||
if (ns == "Color") {
|
||||
if is_colorfn_ns(meth) { return emit_colorfn_ns(meth, e) }
|
||||
perr(`unknown builtin Color.{meth}`)
|
||||
return emit_alias_or_fail("Color", meth, e)
|
||||
}
|
||||
if (ns == "Time") {
|
||||
if is_time_ns(meth) { return emit_time_ns(meth, e) }
|
||||
perr(`unknown builtin Time.{meth}`)
|
||||
return emit_alias_or_fail("Time", meth, e)
|
||||
}
|
||||
if (ns == "Hash") {
|
||||
if is_hash_ns(meth) { return emit_hash_ns(meth, e) }
|
||||
perr(`unknown builtin Hash.{meth}`)
|
||||
return emit_alias_or_fail("Hash", meth, e)
|
||||
}
|
||||
if (ns == "Crypto") {
|
||||
if is_crypto_ns(meth) { return emit_crypto_ns(meth, e) }
|
||||
perr(`unknown builtin Crypto.{meth}`)
|
||||
return emit_alias_or_fail("Crypto", meth, e)
|
||||
}
|
||||
if (ns == "Uuid") {
|
||||
if is_uuid_ns(meth) { return emit_uuid_ns(meth, e) }
|
||||
perr(`unknown builtin Uuid.{meth}`)
|
||||
return emit_alias_or_fail("Uuid", meth, e)
|
||||
}
|
||||
if (ns == "Noise") {
|
||||
if is_noise_ns(meth) { return emit_noise_ns(meth, e) }
|
||||
perr(`unknown builtin Noise.{meth}`)
|
||||
return emit_alias_or_fail("Noise", meth, e)
|
||||
}
|
||||
if (ns == "Log") {
|
||||
if is_log_ns(meth) { return emit_log_ns(meth, e) }
|
||||
perr(`unknown builtin Log.{meth}`)
|
||||
return emit_alias_or_fail("Log", meth, e)
|
||||
}
|
||||
if (ns == "Os") {
|
||||
if is_os_ns(meth) { return emit_os_ns(meth, e) }
|
||||
perr(`unknown builtin Os.{meth}`)
|
||||
return emit_alias_or_fail("Os", meth, e)
|
||||
}
|
||||
if (ns == "Unicode") {
|
||||
if is_unicode_ns(meth) { return emit_unicode_ns(meth, e) }
|
||||
perr(`unknown builtin Unicode.{meth}`)
|
||||
return emit_alias_or_fail("Unicode", meth, e)
|
||||
}
|
||||
if (ns == "Fs") {
|
||||
if is_fs_ns(meth) { return emit_fs_ns(meth, e) }
|
||||
perr(`unknown builtin Fs.{meth}`)
|
||||
return emit_alias_or_fail("Fs", meth, e)
|
||||
}
|
||||
if (ns == "Path") {
|
||||
if is_path_ns(meth) { return emit_path_ns(meth, e) }
|
||||
perr(`unknown builtin Path.{meth}`)
|
||||
return emit_alias_or_fail("Path", meth, e)
|
||||
}
|
||||
if (ns == "Mime") {
|
||||
if is_mime_ns(meth) { return emit_mime_ns(meth, e) }
|
||||
perr(`unknown builtin Mime.{meth}`)
|
||||
return emit_alias_or_fail("Mime", meth, e)
|
||||
}
|
||||
if (ns == "Vector") {
|
||||
if is_vector_ns(meth) { return emit_vector_ns(meth, e) }
|
||||
perr(`unknown builtin Vector.{meth}`)
|
||||
return emit_alias_or_fail("Vector", meth, e)
|
||||
}
|
||||
if (ns == "IVec2") {
|
||||
if is_ivec_ns(meth) { return emit_ivec_ns(meth, e) }
|
||||
perr(`unknown builtin IVec2.{meth}`)
|
||||
return emit_alias_or_fail("IVec2", meth, e)
|
||||
}
|
||||
if (ns == "Rect") {
|
||||
if is_rect_ns(meth) { return emit_rect_ns(meth, e) }
|
||||
perr(`unknown builtin Rect.{meth}`)
|
||||
return emit_alias_or_fail("Rect", meth, e)
|
||||
}
|
||||
if (ns == "Duration") {
|
||||
if is_duration_ns(meth) { return emit_duration_ns(meth, e) }
|
||||
perr(`unknown builtin Duration.{meth}`)
|
||||
return emit_alias_or_fail("Duration", meth, e)
|
||||
}
|
||||
if (ns == "Date") {
|
||||
if is_date_ns(meth) { return emit_date_ns(meth, e) }
|
||||
perr(`unknown builtin Date.{meth}`)
|
||||
return emit_alias_or_fail("Date", meth, e)
|
||||
}
|
||||
if (ns == "DateTime") {
|
||||
if is_datetime_ns(meth) { return emit_datetime_ns(meth, e) }
|
||||
perr(`unknown builtin DateTime.{meth}`)
|
||||
return emit_alias_or_fail("DateTime", meth, e)
|
||||
}
|
||||
if (ns == "Clock") {
|
||||
if is_clock_ns(meth) { return emit_clock_ns(meth, e) }
|
||||
perr(`unknown builtin Clock.{meth}`)
|
||||
return emit_alias_or_fail("Clock", meth, e)
|
||||
}
|
||||
# #80 — entity pool stats. The ECS allocator already recycles freed entity slots
|
||||
# through a freelist (L_alloc pops @L_freen before growing @L_entc), and component
|
||||
|
|
@ -287,7 +294,7 @@ function emit_ns_call(ns: pointer, meth: pointer, e: Node) -> Val {
|
|||
if g_windowed { emit(` call void @app_{meth}()\n`) }
|
||||
return val("0", "void")
|
||||
}
|
||||
perr(`unknown builtin App.{meth}`)
|
||||
return emit_alias_or_fail("App", meth, e)
|
||||
}
|
||||
if (ns == "Pool") {
|
||||
if (meth == "capacity") { return val(itoa(MAX_ENT), "int") } # max entities
|
||||
|
|
@ -298,7 +305,7 @@ function emit_ns_call(ns: pointer, meth: pointer, e: Node) -> Val {
|
|||
let fr = emit_bind("load i32, ptr @L_freen")
|
||||
return val(emit_bind(`sub i32 {ec}, {fr}`), "int")
|
||||
}
|
||||
perr(`unknown builtin Pool.{meth}`)
|
||||
return emit_alias_or_fail("Pool", meth, e)
|
||||
}
|
||||
# L6: a method declared by `alias` in a namespace block - the engine's own in
|
||||
# runtime/native/namespaces.ludic, a package's in its files - is a call to its target
|
||||
|
|
@ -341,7 +348,7 @@ function emit_ns_call(ns: pointer, meth: pointer, e: Node) -> Val {
|
|||
let nm = emit_expr(e.kids[0])
|
||||
return val(emit_bind(`call i32 @L_spawn_prefab(ptr {nm.code})`), "entity")
|
||||
}
|
||||
perr(`unknown builtin Prefab.{meth}`)
|
||||
return emit_alias_or_fail("Prefab", meth, e)
|
||||
}
|
||||
# Camera.* — the world-space camera: a draw offset threaded through the render
|
||||
# path (runtime/native/core.ludic). set/follow move it; shake jitters it from
|
||||
|
|
@ -613,11 +620,17 @@ function emit_call(e: Node) -> Val {
|
|||
let w = emit_bind(`zext i32 {n.code} to i64`)
|
||||
return val(emit_bind(`call ptr @malloc(i64 {w})`), "pointer")
|
||||
}
|
||||
if (name == "words") { # words(n): allocate n 32-bit words
|
||||
let n = emit_expr(e.kids[0])
|
||||
let by = emit_bind(`mul i32 {n.code}, 4`)
|
||||
let w = emit_bind(`zext i32 {by} to i64`)
|
||||
return val(emit_bind(`call ptr @malloc(i64 {w})`), "words")
|
||||
if (name == "words") { return emit_sized_slice("int", emit_expr(e.kids[0])) } # words(n): n zeroed ints
|
||||
if (name == "buffer") and (find_fn(name) == null) { return emit_sized_slice("byte", emit_expr(e.kids[0])) } # buffer(n): n zeroed bytes (L7)
|
||||
if (name == "fixeds") and (find_fn(name) == null) { return emit_sized_slice("fixed", emit_expr(e.kids[0])) }
|
||||
if (name == "pointers") and (find_fn(name) == null) { return emit_sized_slice("pointer", emit_expr(e.kids[0])) }
|
||||
# view(xs, start, count): `count` elements of xs from `start`, sharing its storage - checked
|
||||
# against xs's length once, when it is made, and bounds-checked like any slice after (L7)
|
||||
if (name == "view") { return emit_view(e) }
|
||||
# data_of(xs): the address of a slice's first element, for handing to C (unsafe, L7)
|
||||
if (name == "data_of") {
|
||||
let sv = emit_expr(e.kids[0])
|
||||
return val(emit_bind(`load ptr, ptr {slice_field(sv.code, 0)}`), "pointer")
|
||||
}
|
||||
if (name == "fixed") {
|
||||
let a = emit_expr(e.kids[0])
|
||||
|
|
@ -648,7 +661,7 @@ function emit_call(e: Node) -> Val {
|
|||
if ((name == "floats") or (name == "doubles")) and (find_fn(name) == null) {
|
||||
var ft = "float"
|
||||
if (name == "doubles") { ft = "double" }
|
||||
return emit_fp_buffer(ft, emit_expr(e.kids[0]))
|
||||
return emit_sized_slice(ft, emit_expr(e.kids[0]))
|
||||
}
|
||||
if (name == "floor") { let a = emit_expr(e.kids[0]); return val(emit_bind(`ashr i32 {a.code}, 16`), "int") }
|
||||
# --- the testing framework's assertions (see emit_test_runner) --------------
|
||||
|
|
@ -854,9 +867,12 @@ function emit_call(e: Node) -> Val {
|
|||
let eatys = new []pointer
|
||||
var ei = 0
|
||||
while ei < len(e.kids) {
|
||||
let v = emit_expr(e.kids[ei])
|
||||
var v = emit_expr(e.kids[ei])
|
||||
# a C function wants a buffer's elements, never a Ludic slice's header
|
||||
if is_slice_ty(v.ty) { v = val(emit_bind(`load ptr, ptr {slice_field(v.code, 0)}`), "pointer") }
|
||||
var pty = v.ty
|
||||
if ei < len(eptys) { pty = eptys[ei] }
|
||||
if is_slice_ty(pty) { pty = "pointer" }
|
||||
push(eargs, coerce_code(v, pty))
|
||||
push(eatys, pty)
|
||||
ei += 1
|
||||
|
|
|
|||
|
|
@ -86,6 +86,7 @@ function emit_header() -> void {
|
|||
emith("; Ludic (self-hosted) -> LLVM IR\n")
|
||||
emith("declare i32 @printf(ptr, ...)\n")
|
||||
emith("declare ptr @malloc(i64)\n")
|
||||
emith("declare ptr @calloc(i64, i64)\n")
|
||||
emith("declare ptr @realloc(ptr, i64)\n")
|
||||
emith("declare void @free(ptr)\n")
|
||||
emith("declare i64 @fread(ptr, i64, i64, ptr)\n")
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ function emit_intrinsic(name: pointer, e: Node) -> Val {
|
|||
return val(emit_bind(`call ptr @lp_pak_open(ptr {p}, ptr {m})`), "pointer")
|
||||
}
|
||||
if (name == "file_read") or (name == "file_write") {
|
||||
let f = arg_code(e, 0); let b = arg_code(e, 1); let n = arg_code(e, 2)
|
||||
let f = arg_code(e, 0); let b = raw_expr(e.kids[1]).code; let n = arg_code(e, 2)
|
||||
let w = emit_bind(`zext i32 {n} to i64`)
|
||||
var fn2 = "@fread"
|
||||
if (name == "file_write") { fn2 = "@fwrite" }
|
||||
|
|
|
|||
|
|
@ -35,16 +35,23 @@ function is_intrinsic2(name: pointer) -> bool {
|
|||
|
||||
function emit_intrinsic2(name: pointer, e: Node) -> Val {
|
||||
if (name == "free") {
|
||||
let p = arg_code(e, 0); emit(" call void @free(ptr "); emit(p); emit(")\n"); return val("0", "void")
|
||||
let fv = emit_expr(e.kids[0])
|
||||
# a slice is its elements and its header: both go (L7)
|
||||
if is_slice_ty(fv.ty) {
|
||||
let fd = emit_bind(`load ptr, ptr {slice_field(fv.code, 0)}`)
|
||||
emit(` call void @free(ptr {fd})\n`)
|
||||
}
|
||||
emit(` call void @free(ptr {fv.code})\n`)
|
||||
return val("0", "void")
|
||||
}
|
||||
if (name == "fill") {
|
||||
let p = arg_code(e, 0); let v = arg_code(e, 1); let n = arg_code(e, 2)
|
||||
let p = raw_expr(e.kids[0]).code; let v = arg_code(e, 1); let n = arg_code(e, 2)
|
||||
let w = emit_bind(`zext i32 {n} to i64`)
|
||||
emit(" call ptr @memset(ptr "); emit(p); emit(", i32 "); emit(v); emit(", i64 "); emit(w); emit(")\n")
|
||||
return val("0", "void")
|
||||
}
|
||||
if (name == "offset") {
|
||||
let p = arg_code(e, 0); let n = arg_code(e, 1)
|
||||
let p = raw_expr(e.kids[0]).code; let n = arg_code(e, 1)
|
||||
let g = nreg()
|
||||
emit(" "); emit(g); emit(" = getelementptr inbounds i8, ptr "); emit(p); emit(", i32 "); emit(n); emit("\n")
|
||||
return val(g, "pointer")
|
||||
|
|
|
|||
|
|
@ -19,28 +19,28 @@ function emit_mem_ns(meth: pointer, e: Node) -> Val {
|
|||
let n = emit_expr(e.kids[0])
|
||||
let by = emit_bind(`mul i32 {n.code}, 4`)
|
||||
let w = emit_bind(`zext i32 {by} to i64`)
|
||||
return val(emit_bind(`call ptr @malloc(i64 {w})`), "words")
|
||||
return emit_sized_slice("int", n)
|
||||
}
|
||||
if (meth == "copy") { # copy n bytes src -> dst
|
||||
let dst = emit_expr(e.kids[0]); let src = emit_expr(e.kids[1]); let n = emit_expr(e.kids[2])
|
||||
let dst = raw_expr(e.kids[0]); let src = raw_expr(e.kids[1]); let n = emit_expr(e.kids[2])
|
||||
let w = emit_bind(`zext i32 {n.code} to i64`)
|
||||
emit(" call ptr @memcpy(ptr "); emit(dst.code); emit(", ptr "); emit(src.code); emit(", i64 "); emit(w); emit(")\n")
|
||||
return val("0", "void")
|
||||
}
|
||||
if (meth == "fill") { # set n bytes of buf to value v
|
||||
let buf = emit_expr(e.kids[0]); let v = emit_expr(e.kids[1]); let n = emit_expr(e.kids[2])
|
||||
let buf = raw_expr(e.kids[0]); let v = emit_expr(e.kids[1]); let n = emit_expr(e.kids[2])
|
||||
let w = emit_bind(`zext i32 {n.code} to i64`)
|
||||
emit(" call ptr @memset(ptr "); emit(buf.code); emit(", i32 "); emit(v.code); emit(", i64 "); emit(w); emit(")\n")
|
||||
return val("0", "void")
|
||||
}
|
||||
if (meth == "peek") { # read one byte at buf[i], 0..255
|
||||
let buf = emit_expr(e.kids[0]); let i = emit_expr(e.kids[1])
|
||||
let buf = raw_expr(e.kids[0]); let i = emit_expr(e.kids[1])
|
||||
let p = emit_bind(`getelementptr inbounds i8, ptr {buf.code}, i32 {i.code}`)
|
||||
let c = emit_bind(`load i8, ptr {p}`)
|
||||
return val(emit_bind(`zext i8 {c} to i32`), "int")
|
||||
}
|
||||
# poke: write the low byte of v at buf[i]
|
||||
let buf = emit_expr(e.kids[0]); let i = emit_expr(e.kids[1]); let v = emit_expr(e.kids[2])
|
||||
let buf = raw_expr(e.kids[0]); let i = emit_expr(e.kids[1]); let v = emit_expr(e.kids[2])
|
||||
let p = emit_bind(`getelementptr inbounds i8, ptr {buf.code}, i32 {i.code}`)
|
||||
let b = emit_bind(`trunc i32 {v.code} to i8`)
|
||||
emit(" store i8 "); emit(b); emit(", ptr "); emit(p); emit("\n")
|
||||
|
|
|
|||
|
|
@ -59,6 +59,54 @@ function emit_new_slice(ty: pointer) -> Val {
|
|||
return val(h, ty)
|
||||
}
|
||||
|
||||
# words(n), floats(n), doubles(n): a slice of n zeroed elements, its length n (L7)
|
||||
function emit_sized_slice(el: pointer, n: Val) -> Val {
|
||||
let h = emit_new_slice("[]" + el)
|
||||
let esz = emit_sizeof(llty(el))
|
||||
let nn = emit_bind(`zext i32 {n.code} to i64`)
|
||||
let data = emit_bind(`call ptr @calloc(i64 {nn}, i64 {esz})`)
|
||||
emit(` store ptr {data}, ptr {slice_field(h.code, 0)}\n`)
|
||||
emit(` store i32 {n.code}, ptr {slice_field(h.code, 1)}\n`)
|
||||
emit(` store i32 {n.code}, ptr {slice_field(h.code, 2)}\n`)
|
||||
return h
|
||||
}
|
||||
function emit_view(e: Node) -> Val {
|
||||
let xs = emit_expr(e.kids[0])
|
||||
if not is_slice_ty(xs.ty) { perr(`view takes a slice, and this is {xs.ty}`) }
|
||||
let st = emit_expr(e.kids[1])
|
||||
let ct = emit_expr(e.kids[2])
|
||||
let el = slice_elem(xs.ty)
|
||||
let ln = emit_bind(`load i32, ptr {slice_field(xs.code, 1)}`)
|
||||
let endv = emit_bind(`add i32 {st.code}, {ct.code}`)
|
||||
let ok1 = emit_bind(`icmp ule i32 {endv}, {ln}`)
|
||||
let ok2 = emit_bind(`icmp sge i32 {st.code}, 0`)
|
||||
let ok3 = emit_bind(`icmp sge i32 {ct.code}, 0`)
|
||||
let ok12 = emit_bind(`and i1 {ok1}, {ok2}`)
|
||||
let ok = emit_bind(`and i1 {ok12}, {ok3}`)
|
||||
let lok = lbl("vwok")
|
||||
let lbad = lbl("vwbad")
|
||||
emit(` br i1 {ok}, label %{lok}, label %{lbad}\n`)
|
||||
emit(`{lbad}:\n`)
|
||||
g_uses_bounds = true
|
||||
let bmsg = emit_str_const(`{g_src_name}:{itoa(e.line)}: view past the end: from `)
|
||||
let bse = emit_bind(stdstream_rhs(2))
|
||||
emit(` call i32 (ptr, ptr, ...) @fprintf(ptr {bse}, ptr @.fmt_bounds, ptr {bmsg}, i32 {endv}, i32 {ln})\n`)
|
||||
emit(" call void @exit(i32 1)\n unreachable\n")
|
||||
emit(`{lok}:\n`)
|
||||
let d0 = emit_bind(`load ptr, ptr {slice_field(xs.code, 0)}`)
|
||||
let d1 = emit_bind(`getelementptr inbounds {llty(el)}, ptr {d0}, i32 {st.code}`)
|
||||
let h = emit_new_slice(xs.ty)
|
||||
emit(` store ptr {d1}, ptr {slice_field(h.code, 0)}\n`)
|
||||
emit(` store i32 {ct.code}, ptr {slice_field(h.code, 1)}\n`)
|
||||
emit(` store i32 {ct.code}, ptr {slice_field(h.code, 2)}\n`)
|
||||
return h
|
||||
}
|
||||
# an expression that a raw-memory intrinsic reads as an address: a slice gives its elements (L7)
|
||||
function raw_expr(n: Node) -> Val {
|
||||
let v = emit_expr(n)
|
||||
if is_slice_ty(v.ty) { return val(emit_bind(`load ptr, ptr {slice_field(v.code, 0)}`), "pointer") }
|
||||
return v
|
||||
}
|
||||
function slice_field(h: pointer, i: int) -> pointer {
|
||||
let r = nreg()
|
||||
emit(" "); emit(r); emit(" = getelementptr inbounds %LSlice, ptr "); emit(h)
|
||||
|
|
|
|||
|
|
@ -146,6 +146,7 @@ function block_ends(b: Node) -> bool {
|
|||
}
|
||||
function stmt_ends(st: Node) -> bool {
|
||||
if st.kind == S_RETURN { return true }
|
||||
if st.kind == S_UNSAFE { return block_ends(st.a) }
|
||||
if st.kind == S_IF {
|
||||
if st.c == null or not block_ends(st.b) { return false }
|
||||
if st.c.kind == S_IF { return stmt_ends(st.c) }
|
||||
|
|
@ -377,6 +378,7 @@ function emit_stmt(st: Node) -> void {
|
|||
if st.kind == S_SPAWN { let se = emit_spawn(st); return }
|
||||
if st.kind == S_DESPAWN { emit_despawn(st); return }
|
||||
if st.kind == S_TOGGLE { emit_toggle(st); return }
|
||||
if st.kind == S_UNSAFE { emit_block(st.a); return }
|
||||
if st.kind == S_ATTACH { emit_attach(st); return }
|
||||
if st.kind == S_DETACH { emit_detach(st); return }
|
||||
if st.kind == S_EMIT { let v = emit_emit(st); return } # statement form: discard the flag
|
||||
|
|
|
|||
|
|
@ -84,6 +84,37 @@ function ck_builtin(e: Node, name: pointer) -> pointer {
|
|||
ck_walk_args(e)
|
||||
return "int"
|
||||
}
|
||||
if (name == "view") and len(e.kids) == 3 {
|
||||
let vt = ck_expr(e.kids[0])
|
||||
ck_give("int", ck_expr(e.kids[1]), e.kids[1], "the start of a view")
|
||||
ck_give("int", ck_expr(e.kids[2]), e.kids[2], "the length of a view")
|
||||
if not ck_unknown(vt) and not is_slice_ty(vt) { ck_err("kind", e, `view takes a slice, and this is {ck_a(vt)}`) }
|
||||
return vt
|
||||
}
|
||||
if (name == "bytes") or (name == "offset") {
|
||||
ck_walk_args(e)
|
||||
return "pointer"
|
||||
}
|
||||
if (name == "data_of") {
|
||||
ck_walk_args(e)
|
||||
return "pointer"
|
||||
}
|
||||
if (name == "words") {
|
||||
ck_walk_args(e)
|
||||
return "[]int"
|
||||
}
|
||||
if (name == "buffer") {
|
||||
ck_walk_args(e)
|
||||
return "[]byte"
|
||||
}
|
||||
if (name == "fixeds") or (name == "pointers") {
|
||||
ck_walk_args(e)
|
||||
return "[]" + name[0 .. len(name) - 1]
|
||||
}
|
||||
if (name == "floats") or (name == "doubles") {
|
||||
ck_walk_args(e)
|
||||
return "[]" + name[0 .. len(name) - 1]
|
||||
}
|
||||
if (name == "float_from_bits") {
|
||||
ck_walk_args(e)
|
||||
return "float"
|
||||
|
|
@ -107,11 +138,20 @@ function ck_call(e: Node) -> pointer {
|
|||
let b = c.a
|
||||
if b.kind == E_ID and ck_local(b.s) < 0 and ck_global(b.s) == null {
|
||||
# an alias (L6) is its target, labels and all, so its arguments are checked in full
|
||||
if (b.s == "Memory") { ck_raw(e, `Memory.{c.s}`) }
|
||||
let al = ns_alias_find(b.s, c.s)
|
||||
if al >= 0 {
|
||||
var tf = ck_fn(g_al_target[al])
|
||||
if tf == null { tf = ck_extern(g_al_target[al]) }
|
||||
if tf != null { return ck_call_alias(e, `{b.s}.{c.s}`, al, tf) }
|
||||
if tf == null {
|
||||
tf = ck_extern(g_al_target[al])
|
||||
if tf != null { ck_raw(e, `{b.s}.{c.s}, a C function`) }
|
||||
}
|
||||
if tf != null {
|
||||
ck_extern_arg = ck_extern(g_al_target[al]) != null
|
||||
let at = ck_call_alias(e, `{b.s}.{c.s}`, al, tf)
|
||||
ck_extern_arg = false
|
||||
return at
|
||||
}
|
||||
ck_walk_args(e)
|
||||
return "?"
|
||||
}
|
||||
|
|
@ -141,6 +181,7 @@ function ck_call_named(e: Node, name: pointer) -> pointer {
|
|||
ck_walk_args(e)
|
||||
return "?"
|
||||
}
|
||||
if ck_raw_builtin(name) { ck_raw(e, `{name}()`) }
|
||||
let bt = ck_builtin(e, name)
|
||||
if bt != null { return bt }
|
||||
let f = ck_fn(name)
|
||||
|
|
@ -148,7 +189,13 @@ function ck_call_named(e: Node, name: pointer) -> pointer {
|
|||
let gt = gen_template(g_gen_fns, name)
|
||||
if gt != null { return gen_call(e, name, gt) }
|
||||
let x = ck_extern(name)
|
||||
if x != null { return ck_call_fn(e, name, x) }
|
||||
if x != null {
|
||||
ck_raw(e, `the C function {name}`)
|
||||
ck_extern_arg = true
|
||||
let xt = ck_call_fn(e, name, x)
|
||||
ck_extern_arg = false
|
||||
return xt
|
||||
}
|
||||
let g = ck_global(name)
|
||||
if g != null and is_fn_type(g.ty) { return ck_call_sig(e, name, g.ty) }
|
||||
ck_walk_args(e)
|
||||
|
|
|
|||
|
|
@ -53,5 +53,13 @@ function ck_err(cat: pointer, n: Node, msg: pointer) -> void {
|
|||
function ck_give(to: pointer, from: pointer, e: Node, what: pointer) -> void {
|
||||
let cat = ck_mismatch(to, from, e)
|
||||
if cat == null { return }
|
||||
if (cat == "slice-pointer") {
|
||||
ck_err(cat, e, `{what} wants a pointer and this is {ck_a(from)}: take the slice itself, or data_of(xs) in unsafe code`)
|
||||
return
|
||||
}
|
||||
if (cat == "pointer-slice") {
|
||||
ck_err(cat, e, `{what} wants {ck_a(to)} and this is a pointer: a slice is made with words(n), floats(n) or new, not from an address`)
|
||||
return
|
||||
}
|
||||
ck_err(cat, e, `{what} wants {ck_a(to)} and this is {ck_a(from)}`)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ function ck_any(n: Node) -> void {
|
|||
if n == null { return }
|
||||
if n.kind == N_BLOCK { ck_block(n); return }
|
||||
if n.kind == E_FINIT { ck_expr(n.a); return }
|
||||
if (n.kind >= S_LET and n.kind <= S_BECOME) or n.kind == S_EMIT { ck_stmt(n); return }
|
||||
if (n.kind >= S_LET and n.kind <= S_BECOME) or n.kind == S_EMIT or n.kind == S_UNSAFE { ck_stmt(n); return }
|
||||
ck_expr(n)
|
||||
}
|
||||
function ck_expr(e: Node) -> pointer {
|
||||
|
|
@ -125,6 +125,7 @@ function ck_member(e: Node) -> pointer {
|
|||
function ck_index_of(e: Node) -> pointer {
|
||||
let bt = ck_expr(e.a)
|
||||
let it = ck_expr(e.b)
|
||||
if ck_is_raw(bt) { ck_raw(e, `indexing {ck_a(bt)}`) }
|
||||
if not ck_unknown(it) and not ck_is_int(it) { ck_err("index", e.b, `an index wants an int and this is {ck_a(it)}`) }
|
||||
if ck_unknown(bt) { return "?" }
|
||||
if is_slice_ty(bt) { return slice_elem(bt) }
|
||||
|
|
|
|||
|
|
@ -107,6 +107,13 @@ function ck_stmt(s: Node) -> void {
|
|||
if k == S_EXPR { ck_expr(s.a); return }
|
||||
if k == S_MATCH { ck_match(s); return }
|
||||
if k == S_EMIT { ck_emit(s); return }
|
||||
if k == S_UNSAFE {
|
||||
ck_unsafe_here(s)
|
||||
ck_unsafe += 1
|
||||
ck_block(s.a)
|
||||
ck_unsafe -= 1
|
||||
return
|
||||
}
|
||||
}
|
||||
function ck_fn_body(d: Node) -> void {
|
||||
let m = ck_mark()
|
||||
|
|
@ -117,7 +124,12 @@ function ck_fn_body(d: Node) -> void {
|
|||
}
|
||||
ck_ret = d.ty
|
||||
if ck_ret == null { ck_ret = "void" }
|
||||
if d.uns == 1 {
|
||||
ck_unsafe_here(d)
|
||||
ck_unsafe += 1
|
||||
}
|
||||
ck_block(d.a)
|
||||
if d.uns == 1 { ck_unsafe -= 1 }
|
||||
ck_ret = "void"
|
||||
ck_pop(m)
|
||||
}
|
||||
|
|
@ -140,7 +152,7 @@ function check_program() -> void {
|
|||
gen_collect()
|
||||
ck_index()
|
||||
var i = 0
|
||||
while i < g_prog_user_end {
|
||||
while i < len(prog) {
|
||||
let d = prog[i]
|
||||
if d.kind == N_FN { ck_fn_body(d) }
|
||||
if d.kind == N_MAIN or d.kind == N_TEST { ck_block(d.a) }
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
# agrees with everything, so the checker only ever reports a mix-up it can prove. "null" is the
|
||||
# type of the null literal, and `pointer` is untyped: numbers, bools, text, records, slices and
|
||||
# functions are kept apart, and a pointer is trusted to be whatever it is given as.
|
||||
var ck_extern_arg: bool = false # an extern's arguments are being given: a slice goes as its data
|
||||
function ck_unknown(t: pointer) -> bool { return t == null or (t == "?") }
|
||||
function ck_is_int(t: pointer) -> bool {
|
||||
if (t == "int") or (t == "long") or (t == "byte") or (t == "i64") or (t == "u8") { return true }
|
||||
|
|
@ -72,6 +73,17 @@ function ck_mismatch(to: pointer, from: pointer, e: Node) -> pointer {
|
|||
if ck_is_num(from) { return "kind" }
|
||||
# `pointer` is the untyped reference, C's void *: it goes anywhere a reference does, and a
|
||||
# reference goes into it. What it may hold is L7's question (unsafe), not this pass's.
|
||||
# `bytes` is a raw buffer, a pointer by another name: the same rules
|
||||
if (to == "bytes") and is_slice_ty(from) {
|
||||
if ck_extern_arg { return null }
|
||||
return "slice-pointer"
|
||||
}
|
||||
if (from == "bytes") and is_slice_ty(to) { return "pointer-slice" }
|
||||
if (to == "pointer") and is_slice_ty(from) {
|
||||
if ck_extern_arg { return null }
|
||||
return "slice-pointer"
|
||||
}
|
||||
if (from == "pointer") and is_slice_ty(to) { return "pointer-slice" }
|
||||
if (to == "pointer") or (from == "pointer") { return null }
|
||||
if (to == "string") { return "kind" }
|
||||
if ck_is_rec(to) and ck_is_rec(from) { return "record" }
|
||||
|
|
|
|||
29
selfhost/check/check_unsafe.ludic
Normal file
29
selfhost/check/check_unsafe.ludic
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
# check_unsafe.ludic — L7: raw memory is `unsafe`. A bytes() buffer, indexing a bare pointer,
|
||||
# data_of(a slice), free, resize, the raw file calls, Memory.* and calling a C function (an
|
||||
# `extern`) are refused outside an `unsafe { }` block or an `unsafe function`. The typed buffers -
|
||||
# words(n), floats(n) - are slices, bounds-checked, and need none of it.
|
||||
# And `unsafe` itself is only for the files that are the platform: the runtime, a package the
|
||||
# toolchain or ludic_modules provides, and what they import beside them - a project's own files
|
||||
# may write it only when the build says --unsafe. A game is written against APIs, not memory.
|
||||
var ck_unsafe: int = 0 # how many unsafe blocks and functions enclose this point
|
||||
|
||||
# words(n), floats(n) and the rest are slices now - bounds-checked, safe - so what is raw is a
|
||||
# bare pointer indexed, and the builtins that hand out or take back addresses
|
||||
function ck_is_raw(t: pointer) -> bool { return (t == "pointer") or (t == "bytes") }
|
||||
function ck_raw_builtin(name: pointer) -> bool {
|
||||
if (name == "bytes") or (name == "data_of") or (name == "free") or (name == "resize") { return true }
|
||||
return (name == "file_read") or (name == "file_write")
|
||||
}
|
||||
# the platform - the runtime and the packages - is raw memory by trade: its files are unsafe
|
||||
# throughout, and the rule is for a project's own code
|
||||
function ck_raw(n: Node, what: pointer) -> void {
|
||||
if ck_unsafe > 0 { return }
|
||||
if n != null and n.file != null and unsafe_trusted(n.file) { return }
|
||||
ck_err("unsafe", n, `{what} is raw memory: it belongs inside unsafe {{ }}, and a game reaches it through an API`)
|
||||
}
|
||||
# an unsafe block or function where the file may not have one
|
||||
function ck_unsafe_here(n: Node) -> void {
|
||||
if n == null or n.file == null { return }
|
||||
if unsafe_trusted(n.file) { return }
|
||||
ck_err("unsafe-block", n, "unsafe is for the runtime and packages; this file may use it only when the build says --unsafe")
|
||||
}
|
||||
|
|
@ -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() {
|
||||
|
|
|
|||
66406
selfhost/ludicc.seed.ll
66406
selfhost/ludicc.seed.ll
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -147,6 +147,7 @@ entry {
|
|||
else if a == "--windowed" { want = 1 }
|
||||
else if a == "--headless" { want = 2 }
|
||||
else if a == "--emit-llvm" { emit_ir = true }
|
||||
else if a == "--unsafe" { g_unsafe_all = true } # L7: this program's own files may write `unsafe`
|
||||
else if a == "--emit-module" { g_emit_module = true; emit_ir = true } # issue #64: prebuilt binary module IR
|
||||
else if a == "--fmt" { fmt = true }
|
||||
else if a == "--save-temps" { save = true }
|
||||
|
|
|
|||
|
|
@ -47,17 +47,17 @@ function compile_app(src: pointer, out: pointer, mode: int, save: bool) -> bool
|
|||
var flags = "--windowed"
|
||||
if mode == 2 { flags = "--headless" }
|
||||
if save { flags = flags + " --save-temps" }
|
||||
return shq(`{ludicc()} {flags}{title_flag()} {src} -o {out}`)
|
||||
return shq(`{ludicc()} {flags}{title_flag()}{unsafe_flag()} {src} -o {out}`)
|
||||
}
|
||||
|
||||
if mode == 2 {
|
||||
if not shq(`{ludicc()} --headless{title_flag()} {src} --emit-llvm -o {ll}`) { return false }
|
||||
if not shq(`{ludicc()} --headless{title_flag()}{unsafe_flag()} {src} --emit-llvm -o {ll}`) { return false }
|
||||
if not shq(`{cc()} -O2 {ll}{gl_link_flags(ll)}{vk_link_flags(ll)}{http_link_flags(ll)}{udp_link_flags(ll)}{process_link_flags(ll)}{threads_link_flags(ll)}{pbf} -o {out}`) { return false }
|
||||
if not save { shell(`rm -f {ll}`) }
|
||||
return true
|
||||
}
|
||||
|
||||
if not shq(`{ludicc()} --windowed{title_flag()} {src} --emit-llvm -o {ll}`) { return false }
|
||||
if not shq(`{ludicc()} --windowed{title_flag()}{unsafe_flag()} {src} --emit-llvm -o {ll}`) { return false }
|
||||
# audio.ll (#22) is always linked here — unused snd_* are dead-stripped; the
|
||||
# canonical `ludicc -o` path links it only when Audio.* is used.
|
||||
let cocoa = `{home}runtime/native/cocoa.ll`
|
||||
|
|
@ -70,6 +70,12 @@ function compile_app(src: pointer, out: pointer, mode: int, save: bool) -> bool
|
|||
# ` --title "<app name>"` when the project's package.ludic names the app, so the window
|
||||
# opens under that name rather than the `program` name; "" otherwise, and for a name the
|
||||
# shell line could not carry safely.
|
||||
# ` --unsafe` when the build said so: the project's own files may write `unsafe` (L7)
|
||||
var g_unsafe_build: bool = false
|
||||
function unsafe_flag() -> pointer {
|
||||
if g_unsafe_build { return " --unsafe" }
|
||||
return ""
|
||||
}
|
||||
function title_flag() -> pointer {
|
||||
let name = manifest_app(read_root_manifest(), "name")
|
||||
if name == "" { return "" }
|
||||
|
|
|
|||
|
|
@ -275,6 +275,7 @@ function parse_build_args(start: int) -> pointer {
|
|||
if a == "--headless" { g_mode = 2 }
|
||||
else if a == "--windowed" { g_mode = 1 }
|
||||
else if a == "--save-temps" { g_save = true }
|
||||
else if a == "--unsafe" { g_unsafe_build = true }
|
||||
else if a == "-o" {
|
||||
ai += 1
|
||||
if ai < arg_count() { g_out = arg(ai) }
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ function selfhost_frags() -> []pointer {
|
|||
push(f, "selfhost/check/check_stmt.ludic")
|
||||
push(f, "selfhost/check/check_gen.ludic")
|
||||
push(f, "selfhost/check/check_gen_call.ludic")
|
||||
push(f, "selfhost/check/check_unsafe.ludic")
|
||||
push(f, "selfhost/backend/emit_stmt.ludic")
|
||||
push(f, "selfhost/backend/game/emit_ecs.ludic")
|
||||
push(f, "selfhost/backend/game/emit_query.ludic")
|
||||
|
|
@ -116,7 +117,7 @@ function cmd_selfhost_build(lc: pointer, outbin: pointer) -> int {
|
|||
if not write_selfhost_src(src) { err("ludic-dev: cannot write build/selfhost.ludic\n"); return 1 }
|
||||
let ll = `{outbin}.ll`
|
||||
# say why when it fails: it used to exit 1 with nothing on the screen
|
||||
if not shq(`{lc} {src} > {ll} 2>{tmp_dir()}/shb.err`) {
|
||||
if not shq(`{lc} --unsafe {src} > {ll} 2>{tmp_dir()}/shb.err`) {
|
||||
shell(`rm -f {ll}`)
|
||||
err(capture(`grep -i error {tmp_dir()}/shb.err | head -5`))
|
||||
return 1
|
||||
|
|
@ -142,9 +143,16 @@ function cmd_sh_compile(shbin: pointer, in: pointer, outbin: pointer) -> int {
|
|||
|
||||
# the quiet core, reused by the test suites; returns true on success. On failure
|
||||
# the self-host/link diagnostics are left in tmp_path("gb.err").
|
||||
# an example that is a raw-memory program on purpose (a decoder's ABI, a Vulkan demo) is built
|
||||
# with --unsafe (L7); the suite sets this around those cases only
|
||||
var g_case_unsafe: bool = false
|
||||
function case_unsafe_flag() -> pointer {
|
||||
if g_case_unsafe { return " --unsafe" }
|
||||
return ""
|
||||
}
|
||||
function game_build_ok(shbin: pointer, game: pointer, outbin: pointer) -> bool {
|
||||
let ll = `{outbin}.ll`
|
||||
if not shq(`{shbin} {game} > {ll} 2>{tmp_dir()}/gb.err`) { return false }
|
||||
if not shq(`{shbin}{case_unsafe_flag()} {game} > {ll} 2>{tmp_dir()}/gb.err`) { return false }
|
||||
if not shq(`{cc()} -O2 {ll}{threads_link_flags(ll)} -o {outbin} 2>{tmp_dir()}/gb.err`) { return false }
|
||||
shell(`rm -f {ll}`)
|
||||
return true
|
||||
|
|
@ -168,11 +176,11 @@ function cmd_bootstrap() -> int {
|
|||
if cmd_selfhost_build("bin/ludicc", "build/boot/gen1") != 0 { print("FAIL: stage0 build"); return 1 }
|
||||
print(" stage0: bin/ludicc -> gen1 (self-host compiler)")
|
||||
|
||||
if not shq("build/boot/gen1 build/selfhost.ludic > build/boot/gen2.ll 2>/dev/null") { print("FAIL: gen1 self-compile"); return 1 }
|
||||
if not shq("build/boot/gen1 --unsafe build/selfhost.ludic > build/boot/gen2.ll 2>/dev/null") { print("FAIL: gen1 self-compile"); return 1 }
|
||||
if not shq(`{cc()} build/boot/gen2.ll -o build/boot/gen2 2>/dev/null`) { print("FAIL: gen2 assemble"); return 1 }
|
||||
print(` stage1: gen1 -> gen2.ll ({line_count("build/boot/gen2.ll")} lines) -> gen2`)
|
||||
|
||||
if not shq("build/boot/gen2 build/selfhost.ludic > build/boot/gen3.ll 2>/dev/null") { print("FAIL: gen2 self-compile"); return 1 }
|
||||
if not shq("build/boot/gen2 --unsafe build/selfhost.ludic > build/boot/gen3.ll 2>/dev/null") { print("FAIL: gen2 self-compile"); return 1 }
|
||||
print(` stage2: gen2 -> gen3.ll ({line_count("build/boot/gen3.ll")} lines)`)
|
||||
|
||||
if shq("cmp -s build/boot/gen2.ll build/boot/gen3.ll") {
|
||||
|
|
@ -194,7 +202,7 @@ function cmd_bootstrap_cfree() -> int {
|
|||
if not shq(`{cc()} selfhost/ludicc.seed.ll -o build/cfree/sh_seed 2>/dev/null`) { print("FAIL: assemble seed"); return 1 }
|
||||
print(" seed.ll --clang--> sh_seed (no C compiler used)")
|
||||
if not write_selfhost_src("build/cfree/selfhost.ludic") { print("FAIL: write source"); return 1 }
|
||||
if not shq("build/cfree/sh_seed build/cfree/selfhost.ludic > build/cfree/out.ll 2>/dev/null") { print("FAIL: seed compiler self-compile"); return 1 }
|
||||
if not shq("build/cfree/sh_seed --unsafe build/cfree/selfhost.ludic > build/cfree/out.ll 2>/dev/null") { print("FAIL: seed compiler self-compile"); return 1 }
|
||||
print(` sh_seed compiles selfhost.ludic -> out.ll ({line_count("build/cfree/out.ll")} lines)`)
|
||||
if shq("cmp -s build/cfree/out.ll selfhost/ludicc.seed.ll") {
|
||||
print(" out.ll == seed.ll — the compiler rebuilds itself with no C compiler")
|
||||
|
|
@ -214,17 +222,17 @@ function cmd_reseed() -> int {
|
|||
shell("mkdir -p build/cfree")
|
||||
if not write_selfhost_src("build/cfree/selfhost.ludic") { print("FAIL: write source"); return 1 }
|
||||
if shq(`{cc()} selfhost/ludicc.seed.ll -o build/cfree/sh_old 2>/dev/null`) {
|
||||
if not shq("build/cfree/sh_old build/cfree/selfhost.ludic > build/cfree/step1.ll") { print("FAIL: step1"); return 1 }
|
||||
if not shq("build/cfree/sh_old --unsafe build/cfree/selfhost.ludic > build/cfree/step1.ll") { print("FAIL: step1"); return 1 }
|
||||
if not shq(`{cc()} build/cfree/step1.ll -o build/cfree/sh_new`) { print("FAIL: assemble sh_new"); return 1 }
|
||||
if not shq("build/cfree/sh_new build/cfree/selfhost.ludic > selfhost/ludicc.seed.ll") { print("FAIL: reseed"); return 1 }
|
||||
if not shq("build/cfree/sh_new --unsafe build/cfree/selfhost.ludic > selfhost/ludicc.seed.ll") { print("FAIL: reseed"); return 1 }
|
||||
# and the Windows seed, from the same compiler: a Windows checkout bootstraps from it
|
||||
if not shq("build/cfree/sh_new --target x86_64-pc-windows-msvc build/cfree/selfhost.ludic > selfhost/ludicc.win.seed.ll") { print("FAIL: reseed (windows)"); return 1 }
|
||||
if not shq("build/cfree/sh_new --unsafe --target x86_64-pc-windows-msvc build/cfree/selfhost.ludic > selfhost/ludicc.win.seed.ll") { print("FAIL: reseed (windows)"); return 1 }
|
||||
} else {
|
||||
print("seed does not build; reseeding from bin/ludicc")
|
||||
ensure_ludicc()
|
||||
if cmd_selfhost_build("bin/ludicc", "build/cfree/sh_c") != 0 { print("FAIL: build from bin/ludicc"); return 1 }
|
||||
if not shq("build/cfree/sh_c build/cfree/selfhost.ludic > selfhost/ludicc.seed.ll") { print("FAIL: reseed"); return 1 }
|
||||
if not shq("build/cfree/sh_c --target x86_64-pc-windows-msvc build/cfree/selfhost.ludic > selfhost/ludicc.win.seed.ll") { print("FAIL: reseed (windows)"); return 1 }
|
||||
if not shq("build/cfree/sh_c --unsafe build/cfree/selfhost.ludic > selfhost/ludicc.seed.ll") { print("FAIL: reseed"); return 1 }
|
||||
if not shq("build/cfree/sh_c --unsafe --target x86_64-pc-windows-msvc build/cfree/selfhost.ludic > selfhost/ludicc.win.seed.ll") { print("FAIL: reseed (windows)"); return 1 }
|
||||
}
|
||||
print(`reseeded: {line_count("selfhost/ludicc.seed.ll")} lines`)
|
||||
return 0
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ function sh_case(name: pointer, exp: pointer) -> void {
|
|||
let ll = `{tmp_dir()}/sh_{name}.ll`
|
||||
let er = `{tmp_dir()}/sh_{name}.err`
|
||||
let bn = `{tmp_dir()}/sh_{name}`
|
||||
if not shq(`bin/ludicc selfhost/tests/{name}.ludic > {ll} 2>{er}`) {
|
||||
if not shq(`bin/ludicc --unsafe selfhost/tests/{name}.ludic > {ll} 2>{er}`) {
|
||||
bad2(`{name}: self-host errored`, capture_line(`head -1 {er}`)); return
|
||||
}
|
||||
if not shq(`{cc()} {ll} -o {bn} 2>/dev/null`) { bad(`{name}: IR did not assemble`); return }
|
||||
|
|
|
|||
|
|
@ -477,7 +477,7 @@ function net_case(path: pointer, exp: pointer) -> void {
|
|||
let nm = flat(path)
|
||||
let ll = `{tmp_dir()}/n_{nm}.ll`
|
||||
let log = `{tmp_dir()}/n_{nm}.out`
|
||||
if not shq(`bin/ludicc --headless examples/{path}.ludic --emit-llvm -o {ll} > {log} 2>&1`) {
|
||||
if not shq(`bin/ludicc{case_unsafe_flag()} --headless examples/{path}.ludic --emit-llvm -o {ll} > {log} 2>&1`) {
|
||||
let t = capture_line(`tail -1 {log}`)
|
||||
bad2(path, `build ({t})`); return
|
||||
}
|
||||
|
|
@ -647,7 +647,9 @@ function cmd_dev_test() -> int {
|
|||
feat_case("library/query", "", "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18", "query.ludic (Query count/first/nearest/within — ECS spatial queries over the reflection ABI)")
|
||||
feat_case("library/reflect", "", "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20", "reflect.ludic (Reflect prop/field enumeration + type + get/set/has/kind — runtime reflection over the world schema)")
|
||||
feat_case("library/serialize", "", "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16", "serialize.ludic (Value tree + Json encode/parse + Reflect.serialize/apply — bit-exact save/load; issue #44)")
|
||||
g_case_unsafe = true # it drives the decoders' raw buffers
|
||||
feat_case("library/tiled_p0", "", "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21", "tiled_p0.ludic (Tiled P0: Xml reader + Base64 decode/encode + gzip framing over inflate; issue #67)")
|
||||
g_case_unsafe = false
|
||||
feat_case("library/tiled_p05", "", "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30", "tiled_p05.ludic (Tiled P0.5: TMX/TSX reader -> same Value-tree intermediate as TMJ; Kenney sampleMap.tmx; issue #68)")
|
||||
feat_case("library/tiled_p1", "", "1 2 3 4 5 6 7 8 9 10 11 12 13 14", "tiled_p1.ludic (Tiled P1: rt_tmap model + GID resolver/flip decode + render; loads+draws Kenney map identically from .tmx and .tmj; issue #69)")
|
||||
feat_case("library/tiled_p2", "", "1 2 3 4 5 6 7 8 9 10 11 12 13 14", "tiled_p2.ludic (Tiled P2: per-tile objectgroup / property-convention / collision-layer -> Solids feed; A* baseline; issue #70)")
|
||||
|
|
@ -700,8 +702,10 @@ function cmd_dev_test() -> int {
|
|||
# asserted from its own `entry`. The transport is the compiler's built-in
|
||||
# loopback, so a networked game runs with zero foreign code.
|
||||
net_case("events/mod_events", "10 32 42")
|
||||
g_case_unsafe = true # the transport ABI takes raw bytes
|
||||
net_case("networking/net_echo", "4 10 20 30 42")
|
||||
net_case("networking/net_snapshot", "50 7 50")
|
||||
g_case_unsafe = false
|
||||
net_case("networking/net_sync", "12 3 4 50 999")
|
||||
net_case("networking/net_owner", "-1 7 0 1")
|
||||
net_case("networking/net_rpc", "0 8")
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
function build_tool(name: pointer, src: pointer) -> bool {
|
||||
let ll = `build/{name}.ll`
|
||||
let out = `bin/{exe_name(name)}`
|
||||
if not shq(`bin/{exe_name("ludicc")} {src} --emit-llvm -o {ll} 2>/dev/null`) { print(`build failed: {name} (compile)`); return false }
|
||||
if not shq(`bin/{exe_name("ludicc")} --unsafe {src} --emit-llvm -o {ll} 2>/dev/null`) { print(`build failed: {name} (compile)`); return false }
|
||||
# write to a temp then move, so a running bin/ludic can rebuild itself in place
|
||||
# (Windows lets a running executable be renamed, never replaced, so the old one
|
||||
# steps aside first)
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ property VkMap { keys: pointers, vals: words, cap: int = 0, count: int = 0 }
|
|||
function vkm_new() -> VkMap {
|
||||
let m = new VkMap
|
||||
m.cap = 16384
|
||||
m.keys = bytes(m.cap * 8)
|
||||
m.keys = pointers(m.cap)
|
||||
for i in 0 .. m.cap { m.keys[i] = null }
|
||||
m.vals = words(m.cap)
|
||||
return m
|
||||
|
|
@ -45,7 +45,7 @@ function vkm_slot(m: VkMap, key: pointer) -> int {
|
|||
function vkm_grow(m: VkMap) -> void {
|
||||
let ok = m.keys; let ov = m.vals; let oc = m.cap
|
||||
m.cap = oc * 2
|
||||
m.keys = bytes(m.cap * 8)
|
||||
m.keys = pointers(m.cap)
|
||||
for i in 0 .. m.cap { m.keys[i] = null }
|
||||
m.vals = words(m.cap)
|
||||
m.count = 0
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue