compiler: Construct published instructions from checked operands

c8d0ba8937dc35adaf707bc491e3e055c1bb4c560dfea3a92a719579b7eb94b3
Alexis Sellier committed ago 1 parent 25d7de6e
lib/std/arch/rv64/bounds.rad +51 -0
80 80
}
81 81
82 82
/// Reusable emitter allocation storage.
83 83
static MEMORY: [u8; 16777216] = [0; 16777216];
84 84
85 +
/// Checked IL construction reaches instruction selection without a raw IL import.
86 +
@test unsafe fn constructedInstructions() throws (testing::TestError) {
87 +
    let mut arena = alloc::new(&mut SCRATCH[..]);
88 +
    let mut code = alloc::new(&mut MEMORY[..]);
89 +
    let mut e = try! emit::emitter(&mut code, false);
90 +
    use arena as storage in {
91 +
        try! selectConstructed(&storage, &mut e);
92 +
    }
93 +
}
94 +
95 +
/// Construct, allocate, and select a function that returns a constant.
96 +
fn selectConstructed 'view (storage: &Session 'view, e: &mut emit::Emitter)
97 +
    throws (alloc::AllocError)
98 +
{
99 +
    let copy = try storage.new(il::published::Code 'view::Fixed(
100 +
        il::Instr::Copy { dst: il::Reg { n: 0 }, val: il::Val::Imm(7) }));
101 +
    let ret = try storage.new(il::published::Code 'view::Fixed(
102 +
        il::Instr::Ret { val: il::Val::Reg(il::Reg { n: 0 }) }));
103 +
    let first = try il::published::buildInstruction(copy, nil, storage);
104 +
    let instructions = try storage.fill(first, 2);
105 +
    set instructions[1] = try il::published::buildInstruction(ret, nil, storage);
106 +
    let noParams: [il::Param; 0] = [];
107 +
    let params: &'view [il::Param] = try storage.copy(&noParams[..]);
108 +
    let noLocations: [il::SrcLoc; 0] = [];
109 +
    let locations = try storage.copy(&noLocations[..]);
110 +
    let blocks = try storage.fill(il::published::Block 'view { identity: nil, params,
111 +
        instructions: &instructions[..], locations, loopDepth: 0 }, 1);
112 +
    let function = il::published::Function 'view { name: "p::constructed", isLeaf: true,
113 +
        params, blocks: &blocks[..] };
114 +
    let config = super::targetConfig();
115 +
    let allocation = try regalloc::allocate(&function, &config, storage);
116 +
    isel::selectFn(e, &allocation, &function);
117 +
    assert e.error == nil;
118 +
    let assigned = allocation.assignments[0] else panic;
119 +
    let expected = encode::addi(super::SCRATCH1, super::ZERO, 7);
120 +
    let move = encode::mv(assigned, super::SCRATCH1);
121 +
    let output: 'output = &*e in {
122 +
        let mut found = false;
123 +
        let mut moved = *assigned == *super::SCRATCH1;
124 +
        for word in emit::getCode(output) {
125 +
            if word == expected {
126 +
                set found = true;
127 +
            }
128 +
            if word == move {
129 +
                set moved = true;
130 +
            }
131 +
        }
132 +
        assert found and moved;
133 +
    }
134 +
}
135 +
85 136
/// Selection owns call arguments and instructions used to size the stack frame.
86 137
@test unsafe fn publishedCalls() throws (testing::TestError) {
87 138
    for indirect in [false, true] {
88 139
        let params = [il::Param { value: il::Reg { n: 0 }, type: il::Type::W64 }];
89 140
        let mut args = [il::Val::Imm(7), il::Val::Reg(il::Reg { n: 1 })];
lib/std/lang/il/published.rad +102 -30
21 21
    blocks: &'view [Block 'view],
22 22
}
23 23
24 24
/// Read-only block tables and their original identity.
25 25
export record Block: 'view + Copy {
26 -
    /// Original block pointer, used only for identity comparisons.
27 -
    identity: *unsafe il::Block,
26 +
    /// Original block pointer for imported code, used only for identity comparisons.
27 +
    identity: ?*unsafe il::Block,
