compiler: Encode inline instruction operands in checked code
8a2937566a013883fc9d175ea32beba3507970ed5c433019090ca19daefe4ba3
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) { |