Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions modules/dasLLVM/daslib/llvm_jit_run.das
Original file line number Diff line number Diff line change
Expand Up @@ -756,6 +756,8 @@ def public run_jit(prog : Program?; var ctx : Context?; cli_opts : JitCliOptions
if (!sres.main_ok) {
reset_codegen_accumulators()
finalize_jit(use_dll, gen_exe, g_dynamic_lib_handle)
to_log(LOG_ERROR, "LLVM EXE: no executable written for {output_path}\n")
run.ok = false
return run
}
LINK_WHOLE_LIB = sres.link_whole_lib
Expand Down Expand Up @@ -806,8 +808,8 @@ def public run_jit(prog : Program?; var ctx : Context?; cli_opts : JitCliOptions
di_finalize()
reset_codegen_accumulators()
finalize_jit(use_dll, gen_exe, g_dynamic_lib_handle)
// JIT failed. Couldn't create executable.
// Still return true, codegen ran correctly.
to_log(LOG_ERROR, "LLVM EXE: no executable written for {output_path}\n")
run.ok = false
return run
}
}
Expand Down
9 changes: 5 additions & 4 deletions modules/dasLLVM/tests/_exe_thread_collect_root.das
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ require daslib/jobque_boost

// A thread lambda's capture outlives a collect the thread runs: the capture block's header names
// the capture's type, so the root walk reaches the captured array where a headerless block
// would have the walk read a null type.
// would have the walk read a null type. Each collect runs in the thread's or the job's own context,
// on a stack the host starts empty - the case `[unsafe_heap_collect]` exists for.

