kernel: Complete preemptible runtime loading

fa787cc52463bb0996aa77aa3441f223fdc8704ff180f76b89ba047c8a9141e4
Alexis Sellier committed ago 1 parent 5e7ead57
Makefile +7 -1
21 21
22 22
# Verify the emulator binary exists.
23 23
EMU_PATH := $(shell command -v $(EMU) 2>/dev/null)
24 24
25 25
default: emulator $(RAD_BIN)
26 -
test: emulator seed-test std-test bin-test kernel-test module-test package-test native-test shared-test sync-test kernel-boot-test trap-test page-test loader-test dispatch-test smp-test termination-test
26 +
test: emulator seed-test std-test bin-test kernel-test module-test package-test native-test shared-test sync-test kernel-boot-test trap-test page-test loader-test dispatch-test smp-test termination-test runtime-test
27 27
28 28
seed-test:
29 29
	@seed/test
30 30
31 31
module-test: emulator $(RAD_BIN)
220 220
# User exit, abort, and CPU faults preserve the surviving parent's execution.
221 221
termination-test: emulator $(RAD_BIN) $(BIN_DIR)/kernel.build.rv64
222 222
	@RAD_EMULATOR="$(EMU)" sh test/dispatch/run termination
223 223
224 224
.PHONY: termination-test
225 +
226 +
# Scheduled compilation, cancellation, retry, and shared executable publication.
227 +
runtime-test: emulator $(RAD_BIN) $(BIN_DIR)/kernel.build.rv64
228 +
	@RAD_EMULATOR="$(EMU)" sh test/runtime/run
229 +
230 +
.PHONY: runtime-test
kernel/kernel/frames.rad +68 -14
21 21
22 22
/// Physical addresses and occupancy of allocatable frames, sorted by address.
23 23
export record Pool: Copy {
24 24
    /// Physical base address of each eligible frame.
25 25
    addresses: [u64; limits::FRAMES],
26 -
    /// True when the frame is available for reservation.
27 -
    free: [bool; limits::FRAMES],
26 +
    /// One set bit per available frame, indexed from the low bit of each word.
27 +
    free: [u64; limits::FRAMES / 64],
28 28
    /// Number of valid frame-map entries.
29 29
    count: u32,
30 30
    /// Retired runs whose frames remain unavailable until reclamation.
31 31
    retired: [Run; limits::FRAMES],
32 32
    /// Index of the next retired run to reclaim.
46 46
47 47
/// Build a fresh frame map from complete RAM pages with no reserved byte.
48 48
/// The pool has zero valid entries if initialization fails.
49 49
export unsafe fn initialize(pool: &mut Pool, machine: &platform::Platform) throws (abi::Error) {
50 50
    set pool.count = 0;
51 +
    set pool.free = [0; limits::FRAMES / 64];
51 52
    set pool.retiredHead = 0;
52 53
    set pool.retiredCount = 0;
53 54
    if machine.ramCount > platform::RAM_BANKS or machine.reservedCount > platform::RESERVATIONS {
54 55
        throw abi::Error::InvalidArg;
55 56
    }
81 82
                set address = (reserved[next].end + limits::FRAME_SIZE - 1) & ~(limits::FRAME_SIZE - 1);
82 83
                continue;
83 84
            }
84 85
            if count == limits::FRAMES { throw abi::Error::Exhausted; }
85 86
            set pool.addresses[count] = address;
86 -
            set pool.free[count] = true;
87 +
            set pool.free[count / 64] |= 1 << (count % 64) as u64;
87 88
            set count += 1;
88 89
            set address += limits::FRAME_SIZE;
89 90
        }
90 91
    }
91 92
    set pool.count = count;
