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
/// Collect names from an instruction's variable-length operand sequence.
82
unsafe fn values(names: &mut Names, owner: *[u8], items: &[il::Val]) throws (binary::Error) {
83
    for item in items {
84
        try value(names, owner, item);
85
    }
86
}
87
88
/// Collect all symbolic instruction operands.
89
unsafe fn instruction(names: &mut Names, owner: *[u8], instr: il::Instr) throws (binary::Error) {
90
    match instr {
91
        case il::Instr::Reserve { size, .. } => try value(names, owner, size),
92
        case il::Instr::Blit { size, .. } => try value(names, owner, size),
93
        case il::Instr::Store { src, .. } => try value(names, owner, src),
94
        case il::Instr::Copy { val, .. } => try value(names, owner, val),
95
        case il::Instr::Zext { val, .. } => try value(names, owner, val),
96
        case il::Instr::Sext { val, .. } => try value(names, owner, val),
97
        case il::Instr::BinOp { a, b, .. } => {
98
            try value(names, owner, a);
99
            try value(names, owner, b);
100
        },
101
        case il::Instr::UnOp { a, .. } => try value(names, owner, a),
102
        case il::Instr::Call { func, args, .. } => {
103
            try value(names, owner, func);
104
            try values(names, owner, args);
105
        },
106
        case il::Instr::Ret { val } => {
107
            if let item = val {
108
                try value(names, owner, item);
109
            }
110
        },
111
        case il::Instr::Jmp { args, .. } => try values(names, owner, args),
112
        case il::Instr::Br { a, b, thenArgs, elseArgs, .. } => {
113
            try value(names, owner, a);
114
            try value(names, owner, b);
115
            try values(names, owner, thenArgs);
116
            try values(names, owner, elseArgs);
117
        },
118
        case il::Instr::Switch { val, defaultArgs, cases, .. } => {
119
            try value(names, owner, val);
120
            try values(names, owner, defaultArgs);
121
            for branch in cases {
122
                try values(names, owner, branch.args);
123
            }
124
        },
125
        case il::Instr::Ecall { num, a0, a1, a2, a3, .. } => {
126
            try value(names, owner, num);
127
            try value(names, owner, a0);
128
            try value(names, owner, a1);
129
            try value(names, owner, a2);
130
            try value(names, owner, a3);
131
        },
132
        case il::Instr::DeviceRead { handle, offset, .. } => {
133
            try value(names, owner, handle); try value(names, owner, offset);
134
        },
135
        case il::Instr::DeviceWrite { handle, offset, value: source, .. } => {
136
            try value(names, owner, handle); try value(names, owner, offset); try value(names, owner, source);
137
        },
138
        case il::Instr::Load { .. }, il::Instr::Sload { .. }, il::Instr::Unreachable,
139
             il::Instr::Ebreak, il::Instr::MemoryFence => {},
140
    }
141
}
142
143
/// Check that a definition belongs to the selected package.
144
unsafe fn definition(names: &mut Names, owner: *[u8], name: *[u8]) throws (binary::Error) {
145
    let rest = mem::stripPrefix(owner, name) else {
146
        throw binary::Error::Invalid;
147
    };
148
    if rest.len < 3 or rest[0] <> ':' or rest[1] <> ':' {
149
        throw binary::Error::Invalid;
150
    }
151
    try add(names, name);
152
}
153
154
/// Build package tables from local definitions and their qualified references.
155
/// Returned tables borrow `names`. Reset the collector before building another package.
156
export unsafe fn package(
157
    names: &mut Names,
158
    owner: *[u8],
159
    program: il::Program,
160
    exports: &[binary::Export],
161
    entry: ?*[u8]
162
) -> binary::Package throws (binary::Error) {
163
    try add(names, owner);
164
    for item in exports {
165
        try definition(names, owner, item.name);
166
    }
167
    if let name = entry {
168
        try definition(names, owner, name);
169
    }
170
    for item in program.data {
171
        try definition(names, owner, item.name);
172
        for data in item.values {
173
            match data.item {
174
                case il::DataItem::Sym(name) => try reference(names, owner, name),
175
                case il::DataItem::Fn(name) => try reference(names, owner, name),
176
                else => {},
177
            }
178
        }
179
    }
180
    for func in program.fns {
181
        try definition(names, owner, func.name);
182
        for block in func.blocks {
183
            for instr in block.instrs {
184
                try instruction(names, owner, instr);
185
            }
186
        }
187
    }
188
    return binary::Package {
189
        symbols: &names.symbols[..names.symbolCount], name: owner,
190
        dependencies: &names.dependencies[..names.dependencyCount], exports: exports as *unsafe [binary::Export], entry, program,
191
    };
192
}