compiler: Initialize decoded operands through exact storage

f6905c51ccc4c0168df84c223f55f1aa4de378a74709156ce758bbb5125ffc1d
Alexis Sellier committed ago 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] {