assembler: bound image linking storage and encode machine fences

fdf8f5d1395694cd90e79bb283efee2b1d20b6c4f9615dbb293f3061530b89f8
Verified: make -C kernel check; make std-test bin-test with the machine-capable emulator; all pass.
Alexis Sellier committed ago 1 parent 566537b1
kernel/NOTES.md +11 -2
1 1
# Kernel implementation decisions
2 2
3 3
The specification at https://radiant.computer/system/kernel takes precedence
4 4
for fixed call numbers, handle layout, rights, and object behavior. These notes
5 -
record the contracts established through step 14 of the 22-step plan.
5 +
record the contracts established through step 15 of the 22-step plan.
6 6
7 7
## Source and trust boundary
8 8
9 9
- Kernel mechanisms use freestanding Radiance; RAS owns machine entry, register
10 10
  state, atomics, and MMIO. Hosted checks exercise the same mechanism modules.
189 189
  exclusive receiver transfer.
190 190
- Missing, dead, or stale receiver authority leaves the source masked. Invalid
191 191
  consumer progress cannot release a claim or rearm a source. The two-hart
192 192
  IRQ/UART check exercises the primary PLIC route, not multicore dispatch.
193 193
194 +
## Assembler storage and machine fences
195 +
196 +
- Size assembler symbol, fixup, and hash-table storage from possible name tokens,
197 +
  not source byte length. Whitespace, comments, and literal bytes consume no
198 +
  symbol slots.
199 +
- Bare fence orders memory and device I/O (iorw,iorw). fence.i synchronizes
200 +
  local instruction fetch. These are distinct ordering obligations.
