Fork of daniellemaywood.uk/gleam — Wasm codegen work
2

Configure Feed

Select the types of activity you want to include in your feed.

gleam / compiler-core / src / wasm / zed.rs
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}