il: Encode binary RIL values and instructions

ccafc74da702be838ef3523fc0741188b3b845e290eadcbb84ea2a214273a26d
Define explicit wire tags and encode values, instructions, and
initializers into bounded caller-owned storage. Check exact byte
fixtures, operation tags, and output capacity failures.

Assisted-by: Codex:gpt-6
Alexis Sellier committed ago 1 parent 0ea2981a
lib/std/lang/il.rad +1 -0
57 57
58 58
// TODO: Labels should have their own type.
59 59
// TODO: Blocks should have an instruction in `Instr`.
60 60
61 61
export mod printer;
62 +
export mod binary;
62 63
63 64
use std::mem;
64 65
use std::lang::alloc;
65 66
66 67
/// Source location for debug info.
lib/std/lang/il/binary.rad added +140 -0
1 +
//! Binary RIL wire definitions. Integers use little-endian byte order.
2 +
//! Sequences have a u32 element count. Symbols use u32 table indices.
3 +
//! Optional fields use a byte: zero for absent, one for present.
4 +
5 +
export mod writer;
6 +
7 +
@test export mod tests;
8 +
9 +
/// Binary RIL signature, encoded as the bytes RIL followed by zero.
10 +
export constant MAGIC: u32 = 0x004c4952;
11 +
/// Binary RIL format version.
12 +
export constant VERSION: u32 = 1;
13 +
14 +
/// A binary RIL encoding or decoding failure.
15 +
export union Error: Copy {
16 +
    /// The output buffer has insufficient space.
17 +
    Capacity,
18 +
    /// A referenced name is absent from the symbol table.
19 +
    Symbol,
20 +
    /// An integer width, tag, or field is invalid.
21 +
    Invalid,
22 +
    /// The input ends inside a field.
23 +
    Truncated,
24 +
    /// The decode arena has insufficient space.
25 +
    Storage,
26 +
}
27 +
28 +
/// Wire tag for value Reg.
29 +
export constant VALUE_REG: u8 = 0;
30 +
/// Wire tag for value Imm.
31 +
export constant VALUE_IMM: u8 = 1;
32 +
/// Wire tag for value DataSym.
33 +
export constant VALUE_DATASYM: u8 = 2;
34 +
/// Wire tag for value FnAddr.
35 +
export constant VALUE_FNADDR: u8 = 3;
36 +
/// Wire tag for value Undef.
37 +
export constant VALUE_UNDEF: u8 = 4;
38 +
39 +
/// Wire tag for instruction Reserve.
40 +
export constant INSTR_RESERVE: u8 = 0;
41 +
/// Wire tag for instruction Load.
42 +
export constant INSTR_LOAD: u8 = 1;
43 +
/// Wire tag for instruction Sload.
44 +
export constant INSTR_SLOAD: u8 = 2;
45 +
/// Wire tag for instruction Store.
46 +
export constant INSTR_STORE: u8 = 3;
47 +
/// Wire tag for instruction Blit.
48 +
export constant INSTR_BLIT: u8 = 4;
49 +
/// Wire tag for instruction Copy.
50 +
export constant INSTR_COPY: u8 = 5;
51 +
/// Wire tag for instruction BinOp.
52 +
export constant INSTR_BINOP: u8 = 6;
53 +
/// Wire tag for instruction UnOp.
54 +
export constant INSTR_UNOP: u8 = 7;
55 +
/// Wire tag for instruction Zext.
56 +
export constant INSTR_ZEXT: u8 = 8;
57 +
/// Wire tag for instruction Sext.
58 +
export constant INSTR_SEXT: u8 = 9;
59 +
/// Wire tag for instruction Call.
60 +
export constant INSTR_CALL: u8 = 10;
61 +
/// Wire tag for instruction Ret.
62 +
export constant INSTR_RET: u8 = 11;
63 +
/// Wire tag for instruction Jmp.
64 +
export constant INSTR_JMP: u8 = 12;
65 +
/// Wire tag for instruction Br.
66 +
export constant INSTR_BR: u8 = 13;
67 +
/// Wire tag for instruction Switch.
68 +
export constant INSTR_SWITCH: u8 = 14;
69 +
/// Wire tag for instruction Unreachable.
70 +
export constant INSTR_UNREACHABLE: u8 = 15;
71 +
/// Wire tag for instruction Ecall.
72 +
export constant INSTR_ECALL: u8 = 16;
73 +
/// Wire tag for instruction Ebreak.
74 +
export constant INSTR_EBREAK: u8 = 17;
75 +
/// Wire tag for instruction MemoryFence.
76 +
export constant INSTR_MEMORYFENCE: u8 = 18;
77 +
78 +
/// Wire tag for data Val.
79 +
export constant DATA_VAL: u8 = 0;
80 +
/// Wire tag for data Sym.
81 +
export constant DATA_SYM: u8 = 1;
82 +
/// Wire tag for data Fn.
83 +
export constant DATA_FN: u8 = 2;
84 +
/// Wire tag for data Str.
85 +
export constant DATA_STR: u8 = 3;
86 +
/// Wire tag for data Undef.
87 +
export constant DATA_UNDEF: u8 = 4;
88 +
89 +
/// Wire tag for binary operation Add.
90 +
export constant BIN_ADD: u8 = 0;
91 +
/// Wire tag for binary operation Sub.
92 +
export constant BIN_SUB: u8 = 1;
93 +
/// Wire tag for binary operation Mul.
94 +
export constant BIN_MUL: u8 = 2;
95 +
/// Wire tag for binary operation Sdiv.
96 +
export constant BIN_SDIV: u8 = 3;
97 +
/// Wire tag for binary operation Udiv.
98 +
export constant BIN_UDIV: u8 = 4;
99 +
/// Wire tag for binary operation Srem.
100 +
export constant BIN_SREM: u8 = 5;
101 +
/// Wire tag for binary operation Urem.
102 +
export constant BIN_UREM: u8 = 6;
103 +
/// Wire tag for binary operation Eq.
104 +
export constant BIN_EQ: u8 = 7;
105 +
/// Wire tag for binary operation Ne.
106 +
export constant BIN_NE: u8 = 8;
107 +
/// Wire tag for binary operation Slt.
108 +
export constant BIN_SLT: u8 = 9;
109 +
/// Wire tag for binary operation Sge.
110 +
export constant BIN_SGE: u8 = 10;
111 +
/// Wire tag for binary operation Ult.
112 +
export constant BIN_ULT: u8 = 11;
113 +
/// Wire tag for binary operation Uge.
114 +
export constant BIN_UGE: u8 = 12;
115 +
/// Wire tag for binary operation And.
116 +
export constant BIN_AND: u8 = 13;
117 +
/// Wire tag for binary operation Or.
118 +
export constant BIN_OR: u8 = 14;
119 +
/// Wire tag for binary operation Xor.
120 +
export constant BIN_XOR: u8 = 15;
121 +
/// Wire tag for binary operation Shl.
122 +
export constant BIN_SHL: u8 = 16;
123 +
/// Wire tag for binary operation Sshr.
124 +
export constant BIN_SSHR: u8 = 17;
125 +
/// Wire tag for binary operation Ushr.
126 +
export constant BIN_USHR: u8 = 18;
127 +
128 +
/// Wire tag for unary operation Neg.
129 +
export constant UN_NEG: u8 = 0;
130 +
/// Wire tag for unary operation Not.
131 +
export constant UN_NOT: u8 = 1;
132 +
133 +
/// Wire tag for comparison Eq.
134 +
export constant CMP_EQ: u8 = 0;
135 +
/// Wire tag for comparison Ne.
136 +
export constant CMP_NE: u8 = 1;
137 +
/// Wire tag for comparison Slt.
138 +
export constant CMP_SLT: u8 = 2;
139 +
/// Wire tag for comparison Ult.
140 +
export constant CMP_ULT: u8 = 3;
lib/std/lang/il/binary/tests.rad added +233 -0
1 +
//! Binary RIL encoding fixtures.
2 +
3 +
use std::testing;
4 +
use std::lang::il;
5 +
use std::lang::il::binary::writer;
6 +
use std::lang::il::binary;
7 +
8 +
/// Decode arena backing storage. Tests reset it before each use.
9 +
static MEMORY: [u8; 512] = [0; 512];
10 +
11 +
/// Check little-endian encoding for every integer width.
12 +
@test unsafe fn integers() throws (testing::TestError) {
13 +
    let mut buffer: [u8; 15] = [0; 15];
14 +
    let mut out = writer::new(&mut buffer[..], &[]);
15 +
    try writer::integer(&mut out, 0x12, 1) catch { throw testing::TestError::Failed; };
16 +
    try writer::integer(&mut out, 0x3456, 2) catch { throw testing::TestError::Failed; };
17 +
    try writer::integer(&mut out, 0x789abcde, 4) catch { throw testing::TestError::Failed; };
18 +
    try writer::integer(&mut out, 0x0123456789abcdef, 8) catch { throw testing::TestError::Failed; };
19 +
    try testing::expectBytesEq(&buffer[..], &[0x12, 0x56, 0x34, 0xde, 0xbc, 0x9a, 0x78, 0xef, 0xcd,
20 +
        0xab, 0x89, 0x67, 0x45, 0x23, 0x01]);
21 +
}
22 +
23 +
/// Compare one instruction with its fixed wire representation.
24 +
unsafe fn instruction(item: il::Instr, expected: &[u8]) throws (testing::TestError) {
25 +
    let mut buffer: [u8; 256] = [0; 256];
26 +
    let mut out = writer::new(&mut buffer[..], &["data", "fn"]);
27 +
    try writer::instr(&mut out, item) catch { throw testing::TestError::Failed; };
28 +
    try testing::expectBytesEq(&buffer[..out.offset], expected);
29 +
    for capacity in 0..expected.len {
30 +
        let mut short = writer::new(&mut buffer[..capacity], &["data", "fn"]);
31 +
        let mut failed = false;
32 +
        try writer::instr(&mut short, item) catch err {
33 +
            try testing::expect(err == binary::Error::Capacity);
34 +
            set failed = true;
35 +
        };
36 +
        try testing::expect(failed);
37 +
        try testing::expect(short.offset <= capacity);
38 +
    }
39 +
40 +
}
41 +
42 +
/// Check all instruction tags and their field order.
43 +
@test unsafe fn instructions() throws (testing::TestError) {
44 +
    let mut args: [il::Val; 1] = [il::Val::Undef];
45 +
    let mut cases: [il::SwitchCase; 1] = [il::SwitchCase {
46 +
        value: -1, target: 2, args: &mut args[..],
47 +
    }];
48 +
    try instruction(il::Instr::Reserve {
49 +
        dst: il::Reg { n: 1 }, size: il::Val::Undef, alignment: 16,
50 +
    }, &[0, 1, 0, 0, 0, 4, 16, 0, 0, 0]);
51 +
    try instruction(il::Instr::Load {
52 +
        typ: il::Type::W8, dst: il::Reg { n: 1 }, src: il::Reg { n: 2 }, offset: -1,
53 +
    }, &[1, 1, 1, 0, 0, 0, 2, 0, 0, 0, 255, 255, 255, 255]);
54 +
    try instruction(il::Instr::Sload {
55 +
        typ: il::Type::W16, dst: il::Reg { n: 1 }, src: il::Reg { n: 2 }, offset: 3,
56 +
    }, &[2, 2, 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0]);
57 +
    try instruction(il::Instr::Store {
58 +
        typ: il::Type::W32, src: il::Val::Undef, dst: il::Reg { n: 2 }, offset: 3,
59 +
    }, &[3, 4, 4, 2, 0, 0, 0, 3, 0, 0, 0]);
60 +
    try instruction(il::Instr::Blit {
61 +
        dst: il::Reg { n: 1 }, src: il::Reg { n: 2 }, size: il::Val::Undef,
62 +
    }, &[4, 1, 0, 0, 0, 2, 0, 0, 0, 4]);
63 +
    try instruction(il::Instr::Copy {
64 +
        dst: il::Reg { n: 1 }, val: il::Val::Undef,
65 +
    }, &[5, 1, 0, 0, 0, 4]);
66 +
    try instruction(il::Instr::BinOp {
67 +
        op: il::BinOp::Add, typ: il::Type::W64, dst: il::Reg { n: 1 }, a: il::Val::Undef,
68 +
        b: il::Val::Undef,
69 +
    }, &[6, 0, 8, 1, 0, 0, 0, 4, 4]);
70 +
    try instruction(il::Instr::UnOp {
71 +
        op: il::UnOp::Neg, typ: il::Type::W64, dst: il::Reg { n: 1 }, a: il::Val::Undef,
72 +
    }, &[7, 0, 8, 1, 0, 0, 0, 4]);
73 +
    try instruction(il::Instr::Zext {
74 +
        typ: il::Type::W8, dst: il::Reg { n: 1 }, val: il::Val::Undef,
75 +
    }, &[8, 1, 1, 0, 0, 0, 4]);
76 +
    try instruction(il::Instr::Sext {
77 +
        typ: il::Type::W16, dst: il::Reg { n: 1 }, val: il::Val::Undef,
78 +
    }, &[9, 2, 1, 0, 0, 0, 4]);
79 +
    try instruction(il::Instr::Call {
80 +
        retTy: il::Type::W64, dst: nil, func: il::Val::FnAddr("fn"), args: &[],
81 +
    }, &[10, 8, 0, 3, 1, 0, 0, 0, 0, 0, 0, 0]);
82 +
    try instruction(il::Instr::Call {
83 +
        retTy: il::Type::W64, dst: il::Reg { n: 2 }, func: il::Val::FnAddr("fn"), args: &args[..],
84 +
    }, &[10, 8, 1, 2, 0, 0, 0, 3, 1, 0, 0, 0, 1, 0, 0, 0, 4]);
85 +
    try instruction(il::Instr::Ret {
86 +
        val: nil,
87 +
    }, &[11, 0]);
88 +
    try instruction(il::Instr::Ret {
89 +
        val: il::Val::Undef,
90 +
    }, &[11, 1, 4]);
91 +
    try instruction(il::Instr::Jmp {
92 +
        target: 2, args: &mut args[..],
93 +
    }, &[12, 2, 0, 0, 0, 1, 0, 0, 0, 4]);
94 +
    try instruction(il::Instr::Br {
95 +
        op: il::CmpOp::Eq, typ: il::Type::W32, a: il::Val::Undef, b: il::Val::Undef, thenTarget: 1,
96 +
        thenArgs: &mut args[..], elseTarget: 2, elseArgs: &mut args[..],
97 +
    }, &[13, 0, 4, 4, 4, 1, 0, 0, 0, 1, 0, 0, 0, 4, 2, 0, 0, 0, 1, 0, 0, 0, 4]);
98 +
    try instruction(il::Instr::Switch {
99 +
        val: il::Val::Undef, defaultTarget: 1, defaultArgs: &mut args[..], cases: &mut cases[..],
100 +
    }, &[
101 +
        14, 4, 1, 0, 0, 0, 1, 0, 0, 0, 4, 1,
102 +
        0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 2,
103 +
        0, 0, 0, 1, 0, 0, 0, 4,
104 +
    ]);
105 +
    try instruction(il::Instr::Unreachable, &[15]);
106 +
    try instruction(il::Instr::Ecall {
107 +
        dst: il::Reg { n: 1 }, num: il::Val::Undef, a0: il::Val::Undef, a1: il::Val::Undef,
108 +
        a2: il::Val::Undef, a3: il::Val::Undef,
109 +
    }, &[16, 1, 0, 0, 0, 4, 4, 4, 4, 4]);
110 +
    try instruction(il::Instr::Ebreak, &[17]);
111 +
    try instruction(il::Instr::MemoryFence, &[18]);
112 +
}
113 +
114 +
/// Check every value tag and empty and nonempty sequences.
115 +
@test unsafe fn values() throws (testing::TestError) {
116 +
    let mut buffer: [u8; 64] = [0; 64];
117 +
    let mut out = writer::new(&mut buffer[..], &["data", "fn"]);
118 +
    try writer::values(&mut out, &[]) catch { throw testing::TestError::Failed; };
119 +
    try writer::values(&mut out, &[
120 +
        il::Val::Reg(il::Reg { n: 0x12345678 }),
121 +
        il::Val::Imm(-2), il::Val::DataSym("data"),
122 +
        il::Val::FnAddr("fn"), il::Val::Undef,
123 +
    ]) catch { throw testing::TestError::Failed; };
124 +
    try testing::expectBytesEq(&buffer[..out.offset], &[
125 +
        0, 0, 0, 0, 5, 0, 0, 0,
126 +
        0, 0x78, 0x56, 0x34, 0x12,
127 +
        1, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
128 +
        2, 0, 0, 0, 0, 3, 1, 0, 0, 0, 4,
129 +
    ]);
130 +
131 +
}
132 +
133 +
/// Check initializer bytes and repetition counts.
134 +
@test unsafe fn initializers() throws (testing::TestError) {
135 +
    let mut buffer: [u8; 128] = [0; 128];
136 +
    let mut out = writer::new(&mut buffer[..], &["data", "fn"]);
137 +
    try writer::dataValue(&mut out, il::DataValue { item: il::DataItem::Val { typ: il::Type::W8,
138 +
        val: -1 }, count: 0 }) catch { throw testing::TestError::Failed; };
139 +
    try writer::dataValue(&mut out, il::DataValue { item: il::DataItem::Val { typ: il::Type::W16,
140 +
        val: -2 }, count: 1 }) catch { throw testing::TestError::Failed; };
141 +
    try writer::dataValue(&mut out, il::DataValue { item: il::DataItem::Val { typ: il::Type::W32,
142 +
        val: -3 }, count: 2 }) catch { throw testing::TestError::Failed; };
143 +
    try writer::dataValue(&mut out, il::DataValue { item: il::DataItem::Val { typ: il::Type::W64,
144 +
        val: -4 }, count: 3 }) catch { throw testing::TestError::Failed; };
145 +
    try writer::dataValue(&mut out, il::DataValue { item: il::DataItem::Sym("data"), count: 4 })
146 +
        catch { throw testing::TestError::Failed; };
147 +
    try writer::dataValue(&mut out, il::DataValue { item: il::DataItem::Fn("fn"), count: 5 }) catch
148 +
        { throw testing::TestError::Failed; };
149 +
    try writer::dataValue(&mut out, il::DataValue { item: il::DataItem::Str("ab"), count: 6 }) catch
150 +
        { throw testing::TestError::Failed; };
151 +
    try writer::dataValue(&mut out, il::DataValue { item: il::DataItem::Str(""), count: 7 }) catch {
152 +
        throw testing::TestError::Failed; };
153 +
    try writer::dataValue(&mut out, il::DataValue { item: il::DataItem::Undef, count: 8 }) catch {
154 +
        throw testing::TestError::Failed; };
155 +
    try testing::expectBytesEq(&buffer[..out.offset], &[0, 1, 255, 0, 0, 0, 0, 0, 2, 254, 255, 1, 0,
156 +
        0, 0, 0, 4, 253, 255, 255, 255, 2, 0, 0, 0, 0, 8, 252, 255, 255, 255, 255, 255, 255, 255, 3,
157 +
        0, 0, 0, 1, 0, 0, 0, 0, 4, 0, 0, 0, 2, 1, 0, 0, 0, 5, 0, 0, 0, 3, 2, 0, 0, 0, 97, 98, 6, 0,
158 +
        0, 0, 3, 0, 0, 0, 0, 7, 0, 0, 0, 4, 8, 0, 0, 0]);
159 +
}
160 +
161 +
/// Reject invalid widths, insufficient storage, and absent symbols.
162 +
@test unsafe fn errors() throws (testing::TestError) {
163 +
    let mut buffer: [u8; 8] = [0; 8];
164 +
    let mut out = writer::new(&mut buffer[..], &[]);
165 +
    let mut failures: u32 = 0;
166 +
    try writer::integer(&mut out, 1, 3) catch err {
167 +
        try testing::expect(err == binary::Error::Invalid);
168 +
        set failures += 1;
169 +
    };
170 +
    try writer::symbol(&mut out, "absent") catch err {
171 +
        try testing::expect(err == binary::Error::Symbol);
172 +
        set failures += 1;
173 +
    };
174 +
    try testing::expect(out.offset == 0);
175 +
    try writer::integer(&mut out, 0, 8) catch { throw testing::TestError::Failed; };
176 +
    try writer::integer(&mut out, 1, 1) catch err {
177 +
        try testing::expect(err == binary::Error::Capacity);
178 +
        set failures += 1;
179 +
    };
180 +
    try testing::expect(failures == 3);
181 +
    try testing::expect(out.offset == 8);
182 +
}
183 +
184 +
/// Check operation tags independently of the native union representation.
185 +
@test unsafe fn operations() throws (testing::TestError) {
186 +
    let binaryOps = &[
187 +
        il::BinOp::Add, il::BinOp::Sub, il::BinOp::Mul,
188 +
        il::BinOp::Sdiv, il::BinOp::Udiv, il::BinOp::Srem,
189 +
        il::BinOp::Urem, il::BinOp::Eq, il::BinOp::Ne,
190 +
        il::BinOp::Slt, il::BinOp::Sge, il::BinOp::Ult,
191 +
        il::BinOp::Uge, il::BinOp::And, il::BinOp::Or,
192 +
        il::BinOp::Xor, il::BinOp::Shl, il::BinOp::Sshr,
193 +
        il::BinOp::Ushr,
194 +
    ];
195 +
    for op, tag in binaryOps {
196 +
        try instruction(il::Instr::BinOp {
197 +
            op, typ: il::Type::W64, dst: il::Reg { n: 1 },
198 +
            a: il::Val::Undef, b: il::Val::Undef,
199 +
        }, &[6, tag as u8, 8, 1, 0, 0, 0, 4, 4]);
200 +
    }
201 +
    for op, tag in &[il::UnOp::Neg, il::UnOp::Not] {
202 +
        try instruction(il::Instr::UnOp {
203 +
            op, typ: il::Type::W64, dst: il::Reg { n: 1 },
204 +
            a: il::Val::Undef,
205 +
        }, &[7, tag as u8, 8, 1, 0, 0, 0, 4]);
206 +
    }
207 +
    let mut empty: [il::Val; 0] = [];
208 +
    for op, tag in &[il::CmpOp::Eq, il::CmpOp::Ne, il::CmpOp::Slt, il::CmpOp::Ult] {
209 +
        try instruction(il::Instr::Br {
210 +
            op, typ: il::Type::W8, a: il::Val::Undef, b: il::Val::Undef,
211 +
            thenTarget: 1, thenArgs: &mut empty[..],
212 +
            elseTarget: 2, elseArgs: &mut empty[..],
213 +
        }, &[13, tag as u8, 1, 4, 4, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0]);
214 +
    }
215 +
}
216 +
217 +
/// Check byte-sequence bounds at every output size.
218 +
@test unsafe fn byteCapacity() throws (testing::TestError) {
219 +
    let mut buffer: [u8; 7] = [0; 7];
220 +
    for capacity in 0..7 {
221 +
        let mut out = writer::new(&mut buffer[..capacity], &[]);
222 +
        let mut failed = false;
223 +
        try writer::bytes(&mut out, "abc") catch err {
224 +
            try testing::expect(err == binary::Error::Capacity);
225 +
            set failed = true;
226 +
        };
227 +
        try testing::expect(failed);
228 +
        try testing::expect(out.offset <= capacity);
229 +
    }
230 +
    let mut out = writer::new(&mut buffer[..], &[]);
231 +
    try writer::bytes(&mut out, "abc") catch { throw testing::TestError::Failed; };
232 +
    try testing::expectBytesEq(&buffer[..], &[3, 0, 0, 0, 97, 98, 99]);
233 +
}
lib/std/lang/il/binary/writer.rad added +302 -0
1 +
//! Bounded binary RIL encoding into caller-owned storage.
2 +
//! After an error, discard bytes written by the failed operation.
3 +
4 +
use std::lang::il;
5 +
use std::mem;
6 +
use std::lang::il::binary;
7 +
8 +
/// Output cursor and the symbol table used by this package.
9 +
/// The output buffer and symbol table must remain valid until the last write.
10 +
export record Writer: Copy {
11 +
    /// Destination bytes.
12 +
    bytes: *unsafe mut [u8],
13 +
    /// Number of bytes written.
14 +
    offset: u32,
15 +
    /// Names in wire-index order. The caller supplies unique names.
16 +
    symbols: *unsafe [*[u8]],
17 +
}
18 +
19 +
/// Create an output cursor without allocation.
20 +
/// The caller must retain exclusive access to the output buffer.
21 +
export unsafe fn new(bytes: &mut [u8], symbols: *unsafe [*[u8]]) -> Writer {
22 +
    return Writer { bytes: bytes as *unsafe mut [u8], offset: 0, symbols };
23 +
}
24 +
25 +
/// Write the low bytes of an integer with width 1, 2, 4, or 8.
26 +
export unsafe fn integer(out: &mut Writer, value: u64, width: u32) throws (binary::Error) {
27 +
    if width <> 1 and width <> 2 and width <> 4 and width <> 8 {
28 +
        throw binary::Error::Invalid;
29 +
    }
30 +
    if out.offset > out.bytes.len or width > out.bytes.len - out.offset {
31 +
        throw binary::Error::Capacity;
32 +
    }
33 +
    for i in 0..width {
34 +
        set out.bytes[out.offset + i] = (value >> (i * 8) as u64) as u8;
35 +
    }
36 +
    set out.offset += width;
37 +
}
38 +
39 +
/// Write a byte sequence with its u32 length.
40 +
export unsafe fn bytes(out: &mut Writer, value: &[u8]) throws (binary::Error) {
41 +
    try integer(out, value.len as u64, 4);
42 +
    if value.len > out.bytes.len - out.offset {
43 +
        throw binary::Error::Capacity;
44 +
    }
45 +
    for b, i in value {
46 +
        set out.bytes[out.offset + i] = b;
47 +
    }
48 +
    set out.offset += value.len;
49 +
}
50 +
51 +
/// Write the index of a name in the package symbol table.
52 +
export unsafe fn symbol(out: &mut Writer, name: &[u8]) throws (binary::Error) {
53 +
    for candidate, i in out.symbols {
54 +
        if mem::eq(candidate, name) {
55 +
            try integer(out, i as u64, 4);
56 +
            return;
57 +
        }
58 +
    }
59 +
    throw binary::Error::Symbol;
60 +
}
61 +
62 +
/// Write an IL type as its byte width.
63 +
export unsafe fn typ(out: &mut Writer, value: il::Type) throws (binary::Error) {
64 +
    try integer(out, il::typeSize(value) as u64, 1);
65 +
}
66 +
67 +
/// Write a tagged value. Symbol addresses use table indices.
68 +
export unsafe fn val(out: &mut Writer, value: il::Val) throws (binary::Error) {
69 +
    match value {
70 +
        case il::Val::Reg(reg) => {
71 +
            try integer(out, super::VALUE_REG as u64, 1);
72 +
            try integer(out, reg.n as u64, 4);
73 +
        },
74 +
        case il::Val::Imm(n) => {
75 +
            try integer(out, super::VALUE_IMM as u64, 1);
76 +
            try integer(out, n as u64, 8);
77 +
        },
78 +
        case il::Val::DataSym(name) => {
79 +
            try integer(out, super::VALUE_DATASYM as u64, 1);
80 +
            try symbol(out, name);
81 +
        },
82 +
        case il::Val::FnAddr(name) => {
83 +
            try integer(out, super::VALUE_FNADDR as u64, 1);
84 +
            try symbol(out, name);
85 +
        },
86 +
        case il::Val::Undef => try integer(out, super::VALUE_UNDEF as u64, 1),
87 +
    }
88 +
}
89 +
90 +
/// Write a sequence of values.
91 +
export unsafe fn values(out: &mut Writer, items: &[il::Val]) throws (binary::Error) {
92 +
    try integer(out, items.len as u64, 4);
93 +
    for item in items {
94 +
        try val(out, item);
95 +
    }
96 +
}
97 +
98 +
/// Write an explicit bin operation tag.
99 +
unsafe fn binOp(out: &mut Writer, value: il::BinOp) throws (binary::Error) {
100 +
    match value {
101 +
        case il::BinOp::Add => try integer(out, super::BIN_ADD as u64, 1),
102 +
        case il::BinOp::Sub => try integer(out, super::BIN_SUB as u64, 1),
103 +
        case il::BinOp::Mul => try integer(out, super::BIN_MUL as u64, 1),
104 +
        case il::BinOp::Sdiv => try integer(out, super::BIN_SDIV as u64, 1),
105 +
        case il::BinOp::Udiv => try integer(out, super::BIN_UDIV as u64, 1),
106 +
        case il::BinOp::Srem => try integer(out, super::BIN_SREM as u64, 1),
107 +
        case il::BinOp::Urem => try integer(out, super::BIN_UREM as u64, 1),
108 +
        case il::BinOp::Eq => try integer(out, super::BIN_EQ as u64, 1),
109 +
        case il::BinOp::Ne => try integer(out, super::BIN_NE as u64, 1),
110 +
        case il::BinOp::Slt => try integer(out, super::BIN_SLT as u64, 1),
111 +
        case il::BinOp::Sge => try integer(out, super::BIN_SGE as u64, 1),
112 +
        case il::BinOp::Ult => try integer(out, super::BIN_ULT as u64, 1),
113 +
        case il::BinOp::Uge => try integer(out, super::BIN_UGE as u64, 1),
114 +
        case il::BinOp::And => try integer(out, super::BIN_AND as u64, 1),
115 +
        case il::BinOp::Or => try integer(out, super::BIN_OR as u64, 1),
116 +
        case il::BinOp::Xor => try integer(out, super::BIN_XOR as u64, 1),
117 +
        case il::BinOp::Shl => try integer(out, super::BIN_SHL as u64, 1),
118 +
        case il::BinOp::Sshr => try integer(out, super::BIN_SSHR as u64, 1),
119 +
        case il::BinOp::Ushr => try integer(out, super::BIN_USHR as u64, 1),
120 +
    }
121 +
}
122 +
123 +
/// Write an explicit un operation tag.
124 +
unsafe fn unOp(out: &mut Writer, value: il::UnOp) throws (binary::Error) {
125 +
    match value {
126 +
        case il::UnOp::Neg => try integer(out, super::UN_NEG as u64, 1),
127 +
        case il::UnOp::Not => try integer(out, super::UN_NOT as u64, 1),
128 +
    }
129 +
}
130 +
131 +
/// Write an explicit cmp operation tag.
132 +
unsafe fn cmpOp(out: &mut Writer, value: il::CmpOp) throws (binary::Error) {
133 +
    match value {
134 +
        case il::CmpOp::Eq => try integer(out, super::CMP_EQ as u64, 1),
135 +
        case il::CmpOp::Ne => try integer(out, super::CMP_NE as u64, 1),
136 +
        case il::CmpOp::Slt => try integer(out, super::CMP_SLT as u64, 1),
137 +
        case il::CmpOp::Ult => try integer(out, super::CMP_ULT as u64, 1),
138 +
    }
139 +
}
140 +
141 +
/// Write one instruction. Fields follow the IL record declaration order.
142 +
export unsafe fn instr(out: &mut Writer, item: il::Instr) throws (binary::Error) {
143 +
    match item {
144 +
        case il::Instr::Reserve { dst: vdst, size: vsize, alignment: valignment } => {
145 +
            try integer(out, super::INSTR_RESERVE as u64, 1);
146 +
            try integer(out, vdst.n as u64, 4);
147 +
            try val(out, vsize);
148 +
            try integer(out, valignment as u64, 4);
149 +
        },
150 +
        case il::Instr::Load { typ: vtyp, dst: vdst, src: vsrc, offset: voffset } => {
151 +
            try integer(out, super::INSTR_LOAD as u64, 1);
152 +
            try typ(out, vtyp);
153 +
            try integer(out, vdst.n as u64, 4);
154 +
            try integer(out, vsrc.n as u64, 4);
155 +
            try integer(out, voffset as u64, 4);
156 +
        },
157 +
        case il::Instr::Sload { typ: vtyp, dst: vdst, src: vsrc, offset: voffset } => {
158 +
            try integer(out, super::INSTR_SLOAD as u64, 1);
159 +
            try typ(out, vtyp);
160 +
            try integer(out, vdst.n as u64, 4);
161 +
            try integer(out, vsrc.n as u64, 4);
162 +
            try integer(out, voffset as u64, 4);
163 +
        },
164 +
        case il::Instr::Store { typ: vtyp, src: vsrc, dst: vdst, offset: voffset } => {
165 +
            try integer(out, super::INSTR_STORE as u64, 1);
166 +
            try typ(out, vtyp);
167 +
            try val(out, vsrc);
168 +
            try integer(out, vdst.n as u64, 4);
169 +
            try integer(out, voffset as u64, 4);
170 +
        },
171 +
        case il::Instr::Blit { dst: vdst, src: vsrc, size: vsize } => {
172 +
            try integer(out, super::INSTR_BLIT as u64, 1);
173 +
            try integer(out, vdst.n as u64, 4);
174 +
            try integer(out, vsrc.n as u64, 4);
175 +
            try val(out, vsize);
176 +
        },
177 +
        case il::Instr::Copy { dst: vdst, val: vval } => {
178 +
            try integer(out, super::INSTR_COPY as u64, 1);
179 +
            try integer(out, vdst.n as u64, 4);
180 +
            try val(out, vval);
181 +
        },
182 +
        case il::Instr::BinOp { op: vop, typ: vtyp, dst: vdst, a: va, b: vb } => {
183 +
            try integer(out, super::INSTR_BINOP as u64, 1);
184 +
            try binOp(out, vop);
185 +
            try typ(out, vtyp);
186 +
            try integer(out, vdst.n as u64, 4);
187 +
            try val(out, va);
188 +
            try val(out, vb);
189 +
        },
190 +
        case il::Instr::UnOp { op: vop, typ: vtyp, dst: vdst, a: va } => {
191 +
            try integer(out, super::INSTR_UNOP as u64, 1);
192 +
            try unOp(out, vop);
193 +
            try typ(out, vtyp);
194 +
            try integer(out, vdst.n as u64, 4);
195 +
            try val(out, va);
196 +
        },
197 +
        case il::Instr::Zext { typ: vtyp, dst: vdst, val: vval } => {
198 +
            try integer(out, super::INSTR_ZEXT as u64, 1);
199 +
            try typ(out, vtyp);
200 +
            try integer(out, vdst.n as u64, 4);
201 +
            try val(out, vval);
202 +
        },
203 +
        case il::Instr::Sext { typ: vtyp, dst: vdst, val: vval } => {
204 +
            try integer(out, super::INSTR_SEXT as u64, 1);
205 +
            try typ(out, vtyp);
206 +
            try integer(out, vdst.n as u64, 4);
207 +
            try val(out, vval);
208 +
        },
209 +
        case il::Instr::Call { retTy: vretTy, dst: vdst, func: vfunc, args: vargs } => {
210 +
            try integer(out, super::INSTR_CALL as u64, 1);
211 +
            try typ(out, vretTy);
212 +
            if let present = vdst {
213 +
                try integer(out, 1, 1);
214 +
                try integer(out, present.n as u64, 4);
215 +
            } else {
216 +
                try integer(out, 0, 1);
217 +
            }
218 +
            try val(out, vfunc);
219 +
            try values(out, vargs);
220 +
        },
221 +
        case il::Instr::Ret { val: vval } => {
222 +
            try integer(out, super::INSTR_RET as u64, 1);
223 +
            if let present = vval {
224 +
                try integer(out, 1, 1);
225 +
                try val(out, present);
226 +
            } else {
227 +
                try integer(out, 0, 1);
228 +
            }
229 +
        },
230 +
        case il::Instr::Jmp { target: vtarget, args: vargs } => {
231 +
            try integer(out, super::INSTR_JMP as u64, 1);
232 +
            try integer(out, vtarget as u64, 4);
233 +
            try values(out, vargs);
234 +
        },
235 +
        case il::Instr::Br { op: vop, typ: vtyp, a: va, b: vb, thenTarget: vthenTarget, thenArgs: vthenArgs, elseTarget: velseTarget, elseArgs: velseArgs } => {
236 +
            try integer(out, super::INSTR_BR as u64, 1);
237 +
            try cmpOp(out, vop);
238 +
            try typ(out, vtyp);
239 +
            try val(out, va);
240 +
            try val(out, vb);
241 +
            try integer(out, vthenTarget as u64, 4);
242 +
            try values(out, vthenArgs);
243 +
            try integer(out, velseTarget as u64, 4);
244 +
            try values(out, velseArgs);
245 +
        },
246 +
        case il::Instr::Switch { val: vval, defaultTarget: vdefaultTarget, defaultArgs: vdefaultArgs, cases: vcases } => {
247 +
            try integer(out, super::INSTR_SWITCH as u64, 1);
248 +
            try val(out, vval);
249 +
            try integer(out, vdefaultTarget as u64, 4);
250 +
            try values(out, vdefaultArgs);
251 +
            try integer(out, vcases.len as u64, 4);
252 +
            for branch in vcases {
253 +
                try integer(out, branch.value as u64, 8);
254 +
                try integer(out, branch.target as u64, 4);
255 +
                try values(out, branch.args);
256 +
            }
257 +
        },
258 +
        case il::Instr::Unreachable => {
259 +
            try integer(out, super::INSTR_UNREACHABLE as u64, 1);
260 +
        },
261 +
        case il::Instr::Ecall { dst: vdst, num: vnum, a0: va0, a1: va1, a2: va2, a3: va3 } => {
262 +
            try integer(out, super::INSTR_ECALL as u64, 1);
263 +
            try integer(out, vdst.n as u64, 4);
264 +
            try val(out, vnum);
265 +
            try val(out, va0);
266 +
            try val(out, va1);
267 +
            try val(out, va2);
268 +
            try val(out, va3);
269 +
        },
270 +
        case il::Instr::Ebreak => {
271 +
            try integer(out, super::INSTR_EBREAK as u64, 1);
272 +
        },
273 +
        case il::Instr::MemoryFence => {
274 +
            try integer(out, super::INSTR_MEMORYFENCE as u64, 1);
275 +
        },
276 +
    }
277 +
}
278 +
279 +
/// Write one data initializer and its repetition count.
280 +
export unsafe fn dataValue(out: &mut Writer, value: il::DataValue) throws (binary::Error) {
281 +
    match value.item {
282 +
        case il::DataItem::Val { typ: t, val: n } => {
283 +
            try integer(out, super::DATA_VAL as u64, 1);
284 +
            try typ(out, t);
285 +
            try integer(out, n as u64, il::typeSize(t));
286 +
        },
287 +
        case il::DataItem::Sym(name) => {
288 +
            try integer(out, super::DATA_SYM as u64, 1);
289 +
            try symbol(out, name);
290 +
        },
291 +
        case il::DataItem::Fn(name) => {
292 +
            try integer(out, super::DATA_FN as u64, 1);
293 +
            try symbol(out, name);
294 +
        },
295 +
        case il::DataItem::Str(text) => {
296 +
            try integer(out, super::DATA_STR as u64, 1);
297 +
            try bytes(out, text);
298 +
        },
299 +
        case il::DataItem::Undef => try integer(out, super::DATA_UNDEF as u64, 1),
300 +
    }
301 +
    try integer(out, value.count as u64, 4);
302 +
}
std.lib +2 -0
29 29
lib/std/lang/ast/printer.rad
30 30
lib/std/lang/scanner.rad
31 31
lib/std/lang/parser.rad
32 32
lib/std/lang/il.rad
33 33
lib/std/lang/il/printer.rad
34 +
lib/std/lang/il/binary.rad
35 +
lib/std/lang/il/binary/writer.rad
34 36
lib/std/lang/resolver.rad
35 37
lib/std/lang/resolver/printer.rad
36 38
lib/std/lang/lower.rad
37 39
lib/std/lang/module.rad
38 40
lib/std/lang/module/printer.rad
std.lib.test +1 -0
8 8
lib/std/lang/parser/tests.rad
9 9
lib/std/lang/module/tests.rad
10 10
lib/std/lang/scanner/tests.rad
11 11
lib/std/lang/resolver/tests.rad
12 12
lib/std/lang/gen/bitset/tests.rad
13 +
lib/std/lang/il/binary/tests.rad