201 +
194 202
## Validation
195 203
196 204
Use the current machine-capable sibling emulator. Set `RAD_EMULATOR`, pass
197 205
`EMU` to the kernel Make invocation, or put `emulator` on PATH. The kernel build
198 206
checks compiler dependencies. From the repository root, run:
199 207
200 208
```sh
201 209
make -C kernel check
210 +
make std-test bin-test
202 211
```
203 212
204 -
Run the two-hart IRQ/UART scenario with IRQ 3 at tick 4,000,000 and UART byte R at tick 8,000,000. Check retained-claim transfer through a full queue, one delivery, draining, dropped authority, and the next timer wake.
213 +
Exercise name-token-bounded assembler allocation and both fence encodings along with the existing kernel machine paths.
205 214
206 215
Run the context reservation probe with an emulator that retains LR/SC
207 216
reservations across traps. This checks the kernel's reservation invalidation.
lib/std/arch/rv64/asm.rad +20 -2
124 124
125 125
/// Parser and encoder behavior for one instruction mnemonic.
126 126
export union InstructionEncoder: Copy {
127 127
    /// No-operand instruction encoded by a fixed encoder.
128 128
    NoOperand { enc: fn() -> u32 },
129 +
    /// Memory/I/O fence or its `.i` instruction-cache form.
130 +
    Fence,
129 131
    /// Load-immediate pseudo-instruction.
130 132
    Li,
131 133
    /// Load-address pseudo-instruction.
132 134
    La,
133 135
    /// Two-register instruction or pseudo-instruction.
301 303
    { name: "divu",   encoder: InstructionEncoder::RRR { enc: encode::divu } },
302 304
    { name: "divuw",  encoder: InstructionEncoder::RRR { enc: encode::divuw } },
303 305
    { name: "divw",   encoder: InstructionEncoder::RRR { enc: encode::divw } },
304 306
    { name: "ebreak", encoder: InstructionEncoder::NoOperand { enc: encode::ebreak } },
305 307
    { name: "ecall",  encoder: InstructionEncoder::NoOperand { enc: encode::ecall } },
306 -
    { name: "fence",  encoder: InstructionEncoder::NoOperand { enc: encode::fence } },
308 +
    { name: "fence",  encoder: InstructionEncoder::Fence },
307 309
    { name: "j",      encoder: InstructionEncoder::Jump { rd: rv64::ZERO } },
308 310
    { name: "jal",    encoder: InstructionEncoder::Jal },
309 311
    { name: "jalr",   encoder: InstructionEncoder::RRI { enc: encode::jalr } },
310 312
    { name: "la",     encoder: InstructionEncoder::La },
311 313
    { name: "lb",     encoder: InstructionEncoder::Load { enc: encode::lb } },
490 492
    externalFixups: *mut [Fixup],
491 493
    /// Absolute runtime address of data-section offset zero.
492 494
    dataBase: u32,
493 495
}
494 496
497 +
/// Bound symbol, fixup, and dictionary entries by their possible name tokens.
498 +
/// Comments, whitespace, and literal byte contents need no symbol storage.
499 +
fn symbolCapacity(sourceKind: scanner::SourceKind, source: *[u8], pool: *mut strings::Pool) -> u32 throws (Error) {
500 +
    let mut scan = scanner::scanner(sourceKind, source, pool);
501 +
    let mut count = SOURCE_CAP_PADDING;
502 +
    loop {
503 +
        let token = scanner::next(&mut scan);
504 +
        match token.kind {
505 +
            case scanner::TokenKind::Eof => return count,
506 +
            case scanner::TokenKind::Invalid => throw Error::Invalid { offset: token.offset, message: token.source },
507 +
            case scanner::TokenKind::Ident, scanner::TokenKind::Label, scanner::TokenKind::QuotedLabel => set count += 1,
508 +
            else => {},
509 +
        }
510 +
    }
511 +
}
512 +
495 513
/// Assemble source using `dataBase` as the runtime address of the data-section.
496 514
export fn assemble(
497 515
    sourceKind: scanner::SourceKind,
498 516
    source: *[u8],
499 517
    textBuf: *mut [u32],
500 518
    dataBuf: *mut [u8],
501 519
    arena: *mut alloc::Arena,
502 520
    pool: *mut strings::Pool,
503 521
    dataBase: u32
504 522
) -> Program throws (Error) {
505 -
    let slotCap = source.len + SOURCE_CAP_PADDING;
523 +
    let slotCap = try symbolCapacity(sourceKind, source, pool);
506 524
    let tableCap = nextPowerOfTwo(slotCap * TABLE_CAPACITY_SCALE);
507 525
508 526
    let symbols = try! alloc::allocSlice(arena, @sizeOf(Symbol), @alignOf(Symbol), slotCap);
509 527
    let fixups = try! alloc::allocSlice(arena, @sizeOf(Fixup), @alignOf(Fixup), slotCap);
510 528
    let externalFixups = try! alloc::allocSlice(arena, @sizeOf(Fixup), @alignOf(Fixup), slotCap);
lib/std/arch/rv64/asm/parser.rad +16 -0
292 292
                throw fail(a, "unexpected operand");
293 293
            }
294 294
            try emit::emitText(a, enc());
295 295
            return;
296 296
        }
297 +
        case super::InstructionEncoder::Fence => return try parseFence(a, tok),
297 298
        case super::InstructionEncoder::Li => return try parseLi(a),
298 299
        case super::InstructionEncoder::La => return try parseLa(a),
299 300
        case super::InstructionEncoder::RR { enc } => return try parseRR(a, enc),
300 301
        case super::InstructionEncoder::RRR { enc } => return try parseRRR(a, enc),
301 302
        case super::InstructionEncoder::RRI { enc } => return try parseRRI(a, enc),
316 317
        case super::InstructionEncoder::Csrsi => return try parseCsrsi(a),
317 318
        case super::InstructionEncoder::Upper { enc } => return try parseUpper(a, enc),
318 319
    }