92 93
}
93 94
94 -
/// Reserve the first physically contiguous free run with a bounded scan.
95 +
/// Test one valid frame's availability without exposing unused bitmap bits.
96 +
export fn available(pool: &Pool, index: u32) -> bool {
97 +
    return index < pool.count and (pool.free[index / 64] & (1 << (index % 64) as u64)) <> 0;
98 +
}
99 +
100 +
/// Low bits selected by a nonzero word-sized span.
101 +
fn mask(count: u32) -> u64 {
102 +
    assert count > 0 and count <= 64;
103 +
    if count == 64 { return 0xffffffffffffffff; }
104 +
    return (1 << count as u64) - 1;
105 +
}
106 +
107 +
/// Change a validated run's occupancy one bitmap word at a time.
108 +
fn assign(pool: &mut Pool, run: Run, free: bool) {
109 +
    let mut first = run.first;
110 +
    let end = first + run.count;
111 +
    while first < end {
112 +
        let offset = first % 64;
113 +
        let mut count = 64 - offset;
114 +
        if count > end - first { set count = end - first; }
115 +
        let bits = mask(count) << offset as u64;
116 +
        if free { set pool.free[first / 64] |= bits; }
117 +
        else { set pool.free[first / 64] &= ~bits; }
118 +
        set first += count;
119 +
    }
120 +
}
121 +
122 +
/// Reserve the first contiguous run, scanning uniform bitmap words together.
95 123
export fn reserve(pool: &mut Pool, count: u32) -> Reservation throws (abi::Error) {
96 124
    if count == 0 { throw abi::Error::InvalidArg; }
97 125
    let mut first: u32 = 0;
98 126
    let mut found: u32 = 0;
99 127
    let mut previous: u64 = 0;
100 -
    for i in 0..pool.count {
101 -
        if not pool.free[i] { set found = 0; continue; }
102 -
        if found == 0 or pool.addresses[i] <> previous + limits::FRAME_SIZE { set first = i; set found = 0; }
103 -
        set found += 1;
104 -
        set previous = pool.addresses[i];
105 -
        if found == count {
106 -
            for j in first..first + count { set pool.free[j] = false; }
107 -
            return Reservation::Held(Run { first, count });
128 +
    for word in 0..(pool.count + 63) / 64 {
129 +
        let base = word * 64;
130 +
        let mut length = pool.count - base;
131 +
        if length > 64 { set length = 64; }
132 +
        let full = mask(length);
133 +
        let bits = pool.free[word] & full;
134 +
        if bits == 0 { set found = 0; continue; }
135 +
        let address = pool.addresses[base];
136 +
        if bits == full and pool.addresses[base + length - 1] - address == (length - 1) as u64 * limits::FRAME_SIZE {
137 +
            if found == 0 or address <> previous + limits::FRAME_SIZE { set first = base; set found = 0; }
138 +
            set found += length;
139 +
            set previous = pool.addresses[base + length - 1];
140 +
        } else {
141 +
            for offset in 0..length {
142 +
                let i = base + offset;
143 +
                if (bits & (1 << offset as u64)) == 0 { set found = 0; continue; }
144 +
                if found == 0 or pool.addresses[i] <> previous + limits::FRAME_SIZE { set first = i; set found = 0; }
145 +
                set found += 1;
146 +
                set previous = pool.addresses[i];
147 +
                if found >= count { break; }
148 +
            }
149 +
        }
150 +
        if found >= count {
151 +
            let run = Run { first, count };
152 +
            assign(pool, run, false);
153 +
            return Reservation::Held(run);
108 154
        }
109 155
    }
110 156
    throw abi::Error::OutOfMemory;
111 157
}
112 158
127 173
128 174
/// Return a backing allocation's frames after all handles and exposures end.
129 175
/// Only the owner of a committed run may release it.
130 176
export fn release(pool: &mut Pool, run: Run) throws (abi::Error) {
131 177
    let memory = try extent(pool, run);
132 -
    for i in run.first..run.first + run.count { if pool.free[i] { throw abi::Error::BadHandle; } }
133 -
    for i in run.first..run.first + run.count { set pool.free[i] = true; }
178 +
    let mut first = run.first;
179 +
    let end = first + run.count;
180 +
    while first < end {
181 +
        let offset = first % 64;
182 +
        let mut count = 64 - offset;
183 +
        if count > end - first { set count = end - first; }
184 +
        if (pool.free[first / 64] & (mask(count) << offset as u64)) <> 0 { throw abi::Error::BadHandle; }
185 +
        set first += count;
186 +
    }
187 +
    assign(pool, run, true);
134 188
}
135 189
136 190
/// Transfer exclusive ownership of a committed run to bounded reclamation.
137 191
/// The caller must not release or retire this run again.
138 192
export fn retire(pool: &mut Pool, run: Run) throws (abi::Error) {
kernel/kernel/tests/backing.rad +13 -13
13 13
unsafe fn initialize() {
14 14
    backing::initialize(&mut STORE);
15 15
    try! backing::registerDomain(&mut STORE, abi::Ref { index: 0, generation: 1 });
16 16
    try! backing::registerDomain(&mut STORE, abi::Ref { index: 1, generation: 1 });
17 17
    set STORE.pool.count = 4;
18 -
    for i in 0..4 { set STORE.pool.addresses[i] = 0x80000000 + i as u64 * 4096; set STORE.pool.free[i] = true; }
18 +
    for i in 0..4 { set STORE.pool.addresses[i] = 0x80000000 + i as u64 * 4096; set STORE.pool.free[i / 64] |= 1 << (i % 64) as u64; }
19 19
}
20 20
21 21
/// Publish one backing record with its initial page and origin exposure.
22 22
unsafe fn allocate() -> abi::Ref {
23 23
    let slot = try! slots::reserve(&mut STORE.slots[..]);
47 47
    };
48 48
    assert invalid and backing::domainLive(&STORE, owner);
49 49
    assert not try! backing::endDomainStep(&mut STORE, owner, 1);
50 50
    assert not backing::domainLive(&STORE, owner);
51 51
    assert STORE.slots[objects[0].index].state == slots::State::Free;
52 -
    assert STORE.pool.retiredCount == 1 and not STORE.pool.free[0];
52 +
    assert STORE.pool.retiredCount == 1 and not frames::available(&STORE.pool, 0);
53 53
    assert STORE.records[objects[1].index].exposed == 2;
54 54
    let mut busy = false;
55 55
    try backing::registerDomain(&mut STORE, abi::Ref { index: 0, generation: 2 }) catch error {
56 56
        assert error == abi::Error::Busy; set busy = true;
57 57
    };
65 65
    assert STORE.records[objects[1].index].exposed == 1;
66 66
    assert STORE.records[objects[2].index].exposed == 1;
67 67
    while not try! backing::endDomainStep(&mut STORE, owner, 64) {}
68 68
    assert STORE.records[objects[2].index].exposed == 0 and STORE.records[objects[2].index].pages == 1;
69 69
    assert STORE.pool.retiredCount == 1;
70 -
    assert frames::reclaim(&mut STORE.pool, 1) == 1 and STORE.pool.free[0];
71 -
    assert not STORE.pool.free[1] and not STORE.pool.free[2];
70 +
    assert frames::reclaim(&mut STORE.pool, 1) == 1 and frames::available(&STORE.pool, 0);
71 +
    assert not frames::available(&STORE.pool, 1) and not frames::available(&STORE.pool, 2);
72 72
    let next = abi::Ref { index: 0, generation: 2 };
73 73
    try! backing::registerDomain(&mut STORE, next);
74 74
    assert backing::domainLive(&STORE, next);
75 75
    let mut stale = false;
76 76
    try backing::endDomainStep(&mut STORE, owner, 64) catch error {
77 77
        assert error == abi::Error::BadHandle; set stale = true;
78 78
    };
79 79
    assert stale and backing::domainLive(&STORE, next);
80 80
    try! backing::releasePage(&mut STORE, objects[2]);
81 -
    assert STORE.pool.free[2];
81 +
    assert frames::available(&STORE.pool, 2);
82 82
    while not try! backing::endDomainStep(&mut STORE, receiver, 64) {}
83 -
    assert STORE.pool.retiredCount == 1 and not STORE.pool.free[1];
84 -
    assert frames::reclaim(&mut STORE.pool, 1) == 1 and STORE.pool.free[1];
83 +
    assert STORE.pool.retiredCount == 1 and not frames::available(&STORE.pool, 1);
84 +
    assert frames::reclaim(&mut STORE.pool, 1) == 1 and frames::available(&STORE.pool, 1);
85 85
}
86 86
87 87
/// Dropping all page handles preserves capacity while an exposed domain lives.
88 88
@test unsafe fn exposures() throws (testing::TestError) {
89 89
    initialize();
92 92
    try! backing::expose(&mut STORE, object, receiver);
93 93
    try! backing::expose(&mut STORE, object, receiver);
94 94
    try testing::expect(STORE.records[object.index].exposed == 2);
95 95
    try! backing::releasePage(&mut STORE, object);
96 96
    try! backing::endDomain(&mut STORE, abi::Ref { index: 0, generation: 1 });
97 -
    try testing::expect(not STORE.pool.free[0] and slots::matches(&STORE.slots[..], object, slots::State::Live));
97 +
    assert not frames::available(&STORE.pool, 0) and slots::matches(&STORE.slots[..], object, slots::State::Live);
98 98
    try! backing::endDomain(&mut STORE, receiver);
99 -
    for i in 0..4 { try testing::expect(STORE.pool.free[i]); }
99 +
    for i in 0..4 { assert frames::available(&STORE.pool, i); }
100 100
    try testing::expect(not slots::matches(&STORE.slots[..], object, slots::State::Live));
101 101
}
102 102
103 103
/// Splits keep their common backing alive until every page object is gone.
104 104
@test unsafe fn splitLifetime() throws (testing::TestError) {
105 105
    initialize();
106 106
    let object = allocate();
107 107
    try! backing::retainPage(&mut STORE, object);
108 108
    try! backing::endDomain(&mut STORE, abi::Ref { index: 0, generation: 1 });
109 109
    try! backing::releasePage(&mut STORE, object);
110 -
    try testing::expect(not STORE.pool.free[0] and STORE.records[object.index].pages == 1);
110 +
    assert not frames::available(&STORE.pool, 0) and STORE.records[object.index].pages == 1;
111 111
    try! backing::releasePage(&mut STORE, object);
112 -
    try testing::expect(STORE.pool.free[0]);
112 +
    assert frames::available(&STORE.pool, 0);
113 113
    let mut stale = false;
114 114
    try backing::releasePage(&mut STORE, object) catch err {
115 115
        try testing::expect(err == abi::Error::BadHandle); set stale = true;
116 116
    };
117 117
    try testing::expect(stale);
130 130
        try testing::expect(err == abi::Error::BadHandle); set stale = true;
131 131
    };
132 132
    try testing::expect(stale and STORE.records[object.index].exposed == 1);
133 133
    try! backing::releasePage(&mut STORE, object);
134 134
    try! backing::endDomain(&mut STORE, abi::Ref { index: 0, generation: 1 });
135 -
    try testing::expect(STORE.pool.free[0]);
135 +
    assert frames::available(&STORE.pool, 0);
136 136
}
137 137
138 138
/// Retained backing metadata cannot create new page authority after the last page ends.
139 139
@test unsafe fn noResurrection() throws (testing::TestError) {
140 140
    initialize();
147 147
    try backing::expose(&mut STORE, object, abi::Ref { index: 1, generation: 1 }) catch err {
148 148
        try testing::expect(err == abi::Error::BadHandle); set failures += 1;
149 149
    };
150 150
    try testing::expect(failures == 2 and STORE.records[object.index].exposed == 1);
151 151
    try! backing::endDomain(&mut STORE, abi::Ref { index: 0, generation: 1 });
152 -
    try testing::expect(STORE.pool.free[0]);
152 +
    assert frames::available(&STORE.pool, 0);
153 153
}
kernel/kernel/tests/calls.rad +2 -1
1 1
//! Direct-call scalar results, checked record copies, and architectural replies.
2 2
3 3
use std::testing;
4 4
use kernel::abi;
5 +
use kernel::frames;
5 6
use kernel::calls;
6 7
use kernel::domains;
7 8
use kernel::pages;
8 9
use kernel::backing;
9 10
use kernel::capability;
296 297
    let aligned = (base + 4095) & ~4095;
297 298
    set OFFSET = ((aligned - base) / 8) as u32;
298 299
    set pages::STORE.backings.pool.count = 4;
299 300
    for i in 0..4 {
300 301
        set pages::STORE.backings.pool.addresses[i] = aligned + i as u64 * 4096;
301 -
        set pages::STORE.backings.pool.free[i] = true;
302 +
        set pages::STORE.backings.pool.free[i / 64] |= 1 << (i % 64) as u64;
302 303
    }
303 304
    let pending = try! slots::reserve(&mut domains::STORE.slots[..]);
304 305
    let owner = try! slots::commit(&mut domains::STORE.slots[..], pending);
305 306
    try! backing::registerDomain(&mut pages::STORE.backings, owner);
306 307
    capability::initialize(&mut MEMORY.table, owner);
kernel/kernel/tests/domains.rad +15 -15
49 49
    let base = pointer as u64;
50 50
    let aligned = (base + 4095) & ~4095;
51 51
    set BACKINGS.pool.count = (@sizeOf([u64; 131072]) - (aligned - base) as u32) / 4096;
52 52
    for i in 0..BACKINGS.pool.count {
53 53
        set BACKINGS.pool.addresses[i] = aligned + i as u64 * 4096;
54 -
        set BACKINGS.pool.free[i] = true;
54 +
        set BACKINGS.pool.free[i / 64] |= 1 << (i % 64) as u64;
55 55
    }
56 56
    let pending = try! slots::reserve(&mut DOMAINS.slots[..]);
57 57
    let owner = try! slots::commit(&mut DOMAINS.slots[..], pending);
58 58
    set DOMAINS.records[owner.index].state = domains::Lifecycle::Active;
59 59
    set DOMAINS.records[owner.index].parent = nil;
98 98
    set DOMAINS.records[permit.object.index].state = domains::Lifecycle::Dead;
99 99
    set DOMAINS.contexts[caller.initial.index].state = domains::ContextState::Stopped;
100 100
    set DOMAINS.contexts[caller.initial.index].hart = 1;
101 101
    lifecycle::cancelAllocations(&mut CALLS, &mut DOMAINS, &mut PAGES, &mut loader::STATE, &mut registry::STORE);
102 102
    assert DOMAINS.contexts[caller.initial.index].reservation;
103 -
    assert not PAGES.backings.pool.free[creation.storage.allocation.first];
103 +
    assert not frames::available(&PAGES.backings.pool, creation.storage.allocation.first);
104 104
    match lease { case lifecycle::Lease::Held(context) => { assert context == caller.initial; }, }
105 105
    set DOMAINS.contexts[caller.initial.index].hart = nil;
106 106
    lifecycle::cancelAllocations(&mut CALLS, &mut DOMAINS, &mut PAGES, &mut loader::STATE, &mut registry::STORE);
107 107
    assert not DOMAINS.contexts[caller.initial.index].reservation;
108 108
    assert DOMAINS.slots[creation.domain.index].state == slots::State::Free;
112 112
    while PAGES.backings.pool.retiredCount > 0 {
113 113
        let reclaimed = frames::reclaim(&mut PAGES.backings.pool, 256);
114 114
        assert reclaimed > 0 and reclaimed <= 256;
115 115
    }
116 116
    for run in [creation.storage.allocation, creation.storage.kernelFrames, creation.storage.graph.frames] {
117 -
        for i in run.first..run.first + run.count { assert PAGES.backings.pool.free[i]; }
117 +
        for i in run.first..run.first + run.count { assert frames::available(&PAGES.backings.pool, i); }
118 118
    }
119 119
    set DOMAINS.records[permit.object.index].state = domains::Lifecycle::Active;
120 120
    let next = try! domains::reserve(&mut DOMAINS, &mut PAGES.backings, &PACKAGES, &mut caller.memory.table, authority, image);
121 121
    let active = lifecycle::holdDomain(&mut CALLS, &mut DOMAINS, caller.initial, next);
122 122
    try! lifecycle::prepareDomain(&CALLS, &PACKAGES, &active);
264 264
    assert not lifecycle::reapNext(&mut DOMAINS, &mut PAGES, &mut WINDOWS, &mut IRQS);
265 265
    while PAGES.backings.pool.retiredCount > 0 {
266 266
        let reclaimed = frames::reclaim(&mut PAGES.backings.pool, 256);
267 267
        assert reclaimed > 0 and reclaimed <= 256;
268 268
    }
269 -
    assert not PAGES.backings.pool.free[run.first];
269 +
    assert not frames::available(&PAGES.backings.pool, run.first);
270 270
    assert PAGES.backings.records[allocation.index].exposed == 1;
271 271
    assert (try! pages::get(&PAGES, (try! capability::get(&parent.memory.table, shared)).object)).count == 1;
272 272
    assert try! events::flush(&mut DOMAINS.events, receiver, &mut parent.memory.ring) == 1;
273 273
    let event = try! events::pop(&mut parent.memory.ring) else panic "missing reaped child event";
274 274
    assert event.value == abi::id(target) and event.code == 19;
283 283
    assert DOMAINS.slots[receiver.index].state == slots::State::Free;
284 284
    while PAGES.backings.pool.retiredCount > 0 {
285 285
        let reclaimed = frames::reclaim(&mut PAGES.backings.pool, 256);
286 286
        assert reclaimed > 0 and reclaimed <= 256;
287 287
    }
288 -
    for i in 0..PAGES.backings.pool.count { assert PAGES.backings.pool.free[i]; }
288 +
    for i in 0..PAGES.backings.pool.count { assert frames::available(&PAGES.backings.pool, i); }
289 289
}
290 290
291 291
/// Initial return reports to the current parent and retains its terminal identity.
292 292
@test unsafe fn terminalParent() throws (testing::TestError) {
293 293
    initialize(abi::CREATE);
478 478
    };