[export]
[export, unsafe_heap_collect]
def main {
var data <- [for (i in range(64)); i]
with_channel(1) $(ch) {
new_thread() <| @capture(:= data) {
new_thread() <| @ [unsafe_heap_collect] capture(:= data) {
unsafe(heap_collect(false, true))
let churn <- [for (i in range(4096)); i]
var sum = 0
Expand All @@ -28,7 +29,7 @@ def main {
var jdata <- [for (i in range(32)); i * 2]
with_job_que() {
with_channel(1) $(ch) {
new_job() <| @capture(:= jdata) {
new_job() <| @ [unsafe_heap_collect] capture(:= jdata) {
unsafe(heap_collect(false, true))
let churn <- [for (i in range(4096)); i]
var sum = 0
Expand Down
4 changes: 4 additions & 0 deletions modules/dasTerminal/tests/ownership_semantics.das
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ require strings
[export]
def main {
unsafe(heap_collect(true, true))
terminal_releases_owned_rows()
}

def terminal_releases_owned_rows {
var inscope terminal <- terminal_create(240, 100)
let live_grid_bytes = heap_bytes_allocated()

Expand Down
4 changes: 3 additions & 1 deletion tests/strudel/test_vowel.das
Original file line number Diff line number Diff line change
Expand Up @@ -156,12 +156,14 @@ def test_vowel_filter_tick_produces_output(t : T?) {
vowel_filter_init(vf, "a")
var seed = 12345u
var maxAbs = 0.0
var energy = 0.0
for (_i in range(4800)) {
let inp = noise(seed)
let out = vowel_filter_tick(vf, inp)
maxAbs = max(maxAbs, abs(out))
energy += out * out
}
t |> success(maxAbs > 0.1)
t |> success(maxAbs > 0.1, "peak {maxAbs}, energy {energy} over 4800 samples - this seed peaks near 0.28")
}

[test]
Expand Down
40 changes: 40 additions & 0 deletions tests/strudel_device/_strudel_device_common.das
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
options gen2
options indenting = 4

module _strudel_device_common public

require strudel/strudel_player
require daslib/fio

//! A worker whose clock stands still this long has stalled. A slow worker - a sanitizer build on a busy runner - is not a failure.
let WORKER_STALL_MS = 30000
let private POLL_MS = 20u

//! The playback worker's own clock: seconds of audio it has rendered, so it slows down with the worker.
def worker_seconds() : double {
return strudel_get_playback_time()._0
}

//! Polls until the worker's clock reaches `until`, running `each_poll` after every poll; false when the clock stands still for WORKER_STALL_MS of real time first.
def wait_worker_until(until : double; each_poll : block<() : void>) : bool {
var clock = worker_seconds()
var moved = ref_time_ticks()
while (clock < until) {
sleep(POLL_MS)
invoke(each_poll)
let now = worker_seconds()
if (now > clock) {
clock = now
moved = ref_time_ticks()
} elif (get_time_usec(moved) >= WORKER_STALL_MS * 1000) {
return false
}
}
return true
}

def wait_worker_until(until : double) : bool {
return wait_worker_until(until) $ {
pass
}
}
7 changes: 4 additions & 3 deletions tests/strudel_device/test_sound_status_seqbox.das
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ def private short_tone(seconds : float) : array<float> {

[test]
def test_status_seeded_on_the_calling_thread(t : T?) {
t |> run("the box reads `starting` before the audio thread has seen the sound") @(t : T?) {
t |> run("the box reads the new sound's state the instant set_status_update returns") @(t : T?) {
sound_set_null_device(true)
with_audio_system() {
var tone <- short_tone(0.2)
var tone <- short_tone(10.0)
let sid = play_sound_from_pcm(MA_SAMPLE_RATE, 1, tone)
var box = seq_box_create()
set_status_update(sid, box)
Expand All @@ -37,7 +37,8 @@ def test_status_seeded_on_the_calling_thread(t : T?) {
state = s.state
}
t |> equal(got, true, "the box must not read empty right after registration")
t |> equal(state, AudioChannelState.starting)
t |> success(state == AudioChannelState.starting || state == AudioChannelState.playing,
"the seeded `starting`, or `playing` once the audio thread has picked the sound up: {state}")
stop(sid)
unset_status_update(sid)
box |> seq_box_release
Expand Down
36 changes: 10 additions & 26 deletions tests/strudel_device/test_worker_heap.das
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ require dastest/testing_boost
require strudel/strudel
require strudel/strudel_player
require audio/audio_boost
require daslib/fio
require _strudel_device_common

// The playback worker's context is its own: nothing else collects it, and a bed that queries
// four patterns every tick leaves megabytes of garbage a second behind. The number the worker
Expand All @@ -27,7 +27,6 @@ let WARM_UP_SECONDS = 6.0lf
let STRETCH_SECONDS = 3.0lf
let MIN_DROPS = 6 // an idle 64-core box reads ~28 per two stretches
let FLOOR_DRIFT_LIMIT = 4ul * 1024ul * 1024ul // twice the worker's collect window; uncollected grows ~10 MB a stretch
let WALL_CAP_MS = 90000u // a worker that never advances is its own failure

[unsafe_heap_collect]
def worker_setup() {
Expand All @@ -39,35 +38,20 @@ def worker_setup() {
strudel_play()
}

def worker_seconds() : double {
return strudel_get_playback_time()._0
}

// Sleeps until the worker's clock reaches `until`, or the wall cap passes; false on the cap.
def wait_worker_until(until : double; var wall_ms : uint&) : bool {
while (worker_seconds() < until) {
return false if (wall_ms >= WALL_CAP_MS)
sleep(20u)
wall_ms += 20u
}
return true
}

struct Stretch {
samples : int // distinct published numbers seen; a stretch with none measured nothing
lo : uint64
drops : int
finished : bool // false: the worker's clock stalled before the stretch ended
}

// The least published heap over one stretch of worker time, and how many times a tick published
// less than the tick before it; `prev` carries the last number across stretches.
def sample_stretch(until : double; var prev : uint64&; var wall_ms : uint&) : Stretch {
def sample_stretch(until : double; var prev : uint64&) : Stretch {
var st = Stretch(lo = 0xFFFFFFFFFFFFFFFFul)
while (worker_seconds() < until && wall_ms < WALL_CAP_MS) {
sleep(20u)
wall_ms += 20u
st.finished = wait_worker_until(until) $ {
let h = strudel_get_worker_heap_bytes()
continue if (h == prev) // the same tick as last time
return if (h == prev) // the same tick as last time
if (prev != 0ul && h < prev) { st.drops++ }
prev = h
st.lo = min(st.lo, h)
Expand All @@ -82,14 +66,14 @@ def test_worker_heap_stays_flat(t : T?) {
sound_set_null_device(true)
with_audio_system() {
strudel_init(@@worker_setup)
var wall_ms = 0u
let warm = wait_worker_until(WARM_UP_SECONDS, wall_ms)
t |> success(warm, "the worker reached {WARM_UP_SECONDS} s of pattern time inside {WALL_CAP_MS} ms of wall time")
let warm = wait_worker_until(WARM_UP_SECONDS)
t |> success(warm, "the worker reached {WARM_UP_SECONDS} s of pattern time without its clock standing still for {WORKER_STALL_MS} ms")
if (warm) {
var prev = 0ul
let w0 = worker_seconds()
let early = sample_stretch(w0 + STRETCH_SECONDS, prev, wall_ms)
let late = sample_stretch(w0 + 2.0lf * STRETCH_SECONDS, prev, wall_ms)
let early = sample_stretch(w0 + STRETCH_SECONDS, prev)
let late = sample_stretch(w0 + 2.0lf * STRETCH_SECONDS, prev)
t |> success(early.finished && late.finished, "the worker's clock reached {worker_seconds()} s of {w0 + 2.0lf * STRETCH_SECONDS} without standing still for {WORKER_STALL_MS} ms")
// a stretch that saw no published number leaves `lo` at its sentinel, which would
// pass a bare `lo > 0`; the sample counts are what say the worker published at all
t |> success(early.samples > 0 && late.samples > 0, "the worker publishes its heap: {early.samples} then {late.samples} distinct numbers")
Expand Down
7 changes: 4 additions & 3 deletions tests/strudel_device/test_worker_no_gc.das
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ require dastest/testing_boost
require strudel/strudel_mini
require strudel/strudel_player
require audio/audio_boost
require daslib/fio
require _strudel_device_common

// A program without `options gc` and `options persistent_heap` cannot collect its worker's
// heap: the probe collect at the worker's start throws, the arm logs one warning and keeps the
Expand All @@ -27,10 +27,11 @@ def test_worker_without_gc_keeps_playing(t : T?) {
sound_set_null_device(true)
with_audio_system() {
strudel_init(@@worker_setup)
sleep(2500u)
let played = wait_worker_until(2.5lf)
let a = strudel_get_worker_heap_bytes()
sleep(1500u)
let played_on = played && wait_worker_until(worker_seconds() + 1.5lf)
let b = strudel_get_worker_heap_bytes()
t |> success(played_on, "the worker's clock reached {worker_seconds()} s without standing still for {WORKER_STALL_MS} ms")
t |> success(a > 0ul, "the worker publishes its heap")
t |> success(b >= a, "with no collect the heap only grows: {int64(a) / 1024l} KB then {int64(b) / 1024l} KB")
strudel_shutdown()
Expand Down
Loading