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

Configure Feed

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

gleam / compiler-core / src / wasm / zed.rs
32 kB 926 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 // Match zed_extension_api defaults: Ok(None), not Err. 255 "language-server-initialization-options" 256 | "language-server-workspace-configuration" 257 | "language-server-additional-initialization-options" 258 | "language-server-additional-workspace-configuration" 259 | "context-server-configuration" => { 260 compile_ok_none(resolve, func, &import_index, &sig) 261 } 262 // Match defaults: Ok([]) 263 "labels-for-completions" 264 | "labels-for-symbols" 265 | "complete-slash-command-argument" 266 | "suggest-docs-packages" => { 267 compile_ok_empty_list(resolve, func, &import_index, &sig) 268 } 269 other if other.starts_with("language-server-") 270 || other == "run-slash-command" 271 || other == "context-server-command" 272 || other == "index-docs" 273 || other.starts_with("dap-") 274 || other == "get-dap-binary" 275 || other == "run-dap-locator" => 276 { 277 compile_default_result_export(resolve, func, &import_index, &sig, other) 278 } 279 _ => compile_default_result_export(resolve, func, &import_index, &sig, &func.name), 280 }; 281 282 let _ = codes.function(&body); 283 let _ = exports.export(&export_name, ExportKind::Func, next_func); 284 285 // Post-return: params match the export's results (may be empty). 286 let post_name = resolve.wasm_export_name( 287 MANGLING, 288 WasmExport::Func { 289 interface: None, 290 func, 291 kind: WasmExportKind::PostReturn, 292 }, 293 ); 294 if post_name != export_name { 295 let post_ty = types.len(); 296 let post_params: Vec<ValType> = sig.results.iter().map(wasm_type).collect(); 297 let _ = types.ty().function(post_params.clone(), vec![]); 298 let _ = functions.function(post_ty); 299 let _ = codes.function(&compile_post_return(&post_params)); 300 next_func += 1; 301 let _ = exports.export(&post_name, ExportKind::Func, next_func); 302 } 303 304 next_func += 1; 305 } 306 307 // Optionally link user Gleam functions as additional exports (debug). 308 // Primary integration for language_server_command is via inlined CABI above 309 // that either calls the Gleam export or implements the default glint resolver. 310 311 let mut module = Module::new(); 312 let _ = module.section(&types); 313 let _ = module.section(&imports); 314 let _ = module.section(&functions); 315 316 let mut memories = MemorySection::new(); 317 let _ = memories.memory(MemoryType { 318 minimum: 2, 319 maximum: None, 320 memory64: false, 321 shared: false, 322 page_size_log2: None, 323 }); 324 let _ = module.section(&memories); 325 326 let mut globals = GlobalSection::new(); 327 let _ = globals.global( 328 GlobalType { 329 val_type: ValType::I32, 330 mutable: true, 331 shared: false, 332 }, 333 &ConstExpr::i32_const(HEAP_START), 334 ); 335 let _ = module.section(&globals); 336 337 let _ = exports.export("memory", ExportKind::Memory, 0); 338 let _ = module.section(&exports); 339 let _ = module.section(&codes); 340 341 // Static strings used by the guest shell. 342 let mut data = DataSection::new(); 343 let static_bytes = build_static_data(); 344 let _ = data.active(0, &ConstExpr::i32_const(0), static_bytes.iter().copied()); 345 let _ = module.section(&data); 346 347 Ok(module.finish()) 348} 349 350struct GleamHooks { 351 /// Mangled export name for `language_server_command`, if present. 352 language_server_command: Option<EcoString>, 353} 354 355fn discover_hooks(linked: &ast::Module<CompleteType>, package_name: &str) -> GleamHooks { 356 let candidates = [ 357 format!("{package_name}__language_server_command"), 358 "language_server_command".into(), 359 format!("{package_name}/extension__language_server_command"), 360 ]; 361 let language_server_command = linked.functions.iter().find_map(|f| { 362 if candidates.iter().any(|c| c == f.name.as_str()) 363 || f.name.ends_with("__language_server_command") 364 { 365 Some(f.name.clone()) 366 } else { 367 None 368 } 369 }); 370 GleamHooks { 371 language_server_command, 372 } 373} 374 375fn push_import( 376 resolve: &Resolve, 377 types: &mut TypeSection, 378 imports: &mut ImportSection, 379 import_index: &mut HashMap<String, u32>, 380 import_count: &mut u32, 381 interface: Option<&WorldKey>, 382 func: &WitFunction, 383) -> Result<()> { 384 let sig = resolve.wasm_signature(AbiVariant::GuestImport, func); 385 let ty = types.len(); 386 let _ = types.ty().function( 387 sig.params.iter().map(wasm_type).collect::<Vec<_>>(), 388 sig.results.iter().map(wasm_type).collect::<Vec<_>>(), 389 ); 390 let (module, name) = resolve.wasm_import_name( 391 MANGLING, 392 WasmImport::Func { 393 interface, 394 func, 395 }, 396 ); 397 let _ = imports.import(&module, &name, EntityType::Function(ty)); 398 _ = import_index.insert(name.clone(), *import_count); 399 // Also key by short name for method lookups. 400 _ = import_index.insert(func.name.clone(), *import_count); 401 *import_count += 1; 402 Ok(()) 403} 404 405fn push_resource_imports( 406 resolve: &Resolve, 407 types: &mut TypeSection, 408 imports: &mut ImportSection, 409 import_index: &mut HashMap<String, u32>, 410 import_count: &mut u32, 411 type_id: wit_parser::TypeId, 412) -> Result<()> { 413 use wit_parser::{ResourceIntrinsic, TypeDefKind}; 414 let ty = &resolve.types[type_id]; 415 let TypeDefKind::Resource = &ty.kind else { 416 return Ok(()); 417 }; 418 // Imported resource drop intrinsic. 419 let (module, name) = resolve.wasm_import_name( 420 MANGLING, 421 WasmImport::ResourceIntrinsic { 422 interface: None, 423 resource: type_id, 424 intrinsic: ResourceIntrinsic::ImportedDrop, 425 }, 426 ); 427 let ty_idx = types.len(); 428 let _ = types.ty().function(vec![ValType::I32], vec![]); 429 let _ = imports.import(&module, &name, EntityType::Function(ty_idx)); 430 _ = import_index.insert(name.clone(), *import_count); 431 // Alias by resource name for easy lookup (e.g. "worktree" -> drop). 432 if let Some(res_name) = ty.name.as_deref() { 433 _ = import_index.insert(format!("[resource-drop]{res_name}"), *import_count); 434 _ = import_index.insert(format!("{res_name}_drop"), *import_count); 435 } 436 *import_count += 1; 437 Ok(()) 438} 439 440fn wasm_type(t: &WasmType) -> ValType { 441 match t { 442 WasmType::I32 | WasmType::Pointer | WasmType::Length | WasmType::PointerOrI64 => { 443 ValType::I32 444 } 445 WasmType::I64 => ValType::I64, 446 WasmType::F32 => ValType::F32, 447 WasmType::F64 => ValType::F64, 448 } 449} 450 451fn mem(offset: u64) -> MemArg { 452 MemArg { 453 offset, 454 align: 2, 455 memory_index: 0, 456 } 457} 458 459fn compile_cabi_realloc() -> Function { 460 // (ptr, old_size, align, new_size) -> ptr 461 // Simple bump: ignore ptr/old_size, align heap, allocate new_size. 462 // locals: aligned=4 463 let mut f = Function::new_with_locals_types([ValType::I32]); 464 // aligned = (heap + align - 1) & !(align - 1) 465 let _ = f.instructions().global_get(0); 466 let _ = f.instructions().local_get(2); // align 467 let _ = f.instructions().i32_const(1); 468 let _ = f.instructions().i32_sub(); 469 let _ = f.instructions().i32_add(); 470 let _ = f.instructions().local_get(2); 471 let _ = f.instructions().i32_const(1); 472 let _ = f.instructions().i32_sub(); 473 let _ = f.instructions().i32_const(-1); 474 let _ = f.instructions().i32_xor(); 475 let _ = f.instructions().i32_and(); 476 let _ = f.instructions().local_set(4); 477 // heap = aligned + new_size 478 let _ = f.instructions().local_get(4); 479 let _ = f.instructions().local_get(3); 480 let _ = f.instructions().i32_add(); 481 let _ = f.instructions().global_set(0); 482 // return aligned 483 let _ = f.instructions().local_get(4); 484 let _ = f.instructions().end(); 485 f 486} 487 488fn compile_init() -> Function { 489 let mut f = Function::new([]); 490 let _ = f.instructions().end(); 491 f 492} 493 494fn compile_post_return(_params: &[ValType]) -> Function { 495 // Bump allocator: nothing to free. Params are declared by the type section. 496 let mut f = Function::new([]); 497 let _ = f.instructions().end(); 498 f 499} 500 501/// Static data layout (addresses): 502/// 0..64 reserved 503/// 64..256 RET_AREA for export results 504/// 256..512 WHICH_RET for which() results 505/// 512..1024 list scratch 506/// 1024+ static strings 507const STR_GLINT: i32 = 1024; 508const STR_LSP: i32 = 1040; 509const STR_DEFAULT: i32 = 1056; 510const STR_NOT_IMPL: i32 = 1200; 511 512fn build_static_data() -> Vec<u8> { 513 let mut data = vec![0u8; 1400]; 514 // "glint" at STR_GLINT as raw UTF-8 (CABI uses ptr+len, not Gleam objects) 515 write_raw_str(&mut data, STR_GLINT as usize, b"glint"); 516 write_raw_str(&mut data, STR_LSP as usize, b"lsp"); 517 write_raw_str( 518 &mut data, 519 STR_DEFAULT as usize, 520 b"/home/nandi/code/glint/result/bin/glint", 521 ); 522 write_raw_str( 523 &mut data, 524 STR_NOT_IMPL as usize, 525 b"not implemented", 526 ); 527 data 528} 529 530fn write_raw_str(data: &mut [u8], offset: usize, bytes: &[u8]) { 531 if offset + bytes.len() <= data.len() { 532 data[offset..offset + bytes.len()].copy_from_slice(bytes); 533 } 534} 535 536/// language-server-command(id_ptr, id_len, worktree) -> ret_ptr 537/// 538/// Implements the glint resolver: 539/// 1. Call worktree.which("glint") 540/// 2. On Some(path) → Ok(Command{path, ["lsp"], []}) 541/// 3. Else → Ok(Command{default_path, ["lsp"], []}) 542fn compile_language_server_command( 543 import_index: &HashMap<String, u32>, 544 _hooks: &GleamHooks, 545 _linked: &ast::Module<CompleteType>, 546 _realloc: u32, 547) -> Result<Function> { 548 let which = import_index 549 .get("[method]worktree.which") 550 .or_else(|| import_index.get("which")) 551 .copied() 552 .ok_or_else(|| anyhow!("missing import [method]worktree.which"))?; 553 554 // params: id_ptr=0, id_len=1, worktree=2 555 // locals: path_ptr=3, path_len=4, disc=5 556 let mut f = Function::new_with_locals_types([ 557 ValType::I32, 558 ValType::I32, 559 ValType::I32, 560 ]); 561 562 // which(worktree, "glint", WHICH_RET) 563 let _ = f.instructions().local_get(2); 564 let _ = f.instructions().i32_const(STR_GLINT); 565 let _ = f.instructions().i32_const(5); // len("glint") 566 let _ = f.instructions().i32_const(WHICH_RET); 567 let _ = f.instructions().call(which); 568 569 // disc = *WHICH_RET 570 let _ = f.instructions().i32_const(WHICH_RET); 571 let _ = f.instructions().i32_load(mem(0)); 572 let _ = f.instructions().local_set(5); 573 574 // if disc == 1 (some): path from WHICH_RET+4 575 let _ = f.instructions().local_get(5); 576 let _ = f.instructions().i32_const(1); 577 let _ = f.instructions().i32_eq(); 578 let _ = f.instructions().if_(wasm_encoder::BlockType::Empty); 579 { 580 let _ = f.instructions().i32_const(WHICH_RET); 581 let _ = f.instructions().i32_load(mem(4)); 582 let _ = f.instructions().local_set(3); 583 let _ = f.instructions().i32_const(WHICH_RET); 584 let _ = f.instructions().i32_load(mem(8)); 585 let _ = f.instructions().local_set(4); 586 } 587 let _ = f.instructions().else_(); 588 { 589 // default path 590 let _ = f.instructions().i32_const(STR_DEFAULT); 591 let _ = f.instructions().local_set(3); 592 let _ = f 593 .instructions() 594 .i32_const("/home/nandi/code/glint/result/bin/glint".len() as i32); 595 let _ = f.instructions().local_set(4); 596 } 597 let _ = f.instructions().end(); 598 599 // Build result at RET_AREA: 600 // disc=0 (ok) 601 // command string ptr/len 602 // args list: 1 element ["lsp"] stored at SCRATCH_LIST 603 // env list: empty 604 605 // args list body: one (ptr,len) pair for "lsp" 606 let _ = f.instructions().i32_const(SCRATCH_LIST); 607 let _ = f.instructions().i32_const(STR_LSP); 608 let _ = f.instructions().i32_store(mem(0)); 609 let _ = f.instructions().i32_const(SCRATCH_LIST); 610 let _ = f.instructions().i32_const(3); // "lsp" 611 let _ = f.instructions().i32_store(mem(4)); 612 613 // RET_AREA layout for result<command, string> ok branch: 614 // +0: i32 disc = 0 615 // +4: command.command ptr 616 // +8: command.command len 617 // +12: args ptr 618 // +16: args len 619 // +20: env ptr 620 // +24: env len 621 let _ = f.instructions().i32_const(RET_AREA); 622 let _ = f.instructions().i32_const(0); 623 let _ = f.instructions().i32_store(mem(0)); 624 625 let _ = f.instructions().i32_const(RET_AREA); 626 let _ = f.instructions().local_get(3); 627 let _ = f.instructions().i32_store(mem(4)); 628 629 let _ = f.instructions().i32_const(RET_AREA); 630 let _ = f.instructions().local_get(4); 631 let _ = f.instructions().i32_store(mem(8)); 632 633 let _ = f.instructions().i32_const(RET_AREA); 634 let _ = f.instructions().i32_const(SCRATCH_LIST); 635 let _ = f.instructions().i32_store(mem(12)); 636 637 let _ = f.instructions().i32_const(RET_AREA); 638 let _ = f.instructions().i32_const(1); // one arg 639 let _ = f.instructions().i32_store(mem(16)); 640 641 let _ = f.instructions().i32_const(RET_AREA); 642 let _ = f.instructions().i32_const(0); // env ptr (unused) 643 let _ = f.instructions().i32_store(mem(20)); 644 645 let _ = f.instructions().i32_const(RET_AREA); 646 let _ = f.instructions().i32_const(0); // env len 647 let _ = f.instructions().i32_store(mem(24)); 648 649 // Release borrow<worktree> (param index 2). Leaving it triggers: 650 // "borrow handles still remain at the end of the call". 651 if let Some(drop_wt) = import_index 652 .get("[resource-drop]worktree") 653 .or_else(|| import_index.get("worktree_drop")) 654 .copied() 655 { 656 let _ = f.instructions().local_get(2); 657 let _ = f.instructions().call(drop_wt); 658 } 659 660 let _ = f.instructions().i32_const(RET_AREA); 661 let _ = f.instructions().end(); 662 Ok(f) 663} 664 665/// Emit resource-handle drops for an export body (no-op when params are indirect). 666fn emit_param_drops( 667 f: &mut Function, 668 resolve: &Resolve, 669 func: &WitFunction, 670 import_index: &HashMap<String, u32>, 671 sig: &wit_parser::abi::WasmSignature, 672) { 673 if sig.indirect_params { 674 return; 675 } 676 let drops = resource_handle_drops(resolve, func, import_index, sig.params.len() as u32); 677 for (local, drop_fn) in &drops { 678 let _ = f.instructions().local_get(*local); 679 let _ = f.instructions().call(*drop_fn); 680 } 681} 682 683/// `Ok(None)` for `result<option<_>, string>` (LSP init/workspace config defaults). 684fn compile_ok_none( 685 resolve: &Resolve, 686 func: &WitFunction, 687 import_index: &HashMap<String, u32>, 688 sig: &wit_parser::abi::WasmSignature, 689) -> Function { 690 let mut f = Function::new_with_locals_types([]); 691 emit_param_drops(&mut f, resolve, func, import_index, sig); 692 // result disc = 0 (ok) 693 let _ = f.instructions().i32_const(RET_AREA); 694 let _ = f.instructions().i32_const(0); 695 let _ = f.instructions().i32_store(mem(0)); 696 // option disc = 0 (none) 697 let _ = f.instructions().i32_const(RET_AREA); 698 let _ = f.instructions().i32_const(0); 699 let _ = f.instructions().i32_store(mem(4)); 700 let _ = f.instructions().i32_const(RET_AREA); 701 let _ = f.instructions().end(); 702 f 703} 704 705/// `Ok([])` for `result<list<_>, string>`. 706fn compile_ok_empty_list( 707 resolve: &Resolve, 708 func: &WitFunction, 709 import_index: &HashMap<String, u32>, 710 sig: &wit_parser::abi::WasmSignature, 711) -> Function { 712 let mut f = Function::new_with_locals_types([]); 713 emit_param_drops(&mut f, resolve, func, import_index, sig); 714 // result disc = 0 (ok) 715 let _ = f.instructions().i32_const(RET_AREA); 716 let _ = f.instructions().i32_const(0); 717 let _ = f.instructions().i32_store(mem(0)); 718 // list ptr (unused) + len 0 719 let _ = f.instructions().i32_const(RET_AREA); 720 let _ = f.instructions().i32_const(0); 721 let _ = f.instructions().i32_store(mem(4)); 722 let _ = f.instructions().i32_const(RET_AREA); 723 let _ = f.instructions().i32_const(0); 724 let _ = f.instructions().i32_store(mem(8)); 725 let _ = f.instructions().i32_const(RET_AREA); 726 let _ = f.instructions().end(); 727 f 728} 729 730/// Default export: return `Err("not implemented")` in the CABI result layout 731/// when the function returns a pointer; otherwise no-op / zero. 732/// 733/// Also drops any `borrow`/`own` resource handles received as parameters so 734/// the component model borrow table is clean on return. 735fn compile_default_result_export( 736 resolve: &Resolve, 737 func: &WitFunction, 738 import_index: &HashMap<String, u32>, 739 sig: &wit_parser::abi::WasmSignature, 740 _name: &str, 741) -> Function { 742 let results: Vec<ValType> = sig.results.iter().map(wasm_type).collect(); 743 let mut f = Function::new_with_locals_types([]); 744 emit_param_drops(&mut f, resolve, func, import_index, sig); 745 746 if results.is_empty() { 747 let _ = f.instructions().end(); 748 return f; 749 } 750 751 // Assume single Pointer result → write Err("not implemented") at RET_AREA. 752 if results.len() == 1 && matches!(results[0], ValType::I32) { 753 // disc = 1 (err) 754 let _ = f.instructions().i32_const(RET_AREA); 755 let _ = f.instructions().i32_const(1); 756 let _ = f.instructions().i32_store(mem(0)); 757 // error string 758 let _ = f.instructions().i32_const(RET_AREA); 759 let _ = f.instructions().i32_const(STR_NOT_IMPL); 760 let _ = f.instructions().i32_store(mem(4)); 761 let _ = f.instructions().i32_const(RET_AREA); 762 let _ = f.instructions().i32_const(15); // "not implemented" 763 let _ = f.instructions().i32_store(mem(8)); 764 let _ = f.instructions().i32_const(RET_AREA); 765 let _ = f.instructions().end(); 766 return f; 767 } 768 769 // Fallback zeros for other shapes. 770 for r in &results { 771 match r { 772 ValType::I32 => { 773 let _ = f.instructions().i32_const(0); 774 } 775 ValType::I64 => { 776 let _ = f.instructions().i64_const(0); 777 } 778 ValType::F32 => { 779 let _ = f.instructions().f32_const(0.0.into()); 780 } 781 ValType::F64 => { 782 let _ = f.instructions().f64_const(0.0.into()); 783 } 784 _ => { 785 let _ = f.instructions().i32_const(0); 786 } 787 } 788 } 789 let _ = f.instructions().end(); 790 f 791} 792 793/// Map flat CABI param indices that are resource handles to their drop imports. 794/// Only handles plain `borrow`/`own` (not nested in option/list) for stubs. 795fn resource_handle_drops( 796 resolve: &Resolve, 797 func: &WitFunction, 798 import_index: &HashMap<String, u32>, 799 param_locals: u32, 800) -> Vec<(u32, u32)> { 801 use wit_parser::{Handle, Type, TypeDefKind}; 802 let mut out = Vec::new(); 803 let mut idx = 0u32; 804 for (_name, ty) in &func.params { 805 if let Some(res) = as_plain_handle_resource(resolve, ty) { 806 if idx < param_locals { 807 if let Some(drop_fn) = drop_for_resource(resolve, res, import_index) { 808 out.push((idx, drop_fn)); 809 } 810 } 811 idx += 1; 812 continue; 813 } 814 // Advance by this parameter's flat width (GuestImport flattening is 815 // enough for counting slots; we only emit drops for plain handles). 816 idx = idx.saturating_add(flat_width(resolve, ty)); 817 if idx > param_locals { 818 break; 819 } 820 } 821 out 822} 823 824fn as_plain_handle_resource( 825 resolve: &Resolve, 826 ty: &wit_parser::Type, 827) -> Option<wit_parser::TypeId> { 828 use wit_parser::{Handle, Type, TypeDefKind}; 829 match ty { 830 Type::Id(id) => match &resolve.types[*id].kind { 831 TypeDefKind::Handle(Handle::Borrow(res) | Handle::Own(res)) => Some(*res), 832 TypeDefKind::Type(inner) => as_plain_handle_resource(resolve, inner), 833 _ => None, 834 }, 835 _ => None, 836 } 837} 838 839fn drop_for_resource( 840 resolve: &Resolve, 841 res: wit_parser::TypeId, 842 import_index: &HashMap<String, u32>, 843) -> Option<u32> { 844 let name = resolve.types[res].name.as_deref()?; 845 import_index 846 .get(&format!("[resource-drop]{name}")) 847 .or_else(|| import_index.get(&format!("{name}_drop"))) 848 .copied() 849} 850 851/// Flat core-param width for a single interface type (approx., for index tracking). 852fn flat_width(resolve: &Resolve, ty: &wit_parser::Type) -> u32 { 853 use wit_parser::{Handle, Type, TypeDefKind}; 854 match ty { 855 Type::Bool 856 | Type::U8 857 | Type::U16 858 | Type::U32 859 | Type::S8 860 | Type::S16 861 | Type::S32 862 | Type::Char 863 | Type::F32 864 | Type::ErrorContext => 1, 865 Type::U64 | Type::S64 | Type::F64 => 1, // may be i64 but still one local 866 Type::String => 2, 867 Type::Id(id) => match &resolve.types[*id].kind { 868 TypeDefKind::Handle(_) => 1, 869 TypeDefKind::Resource => 1, 870 TypeDefKind::Enum(_) | TypeDefKind::Flags(_) => 1, 871 TypeDefKind::Type(inner) => flat_width(resolve, inner), 872 TypeDefKind::Option(inner) => 1 + flat_width(resolve, inner), 873 TypeDefKind::Result(r) => { 874 let ok = r.ok.as_ref().map(|t| flat_width(resolve, t)).unwrap_or(0); 875 let err = r.err.as_ref().map(|t| flat_width(resolve, t)).unwrap_or(0); 876 1 + ok.max(err) 877 } 878 TypeDefKind::List(_) => 2, 879 TypeDefKind::Record(r) => r.fields.iter().map(|f| flat_width(resolve, &f.ty)).sum(), 880 TypeDefKind::Tuple(t) => t.types.iter().map(|t| flat_width(resolve, t)).sum(), 881 TypeDefKind::Variant(v) => { 882 1 + v 883 .cases 884 .iter() 885 .map(|c| c.ty.as_ref().map(|t| flat_width(resolve, t)).unwrap_or(0)) 886 .max() 887 .unwrap_or(0) 888 } 889 TypeDefKind::FixedSizeList(inner, n) => flat_width(resolve, inner) * (*n as u32), 890 TypeDefKind::Future(_) | TypeDefKind::Stream(_) => 1, 891 TypeDefKind::Unknown => 1, 892 }, 893 } 894} 895 896/// Public entry used by the CLI / package compiler for zed-extension builds. 897pub fn build_from_mir_modules( 898 modules: Vec<ast::Module<ast::IncompleteType>>, 899 package_name: &str, 900) -> Result<Vec<u8>> { 901 let monomorphized = crate::wasm::mir::lower::lower(modules); 902 let linked = crate::wasm::link_package(monomorphized); 903 compile_extension_component(&linked, package_name) 904} 905 906#[cfg(test)] 907mod tests { 908 use super::*; 909 910 #[test] 911 fn packages_empty_extension_component() { 912 let linked = ast::Module { 913 name: "zed_glint".into(), 914 functions: vec![], 915 }; 916 let bytes = compile_extension_component(&linked, "zed_glint") 917 .expect("component should build"); 918 assert!(bytes.starts_with(&[0x00, 0x61, 0x73, 0x6d, 0x0d, 0x00, 0x01, 0x00])); 919 assert!(bytes.windows(API_VERSION.len()).any(|w| w == API_VERSION)); 920 assert!( 921 bytes.windows(b"zed:api-version".len()).any(|w| w == b"zed:api-version"), 922 "missing zed:api-version section name" 923 ); 924 eprintln!("component size: {}", bytes.len()); 925 } 926}