479 479
    assert overlap;
480 480
    let reservedFrames = DOMAINS.contexts[index].kernelFrames;
481 481
    domains::contextCancel(&mut DOMAINS, &mut PAGES, reserved);
482 482
    assert DOMAINS.contextSlots[index].state == slots::State::Free;
483 -
    for i in reservedFrames.first..reservedFrames.first + reservedFrames.count { assert PAGES.backings.pool.free[i]; }
483 +
    for i in reservedFrames.first..reservedFrames.first + reservedFrames.count { assert frames::available(&PAGES.backings.pool, i); }
484 484
    let pendingContext = try! domains::contextReserve(&mut DOMAINS, &mut PAGES, &PACKAGES, &TABLE, handle, start);
485 485
    let stackBase = DOMAINS.contexts[index].kernelStack.start;
486 486
    let base = (&RAM[0]) as u64;
487 487
    let firstWord = ((stackBase - base) / 8) as u32;
488 488
    let lastWord = firstWord + domains::KERNEL_STACK_PAGES * 512 - 1;
561 561
    try domains::contextDestroy(&mut DOMAINS, &mut PAGES, &TABLE, handle, context) catch error {
562 562
        assert error == abi::Error::Busy; set retained = true;
563 563
    };
564 564
    assert retained and DOMAINS.contextSlots[context.index].state == slots::State::Live;
565 565
    for i in created.kernelFrames.first..created.kernelFrames.first + created.kernelFrames.count {
566 -
        assert not PAGES.backings.pool.free[i];
566 +
        assert not frames::available(&PAGES.backings.pool, i);
567 567
    }
568 568
    set DOMAINS.contexts[context.index].pending = nil;
569 569
    set DOMAINS.contexts[context.index].reservation = true;
570 570
    set retained = false;
571 571
    try domains::contextDestroy(&mut DOMAINS, &mut PAGES, &TABLE, handle, context) catch error {
573 573
    };
574 574
    assert retained and DOMAINS.contextSlots[context.index].state == slots::State::Live;
575 575
    set DOMAINS.contexts[context.index].reservation = false;
576 576
    try! domains::contextDestroy(&mut DOMAINS, &mut PAGES, &TABLE, handle, context);
577 577
    for i in created.kernelFrames.first..created.kernelFrames.first + created.kernelFrames.count {
578 -
        try testing::expect(PAGES.backings.pool.free[i]);
578 +
        assert frames::available(&PAGES.backings.pool, i);
579 579
    }
580 580
    try domains::context(&DOMAINS, authority.object, context) catch error {
581 581
        try testing::expect(error == abi::Error::BadHandle); set rejected += 1;
582 582
    };
583 583
    let next = try! domains::contextCreate(&mut DOMAINS, &mut PAGES, &PACKAGES, &TABLE, handle, start);
651 651
    let cancelled = try! domains::reserve(&mut DOMAINS, &mut BACKINGS, &PACKAGES, &mut TABLE, abi::Handle(0), IMAGE);
652 652
    domains::cancel(&mut DOMAINS, &mut BACKINGS, &mut TABLE, cancelled);
653 653
    assert DOMAINS.slots[1].state == slots::State::Free;
654 654
    assert DOMAINS.contextSlots[0].state == slots::State::Free;
655 655
    assert TABLE.slots[2].state == slots::State::Free;
656 -
    for i in 0..BACKINGS.pool.count { assert BACKINGS.pool.free[i]; }
656 +
    for i in 0..BACKINGS.pool.count { assert frames::available(&BACKINGS.pool, i); }
657 657
    set PACKAGES.entries[0].package.relocations = &[shared::Relocation {
658 658
        offset: 0, count: 1, target: shared::DataRef { slot: 5, offset: 0 },
659 659
    }];
660 660
    let mut invalid = false;
661 661
    try domains::create(&mut DOMAINS, &mut BACKINGS, &PACKAGES, &mut TABLE, abi::Handle(0), IMAGE) catch error {
662 662
        assert error == abi::Error::VerifyFailed; set invalid = true;
663 663
    };
664 664
    assert invalid and DOMAINS.slots[1].state == slots::State::Free;
665 -
    for i in 0..BACKINGS.pool.count { assert BACKINGS.pool.free[i]; }
665 +
    for i in 0..BACKINGS.pool.count { assert frames::available(&BACKINGS.pool, i); }
666 666
    initialize(abi::CREATE);
667 667
    let abandoned = try! domains::reserve(&mut DOMAINS, &mut BACKINGS, &PACKAGES, &mut TABLE, abi::Handle(0), IMAGE);
668 668
    try! domains::prepare(&PACKAGES, &abandoned);
669 669
    try! backing::endDomain(&mut BACKINGS, TABLE.owner);
670 670
    let mut dead = false;
671 671
    try domains::publish(&mut DOMAINS, &mut BACKINGS, &mut TABLE, abandoned) catch error {
672 672
        assert error == abi::Error::BadHandle; set dead = true;
673 673
    };
674 674
    assert dead and DOMAINS.slots[1].state == slots::State::Free;
675 675
    assert TABLE.slots[2].state == slots::State::Free;
676 -
    for i in 0..BACKINGS.pool.count { assert BACKINGS.pool.free[i]; }
676 +
    for i in 0..BACKINGS.pool.count { assert frames::available(&BACKINGS.pool, i); }
677 677
}
678 678
679 679
/// Creation installs only Events in the child and preserves selected authorizing rights.
680 680
@test unsafe fn pending() throws (testing::TestError) {
681 681
    initialize(abi::CREATE | abi::ALLOCATE);
706 706
    let mut rejected = false;
707 707
    try domains::create(&mut DOMAINS, &mut BACKINGS, &PACKAGES, &mut TABLE, abi::Handle(0), IMAGE) catch err {
708 708
        try testing::expect(err == expected); set rejected = true;
709 709
    };
710 710
    try testing::expect(rejected);
711 -
    for i in 0..BACKINGS.pool.count { try testing::expect(BACKINGS.pool.free[i]); }
711 +
    for i in 0..BACKINGS.pool.count { assert frames::available(&BACKINGS.pool, i); }
712 712
    try testing::expect(DOMAINS.slots[1].state == slots::State::Free);
713 713
    try testing::expect(DOMAINS.contextSlots[0].state == slots::State::Free);
714 714
    try testing::expect(TABLE.slots[2].state == slots::State::Free);
715 715
    try testing::expect(BACKINGS.domains[1] == 0);
716 716
}
743 743
    try domains::create(&mut DOMAINS, &mut BACKINGS, &PACKAGES, &mut TABLE, abi::Handle(0), IMAGE) catch err {
744 744
        try testing::expect(err == abi::Error::Exhausted); set rejected = true;
745 745
    };
746 746
    try testing::expect(rejected and DOMAINS.slots[1].state == slots::State::Free);
747 747
    try testing::expect(TABLE.slots[2].state == slots::State::Free);
748 -
    for i in 0..BACKINGS.pool.count { try testing::expect(BACKINGS.pool.free[i]); }
748 +
    for i in 0..BACKINGS.pool.count { assert frames::available(&BACKINGS.pool, i); }
749 749
}
750 750
751 751
/// A creator can grant Create without Allocate and the next creation preserves that choice.
752 752
@test unsafe fn selectiveAuthority() throws (testing::TestError) {
753 753
    initialize(abi::CREATE | abi::ALLOCATE);
795 795
    let mut failures: u32 = 0;
796 796
    try domains::create(&mut DOMAINS, &mut BACKINGS, &PACKAGES, &mut TABLE, abi::Handle(0), IMAGE) catch err {
797 797
        try testing::expect(err == abi::Error::Exhausted); set failures += 1;
798 798
    };
799 799
    try testing::expect(DOMAINS.slots[1].state == slots::State::Free);
800 -
    for i in 0..BACKINGS.pool.count { try testing::expect(BACKINGS.pool.free[i]); }
800 +
    for i in 0..BACKINGS.pool.count { assert frames::available(&BACKINGS.pool, i); }
801 801
    initialize(abi::CREATE);
802 802
    for i in 1..DOMAINS.slots.len { set DOMAINS.slots[i].state = slots::State::Retired; }
803 803
    try domains::create(&mut DOMAINS, &mut BACKINGS, &PACKAGES, &mut TABLE, abi::Handle(0), IMAGE) catch err {
804 804
        try testing::expect(err == abi::Error::Exhausted); set failures += 1;
805 805
    };
806 806
    try testing::expect(failures == 2 and TABLE.slots[2].state == slots::State::Free);
807 807
    try testing::expect(DOMAINS.contextSlots[0].state == slots::State::Free);
808 -
    for i in 0..BACKINGS.pool.count { try testing::expect(BACKINGS.pool.free[i]); }
808 +
    for i in 0..BACKINGS.pool.count { assert frames::available(&BACKINGS.pool, i); }
809 809
}
kernel/kernel/tests/frames.rad +63 -6
18 18
    set machine.reservedCount = 1;
19 19
    set machine.reserved[0] = range::Range { start: 0x80003000, end: 0x80005000 };
20 20
    try! frames::initialize(&mut POOL, &machine);
