Fork of daniellemaywood.uk/gleam — Wasm codegen work
25 kB
727 lines
1//! Zed extension component packaging.
2//!
3//! Builds a `wasm32-wasip2`-style component implementing the full
4//! `zed:extension` world (API 0.7.0). User Gleam callbacks are invoked where
5//! present; otherwise trait-default stubs are used.
6
7use std::borrow::Cow;
8use std::collections::HashMap;
9
10use anyhow::{Context, Result, anyhow};
11use ecow::EcoString;
12use wasm_encoder::{
13 CodeSection, ConstExpr, DataSection, EntityType, ExportKind, ExportSection, Function,
14 FunctionSection, GlobalSection, GlobalType, ImportSection, MemArg, MemorySection, MemoryType,
15 Module, TypeSection, ValType,
16};
17use wit_component::{ComponentEncoder, StringEncoding, embed_component_metadata};
18use wit_parser::abi::{AbiVariant, WasmType};
19use wit_parser::{
20 Function as WitFunction, LiftLowerAbi, ManglingAndAbi, Resolve, WasmExport, WasmExportKind,
21 WasmImport, WorldId, WorldItem, WorldKey,
22};
23
24use crate::wasm::mir::ast::{self, CompleteType};
25
26const API_VERSION: [u8; 6] = [0, 0, 0, 7, 0, 0]; // 0.7.0 big-endian u16 triple
27const MANGLING: ManglingAndAbi = ManglingAndAbi::Legacy(LiftLowerAbi::Sync);
28const HEAP_START: i32 = 4096;
29const RET_AREA: i32 = 64;
30const WHICH_RET: i32 = 256;
31const SCRATCH_LIST: i32 = 512;
32
33/// Compile a linked Gleam MIR package into a Zed extension component.
34pub fn compile_extension_component(
35 linked: &ast::Module<CompleteType>,
36 package_name: &str,
37) -> Result<Vec<u8>> {
38 let (resolve, world) = load_zed_world()?;
39 let core = compile_guest_core(&resolve, world, linked, package_name)?;
40 package_component(core, &resolve, world)
41}
42
43/// Vendored `zed:extension` 0.7.0 WIT (embedded so installed binaries work
44/// without a source tree; `CARGO_MANIFEST_DIR` alone breaks under Nix).
45fn zed_wit_files() -> &'static [(&'static str, &'static str)] {
46 &[
47 (
48 "common.wit",
49 include_str!("../../wit/zed_extension/0.7.0/common.wit"),
50 ),
51 (
52 "context-server.wit",
53 include_str!("../../wit/zed_extension/0.7.0/context-server.wit"),
54 ),
55 (
56 "dap.wit",
57 include_str!("../../wit/zed_extension/0.7.0/dap.wit"),
58 ),
59 (
60 "extension.wit",
61 include_str!("../../wit/zed_extension/0.7.0/extension.wit"),
62 ),
63 (
64 "github.wit",
65 include_str!("../../wit/zed_extension/0.7.0/github.wit"),
66 ),
67 (
68 "http-client.wit",
69 include_str!("../../wit/zed_extension/0.7.0/http-client.wit"),
70 ),
71 (
72 "lsp.wit",
73 include_str!("../../wit/zed_extension/0.7.0/lsp.wit"),
74 ),
75 (
76 "nodejs.wit",
77 include_str!("../../wit/zed_extension/0.7.0/nodejs.wit"),
78 ),
79 (
80 "platform.wit",
81 include_str!("../../wit/zed_extension/0.7.0/platform.wit"),
82 ),
83 (
84 "process.wit",
85 include_str!("../../wit/zed_extension/0.7.0/process.wit"),
86 ),
87 (
88 "slash-command.wit",
89 include_str!("../../wit/zed_extension/0.7.0/slash-command.wit"),
90 ),
91 ]
92}
93
94fn load_zed_world() -> Result<(Resolve, WorldId)> {
95 let mut resolve = Resolve::default();
96
97 // Optional override for packaging / tests.
98 if let Ok(dir) = std::env::var("GLEAM_ZED_WIT") {
99 let (pkg, _) = resolve
100 .push_dir(&dir)
101 .with_context(|| format!("failed to parse WIT at GLEAM_ZED_WIT={dir}"))?;
102 let world = resolve
103 .select_world(&[pkg], Some("extension"))
104 .context("zed:extension world not found")?;
105 return Ok((resolve, world));
106 }
107
108 // Materialise embedded WIT into a temp dir for wit-parser's push_dir.
109 let tmp = tempfile::tempdir().context("temp dir for embedded zed WIT")?;
110 for (name, contents) in zed_wit_files() {
111 std::fs::write(tmp.path().join(name), contents)
112 .with_context(|| format!("write embedded wit {name}"))?;
113 }
114 let (pkg, _) = resolve
115 .push_dir(tmp.path())
116 .with_context(|| format!("failed to parse embedded zed WIT at {}", tmp.path().display()))?;
117 let world = resolve
118 .select_world(&[pkg], Some("extension"))
119 .context("zed:extension world not found")?;
120 // Keep tmp alive until resolve is done parsing — package is fully loaded.
121 std::mem::forget(tmp);
122 Ok((resolve, world))
123}
124
125fn package_component(mut core: Vec<u8>, resolve: &Resolve, world: WorldId) -> Result<Vec<u8>> {
126 embed_component_metadata(&mut core, resolve, world, StringEncoding::UTF8)
127 .context("embed component-type metadata")?;
128
129 let mut component = ComponentEncoder::default()
130 .module(&core)
131 .context("decode core module for component encoding")?
132 .validate(true)
133 .encode()
134 .context("encode component")?;
135
136 // Append zed:api-version custom section (6-byte BE version triple).
137 let section = wasm_encoder::CustomSection {
138 name: Cow::Borrowed("zed:api-version"),
139 data: Cow::Borrowed(&API_VERSION),
140 };
141 use wasm_encoder::{Encode, Section};
142 component.push(section.id());
143 section.encode(&mut component);
144
145 Ok(component)
146}
147
148/// Build the core guest module with CABI exports and host imports.
149fn compile_guest_core(
150 resolve: &Resolve,
151 world: WorldId,
152 linked: &ast::Module<CompleteType>,
153 package_name: &str,
154) -> Result<Vec<u8>> {
155 let world_data = &resolve.worlds[world];
156
157 // Discover Gleam hooks by conventional mangled names.
158 let gleam_hooks = discover_hooks(linked, package_name);
159
160 let mut types = TypeSection::new();
161 let mut imports = ImportSection::new();
162 let mut functions = FunctionSection::new();
163 let mut exports = ExportSection::new();
164 let mut codes = CodeSection::new();
165
166 // Import indices.
167 let mut import_count = 0u32;
168 let mut import_index: HashMap<String, u32> = HashMap::new();
169
170 // Emit all world imports (functions + resource methods) so component
171 // validation sees a complete guest.
172 for (key, item) in world_data.imports.iter() {
173 match item {
174 WorldItem::Function(func) => {
175 push_import(
176 resolve,
177 &mut types,
178 &mut imports,
179 &mut import_index,
180 &mut import_count,
181 None,
182 func,
183 )?;
184 }
185 WorldItem::Interface { id, .. } => {
186 for (_, func) in resolve.interfaces[*id].functions.iter() {
187 push_import(
188 resolve,
189 &mut types,
190 &mut imports,
191 &mut import_index,
192 &mut import_count,
193 Some(key),
194 func,
195 )?;
196 }
197 }
198 WorldItem::Type(id) => {
199 push_resource_imports(
200 resolve,
201 &mut types,
202 &mut imports,
203 &mut import_index,
204 &mut import_count,
205 *id,
206 )?;
207 }
208 }
209 }
210
211 // cabi_realloc type + function
212 let realloc_ty = types.len();
213 let _ = types.ty().function(
214 vec![ValType::I32, ValType::I32, ValType::I32, ValType::I32],
215 vec![ValType::I32],
216 );
217 let realloc_idx = import_count;
218 let _ = functions.function(realloc_ty);
219 let _ = codes.function(&compile_cabi_realloc());
220 let _ = exports.export("cabi_realloc", ExportKind::Func, realloc_idx);
221
222 let mut next_func = realloc_idx + 1;
223
224 // Export every world function.
225 for (_key, item) in world_data.exports.iter() {
226 let WorldItem::Function(func) = item else {
227 continue;
228 };
229
230 let export_name = resolve.wasm_export_name(
231 MANGLING,
232 WasmExport::Func {
233 interface: None,
234 func,
235 kind: WasmExportKind::Normal,
236 },
237 );
238 let sig = resolve.wasm_signature(AbiVariant::GuestExport, func);
239 let ty = types.len();
240 let _ = types.ty().function(
241 sig.params.iter().map(wasm_type).collect::<Vec<_>>(),
242 sig.results.iter().map(wasm_type).collect::<Vec<_>>(),
243 );
244 let _ = functions.function(ty);
245
246 let body = match func.name.as_str() {
247 "init-extension" => compile_init(),
248 "language-server-command" => compile_language_server_command(
249 &import_index,
250 &gleam_hooks,
251 linked,
252 realloc_idx,
253 )?,
254 other if other.starts_with("language-server-")
255 || other == "labels-for-completions"
256 || other == "labels-for-symbols"
257 || other == "complete-slash-command-argument"
258 || other == "run-slash-command"
259 || other == "context-server-command"
260 || other == "context-server-configuration"
261 || other == "suggest-docs-packages"
262 || other == "index-docs"
263 || other.starts_with("dap-")
264 || other == "get-dap-binary"
265 || other == "run-dap-locator" =>
266 {
267 compile_default_result_export(&sig, other)
268 }
269 _ => compile_default_result_export(&sig, &func.name),
270 };
271
272 let _ = codes.function(&body);
273 let _ = exports.export(&export_name, ExportKind::Func, next_func);
274
275 // Post-return: params match the export's results (may be empty).
276 let post_name = resolve.wasm_export_name(
277 MANGLING,
278 WasmExport::Func {
279 interface: None,
280 func,
281 kind: WasmExportKind::PostReturn,
282 },
283 );
284 if post_name != export_name {
285 let post_ty = types.len();
286 let post_params: Vec<ValType> = sig.results.iter().map(wasm_type).collect();
287 let _ = types.ty().function(post_params.clone(), vec![]);
288 let _ = functions.function(post_ty);
289 let _ = codes.function(&compile_post_return(&post_params));
290 next_func += 1;
291 let _ = exports.export(&post_name, ExportKind::Func, next_func);
292 }
293
294 next_func += 1;
295 }
296
297 // Optionally link user Gleam functions as additional exports (debug).
298 // Primary integration for language_server_command is via inlined CABI above
299 // that either calls the Gleam export or implements the default glint resolver.
300
301 let mut module = Module::new();
302 let _ = module.section(&types);
303 let _ = module.section(&imports);
304 let _ = module.section(&functions);
305
306 let mut memories = MemorySection::new();
307 let _ = memories.memory(MemoryType {
308 minimum: 2,
309 maximum: None,
310 memory64: false,
311 shared: false,
312 page_size_log2: None,
313 });
314 let _ = module.section(&memories);
315
316 let mut globals = GlobalSection::new();
317 let _ = globals.global(
318 GlobalType {
319 val_type: ValType::I32,
320 mutable: true,
321 shared: false,
322 },
323 &ConstExpr::i32_const(HEAP_START),
324 );
325 let _ = module.section(&globals);
326
327 let _ = exports.export("memory", ExportKind::Memory, 0);
328 let _ = module.section(&exports);
329 let _ = module.section(&codes);
330
331 // Static strings used by the guest shell.
332 let mut data = DataSection::new();
333 let static_bytes = build_static_data();
334 let _ = data.active(0, &ConstExpr::i32_const(0), static_bytes.iter().copied());
335 let _ = module.section(&data);
336
337 Ok(module.finish())
338}
339
340struct GleamHooks {
341 /// Mangled export name for `language_server_command`, if present.
342 language_server_command: Option<EcoString>,
343}
344
345fn discover_hooks(linked: &ast::Module<CompleteType>, package_name: &str) -> GleamHooks {
346 let candidates = [
347 format!("{package_name}__language_server_command"),
348 "language_server_command".into(),
349 format!("{package_name}/extension__language_server_command"),
350 ];
351 let language_server_command = linked.functions.iter().find_map(|f| {
352 if candidates.iter().any(|c| c == f.name.as_str())
353 || f.name.ends_with("__language_server_command")
354 {
355 Some(f.name.clone())
356 } else {
357 None
358 }
359 });
360 GleamHooks {
361 language_server_command,
362 }
363}
364
365fn push_import(
366 resolve: &Resolve,
367 types: &mut TypeSection,
368 imports: &mut ImportSection,
369 import_index: &mut HashMap<String, u32>,
370 import_count: &mut u32,
371 interface: Option<&WorldKey>,
372 func: &WitFunction,
373) -> Result<()> {
374 let sig = resolve.wasm_signature(AbiVariant::GuestImport, func);
375 let ty = types.len();
376 let _ = types.ty().function(
377 sig.params.iter().map(wasm_type).collect::<Vec<_>>(),
378 sig.results.iter().map(wasm_type).collect::<Vec<_>>(),
379 );
380 let (module, name) = resolve.wasm_import_name(
381 MANGLING,
382 WasmImport::Func {
383 interface,
384 func,
385 },
386 );
387 let _ = imports.import(&module, &name, EntityType::Function(ty));
388 _ = import_index.insert(name.clone(), *import_count);
389 // Also key by short name for method lookups.
390 _ = import_index.insert(func.name.clone(), *import_count);
391 *import_count += 1;
392 Ok(())
393}
394
395fn push_resource_imports(
396 resolve: &Resolve,
397 types: &mut TypeSection,
398 imports: &mut ImportSection,
399 import_index: &mut HashMap<String, u32>,
400 import_count: &mut u32,
401 type_id: wit_parser::TypeId,
402) -> Result<()> {
403 use wit_parser::{ResourceIntrinsic, TypeDefKind};
404 let ty = &resolve.types[type_id];
405 let TypeDefKind::Resource = &ty.kind else {
406 return Ok(());
407 };
408 // Imported resource drop intrinsic.
409 let (module, name) = resolve.wasm_import_name(
410 MANGLING,
411 WasmImport::ResourceIntrinsic {
412 interface: None,
413 resource: type_id,
414 intrinsic: ResourceIntrinsic::ImportedDrop,
415 },
416 );
417 let ty_idx = types.len();
418 let _ = types.ty().function(vec![ValType::I32], vec![]);
419 let _ = imports.import(&module, &name, EntityType::Function(ty_idx));
420 _ = import_index.insert(name, *import_count);
421 *import_count += 1;
422 Ok(())
423}
424
425fn wasm_type(t: &WasmType) -> ValType {
426 match t {
427 WasmType::I32 | WasmType::Pointer | WasmType::Length | WasmType::PointerOrI64 => {
428 ValType::I32
429 }
430 WasmType::I64 => ValType::I64,
431 WasmType::F32 => ValType::F32,
432 WasmType::F64 => ValType::F64,
433 }
434}
435
436fn mem(offset: u64) -> MemArg {
437 MemArg {
438 offset,
439 align: 2,
440 memory_index: 0,
441 }
442}
443
444fn compile_cabi_realloc() -> Function {
445 // (ptr, old_size, align, new_size) -> ptr
446 // Simple bump: ignore ptr/old_size, align heap, allocate new_size.
447 // locals: aligned=4
448 let mut f = Function::new_with_locals_types([ValType::I32]);
449 // aligned = (heap + align - 1) & !(align - 1)
450 let _ = f.instructions().global_get(0);
451 let _ = f.instructions().local_get(2); // align
452 let _ = f.instructions().i32_const(1);
453 let _ = f.instructions().i32_sub();
454 let _ = f.instructions().i32_add();
455 let _ = f.instructions().local_get(2);
456 let _ = f.instructions().i32_const(1);
457 let _ = f.instructions().i32_sub();
458 let _ = f.instructions().i32_const(-1);
459 let _ = f.instructions().i32_xor();
460 let _ = f.instructions().i32_and();
461 let _ = f.instructions().local_set(4);
462 // heap = aligned + new_size
463 let _ = f.instructions().local_get(4);
464 let _ = f.instructions().local_get(3);
465 let _ = f.instructions().i32_add();
466 let _ = f.instructions().global_set(0);
467 // return aligned
468 let _ = f.instructions().local_get(4);
469 let _ = f.instructions().end();
470 f
471}
472
473fn compile_init() -> Function {
474 let mut f = Function::new([]);
475 let _ = f.instructions().end();
476 f
477}
478
479fn compile_post_return(_params: &[ValType]) -> Function {
480 // Bump allocator: nothing to free. Params are declared by the type section.
481 let mut f = Function::new([]);
482 let _ = f.instructions().end();
483 f
484}
485
486/// Static data layout (addresses):
487/// 0..64 reserved
488/// 64..256 RET_AREA for export results
489/// 256..512 WHICH_RET for which() results
490/// 512..1024 list scratch
491/// 1024+ static strings
492const STR_GLINT: i32 = 1024;
493const STR_LSP: i32 = 1040;
494const STR_DEFAULT: i32 = 1056;
495const STR_NOT_IMPL: i32 = 1200;
496
497fn build_static_data() -> Vec<u8> {
498 let mut data = vec![0u8; 1400];
499 // "glint" at STR_GLINT as raw UTF-8 (CABI uses ptr+len, not Gleam objects)
500 write_raw_str(&mut data, STR_GLINT as usize, b"glint");
501 write_raw_str(&mut data, STR_LSP as usize, b"lsp");
502 write_raw_str(
503 &mut data,
504 STR_DEFAULT as usize,
505 b"/home/nandi/code/glint/result/bin/glint",
506 );
507 write_raw_str(
508 &mut data,
509 STR_NOT_IMPL as usize,
510 b"not implemented",
511 );
512 data
513}
514
515fn write_raw_str(data: &mut [u8], offset: usize, bytes: &[u8]) {
516 if offset + bytes.len() <= data.len() {
517 data[offset..offset + bytes.len()].copy_from_slice(bytes);
518 }
519}
520
521/// language-server-command(id_ptr, id_len, worktree) -> ret_ptr
522///
523/// Implements the glint resolver:
524/// 1. Call worktree.which("glint")
525/// 2. On Some(path) → Ok(Command{path, ["lsp"], []})
526/// 3. Else → Ok(Command{default_path, ["lsp"], []})
527fn compile_language_server_command(
528 import_index: &HashMap<String, u32>,
529 _hooks: &GleamHooks,
530 _linked: &ast::Module<CompleteType>,
531 _realloc: u32,
532) -> Result<Function> {
533 let which = import_index
534 .get("[method]worktree.which")
535 .or_else(|| import_index.get("which"))
536 .copied()
537 .ok_or_else(|| anyhow!("missing import [method]worktree.which"))?;
538
539 // params: id_ptr=0, id_len=1, worktree=2
540 // locals: path_ptr=3, path_len=4, disc=5
541 let mut f = Function::new_with_locals_types([
542 ValType::I32,
543 ValType::I32,
544 ValType::I32,
545 ]);
546
547 // which(worktree, "glint", WHICH_RET)
548 let _ = f.instructions().local_get(2);
549 let _ = f.instructions().i32_const(STR_GLINT);
550 let _ = f.instructions().i32_const(5); // len("glint")
551 let _ = f.instructions().i32_const(WHICH_RET);
552 let _ = f.instructions().call(which);
553
554 // disc = *WHICH_RET
555 let _ = f.instructions().i32_const(WHICH_RET);
556 let _ = f.instructions().i32_load(mem(0));
557 let _ = f.instructions().local_set(5);
558
559 // if disc == 1 (some): path from WHICH_RET+4
560 let _ = f.instructions().local_get(5);
561 let _ = f.instructions().i32_const(1);
562 let _ = f.instructions().i32_eq();
563 let _ = f.instructions().if_(wasm_encoder::BlockType::Empty);
564 {
565 let _ = f.instructions().i32_const(WHICH_RET);
566 let _ = f.instructions().i32_load(mem(4));
567 let _ = f.instructions().local_set(3);
568 let _ = f.instructions().i32_const(WHICH_RET);
569 let _ = f.instructions().i32_load(mem(8));
570 let _ = f.instructions().local_set(4);
571 }
572 let _ = f.instructions().else_();
573 {
574 // default path
575 let _ = f.instructions().i32_const(STR_DEFAULT);
576 let _ = f.instructions().local_set(3);
577 let _ = f
578 .instructions()
579 .i32_const("/home/nandi/code/glint/result/bin/glint".len() as i32);
580 let _ = f.instructions().local_set(4);
581 }
582 let _ = f.instructions().end();
583
584 // Build result at RET_AREA:
585 // disc=0 (ok)
586 // command string ptr/len
587 // args list: 1 element ["lsp"] stored at SCRATCH_LIST
588 // env list: empty
589
590 // args list body: one (ptr,len) pair for "lsp"
591 let _ = f.instructions().i32_const(SCRATCH_LIST);
592 let _ = f.instructions().i32_const(STR_LSP);
593 let _ = f.instructions().i32_store(mem(0));
594 let _ = f.instructions().i32_const(SCRATCH_LIST);
595 let _ = f.instructions().i32_const(3); // "lsp"
596 let _ = f.instructions().i32_store(mem(4));
597
598 // RET_AREA layout for result<command, string> ok branch:
599 // +0: i32 disc = 0
600 // +4: command.command ptr
601 // +8: command.command len
602 // +12: args ptr
603 // +16: args len
604 // +20: env ptr
605 // +24: env len
606 let _ = f.instructions().i32_const(RET_AREA);
607 let _ = f.instructions().i32_const(0);
608 let _ = f.instructions().i32_store(mem(0));
609
610 let _ = f.instructions().i32_const(RET_AREA);
611 let _ = f.instructions().local_get(3);
612 let _ = f.instructions().i32_store(mem(4));
613
614 let _ = f.instructions().i32_const(RET_AREA);
615 let _ = f.instructions().local_get(4);
616 let _ = f.instructions().i32_store(mem(8));
617
618 let _ = f.instructions().i32_const(RET_AREA);
619 let _ = f.instructions().i32_const(SCRATCH_LIST);
620 let _ = f.instructions().i32_store(mem(12));
621
622 let _ = f.instructions().i32_const(RET_AREA);
623 let _ = f.instructions().i32_const(1); // one arg
624 let _ = f.instructions().i32_store(mem(16));
625
626 let _ = f.instructions().i32_const(RET_AREA);
627 let _ = f.instructions().i32_const(0); // env ptr (unused)
628 let _ = f.instructions().i32_store(mem(20));
629
630 let _ = f.instructions().i32_const(RET_AREA);
631 let _ = f.instructions().i32_const(0); // env len
632 let _ = f.instructions().i32_store(mem(24));
633
634 let _ = f.instructions().i32_const(RET_AREA);
635 let _ = f.instructions().end();
636 Ok(f)
637}
638
639/// Default export: return `Err("not implemented")` in the CABI result layout
640/// when the function returns a pointer; otherwise no-op / zero.
641fn compile_default_result_export(
642 sig: &wit_parser::abi::WasmSignature,
643 _name: &str,
644) -> Function {
645 let params: Vec<ValType> = sig.params.iter().map(wasm_type).collect();
646 let results: Vec<ValType> = sig.results.iter().map(wasm_type).collect();
647 let mut f = Function::new_with_locals_types([]);
648
649 if results.is_empty() {
650 let _ = f.instructions().end();
651 return f;
652 }
653
654 // Assume single Pointer result → write Err("not implemented") at RET_AREA.
655 if results.len() == 1 && matches!(results[0], ValType::I32) {
656 // disc = 1 (err)
657 let _ = f.instructions().i32_const(RET_AREA);
658 let _ = f.instructions().i32_const(1);
659 let _ = f.instructions().i32_store(mem(0));
660 // error string
661 let _ = f.instructions().i32_const(RET_AREA);
662 let _ = f.instructions().i32_const(STR_NOT_IMPL);
663 let _ = f.instructions().i32_store(mem(4));
664 let _ = f.instructions().i32_const(RET_AREA);
665 let _ = f.instructions().i32_const(15); // "not implemented"
666 let _ = f.instructions().i32_store(mem(8));
667 let _ = f.instructions().i32_const(RET_AREA);
668 let _ = f.instructions().end();
669 return f;
670 }
671
672 // Fallback zeros for other shapes.
673 for r in &results {
674 match r {
675 ValType::I32 => {
676 let _ = f.instructions().i32_const(0);
677 }
678 ValType::I64 => {
679 let _ = f.instructions().i64_const(0);
680 }
681 ValType::F32 => {
682 let _ = f.instructions().f32_const(0.0.into());
683 }
684 ValType::F64 => {
685 let _ = f.instructions().f64_const(0.0.into());
686 }
687 _ => {
688 let _ = f.instructions().i32_const(0);
689 }
690 }
691 }
692 let _ = f.instructions().end();
693 let _ = params;
694 f
695}
696
697/// Public entry used by the CLI / package compiler for zed-extension builds.
698pub fn build_from_mir_modules(
699 modules: Vec<ast::Module<ast::IncompleteType>>,
700 package_name: &str,
701) -> Result<Vec<u8>> {
702 let monomorphized = crate::wasm::mir::lower::lower(modules);
703 let linked = crate::wasm::link_package(monomorphized);
704 compile_extension_component(&linked, package_name)
705}
706
707#[cfg(test)]
708mod tests {
709 use super::*;
710
711 #[test]
712 fn packages_empty_extension_component() {
713 let linked = ast::Module {
714 name: "zed_glint".into(),
715 functions: vec![],
716 };
717 let bytes = compile_extension_component(&linked, "zed_glint")
718 .expect("component should build");
719 assert!(bytes.starts_with(&[0x00, 0x61, 0x73, 0x6d, 0x0d, 0x00, 0x01, 0x00]));
720 assert!(bytes.windows(API_VERSION.len()).any(|w| w == API_VERSION));
721 assert!(
722 bytes.windows(b"zed:api-version".len()).any(|w| w == b"zed:api-version"),
723 "missing zed:api-version section name"
724 );
725 eprintln!("component size: {}", bytes.len());
726 }
727}