compiler: Encode inline instruction operands in checked code

8a2937566a013883fc9d175ea32beba3507970ed5c433019090ca19daefe4ba3
Alexis Sellier committed ago 1 parent 1f3e014e
lib/std/lang/il/binary/tests.rad +11 -2
7 7
use std::lang::il::binary::reader;
8 8
use std::lang::alloc;
9 9
10 10
/// Decode arena backing storage. Tests reset it before each use.
11 11
static MEMORY: [u8; 512] = [0; 512];
12 +
/// Marker for bytes beyond a writer's borrowed output extent.
13 +
constant OUTPUT_CANARY: u8 = 0xa5;
12 14
13 15
/// Scalar decoding uses only checked input bytes and numeric cursor bounds.
14 16
fn checkScalarReader 'input (input: &mut reader::Reader 'input) throws (testing::TestError) {
15 17
    set input.registers = 2;
16 18
    set input.blocks = 3;
117 119
        let mut out = writer::new(storage, names);
118 120
        try writer::instr(&mut out, item) catch {
119 121
            throw testing::TestError::Failed;
120 122
        };
121 123
        try testing::expectBytesEq(&out.bytes[..out.offset], expected);
122 -
        for capacity in 0..expected.len {
124 +
        for capacity in 0..(expected.len + 1) {
125 +
            for i in 0..out.bytes.len {
126 +
                set out.bytes[i] = OUTPUT_CANARY;
127 +
            }
123 128
            let shortStorage: 'short = &mut out.bytes[..capacity], shortNames = &out.symbols[..] in {
124 129
                let mut short = writer::new(shortStorage, shortNames);
125 130
                let mut failed = false;
126 131
                try writer::instr(&mut short, item) catch err {
127 132
                    try testing::expect(err == binary::Error::Capacity);
128 133
                    set failed = true;
129 134
                };
130 -
                try testing::expect(failed);
135 +
                assert failed == (capacity < expected.len);
131 136
                try testing::expect(short.offset <= capacity);
137 +
                try testing::expectBytesEq(&short.bytes[..short.offset], &expected[..short.offset]);
138 +
            }
139 +
            for i in capacity..out.bytes.len {
140 +
                assert out.bytes[i] == OUTPUT_CANARY;
132 141
            }
133 142
        }
134 143
        // Inline operands use only the input bytes and symbol table.
135 144
        let mut arenaSize: u32 = 0;
136 145
        match item {
lib/std/lang/il/binary/writer.rad +51 -41
136 136
        case il::CmpOp::Slt => try integer(out, super::CMP_SLT as u64, 1),
137 137
        case il::CmpOp::Ult => try integer(out, super::CMP_ULT as u64, 1),
138 138
    }
139 139
}
140 140
141 -
/// Write one instruction. Fields follow the IL record declaration order.
142 -
/// All raw instruction operand tables must remain valid during encoding.
143 -
export unsafe fn instr 'buffer (out: &mut Writer 'buffer, item: il::Instr) throws (binary::Error) {
141 +
/// Write an instruction with inline operands.
142 +
fn fixedInstr 'buffer (out: &mut Writer 'buffer, item: il::Instr) throws (binary::Error) {
144 143
    match item {
145 144
        case il::Instr::Reserve { dst: vdst, size: vsize, alignment: valignment } => {
146 145
            try integer(out, super::INSTR_RESERVE as u64, 1);
147 146
            try integer(out, vdst.n as u64, 4);
148 147
            try val(out, vsize);
205 204
            try integer(out, super::INSTR_SEXT as u64, 1);
206 205
            try typ(out, vtyp);
207 206
            try integer(out, vdst.n as u64, 4);
208 207
            try val(out, vval);
209 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 +
    match item {
210 257
        case il::Instr::Call { retTy: vretTy, dst: vdst, func: vfunc, args: vargs } => {
211 258
            try integer(out, super::INSTR_CALL as u64, 1);
212 259
            try typ(out, vretTy);
213 260
            if let present = vdst {
214 261
                try integer(out, 1, 1);
217 264
                try integer(out, 0, 1);
218 265
            }
219 266
            try val(out, vfunc);
220 267
            try values(out, vargs);
221 268
        },
222 -
        case il::Instr::Ret { val: vval } => {
223 -
            try integer(out, super::INSTR_RET as u64, 1);
224 -
            if let present = vval {
225 -
                try integer(out, 1, 1);
226 -
                try val(out, present);
227 -
            } else {
228 -
                try integer(out, 0, 1);
229 -
            }
230 -
        },
231 269
        case il::Instr::Jmp { target: vtarget, args: vargs } => {
232 270
            try integer(out, super::INSTR_JMP as u64, 1);
233 271
            try integer(out, vtarget as u64, 4);
234 272
            try values(out, vargs);
235 273
        },
254 292
                try integer(out, branch.value as u64, 8);
255 293
                try integer(out, branch.target as u64, 4);
256 294
                try values(out, branch.args);
257 295
            }
258 296
        },
259 -
        case il::Instr::Unreachable => {
260 -
            try integer(out, super::INSTR_UNREACHABLE as u64, 1);
261 -
        },
262 -
        case il::Instr::Ecall { dst: vdst, num: vnum, a0: va0, a1: va1, a2: va2, a3: va3 } => {
263 -
            try integer(out, super::INSTR_ECALL as u64, 1);
264 -
            try integer(out, vdst.n as u64, 4);
265 -
            try val(out, vnum);
266 -
            try val(out, va0);
267 -
            try val(out, va1);
268 -
            try val(out, va2);
269 -
            try val(out, va3);
270 -
        },
271 -
        case il::Instr::DeviceRead { typ: t, dst, handle, offset } => {
272 -
            try integer(out, super::INSTR_DEVICE_READ as u64, 1);
273 -
            try typ(out, t);
274 -
            try integer(out, dst.n as u64, 4);
275 -
            try val(out, handle); try val(out, offset);
276 -
        },
277 -
        case il::Instr::DeviceWrite { typ: t, handle, offset, value } => {
278 -
            try integer(out, super::INSTR_DEVICE_WRITE as u64, 1);
279 -
            try typ(out, t);
280 -
            try val(out, handle); try val(out, offset); try val(out, value);
281 -
        },
282 -
        case il::Instr::Ebreak => {
283 -
            try integer(out, super::INSTR_EBREAK as u64, 1);
284 -
        },
285 -
        case il::Instr::MemoryFence => {
286 -
            try integer(out, super::INSTR_MEMORYFENCE as u64, 1);
287 -
        },
297 +
        else => try fixedInstr(out, item),
288 298
    }
289 299
}
290 300
291 301
/// Write one data initializer and its repetition count.
292 302
export fn dataValue 'buffer (out: &mut Writer 'buffer, value: il::DataValue) throws (binary::Error) {