21 21
}
22 22
23 +
/// Bitmap allocation agrees with a scalar scan across holes and word boundaries.
24 +
@test unsafe fn bitmapRuns() throws (testing::TestError) {
25 +
    let mut machine: platform::Platform = undefined;
26 +
    set machine.ramCount = 1;
27 +
    set machine.ram[0] = range::Range { start: 0x80000000, end: 0x800c2000 };
28 +
    set machine.reservedCount = 1;
29 +
    set machine.reserved[0] = range::Range { start: 0x8001f000, end: 0x80020000 };
30 +
    for pattern in 0..5 {
31 +
        for count in [1 as u32, 2, 31, 32, 63, 64, 65, 127, 128, 129, 193, 194] {
32 +
            try! frames::initialize(&mut POOL, &machine);
33 +
            assert POOL.count == 193;
34 +
            for i in 0..POOL.count {
35 +
                if pattern > 0 and i % (pattern * 32) == 0 {
36 +
                    set POOL.free[i / 64] &= ~(1 << (i % 64) as u64);
37 +
                }
38 +
            }
39 +
            // Padding bits are outside the pool even when their values are set.
40 +
            set POOL.free[3] |= 0xfffffffffffffffe;
41 +
            let before = [POOL.free[0], POOL.free[1], POOL.free[2], POOL.free[3]];
42 +
            let mut expected: ?u32 = nil;
43 +
            let mut found: u32 = 0;
44 +
            for i in 0..POOL.count {
45 +
                if not frames::available(&POOL, i) { set found = 0; continue; }
46 +
                if found > 0 and POOL.addresses[i] <> POOL.addresses[i - 1] + 4096 { set found = 0; }
47 +
                set found += 1;
48 +
                if found == count { set expected = i + 1 - count; break; }
49 +
            }
50 +
            let reservation = try frames::reserve(&mut POOL, count) catch error {
51 +
                assert expected == nil and error == abi::Error::OutOfMemory;
52 +
                for i in 0..4 { assert POOL.free[i] == before[i]; }
53 +
                continue;
54 +
            };
55 +
            let first = expected else panic "unexpected frame reservation";
56 +
            let run = frames::commit(reservation);
57 +
            assert run.first == first and run.count == count;
58 +
            for i in 0..POOL.count {
59 +
                if i >= first and i - first < count { assert not frames::available(&POOL, i); }
60 +
                else { assert frames::available(&POOL, i) == ((before[i / 64] & (1 << (i % 64) as u64)) <> 0); }
61 +
            }
62 +
            try! frames::release(&mut POOL, run);
63 +
            for i in 0..4 { assert POOL.free[i] == before[i]; }
64 +
        }
65 +
    }
66 +
    assert not frames::available(&POOL, POOL.count);
67 +
    try! frames::initialize(&mut POOL, &machine);
68 +
    let run = frames::commit(try! frames::reserve(&mut POOL, 128));
69 +
    assert run.first == 31;
70 +
    let before = [POOL.free[0], POOL.free[1], POOL.free[2], POOL.free[3]];
71 +
    let mut invalid = false;
72 +
    try frames::release(&mut POOL, frames::Run { first: run.first, count: 129 }) catch error {
73 +
        assert error == abi::Error::BadHandle; set invalid = true;
74 +
    };
75 +
    assert invalid;
76 +
    for i in 0..4 { assert POOL.free[i] == before[i]; }
77 +
    try! frames::release(&mut POOL, run);
78 +
}
79 +
23 80
/// Retired frames stay unavailable until bounded reclamation returns them.
24 81
@test unsafe fn retirement() throws (testing::TestError) {
25 82
    initialize();
26 83
    set POOL.retiredHead = limits::FRAMES - 1;
27 84
    let left = frames::commit(try! frames::reserve(&mut POOL, 3));
28 85
    let right = frames::commit(try! frames::reserve(&mut POOL, 5));
29 86
    try! frames::retire(&mut POOL, left);
30 87
    try! frames::retire(&mut POOL, right);
31 88
    assert POOL.retiredCount == 2;
32 -
    for i in 0..8 { assert not POOL.free[i]; }
89 +
    for i in 0..8 { assert not frames::available(&POOL, i); }
33 90
    assert frames::reclaim(&mut POOL, 0) == 0;
34 91
    assert frames::reclaim(&mut POOL, 2) == 2;
35 -
    assert POOL.free[0] and POOL.free[1] and not POOL.free[2];
92 +
    assert frames::available(&POOL, 0) and frames::available(&POOL, 1) and not frames::available(&POOL, 2);
36 93
    let reused = frames::commit(try! frames::reserve(&mut POOL, 2));
37 94
    assert reused.first == 0;
38 95
    assert frames::reclaim(&mut POOL, 3) == 3;
39 -
    assert POOL.retiredCount == 1 and not POOL.free[5];
40 -
    assert not POOL.free[0] and not POOL.free[1];
96 +
    assert POOL.retiredCount == 1 and not frames::available(&POOL, 5);
97 +
    assert not frames::available(&POOL, 0) and not frames::available(&POOL, 1);
41 98
    try! frames::retire(&mut POOL, reused);
42 99
    assert frames::reclaim(&mut POOL, 20) == 5;
43 100
    assert POOL.retiredCount == 0;
44 -
    for i in 0..8 { assert POOL.free[i]; }
101 +
    for i in 0..8 { assert frames::available(&POOL, i); }
45 102
    assert frames::reclaim(&mut POOL, 20) == 0;
46 103
}
47 104
48 105
/// Adjacent physical frames form runs; a reserved hole cannot be crossed.
49 106
@test unsafe fn fragmentation() throws (testing::TestError) {
187 244
    try! frames::initialize(&mut POOL, &machine);
188 245
    let mut count: u32 = 0;
189 246
    for i in 0..96 {
190 247
        let start = 0x80000000 + i as u64 * 4096;
191 248
        if platform::available(&machine, range::Range { start, end: start + 4096 }) {
192 -
            try testing::expect(count < POOL.count and POOL.addresses[count] == start and POOL.free[count]);
249 +
            assert count < POOL.count and POOL.addresses[count] == start and frames::available(&POOL, count);
193 250
            set count += 1;
194 251
        }
195 252
    }
196 253
    try testing::expect(count == POOL.count);
197 254
}
kernel/kernel/tests/instances.rad +4 -4
21 21
    let base = pointer as u64;
22 22
    let aligned = (base + 4095) & ~4095;
23 23
    set POOL.count = (@sizeOf([u64; 131072]) - (aligned - base) as u32) / 4096;
24 24
    for i in 0..POOL.count {
25 25
        set POOL.addresses[i] = aligned + i as u64 * 4096;
26 -
        set POOL.free[i] = true;
26 +
        set POOL.free[i / 64] |= 1 << (i % 64) as u64;
27 27
    }
28 28
    registry::initialize(&mut PACKAGES);
29 29
}
30 30
31 31
/// Publish a package with one private word and optional pointer relocation.
66 66
    assert first.frames.first + first.frames.count <= other.frames.first;
67 67
    try! frames::release(&mut POOL, first.frames);
68 68
    try! frames::release(&mut POOL, other.frames);
69 69
    let cancelled = try! instances::reserve(&PACKAGES, &mut POOL, root);
70 70
    instances::cancel(&mut POOL, cancelled);
71 -
    for i in 0..POOL.count { assert POOL.free[i]; }
71 +
    for i in 0..POOL.count { assert frames::available(&POOL, i); }
72 72
}
73 73
74 74
/// A diamond has one dependency instance per domain and independent relocated pointers.
75 75
@test unsafe fn diamond() throws (testing::TestError) {
76 76
    initialize();
90 90
    set *word(first.table[base.index]) = 99;
91 91
    try testing::expect(*word(second.table[base.index]) == 7);
92 92
    try testing::expect(first.table[4] == 0 and second.table[4] == 0);
93 93
    try! frames::release(&mut POOL, first.frames);
94 94
    try! frames::release(&mut POOL, second.frames);
95 -
    for i in 0..POOL.count { try testing::expect(POOL.free[i]); }
95 +
    for i in 0..POOL.count { assert frames::available(&POOL, i); }
96 96
}
97 97
98 98
/// State alignment can exceed a physical frame and zero-fill follows the template.
99 99
@test unsafe fn alignment() throws (testing::TestError) {
100 100
    initialize();
125 125
    };
126 126
    try instances::create(&PACKAGES, &mut POOL, abi::Ref { index: root.index, generation: 2 }) catch err {
127 127
        try testing::expect(err == abi::Error::BadHandle); set failures += 1;
128 128
    };
129 129
    try testing::expect(failures == 3);
130 -
    for i in 0..POOL.count { try testing::expect(POOL.free[i]); }
130 +
    for i in 0..POOL.count { assert frames::available(&POOL, i); }
131 131
    let retry = try! instances::create(&PACKAGES, &mut POOL, root);
132 132
    try testing::expect(*word(retry.table[root.index]) == 7);
133 133
    try! frames::release(&mut POOL, retry.frames);
134 134
}
kernel/kernel/tests/lifecycle.rad +11 -11
80 80
    });
81 81
    let base = ((&RAM[0]) as u64 + 4095) & ~4095;
82 82
    set MEMORY.backings.pool.count = 2;
83 83
    for i in 0..2 {
84 84
        set MEMORY.backings.pool.addresses[i] = base + i as u64 * 4096;
85 -
        set MEMORY.backings.pool.free[i] = true;
85 +
        set MEMORY.backings.pool.free[i / 64] |= 1 << (i % 64) as u64;
86 86
    }
87 87
    let reservation = try! pages::reserve(&mut MEMORY, &mut FIRST.table, authority, 2);
88 88
    let held = lifecycle::holdPage(&mut CALLS, &mut STORE, owner, reservation);
89 89
    assert STORE.contexts[0].reservation;
90 90
    lifecycle::clearPage(&CALLS, &held);
91 91
    lifecycle::cancelAllocations(&mut CALLS, &mut STORE, &mut MEMORY, &mut loader::STATE, &mut registry::STORE);
92 -
    assert not MEMORY.backings.pool.free[0] and not MEMORY.backings.pool.free[1];
92 +
    assert not frames::available(&MEMORY.backings.pool, 0) and not frames::available(&MEMORY.backings.pool, 1);
93 93
    set STORE.records[0].state = domains::Lifecycle::Dead;
94 94
    set STORE.contexts[0].state = domains::ContextState::Stopped;