319 320
}
320 321
322 +
/// Parse a full memory/I/O fence or the adjacent `.i` instruction-cache suffix.
323 +
fn parseFence(a: *mut super::Assembler, mnemonic: scanner::Token) throws (super::Error) {
324 +
    let mut instruction = encode::fence();
325 +
    if a.scan.current.kind == scanner::TokenKind::Directive {
326 +
        if a.scan.current.offset <> mnemonic.offset + mnemonic.source.len
327 +
            or not mem::eq(a.scan.current.source, ".i") {
328 +
            throw fail(a, "expected adjacent .i fence suffix");
329 +
        }
330 +
        advance(a);
331 +
        set instruction = encode::fenceI();
332 +
    }
333 +
    try expectTerminator(a, "unexpected fence operand");
334 +
    try emit::emitText(a, instruction);
335 +
}
336 +
321 337
/// Parse the `li` pseudo-instruction.
322 338
fn parseLi(a: *mut super::Assembler) throws (super::Error) {
323 339
    let rd = try parseRegister(a);
324 340
    let value = try parseValue(a);
325 341
    if encode::isSmallImm64(value) {
lib/std/arch/rv64/asm/tests.rad +23 -0
91 91
    try testing::expect(program.text.len == 2);
92 92
    try testing::expect(program.text[0] == encode::jal(rv64::ZERO, 4));
93 93
    try testing::expect(program.text[1] == encode::jalr(rv64::ZERO, rv64::RA, 0));
94 94
}
95 95
96 +
/// Verbose source must fit an arena sized for its small assembled program.
97 +
@test fn testAssembleLongCommentWithBoundedSymbolStorage() throws (testing::TestError) {
98 +
    let mut source: [u8; 8192] = ['x'; 8192];
99 +
    set source[0] = '/';
100 +
    set source[1] = '/';
101 +
    let body = "\n.text; j @\"local::destination\"; @\"local::destination\" ret;";
102 +
    let _copied = try! mem::copy(&mut source[8192 - body.len..], body);
103 +
    let program = try assembleSource(&source[..]);
104 +
    try testing::expect(program.text.len == 2);
105 +
    try testing::expect(program.text[0] == encode::jal(rv64::ZERO, 4));
106 +
    try testing::expect(program.text[1] == encode::jalr(rv64::ZERO, rv64::RA, 0));
107 +
}
108 +
109 +
/// Fence encodings must order device I/O and synchronize instruction fetches.
110 +
@test fn testAssembleMachineFences() throws (testing::TestError) {
111 +
    let program = try assembleSource(".text; fence; fence.i;");
112 +
    try testing::expect(program.text.len == 2);
113 +
    try testing::expect(program.text[0] == 0x0ff0000f);
114 +
    try testing::expect(program.text[1] == 0x0000100f);
115 +
    try expectAssembleFail(".text; fence.i %a0;");
116 +
    try expectAssembleFail(".text; fence.x;");
117 +
}
118 +
96 119
@test fn testAssembleGlobalMarksOnlyDeclaredSymbols() throws (testing::TestError) {
97 120
    let program = try assembleSource(
98 121
        ".text;\n.export @exported;\n@local\nret;\n@exported\nret;\n@late\n.export @late;\nret;\n"
99 122
    );
100 123
    try testing::expect(program.symbols.len == 3);
lib/std/arch/rv64/encode.rad +7 -2
585 585
/// Environment break (debugger breakpoint).
586 586
export fn ebreak() -> u32 {
587 587
    return encodeI(OP_SYSTEM, super::ZERO, super::ZERO, 0, 1);
588 588
}
589 589
590 -
/// Full predecessor/successor memory fence (`fence rw, rw`).
590 +
/// Full predecessor/successor memory and device-I/O fence (`fence iorw, iorw`).
591 591
export fn fence() -> u32 {
592 -
    return 0x0330000F;
592 +
    return 0x0FF0000F;
593 +
}
594 +
595 +
/// Synchronize this hart's instruction fetches with prior instruction stores.
596 +
export fn fenceI() -> u32 {
597 +
    return 0x0000100F;
593 598
}
594 599
595 600
/// Encode a CSR instruction with a register source.
596 601
fn encodeCsr(op: u32, rd: gen::Reg, csr: u32, funct3: u32, rs1: gen::Reg) -> u32 {
597 602
    return (op             & 0x7F)