slow.monster

STUDIO HAZE · topaz

Topaz source

Open by house policy. Topaz is our own language.

Topaz 5.20 · 56 files

compiler/crates/topaz_self_frontend/topaz/emitter.tpz

Topaz · 8384 B · Original file

1import emitter.module { emitProgram }
2import stage1_types {
3 LoweringOutput,
4 STAGE1_IR_SCHEMA,
5 Stage1Module,
6 Stage1Operation,
7 Stage1Provenance,
8 Stage1Response,
9 decodeStage1Response,
12record EmitPayload derives JSON {
13 schema: string,
14 irSchema: string,
15 runtimeTemplate: string,
16 sourceSetId: string,
17 loweredModules: Array<Stage1Module>,
18 loweredOperations: Array<Stage1Operation>,
21const COMPILER_RUNTIME_REGISTRY_SCHEMA =
22 "topaz.compiler.fixed-point-runtime-templates/v1"
23const COMPILER_RUNTIME_TEMPLATE = "compiler-ir-table/v2"
24const COMPILER_PAYLOAD_SCHEMA =
25 "topaz.compiler.fixed-point-ir-payload/v1"
26// Closed folding bounds operands and traversal so preview generation stays finite and portable.
27const CLOSED_INT_OPERAND_LIMIT = 1000000000
28const CLOSED_INT_PRODUCT_OPERAND_LIMIT = 1000000
29const CLOSED_INT_DEPTH_LIMIT = 256
30const ENTRY_PREVIEW_OPERATION_LIMIT = 4096
32function rustRaw(value: string) -> string {
33 let mut hashes = ""
34 while value.contains("\"{hashes}") {
35 hashes = "{hashes}#"
36 }
37 "r{hashes}\"{value}\"{hashes}"
40function rustStringConstant(name: string, value: string) -> string {
41 "pub const {name}: &str = {rustRaw(value)};\n"
44record EmitterOperationCatalog {
45 values: Array<Stage1Operation>,
46 positions: Map<string, int>,
47 runtimeLeaves: Array<string>,
50function emitterOperationCatalog(
51 operations: Array<Stage1Operation>,
52) -> EmitterOperationCatalog {
53 let mut positions: Map<string, int> = Map.new()
54 let mut leaves: Set<string> = set {}
55 let mut index = 0
56 for operation in operations {
57 if !positions.containsKey(operation.id) {
58 positions.insert(operation.id, index)
59 }
60 if operation.runtimeLeaf != "" {
61 leaves.add(operation.runtimeLeaf)
62 }
63 index += 1
64 }
65 let mut orderedLeaves = leaves.toArray()
66 orderedLeaves.sort()
67 EmitterOperationCatalog {
68 values: operations,
69 positions: positions,
70 runtimeLeaves: orderedLeaves,
71 }
74function operation(
75 context: EmitterOperationCatalog,
76 wanted: string,
77) -> Option<Stage1Operation> {
78 match context.positions.get(wanted) {
79 case Some(index) => Some(context.values[index])
80 case None => None
81 }
84function evaluateOperand(
85 context: EmitterOperationCatalog,
86 found: Stage1Operation,
87 index: int,
88 depth: int,
89) -> Option<int> {
90 match found.operands.get(index) {
91 case Some(id) => evaluateClosedInt(context, id, depth + 1)
92 case None => None
93 }
96function evaluateUnaryClosedInt(
97 context: EmitterOperationCatalog,
98 found: Stage1Operation,
99 depth: int,
100) -> Option<int> {
101 if found.operands.length != 1 {
102 return None
103 }
104 match evaluateOperand(context, found, 0, depth) {
105 case Some(number) => if found.detail == "neg" &&
106 number >= -CLOSED_INT_OPERAND_LIMIT && number <= CLOSED_INT_OPERAND_LIMIT {
107 Some(-number)
108 } else if found.detail == "pos" {
109 Some(number)
110 } else {
111 None
112 }
113 case None => None
114 }
117function evaluateBinaryClosedInt(
118 context: EmitterOperationCatalog,
119 found: Stage1Operation,
120 depth: int,
121) -> Option<int> {
122 if found.operands.length != 2 {
123 return None
124 }
125 match [
126 evaluateOperand(context, found, 0, depth),
127 evaluateOperand(context, found, 1, depth),
128 ] {
129 case [Some(lhs), Some(rhs)] => match found.detail {
130 case "add" => if lhs >= -CLOSED_INT_OPERAND_LIMIT && lhs <= CLOSED_INT_OPERAND_LIMIT &&
131 rhs >= -CLOSED_INT_OPERAND_LIMIT && rhs <= CLOSED_INT_OPERAND_LIMIT {
132 Some(lhs + rhs)
133 } else {
134 None
135 }
136 case "sub" => if lhs >= -CLOSED_INT_OPERAND_LIMIT && lhs <= CLOSED_INT_OPERAND_LIMIT &&
137 rhs >= -CLOSED_INT_OPERAND_LIMIT && rhs <= CLOSED_INT_OPERAND_LIMIT {
138 Some(lhs - rhs)
139 } else {
140 None
141 }
142 case "mul" => if lhs >= -CLOSED_INT_PRODUCT_OPERAND_LIMIT &&
143 lhs <= CLOSED_INT_PRODUCT_OPERAND_LIMIT &&
144 rhs >= -CLOSED_INT_PRODUCT_OPERAND_LIMIT &&
145 rhs <= CLOSED_INT_PRODUCT_OPERAND_LIMIT {
146 Some(lhs * rhs)
147 } else {
148 None
149 }
150 case _ => None
151 }
152 case _ => None
153 }
156function evaluateSequenceClosedInt(
157 context: EmitterOperationCatalog,
158 found: Stage1Operation,
159 depth: int,
160) -> Option<int> {
161 let mut result: Option<int> = None
162 for id in found.operands {
163 match evaluateClosedInt(context, id, depth + 1) {
164 case Some(value) => { result = Some(value) }
165 case None => ()
166 }
167 }
168 result
171function evaluateClosedInt(
172 context: EmitterOperationCatalog,
173 wanted: string,
174 depth: int,
175) -> Option<int> {
176 if depth > CLOSED_INT_DEPTH_LIMIT {
177 return None
178 }
179 match operation(context, wanted) {
180 case None => None
181 case Some(found) => {
182 if found.kind == "expression/integer" {
183 return toInt(found.detail)
184 }
185 if found.kind == "expression/unary" {
186 return evaluateUnaryClosedInt(context, found, depth)
187 }
188 if found.kind == "expression/binary" {
189 return evaluateBinaryClosedInt(context, found, depth)
190 }
191 if found.kind == "let" ||
192 found.kind == "constant" ||
193 found.kind == "expression/parenthesized" ||
194 found.kind == "expression/block" ||
195 found.kind == "module" {
196 return evaluateSequenceClosedInt(context, found, depth)
197 }
198 None
199 }
200 }
203function entryPreview(
204 modules: Array<Stage1Module>,
205 catalog: EmitterOperationCatalog,
206) -> Option<int> {
207 if catalog.values.length > ENTRY_PREVIEW_OPERATION_LIMIT {
208 return None
209 }
210 for module in modules {
211 if module.entry {
212 for id in module.operationIds {
213 match operation(catalog, id) {
214 case Some(found) => if found.kind == "module" {
215 return evaluateClosedInt(catalog, id, 0)
216 }
217 case None => ()
218 }
219 }
220 }
221 }
222 None
225function renderRuntimeLeaves(leaves: Array<string>) -> string {
226 let mut rendered: Array<string> = []
227 for leaf in leaves {
228 rendered.push("{rustRaw(leaf)},")
229 }
230 "&[{rendered.join("")}]"
233function render(response: Stage1Response) -> Result<string, string> {
234 let ir = JSON.stringify(EmitPayload {
235 schema: COMPILER_PAYLOAD_SCHEMA,
236 irSchema: STAGE1_IR_SCHEMA,
237 runtimeTemplate: COMPILER_RUNTIME_TEMPLATE,
238 sourceSetId: response.provenance.sourceSetId,
239 loweredModules: response.loweredModules,
240 loweredOperations: response.loweredOperations,
241 })?
242 let catalog = emitterOperationCatalog(response.loweredOperations)
243 let preview = match entryPreview(response.loweredModules, catalog) {
244 case Some(value) => "Some({value}i64)"
245 case None => "None"
246 }
247 let leaves = renderRuntimeLeaves(catalog.runtimeLeaves)
248 Ok(
249 "// @generated by the Topaz compiler kernel; do not edit.\n" +
250 rustStringConstant(
251 "TOPAZ_COMPILER_RUNTIME_REGISTRY_SCHEMA",
252 COMPILER_RUNTIME_REGISTRY_SCHEMA,
253 ) +
254 rustStringConstant(
255 "TOPAZ_COMPILER_RUNTIME_TEMPLATE",
256 COMPILER_RUNTIME_TEMPLATE,
257 ) +
258 rustStringConstant("TOPAZ_COMPILER_IR_SCHEMA", STAGE1_IR_SCHEMA) +
259 rustStringConstant(
260 "TOPAZ_COMPILER_IR_PAYLOAD_SCHEMA",
261 COMPILER_PAYLOAD_SCHEMA,
262 ) +
263 rustStringConstant(
264 "TOPAZ_COMPILER_SOURCE_SET",
265 response.provenance.sourceSetId,
266 ) +
267 rustStringConstant("TOPAZ_COMPILER_IR_JSON", ir) +
268 "pub const TOPAZ_COMPILER_RUNTIME_LEAVES: &[&str] = {leaves};\n" +
269 "pub fn compiler_preview_i64() -> Option<i64> \{ {preview} \}\n",
270 )
273export function emitStep(
274 loweredBytes: Bytes,
275 maxGeneratedRustBytes: int,
276) -> Result<Bytes, string> {
277 let lowered = decodeStage1Response(loweredBytes.decodeUtf8()?)?
278 if lowered.status != "completed" {
279 return Ok(loweredBytes)
280 }
281 if lowered.generatedRust != "" {
282 return Err("Stage 1 emitter received pre-existing generated Rust")
283 }
284 let generated = render(lowered)?
285 if generated.byteLength() > maxGeneratedRustBytes {
286 return Err(
287 "Stage 1 generated Rust exceeds {maxGeneratedRustBytes} bytes",
288 )
289 }
290 let response = Stage1Response {
291 ...lowered,
292 generatedRust: generated,
293 }
294 Ok(Bytes.encodeUtf8(JSON.stringify(response)?))
297// Real Rust source has a separate value boundary from emitStep's table carrier.
298export function emitRust(output: LoweringOutput) -> Result<string, string> {
299 if output.unsupported.length > 0 {
300 return Err(output.unsupported.join("\n"))
301 }
302 emitProgram(output)

SHA256 505e5fa4fd43cb5b5cb97f57753a9d5c1c93a7e821c59a014cc39bbc12631365