95 95
    set STORE.contexts[0].hart = 0;
96 96
    lifecycle::cancelAllocations(&mut CALLS, &mut STORE, &mut MEMORY, &mut loader::STATE, &mut registry::STORE);
97 -
    assert not MEMORY.backings.pool.free[0];
97 +
    assert not frames::available(&MEMORY.backings.pool, 0);
98 98
    // Model the end of the stopped continuation before hart release.
99 99
    match held { case lifecycle::Lease::Held(context) => { assert context == owner; }, }
100 100
    set STORE.contexts[0].hart = nil;
101 101
    lifecycle::cancelAllocations(&mut CALLS, &mut STORE, &mut MEMORY, &mut loader::STATE, &mut registry::STORE);
102 -
    assert MEMORY.backings.pool.retiredCount == 1 and not MEMORY.backings.pool.free[0];
102 +
    assert MEMORY.backings.pool.retiredCount == 1 and not frames::available(&MEMORY.backings.pool, 0);
103 103
    assert frames::reclaim(&mut MEMORY.backings.pool, 256) == 2;
104 -
    assert MEMORY.backings.pool.free[0] and MEMORY.backings.pool.free[1];
104 +
    assert frames::available(&MEMORY.backings.pool, 0) and frames::available(&MEMORY.backings.pool, 1);
105 105
    assert MEMORY.slots[0].state == slots::State::Free;
106 106
    assert MEMORY.backings.slots[0].state == slots::State::Free;
107 107
    assert FIRST.table.slots[1].state == slots::State::Free and CALLS.active[0] == 0;
108 108
    assert not STORE.contexts[0].reservation;
109 109
    set STORE.records[0].state = domains::Lifecycle::Active;
133 133
    });
134 134
    let base = ((&RAM[0]) as u64 + 4095) & ~4095;
135 135
    set MEMORY.backings.pool.count = 2;
136 136
    for i in 0..2 {
137 137
        set MEMORY.backings.pool.addresses[i] = base + i as u64 * 4096;
138 -
        set MEMORY.backings.pool.free[i] = true;
138 +
        set MEMORY.backings.pool.free[i / 64] |= 1 << (i % 64) as u64;
139 139
    }
140 140
    let firstPage = try! pages::reserve(&mut MEMORY, &mut FIRST.table, authority, 1);
141 141
    let first = lifecycle::holdPage(&mut CALLS, &mut STORE, owner, firstPage);
142 142
    let secondPage = try! pages::reserve(&mut MEMORY, &mut FIRST.table, authority, 1);
143 143
    let other = lifecycle::holdPage(&mut CALLS, &mut STORE, second, secondPage);
150 150
    assert CALLS.active[0] == 2 and MEMORY.backings.pool.retiredCount == 1;
151 151
    assert not STORE.contexts[0].reservation and STORE.contexts[1].reservation;
152 152
    assert lifecycle::cancelAllocations(&mut CALLS, &mut STORE, &mut MEMORY, &mut loader::STATE, &mut registry::STORE);
153 153
    assert CALLS.active[0] == 0 and MEMORY.backings.pool.retiredCount == 2;
154 154
    assert not lifecycle::cancelAllocations(&mut CALLS, &mut STORE, &mut MEMORY, &mut loader::STATE, &mut registry::STORE);
155 -
    assert not MEMORY.backings.pool.free[0] and not MEMORY.backings.pool.free[1];
155 +
    assert not frames::available(&MEMORY.backings.pool, 0) and not frames::available(&MEMORY.backings.pool, 1);
156 156
    assert frames::reclaim(&mut MEMORY.backings.pool, 1) == 1;
157 157
    assert frames::reclaim(&mut MEMORY.backings.pool, 1) == 1;
158 158
    assert MEMORY.backings.pool.retiredCount == 0;
159 159
}
160 160
166 166
    let caller = abi::Ref { index: 0, generation: 1 };
167 167
    let target = abi::Ref { index: 1, generation: 1 };
168 168
    let base = ((&RAM[0]) as u64 + 4095) & ~4095;
169 169
    set MEMORY.backings.pool.count = 1;
170 170
    set MEMORY.backings.pool.addresses[0] = base;
171 -
    set MEMORY.backings.pool.free[0] = true;
171 +
    set MEMORY.backings.pool.free[0] |= 1;
172 172
    let slot = try! slots::reserve(&mut STORE.contextSlots[..]);
173 173
    let object = slots::reference(&slot);
174 174
    let run = frames::commit(try! frames::reserve(&mut MEMORY.backings.pool, 1));
175 175
    set STORE.contexts[object.index].owner = target;
176 176
    set STORE.contexts[object.index].kernelFrames = run;
181 181
    });
182 182
    let lease = lifecycle::holdContext(&mut CALLS, &mut STORE, caller, pending);
183 183
    lifecycle::clearContext(&CALLS, &lease);
184 184
    set STORE.records[1].state = domains::Lifecycle::Dead;
185 185
    lifecycle::cancelAllocations(&mut CALLS, &mut STORE, &mut MEMORY, &mut loader::STATE, &mut registry::STORE);
186 -
    assert STORE.contexts[0].reservation and not MEMORY.backings.pool.free[0];
186 +
    assert STORE.contexts[0].reservation and not frames::available(&MEMORY.backings.pool, 0);
187 187
    set STORE.records[0].state = domains::Lifecycle::Dead;
188 188
    set STORE.contexts[0].state = domains::ContextState::Stopped;
189 189
    set STORE.contexts[0].hart = 1;
190 190
    lifecycle::cancelAllocations(&mut CALLS, &mut STORE, &mut MEMORY, &mut loader::STATE, &mut registry::STORE);
191 191
    assert STORE.contextSlots[object.index].state == slots::State::Reserved;
192 192
    match lease { case lifecycle::Lease::Held(id) => { assert id == caller; }, }
193 193
    set STORE.contexts[0].hart = nil;
194 194
    lifecycle::cancelAllocations(&mut CALLS, &mut STORE, &mut MEMORY, &mut loader::STATE, &mut registry::STORE);
195 -
    assert MEMORY.backings.pool.retiredCount == 1 and not MEMORY.backings.pool.free[0];
195 +
    assert MEMORY.backings.pool.retiredCount == 1 and not frames::available(&MEMORY.backings.pool, 0);
196 196
    assert frames::reclaim(&mut MEMORY.backings.pool, 256) == 1;
197 -
    assert MEMORY.backings.pool.free[0];
197 +
    assert frames::available(&MEMORY.backings.pool, 0);
198 198
    assert STORE.contextSlots[object.index].state == slots::State::Free;
199 199
    assert not STORE.contexts[0].reservation and CALLS.active[0] == 0;
200 200
}
201 201
202 202
/// Dead contexts retain requests until hart release and completion of taken work.
kernel/kernel/tests/loader.rad +2 -2
50 50
    let aligned = (base + 4095) & ~4095;
51 51
    set OFFSET = (aligned - base) as u32;
52 52
    set PAGES.backings.pool.count = (@sizeOf([u64; 18874368]) - OFFSET) / 4096;
53 53
    for i in 0..PAGES.backings.pool.count {
54 54
        set PAGES.backings.pool.addresses[i] = aligned + i as u64 * 4096;
55 -
        set PAGES.backings.pool.free[i] = true;
55 +
        set PAGES.backings.pool.free[i / 64] |= 1 << (i % 64) as u64;
56 56
    }
57 57
    let owner = abi::Ref { index: 0, generation: 1 };
58 58
    capability::initialize(&mut TABLE, owner);
59 59
    try! backing::registerDomain(&mut PAGES.backings, owner);
