compiler: Initialize decoded operands through exact storage
f6905c51ccc4c0168df84c223f55f1aa4de378a74709156ce758bbb5125ffc1d
1 parent
4d3903cd
lib/std/lang/il/binary/reader.rad
+55 -5
| 3 | 3 | ||
| 4 | 4 | use std::lang::il; |
|
| 5 | 5 | use std::lang::il::binary; |
|
| 6 | 6 | use std::lang::alloc; |
|
| 7 | 7 | ||
| 8 | - | /// Input cursor and reconstruction storage. |
|
| 9 | 8 | /// Input bytes and the symbol table are borrowed for the reader's region. |
|
| 10 | 9 | /// Allocation requires valid raw storage. |
|
| 11 | 10 | export record Reader: 'input + Copy { |
|
| 12 | 11 | /// Encoded bytes. |
|
| 13 | 12 | bytes: &'input [u8], |
| 143 | 142 | } |
|
| 144 | 143 | ||
| 145 | 144 | /// Read a counted sequence of values. |
|
| 146 | 145 | export unsafe fn values 'input (input: &mut Reader 'input) -> *unsafe mut [il::Val] throws (binary::Error) { |
|
| 147 | 146 | let n = try count(input, 1); |
|
| 148 | - | let result = try storage(input, @sizeOf(il::Val), @alignOf(il::Val), n) as *mut [il::Val]; |
|
| 147 | + | let result = try newValues(input, n); |
|
| 149 | 148 | let initialized = try fillValues(input, result); |
|
| 150 | 149 | return (&mut initialized[..]) as *unsafe mut [il::Val]; |
|
| 151 | 150 | } |
|
| 152 | 151 | ||
| 152 | + | /// Exact-capacity storage with a fully initialized operand prefix. |
|
| 153 | + | record ValueBuilder { |
|
| 154 | + | /// Opaque storage reserved for the full operand count. |
|
| 155 | + | storage: *mut [opaque], |
|
| 156 | + | /// Number of initialized operands in the prefix. |
|
| 157 | + | initialized: u32, |
|
| 158 | + | } |
|
| 159 | + | ||
| 160 | + | /// Reserve operand storage without exposing uninitialized typed elements. |
|
| 161 | + | unsafe fn newValues 'input (input: &mut Reader 'input, count: u32) -> ValueBuilder throws (binary::Error) { |
|
| 162 | + | let buffer = try storage(input, @sizeOf(il::Val), @alignOf(il::Val), count); |
|
| 163 | + | return ValueBuilder { storage: buffer, initialized: 0 }; |
|
| 164 | + | } |
|
| 165 | + | ||
| 166 | + | /// Initialize the next operand. Return false when the storage is full. |
|
| 167 | + | fn pushValue(builder: &mut ValueBuilder, value: il::Val) -> bool { |
|
| 168 | + | if builder.initialized == builder.storage.len { |
|
| 169 | + | return false; |
|
| 170 | + | } |
|
| 171 | + | unsafe { |
|
| 172 | + | let values = (&mut builder.storage[..]) as *unsafe mut [il::Val]; |
|
| 173 | + | set values[builder.initialized] = value; |
|
| 174 | + | } |
|
| 175 | + | set builder.initialized += 1; |
|
| 176 | + | return true; |
|
| 177 | + | } |
|
| 178 | + | ||
| 179 | + | /// Consume a builder and publish its owned table only when it is complete. |
|
| 180 | + | /// Incomplete Copy operands need no element cleanup; the arena retains storage. |
|
| 181 | + | fn finishValues(builder: ValueBuilder) -> ValueCompletion { |
|
| 182 | + | let case ValueBuilder { storage, initialized } = builder |
|
| 183 | + | else panic "finishValues: invalid builder"; |
|
| 184 | + | if initialized <> storage.len { |
|
| 185 | + | return ValueCompletion::Incomplete; |
|
| 186 | + | } |
|
| 187 | + | unsafe { |
|
| 188 | + | return ValueCompletion::Complete(storage as *mut [il::Val]); |
|
| 189 | + | } |
|
| 190 | + | } |
|
| 191 | + | ||
| 192 | + | /// Completion distinguishes an empty initialized table from incomplete storage. |
|
| 193 | + | union ValueCompletion { |
|
| 194 | + | /// Fully initialized table, including zero operands. |
|
| 195 | + | Complete(*mut [il::Val]), |
|
| 196 | + | /// Storage discarded before all operands were initialized. |
|
| 197 | + | Incomplete, |
|
| 198 | + | } |
|
| 199 | + | ||
| 153 | 200 | /// Fill an owned operand table with checked tagged values in input order. |
|
| 154 | - | fn fillValues 'input (input: &mut Reader 'input, result: *mut [il::Val]) -> *mut [il::Val] throws (binary::Error) { |
|
| 155 | - | for i in 0..result.len { |
|
| 156 | - | set result[i] = try val(input); |
|
| 201 | + | fn fillValues 'input (input: &mut Reader 'input, builder: ValueBuilder) -> *mut [il::Val] throws (binary::Error) { |
|
| 202 | + | let mut pending = builder; |
|
| 203 | + | while pending.initialized < pending.storage.len { |
|
| 204 | + | assert pushValue(&mut pending, try val(input)); |
|
| 157 | 205 | } |
|
| 206 | + | let case ValueCompletion::Complete(result) = finishValues(pending) |
|
| 207 | + | else panic "fillValues: incomplete operand table"; |
|
| 158 | 208 | return result; |
|
| 159 | 209 | } |
|
| 160 | 210 | ||
| 161 | 211 | /// Read a checked bin operation tag. |
|
| 162 | 212 | fn binOp 'input (input: &mut Reader 'input) -> il::BinOp throws (binary::Error) { |
lib/std/lang/il/binary/tests.rad
+32 -0
| 405 | 405 | } |
|
| 406 | 406 | assert arena.offset == (3 if capacity == 3 else 0); |
|
| 407 | 407 | } |
|
| 408 | 408 | } |
|
| 409 | 409 | ||
| 410 | + | /// Exact operand tables use one allocation and publish only complete prefixes. |
|
| 411 | + | @test unsafe fn exactValueStorage() throws (testing::TestError) { |
|
| 412 | + | let namesTable: [*[u8]; 0] = []; |
|
| 413 | + | let alignment = @alignOf(il::Val); |
|
| 414 | + | let remainder = ((&MEMORY[0]) as u64) & (alignment as u64 - 1); |
|
| 415 | + | let padding = ((alignment as u64 - remainder) & (alignment as u64 - 1)) as u32; |
|
| 416 | + | for count in 0..4 { |
|
| 417 | + | let required = 0 if count == 0 else padding + count * @sizeOf(il::Val); |
|
| 418 | + | let mut encoded: [u8; 7] = [0, 0, 0, 0, 4, 4, 4]; |
|
| 419 | + | set encoded[0] = count as u8; |
|
| 420 | + | for capacity in 0..required + 1 { |
|
| 421 | + | let mut arena = alloc::new(&mut MEMORY[..capacity]); |
|
| 422 | + | let source: 'input = &encoded[..4 + count], names = &namesTable[..] in { |
|
| 423 | + | let mut input = reader::new(source, &mut arena, names); |
|
| 424 | + | let mut failed = false; |
|
| 425 | + | let result = try reader::values(&mut input) catch error { |
|
| 426 | + | assert error == binary::Error::Storage; |
|
| 427 | + | set failed = true; |
|
| 428 | + | &mut [] as *unsafe mut [il::Val] |
|
| 429 | + | }; |
|
| 430 | + | assert failed == (capacity < required); |
|
| 431 | + | assert input.offset == (4 if failed else 4 + count); |
|
| 432 | + | if not failed { |
|
| 433 | + | assert result.len == count; |
|
| 434 | + | for value in result { assert value == il::Val::Undef; } |
|
| 435 | + | } |
|
| 436 | + | } |
|
| 437 | + | assert arena.offset == (0 if capacity < required else required); |
|
| 438 | + | } |
|
| 439 | + | } |
|
| 440 | + | } |
|
| 441 | + | ||
| 410 | 442 | /// Invalid values stop counted decoding at the failing tag or register index. |
|
| 411 | 443 | @test unsafe fn invalidValueSequences() throws (testing::TestError) { |
|
| 412 | 444 | let namesTable: [*[u8]; 0] = []; |
|
| 413 | 445 | for position in 0..2 { |
|
| 414 | 446 | for invalidTag in [false, true] { |