diff --git a/modules/dasLLVM/daslib/llvm_jit_run.das b/modules/dasLLVM/daslib/llvm_jit_run.das index eb897f121e..37f0f67385 100644 --- a/modules/dasLLVM/daslib/llvm_jit_run.das +++ b/modules/dasLLVM/daslib/llvm_jit_run.das @@ -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 @@ -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 } } diff --git a/modules/dasLLVM/tests/_exe_thread_collect_root.das b/modules/dasLLVM/tests/_exe_thread_collect_root.das index 8a4bca66f8..283774ed28 100644 --- a/modules/dasLLVM/tests/_exe_thread_collect_root.das +++ b/modules/dasLLVM/tests/_exe_thread_collect_root.das @@ -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 @@ -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 diff --git a/modules/dasTerminal/tests/ownership_semantics.das b/modules/dasTerminal/tests/ownership_semantics.das index 86f12bd286..56dc58c6b5 100644 --- a/modules/dasTerminal/tests/ownership_semantics.das +++ b/modules/dasTerminal/tests/ownership_semantics.das @@ -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() diff --git a/tests/strudel/test_vowel.das b/tests/strudel/test_vowel.das index 56dfb342d3..05a16bb129 100644 --- a/tests/strudel/test_vowel.das +++ b/tests/strudel/test_vowel.das @@ -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] diff --git a/tests/strudel_device/_strudel_device_common.das b/tests/strudel_device/_strudel_device_common.das new file mode 100644 index 0000000000..3360ec1c42 --- /dev/null +++ b/tests/strudel_device/_strudel_device_common.das @@ -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 + } +} diff --git a/tests/strudel_device/test_sound_status_seqbox.das b/tests/strudel_device/test_sound_status_seqbox.das index 66bacd6d14..3df89b2d3b 100644 --- a/tests/strudel_device/test_sound_status_seqbox.das +++ b/tests/strudel_device/test_sound_status_seqbox.das @@ -25,10 +25,10 @@ def private short_tone(seconds : float) : array { [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) @@ -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 diff --git a/tests/strudel_device/test_worker_heap.das b/tests/strudel_device/test_worker_heap.das index 86aca83ef7..5e658aaf27 100644 --- a/tests/strudel_device/test_worker_heap.das +++ b/tests/strudel_device/test_worker_heap.das @@ -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 @@ -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() { @@ -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) @@ -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") diff --git a/tests/strudel_device/test_worker_no_gc.das b/tests/strudel_device/test_worker_no_gc.das index 3a1325852e..8d30890ffc 100644 --- a/tests/strudel_device/test_worker_no_gc.das +++ b/tests/strudel_device/test_worker_no_gc.das @@ -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 @@ -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()