rv64: Add atomic instruction support

0d7c82483eb408e07344f46eea49e67f88d7019ec5c1290e30b6073a977438f4
Alexis Sellier committed ago 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