60 60
    let authority = try! capability::install(&mut TABLE, capability::Entry {
88 88
}
89 89
90 90
/// Count available frames to detect leaked temporary or unpublished output storage.
91 91
unsafe fn free() -> u32 {
92 92
    let mut count: u32 = 0;
93 -
    for i in 0..PAGES.backings.pool.count { if PAGES.backings.pool.free[i] { set count += 1; } }
93 +
    for i in 0..PAGES.backings.pool.count { if frames::available(&PAGES.backings.pool, i) { set count += 1; } }
94 94
    return count;
95 95
}
96 96
97 97
/// Dead loaders retain private resources until hart release, then permit a retry.
98 98
@test unsafe fn cancelledCall() throws (testing::TestError) {
kernel/kernel/tests/pages.rad +16 -15
1 1
//! Page allocation, zeroing, split atomicity, and retained-pointer lifetimes.
2 2
3 3
use std::testing;
4 4
use kernel::abi;
5 +
use kernel::frames;
5 6
use kernel::capability;
6 7
use kernel::pages;
7 8
use kernel::backing;
8 9
use kernel::slots;
9 10
use kernel::limits;
27 28
    let aligned = (address + 4095) & ~4095;
28 29
    set OFFSET = (aligned - address) as u32;
29 30
    slots::initialize(&mut STORE.slots[..]);
30 31
    backing::initialize(&mut STORE.backings);
31 32
    set STORE.backings.pool.count = 8;
32 -
    for i in 0..8 { set STORE.backings.pool.addresses[i] = aligned + i as u64 * 4096; set STORE.backings.pool.free[i] = true; }
33 +
    for i in 0..8 { set STORE.backings.pool.addresses[i] = aligned + i as u64 * 4096; set STORE.backings.pool.free[i / 64] |= 1 << (i % 64) as u64; }
33 34
    let owner = abi::Ref { index: 0, generation: 1 };
34 35
    capability::initialize(&mut TABLE, owner);
35 36
    try! backing::registerDomain(&mut STORE.backings, owner);
36 37
    let authority = try! capability::install(&mut TABLE, capability::Entry {
37 38
        kind: abi::Kind::Domain, object: owner, rights: abi::Rights(abi::ALLOCATE),
47 48
@test unsafe fn stagedAllocation() throws (testing::TestError) {
48 49
    initialize();
49 50
    let pending = try! pages::reserve(&mut STORE, &mut TABLE, abi::Handle(0), 2);
50 51
    assert STORE.slots[0].state == slots::State::Reserved;
51 52
    assert TABLE.slots[1].state == slots::State::Reserved;
52 -
    assert not STORE.backings.pool.free[0] and not STORE.backings.pool.free[1];
53 +
    assert not frames::available(&STORE.backings.pool, 0) and not frames::available(&STORE.backings.pool, 1);
53 54
    assert RAM[OFFSET] == 0xa5;
54 55
    let other = try! pages::allocate(&mut STORE, &mut TABLE, abi::Handle(0), 6);
55 56
    set RAM[OFFSET + 8192] = 0x7b;
56 57
    pages::clear(&pending);
57 58
    for i in OFFSET..OFFSET + 8192 { assert RAM[i] == 0; }
60 61
    let entry = try! capability::get(&TABLE, handle);
61 62
    let page = try! pages::get(&STORE, entry.object);
62 63
    assert page.count == 2 and page.origin == TABLE.owner;
63 64
    drop(handle); drop(other);
64 65
    try! backing::endDomain(&mut STORE.backings, TABLE.owner);
65 -
    for i in 0..8 { assert STORE.backings.pool.free[i]; }
66 +
    for i in 0..8 { assert frames::available(&STORE.backings.pool, i); }
66 67
}
67 68
68 69
/// Cancelling unpublished storage returns all capacity without exposing its bytes.
69 70
@test unsafe fn cancelAllocation() throws (testing::TestError) {
70 71
    initialize();
71 72
    let pending = try! pages::reserve(&mut STORE, &mut TABLE, abi::Handle(0), 8);
72 73
    pages::cancel(&mut STORE, &mut TABLE, pending);
73 74
    assert STORE.slots[0].state == slots::State::Free;
74 75
    assert STORE.backings.slots[0].state == slots::State::Free;
75 76
    assert TABLE.slots[1].state == slots::State::Free;
76 -
    for i in 0..8 { assert STORE.backings.pool.free[i]; }
77 +
    for i in 0..8 { assert frames::available(&STORE.backings.pool, i); }
77 78
    assert RAM[OFFSET] == 0xa5;
78 79
    let full = try! pages::allocate(&mut STORE, &mut TABLE, abi::Handle(0), 8);
79 80
    let entry = try! capability::get(&TABLE, full);
80 81
    assert (try! pages::get(&STORE, entry.object)).count == 8;
81 82
}
93 94
    try testing::expect(page.handles == 1 and STORE.backings.records[page.backing.index].exposed == 2);
94 95
    let actual = try! capability::get(&TARGET, moved);
95 96
    try testing::expect(*actual.rights == abi::READ);
96 97
    try! pages::drop(&mut STORE, &mut TARGET, moved);
97 98
    try! backing::endDomain(&mut STORE.backings, receiver);
98 -
    try testing::expect(not STORE.backings.pool.free[0]);
99 +
    assert not frames::available(&STORE.backings.pool, 0);
99 100
    try! backing::endDomain(&mut STORE.backings, TABLE.owner);
100 -
    try testing::expect(STORE.backings.pool.free[0]);
101 +
    assert frames::available(&STORE.backings.pool, 0);
101 102
}
102 103
103 104
/// Grants acquire references only after capacity and receiver checks succeed.
104 105
@test unsafe fn grantRollback() throws (testing::TestError) {
105 106
    initialize();
141 142
    try testing::expect(STORE.records[entry.object.index].handles == 2);
142 143
    try testing::expect(STORE.backings.records[page.backing.index].exposed == 2);
143 144
    drop(original);
144 145
    try! pages::drop(&mut STORE, &mut TARGET, granted);
145 146
    try! backing::endDomain(&mut STORE.backings, TABLE.owner);
146 -
    try testing::expect(not STORE.backings.pool.free[0]);
147 +
    assert not frames::available(&STORE.backings.pool, 0);
147 148
    try! backing::endDomain(&mut STORE.backings, receiver);
148 -
    try testing::expect(STORE.backings.pool.free[0]);
149 +
    assert frames::available(&STORE.backings.pool, 0);
149 150
}
150 151
151 152
/// Self-attenuation adds one reference, preserves authority, and prevents splitting.
152 153
@test unsafe fn selfAttenuation() throws (testing::TestError) {
153 154
    initialize();
160 161
    try testing::expect(*none.rights == 0);
161 162
    drop(narrowed); drop(copy);
162 163
    let right = try! pages::split(&mut STORE, &mut TABLE, original, 1);
163 164
    drop(original); drop(right);
164 165
    try! backing::endDomain(&mut STORE.backings, TABLE.owner);
165 -
    try testing::expect(STORE.backings.pool.free[0] and STORE.backings.pool.free[1]);
166 +
    assert frames::available(&STORE.backings.pool, 0) and frames::available(&STORE.backings.pool, 1);
166 167
}
167 168
168 169
/// New allocations are zeroed completely and carry the documented default rights.
169 170
@test unsafe fn allocation() throws (testing::TestError) {
170 171
    initialize();
183 184
    for count in &[65537 as u64, 0xffffffffffffffff] {
184 185
        let mut exhausted = false;
185 186
        try pages::allocate(&mut STORE, &mut TABLE, abi::Handle(0), count) catch err {
186 187
            try testing::expect(err == abi::Error::OutOfMemory); set exhausted = true;
187 188
        };
188 -
        try testing::expect(exhausted and STORE.backings.pool.free[2]);
189 +
        assert exhausted and frames::available(&STORE.backings.pool, 2);
189 190
    }
190 191
}
191 192
192 193
/// Splitting preserves contents and backing identity while changing only page extents.
193 194
@test unsafe fn split() throws (testing::TestError) {
229 230
    try! pages::retain(&mut STORE, entry.object, receiver);
230 231
    set RAM[OFFSET] = 99;
231 232
    drop(handle);
232 233
    try! pages::release(&mut STORE, entry.object);
233 234
    try! backing::endDomain(&mut STORE.backings, TABLE.owner);
234 -
    try testing::expect(not STORE.backings.pool.free[0] and RAM[OFFSET] == 99);
235 +
    assert not frames::available(&STORE.backings.pool, 0) and RAM[OFFSET] == 99;
235 236
    try! backing::endDomain(&mut STORE.backings, receiver);
236 -
    try testing::expect(STORE.backings.pool.free[0]);
237 +
    assert frames::available(&STORE.backings.pool, 0);
237 238
    let owner = abi::Ref { index: 0, generation: 2 };
238 239
    capability::initialize(&mut TABLE, owner);
239 240
    try! backing::registerDomain(&mut STORE.backings, owner);
240 241
    let authority = try! capability::install(&mut TABLE, capability::Entry {
241 242
        kind: abi::Kind::Domain, object: owner, rights: abi::Rights(abi::ALLOCATE),
261 262
    };
262 263
    try pages::split(&mut STORE, &mut TABLE, handle, 2) catch err {
263 264
        try testing::expect(err == abi::Error::Exhausted); set failed += 1;
264 265
    };
265 266
    let page = try! pages::get(&STORE, entry.object);
266 -
    try testing::expect(failed == 2 and page.count == 4 and STORE.backings.pool.free[4]);
267 +
    assert failed == 2 and page.count == 4 and frames::available(&STORE.backings.pool, 4);
267 268
}
268 269
269 270
/// Failure at each storage stage releases all earlier reservations.
270 271
@test unsafe fn rollback() throws (testing::TestError) {
271 272
    initialize();
285 286
        }
286 287
        let mut exhausted = false;
287 288
        try pages::allocate(&mut STORE, &mut TABLE, abi::Handle(0), 1) catch err {
288 289
            try testing::expect(err == abi::Error::Exhausted); set exhausted = true;
289 290
        };
290 -
        try testing::expect(exhausted and TABLE.slots[1].state == slots::State::Free and STORE.backings.pool.free[0]);
291 +
        assert exhausted and TABLE.slots[1].state == slots::State::Free and frames::available(&STORE.backings.pool, 0);
291 292
        if stage == 0 { try testing::expect(STORE.slots[0].state == slots::State::Free); }
292 293
    }
293 294
}
294 295
295 296
/// A Domain handle with Allocate rights must name the caller to allocate RAM.
300 301
    });
301 302
    let mut denied = false;
302 303
    try pages::allocate(&mut STORE, &mut TABLE, other, 1) catch err {
303 304
        try testing::expect(err == abi::Error::Denied); set denied = true;
304 305
    };
305 -
    try testing::expect(denied and STORE.backings.pool.free[0] and TABLE.slots[2].state == slots::State::Free);
306 +
    assert denied and frames::available(&STORE.backings.pool, 0) and TABLE.slots[2].state == slots::State::Free;
