rv64: Add atomic instruction support
0d7c82483eb408e07344f46eea49e67f88d7019ec5c1290e30b6073a977438f4
1 parent
0432def4
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 |