compiler: Collect inline instruction names in checked code

1de1349d4f7c098e324e0c9b25e1188bdf0b03aa38d379c94feca098fb986ab6
Alexis Sellier committed ago 1 parent edab4b3c
lib/std/lang/il/binary/collect.rad +28 -19
85 85
        try value(names, owner, item);
86 86
    }
87 87
}
88 88
89 89
/// Collect all symbolic instruction operands.
90 -
unsafe fn instruction 'tables (names: &mut Names 'tables, owner: *[u8], instr: il::Instr) throws (binary::Error) {
91 -
    match instr {
92 -
        case il::Instr::Reserve { size, .. } => try value(names, owner, size),
93 -
        case il::Instr::Blit { size, .. } => try value(names, owner, size),
94 -
        case il::Instr::Store { src, .. } => try value(names, owner, src),
95 -
        case il::Instr::Copy { val, .. } => try value(names, owner, val),
96 -
        case il::Instr::Zext { val, .. } => try value(names, owner, val),
97 -
        case il::Instr::Sext { val, .. } => try value(names, owner, val),
98 -
        case il::Instr::BinOp { a, b, .. } => {
99 -
            try value(names, owner, a);
100 -
            try value(names, owner, b);
101 -
        },
102 -
        case il::Instr::UnOp { a, .. } => try value(names, owner, a),
90 +
unsafe fn instruction 'tables (names: &mut Names 'tables, owner: *[u8], instr: &il::Instr) throws (binary::Error) {
91 +
    match *instr {
103 92
        case il::Instr::Call { func, args, .. } => {
104 93
            try value(names, owner, func);
105 94
            try values(names, owner, args);
106 95
        },
107 -
        case il::Instr::Ret { val } => {
108 -
            if let item = val {
109 -
                try value(names, owner, item);
110 -
            }
111 -
        },
112 96
        case il::Instr::Jmp { args, .. } => try values(names, owner, args),
113 97
        case il::Instr::Br { a, b, thenArgs, elseArgs, .. } => {
114 98
            try value(names, owner, a);
115 99
            try value(names, owner, b);
116 100
            try values(names, owner, thenArgs);
121 105
            try values(names, owner, defaultArgs);
122 106
            for branch in cases {
123 107
                try values(names, owner, branch.args);
124 108
            }
125 109
        },
110 +
        else => try fixedInstruction(names, owner, instr),
111 +
    }
112 +
}
113 +
114 +
/// Collect symbolic operands stored directly in an instruction.
115 +
fn fixedInstruction 'tables (names: &mut Names 'tables, owner: *[u8], instr: &il::Instr) throws (binary::Error) {
116 +
    match *instr {
117 +
        case il::Instr::Reserve { size, .. } => try value(names, owner, size),
118 +
        case il::Instr::Blit { size, .. } => try value(names, owner, size),
119 +
        case il::Instr::Store { src, .. } => try value(names, owner, src),
120 +
        case il::Instr::Copy { val, .. } => try value(names, owner, val),
121 +
        case il::Instr::Zext { val, .. } => try value(names, owner, val),
122 +
        case il::Instr::Sext { val, .. } => try value(names, owner, val),
123 +
        case il::Instr::BinOp { a, b, .. } => {
124 +
            try value(names, owner, a);
125 +
            try value(names, owner, b);
126 +
        },
127 +
        case il::Instr::UnOp { a, .. } => try value(names, owner, a),
128 +
        case il::Instr::Ret { val } => {
129 +
            if let item = val {
130 +
                try value(names, owner, item);
131 +
            }
132 +
        },
126 133
        case il::Instr::Ecall { num, a0, a1, a2, a3, .. } => {
127 134
            try value(names, owner, num);
128 135
            try value(names, owner, a0);
129 136
            try value(names, owner, a1);
130 137
            try value(names, owner, a2);
137 144
            try value(names, owner, handle); try value(names, owner, offset); try value(names, owner, source);
138 145
        },
139 146
        case il::Instr::Load { .. }, il::Instr::Sload { .. }, il::Instr::Unreachable,
140 147
             il::Instr::Ebreak, il::Instr::MemoryFence => {
141 148
             },
149 +
        case il::Instr::Call { .. }, il::Instr::Jmp { .. }, il::Instr::Br { .. }, il::Instr::Switch { .. } =>
150 +
            panic "fixedInstruction: expected inline operands",
142 151
    }
143 152
}
144 153
145 154
/// Check that a definition belongs to the selected package.
146 155
fn definition 'tables (names: &mut Names 'tables, owner: *[u8], name: *[u8]) throws (binary::Error) {
187 196
    try collectData(names, owner, &program.data[..]);
188 197
    for func in program.fns {
189 198
        try definition(names, owner, func.name);
190 199
        for block in func.blocks {
191 200
            for instr in block.instrs {
192 -
                try instruction(names, owner, instr);
201 +
                try instruction(names, owner, &instr);
193 202
            }
194 203
        }
195 204
    }
196 205
    return binary::Package {
197 206
        symbols: &names.symbols[..names.symbolCount], name: owner,
lib/std/lang/il/binary/decodeTests.rad +70 -0
360 360
        try testing::expectBytesEq(package.dependencies[0], "dep");
361 361
        try testing::expect(package.symbols.len == 4);
362 362
    }
363 363
}
364 364
365 +
/// Fixed instruction operands retain symbol order and bounded table writes.
366 +
@test unsafe fn fixedInstructionCollection() throws (testing::TestError) {
367 +
    let r = il::Reg { n: 0 };
368 +
    let a = il::Val::DataSym("z::data");
369 +
    let b = il::Val::FnAddr("a::fn");
370 +
    let c = il::Val::DataSym("c::data");
371 +
    let d = il::Val::FnAddr("d::fn");
372 +
    let e = il::Val::DataSym("e::data");
373 +
    let single: [*[u8]; 2] = ["z::data", "z"];
374 +
    let pair: [*[u8]; 4] = ["z::data", "z", "a::fn", "a"];
375 +
    for instr in [
376 +
        il::Instr::Reserve { dst: r, size: a, alignment: 8 },
377 +
        il::Instr::Blit { dst: r, src: r, size: a },
378 +
        il::Instr::Store { typ: il::Type::W64, src: a, dst: r, offset: 0 },
379 +
        il::Instr::Copy { dst: r, val: a },
380 +
        il::Instr::Zext { typ: il::Type::W8, dst: r, val: a },
381 +
        il::Instr::Sext { typ: il::Type::W8, dst: r, val: a },
382 +
        il::Instr::UnOp { op: il::UnOp::Neg, typ: il::Type::W64, dst: r, a },
383 +
        il::Instr::Ret { val: a },
384 +
        il::Instr::BinOp { op: il::BinOp::Add, typ: il::Type::W64, dst: r, a, b: a },
385 +
    ] { try checkInstructionNames(instr, &single[..]); }
386 +
    for instr in [
387 +
        il::Instr::BinOp { op: il::BinOp::Add, typ: il::Type::W64, dst: r, a, b },
388 +
        il::Instr::DeviceRead { typ: il::Type::W8, dst: r, handle: a, offset: b },
389 +
    ] { try checkInstructionNames(instr, &pair[..]); }
390 +
    try checkInstructionNames(il::Instr::Ecall { dst: r, num: a, a0: b, a1: c, a2: d, a3: e },
391 +
        &["z::data", "z", "a::fn", "a", "c::data", "c", "d::fn", "d", "e::data", "e"]);
392 +
    try checkInstructionNames(il::Instr::DeviceWrite { typ: il::Type::W8, handle: a, offset: b, value: c },
393 +
        &["z::data", "z", "a::fn", "a", "c::data", "c"]);
394 +
    for instr in [
395 +
        il::Instr::Load { typ: il::Type::W8, dst: r, src: r, offset: 0 },
396 +
        il::Instr::Sload { typ: il::Type::W8, dst: r, src: r, offset: 0 },
397 +
        il::Instr::Ret { val: nil },
398 +
        il::Instr::Copy { dst: r, val: il::Val::Imm(9) },
399 +
        il::Instr::Unreachable, il::Instr::Ebreak, il::Instr::MemoryFence,
400 +
    ] { try checkInstructionNames(instr, &[]); }
401 +
}
402 +
403 +
/// Collect one instruction at exact-fit and every shorter symbol capacity.
404 +
unsafe fn checkInstructionNames(instr: il::Instr, expected: &[*[u8]]) throws (testing::TestError) {
405 +
    let mut body = [instr];
406 +
    let function = il::Fn {
407 +
        name: "p::f", params: &[], returnType: il::Type::W64, isExtern: false, isLeaf: true,
408 +
        blocks: &[il::Block { label: "entry", params: &[], instrs: &mut body[..], locs: &[], preds: &[], loopDepth: 0 }],
409 +
    };
410 +
    let local = il::Program { data: &[], fns: &[&function] };
411 +
    for capacity in 0..expected.len + 3 {
412 +
        let mut symbols: [*[u8]; 16] = [""; 16];
413 +
        let mut dependencies: [*[u8]; 5] = [""; 5];
414 +
        let symbolTable: 'tables = &mut symbols[..capacity], dependencyTable = &mut dependencies[..] in {
415 +
            let mut names = collect::new(symbolTable, dependencyTable);
416 +
            let mut failed = false;
417 +
            try collect::package(&mut names, "p", local, &[], nil) catch err {
418 +
                assert err == binary::Error::Capacity;
419 +
                set failed = true;
420 +
            };
421 +
            assert failed == (capacity < expected.len + 2);
422 +
            assert names.symbolCount <= capacity;
423 +
            if not failed {
424 +
                assert names.symbolCount == expected.len + 2;
425 +
                try testing::expectBytesEq(names.symbols[0], "p");
426 +
                try testing::expectBytesEq(names.symbols[1], "p::f");
427 +
                for name, i in expected { try testing::expectBytesEq(names.symbols[i + 2], name); }
428 +
                assert names.dependencyCount == expected.len / 2;
429 +
            }
430 +
        }
431 +
        for i in capacity..symbols.len { assert symbols[i].len == 0; }
432 +
    }
433 +
}
434 +
365 435
/// Data collection retains first-use order and unique dependencies.
366 436
@test unsafe fn dataCollectionOrder() throws (testing::TestError) {
367 437
    let item = il::Data {
368 438
        name: "p::data", size: 64, alignment: 8, readOnly: true, isZeroInit: false,
369 439
        values: &[