28 28
    /// Block parameter definitions.
29 29
    params: &'view [il::Param],
30 30
    /// Published instruction descriptors.
31 31
    instructions: &'view [Instruction 'view],
32 32
    /// Source locations in instruction order, or an empty slice without debug data.
35 35
    loopDepth: u32,
36 36
}
37 37
38 38
/// An instruction and its immutable register-allocation inputs.
39 39
export opaque record Instruction: 'view + Copy {
40 -
    /// Original instruction pointer, used only for identity comparisons.
41 -
    identity: *unsafe il::Instr,
40 +
    /// Original instruction pointer for imported code, used only for identity comparisons.
41 +
    identity: ?*unsafe il::Instr,
42 42
    /// Instruction operands with owned variable-length tables.
43 43
    code: &'view Code 'view,
44 44
    /// Destination register, when the instruction defines a value.
45 45
    destination: ?il::Reg,
46 46
    /// Whether the instruction has call-clobber semantics.
156 156
    }
157 157
    return Block 'view { identity: block as *unsafe il::Block, params,
158 158
        instructions: &views[..], locations, loopDepth: block.loopDepth };
159 159
}
160 160
161 -
/// Collect source registers and successor positions for one instruction.
161 +
/// Import an instruction and construct its checked analysis tables.
162 162
unsafe fn publishInstruction 'view (source: &il::Instr, storage: &Session 'view)
163 163
    -> Instruction 'view throws (alloc::AllocError)
164 164
{
165 +
    let code = try storage.new(try publishCode(source, storage));
166 +
    return try buildInstruction(code, source as *unsafe il::Instr, storage);
167 +
}
168 +
169 +
/// Construct instruction metadata from immutable, region-owned operands.
170 +
/// The source identity is retained as a value and is never dereferenced.
171 +
export fn buildInstruction 'view (
172 +
    code: &'view Code 'view,
173 +
    identity: ?*unsafe il::Instr,
174 +
    storage: &Session 'view
175 +
) -> Instruction 'view throws (alloc::AllocError) {
165 176
    let noTargets: [u32; 0] = [];
166 177
    let mut targets: &'view [u32] = try storage.copy(&noTargets[..]);
