rv64: Add kernel synchronization primitives
50d813c0cb4be77d4ba0818fc75df8730e1dda5faea3ec6ead0c4391014d6479
Assisted-by: Codex:gpt-6
1 parent
7cf509a8
Makefile
+10 -2
| 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 package-test native-test shared-test |
|
| 26 | + | test: emulator seed-test std-test bin-test kernel-test package-test native-test shared-test sync-test |
|
| 27 | 27 | ||
| 28 | 28 | seed-test: |
|
| 29 | 29 | @seed/test |
|
| 30 | 30 | ||
| 31 | 31 | # Emulator command check |
| 102 | 102 | @RAD_EMULATOR="$(EMU)" test/shared/run |
|
| 103 | 103 | ||
| 104 | 104 | $(BIN_DIR)/shared.build.rv64: test/shared/build.rad $(STD_LIB) $(RAD_BIN) |
|
| 105 | 105 | @$(RADIANCE) $(STD) -pkg build -mod $< -entry build -o $@ |
|
| 106 | 106 | ||
| 107 | + | # Kernel synchronization machine tests |
|
| 108 | + | ||
| 109 | + | sync-test: $(BIN_DIR)/sync.build.rv64 |
|
| 110 | + | @RAD_EMULATOR="$(EMU)" test/sync/run |
|
| 111 | + | ||
| 112 | + | $(BIN_DIR)/sync.build.rv64: test/sync/build.rad $(STD_LIB) $(RAD_BIN) |
|
| 113 | + | @$(RADIANCE) $(STD) -pkg build -mod $< -entry build -o $@ |
|
| 114 | + | ||
| 107 | 115 | # Binary Tests |
|
| 108 | 116 | ||
| 109 | 117 | BIN_TEST_DIR := test/tests |
|
| 110 | 118 | # Only tests with `//! returns:` are compiled to binaries and executed. |
|
| 111 | 119 | BIN_TEST_EXE_SRC := $(shell grep -rl '^//! returns:' $(BIN_TEST_DIR)) |
| 159 | 167 | clean: clean-std-test clean-bin-test clean-rad |
|
| 160 | 168 | ||
| 161 | 169 | t: test |
|
| 162 | 170 | c: clean |
|
| 163 | 171 | ||
| 164 | - | .PHONY: test clean default seed-test std-test bin-test kernel-test package-test native-test shared-test seed \ |
|
| 172 | + | .PHONY: test clean default seed-test std-test bin-test kernel-test package-test native-test shared-test sync-test seed \ |
|
| 165 | 173 | clean-std-test clean-bin-test clean-rad emulator |
|
| 166 | 174 | .SUFFIXES: |
|
| 167 | 175 | .DELETE_ON_ERROR: |
|
| 168 | 176 | .SILENT: |
kernel/kernel.rad
+1 -0
| 1 | 1 | //! Kernel resource management and machine execution. |
|
| 2 | 2 | ||
| 3 | 3 | use std::testing; |
|
| 4 | 4 | ||
| 5 | 5 | export mod range; |
|
| 6 | + | export mod sync; |
|
| 6 | 7 | @test export mod tests; |
kernel/kernel/sync.rad
added
+22 -0
| 1 | + | //! RV64 synchronization and ordered device access. |
|
| 2 | + | ||
| 3 | + | /// Allocate one wrapping ticket from a naturally aligned u32 counter. |
|
| 4 | + | export fn nextTicket(counter: &mut u32) -> u32; |
|
| 5 | + | /// Read a naturally aligned shared word with acquire ordering. |
|
| 6 | + | export fn loadAcquire(value: &u64) -> u64; |
|
| 7 | + | /// Publish a naturally aligned shared word with release ordering. |
|
| 8 | + | export fn storeRelease(value: &mut u64, next: u64); |
|
| 9 | + | /// Read a naturally aligned shared 32-bit word with acquire ordering. |
|
| 10 | + | export fn loadAcquire32(value: &u32) -> u32; |
|
| 11 | + | /// Publish a naturally aligned shared 32-bit word with release ordering. |
|
| 12 | + | export fn storeRelease32(value: &mut u32, next: u32); |
|
| 13 | + | /// Add to a naturally aligned shared word and return its previous value. |
|
| 14 | + | export fn fetchAdd(value: &mut u64, amount: u64) -> u64; |
|
| 15 | + | /// Synchronize local instruction fetch after executable bytes become visible. |
|
| 16 | + | export fn syncInstructions(); |
|
| 17 | + | /// Order memory and device accesses in both directions. |
|
| 18 | + | export fn deviceFence(); |
|
| 19 | + | /// Read a 32-bit device register with memory and I/O ordering. |
|
| 20 | + | export unsafe fn read32(address: &u32) -> u32; |
|
| 21 | + | /// Write a 32-bit device register with memory and I/O ordering. |
|
| 22 | + | export unsafe fn write32(address: &mut u32, value: u32); |
kernel/kernel/sync.ras
added
+73 -0
| 1 | + | //! RV64 synchronization boundary. Shared words must be naturally aligned. |
|
| 2 | + | .text; |
|
| 3 | + | .export @kernel::sync::nextTicket; |
|
| 4 | + | .export @kernel::sync::loadAcquire; |
|
| 5 | + | .export @kernel::sync::storeRelease; |
|
| 6 | + | .export @kernel::sync::loadAcquire32; |
|
| 7 | + | .export @kernel::sync::storeRelease32; |
|
| 8 | + | .export @kernel::sync::fetchAdd; |
|
| 9 | + | .export @kernel::sync::syncInstructions; |
|
| 10 | + | .export @kernel::sync::deviceFence; |
|
| 11 | + | .export @kernel::sync::read32; |
|
| 12 | + | .export @kernel::sync::write32; |
|
| 13 | + | ||
| 14 | + | // Allocate a wrapping ticket and return its zero-extended u32 value. |
|
| 15 | + | @kernel::sync::nextTicket |
|
| 16 | + | li %t0 1; |
|
| 17 | + | amoadd.w.aqrl %a0 %t0 (%a0); |
|
| 18 | + | slli %a0 %a0 32; |
|
| 19 | + | srli %a0 %a0 32; |
|
| 20 | + | ret; |
|
| 21 | + | ||
| 22 | + | // Order subsequent memory accesses after this load. |
|
| 23 | + | @kernel::sync::loadAcquire |
|
| 24 | + | ld %a0 0(%a0); |
|
| 25 | + | fence r rw; |
|
| 26 | + | ret; |
|
| 27 | + | ||
| 28 | + | // Order preceding memory accesses before this store. |
|
| 29 | + | @kernel::sync::storeRelease |
|
| 30 | + | fence rw w; |
|
| 31 | + | sd %a1 0(%a0); |
|
| 32 | + | ret; |
|
| 33 | + | ||
| 34 | + | // Order subsequent memory accesses after a zero-extended word load. |
|
| 35 | + | @kernel::sync::loadAcquire32 |
|
| 36 | + | lwu %a0 0(%a0); |
|
| 37 | + | fence r rw; |
|
| 38 | + | ret; |
|
| 39 | + | ||
| 40 | + | // Order preceding memory accesses before a word store. |
|
| 41 | + | @kernel::sync::storeRelease32 |
|
| 42 | + | fence rw w; |
|
| 43 | + | sw %a1 0(%a0); |
|
| 44 | + | ret; |
|
| 45 | + | ||
| 46 | + | // Atomically add with acquire and release ordering. |
|
| 47 | + | @kernel::sync::fetchAdd |
|
| 48 | + | amoadd.d.aqrl %a0 %a1 (%a0); |
|
| 49 | + | ret; |
|
| 50 | + | ||
| 51 | + | // Refresh the local instruction stream after code publication. |
|
| 52 | + | @kernel::sync::syncInstructions |
|
| 53 | + | fence.i; |
|
| 54 | + | ret; |
|
| 55 | + | ||
| 56 | + | // Order both memory and device accesses. |
|
| 57 | + | @kernel::sync::deviceFence |
|
| 58 | + | fence iorw iorw; |
|
| 59 | + | ret; |
|
| 60 | + | ||
| 61 | + | // Read a device word between full I/O barriers. |
|
| 62 | + | @kernel::sync::read32 |
|
| 63 | + | fence iorw iorw; |
|
| 64 | + | lwu %a0 0(%a0); |
|
| 65 | + | fence iorw iorw; |
|
| 66 | + | ret; |
|
| 67 | + | ||
| 68 | + | // Write a device word between full I/O barriers. |
|
| 69 | + | @kernel::sync::write32 |
|
| 70 | + | fence iorw iorw; |
|
| 71 | + | sw %a1 0(%a0); |
|
| 72 | + | fence iorw iorw; |
|
| 73 | + | ret; |
lib/std/arch/rv64.rad
+2 -0
| 9 | 9 | //! * emit: Binary emission context and branch patching |
|
| 10 | 10 | //! * isel: Instruction selection (IL to RV64 instructions) |
|
| 11 | 11 | //! * printer: Assembly text output |
|
| 12 | 12 | ||
| 13 | 13 | export mod image; |
|
| 14 | + | export mod atomics; |
|
| 14 | 15 | export mod shared; |
|
| 15 | 16 | export mod encode; |
|
| 16 | 17 | export mod decode; |
|
| 17 | 18 | export mod emit; |
|
| 18 | 19 | export mod isel; |
|
| 19 | 20 | export mod printer; |
|
| 20 | 21 | export mod asm; |
|
| 21 | 22 | ||
| 22 | 23 | @test mod tests; |
|
| 23 | 24 | @test mod bounds; |
|
| 25 | + | @test mod atomicTests; |
|
| 24 | 26 | ||
| 25 | 27 | use std::mem; |
|
| 26 | 28 | use std::collections::dict; |
|
| 27 | 29 | use std::lang::il; |
|
| 28 | 30 | use std::lang::alloc; |
lib/std/arch/rv64/asm.rad
+5 -2
| 146 | 146 | Store { enc: fn(gen::Reg, gen::Reg, i32) -> u32 }, |
|
| 147 | 147 | /// Two-register branch instruction. |
|
| 148 | 148 | Branch { op: BranchOp }, |
|
| 149 | 149 | /// One-register branch-to-zero pseudo-instruction. |
|
| 150 | 150 | BranchZero { op: BranchOp }, |
|
| 151 | + | /// Memory fence with optional predecessor and successor masks. |
|
| 152 | + | Fence, |
|
| 151 | 153 | /// `jal` instruction with explicit destination register. |
|
| 152 | 154 | Jal, |
|
| 153 | 155 | /// Jump pseudo-instruction with fixed destination register. |
|
| 154 | 156 | Jump { rd: gen::Reg }, |
|
| 155 | 157 | /// CSR read-style operand form. |
| 260 | 262 | export constant SHIFT_LIMIT: i32 = 64; |
|
| 261 | 263 | /// Largest `lui` or `auipc` immediate. |
|
| 262 | 264 | export constant UPPER_IMM_MAX_VALUE: i64 = 0xFFFFF; |
|
| 263 | 265 | ||
| 264 | 266 | /// Sorted instruction descriptor table used by the assembler parser. |
|
| 265 | - | export constant INSTRUCTIONS: [InstructionEntry; 88] = [ |
|
| 267 | + | export constant INSTRUCTIONS: [InstructionEntry; 89] = [ |
|
| 266 | 268 | { name: "add", encoder: InstructionEncoder::RRR { enc: encode::add } }, |
|
| 267 | 269 | { name: "addi", encoder: InstructionEncoder::RRI { enc: encode::addi } }, |
|
| 268 | 270 | { name: "addiw", encoder: InstructionEncoder::RRI { enc: encode::addiw } }, |
|
| 269 | 271 | { name: "addw", encoder: InstructionEncoder::RRR { enc: encode::addw } }, |
|
| 270 | 272 | { name: "and", encoder: InstructionEncoder::RRR { enc: encode::and_ } }, |
| 290 | 292 | { name: "divu", encoder: InstructionEncoder::RRR { enc: encode::divu } }, |
|
| 291 | 293 | { name: "divuw", encoder: InstructionEncoder::RRR { enc: encode::divuw } }, |
|
| 292 | 294 | { name: "divw", encoder: InstructionEncoder::RRR { enc: encode::divw } }, |
|
| 293 | 295 | { name: "ebreak", encoder: InstructionEncoder::NoOperand { enc: encode::ebreak } }, |
|
| 294 | 296 | { name: "ecall", encoder: InstructionEncoder::NoOperand { enc: encode::ecall } }, |
|
| 295 | - | { name: "fence", encoder: InstructionEncoder::NoOperand { enc: encode::fence } }, |
|
| 297 | + | { name: "fence", encoder: InstructionEncoder::Fence }, |
|
| 298 | + | { name: "fence.i", encoder: InstructionEncoder::NoOperand { enc: encode::fenceI } }, |
|
| 296 | 299 | { name: "j", encoder: InstructionEncoder::Jump { rd: rv64::ZERO } }, |
|
| 297 | 300 | { name: "jal", encoder: InstructionEncoder::Jal }, |
|
| 298 | 301 | { name: "jalr", encoder: InstructionEncoder::RRI { enc: encode::jalr } }, |
|
| 299 | 302 | { name: "la", encoder: InstructionEncoder::La }, |
|
| 300 | 303 | { name: "lb", encoder: InstructionEncoder::Load { enc: encode::lb } }, |
lib/std/arch/rv64/asm/parser.rad
+44 -0
| 6 | 6 | use std::lang::parser; |
|
| 7 | 7 | use std::lang::gen; |
|
| 8 | 8 | use std::collections::dict; |
|
| 9 | 9 | use std::arch::rv64::encode; |
|
| 10 | 10 | use std::arch::rv64; |
|
| 11 | + | use std::arch::rv64::atomics; |
|
| 11 | 12 | ||
| 12 | 13 | use super::emit; |
|
| 13 | 14 | use super::scanner; |
|
| 14 | 15 | ||
| 15 | 16 | /// Parsed memory operand with base register and signed byte offset. |
| 282 | 283 | /// Parse an instruction after its mnemonic has already been consumed. |
|
| 283 | 284 | unsafe fn parseInstruction(a: &mut super::Assembler, name: *[u8], tok: scanner::Token) throws (super::Error) { |
|
| 284 | 285 | if a.section <> super::Section::Text { |
|
| 285 | 286 | throw failOnToken(tok, "instructions are only valid in the text section"); |
|
| 286 | 287 | } |
|
| 288 | + | if let format = atomics::parse(name) { |
|
| 289 | + | let rd = try parseRegister(a); |
|
| 290 | + | let mut rs2 = rv64::ZERO; |
|
| 291 | + | if format.operation <> 2 { set rs2 = try parseRegister(a); } |
|
| 292 | + | let memory = try parseMemory(a); |
|
| 293 | + | if memory.offset <> 0 { throw fail(a, "atomic memory offset must be zero"); } |
|
| 294 | + | try emit::emitText(a, atomics::encode(atomics::Instruction { format, rd, rs1: memory.base, rs2 })); |
|
| 295 | + | return; |
|
| 296 | + | } |
|
| 287 | 297 | let form = lookupInstruction(name) else { |
|
| 288 | 298 | throw failOnToken(tok, "unknown instruction"); |
|
| 289 | 299 | }; |
|
| 290 | 300 | match form { |
|
| 301 | + | case super::InstructionEncoder::Fence => return try parseFence(a), |
|
| 291 | 302 | case super::InstructionEncoder::NoOperand { enc } => { |
|
| 292 | 303 | if a.scan.current.kind <> scanner::TokenKind::Semicolon { |
|
| 293 | 304 | throw fail(a, "unexpected operand"); |
|
| 294 | 305 | } |
|
| 295 | 306 | try emit::emitText(a, enc()); |
| 855 | 866 | fn parseCharLiteral(tok: scanner::Token) -> ?u8 { |
|
| 856 | 867 | return try fmt::parseChar(tok.source) catch { |
|
| 857 | 868 | return nil; |
|
| 858 | 869 | }; |
|
| 859 | 870 | } |
|
| 871 | + | ||
| 872 | + | /// Parse an access-class mask without duplicate fields. |
|
| 873 | + | unsafe fn parseFenceMask(a: &mut super::Assembler) -> u32 throws (super::Error) { |
|
| 874 | + | if a.scan.current.kind == scanner::TokenKind::Number { |
|
| 875 | + | let value = try parseValue(a); |
|
| 876 | + | if value <> 0 { throw fail(a, "numeric fence mask must be zero"); } |
|
| 877 | + | return 0; |
|
| 878 | + | } |
|
| 879 | + | let token = try expectToken(a, scanner::TokenKind::Ident, "expected fence access classes"); |
|
| 880 | + | let mut mask: u32 = 0; |
|
| 881 | + | for ch in token.source { |
|
| 882 | + | let mut bit: u32 = 0; |
|
| 883 | + | match ch { |
|
| 884 | + | case 'i' => { set bit = 8; }, case 'o' => { set bit = 4; }, |
|
| 885 | + | case 'r' => { set bit = 2; }, case 'w' => { set bit = 1; }, |
|
| 886 | + | else => throw failOnToken(token, "invalid fence access class"), |
|
| 887 | + | } |
|
| 888 | + | if (mask & bit) <> 0 { throw failOnToken(token, "duplicate fence access class"); } |
|
| 889 | + | set mask |= bit; |
|
| 890 | + | } |
|
| 891 | + | return mask; |
|
| 892 | + | } |
|
| 893 | + | ||
| 894 | + | /// Parse a full memory fence or a pair of explicit access-class masks. |
|
| 895 | + | unsafe fn parseFence(a: &mut super::Assembler) throws (super::Error) { |
|
| 896 | + | if a.scan.current.kind == scanner::TokenKind::Semicolon { |
|
| 897 | + | try emit::emitText(a, encode::fence()); |
|
| 898 | + | return; |
|
| 899 | + | } |
|
| 900 | + | let predecessor = try parseFenceMask(a); |
|
| 901 | + | let successor = try parseFenceMask(a); |
|
| 902 | + | try emit::emitText(a, encode::fenceOrder(predecessor, successor)); |
|
| 903 | + | } |
lib/std/arch/rv64/asm/scanner.rad
+1 -1
| 197 | 197 | return Token { kind, source: &s.source[s.token..s.cursor], offset: s.token }; |
|
| 198 | 198 | } |
|
| 199 | 199 | ||
| 200 | 200 | /// Scan the identifier continuation characters that follow the current token start. |
|
| 201 | 201 | fn scanIdentifierBody(s: &mut Scanner) { |
|
| 202 | - | while let ch = current(s); char::isAlpha(ch) or char::isDigit(ch) or ch == '_' { |
|
| 202 | + | while let ch = current(s); char::isAlpha(ch) or char::isDigit(ch) or ch == '_' or ch == '.' { |
|
| 203 | 203 | advance(s); |
|
| 204 | 204 | } |
|
| 205 | 205 | } |
|
| 206 | 206 | ||
| 207 | 207 | /// Scan a signed number when `+` or `-` is followed by a digit, otherwise return the punctuation token. |
lib/std/arch/rv64/atomicTests.rad
added
+109 -0
| 1 | + | //! RV64 A-extension and fence encoding checks. |
|
| 2 | + | ||
| 3 | + | use std::testing; |
|
| 4 | + | use std::lang::alloc; |
|
| 5 | + | use std::lang::strings; |
|
| 6 | + | use super::atomics; |
|
| 7 | + | use super::encode; |
|
| 8 | + | use super::decode; |
|
| 9 | + | use super::asm; |
|
| 10 | + | ||
| 11 | + | /// Assembler workspace for individual atomic instruction fixtures. |
|
| 12 | + | static MEMORY: [u8; 65536] = [0; 65536]; |
|
| 13 | + | /// Interned fixture names. |
|
| 14 | + | unsafe static STRINGS: strings::Pool = strings::Pool { table: undefined, count: 0 }; |
|
| 15 | + | ||
| 16 | + | /// Output storage for one assembled instruction. |
|
| 17 | + | static WORDS: [u32; 1] = [0; 1]; |
|
| 18 | + | ||
| 19 | + | /// Exact unordered word encodings for a0, a1, and a2 operands. |
|
| 20 | + | @test fn encodings() throws (testing::TestError) { |
|
| 21 | + | let operations: [u32; 11] = [0, 1, 2, 3, 4, 8, 12, 16, 20, 24, 28]; |
|
| 22 | + | let words: [u32; 11] = [ |
|
| 23 | + | 0x00c5a52f, 0x08c5a52f, 0x1005a52f, 0x18c5a52f, 0x20c5a52f, |
|
| 24 | + | 0x40c5a52f, 0x60c5a52f, 0x80c5a52f, 0xa0c5a52f, 0xc0c5a52f, 0xe0c5a52f, |
|
| 25 | + | ]; |
|
| 26 | + | for operation, i in &operations[..] { |
|
| 27 | + | for wide in 0..2 { |
|
| 28 | + | for order in 0..4 { |
|
| 29 | + | let item = atomics::Instruction { |
|
| 30 | + | format: atomics::Format { operation, width: 2 + wide, order }, |
|
| 31 | + | rd: super::A0, rs1: super::A1, rs2: super::ZERO if operation == 2 else super::A2, |
|
| 32 | + | }; |
|
| 33 | + | let word = words[i] | (wide << 12) | (order << 25); |
|
| 34 | + | try testing::expect(atomics::encode(item) == word); |
|
| 35 | + | let decoded = atomics::decode(word) else { throw testing::TestError::Failed; }; |
|
| 36 | + | try testing::expect(decoded == item); |
|
| 37 | + | let case decode::Instr::Atomic(instruction) = decode::decode(word) else { throw testing::TestError::Failed; }; |
|
| 38 | + | try testing::expect(instruction == item); |
|
| 39 | + | } |
|
| 40 | + | } |
|
| 41 | + | } |
|
| 42 | + | } |
|
| 43 | + | ||
| 44 | + | /// Reject reserved opcodes, widths, LR sources, and malformed suffixes. |
|
| 45 | + | @test fn invalid() throws (testing::TestError) { |
|
| 46 | + | for word in &[0x2805a52f, 0x00c5852f, 0x10c5a52f, 0x00c5a513] { |
|
| 47 | + | try testing::expect(atomics::decode(word) == nil); |
|
| 48 | + | } |
|
| 49 | + | for name in &["lr", "lr.q", "lr.w.aq.rl", "sc.d.rl.aq", "amoswap.d.bad", "amoadd.wextra"] { |
|
| 50 | + | try testing::expect(atomics::parse(name) == nil); |
|
| 51 | + | } |
|
| 52 | + | let format = atomics::parse("amoswap.d.aqrl") else { throw testing::TestError::Failed; }; |
|
| 53 | + | try testing::expect(format.operation == 1 and format.width == 3 and format.order == 3); |
|
| 54 | + | } |
|
| 55 | + | ||
| 56 | + | /// Assemble one instruction and return its exact word. |
|
| 57 | + | unsafe fn assemble(source: *[u8]) -> u32 throws (testing::TestError) { |
|
| 58 | + | let mut arena = alloc::new(&mut MEMORY[..]); |
|
| 59 | + | let words = &mut WORDS[..]; |
|
| 60 | + | let data: *mut [u8] = &mut []; |
|
| 61 | + | let result = try asm::assemble(asm::scanner::SourceKind::String, source, |
|
| 62 | + | &mut words[..], &mut data[..], &mut arena, &mut STRINGS, 0) |
|
| 63 | + | catch { throw testing::TestError::Failed; }; |
|
| 64 | + | try testing::expect(result.text.len == 1); |
|
| 65 | + | return result.text[0]; |
|
| 66 | + | } |
|
| 67 | + | ||
| 68 | + | /// Check instruction suffix scanning and assembler operand order. |
|
| 69 | + | @test unsafe fn assembly() throws (testing::TestError) { |
|
| 70 | + | try testing::expect((try assemble("amoadd.d.aqrl %a0 %a2 (%a1);")) == 0x06c5b52f); |
|
| 71 | + | try testing::expect((try assemble("amoswap.w.aq %a0 %a2 0(%a1);")) == 0x0cc5a52f); |
|
| 72 | + | try testing::expect((try assemble("lr.d.aq %a0 (%a1);")) == 0x1405b52f); |
|
| 73 | + | try testing::expect((try assemble("sc.w.rl %a0 %a2 (%a1);")) == 0x1ac5a52f); |
|
| 74 | + | try testing::expect((try assemble("fence.i;")) == 0x0000100f); |
|
| 75 | + | try testing::expect((try assemble("fence iorw iorw;")) == 0x0ff0000f); |
|
| 76 | + | try testing::expect((try assemble("fence r rw;")) == 0x0230000f); |
|
| 77 | + | try testing::expect((try assemble("fence rw w;")) == 0x0310000f); |
|
| 78 | + | try testing::expect((try assemble("fence 0 0;")) == 0x0000000f); |
|
| 79 | + | } |
|
| 80 | + | ||
| 81 | + | /// Decode fence classes and local instruction synchronization. |
|
| 82 | + | @test fn fences() throws (testing::TestError) { |
|
| 83 | + | try testing::expect(encode::fenceI() == 0x0000100f); |
|
| 84 | + | try testing::expect(decode::decode(encode::fenceI()) == decode::Instr::FenceI); |
|
| 85 | + | let case decode::Instr::Fence { predecessor, successor } = decode::decode(encode::fenceOrder(15, 15)) |
|
| 86 | + | else { throw testing::TestError::Failed; }; |
|
| 87 | + | try testing::expect(predecessor == 15 and successor == 15); |
|
| 88 | + | let case decode::Instr::Unknown { .. } = decode::decode(0x0000200f) |
|
| 89 | + | else { throw testing::TestError::Failed; }; |
|
| 90 | + | } |
|
| 91 | + | ||
| 92 | + | /// Reject invalid atomic operands and fence access classes. |
|
| 93 | + | @test unsafe fn invalidAssembly() throws (testing::TestError) { |
|
| 94 | + | for source in &[ |
|
| 95 | + | "lr.w %a0 %a2 (%a1);", "sc.d %a0 %a2 8(%a1);", |
|
| 96 | + | "amoadd.d.aq.aq %a0 %a2 (%a1);", "fence rr rw;", "fence rx rw;", |
|
| 97 | + | "fence 1 rw;", "fence.i %a0;", |
|
| 98 | + | ] { |
|
| 99 | + | let mut arena = alloc::new(&mut MEMORY[..]); |
|
| 100 | + | let words = &mut WORDS[..]; |
|
| 101 | + | let data: *mut [u8] = &mut []; |
|
| 102 | + | let mut rejected = false; |
|
| 103 | + | try asm::assemble(asm::scanner::SourceKind::String, source, |
|
| 104 | + | &mut words[..], &mut data[..], &mut arena, &mut STRINGS, 0) catch { |
|
| 105 | + | set rejected = true; |
|
| 106 | + | }; |
|
| 107 | + | try testing::expect(rejected); |
|
| 108 | + | } |
|
| 109 | + | } |
lib/std/arch/rv64/atomics.rad
added
+92 -0
| 1 | + | //! RV64 atomic instruction fields and canonical assembly names. |
|
| 2 | + | ||
| 3 | + | use std::mem; |
|
| 4 | + | use std::lang::gen; |
|
| 5 | + | ||
| 6 | + | /// Atomic operation, width, and ordering fields. |
|
| 7 | + | export record Format: Copy { |
|
| 8 | + | /// Five-bit operation code from the A extension. |
|
| 9 | + | operation: u32, |
|
| 10 | + | /// Memory width encoding: 2 for a word, 3 for a doubleword. |
|
| 11 | + | width: u32, |
|
| 12 | + | /// Ordering bits: 2 for acquire, 1 for release, 3 for both. |
|
| 13 | + | order: u32, |
|
| 14 | + | } |
|
| 15 | + | ||
| 16 | + | /// Decoded atomic instruction operands. |
|
| 17 | + | export record Instruction: Copy { |
|
| 18 | + | /// Operation and ordering fields. |
|
| 19 | + | format: Format, |
|
| 20 | + | /// Destination register. |
|
| 21 | + | rd: gen::Reg, |
|
| 22 | + | /// Address register. |
|
| 23 | + | rs1: gen::Reg, |
|
| 24 | + | /// Source value; zero for load-reserved. |
|
| 25 | + | rs2: gen::Reg, |
|
| 26 | + | } |
|
| 27 | + | ||
| 28 | + | /// An architectural operation and its mnemonic stem. |
|
| 29 | + | record Operation: Copy { |
|
| 30 | + | /// Mnemonic without width or ordering suffixes. |
|
| 31 | + | name: *[u8], |
|
| 32 | + | /// Five-bit instruction field. |
|
| 33 | + | code: u32, |
|
| 34 | + | } |
|
| 35 | + | ||
| 36 | + | /// Operations shared by the 32-bit and 64-bit forms. |
|
| 37 | + | constant OPERATIONS: [Operation; 11] = [ |
|
| 38 | + | { name: "amoadd", code: 0 }, { name: "amoswap", code: 1 }, |
|
| 39 | + | { name: "lr", code: 2 }, { name: "sc", code: 3 }, |
|
| 40 | + | { name: "amoxor", code: 4 }, { name: "amoor", code: 8 }, |
|
| 41 | + | { name: "amoand", code: 12 }, { name: "amomin", code: 16 }, |
|
| 42 | + | { name: "amomax", code: 20 }, { name: "amominu", code: 24 }, |
|
| 43 | + | { name: "amomaxu", code: 28 }, |
|
| 44 | + | ]; |
|
| 45 | + | ||
| 46 | + | /// Look up an operation's canonical mnemonic stem. |
|
| 47 | + | export fn name(code: u32) -> ?*[u8] { |
|
| 48 | + | for operation in &OPERATIONS[..] { if operation.code == code { return operation.name; } } |
|
| 49 | + | return nil; |
|
| 50 | + | } |
|
| 51 | + | ||
| 52 | + | /// Parse an atomic mnemonic with mandatory width and optional ordering suffix. |
|
| 53 | + | export fn parse(text: *[u8]) -> ?Format { |
|
| 54 | + | for operation in &OPERATIONS[..] { |
|
| 55 | + | let n = operation.name.len; |
|
| 56 | + | if text.len < n + 2 or not mem::eq(&text[..n], operation.name) or text[n] <> '.' { continue; } |
|
| 57 | + | let mut width: u32 = 2; |
|
| 58 | + | if text[n + 1] == 'd' { set width = 3; } |
|
| 59 | + | else if text[n + 1] <> 'w' { return nil; } |
|
| 60 | + | let suffix = &text[n + 2..]; |
|
| 61 | + | let mut order: u32 = 0; |
|
| 62 | + | if mem::eq(suffix, ".aq") { set order = 2; } |
|
| 63 | + | else if mem::eq(suffix, ".rl") { set order = 1; } |
|
| 64 | + | else if mem::eq(suffix, ".aqrl") { set order = 3; } |
|
| 65 | + | else if suffix.len <> 0 { return nil; } |
|
| 66 | + | return Format { operation: operation.code, width, order }; |
|
| 67 | + | } |
|
| 68 | + | return nil; |
|
| 69 | + | } |
|
| 70 | + | ||
| 71 | + | /// Encode a validated atomic operation and register operands. |
|
| 72 | + | export fn encode(instruction: Instruction) -> u32 { |
|
| 73 | + | let format = instruction.format; |
|
| 74 | + | assert name(format.operation) <> nil and (format.width == 2 or format.width == 3) and format.order <= 3; |
|
| 75 | + | assert format.operation <> 2 or instruction.rs2 == super::ZERO; |
|
| 76 | + | return 0x2f | (*instruction.rd as u32 << 7) | (format.width << 12) |
|
| 77 | + | | (*instruction.rs1 as u32 << 15) | (*instruction.rs2 as u32 << 20) |
|
| 78 | + | | (format.order << 25) | (format.operation << 27); |
|
| 79 | + | } |
|
| 80 | + | ||
| 81 | + | /// Decode a supported atomic word and reject reserved width and LR fields. |
|
| 82 | + | export fn decode(word: u32) -> ?Instruction { |
|
| 83 | + | let operation = word >> 27; |
|
| 84 | + | let width = (word >> 12) & 7; |
|
| 85 | + | let rs2 = super::reg(((word >> 20) & 31) as u8); |
|
| 86 | + | if (word & 127) <> 0x2f or name(operation) == nil or (width <> 2 and width <> 3) { return nil; } |
|
| 87 | + | if operation == 2 and rs2 <> super::ZERO { return nil; } |
|
| 88 | + | return Instruction { |
|
| 89 | + | format: Format { operation, width, order: (word >> 25) & 3 }, |
|
| 90 | + | rd: super::reg(((word >> 7) & 31) as u8), rs1: super::reg(((word >> 15) & 31) as u8), rs2, |
|
| 91 | + | }; |
|
| 92 | + | } |
lib/std/arch/rv64/decode.rad
+24 -0
| 2 | 2 | //! |
|
| 3 | 3 | //! Decodes 32-bit instruction words into structured representations. |
|
| 4 | 4 | ||
| 5 | 5 | use std::lang::gen; |
|
| 6 | 6 | use super::encode; |
|
| 7 | + | use super::atomics; |
|
| 7 | 8 | ||
| 8 | 9 | /////////////////////// |
|
| 9 | 10 | // Field Extraction // |
|
| 10 | 11 | /////////////////////// |
|
| 11 | 12 |
| 188 | 189 | ||
| 189 | 190 | // System. |
|
| 190 | 191 | Ecall, |
|
| 191 | 192 | Ebreak, |
|
| 192 | 193 | ||
| 194 | + | /// Atomic memory operation. |
|
| 195 | + | Atomic(atomics::Instruction), |
|
| 196 | + | /// Memory and I/O ordering fence. |
|
| 197 | + | Fence { |
|
| 198 | + | /// Ordered preceding access classes. |
|
| 199 | + | predecessor: u32, |
|
| 200 | + | /// Ordered following access classes. |
|
| 201 | + | successor: u32, |
|
| 202 | + | }, |
|
| 203 | + | /// Local instruction-fetch synchronization. |
|
| 204 | + | FenceI, |
|
| 205 | + | ||
| 193 | 206 | // Unknown/invalid instruction. |
|
| 194 | 207 | Unknown { bits: u32 }, |
|
| 195 | 208 | } |
|
| 196 | 209 | ||
| 197 | 210 | /// Decode a 32-bit instruction word into an [`Instr`]. |
| 202 | 215 | let rd = super::reg(rd(instr)); |
|
| 203 | 216 | let rs1 = super::reg(rs1(instr)); |
|
| 204 | 217 | let rs2 = super::reg(rs2(instr)); |
|
| 205 | 218 | ||
| 206 | 219 | match op { |
|
| 220 | + | case 0x2f => { |
|
| 221 | + | if let atomic = atomics::decode(instr) { return Instr::Atomic(atomic); } |
|
| 222 | + | return Instr::Unknown { bits: instr }; |
|
| 223 | + | }, |
|
| 224 | + | case 0x0f => { |
|
| 225 | + | if instr == 0x100f { return Instr::FenceI; } |
|
| 226 | + | if (instr & 0xf00fffff) == 0x0f { |
|
| 227 | + | return Instr::Fence { predecessor: (instr >> 24) & 15, successor: (instr >> 20) & 15 }; |
|
| 228 | + | } |
|
| 229 | + | return Instr::Unknown { bits: instr }; |
|
| 230 | + | }, |
|
| 207 | 231 | case encode::OP_LUI => { |
|
| 208 | 232 | return Instr::Lui { rd, imm: immU(instr) }; |
|
| 209 | 233 | }, |
|
| 210 | 234 | case encode::OP_AUIPC => { |
|
| 211 | 235 | return Instr::Auipc { rd, imm: immU(instr) }; |
lib/std/arch/rv64/encode.rad
+11 -1
| 546 | 546 | return encodeI(OP_SYSTEM, super::ZERO, super::ZERO, 0, 1); |
|
| 547 | 547 | } |
|
| 548 | 548 | ||
| 549 | 549 | /// Full predecessor/successor memory fence (`fence rw, rw`). |
|
| 550 | 550 | export fn fence() -> u32 { |
|
| 551 | - | return 0x0330000F; |
|
| 551 | + | return fenceOrder(3, 3); |
|
| 552 | 552 | } |
|
| 553 | 553 | ||
| 554 | 554 | /// Encode a CSR instruction with a register source. |
|
| 555 | 555 | fn encodeCsr(op: u32, rd: gen::Reg, csr: u32, funct3: u32, rs1: gen::Reg) -> u32 { |
|
| 556 | 556 | return (op & 0x7F) |
| 675 | 675 | ||
| 676 | 676 | /// Call: `jal ra, imm`. |
|
| 677 | 677 | export fn call(imm: i32) -> u32 { |
|
| 678 | 678 | return jal(super::RA, imm); |
|
| 679 | 679 | } |
|
| 680 | + | ||
| 681 | + | /// Order the specified predecessor and successor memory or I/O access classes. |
|
| 682 | + | /// Each mask uses I=8, O=4, R=2, W=1. |
|
| 683 | + | export fn fenceOrder(predecessor: u32, successor: u32) -> u32 { |
|
| 684 | + | assert predecessor <= 15 and successor <= 15; |
|
| 685 | + | return 0x0f | (predecessor << 24) | (successor << 20); |
|
| 686 | + | } |
|
| 687 | + | ||
| 688 | + | /// Synchronize subsequent instruction fetch on the executing hart. |
|
| 689 | + | export fn fenceI() -> u32 { return 0x0000100f; } |
lib/std/arch/rv64/printer.rad
+26 -0
| 8 | 8 | use std::lang::gen; |
|
| 9 | 9 | use std::lang::sexpr; |
|
| 10 | 10 | use std::lang::gen::types; |
|
| 11 | 11 | ||
| 12 | 12 | use super::decode; |
|
| 13 | + | use super::atomics; |
|
| 13 | 14 | use super::emit; |
|
| 14 | 15 | ||
| 15 | 16 | ///////////////////// |
|
| 16 | 17 | // Register Names // |
|
| 17 | 18 | ///////////////////// |
| 294 | 295 | case decode::Instr::Mulw { rd, rs1, rs2 } => fmtR(out, "mulw", rd, rs1, rs2), |
|
| 295 | 296 | case decode::Instr::Divw { rd, rs1, rs2 } => fmtR(out, "divw", rd, rs1, rs2), |
|
| 296 | 297 | case decode::Instr::Divuw { rd, rs1, rs2 } => fmtR(out, "divuw", rd, rs1, rs2), |
|
| 297 | 298 | case decode::Instr::Remw { rd, rs1, rs2 } => fmtR(out, "remw", rd, rs1, rs2), |
|
| 298 | 299 | case decode::Instr::Remuw { rd, rs1, rs2 } => fmtR(out, "remuw", rd, rs1, rs2), |
|
| 300 | + | case decode::Instr::Atomic(instruction) => { |
|
| 301 | + | let stem = atomics::name(instruction.format.operation) else panic "invalid atomic operation"; |
|
| 302 | + | write(out, stem); |
|
| 303 | + | write(out, ".w" if instruction.format.width == 2 else ".d"); |
|
| 304 | + | match instruction.format.order { |
|
| 305 | + | case 1 => write(out, ".rl"), case 2 => write(out, ".aq"), case 3 => write(out, ".aqrl"), |
|
| 306 | + | else => {}, |
|
| 307 | + | } |
|
| 308 | + | write(out, " "); write(out, regNameR(instruction.rd)); write(out, ", "); |
|
| 309 | + | if instruction.format.operation <> 2 { write(out, regNameR(instruction.rs2)); write(out, ", "); } |
|
| 310 | + | write(out, "0("); write(out, regNameR(instruction.rs1)); write(out, ")"); |
|
| 311 | + | }, |
|
| 312 | + | case decode::Instr::Fence { predecessor, successor } => { |
|
| 313 | + | write(out, "fence "); fenceMask(out, predecessor); write(out, ", "); fenceMask(out, successor); |
|
| 314 | + | }, |
|
| 315 | + | case decode::Instr::FenceI => write(out, "fence.i"), |
|
| 299 | 316 | case decode::Instr::Ecall => write(out, "ecall"), |
|
| 300 | 317 | case decode::Instr::Ebreak => write(out, "ebreak"), |
|
| 301 | 318 | case decode::Instr::Unknown { bits } => { |
|
| 302 | 319 | write(out, "unknown"); |
|
| 303 | 320 | writeParens(out, formatU32(a, bits)); |
| 330 | 347 | return funcs[i].name; |
|
| 331 | 348 | } |
|
| 332 | 349 | } |
|
| 333 | 350 | return nil; |
|
| 334 | 351 | } |
|
| 352 | + | ||
| 353 | + | /// Print a memory-ordering mask in canonical order. |
|
| 354 | + | unsafe fn fenceMask(out: &mut sexpr::Output, mask: u32) { |
|
| 355 | + | if mask == 0 { write(out, "0"); return; } |
|
| 356 | + | if (mask & 8) <> 0 { write(out, "i"); } |
|
| 357 | + | if (mask & 4) <> 0 { write(out, "o"); } |
|
| 358 | + | if (mask & 2) <> 0 { write(out, "r"); } |
|
| 359 | + | if (mask & 1) <> 0 { write(out, "w"); } |
|
| 360 | + | } |
std.lib
+1 -0
| 51 | 51 | lib/std/lang/gen/regalloc.rad |
|
| 52 | 52 | lib/std/lang/gen/regalloc/liveness.rad |
|
| 53 | 53 | lib/std/lang/gen/regalloc/spill.rad |
|
| 54 | 54 | lib/std/lang/gen/regalloc/assign.rad |
|
| 55 | 55 | lib/std/arch/rv64/shared.rad |
|
| 56 | + | lib/std/arch/rv64/atomics.rad |
std.lib.test
+1 -0
| 13 | 13 | lib/std/lang/il/binary/tests.rad |
|
| 14 | 14 | lib/std/lang/il/binary/decodeTests.rad |
|
| 15 | 15 | lib/std/arch/rv64/image/tests.rad |
|
| 16 | 16 | lib/std/arch/rv64/shared/tests.rad |
|
| 17 | 17 | lib/std/arch/rv64/bounds.rad |
|
| 18 | + | lib/std/arch/rv64/atomicTests.rad |
test/sync/build.rad
added
+50 -0
| 1 | + | //! Build a native image from the synchronization machine fixture. |
|
| 2 | + | ||
| 3 | + | use std::sys; |
|
| 4 | + | use std::io; |
|
| 5 | + | use std::sys::unix; |
|
| 6 | + | use std::lang::alloc; |
|
| 7 | + | use std::lang::strings; |
|
| 8 | + | use std::arch::rv64::asm; |
|
| 9 | + | use std::arch::rv64::image; |
|
| 10 | + | ||
| 11 | + | /// Assembly input storage. |
|
| 12 | + | static SOURCE: [u8; 65536] = [0; 65536]; |
|
| 13 | + | /// Encoded text words. |
|
| 14 | + | static TEXT: [u32; 4096] = [0; 4096]; |
|
| 15 | + | /// Assembly name and fixup storage. |
|
| 16 | + | static MEMORY: [u8; 4194304] = [0; 4194304]; |
|
| 17 | + | /// Interned assembly identifiers. |
|
| 18 | + | unsafe static STRINGS: strings::Pool = strings::Pool { table: undefined, count: 0 }; |
|
| 19 | + | ||
| 20 | + | /// Assemble startup and synchronization routines into one native code segment. |
|
| 21 | + | @default unsafe fn main(env: *sys::Env) -> i32 { |
|
| 22 | + | assert env.args.len == 3; |
|
| 23 | + | let length = unix::readFile(env.args[1], &mut SOURCE[..]) else panic "missing assembly"; |
|
| 24 | + | let mut arena = alloc::new(&mut MEMORY[..]); |
|
| 25 | + | let data: *mut [u8] = &mut []; |
|
| 26 | + | let program = try asm::assemble(asm::scanner::SourceKind::String, &SOURCE[..length], |
|
| 27 | + | &mut TEXT[..], &mut data[..], &mut arena, &mut STRINGS, 0) catch err { |
|
| 28 | + | match err { |
|
| 29 | + | case asm::Error::Invalid { offset, message } => { |
|
| 30 | + | io::printU32(offset); io::print(": "); io::printLn(message); |
|
| 31 | + | }, |
|
| 32 | + | else => io::printLn("assembly output full"), |
|
| 33 | + | } |
|
| 34 | + | return 1; |
|
| 35 | + | }; |
|
| 36 | + | assert program.externalFixups.len == 0; |
|
| 37 | + | let size = program.text.len * 4; |
|
| 38 | + | let header = try! image::header(image::Layout { |
|
| 39 | + | entry: 0x80010000, |
|
| 40 | + | code: image::Segment { address: 0x80010000, initialized: size, memory: size }, |
|
| 41 | + | roData: image::Segment { address: 0, initialized: 0, memory: 0 }, |
|
| 42 | + | rwData: image::Segment { address: 0x80020000, initialized: 0, memory: 4096 }, |
|
| 43 | + | }); |
|
| 44 | + | let fd = unix::openOpts(env.args[2], unix::OpenFlags(*unix::O_WRONLY | *unix::O_CREAT | *unix::O_TRUNC), 420); |
|
| 45 | + | assert fd >= 0; |
|
| 46 | + | let written = unix::writeAll(fd, &header[..]) and unix::writeAll(fd, @sliceOf(program.text.ptr as *u8, size)); |
|
| 47 | + | let closed = unix::close(fd) == 0; |
|
| 48 | + | assert written and closed; |
|
| 49 | + | return 0; |
|
| 50 | + | } |
test/sync/machine.ras
added
+118 -0
| 1 | + | //! Exercise code publication and contended metadata updates on each hart. |
|
| 2 | + | .constant EXPECTED HARTS * 100; |
|
| 3 | + | .text; |
|
| 4 | + | @entry |
|
| 5 | + | csrr %s6 mhartid; |
|
| 6 | + | li %s0 0x40010000; |
|
| 7 | + | slli %s0 %s0 1; |
|
| 8 | + | bnez %s6 @waitCode; |
|
| 9 | + | li %t0 1; |
|
| 10 | + | slli %t0 %t0 32; |
|
| 11 | + | addi %t0 %t0 -4; |
|
| 12 | + | sw %t0 0(%s0); |
|
| 13 | + | sw %t0 4(%s0); |
|
| 14 | + | li %t0 0x02a00513; |
|
| 15 | + | sw %t0 64(%s0); |
|
| 16 | + | li %t0 0x00008067; |
|
| 17 | + | sw %t0 68(%s0); |
|
| 18 | + | li %t0 37; |
|
| 19 | + | sw %t0 44(%s0); |
|
| 20 | + | addi %a0 %s0 40; |
|
| 21 | + | li %a1 -1; |
|
| 22 | + | call @kernel::sync::storeRelease32; |
|
| 23 | + | addi %a0 %s0 24; |
|
| 24 | + | li %a1 1; |
|
| 25 | + | call @kernel::sync::storeRelease; |
|
| 26 | + | @waitCode |
|
| 27 | + | addi %a0 %s0 24; |
|
| 28 | + | call @kernel::sync::loadAcquire; |
|
| 29 | + | beqz %a0 @waitCode; |
|
| 30 | + | addi %a0 %s0 40; |
|
| 31 | + | call @kernel::sync::loadAcquire32; |
|
| 32 | + | li %t0 1; |
|
| 33 | + | slli %t0 %t0 32; |
|
| 34 | + | addi %t0 %t0 -1; |
|
| 35 | + | bne %a0 %t0 @fail; |
|
| 36 | + | lwu %t0 44(%s0); |
|
| 37 | + | li %t1 37; |
|
| 38 | + | bne %t0 %t1 @fail; |
|
| 39 | + | call @kernel::sync::syncInstructions; |
|
| 40 | + | addi %t0 %s0 64; |
|
| 41 | + | jalr %ra %t0 0; |
|
| 42 | + | li %t0 42; |
|
| 43 | + | bne %a0 %t0 @fail; |
|
| 44 | + | li %s1 100; |
|
| 45 | + | @ticket |
|
| 46 | + | mv %a0 %s0; |
|
| 47 | + | call @kernel::sync::nextTicket; |
|
| 48 | + | mv %s2 %a0; |
|
| 49 | + | li %t0 100; |
|
| 50 | + | bne %s1 %t0 @acquire; |
|
| 51 | + | // Every hart holds its first ticket before any hart can enter the lock. |
|
| 52 | + | addi %a0 %s0 72; |
|
| 53 | + | li %a1 1; |
|
| 54 | + | call @kernel::sync::fetchAdd; |
|
| 55 | + | @queued |
|
| 56 | + | addi %a0 %s0 72; |
|
| 57 | + | call @kernel::sync::loadAcquire; |
|
| 58 | + | li %t0 HARTS; |
|
| 59 | + | bne %a0 %t0 @queued; |
|
| 60 | + | @acquire |
|
| 61 | + | addi %a0 %s0 4; |
|
| 62 | + | call @kernel::sync::loadAcquire32; |
|
| 63 | + | bne %a0 %s2 @acquire; |
|
| 64 | + | // The protected count must follow ticket order across the u32 wrap. |
|
| 65 | + | addi %t1 %s2 4; |
|
| 66 | + | slli %t1 %t1 32; |
|
| 67 | + | srli %t1 %t1 32; |
|
| 68 | + | ld %t0 8(%s0); |
|
| 69 | + | bne %t0 %t1 @fail; |
|
| 70 | + | addi %t0 %t0 1; |
|
| 71 | + | sd %t0 8(%s0); |
|
| 72 | + | addi %a0 %s0 4; |
|
| 73 | + | addi %a1 %s2 1; |
|
| 74 | + | call @kernel::sync::storeRelease32; |
|
| 75 | + | addi %s1 %s1 -1; |
|
| 76 | + | bnez %s1 @ticket; |
|
| 77 | + | addi %a0 %s0 16; |
|
| 78 | + | li %a1 1; |
|
| 79 | + | call @kernel::sync::fetchAdd; |
|
| 80 | + | addi %a0 %a0 1; |
|
| 81 | + | li %t0 HARTS; |
|
| 82 | + | beq %a0 %t0 @check; |
|
| 83 | + | @park |
|
| 84 | + | wfi; |
|
| 85 | + | j @park; |
|
| 86 | + | @check |
|
| 87 | + | ld %t0 8(%s0); |
|
| 88 | + | li %t1 EXPECTED; |
|
| 89 | + | bne %t0 %t1 @fail; |
|
| 90 | + | lwu %t0 0(%s0); |
|
| 91 | + | lwu %t2 4(%s0); |
|
| 92 | + | bne %t0 %t2 @fail; |
|
| 93 | + | addi %t1 %t1 -4; |
|
| 94 | + | bne %t0 %t1 @fail; |
|
| 95 | + | addi %t2 %s0 32; |
|
| 96 | + | lr.w.aq %t0 (%t2); |
|
| 97 | + | bnez %t0 @fail; |
|
| 98 | + | li %t1 13; |
|
| 99 | + | sc.w.rl %t0 %t1 (%t2); |
|
| 100 | + | bnez %t0 @fail; |
|
| 101 | + | lr.d.aq %t0 (%t2); |
|
| 102 | + | bne %t0 %t1 @fail; |
|
| 103 | + | sc.d.rl %t0 %zero (%t2); |
|
| 104 | + | bnez %t0 @fail; |
|
| 105 | + | li %a0 0x02000000; |
|
| 106 | + | call @kernel::sync::read32; |
|
| 107 | + | bnez %a0 @fail; |
|
| 108 | + | j @success; |
|
| 109 | + | @fail |
|
| 110 | + | li %a0 0x10001000; |
|
| 111 | + | li %a1 0x13333; |
|
| 112 | + | call @kernel::sync::write32; |
|
| 113 | + | ebreak; |
|
| 114 | + | @success |
|
| 115 | + | li %a0 0x10001000; |
|
| 116 | + | li %a1 0x5555; |
|
| 117 | + | call @kernel::sync::write32; |
|
| 118 | + | ebreak; |
test/sync/run
added
+13 -0
| 1 | + | #!/bin/sh |
|
| 2 | + | # Execute the synchronization boundary under deterministic hart interleaving. |
|
| 3 | + | set -eu |
|
| 4 | + | emulator=${RAD_EMULATOR:-emulator} |
|
| 5 | + | work=$(mktemp -d) |
|
| 6 | + | trap 'rm -rf "$work"' EXIT HUP INT TERM |
|
| 7 | + | for harts in 1 2 8; do |
|
| 8 | + | printf '.constant HARTS %s;\n' "$harts" > "$work/sync.ras" |
|
| 9 | + | cat test/sync/machine.ras kernel/kernel/sync.ras >> "$work/sync.ras" |
|
| 10 | + | "$emulator" -run bin/sync.build.rv64 -- "$work/sync.ras" "$work/sync.rv64" |
|
| 11 | + | "$emulator" -machine -harts="$harts" -run "$work/sync.rv64" |
|
| 12 | + | done |
|
| 13 | + | printf 'synchronization: code publication, locks, atomics, and MMIO passed on 1/2/8 harts\n' |