lib/std/lang/il/binary/collect.rad 7.2 KiB raw
1
//! Deterministic symbol and dependency collection for package-local IL.
2
3
use std::mem;
4
use std::lang::il;
5
use std::lang::il::binary;
6
7
/// Caller-owned storage for a package's indexed names.
8
/// Both tables must outlive the collector and all packages that use them.
9
export record Names: Copy {
10
    /// Unique names in first-use order.
11
    symbols: *unsafe mut [*[u8]],
12
    /// Number of initialized symbol entries.
13
    symbolCount: u32,
14
    /// Unique dependency names in first-use order.
15
    dependencies: *unsafe mut [*[u8]],
16
    /// Number of initialized dependency entries.
17
    dependencyCount: u32,
18
}
19
20
/// Start collection with empty caller-provided tables.
21
/// The caller must retain exclusive access to both tables during collection.
22
export unsafe fn new(symbols: &mut [*[u8]], dependencies: &mut [*[u8]]) -> Names {
23
    return Names { symbols: symbols as *unsafe mut [*[u8]], symbolCount: 0, dependencies: dependencies as *unsafe mut [*[u8]], dependencyCount: 0 };
24
}
25
26
/// Add a unique name and check the symbol table's capacity.
27
unsafe fn add(names: &mut Names, name: *[u8]) throws (binary::Error) {
28
    if name.len == 0 {
29
        throw binary::Error::Invalid;
30
    }
31
    for i in 0..names.symbolCount {
32
        if mem::eq(names.symbols[i], name) {
33
            return;
34
        }
35
    }
36
    if names.symbolCount == names.symbols.len {
37
        throw binary::Error::Capacity;
38
    }
39
    set names.symbols[names.symbolCount] = name;
40
    set names.symbolCount += 1;
41
}
42
43
/// Add a qualified reference and its owning package dependency.
44
unsafe fn reference(names: &mut Names, owner: *[u8], name: *[u8]) throws (binary::Error) {
45
    try add(names, name);
46
    for i in 0..name.len {
47
        if name[i] == ':' and i + 1 < name.len and name[i + 1] == ':' {
48
            if i == 0 or i + 2 == name.len {
49
                throw binary::Error::Invalid;
50
            }
51
            let dependency = &name[..i];
52
            if mem::eq(dependency, owner) {
53
                return;
54
            }
55
            try add(names, dependency);
56
            for j in 0..names.dependencyCount {
57
                if mem::eq(names.dependencies[j], dependency) {
58
                    return;
59
                }
60
            }
61
            if names.dependencyCount == names.dependencies.len {
62
                throw binary::Error::Capacity;
63
            }
64
            set names.dependencies[names.dependencyCount] = dependency;
65
            set names.dependencyCount += 1;
66
            return;
67
        }
68
    }
69
    throw binary::Error::Invalid;
70
}
71
72
/// Collect names referenced by one IL value.
73
unsafe fn value(names: &mut Names, owner: *[u8], item: il::Val) throws (binary::Error) {
74
    match item {
75
        case il::Val::DataSym(name) => try reference(names, owner, name),
76
        case il::Val::FnAddr(name) => try reference(names, owner, name),
77
        else => {
78
        },
79
    }
80
}
81
82
/// Collect names from an instruction's variable-length operand sequence.
83
unsafe fn values(names: &mut Names, owner: *[u8], items: &[il::Val]) throws (binary::Error) {
84
    for item in items {
85
        try value(names, owner, item);
86
    }
87
}
88
89
/// Collect all symbolic instruction operands.
90
unsafe fn instruction(names: &mut Names, 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),
103
        case il::Instr::Call { func, args, .. } => {
104
            try value(names, owner, func);
105
            try values(names, owner, args);
106
        },
107
        case il::Instr::Ret { val } => {
108
            if let item = val {
109
                try value(names, owner, item);
110
            }
111
        },
112
        case il::Instr::Jmp { args, .. } => try values(names, owner, args),
113
        case il::Instr::Br { a, b, thenArgs, elseArgs, .. } => {
114
            try value(names, owner, a);
115
            try value(names, owner, b);
116
            try values(names, owner, thenArgs);
117
            try values(names, owner, elseArgs);
118
        },
119
        case il::Instr::Switch { val, defaultArgs, cases, .. } => {
120
            try value(names, owner, val);
121
            try values(names, owner, defaultArgs);
122
            for branch in cases {
123
                try values(names, owner, branch.args);
124
            }
125
        },
126
        case il::Instr::Ecall { num, a0, a1, a2, a3, .. } => {
127
            try value(names, owner, num);
128
            try value(names, owner, a0);
129
            try value(names, owner, a1);
130
            try value(names, owner, a2);
131
            try value(names, owner, a3);
132
        },
133
        case il::Instr::DeviceRead { handle, offset, .. } => {
134
            try value(names, owner, handle); try value(names, owner, offset);
135
        },
136
        case il::Instr::DeviceWrite { handle, offset, value: source, .. } => {
137
            try value(names, owner, handle); try value(names, owner, offset); try value(names, owner, source);
138
        },
139
        case il::Instr::Load { .. }, il::Instr::Sload { .. }, il::Instr::Unreachable,
140
             il::Instr::Ebreak, il::Instr::MemoryFence => {
141
             },
142
    }
143
}
144
145
/// Check that a definition belongs to the selected package.
146
unsafe fn definition(names: &mut Names, owner: *[u8], name: *[u8]) throws (binary::Error) {
147
    let rest = mem::stripPrefix(owner, name) else {
148
        throw binary::Error::Invalid;
149
    };
150
    if rest.len < 3 or rest[0] <> ':' or rest[1] <> ':' {
151
        throw binary::Error::Invalid;
152
    }
153
    try add(names, name);
154
}
155
156
/// Build package tables from local definitions and their qualified references.
157
/// Returned tables borrow `names`. Reset the collector before building another package.
158
export unsafe fn package(
159
    names: &mut Names,
160
    owner: *[u8],
161
    program: il::Program,
162
    exports: &[binary::Export],
163
    entry: ?*[u8]
164
) -> binary::Package throws (binary::Error) {
165
    try add(names, owner);
166
    for item in exports {
167
        try definition(names, owner, item.name);
168
    }
169
    if let name = entry {
170
        try definition(names, owner, name);
171
    }
172
    for item in program.data {
173
        try definition(names, owner, item.name);
174
        for data in item.values {
175
            match data.item {
176
                case il::DataItem::Sym(name) => try reference(names, owner, name),
177
                case il::DataItem::Fn(name) => try reference(names, owner, name),
178
                else => {
179
                },
180
            }
181
        }
182
    }
183
    for func in program.fns {
184
        try definition(names, owner, func.name);
185
        for block in func.blocks {
186
            for instr in block.instrs {
187
                try instruction(names, owner, instr);
188
            }
189
        }
190
    }
191
    return binary::Package {
192
        symbols: &names.symbols[..names.symbolCount], name: owner,
193
        dependencies: &names.dependencies[..names.dependencyCount], exports: exports as *unsafe [binary::Export], entry, program,
194
    };
195
}