306 307
}
307 308
308 309
/// Buffer authority covers complete ranges and rejects overflow and stale objects.
309 310
@test unsafe fn bufferAuthority() throws (testing::TestError) {
310 311
    initialize();
test/runtime/kernel/dispatchcheck.rad added +216 -0
1 +
//! Runtime compiler ownership across scheduled windows and domain destruction.
2 +
use std::mem;
3 +
use kernel::abi;
4 +
use kernel::frames;
5 +
use kernel::slots;
6 +
use kernel::backing;
7 +
use kernel::pages;
8 +
use kernel::capability;
9 +
use kernel::registry;
10 +
use kernel::loader;
11 +
use kernel::domains;
12 +
use kernel::budgets;
13 +
use kernel::dispatch;
14 +
use kernel::boot;
15 +
use kernel::sync;
16 +
use kernel::calls;
17 +
use kernel::lifecycle;
18 +
use kernel::remote;
19 +
use kernel::dispatchinput;
20 +
21 +
/// Bootstrap resource capabilities.
22 +
unsafe static TABLE: capability::Table = undefined;
23 +
/// First loader, retry loader, witness, and controller domain handles.
24 +
unsafe static HANDLES: [abi::Handle; 4] = undefined;
25 +
/// Generation-bearing domain identities for each actor.
26 +
unsafe static ACTORS: [abi::Ref; 4] = undefined;
27 +
/// Initial context identities for each actor.
28 +
unsafe static CONTEXTS: [abi::Ref; 4] = undefined;
29 +
/// Controller capability for the first loader.
30 +
unsafe static VICTIM: abi::Handle = undefined;
31 +
/// Controller capability for the binary input page.
32 +
unsafe static SOURCE: abi::Handle = undefined;
33 +
/// Shared user progress and published package identity.
34 +
unsafe static PROGRESS: *mut u64 = undefined;
35 +
/// Start of the controller's final execution window.
36 +
static FINAL: u64 = 0;
37 +
/// Write one diagnostic byte to the fixture UART.
38 +
fn put(byte: u8);
39 +
/// Write a fixed-width hexadecimal diagnostic value.
40 +
fn number(value: u64) {
41 +
    let digits = "0123456789abcdef";
42 +
    for i in 0..16 { put(digits[((value >> ((15 - i) as u64 * 4)) & 15) as u32]); }
43 +
}
44 +
45 +
/// Map a validated physical page.
46 +
fn memory(address: u64) -> *mut u8;
47 +
/// Current kernel package-state base.
48 +
fn kernelGp() -> u64;
49 +
/// Native controller entry address.
50 +
fn completion() -> u64;
51 +
/// Execute published code with one private package-state table.
52 +
fn invoke(address: u64, table: u64) -> i64;
53 +
/// Signal native fixture completion.
54 +
fn finish();
55 +
56 +
/// Copy trusted package bytes into a bootstrap-owned page allocation.
57 +
unsafe fn input(authority: abi::Handle, bytes: &[u8]) -> abi::Handle {
58 +
    let handle = try! pages::allocate(&mut pages::STORE, &mut TABLE, authority, (bytes.len as u64 + 4095) / 4096);
59 +
    let page = try! pages::get(&pages::STORE, (try! capability::get(&TABLE, handle)).object);
60 +
    let copied = try! mem::copy(@sliceOf(memory(page.base), page.count * 4096), bytes);
61 +
    assert copied == bytes.len;
62 +
    return handle;
63 +
}
64 +
65 +
/// Install user actors and disjoint windows that interrupt two compiler calls.
66 +
export unsafe fn setup() {
67 +
    let pending = try! slots::reserve(&mut domains::STORE.slots[..]);
68 +
    let owner = try! slots::commit(&mut domains::STORE.slots[..], pending);
69 +
    capability::initialize(&mut TABLE, owner);
70 +
    try! backing::registerDomain(&mut pages::STORE.backings, owner);
71 +
    let authority = try! capability::install(&mut TABLE, capability::Entry {
72 +
        kind: abi::Kind::Domain, object: owner, rights: abi::Rights(abi::CREATE | abi::ALLOCATE),
73 +
    });
74 +
    let source = input(authority, &dispatchinput::INPUT[..]);
75 +
    let image = try! loader::load(&mut loader::STATE, &mut pages::STORE, &mut registry::STORE, &mut TABLE,
76 +
        loader::Request { authority, source, offset: 0, length: dispatchinput::INPUT.len as u64 });
77 +
    let payload = input(authority, &dispatchinput::PAYLOAD[..]);
78 +
    let progress = try! pages::allocate(&mut pages::STORE, &mut TABLE, authority, 1);
79 +
    let shared = try! pages::get(&pages::STORE, (try! capability::get(&TABLE, progress)).object);
80 +
    set PROGRESS = memory(shared.base) as *mut u64;
81 +
    for i in 0..4 {
82 +
        let handle = try! domains::create(&mut domains::STORE, &mut pages::STORE.backings, &registry::STORE, &mut TABLE, authority, image);
83 +
        let object = (try! capability::get(&TABLE, handle)).object;
84 +
        let mut child = try! domains::get(&domains::STORE, object);
85 +
        let mut rights = abi::ALLOCATE;
86 +
        if i == 3 { set rights |= abi::CREATE; }
87 +
        let self = try! capability::install(&mut child.memory.table, capability::Entry {
88 +
            kind: abi::Kind::Domain, object, rights: abi::Rights(rights),
89 +
        });
90 +
        set HANDLES[i] = handle; set ACTORS[i] = object; set CONTEXTS[i] = child.initial;
91 +
        let readable = try! pages::grant(&mut pages::STORE, &TABLE, &mut child.memory.table, payload, abi::READ as u64);
92 +
        let writable = try! pages::grant(&mut pages::STORE, &TABLE, &mut child.memory.table, progress, (abi::READ | abi::WRITE) as u64);
93 +
        if i == 3 {
94 +
            set SOURCE = readable;
95 +
            set VICTIM = try! capability::install(&mut child.memory.table, capability::Entry {
96 +
                kind: abi::Kind::Domain, object: ACTORS[0], rights: abi::Rights(abi::DESTROY),
97 +
            });
98 +
            set domains::STORE.records[object.index].state = domains::Lifecycle::Active;
99 +
            let frame = &mut domains::STORE.contexts[child.initial.index].frame;
100 +
            set frame.pc = completion(); set frame.status = 0x1880;
101 +
            set frame.registers[2] = domains::STORE.contexts[child.initial.index].kernelStack.end;
102 +
            set frame.registers[3] = kernelGp();
103 +
            continue;
104 +
        }
105 +
        let stack = try! pages::allocate(&mut pages::STORE, &mut TABLE, authority, 1);
106 +
        let granted = try! pages::grant(&mut pages::STORE, &TABLE, &mut child.memory.table, stack, (abi::READ | abi::WRITE) as u64);
107 +
        let storage = try! pages::get(&pages::STORE, (try! capability::get(&TABLE, stack)).object);
108 +
        let args = @sliceOf(memory(storage.base) as *mut u64, 3);
109 +
        set args[0] = *readable; set args[1] = dispatchinput::PAYLOAD.len as u64; set args[2] = shared.base;
110 +
        try! domains::activate(&mut domains::STORE, &pages::STORE, &TABLE, handle, storage.base + 4096, storage.base, i as u64);
111 +
    }
112 +
    for byte in "runtime actors ready\n" { put(byte); }
113 +
    let now = dispatch::clock();
114 +
    let start = now + 1000000;
115 +
    set FINAL = start + 300000000;
116 +
    let mut remaining = try! budgets::seed(&mut budgets::STORE, &mut TABLE, 0, start, FINAL + 10000000);
117 +
    let actors = [0 as u32, 3, 1, 2, 1];
118 +
    let ends = [10000000 as u64, 50000000, 60000000, 62000000, 300000000];
119 +
    for i in 0..5 {
120 +
        let next = try! budgets::split(&mut budgets::STORE, &mut TABLE, remaining, start + ends[i], now);
121 +
        let actor = actors[i];
122 +
        let bound = try! budgets::bind(&mut budgets::STORE, &domains::STORE, &mut TABLE,
123 +
            budgets::Binding { budget: remaining, domain: HANDLES[actor], context: CONTEXTS[actor] }, now);
124 +
        set remaining = next;
125 +
    }
126 +
    let bound = try! budgets::bind(&mut budgets::STORE, &domains::STORE, &mut TABLE,
127 +
        budgets::Binding { budget: remaining, domain: HANDLES[3], context: CONTEXTS[3] }, now);
128 +
}
129 +
130 +
/// Verify cancellation, competing admission, progress, and resident code reuse.
131 +
export unsafe fn verify() {
132 +
    for byte in "runtime admission metadata instructions: " { put(byte); }
133 +
    number(sync::maximum()); put(10);
134 +
    let progress = @sliceOf(PROGRESS, 4);
135 +
    let guard = sync::enter();
136 +
    let first = CONTEXTS[0];
137 +
    for byte in "runtime first progress and owner: " { put(byte); }
138 +
    number(progress[0]); put(32); number(abi::id(loader::STATE.owner)); put(10);
139 +
    assert progress[0] == 1 and loader::STATE.busy and loader::STATE.owner == ACTORS[0];
140 +
    assert domains::STORE.contexts[first.index].reservation;
141 +
    assert (domains::STORE.contexts[first.index].frame.status & 0x1800) == 0x1800;
142 +
    let allocation = lifecycle::CALLS.records[first.index].allocation;
143 +
    let mut loading: lifecycle::Loading = undefined;
144 +
    match allocation {
145 +
        case lifecycle::Allocation::Load(value) => { set loading = value; },
146 +
        else => panic "loader reservation required",
147 +
    }
148 +
    let output = loading.output else panic "compilation output required";
149 +
    sync::leave(guard);
150 +
    let mut busy = false;
151 +
    try calls::synchronized(ACTORS[3], 51, &[0, *SOURCE, 0, dispatchinput::PAYLOAD.len as u64], dispatch::clock()) catch error {
152 +
        assert error == abi::Error::Busy; set busy = true;
153 +
    };
154 +
    assert busy;
155 +
    for byte in "runtime competing load rejected\n" { put(byte); }
156 +
    let destroyed = try! calls::synchronized(ACTORS[3], 22, &[*VICTIM, 0, 0, 0], dispatch::clock());
157 +
    assert destroyed == 0;
158 +
    let checking = sync::enter();
159 +
    let request = try! dispatch::request(0, remote::Action::Reschedule);
160 +
    sync::leave(checking);
161 +
    let result = try! dispatch::awaitRequest(0, request);
162 +
    assert result == 0;
163 +
    let cancelled = sync::enter();
164 +
    assert not loader::STATE.busy;
165 +
    assert (lifecycle::CALLS.active[first.index / 32] & (1 << (first.index % 32))) == 0;
166 +
    assert registry::STORE.slots[output.object.index].state == slots::State::Free;
167 +
    sync::leave(cancelled);
168 +
    let mut reclaimed = false;
169 +
    for attempt in 0..10000 {
170 +
        let guard = sync::enter();
171 +
        set reclaimed = pages::STORE.backings.pool.retiredCount == 0;
172 +
        if reclaimed { sync::leave(guard); break; }
173 +
        let request = try! dispatch::request(0, remote::Action::Reschedule);
174 +
        sync::leave(guard);
175 +
        let result = try! dispatch::awaitRequest(0, request);
176 +
        assert result == 0;
177 +
    }
178 +
    assert reclaimed;
179 +
    for run in [loading.input.frames, output.code, output.metadata] {
180 +
        let mut offset: u32 = 0;
181 +
        while offset < run.count {
182 +
            let mut count = run.count - offset;
183 +
            if count > 256 { set count = 256; }
184 +
            let recovered = sync::enter();
185 +
            for i in run.first + offset..run.first + offset + count { assert frames::available(&pages::STORE.backings.pool, i); }
186 +
            sync::leave(recovered);
187 +
            set offset += count;
188 +
        }
189 +
    }
190 +
    for byte in "runtime cancellation passed\n" { put(byte); }
191 +
    while dispatch::clock() < FINAL {}
192 +
    let complete = sync::enter();
193 +
    for byte in "runtime final progress: " { put(byte); }
194 +
    for i in 0..4 { number(progress[i]); put(32); }
195 +
    put(10);
196 +
    assert progress[0] == 1 and progress[1] == 2 and progress[2] == 1;
197 +
    assert not loader::STATE.busy;
198 +
    let package = try! registry::get(&registry::STORE, abi::reference(progress[3]));
199 +
    let entry = package.entry else panic "published entry required";
200 +
    let mut controller = try! domains::get(&domains::STORE, ACTORS[3]);
201 +
    let image = try! registry::install(&registry::STORE, &mut controller.memory.table, abi::reference(progress[3]));
202 +
    sync::leave(complete);
203 +
    let handle = abi::Handle(try! calls::synchronized(ACTORS[3], 20, &[0, *image, 0, 0], dispatch::clock()));
204 +
    let created = sync::enter();
205 +
    let object = (try! capability::get(&controller.memory.table, handle)).object;
206 +
    let domain = try! domains::get(&domains::STORE, object);
207 +
    sync::leave(created);
208 +
    assert invoke(entry, domain.graph.table.ptr as u64) == 43;
209 +
    assert invoke(entry, domain.graph.table.ptr as u64) == 85;
210 +
    for byte in "runtime retry published executable code\n" { put(byte); }
211 +
    let maximum = sync::maximum();
212 +
    for byte in "runtime metadata instructions: " { put(byte); }
213 +
    number(maximum); put(10);
214 +
    assert maximum > 0 and maximum < 1000000;
215 +
    finish();
216 +
}
test/runtime/machine.ras added +44 -0
1 +
//! Boot one hart and check user termination through production dispatch.
2 +
.text;
3 +
    call @kernel::boot::initialize;
4 +
    call @kernel::dispatchcheck::setup;
5 +
    call @kernel::boot::run;
6 +
.export @kernel::dispatchcheck::memory;
7 +
.export @kernel::dispatchcheck::kernelGp;
8 +
.export @kernel::dispatchcheck::completion;
9 +
.export @kernel::dispatchcheck::finish;
10 +
@kernel::dispatchcheck::memory
11 +
    ret;
12 +
@kernel::dispatchcheck::kernelGp
13 +
    mv %a0 %gp;
14 +
    ret;
15 +
@kernel::dispatchcheck::completion
16 +
    la %a0 @complete;
17 +
    ret;
18 +
@complete
19 +
    call @kernel::dispatchcheck::verify;
20 +
    ebreak;
21 +
@kernel::dispatchcheck::finish
22 +
    li %t0 0x10001000;
23 +
    li %t1 0x5555;
24 +
    sw %t1 0(%t0);
25 +
    ebreak;
26 +
27 +
.export @kernel::dispatchcheck::put;
28 +
@kernel::dispatchcheck::put
29 +
    li %t0 0x10000000;
30 +
    sb %a0 0(%t0);
31 +
    ret;
32 +
33 +
.export @kernel::dispatchcheck::invoke;
34 +
// Preserve kernel state while calling one private instance of shared code.
35 +
@kernel::dispatchcheck::invoke
36 +
    addi %sp %sp -16;
37 +
    sd %ra 8(%sp);
38 +
    sd %gp 0(%sp);
39 +
    mv %gp %a1;
40 +
    jalr %ra %a0 0;
41 +
    ld %gp 0(%sp);
42 +
    ld %ra 8(%sp);
43 +
    addi %sp %sp 16;
44 +
    ret;
test/runtime/run added +39 -0
1 +
#!/bin/sh
2 +
# Compile, cancel, and retry a package through scheduled user calls.
3 +
set -eu
4 +
emulator=${RAD_EMULATOR:-emulator}
5 +
fixture=runtime
6 +
work=$(mktemp -d)
7 +
trap 'rm -rf "$work"' EXIT HUP INT TERM
8 +
mkdir "$work/spin"
9 +
cp "test/$fixture/spin.rad" "$work/spin.rad"
10 +
cp kernel/kernel/abi.rad kernel/kernel/sys.rad "$work/spin/"
11 +
"$emulator" -memory-size=385024 -data-size=348160 -stack-size=512 -run bin/radiance.rv64.dev \
12 +
    -pkg spin -mod "$work/spin.rad" -mod "$work/spin/abi.rad" -mod "$work/spin/sys.rad" -entry spin -ril "$work"
13 +
cp test/loader/loaded.rad "$work/loaded.rad"
14 +
index=0
15 +
while [ "$index" -lt 128 ]; do
16 +
    printf '\n/// Compute a distinct exported value.\nexport fn value%s(input: u64) -> u64 { return input + %s; }\n' "$index" "$index" >> "$work/loaded.rad"
17 +
    index=$((index + 1))
18 +
done
19 +
"$emulator" -memory-size=385024 -data-size=348160 -stack-size=512 -run bin/radiance.rv64.dev \
20 +
    -pkg loaded -mod "$work/loaded.rad" -entry loaded -ril "$work"
21 +
cp kernel/kernel.rad "$work/kernel.rad"
22 +
printf '\nexport mod dispatchinput;\nexport mod dispatchcheck;\n' >> "$work/kernel.rad"
23 +
mkdir "$work/kernel"
24 +
cp kernel/kernel/*.rad "$work/kernel/"
25 +
cp "test/$fixture/kernel/dispatchcheck.rad" "$work/kernel/"
26 +
length=$(wc -c < "$work/spin.ril")
27 +
printf '//! Binary user loop.\n/// Complete trusted input package.\nexport static INPUT: [u8; %s] = [\n' "$length" > "$work/kernel/dispatchinput.rad"
28 +
od -An -v -tu1 "$work/spin.ril" | awk '{ for (i = 1; i <= NF; i++) printf "%s,", $i; print "" }' >> "$work/kernel/dispatchinput.rad"
29 +
printf '];\n' >> "$work/kernel/dispatchinput.rad"
30 +
length=$(wc -c < "$work/loaded.ril")
31 +
printf '\n/// Package compiled by scheduled user calls.\nexport static PAYLOAD: [u8; %s] = [\n' "$length" >> "$work/kernel/dispatchinput.rad"
32 +
od -An -v -tu1 "$work/loaded.ril" | awk '{ for (i = 1; i <= NF; i++) printf "%s,", $i; print "" }' >> "$work/kernel/dispatchinput.rad"
33 +
printf '];\n' >> "$work/kernel/dispatchinput.rad"
34 +
sh test/acceptance/compile "$emulator" "$work"
35 +
cat "test/$fixture/machine.ras" kernel/kernel/*.ras > "$work/dispatch.ras"
36 +
"$emulator" -memory-size=385024 -data-size=348160 -stack-size=512 -run bin/kernel.build.rv64 \
37 +
    -- "$work/std.ril" "$work/kernel.ril" "$work/dispatch.ras" "$work/dispatch.rv64"
38 +
"$emulator" -machine -harts=1 -memory-size=262144 -max-steps=2000000000 -run "$work/dispatch.rv64"
39 +
printf 'runtime: scheduled cancellation, retry, and publication passed\n'
test/runtime/spin.rad added +53 -0
1 +
//! Scheduled runtime compilation and an independent window witness.
2 +
export mod abi;
3 +
export mod sys;
4 +
5 +
/// Startup fields used by the scheduled fixture.
6 +
record Request: Copy {
7 +
    /// Readable binary package capability.
8 +
    source: u64,
9 +
    /// Complete binary package byte count.
10 +
    length: u64,
11 +
    /// Shared progress counters.
12 +
    progress: *unsafe mut u64,
13 +
}
14 +
15 +
/// Values supplied at domain activation.
16 +
record Env: Copy {
17 +
    /// Input handle, byte count, and shared progress address.
18 +
    argsPointer: *Request,
19 +
    /// Actor index selected by the parent.
20 +
    argsSize: u64,
21 +
    /// Event capability installed at creation.
22 +
    eventsHandle: u64,
23 +
    /// Shared event queue address.
24 +
    eventsPointer: u64,
25 +
}
26 +
27 +
/// Compile a package twice or record progress during another domain's load.
28 +
@default unsafe fn main(env: *Env) {
29 +
    let args = env.argsPointer;
30 +
    let progress = @sliceOf(args.progress, 4);
31 +
    let actor = env.argsSize as u32;
32 +
    if actor == 2 {
33 +
        assert progress[1] == 1;
34 +
        let mut busy = false;
35 +
        try sys::imageLoad(abi::Handle(0), abi::Handle(args.source), 0, args.length) catch error {
36 +
            assert error == abi::Error::Busy; set busy = true;
37 +
        };
38 +
        assert busy;
39 +
        set progress[2] = 1;
40 +
        return;
41 +
    }
42 +
    set progress[actor] = 1;
43 +
    let image = try sys::imageLoad(abi::Handle(0), abi::Handle(args.source), 0, args.length) catch error {
44 +
        set progress[actor] = 100 + error as u64; return;
45 +
    };
46 +
    let info = try! sys::queryImage(image);
47 +
    let repeated = try sys::imageLoad(abi::Handle(0), abi::Handle(args.source), 0, args.length) catch error {
48 +
        set progress[actor] = 200 + error as u64; return;
49 +
    };
50 +
    assert (try! sys::queryImage(repeated)).id == info.id;
51 +
    set progress[3] = info.id;
52 +
    set progress[actor] = 2;
53 +
}
test/smp/kernel/dispatchcheck.rad +2 -1
1 1
//! One domain executing separate user contexts on every online hart.
2 2
use std::mem;
3 3
use std::arch::rv64::shared;
4 4
use kernel::abi;
5 +
use kernel::frames;
5 6
use kernel::slots;
6 7
use kernel::backing;
7 8
use kernel::pages;
8 9
use kernel::capability;
9 10
use kernel::registry;
564 565
    let guard = sync::enter();
565 566
    assert (lifecycle::CALLS.active[IDLE_CONTEXT.index / 32] & (1 << (IDLE_CONTEXT.index % 32))) == 0;
566 567
    assert pages::STORE.slots[allocation.page.index].state == slots::State::Free;
567 568
    assert pages::STORE.backings.slots[allocation.backing.index].state == slots::State::Free;
568 569
    for i in allocation.run.first..allocation.run.first + allocation.run.count {
569 -
        assert pages::STORE.backings.pool.free[i];
570 +
        assert frames::available(&pages::STORE.backings.pool, i);
570 571
    }
571 572
    sync::leave(guard);
572 573
    for byte in "smp cancelled preempted page allocation\n" { put(byte); }
573 574
}
574 575