Skip to main content

candela/
build.rs

1//! The `candela build` path: compile a `.cdl` source to a `.cdlb` bytecode
2//! artifact.
3//!
4//! The artifact FORMAT and its load/run half live in the VM-only `candela-vm`
5//! crate ([`candela_vm::artifact`]); this module is the compiler-side half that
6//! turns a fresh [`compile`] result into a [`ProgramImage`] and serializes it.
7
8use crate::compiler::CompileOutput;
9use crate::compiler::compile;
10use candela_vm::artifact::DynLibFnImage;
11use candela_vm::artifact::EnumImage;
12use candela_vm::artifact::EnumVariantImage;
13use candela_vm::artifact::HostFnImage;
14use candela_vm::artifact::InstrSrcImage;
15use candela_vm::artifact::ProgramImage;
16use candela_vm::artifact::SourceImage;
17use candela_vm::artifact::StructImage;
18use candela_vm::artifact::serialize_image;
19
20/// Compiles a `.cdl` source string to a `.cdlb` bytecode artifact.
21///
22/// The artifact captures the whole program: every imported workspace `.cdl`
23/// module is linked into the single serialized image, so the resulting `.cdlb`
24/// runs under `candela-vm` with no source tree present. Dynamic-library `import`s
25/// and `host` blocks are captured as recipes (logical name + symbol +
26/// signature) that the VM re-binds by name at load time, never as embedded
27/// binary bytes.
28///
29/// # Errors
30///
31/// Returns an error string if serialization fails.
32pub fn build_bytecode(source: String, filename: &str) -> Result<Vec<u8>, String> {
33    let out = compile(source, filename, false);
34    let image = image_from_output(out);
35    serialize_image(&image)
36}
37
38fn image_from_output(out: CompileOutput) -> ProgramImage {
39    // Dynamic-library bindings become referenced-by-name recipes: the logical
40    // library name, the symbol, and the marshalling signature, never the
41    // shared object's bytes. The VM re-opens the library and rebuilds the libffi
42    // CIF from these at load time.
43    let dyn_lib_fns = out
44        .dyn_lib_fns
45        .iter()
46        .map(|d| DynLibFnImage {
47            library: d.library.to_string(),
48            symbol: d.symbol.to_string(),
49            types: d.types.to_vec(),
50        })
51        .collect();
52    // `host` functions are captured as name + signature so an embedding runtime
53    // can re-bind them; standalone `candela-vm` reports a clear error naming the
54    // function it cannot provide.
55    let host_fns = out
56        .host_fns
57        .iter()
58        .map(|h| HostFnImage {
59            namespace: h.namespace.to_string(),
60            name: h.name.to_string(),
61            types: h.types.to_vec(),
62            variadic: h.variadic,
63        })
64        .collect();
65
66    ProgramImage {
67        instructions: out.instructions,
68        registers: out.registers.iter().map(|d| d.0).collect(),
69        objs: out
70            .pools
71            .objs
72            .0
73            .iter()
74            .map(|v| v.iter().map(|d| d.0).collect())
75            .collect(),
76        maps: out
77            .pools
78            .maps
79            .0
80            .iter()
81            .map(|m| m.iter().map(|(k, v)| (k.0, v.0)).collect())
82            .collect(),
83        strings: out.pools.strings.0.clone(),
84        instr_src: out
85            .instr_src
86            .iter()
87            .map(|s| InstrSrcImage {
88                instr: s.instr,
89                span: s.span,
90                file_id: s.file_id,
91            })
92            .collect(),
93        fn_registers: out.fn_registers,
94        structs: out
95            .structs
96            .iter()
97            .map(|s| StructImage {
98                name: s.name.to_string(),
99                fields: s
100                    .fields
101                    .iter()
102                    .map(|(n, t, sp)| (n.to_string(), t.clone(), *sp))
103                    .collect(),
104                id: s.id,
105                name_span: s.name_span,
106            })
107            .collect(),
108        enums: out
109            .enums
110            .iter()
111            .map(|e| EnumImage {
112                name: e.name.to_string(),
113                variants: e
114                    .variants
115                    .iter()
116                    .map(|vt| EnumVariantImage {
117                        name: vt.name.to_string(),
118                        payload: vt.payload.to_vec(),
119                        name_span: vt.name_span,
120                    })
121                    .collect(),
122                id: e.id,
123                name_span: e.name_span,
124            })
125            .collect(),
126        sources: out
127            .sources
128            .iter()
129            .map(|s| SourceImage {
130                filename: s.filename.to_string(),
131                contents: s.contents.clone(),
132            })
133            .collect(),
134        allocated_arg_count: out.allocated_arg_count as u64,
135        allocated_call_depth: out.allocated_call_depth as u64,
136        dyn_lib_fns,
137        host_fns,
138    }
139}