167 -
    match source {
168 -
        case il::Instr::Jmp { target, .. } => {
178 +
    let mut count: u64 = 0;
179 +
    let mut destination: ?il::Reg = nil;
180 +
    let mut isCall = false;
181 +
    match code {
182 +
        case Code::Fixed(fixed) => {
183 +
            let cursor = il::registers(fixed);
184 +
            assert not cursor.arguments, "buildInstruction: expected inline operands";
185 +
            set count = cursor.count as u64;
186 +
            set destination = il::instrDst(*fixed);
187 +
            set isCall = il::isCall(*fixed);
188 +
        },
189 +
        case Code::Call { dst, func, args, .. } => {
190 +
            let prefix = [*func];
191 +
            set count = registerCount(&prefix[..]) + registerCount(*args);
192 +
            set destination = *dst;
193 +
            set isCall = true;
194 +
        },
195 +
        case Code::Jmp { target, args } => {
196 +
            set count = registerCount(*args);
169 197
            set targets = try storage.fill(*target, 1);
170 198
        },
171 -
        case il::Instr::Br { thenTarget, elseTarget, .. } => {
199 +
        case Code::Br { a, b, thenTarget, thenArgs, elseTarget, elseArgs, .. } => {
200 +
            let prefix = [*a, *b];
201 +
            set count = registerCount(&prefix[..]) + registerCount(*thenArgs) + registerCount(*elseArgs);
172 202
            let edges = try storage.fill(*thenTarget, 2);
173 203
            set edges[1] = *elseTarget;
174 204
            set targets = &edges[..];
175 205
        },
176 -
        case il::Instr::Switch { defaultTarget, cases, .. } => {
177 -
            let cases: &[il::SwitchCase] = *cases;
206 +
        case Code::Switch { val, defaultTarget, defaultArgs, cases } => {
207 +
            let prefix = [*val];
208 +
            set count = registerCount(&prefix[..]) + registerCount(*defaultArgs);
178 209
            if cases.len == MAX_ELEMENTS {
179 210
                throw alloc::AllocError::OutOfMemory;
180 211
            }
181 212
            let edges = try storage.fill(*defaultTarget, cases.len + 1);
182 -
            for item, i in cases {
213 +
            for item, i in *cases {
183 214
                set edges[i + 1] = item.target;
215 +
                set count += registerCount(item.args);
184 216
            }
185 217
            set targets = &edges[..];
186 218
        },
187 -
        else => {},
188 219
    }
189 -
    let mut cursor = il::registers(source);
190 -
    let mut count: u32 = 0;
191 -
    while let reg = il::nextReg(&mut cursor, source) {
192 -
        if count == MAX_ELEMENTS {
193 -
            throw alloc::AllocError::OutOfMemory;
194 -
        }
195 -
        set count += 1;
220 +
    if count > MAX_ELEMENTS as u64 {
221 +
        throw alloc::AllocError::OutOfMemory;
196 222
    }
197 -
    let operands = try storage.fill(il::Reg { n: 0 }, count);
198 -
    set cursor = il::registers(source);
199 -
    for i in 0..count {
200 -
        let reg = il::nextReg(&mut cursor, source)
201 -
            else panic "publishInstruction: operand count changed";
202 -
        set operands[i] = reg;
223 +
    let operands = try storage.fill(il::Reg { n: 0 }, count as u32);
224 +
    let mut written: u32 = 0;
225 +
    match code {
226 +
        case Code::Fixed(fixed) => {
227 +
            let cursor = il::registers(fixed);
228 +
            for i in 0..cursor.count {
229 +
                set operands[i] = cursor.fixed[i];
230 +
            }
231 +
            set written = cursor.count;
232 +
        },
233 +
        case Code::Call { func, args, .. } => {
234 +
            let prefix = [*func];
235 +
            set written = copyRegisters(&prefix[..], operands, 0);
236 +
            set written = copyRegisters(*args, operands, written);
237 +
        },
238 +
        case Code::Jmp { args, .. } => set written = copyRegisters(*args, operands, 0),
239 +
        case Code::Br { a, b, thenArgs, elseArgs, .. } => {
240 +
            let prefix = [*a, *b];
241 +
            set written = copyRegisters(&prefix[..], operands, 0);
242 +
            set written = copyRegisters(*thenArgs, operands, written);
243 +
            set written = copyRegisters(*elseArgs, operands, written);
244 +
        },
245 +
        case Code::Switch { val, defaultArgs, cases, .. } => {
246 +
            let prefix = [*val];
247 +
            set written = copyRegisters(&prefix[..], operands, 0);
248 +
            set written = copyRegisters(*defaultArgs, operands, written);
249 +
            for item in *cases {
250 +
                set written = copyRegisters(item.args, operands, written);
251 +
            }
252 +
        },
203 253
    }
204 -
    let code = try storage.new(try publishCode(source, storage));
205 -
    return Instruction 'view { identity: source as *unsafe il::Instr, code,
206 -
        destination: il::instrDst(*source), isCall: il::isCall(*source),
254 +
    assert written == operands.len, "buildInstruction: operand count changed";
255 +
    return Instruction 'view { identity, code, destination, isCall,
207 256
        operands: &operands[..], targets };
208 257
}
209 258
259 +
/// Count source registers in one operand group, including repeated uses.
260 +
fn registerCount(values: &[il::Val]) -> u64 {
261 +
    let mut count: u64 = 0;
262 +
    for value in values {
263 +
        if let case il::Val::Reg(_) = value {
264 +
            set count += 1;
265 +
        }
266 +
    }
267 +
    return count;
268 +
}
269 +
270 +
/// Write source registers at an offset and return the next free position.
271 +
fn copyRegisters(values: &[il::Val], output: &mut [il::Reg], offset: u32) -> u32 {
272 +
    let mut next = offset;
273 +
    for value in values {
274 +
        if let case il::Val::Reg(reg) = value {
275 +
            set output[next] = reg;
276 +
            set next += 1;
277 +
        }
278 +
    }
279 +
    return next;
280 +
}
281 +
210 282
/// Copy the variable-length operands of an instruction into the session.
211 283
unsafe fn publishCode 'view (source: &il::Instr, storage: &Session 'view)
212 284
    -> Code 'view throws (alloc::AllocError)
213 285
{
214 286
    match *source {
247 319
/// Return the instruction data with session-owned operand tables.
248 320
export fn code 'view (instruction: &Instruction 'view) -> &'view Code 'view {
249 321
    return instruction.code;
250 322
}
251 323
252 -
/// Return the original instruction pointer for identity comparisons.
253 -
export fn identity 'view (instruction: &Instruction 'view) -> *unsafe il::Instr {
324 +
/// Return the original instruction pointer when the instruction was imported.
325 +
export fn identity 'view (instruction: &Instruction 'view) -> ?*unsafe il::Instr {
254 326
    return instruction.identity;
255 327
}
256 328
257 329
/// Return an instruction's destination register.
258 330
export fn destination 'view (instruction: &Instruction 'view) -> ?il::Reg {
lib/std/lang/il/publishedTests.rad +32 -2
3 3
use std::testing;
4 4
use std::lang::alloc;
5 5
use std::lang::il;
6 6
use std::lang::il::published;
7 7
8 +
/// Checked instruction construction preserves argument order and repeated uses.
9 +
@test fn testCheckedInstruction() throws (testing::TestError) {
10 +
    static DATA: [u8; 4096] = [0; 4096];
11 +
    let mut arena = alloc::new(&mut DATA[..]);
12 +
    use arena as storage in {
13 +
        let instruction = try! buildCall(&storage);
14 +
        assert published::identity(&instruction) == nil;
15 +
        assert published::isCall(&instruction);
16 +
        let destination = published::destination(&instruction) else panic;
17 +
        assert destination.n == 9;
18 +
        let regs = published::registers(&instruction);
19 +
        assert regs.len == 3 and regs[0].n == 2 and regs[1].n == 7 and regs[2].n == 7;
20 +
        assert published::successors(&instruction).len == 0;
21 +
    }
22 +
}
23 +
24 +
/// Construct a call whose argument source ends with this stack frame.
25 +
fn buildCall 'view (storage: &Session 'view)
26 +
    -> published::Instruction 'view throws (alloc::AllocError)
27 +
{
28 +
    let values = [il::Val::Imm(4), il::Val::Reg(il::Reg { n: 7 }),
29 +
        il::Val::Reg(il::Reg { n: 7 })];
30 +
    let args = try storage.copy(&values[..]);
31 +
    let code = try storage.new(published::Code 'view::Call { retTy: il::Type::W64,
32 +
        dst: il::Reg { n: 9 }, func: il::Val::Reg(il::Reg { n: 2 }), args });
33 +
    return try published::buildInstruction(code, nil, storage);
34 +
}
35 +
8 36
/// Publish operands from source tables that end with this stack frame.
9 37
unsafe fn transientInput 'view (storage: &Session 'view) -> published::Function 'view
10 38
    throws (alloc::AllocError)
11 39
{
12 40
    let params = [il::Param { value: il::Reg { n: 5 }, type: il::Type::W64 }];
79 107
    let mut arena = alloc::new(&mut DATA[..]);
80 108
    let input: 'input = &function in {
81 109
        use arena as storage in {
82 110
            let view = try! published::publish(input, &storage);
83 111
            assert view.blocks.len == 1;
84 -
            assert view.blocks[0].identity == &blocks[0];
112 +
            let blockIdentity = view.blocks[0].identity else panic;
113 +
            assert blockIdentity == &blocks[0];
85 114
            let instruction = &view.blocks[0].instructions[0];
86 -
            assert published::identity(instruction) == &instructions[0];
115 +
            let identity = published::identity(instruction) else panic;
116 +
            assert identity == &instructions[0];
87 117
            set arguments[1] = il::Val::Reg(il::Reg { n: 99 });
88 118
            set cases[0].target = 42;
89 119
            set instructions[0] = il::Instr::Ret { val: nil };
90 120
            let registers = published::registers(instruction);
91 121
            assert registers.len == 3;