lib/std/lang/il/binary/writer.rad 13.9 KiB raw
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 buffer is exclusively borrowed for the writer's region.
10
export record Writer: 'buffer {
11
    /// Destination bytes.
12
    bytes: &'buffer mut [u8],
13
    /// Number of bytes written.
14
    offset: u32,
15
    /// Names in wire-index order. The caller supplies unique names.
16
    symbols: &'buffer [*[u8]],
17
}
18
19
/// Create an output cursor without allocation.
20
/// Retain the buffer and symbol table for the same region.
21
export fn new 'buffer (bytes: &'buffer mut [u8], symbols: &'buffer [*[u8]]) -> Writer 'buffer {
22
    return Writer 'buffer { bytes, offset: 0, symbols };
23
}
24
25
/// Write the low bytes of an integer with width 1, 2, 4, or 8.
26
export fn integer 'buffer (out: &mut Writer 'buffer, 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 fn bytes 'buffer (out: &mut Writer 'buffer, 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 fn symbol 'buffer (out: &mut Writer 'buffer, 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 fn typ 'buffer (out: &mut Writer 'buffer, 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 fn val 'buffer (out: &mut Writer 'buffer, 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 fn values 'buffer (out: &mut Writer 'buffer, 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
fn binOp 'buffer (out: &mut Writer 'buffer, 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
fn unOp 'buffer (out: &mut Writer 'buffer, 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
fn cmpOp 'buffer (out: &mut Writer 'buffer, 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 an instruction with inline operands.
142
fn fixedInstr 'buffer (out: &mut Writer 'buffer, 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::Ret { val: vval } => {
210
            try integer(out, super::INSTR_RET as u64, 1);
211
            if let present = vval {
212
                try integer(out, 1, 1);
213
                try val(out, present);
214
            } else {
215
                try integer(out, 0, 1);
216
            }
217
        },
218
        case il::Instr::Unreachable => {
219
            try integer(out, super::INSTR_UNREACHABLE as u64, 1);
220
        },
221
        case il::Instr::Ecall { dst: vdst, num: vnum, a0: va0, a1: va1, a2: va2, a3: va3 } => {
222
            try integer(out, super::INSTR_ECALL as u64, 1);
223
            try integer(out, vdst.n as u64, 4);
224
            try val(out, vnum);
225
            try val(out, va0);
226
            try val(out, va1);
227
            try val(out, va2);
228
            try val(out, va3);
229
        },
230
        case il::Instr::DeviceRead { typ: t, dst, handle, offset } => {
231
            try integer(out, super::INSTR_DEVICE_READ as u64, 1);
232
            try typ(out, t);
233
            try integer(out, dst.n as u64, 4);
234
            try val(out, handle); try val(out, offset);
235
        },
236
        case il::Instr::DeviceWrite { typ: t, handle, offset, value } => {
237
            try integer(out, super::INSTR_DEVICE_WRITE as u64, 1);
238
            try typ(out, t);
239
            try val(out, handle); try val(out, offset); try val(out, value);
240
        },
241
        case il::Instr::Ebreak => {
242
            try integer(out, super::INSTR_EBREAK as u64, 1);
243
        },
244
        case il::Instr::MemoryFence => {
245
            try integer(out, super::INSTR_MEMORYFENCE as u64, 1);
246
        },
247
        case il::Instr::Call { .. }, il::Instr::Jmp { .. },
248
             il::Instr::Br { .. }, il::Instr::Switch { .. } =>
249
            panic "fixedInstr: expected inline operands",
250
    }
251
}
252
253
/// Write one instruction. Fields follow the IL record declaration order.
254
/// All raw instruction operand tables must remain valid during encoding.
255
export unsafe fn instr 'buffer (out: &mut Writer 'buffer, item: il::Instr) throws (binary::Error) {
256
    try writeInstr(out, &item);
257
}
258
259
/// Write instruction fields and acquire nested operand views at their wire position.
260
fn writeInstr 'buffer (out: &mut Writer 'buffer, item: &il::Instr) throws (binary::Error) {
261
    match *item {
262
        case il::Instr::Call { retTy: vretTy, dst: vdst, func: vfunc, args: vargs } => {
263
            try integer(out, super::INSTR_CALL as u64, 1);
264
            try typ(out, vretTy);
265
            if let present = vdst {
266
                try integer(out, 1, 1);
267
                try integer(out, present.n as u64, 4);
268
            } else {
269
                try integer(out, 0, 1);
270
            }
271
            try val(out, vfunc);
272
            unsafe {
273
                try values(out, vargs);
274
            }
275
        },
276
        case il::Instr::Jmp { target: vtarget, args: vargs } => {
277
            try integer(out, super::INSTR_JMP as u64, 1);
278
            try integer(out, vtarget as u64, 4);
279
            unsafe {
280
                try values(out, vargs);
281
            }
282
        },
283
        case il::Instr::Br { op: vop, typ: vtyp, a: va, b: vb, thenTarget: vthenTarget, thenArgs: vthenArgs, elseTarget: velseTarget, elseArgs: velseArgs } => {
284
            try integer(out, super::INSTR_BR as u64, 1);
285
            try cmpOp(out, vop);
286
            try typ(out, vtyp);
287
            try val(out, va);
288
            try val(out, vb);
289
            try integer(out, vthenTarget as u64, 4);
290
            unsafe {
291
                try values(out, vthenArgs);
292
            }
293
            try integer(out, velseTarget as u64, 4);
294
            unsafe {
295
                try values(out, velseArgs);
296
            }
297
        },
298
        case il::Instr::Switch { val: vval, defaultTarget: vdefaultTarget, defaultArgs: vdefaultArgs, cases: vcases } => {
299
            try integer(out, super::INSTR_SWITCH as u64, 1);
300
            try val(out, vval);
301
            try integer(out, vdefaultTarget as u64, 4);
302
            unsafe {
303
                try values(out, vdefaultArgs);
304
                try writeCases(out, vcases);
305
            }
306
        },
307
        else => try fixedInstr(out, *item),
308
    }
309
}
310
311
/// Write a counted switch-case table with signed values and argument groups.
312
fn writeCases 'buffer (out: &mut Writer 'buffer, cases: &[il::SwitchCase]) throws (binary::Error) {
313
    try integer(out, cases.len as u64, 4);
314
    for i in 0..cases.len {
315
        let branch = &cases[i];
316
        try integer(out, branch.value as u64, 8);
317
        try integer(out, branch.target as u64, 4);
318
        unsafe {
319
            try values(out, branch.args);
320
        }
321
    }
322
}
323
324
/// Write one data initializer and its repetition count.
325
export fn dataValue 'buffer (out: &mut Writer 'buffer, value: il::DataValue) throws (binary::Error) {
326
    match value.item {
327
        case il::DataItem::Val { typ: t, val: n } => {
328
            try integer(out, super::DATA_VAL as u64, 1);
329
            try typ(out, t);
330
            try integer(out, n as u64, il::typeSize(t));
331
        },
332
        case il::DataItem::Sym(name) => {
333
            try integer(out, super::DATA_SYM as u64, 1);
334
            try symbol(out, name);
335
        },
336
        case il::DataItem::Fn(name) => {
337
            try integer(out, super::DATA_FN as u64, 1);
338
            try symbol(out, name);
339
        },
340
        case il::DataItem::Str(text) => {
341
            try integer(out, super::DATA_STR as u64, 1);
342
            try bytes(out, text);
343
        },
344
        case il::DataItem::Undef => try integer(out, super::DATA_UNDEF as u64, 1),
345
    }
346
    try integer(out, value.count as u64, 4);
347
}