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

Configure Feed

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

Implement Wasm codegen enough to run under wasmtime.

Lower integers, floats, bools, and strings to Wasm, emit WASI _start that
prints main's result, and add examples/hello_wasm as a sample app.

author
nandi
committer
nandi
date (Jul 26, 2026, 12:13 PM -0700) commit 435cd163 parent 4343dd43 change-id ukklmxkz
+1584 -31
+6 -9
compiler-core/src/dep_tree.rs
··· 5 5 use petgraph::{Direction, algo::Cycle, graph::NodeIndex}; 6 6 use std::collections::{HashMap, HashSet}; 7 7 8 - #[cfg(test)] 9 - use pretty_assertions::assert_eq; 10 - 11 8 /// Take a sequence of values and their deps, and return the values in 12 9 /// order so that deps come before the dependents. 13 10 /// ··· 98 95 #[test] 99 96 fn toposort_deps_test() { 100 97 // All deps are nodes 101 - assert_eq!( 98 + pretty_assertions::assert_eq!( 102 99 toposort_deps(vec![ 103 100 ("a".into(), vec!["b".into()]), 104 101 ("c".into(), vec![]), ··· 108 105 ); 109 106 110 107 // No deps 111 - assert_eq!( 108 + pretty_assertions::assert_eq!( 112 109 toposort_deps(vec![ 113 110 ("no-deps-1".into(), vec![]), 114 111 ("no-deps-2".into(), vec![]) ··· 117 114 ); 118 115 119 116 // Some deps are not nodes (and thus are ignored) 120 - assert_eq!( 117 + pretty_assertions::assert_eq!( 121 118 toposort_deps(vec![ 122 119 ("a".into(), vec!["b".into(), "z".into()]), 123 120 ("b".into(), vec!["x".into()]) ··· 132 129 // ^ | 133 130 // | v 134 131 // +----+ 135 - assert_eq!( 132 + pretty_assertions::assert_eq!( 136 133 toposort_deps(vec![("a".into(), vec!["a".into()])]), 137 134 Err(Error::Cycle(vec!["a".into()])) 138 135 ); ··· 141 138 // ^ v 142 139 // | | 143 140 // +---------+ 144 - assert_eq!( 141 + pretty_assertions::assert_eq!( 145 142 toposort_deps(vec![ 146 143 ("a".into(), vec!["b".into()]), 147 144 ("b".into(), vec!["c".into()]), ··· 154 151 // | | ^ 155 152 // v v | 156 153 // f c -> d 157 - assert_eq!( 154 + pretty_assertions::assert_eq!( 158 155 toposort_deps(vec![ 159 156 ("a".into(), vec!["b".into()]), 160 157 ("b".into(), vec!["c".into()]),
+1392 -3
compiler-core/src/wasm.rs
··· 1 - use crate::wasm::mir::ast; 1 + //! WebAssembly codegen from monomorphised MIR. 2 + //! 3 + //! # Value representation 4 + //! 5 + //! | Gleam type | Wasm | 6 + //! |------------|------| 7 + //! | `Int` | `i64` | 8 + //! | `Bool` | `i32` (`0` / `1`) | 9 + //! | `Float` | `f64` | 10 + //! | `String` | `i32` pointer to `{ len: u32, data: [u8; len] }` in linear memory | 11 + //! 12 + //! String literals are placed in the data segment. Concatenation allocates new 13 + //! objects with a simple bump allocator (`$gleam_heap_ptr` global). 14 + //! 15 + //! # Running with wasmtime 16 + //! 17 + //! If the module defines a zero-argument `main`, the backend also emits a WASI 18 + //! `_start` entry that calls `main` and prints the result to stdout via 19 + //! `wasi_snapshot_preview1::fd_write`. Run with: 20 + //! 21 + //! ```text 22 + //! wasmtime path/to/package.wasm 23 + //! ``` 24 + 25 + use std::collections::HashMap; 26 + 27 + use ecow::EcoString; 28 + use num_bigint::BigInt; 29 + use num_traits::ToPrimitive; 30 + use wasm_encoder::{ 31 + BlockType, CodeSection, ConstExpr, DataSection, EntityType, ExportKind, ExportSection, 32 + Function, FunctionSection, GlobalSection, GlobalType, Ieee64, ImportSection, MemArg, 33 + MemorySection, MemoryType, Module, TypeSection, ValType, 34 + }; 35 + 36 + use crate::wasm::mir::ast::{self, CompleteType, Expression}; 2 37 3 38 pub mod mir; 4 39 5 - pub fn compile(_module: ast::Module<ast::CompleteType>) -> Vec<u8> { 6 - todo!() 40 + /// Bytes reserved at the start of linear memory for WASI iovecs and itoa. 41 + /// Layout: 42 + /// - `0..8` — `ciovec { buf: i32, len: i32 }` 43 + /// - `8..12` — `nwritten: i32` 44 + /// - `16..56` — decimal formatting buffer 45 + const SCRATCH_SIZE: u32 = 64; 46 + const SCRATCH_IOV: u32 = 0; 47 + const SCRATCH_NWRITTEN: u32 = 8; 48 + const SCRATCH_ITOA_END: u32 = 56; 49 + 50 + /// Compile a monomorphised MIR module into a WebAssembly binary. 51 + pub fn compile(module: ast::Module<CompleteType>) -> Vec<u8> { 52 + Compiler::new(&module).compile() 53 + } 54 + 55 + /// Indices of synthesised runtime helpers (only present when strings are used). 56 + struct StringRuntime { 57 + alloc: u32, 58 + concat: u32, 59 + eq: u32, 60 + } 61 + 62 + /// WASI command entry for modules that export a zero-arg `main`. 63 + struct WasiEntry { 64 + /// Imported `fd_write` function index (always 0 when present). 65 + fd_write: u32, 66 + main_index: u32, 67 + main_return: CompleteType, 68 + print_string: u32, 69 + print_i64: u32, 70 + start: u32, 71 + newline_offset: u32, 72 + } 73 + 74 + struct Compiler<'a> { 75 + module: &'a ast::Module<CompleteType>, 76 + /// Function name → Wasm function index (including imports). 77 + function_indices: HashMap<EcoString, u32>, 78 + /// Unique string literal → data offset of its heap object. 79 + string_offsets: HashMap<EcoString, u32>, 80 + /// Raw bytes written into the active data segment (starting at address 0). 81 + static_data: Vec<u8>, 82 + /// First free heap address after static data (4-byte aligned). 83 + heap_start: u32, 84 + /// Offset of the interned empty string (valid pointer for dummies). 85 + empty_string_offset: u32, 86 + string_runtime: Option<StringRuntime>, 87 + wasi_entry: Option<WasiEntry>, 88 + /// Number of imported functions (shifts defined-function indices). 89 + import_func_count: u32, 90 + } 91 + 92 + impl<'a> Compiler<'a> { 93 + fn new(module: &'a ast::Module<CompleteType>) -> Self { 94 + let main = module.functions.iter().find(|function| { 95 + function.name == "main" && function.parameters.is_empty() 96 + }); 97 + let emit_wasi = main.is_some(); 98 + let uses_strings = module_uses_strings(module) || emit_wasi; 99 + 100 + let import_func_count = u32::from(emit_wasi); 101 + let function_indices = module 102 + .functions 103 + .iter() 104 + .enumerate() 105 + .map(|(index, function)| { 106 + ( 107 + function.name.clone(), 108 + import_func_count + index as u32, 109 + ) 110 + }) 111 + .collect(); 112 + 113 + let mut compiler = Self { 114 + module, 115 + function_indices, 116 + string_offsets: HashMap::new(), 117 + static_data: Vec::new(), 118 + heap_start: 0, 119 + empty_string_offset: 0, 120 + string_runtime: None, 121 + wasi_entry: None, 122 + import_func_count, 123 + }; 124 + 125 + if uses_strings || emit_wasi { 126 + // Reserve scratch so string objects never overlap WASI temp space. 127 + compiler.static_data.resize(SCRATCH_SIZE as usize, 0); 128 + } 129 + 130 + // Intern every static string *before* freezing `heap_start`, including 131 + // the WASI newline, so bump-allocation cannot overwrite them. 132 + let mut newline_offset = 0u32; 133 + if uses_strings { 134 + compiler.empty_string_offset = compiler.intern_string(""); 135 + for function in &module.functions { 136 + collect_strings_from_expression(&function.body, &mut |value| { 137 + let _ = compiler.intern_string(value); 138 + }); 139 + } 140 + } 141 + if emit_wasi { 142 + newline_offset = compiler.intern_string("\n"); 143 + } 144 + 145 + if uses_strings || emit_wasi { 146 + compiler.heap_start = align4(compiler.static_data.len() as u32); 147 + } 148 + 149 + if uses_strings { 150 + let base = import_func_count + module.functions.len() as u32; 151 + compiler.string_runtime = Some(StringRuntime { 152 + alloc: base, 153 + concat: base + 1, 154 + eq: base + 2, 155 + }); 156 + } 157 + 158 + if emit_wasi { 159 + let main = main.expect("main checked above"); 160 + let user_count = module.functions.len() as u32; 161 + let mut next = import_func_count + user_count; 162 + if compiler.string_runtime.is_some() { 163 + next += 3; 164 + } 165 + let print_string = next; 166 + let print_i64 = next + 1; 167 + let start = next + 2; 168 + compiler.wasi_entry = Some(WasiEntry { 169 + fd_write: 0, 170 + main_index: *compiler 171 + .function_indices 172 + .get("main") 173 + .expect("main index"), 174 + main_return: main.return_type.clone(), 175 + print_string, 176 + print_i64, 177 + start, 178 + newline_offset, 179 + }); 180 + } 181 + 182 + compiler 183 + } 184 + 185 + fn intern_string(&mut self, value: &str) -> u32 { 186 + if let Some(&offset) = self.string_offsets.get(value) { 187 + return offset; 188 + } 189 + 190 + let offset = align4(self.static_data.len() as u32); 191 + self.static_data.resize(offset as usize, 0); 192 + 193 + let bytes = value.as_bytes(); 194 + let len = bytes.len() as u32; 195 + self.static_data.extend_from_slice(&len.to_le_bytes()); 196 + self.static_data.extend_from_slice(bytes); 197 + 198 + _ = self.string_offsets.insert(value.into(), offset); 199 + offset 200 + } 201 + 202 + fn compile(self) -> Vec<u8> { 203 + let mut wasm = Module::new(); 204 + 205 + let mut types = TypeSection::new(); 206 + let mut imports = ImportSection::new(); 207 + let mut functions = FunctionSection::new(); 208 + let mut exports = ExportSection::new(); 209 + let mut codes = CodeSection::new(); 210 + 211 + // Predeclare the fd_write type if we need WASI (type index 0). 212 + let fd_write_type = if self.wasi_entry.is_some() { 213 + let _ = types.ty().function( 214 + vec![ValType::I32, ValType::I32, ValType::I32, ValType::I32], 215 + vec![ValType::I32], 216 + ); 217 + Some(0u32) 218 + } else { 219 + None 220 + }; 221 + 222 + // User function types (type indices continue after fd_write type). 223 + let user_type_base = u32::from(fd_write_type.is_some()); 224 + for (index, function) in self.module.functions.iter().enumerate() { 225 + let params: Vec<ValType> = function 226 + .parameters 227 + .iter() 228 + .map(|parameter| val_type(&parameter.type_)) 229 + .collect(); 230 + let results = vec![val_type(&function.return_type)]; 231 + let _ = types.ty().function(params, results); 232 + let type_index = user_type_base + index as u32; 233 + let _ = functions.function(type_index); 234 + let wasm_index = self.import_func_count + index as u32; 235 + let _ = exports.export(&function.name, ExportKind::Func, wasm_index); 236 + let _ = codes.function(&self.compile_function(function)); 237 + } 238 + 239 + if let Some(fd_type) = fd_write_type { 240 + let _ = imports.import( 241 + "wasi_snapshot_preview1", 242 + "fd_write", 243 + EntityType::Function(fd_type), 244 + ); 245 + } 246 + 247 + // String runtime helpers. 248 + if let Some(runtime) = &self.string_runtime { 249 + let alloc_ty = types.len(); 250 + let _ = types.ty().function(vec![ValType::I32], vec![ValType::I32]); 251 + let _ = functions.function(alloc_ty); 252 + 253 + let concat_ty = types.len(); 254 + let _ = types 255 + .ty() 256 + .function(vec![ValType::I32, ValType::I32], vec![ValType::I32]); 257 + let _ = functions.function(concat_ty); 258 + 259 + let eq_ty = types.len(); 260 + let _ = types 261 + .ty() 262 + .function(vec![ValType::I32, ValType::I32], vec![ValType::I32]); 263 + let _ = functions.function(eq_ty); 264 + 265 + let _ = codes.function(&compile_alloc()); 266 + let _ = codes.function(&compile_string_concat(runtime.alloc)); 267 + let _ = codes.function(&compile_string_eq()); 268 + } 269 + 270 + // WASI print helpers + _start. 271 + if let Some(entry) = &self.wasi_entry { 272 + let print_string_ty = types.len(); 273 + let _ = types.ty().function(vec![ValType::I32], vec![]); 274 + let _ = functions.function(print_string_ty); 275 + 276 + let print_i64_ty = types.len(); 277 + let _ = types.ty().function(vec![ValType::I64], vec![]); 278 + let _ = functions.function(print_i64_ty); 279 + 280 + let start_ty = types.len(); 281 + let _ = types.ty().function(vec![], vec![]); 282 + let _ = functions.function(start_ty); 283 + 284 + let _ = codes.function(&compile_print_string(entry.fd_write)); 285 + let _ = codes.function(&compile_print_i64(entry.fd_write)); 286 + let _ = codes.function(&compile_start(entry)); 287 + let _ = exports.export("_start", ExportKind::Func, entry.start); 288 + } 289 + 290 + let needs_memory = self.string_runtime.is_some() || self.wasi_entry.is_some(); 291 + 292 + let _ = wasm.section(&types); 293 + if !imports.is_empty() { 294 + let _ = wasm.section(&imports); 295 + } 296 + let _ = wasm.section(&functions); 297 + 298 + if needs_memory { 299 + let mut memories = MemorySection::new(); 300 + let _ = memories.memory(MemoryType { 301 + minimum: 1, 302 + maximum: None, 303 + memory64: false, 304 + shared: false, 305 + page_size_log2: None, 306 + }); 307 + let _ = wasm.section(&memories); 308 + 309 + if self.string_runtime.is_some() { 310 + let mut globals = GlobalSection::new(); 311 + let _ = globals.global( 312 + GlobalType { 313 + val_type: ValType::I32, 314 + mutable: true, 315 + shared: false, 316 + }, 317 + &ConstExpr::i32_const(self.heap_start as i32), 318 + ); 319 + let _ = wasm.section(&globals); 320 + } 321 + 322 + let _ = exports.export("memory", ExportKind::Memory, 0); 323 + } 324 + 325 + let _ = wasm.section(&exports); 326 + let _ = wasm.section(&codes); 327 + 328 + if needs_memory && !self.static_data.is_empty() { 329 + let mut data = DataSection::new(); 330 + let _ = data.active( 331 + 0, 332 + &ConstExpr::i32_const(0), 333 + self.static_data.iter().copied(), 334 + ); 335 + let _ = wasm.section(&data); 336 + } 337 + 338 + wasm.finish() 339 + } 340 + 341 + fn compile_function(&self, function: &ast::Function<CompleteType>) -> Function { 342 + let mut locals = LocalAllocator::new(function); 343 + locals.collect_from_expression(&function.body); 344 + 345 + let mut body = Function::new_with_locals_types(locals.extra_locals.iter().copied()); 346 + self.emit_expression(&mut body, &mut locals, &function.body); 347 + let _ = body.instructions().end(); 348 + body 349 + } 350 + 351 + fn emit_expression( 352 + &self, 353 + body: &mut Function, 354 + locals: &mut LocalAllocator, 355 + expression: &Expression<CompleteType>, 356 + ) { 357 + match expression { 358 + Expression::Block(expressions) => { 359 + assert!( 360 + !expressions.is_empty(), 361 + "MIR blocks must contain at least one expression" 362 + ); 363 + let last = expressions.len() - 1; 364 + for (index, expression) in expressions.iter().enumerate() { 365 + match expression { 366 + Expression::Set { name, value } => { 367 + self.emit_expression(body, locals, value); 368 + let local = locals.index(&name.name); 369 + if index == last { 370 + let _ = body.instructions().local_tee(local); 371 + } else { 372 + let _ = body.instructions().local_set(local); 373 + } 374 + } 375 + other => { 376 + self.emit_expression(body, locals, other); 377 + if index != last { 378 + let _ = body.instructions().drop(); 379 + } 380 + } 381 + } 382 + } 383 + } 384 + 385 + Expression::FunctionRef { .. } => { 386 + panic!("bare function references are not supported in Wasm yet") 387 + } 388 + 389 + Expression::Var(var) => { 390 + let local = locals.index(&var.name); 391 + let _ = body.instructions().local_get(local); 392 + } 393 + 394 + Expression::Int { value } => { 395 + let _ = body.instructions().i64_const(bigint_to_i64(value)); 396 + } 397 + 398 + Expression::Float { value } => { 399 + let float: f64 = value 400 + .parse() 401 + .unwrap_or_else(|_| panic!("invalid float literal: {value}")); 402 + let _ = body.instructions().f64_const(Ieee64::from(float)); 403 + } 404 + 405 + Expression::Bool { value } => { 406 + let _ = body.instructions().i32_const(i32::from(*value)); 407 + } 408 + 409 + Expression::String { value } => { 410 + let offset = self 411 + .string_offsets 412 + .get(value) 413 + .unwrap_or_else(|| panic!("string literal was not interned: {value:?}")); 414 + let _ = body.instructions().i32_const(*offset as i32); 415 + } 416 + 417 + Expression::Equals { lhs, rhs } => { 418 + self.emit_eq(body, locals, lhs, rhs, true); 419 + } 420 + 421 + Expression::NotEquals { lhs, rhs } => { 422 + self.emit_eq(body, locals, lhs, rhs, false); 423 + } 424 + 425 + Expression::IntGt { lhs, rhs } => { 426 + self.emit_binary(body, locals, lhs, rhs, |b| { 427 + let _ = b.instructions().i64_gt_s(); 428 + }); 429 + } 430 + Expression::IntGtEq { lhs, rhs } => { 431 + self.emit_binary(body, locals, lhs, rhs, |b| { 432 + let _ = b.instructions().i64_ge_s(); 433 + }); 434 + } 435 + Expression::IntLt { lhs, rhs } => { 436 + self.emit_binary(body, locals, lhs, rhs, |b| { 437 + let _ = b.instructions().i64_lt_s(); 438 + }); 439 + } 440 + Expression::IntLtEq { lhs, rhs } => { 441 + self.emit_binary(body, locals, lhs, rhs, |b| { 442 + let _ = b.instructions().i64_le_s(); 443 + }); 444 + } 445 + Expression::IntAdd { lhs, rhs } => { 446 + self.emit_binary(body, locals, lhs, rhs, |b| { 447 + let _ = b.instructions().i64_add(); 448 + }); 449 + } 450 + Expression::IntSub { lhs, rhs } => { 451 + self.emit_binary(body, locals, lhs, rhs, |b| { 452 + let _ = b.instructions().i64_sub(); 453 + }); 454 + } 455 + Expression::IntMul { lhs, rhs } => { 456 + self.emit_binary(body, locals, lhs, rhs, |b| { 457 + let _ = b.instructions().i64_mul(); 458 + }); 459 + } 460 + Expression::IntDiv { lhs, rhs } => { 461 + // Gleam integer division truncates toward zero. 462 + self.emit_binary(body, locals, lhs, rhs, |b| { 463 + let _ = b.instructions().i64_div_s(); 464 + }); 465 + } 466 + Expression::IntRem { lhs, rhs } => { 467 + self.emit_binary(body, locals, lhs, rhs, |b| { 468 + let _ = b.instructions().i64_rem_s(); 469 + }); 470 + } 471 + 472 + Expression::FloatGt { lhs, rhs } => { 473 + self.emit_binary(body, locals, lhs, rhs, |b| { 474 + let _ = b.instructions().f64_gt(); 475 + }); 476 + } 477 + Expression::FloatGtEq { lhs, rhs } => { 478 + self.emit_binary(body, locals, lhs, rhs, |b| { 479 + let _ = b.instructions().f64_ge(); 480 + }); 481 + } 482 + Expression::FloatLt { lhs, rhs } => { 483 + self.emit_binary(body, locals, lhs, rhs, |b| { 484 + let _ = b.instructions().f64_lt(); 485 + }); 486 + } 487 + Expression::FloatLtEq { lhs, rhs } => { 488 + self.emit_binary(body, locals, lhs, rhs, |b| { 489 + let _ = b.instructions().f64_le(); 490 + }); 491 + } 492 + Expression::FloatAdd { lhs, rhs } => { 493 + self.emit_binary(body, locals, lhs, rhs, |b| { 494 + let _ = b.instructions().f64_add(); 495 + }); 496 + } 497 + Expression::FloatSub { lhs, rhs } => { 498 + self.emit_binary(body, locals, lhs, rhs, |b| { 499 + let _ = b.instructions().f64_sub(); 500 + }); 501 + } 502 + Expression::FloatMul { lhs, rhs } => { 503 + self.emit_binary(body, locals, lhs, rhs, |b| { 504 + let _ = b.instructions().f64_mul(); 505 + }); 506 + } 507 + Expression::FloatDiv { lhs, rhs } => { 508 + self.emit_binary(body, locals, lhs, rhs, |b| { 509 + let _ = b.instructions().f64_div(); 510 + }); 511 + } 512 + 513 + Expression::StringConcat { lhs, rhs } => { 514 + let runtime = self 515 + .string_runtime 516 + .as_ref() 517 + .expect("string runtime required for concatenation"); 518 + self.emit_expression(body, locals, lhs); 519 + self.emit_expression(body, locals, rhs); 520 + let _ = body.instructions().call(runtime.concat); 521 + } 522 + 523 + Expression::List { .. } 524 + | Expression::Tuple { .. } 525 + | Expression::TupleAccess { .. } 526 + | Expression::Struct { .. } 527 + | Expression::StructTag { .. } 528 + | Expression::StructAccess { .. } => { 529 + panic!("compound values are not supported in Wasm yet") 530 + } 531 + 532 + Expression::Set { name, value } => { 533 + // Standalone set used as an expression value. 534 + self.emit_expression(body, locals, value); 535 + let local = locals.index(&name.name); 536 + let _ = body.instructions().local_tee(local); 537 + } 538 + 539 + Expression::If { cond, then, else_ } => { 540 + self.emit_expression(body, locals, cond); 541 + let result_type = block_type(&then.type_()); 542 + let _ = body.instructions().if_(result_type); 543 + self.emit_expression(body, locals, then); 544 + let _ = body.instructions().else_(); 545 + self.emit_expression(body, locals, else_); 546 + let _ = body.instructions().end(); 547 + } 548 + 549 + Expression::Call { 550 + target, 551 + args, 552 + type_: _, 553 + } => { 554 + for arg in args { 555 + self.emit_expression(body, locals, arg); 556 + } 557 + 558 + match target.as_ref() { 559 + Expression::FunctionRef { 560 + module: _, 561 + name, 562 + arity: _, 563 + type_: _, 564 + } => { 565 + let index = self.function_indices.get(name).unwrap_or_else(|| { 566 + panic!("unknown function `{name}` in module `{}`", self.module.name) 567 + }); 568 + let _ = body.instructions().call(*index); 569 + } 570 + _ => panic!("indirect calls are not supported in Wasm yet"), 571 + } 572 + } 573 + 574 + Expression::Panic { type_ } => { 575 + let _ = body.instructions().unreachable(); 576 + // Keep the stack typed for surrounding expressions. 577 + push_dummy_value(body, type_); 578 + } 579 + } 580 + } 581 + 582 + fn emit_binary( 583 + &self, 584 + body: &mut Function, 585 + locals: &mut LocalAllocator, 586 + lhs: &Expression<CompleteType>, 587 + rhs: &Expression<CompleteType>, 588 + op: impl FnOnce(&mut Function), 589 + ) { 590 + self.emit_expression(body, locals, lhs); 591 + self.emit_expression(body, locals, rhs); 592 + op(body); 593 + } 594 + 595 + fn emit_eq( 596 + &self, 597 + body: &mut Function, 598 + locals: &mut LocalAllocator, 599 + lhs: &Expression<CompleteType>, 600 + rhs: &Expression<CompleteType>, 601 + equal: bool, 602 + ) { 603 + self.emit_expression(body, locals, lhs); 604 + self.emit_expression(body, locals, rhs); 605 + match lhs.type_() { 606 + CompleteType::Int => { 607 + if equal { 608 + let _ = body.instructions().i64_eq(); 609 + } else { 610 + let _ = body.instructions().i64_ne(); 611 + } 612 + } 613 + CompleteType::Bool => { 614 + if equal { 615 + let _ = body.instructions().i32_eq(); 616 + } else { 617 + let _ = body.instructions().i32_ne(); 618 + } 619 + } 620 + CompleteType::Float => { 621 + if equal { 622 + let _ = body.instructions().f64_eq(); 623 + } else { 624 + let _ = body.instructions().f64_ne(); 625 + } 626 + } 627 + CompleteType::String => { 628 + let runtime = self 629 + .string_runtime 630 + .as_ref() 631 + .expect("string runtime required for string equality"); 632 + let _ = body.instructions().call(runtime.eq); 633 + if !equal { 634 + let _ = body.instructions().i32_eqz(); 635 + } 636 + } 637 + other => panic!("equality is not supported for type {other:?}"), 638 + } 639 + } 640 + } 641 + 642 + // --------------------------------------------------------------------------- 643 + // Runtime helpers 644 + // --------------------------------------------------------------------------- 645 + 646 + const HEAP_GLOBAL: u32 = 0; 647 + 648 + fn mem_arg(offset: u64, align: u32) -> MemArg { 649 + MemArg { 650 + offset, 651 + align, 652 + memory_index: 0, 653 + } 654 + } 655 + 656 + /// `print_string(s)` — write a Gleam string to stdout via WASI `fd_write`. 657 + fn compile_print_string(fd_write: u32) -> Function { 658 + // param: s=0 659 + let mut f = Function::new([]); 660 + // iov.buf = s + 4 661 + let _ = f.instructions().i32_const(SCRATCH_IOV as i32); 662 + let _ = f.instructions().local_get(0); 663 + let _ = f.instructions().i32_const(4); 664 + let _ = f.instructions().i32_add(); 665 + let _ = f.instructions().i32_store(mem_arg(0, 2)); 666 + // iov.len = *s 667 + let _ = f.instructions().i32_const(SCRATCH_IOV as i32); 668 + let _ = f.instructions().local_get(0); 669 + let _ = f.instructions().i32_load(mem_arg(0, 2)); 670 + let _ = f.instructions().i32_store(mem_arg(4, 2)); 671 + // fd_write(1, iov, 1, &nwritten) 672 + let _ = f.instructions().i32_const(1); 673 + let _ = f.instructions().i32_const(SCRATCH_IOV as i32); 674 + let _ = f.instructions().i32_const(1); 675 + let _ = f.instructions().i32_const(SCRATCH_NWRITTEN as i32); 676 + let _ = f.instructions().call(fd_write); 677 + let _ = f.instructions().drop(); 678 + let _ = f.instructions().end(); 679 + f 680 + } 681 + 682 + /// `print_i64(n)` — write a signed decimal integer to stdout. 683 + fn compile_print_i64(fd_write: u32) -> Function { 684 + // param: n=0 (i64) 685 + // locals: i=1 (i32 cursor), neg=2 (i32), tmp=3 (i64) 686 + let mut f = Function::new_with_locals_types([ValType::I32, ValType::I32, ValType::I64]); 687 + 688 + // i = ITOA_END 689 + let _ = f.instructions().i32_const(SCRATCH_ITOA_END as i32); 690 + let _ = f.instructions().local_set(1); 691 + // neg = 0; tmp = n 692 + let _ = f.instructions().i32_const(0); 693 + let _ = f.instructions().local_set(2); 694 + let _ = f.instructions().local_get(0); 695 + let _ = f.instructions().local_set(3); 696 + 697 + // if n < 0: neg = 1; tmp = -n 698 + let _ = f.instructions().local_get(0); 699 + let _ = f.instructions().i64_const(0); 700 + let _ = f.instructions().i64_lt_s(); 701 + let _ = f.instructions().if_(BlockType::Empty); 702 + let _ = f.instructions().i32_const(1); 703 + let _ = f.instructions().local_set(2); 704 + let _ = f.instructions().i64_const(0); 705 + let _ = f.instructions().local_get(0); 706 + let _ = f.instructions().i64_sub(); 707 + let _ = f.instructions().local_set(3); 708 + let _ = f.instructions().end(); 709 + 710 + // do { write digit; tmp /= 10 } while tmp != 0 711 + let _ = f.instructions().loop_(BlockType::Empty); 712 + // i -= 1 713 + let _ = f.instructions().local_get(1); 714 + let _ = f.instructions().i32_const(1); 715 + let _ = f.instructions().i32_sub(); 716 + let _ = f.instructions().local_set(1); 717 + // *i = '0' + (tmp % 10) 718 + let _ = f.instructions().local_get(1); 719 + let _ = f.instructions().local_get(3); 720 + let _ = f.instructions().i64_const(10); 721 + let _ = f.instructions().i64_rem_u(); 722 + let _ = f.instructions().i32_wrap_i64(); 723 + let _ = f.instructions().i32_const(b'0' as i32); 724 + let _ = f.instructions().i32_add(); 725 + let _ = f.instructions().i32_store8(mem_arg(0, 0)); 726 + // tmp /= 10 727 + let _ = f.instructions().local_get(3); 728 + let _ = f.instructions().i64_const(10); 729 + let _ = f.instructions().i64_div_u(); 730 + let _ = f.instructions().local_set(3); 731 + // continue while tmp != 0 732 + let _ = f.instructions().local_get(3); 733 + let _ = f.instructions().i64_const(0); 734 + let _ = f.instructions().i64_ne(); 735 + let _ = f.instructions().br_if(0); 736 + let _ = f.instructions().end(); // loop 737 + 738 + // if neg: prepend '-' 739 + let _ = f.instructions().local_get(2); 740 + let _ = f.instructions().if_(BlockType::Empty); 741 + let _ = f.instructions().local_get(1); 742 + let _ = f.instructions().i32_const(1); 743 + let _ = f.instructions().i32_sub(); 744 + let _ = f.instructions().local_tee(1); 745 + let _ = f.instructions().i32_const(b'-' as i32); 746 + let _ = f.instructions().i32_store8(mem_arg(0, 0)); 747 + let _ = f.instructions().end(); 748 + 749 + // iov.buf = i; iov.len = ITOA_END - i 750 + let _ = f.instructions().i32_const(SCRATCH_IOV as i32); 751 + let _ = f.instructions().local_get(1); 752 + let _ = f.instructions().i32_store(mem_arg(0, 2)); 753 + let _ = f.instructions().i32_const(SCRATCH_IOV as i32); 754 + let _ = f.instructions().i32_const(SCRATCH_ITOA_END as i32); 755 + let _ = f.instructions().local_get(1); 756 + let _ = f.instructions().i32_sub(); 757 + let _ = f.instructions().i32_store(mem_arg(4, 2)); 758 + // fd_write(1, ...) 759 + let _ = f.instructions().i32_const(1); 760 + let _ = f.instructions().i32_const(SCRATCH_IOV as i32); 761 + let _ = f.instructions().i32_const(1); 762 + let _ = f.instructions().i32_const(SCRATCH_NWRITTEN as i32); 763 + let _ = f.instructions().call(fd_write); 764 + let _ = f.instructions().drop(); 765 + let _ = f.instructions().end(); 766 + f 767 + } 768 + 769 + /// `_start` — call `main`, print the result, print a newline. 770 + fn compile_start(entry: &WasiEntry) -> Function { 771 + let mut f = Function::new([]); 772 + let _ = f.instructions().call(entry.main_index); 773 + match entry.main_return { 774 + CompleteType::String => { 775 + let _ = f.instructions().call(entry.print_string); 776 + } 777 + CompleteType::Int => { 778 + let _ = f.instructions().call(entry.print_i64); 779 + } 780 + CompleteType::Bool => { 781 + // Print 0 / 1 as decimal. 782 + let _ = f.instructions().i64_extend_i32_u(); 783 + let _ = f.instructions().call(entry.print_i64); 784 + } 785 + CompleteType::Float => { 786 + // Drop the float; print a placeholder. 787 + let _ = f.instructions().drop(); 788 + // Fall through to newline only. 789 + } 790 + _ => { 791 + // Unsupported return: drop if needed is already on stack as value. 792 + let _ = f.instructions().drop(); 793 + } 794 + } 795 + // print newline 796 + let _ = f.instructions().i32_const(entry.newline_offset as i32); 797 + let _ = f.instructions().call(entry.print_string); 798 + let _ = f.instructions().end(); 799 + f 800 + } 801 + 802 + /// `alloc(size) -> ptr` — bump-allocate `size` bytes, 4-byte aligned. 803 + fn compile_alloc() -> Function { 804 + // locals: $ptr 805 + let mut f = Function::new_with_locals_types([ValType::I32]); 806 + // ptr = heap 807 + let _ = f.instructions().global_get(HEAP_GLOBAL); 808 + let _ = f.instructions().local_set(1); 809 + // heap = (heap + size + 3) & !3 810 + let _ = f.instructions().global_get(HEAP_GLOBAL); 811 + let _ = f.instructions().local_get(0); 812 + let _ = f.instructions().i32_add(); 813 + let _ = f.instructions().i32_const(3); 814 + let _ = f.instructions().i32_add(); 815 + let _ = f.instructions().i32_const(-4); 816 + let _ = f.instructions().i32_and(); 817 + let _ = f.instructions().global_set(HEAP_GLOBAL); 818 + // return ptr 819 + let _ = f.instructions().local_get(1); 820 + let _ = f.instructions().end(); 821 + f 822 + } 823 + 824 + /// `string_concat(a, b) -> ptr` 825 + fn compile_string_concat(alloc_index: u32) -> Function { 826 + // params: a=0, b=1 827 + // locals: la=2, lb=3, result=4, i=5 828 + let mut f = Function::new_with_locals_types([ 829 + ValType::I32, 830 + ValType::I32, 831 + ValType::I32, 832 + ValType::I32, 833 + ]); 834 + 835 + // la = *a 836 + let _ = f.instructions().local_get(0); 837 + let _ = f.instructions().i32_load(mem_arg(0, 2)); 838 + let _ = f.instructions().local_set(2); 839 + // lb = *b 840 + let _ = f.instructions().local_get(1); 841 + let _ = f.instructions().i32_load(mem_arg(0, 2)); 842 + let _ = f.instructions().local_set(3); 843 + // result = alloc(4 + la + lb) 844 + let _ = f.instructions().local_get(2); 845 + let _ = f.instructions().local_get(3); 846 + let _ = f.instructions().i32_add(); 847 + let _ = f.instructions().i32_const(4); 848 + let _ = f.instructions().i32_add(); 849 + let _ = f.instructions().call(alloc_index); 850 + let _ = f.instructions().local_set(4); 851 + // *result = la + lb 852 + let _ = f.instructions().local_get(4); 853 + let _ = f.instructions().local_get(2); 854 + let _ = f.instructions().local_get(3); 855 + let _ = f.instructions().i32_add(); 856 + let _ = f.instructions().i32_store(mem_arg(0, 2)); 857 + 858 + // copy a → result+4 859 + emit_memcpy(&mut f, /*dest_base*/ 4, /*src_param*/ 0, /*len_local*/ 2, /*i*/ 5); 860 + // copy b → result+4+la 861 + // i = 0 862 + let _ = f.instructions().i32_const(0); 863 + let _ = f.instructions().local_set(5); 864 + // loop 865 + let _ = f.instructions().loop_(BlockType::Empty); 866 + // if i >= lb: break via br to outer... use if 867 + let _ = f.instructions().local_get(5); 868 + let _ = f.instructions().local_get(3); 869 + let _ = f.instructions().i32_lt_u(); 870 + let _ = f.instructions().if_(BlockType::Empty); 871 + // dest = result + 4 + la + i 872 + let _ = f.instructions().local_get(4); 873 + let _ = f.instructions().local_get(2); 874 + let _ = f.instructions().i32_add(); 875 + let _ = f.instructions().local_get(5); 876 + let _ = f.instructions().i32_add(); 877 + // src byte = *(b + 4 + i) 878 + let _ = f.instructions().local_get(1); 879 + let _ = f.instructions().local_get(5); 880 + let _ = f.instructions().i32_add(); 881 + let _ = f.instructions().i32_load8_u(mem_arg(4, 0)); 882 + let _ = f.instructions().i32_store8(mem_arg(4, 0)); 883 + // i += 1 884 + let _ = f.instructions().local_get(5); 885 + let _ = f.instructions().i32_const(1); 886 + let _ = f.instructions().i32_add(); 887 + let _ = f.instructions().local_set(5); 888 + let _ = f.instructions().br(1); // continue loop 889 + let _ = f.instructions().end(); // if 890 + let _ = f.instructions().end(); // loop 891 + 892 + let _ = f.instructions().local_get(4); 893 + let _ = f.instructions().end(); 894 + f 895 + } 896 + 897 + /// Copy `len` bytes from `src_param+4` to `result_local+4`. 898 + /// Uses locals: i for index. 899 + fn emit_memcpy(f: &mut Function, result_local: u32, src_param: u32, len_local: u32, i_local: u32) { 900 + let _ = f.instructions().i32_const(0); 901 + let _ = f.instructions().local_set(i_local); 902 + let _ = f.instructions().loop_(BlockType::Empty); 903 + let _ = f.instructions().local_get(i_local); 904 + let _ = f.instructions().local_get(len_local); 905 + let _ = f.instructions().i32_lt_u(); 906 + let _ = f.instructions().if_(BlockType::Empty); 907 + // dest address = result + i (store8 uses offset 4) 908 + let _ = f.instructions().local_get(result_local); 909 + let _ = f.instructions().local_get(i_local); 910 + let _ = f.instructions().i32_add(); 911 + // src byte 912 + let _ = f.instructions().local_get(src_param); 913 + let _ = f.instructions().local_get(i_local); 914 + let _ = f.instructions().i32_add(); 915 + let _ = f.instructions().i32_load8_u(mem_arg(4, 0)); 916 + let _ = f.instructions().i32_store8(mem_arg(4, 0)); 917 + // i += 1 918 + let _ = f.instructions().local_get(i_local); 919 + let _ = f.instructions().i32_const(1); 920 + let _ = f.instructions().i32_add(); 921 + let _ = f.instructions().local_set(i_local); 922 + let _ = f.instructions().br(1); 923 + let _ = f.instructions().end(); // if 924 + let _ = f.instructions().end(); // loop 925 + } 926 + 927 + /// `string_eq(a, b) -> i32` (1 if equal, 0 otherwise). 928 + fn compile_string_eq() -> Function { 929 + // params: a=0, b=1 930 + // locals: la=2, i=3 931 + let mut f = Function::new_with_locals_types([ValType::I32, ValType::I32]); 932 + 933 + // block $ret (result i32) 934 + let _ = f.instructions().block(BlockType::Result(ValType::I32)); 935 + 936 + // if a == b: return 1 937 + let _ = f.instructions().local_get(0); 938 + let _ = f.instructions().local_get(1); 939 + let _ = f.instructions().i32_eq(); 940 + let _ = f.instructions().if_(BlockType::Empty); 941 + let _ = f.instructions().i32_const(1); 942 + let _ = f.instructions().br(1); // br $ret 943 + let _ = f.instructions().end(); 944 + 945 + // la = *a 946 + let _ = f.instructions().local_get(0); 947 + let _ = f.instructions().i32_load(mem_arg(0, 2)); 948 + let _ = f.instructions().local_tee(2); 949 + // if la != *b: return 0 950 + let _ = f.instructions().local_get(1); 951 + let _ = f.instructions().i32_load(mem_arg(0, 2)); 952 + let _ = f.instructions().i32_ne(); 953 + let _ = f.instructions().if_(BlockType::Empty); 954 + let _ = f.instructions().i32_const(0); 955 + let _ = f.instructions().br(1); 956 + let _ = f.instructions().end(); 957 + 958 + // i = 0 959 + let _ = f.instructions().i32_const(0); 960 + let _ = f.instructions().local_set(3); 961 + let _ = f.instructions().loop_(BlockType::Empty); 962 + // if i >= la: return 1 963 + let _ = f.instructions().local_get(3); 964 + let _ = f.instructions().local_get(2); 965 + let _ = f.instructions().i32_ge_u(); 966 + let _ = f.instructions().if_(BlockType::Empty); 967 + let _ = f.instructions().i32_const(1); 968 + let _ = f.instructions().br(2); // br $ret 969 + let _ = f.instructions().end(); 970 + // if bytes differ: return 0 971 + let _ = f.instructions().local_get(0); 972 + let _ = f.instructions().local_get(3); 973 + let _ = f.instructions().i32_add(); 974 + let _ = f.instructions().i32_load8_u(mem_arg(4, 0)); 975 + let _ = f.instructions().local_get(1); 976 + let _ = f.instructions().local_get(3); 977 + let _ = f.instructions().i32_add(); 978 + let _ = f.instructions().i32_load8_u(mem_arg(4, 0)); 979 + let _ = f.instructions().i32_ne(); 980 + let _ = f.instructions().if_(BlockType::Empty); 981 + let _ = f.instructions().i32_const(0); 982 + let _ = f.instructions().br(2); // br $ret 983 + let _ = f.instructions().end(); 984 + // i += 1; continue 985 + let _ = f.instructions().local_get(3); 986 + let _ = f.instructions().i32_const(1); 987 + let _ = f.instructions().i32_add(); 988 + let _ = f.instructions().local_set(3); 989 + let _ = f.instructions().br(0); 990 + let _ = f.instructions().end(); // loop 991 + 992 + let _ = f.instructions().unreachable(); 993 + let _ = f.instructions().end(); // block $ret 994 + let _ = f.instructions().end(); 995 + f 996 + } 997 + 998 + // --------------------------------------------------------------------------- 999 + // Locals 1000 + // --------------------------------------------------------------------------- 1001 + 1002 + struct LocalAllocator { 1003 + /// Parameter + local name → local index. 1004 + names: HashMap<EcoString, u32>, 1005 + /// Types of non-parameter locals, in declaration order. 1006 + extra_locals: Vec<ValType>, 1007 + next_index: u32, 1008 + } 1009 + 1010 + impl LocalAllocator { 1011 + fn new(function: &ast::Function<CompleteType>) -> Self { 1012 + let mut names = HashMap::new(); 1013 + let mut next_index = 0; 1014 + 1015 + for parameter in &function.parameters { 1016 + if let Some(name) = &parameter.name { 1017 + _ = names.insert(name.clone(), next_index); 1018 + } 1019 + next_index += 1; 1020 + } 1021 + 1022 + Self { 1023 + names, 1024 + extra_locals: Vec::new(), 1025 + next_index, 1026 + } 1027 + } 1028 + 1029 + fn collect_from_expression(&mut self, expression: &Expression<CompleteType>) { 1030 + match expression { 1031 + Expression::Block(expressions) => { 1032 + for expression in expressions { 1033 + self.collect_from_expression(expression); 1034 + } 1035 + } 1036 + Expression::Set { name, value } => { 1037 + self.ensure_local(&name.name, &name.type_); 1038 + self.collect_from_expression(value); 1039 + } 1040 + Expression::Equals { lhs, rhs } 1041 + | Expression::NotEquals { lhs, rhs } 1042 + | Expression::IntGt { lhs, rhs } 1043 + | Expression::IntGtEq { lhs, rhs } 1044 + | Expression::IntLt { lhs, rhs } 1045 + | Expression::IntLtEq { lhs, rhs } 1046 + | Expression::IntAdd { lhs, rhs } 1047 + | Expression::IntSub { lhs, rhs } 1048 + | Expression::IntMul { lhs, rhs } 1049 + | Expression::IntDiv { lhs, rhs } 1050 + | Expression::IntRem { lhs, rhs } 1051 + | Expression::FloatGt { lhs, rhs } 1052 + | Expression::FloatGtEq { lhs, rhs } 1053 + | Expression::FloatLt { lhs, rhs } 1054 + | Expression::FloatLtEq { lhs, rhs } 1055 + | Expression::FloatAdd { lhs, rhs } 1056 + | Expression::FloatSub { lhs, rhs } 1057 + | Expression::FloatMul { lhs, rhs } 1058 + | Expression::FloatDiv { lhs, rhs } 1059 + | Expression::StringConcat { lhs, rhs } => { 1060 + self.collect_from_expression(lhs); 1061 + self.collect_from_expression(rhs); 1062 + } 1063 + Expression::If { cond, then, else_ } => { 1064 + self.collect_from_expression(cond); 1065 + self.collect_from_expression(then); 1066 + self.collect_from_expression(else_); 1067 + } 1068 + Expression::Call { target, args, .. } => { 1069 + self.collect_from_expression(target); 1070 + for arg in args { 1071 + self.collect_from_expression(arg); 1072 + } 1073 + } 1074 + Expression::List { items, tail, .. } => { 1075 + for item in items { 1076 + self.collect_from_expression(item); 1077 + } 1078 + if let Some(tail) = tail { 1079 + self.collect_from_expression(tail); 1080 + } 1081 + } 1082 + Expression::Tuple { items, .. } | Expression::Struct { items, .. } => { 1083 + for item in items { 1084 + self.collect_from_expression(item); 1085 + } 1086 + } 1087 + Expression::TupleAccess { value, .. } 1088 + | Expression::StructTag { value } 1089 + | Expression::StructAccess { value, .. } => { 1090 + self.collect_from_expression(value); 1091 + } 1092 + Expression::FunctionRef { .. } 1093 + | Expression::Var(_) 1094 + | Expression::Int { .. } 1095 + | Expression::Float { .. } 1096 + | Expression::Bool { .. } 1097 + | Expression::String { .. } 1098 + | Expression::Panic { .. } => {} 1099 + } 1100 + } 1101 + 1102 + fn ensure_local(&mut self, name: &EcoString, type_: &CompleteType) { 1103 + if self.names.contains_key(name) { 1104 + return; 1105 + } 1106 + _ = self.names.insert(name.clone(), self.next_index); 1107 + self.extra_locals.push(val_type(type_)); 1108 + self.next_index += 1; 1109 + } 1110 + 1111 + fn index(&self, name: &EcoString) -> u32 { 1112 + *self 1113 + .names 1114 + .get(name) 1115 + .unwrap_or_else(|| panic!("unknown local `{name}`")) 1116 + } 1117 + } 1118 + 1119 + // --------------------------------------------------------------------------- 1120 + // Helpers 1121 + // --------------------------------------------------------------------------- 1122 + 1123 + fn val_type(type_: &CompleteType) -> ValType { 1124 + match type_ { 1125 + CompleteType::Int => ValType::I64, 1126 + CompleteType::Bool => ValType::I32, 1127 + CompleteType::Float => ValType::F64, 1128 + CompleteType::String => ValType::I32, 1129 + CompleteType::Func { .. } 1130 + | CompleteType::Tuple { .. } 1131 + | CompleteType::Struct { .. } 1132 + | CompleteType::List(_) => { 1133 + panic!("Wasm value type not implemented for {type_:?}") 1134 + } 1135 + } 1136 + } 1137 + 1138 + fn block_type(type_: &CompleteType) -> BlockType { 1139 + BlockType::Result(val_type(type_)) 1140 + } 1141 + 1142 + fn bigint_to_i64(value: &BigInt) -> i64 { 1143 + value 1144 + .to_i64() 1145 + .unwrap_or_else(|| panic!("integer literal does not fit in i64: {value}")) 1146 + } 1147 + 1148 + fn push_dummy_value(body: &mut Function, type_: &CompleteType) { 1149 + match type_ { 1150 + CompleteType::Int => { 1151 + let _ = body.instructions().i64_const(0); 1152 + } 1153 + CompleteType::Bool => { 1154 + let _ = body.instructions().i32_const(0); 1155 + } 1156 + CompleteType::String => { 1157 + // Point at the reserved scratch region start — only safe when the 1158 + // empty string was interned just after scratch (offset SCRATCH_SIZE). 1159 + let _ = body.instructions().i32_const(SCRATCH_SIZE as i32); 1160 + } 1161 + CompleteType::Float => { 1162 + let _ = body.instructions().f64_const(Ieee64::from(0.0)); 1163 + } 1164 + other => panic!("cannot synthesise dummy value for {other:?}"), 1165 + } 1166 + } 1167 + 1168 + fn align4(value: u32) -> u32 { 1169 + (value + 3) & !3 1170 + } 1171 + 1172 + fn module_uses_strings(module: &ast::Module<CompleteType>) -> bool { 1173 + module.functions.iter().any(|function| { 1174 + type_is_stringy(&function.return_type) 1175 + || function 1176 + .parameters 1177 + .iter() 1178 + .any(|parameter| type_is_stringy(&parameter.type_)) 1179 + || expression_uses_strings(&function.body) 1180 + }) 1181 + } 1182 + 1183 + fn type_is_stringy(type_: &CompleteType) -> bool { 1184 + matches!(type_, CompleteType::String) 1185 + } 1186 + 1187 + fn expression_uses_strings(expression: &Expression<CompleteType>) -> bool { 1188 + match expression { 1189 + Expression::String { .. } | Expression::StringConcat { .. } => true, 1190 + Expression::Block(expressions) => expressions.iter().any(expression_uses_strings), 1191 + Expression::Set { value, .. } => expression_uses_strings(value), 1192 + Expression::Equals { lhs, rhs } 1193 + | Expression::NotEquals { lhs, rhs } 1194 + | Expression::IntGt { lhs, rhs } 1195 + | Expression::IntGtEq { lhs, rhs } 1196 + | Expression::IntLt { lhs, rhs } 1197 + | Expression::IntLtEq { lhs, rhs } 1198 + | Expression::IntAdd { lhs, rhs } 1199 + | Expression::IntSub { lhs, rhs } 1200 + | Expression::IntMul { lhs, rhs } 1201 + | Expression::IntDiv { lhs, rhs } 1202 + | Expression::IntRem { lhs, rhs } 1203 + | Expression::FloatGt { lhs, rhs } 1204 + | Expression::FloatGtEq { lhs, rhs } 1205 + | Expression::FloatLt { lhs, rhs } 1206 + | Expression::FloatLtEq { lhs, rhs } 1207 + | Expression::FloatAdd { lhs, rhs } 1208 + | Expression::FloatSub { lhs, rhs } 1209 + | Expression::FloatMul { lhs, rhs } 1210 + | Expression::FloatDiv { lhs, rhs } => { 1211 + expression_uses_strings(lhs) || expression_uses_strings(rhs) 1212 + } 1213 + Expression::If { cond, then, else_ } => { 1214 + expression_uses_strings(cond) 1215 + || expression_uses_strings(then) 1216 + || expression_uses_strings(else_) 1217 + } 1218 + Expression::Call { target, args, .. } => { 1219 + expression_uses_strings(target) || args.iter().any(expression_uses_strings) 1220 + } 1221 + Expression::List { items, tail, type_ } => { 1222 + type_is_stringy(type_) 1223 + || items.iter().any(expression_uses_strings) 1224 + || tail.as_ref().is_some_and(|t| expression_uses_strings(t)) 1225 + } 1226 + Expression::Tuple { items, type_ } | Expression::Struct { items, type_, .. } => { 1227 + type_is_stringy(type_) || items.iter().any(expression_uses_strings) 1228 + } 1229 + Expression::TupleAccess { value, type_, .. } 1230 + | Expression::StructAccess { value, type_, .. } => { 1231 + type_is_stringy(type_) || expression_uses_strings(value) 1232 + } 1233 + Expression::StructTag { value } => expression_uses_strings(value), 1234 + Expression::FunctionRef { type_, .. } => type_is_stringy(type_), 1235 + Expression::Var(var) => type_is_stringy(&var.type_), 1236 + Expression::Panic { type_ } => type_is_stringy(type_), 1237 + Expression::Int { .. } 1238 + | Expression::Float { .. } 1239 + | Expression::Bool { .. } => false, 1240 + } 1241 + } 1242 + 1243 + fn collect_strings_from_expression(expression: &Expression<CompleteType>, f: &mut dyn FnMut(&str)) { 1244 + match expression { 1245 + Expression::String { value } => f(value), 1246 + Expression::Block(expressions) => { 1247 + for expression in expressions { 1248 + collect_strings_from_expression(expression, f); 1249 + } 1250 + } 1251 + Expression::Set { value, .. } 1252 + | Expression::StructTag { value } 1253 + | Expression::TupleAccess { value, .. } 1254 + | Expression::StructAccess { value, .. } => { 1255 + collect_strings_from_expression(value, f); 1256 + } 1257 + Expression::Equals { lhs, rhs } 1258 + | Expression::NotEquals { lhs, rhs } 1259 + | Expression::IntGt { lhs, rhs } 1260 + | Expression::IntGtEq { lhs, rhs } 1261 + | Expression::IntLt { lhs, rhs } 1262 + | Expression::IntLtEq { lhs, rhs } 1263 + | Expression::IntAdd { lhs, rhs } 1264 + | Expression::IntSub { lhs, rhs } 1265 + | Expression::IntMul { lhs, rhs } 1266 + | Expression::IntDiv { lhs, rhs } 1267 + | Expression::IntRem { lhs, rhs } 1268 + | Expression::FloatGt { lhs, rhs } 1269 + | Expression::FloatGtEq { lhs, rhs } 1270 + | Expression::FloatLt { lhs, rhs } 1271 + | Expression::FloatLtEq { lhs, rhs } 1272 + | Expression::FloatAdd { lhs, rhs } 1273 + | Expression::FloatSub { lhs, rhs } 1274 + | Expression::FloatMul { lhs, rhs } 1275 + | Expression::FloatDiv { lhs, rhs } 1276 + | Expression::StringConcat { lhs, rhs } => { 1277 + collect_strings_from_expression(lhs, f); 1278 + collect_strings_from_expression(rhs, f); 1279 + } 1280 + Expression::If { cond, then, else_ } => { 1281 + collect_strings_from_expression(cond, f); 1282 + collect_strings_from_expression(then, f); 1283 + collect_strings_from_expression(else_, f); 1284 + } 1285 + Expression::Call { target, args, .. } => { 1286 + collect_strings_from_expression(target, f); 1287 + for arg in args { 1288 + collect_strings_from_expression(arg, f); 1289 + } 1290 + } 1291 + Expression::List { items, tail, .. } => { 1292 + for item in items { 1293 + collect_strings_from_expression(item, f); 1294 + } 1295 + if let Some(tail) = tail { 1296 + collect_strings_from_expression(tail, f); 1297 + } 1298 + } 1299 + Expression::Tuple { items, .. } | Expression::Struct { items, .. } => { 1300 + for item in items { 1301 + collect_strings_from_expression(item, f); 1302 + } 1303 + } 1304 + Expression::FunctionRef { .. } 1305 + | Expression::Var(_) 1306 + | Expression::Int { .. } 1307 + | Expression::Float { .. } 1308 + | Expression::Bool { .. } 1309 + | Expression::Panic { .. } => {} 1310 + } 1311 + } 1312 + 1313 + #[cfg(test)] 1314 + mod tests { 1315 + use super::*; 1316 + use crate::wasm::mir::ast::{Function, FunctionParameter, Module}; 1317 + 1318 + #[test] 1319 + fn compiles_constant_int_main() { 1320 + let module = Module { 1321 + name: "project_wasm".into(), 1322 + functions: vec![Function { 1323 + name: "main".into(), 1324 + return_type: CompleteType::Int, 1325 + parameters: vec![], 1326 + body: Expression::Int { 1327 + value: BigInt::from(0), 1328 + }, 1329 + }], 1330 + }; 1331 + 1332 + let bytes = compile(module); 1333 + assert_eq!(&bytes[..4], b"\0asm"); 1334 + // version 1 1335 + assert_eq!(&bytes[4..8], &[1, 0, 0, 0]); 1336 + } 1337 + 1338 + #[test] 1339 + fn compiles_add() { 1340 + let module = Module { 1341 + name: "maths".into(), 1342 + functions: vec![Function { 1343 + name: "add".into(), 1344 + return_type: CompleteType::Int, 1345 + parameters: vec![ 1346 + FunctionParameter { 1347 + type_: CompleteType::Int, 1348 + name: Some("lhs".into()), 1349 + }, 1350 + FunctionParameter { 1351 + type_: CompleteType::Int, 1352 + name: Some("rhs".into()), 1353 + }, 1354 + ], 1355 + body: Expression::IntAdd { 1356 + lhs: Box::new(Expression::Var(ast::Var { 1357 + name: "lhs".into(), 1358 + type_: CompleteType::Int, 1359 + })), 1360 + rhs: Box::new(Expression::Var(ast::Var { 1361 + name: "rhs".into(), 1362 + type_: CompleteType::Int, 1363 + })), 1364 + }, 1365 + }], 1366 + }; 1367 + 1368 + let bytes = compile(module); 1369 + assert_eq!(&bytes[..4], b"\0asm"); 1370 + } 1371 + 1372 + #[test] 1373 + fn compiles_string_literal_and_concat() { 1374 + let module = Module { 1375 + name: "strings".into(), 1376 + functions: vec![Function { 1377 + name: "greet".into(), 1378 + return_type: CompleteType::String, 1379 + parameters: vec![], 1380 + body: Expression::StringConcat { 1381 + lhs: Box::new(Expression::String { 1382 + value: "hello, ".into(), 1383 + }), 1384 + rhs: Box::new(Expression::String { 1385 + value: "world!".into(), 1386 + }), 1387 + }, 1388 + }], 1389 + }; 1390 + 1391 + let bytes = compile(module); 1392 + assert_eq!(&bytes[..4], b"\0asm"); 1393 + // Must include a memory section / export for host access. 1394 + assert!(bytes.len() > 40); 1395 + } 7 1396 }
+32 -7
compiler-core/src/wasm/mir.rs
··· 132 132 crate::ast::Definition::Function(function) => { 133 133 module.functions.push(self.translate_function(function)); 134 134 } 135 - crate::ast::Definition::TypeAlias(_type_alias) => todo!(), 135 + // Type aliases and imports do not produce runtime code. 136 + crate::ast::Definition::TypeAlias(_type_alias) => {} 136 137 crate::ast::Definition::CustomType(custom_type) => { 137 138 module 138 139 .functions 139 140 .append(&mut self.translate_custom_type(custom_type)); 140 141 } 141 - crate::ast::Definition::Import(_import) => todo!(), 142 - crate::ast::Definition::ModuleConstant(_module_constant) => todo!(), 142 + crate::ast::Definition::Import(_import) => {} 143 + // Module constants are not yet lowered to MIR. 144 + crate::ast::Definition::ModuleConstant(_module_constant) => {} 143 145 } 144 146 } 145 147 ··· 378 380 type_: _, 379 381 value, 380 382 } => Expression::String { 381 - value: value.clone(), 383 + value: crate::strings::convert_string_escape_chars(value), 382 384 }, 383 385 crate::ast::TypedExpr::Block { 384 386 location: _, ··· 578 580 let name = this.make_tmp_var(Translator::translate_type(&subject.type_())); 579 581 let value = this.translate_expression(subject); 580 582 581 - _ = this.decision_map.insert(index, name.clone()); 583 + // Map the exhaustiveness subject variable id → MIR local so 584 + // decision-tree switches look up the correct value. 585 + let subject_variable = &compiled_case.subject_variables[index]; 586 + _ = this 587 + .decision_map 588 + .insert(subject_variable.id, name.clone()); 582 589 583 590 block.push(Expression::Set { 584 591 name: name.clone(), ··· 720 727 value: Box::new(value), 721 728 }); 722 729 723 - _ = this.decision_map.insert(0, value_var.clone()); 730 + if let Some(subject_variable) = assignment.compiled_case.subject_variables.first() { 731 + _ = this 732 + .decision_map 733 + .insert(subject_variable.id, value_var.clone()); 734 + } else { 735 + _ = this.decision_map.insert(0, value_var.clone()); 736 + } 724 737 725 738 block.push(this.translate_decision( 726 739 DecisionKind::Let, ··· 1135 1148 value: value.clone(), 1136 1149 }, 1137 1150 crate::ast::Constant::String { location: _, value } => Expression::String { 1138 - value: value.clone(), 1151 + value: crate::strings::convert_string_escape_chars(value), 1139 1152 }, 1140 1153 crate::ast::Constant::Tuple { 1141 1154 location: _, ··· 1262 1275 labels: _, 1263 1276 fields, 1264 1277 } => { 1278 + // Prelude `Bool` is a two-variant custom type (`True` = 0, `False` = 1) 1279 + // but the Wasm MIR represents booleans as a dedicated `Bool` value. 1280 + if fields.is_empty() && against.type_() == IncompleteType::Bool { 1281 + return Expression::Equals { 1282 + lhs: Box::new(against), 1283 + rhs: Box::new(Expression::Bool { 1284 + // variant_index 0 is `True` 1285 + value: *index == 0, 1286 + }), 1287 + }; 1288 + } 1289 + 1265 1290 let mut block = vec![]; 1266 1291 1267 1292 for (index, field) in fields.iter().enumerate() {
+10 -10
compiler-core/src/wasm/mir/ast.rs
··· 280 280 Expression::FloatGtEq { .. } => IncompleteType::Bool, 281 281 Expression::FloatLt { .. } => IncompleteType::Bool, 282 282 Expression::FloatLtEq { .. } => IncompleteType::Bool, 283 - Expression::FloatAdd { .. } => IncompleteType::Bool, 284 - Expression::FloatSub { .. } => IncompleteType::Bool, 285 - Expression::FloatMul { .. } => IncompleteType::Bool, 286 - Expression::FloatDiv { .. } => IncompleteType::Bool, 287 - Expression::StringConcat { .. } => IncompleteType::Bool, 283 + Expression::FloatAdd { .. } => IncompleteType::Float, 284 + Expression::FloatSub { .. } => IncompleteType::Float, 285 + Expression::FloatMul { .. } => IncompleteType::Float, 286 + Expression::FloatDiv { .. } => IncompleteType::Float, 287 + Expression::StringConcat { .. } => IncompleteType::String, 288 288 Expression::List { type_, .. } => type_.clone(), 289 289 Expression::Tuple { type_, .. } => type_.clone(), 290 290 Expression::TupleAccess { type_, .. } => type_.clone(), ··· 327 327 Expression::FloatGtEq { .. } => CompleteType::Bool, 328 328 Expression::FloatLt { .. } => CompleteType::Bool, 329 329 Expression::FloatLtEq { .. } => CompleteType::Bool, 330 - Expression::FloatAdd { .. } => CompleteType::Bool, 331 - Expression::FloatSub { .. } => CompleteType::Bool, 332 - Expression::FloatMul { .. } => CompleteType::Bool, 333 - Expression::FloatDiv { .. } => CompleteType::Bool, 334 - Expression::StringConcat { .. } => CompleteType::Bool, 330 + Expression::FloatAdd { .. } => CompleteType::Float, 331 + Expression::FloatSub { .. } => CompleteType::Float, 332 + Expression::FloatMul { .. } => CompleteType::Float, 333 + Expression::FloatDiv { .. } => CompleteType::Float, 334 + Expression::StringConcat { .. } => CompleteType::String, 335 335 Expression::List { type_, .. } => type_.clone(), 336 336 Expression::Tuple { type_, .. } => type_.clone(), 337 337 Expression::TupleAccess { type_, .. } => type_.clone(),
+1
examples/hello_wasm/.gitignore
··· 1 + build/
+21
examples/hello_wasm/Makefile
··· 1 + # Sample Gleam Wasm app — build with the in-repo compiler and run under wasmtime. 2 + # 3 + # Usage (from this directory): 4 + # make run 5 + 6 + GLEAM ?= ../../target/debug/gleam 7 + WASM := build/dev/wasm/hello_wasm/hello_wasm.wasm 8 + 9 + .PHONY: gleam-compiler build run clean 10 + 11 + gleam-compiler: 12 + cd ../.. && cargo build -p gleam 13 + 14 + build: gleam-compiler 15 + $(GLEAM) build 16 + 17 + run: build 18 + wasmtime $(WASM) 19 + 20 + clean: 21 + rm -rf build
+48
examples/hello_wasm/README.md
··· 1 + # hello_wasm 2 + 3 + A tiny Gleam program that compiles to WebAssembly and runs under 4 + [wasmtime](https://wasmtime.dev/) via WASI. 5 + 6 + The compiler emits a `_start` entry that calls `main` and prints its return 7 + value to stdout (`wasi_snapshot_preview1::fd_write`). 8 + 9 + ## Run 10 + 11 + From this directory (builds the in-repo `gleam` first): 12 + 13 + ```sh 14 + ./run.sh 15 + ``` 16 + 17 + Or step by step: 18 + 19 + ```sh 20 + # from repo root 21 + cargo build -p gleam 22 + 23 + # from this directory 24 + ../../target/debug/gleam build 25 + wasmtime build/dev/wasm/hello_wasm/hello_wasm.wasm 26 + ``` 27 + 28 + Expected output: 29 + 30 + ```text 31 + Hello from Gleam Wasm! 32 + fib(10) = 55 33 + ``` 34 + 35 + You can also call individual exports: 36 + 37 + ```sh 38 + wasmtime --invoke fib build/dev/wasm/hello_wasm/hello_wasm.wasm 10 39 + # => 55 40 + ``` 41 + 42 + ## Notes 43 + 44 + - Target is set in `gleam.toml`: `target = "wasm"`. 45 + - `main` must take no arguments. Supported return types for printing: `String`, 46 + `Int`, and `Bool`. 47 + - No Hex packages yet — the Wasm backend does not implement enough of the 48 + standard library for typical dependencies.
+3
examples/hello_wasm/gleam.toml
··· 1 + name = "hello_wasm" 2 + version = "1.0.0" 3 + target = "wasm"
+7
examples/hello_wasm/manifest.toml
··· 1 + # This file was generated by Gleam 2 + # You typically do not need to edit this file 3 + 4 + packages = [ 5 + ] 6 + 7 + [requirements]
+7
examples/hello_wasm/run.sh
··· 1 + #!/usr/bin/env bash 2 + set -euo pipefail 3 + cd "$(dirname "$0")" 4 + ROOT="$(cd ../.. && pwd)" 5 + (cd "$ROOT" && cargo build -p gleam) 6 + "$ROOT/target/debug/gleam" build 7 + exec wasmtime build/dev/wasm/hello_wasm/hello_wasm.wasm
+43
examples/hello_wasm/src/hello_wasm.gleam
··· 1 + //// Sample Gleam → Wasm program. 2 + //// 3 + //// ```sh 4 + //// make run 5 + //// ``` 6 + 7 + pub fn main() -> String { 8 + "Hello from Gleam Wasm!\n" 9 + <> "fib(10) = " 10 + <> int_to_string(fib(10)) 11 + } 12 + 13 + pub fn fib(n: Int) -> Int { 14 + case n { 15 + 0 -> 0 16 + 1 -> 1 17 + _ -> fib(n - 1) + fib(n - 2) 18 + } 19 + } 20 + 21 + fn int_to_string(n: Int) -> String { 22 + case n { 23 + n if n < 0 -> "-" <> int_to_string(0 - n) 24 + n if n < 10 -> digit(n) 25 + n -> int_to_string(n / 10) <> digit(n % 10) 26 + } 27 + } 28 + 29 + fn digit(n: Int) -> String { 30 + case n { 31 + 0 -> "0" 32 + 1 -> "1" 33 + 2 -> "2" 34 + 3 -> "3" 35 + 4 -> "4" 36 + 5 -> "5" 37 + 6 -> "6" 38 + 7 -> "7" 39 + 8 -> "8" 40 + 9 -> "9" 41 + _ -> "?" 42 + } 43 + }
+14 -2
test/project_wasm/src/project_wasm.gleam
··· 1 - pub fn main() { 2 - 0 1 + pub fn main() -> String { 2 + greet("world") 3 + } 4 + 5 + pub fn greet(name: String) -> String { 6 + "hello, " <> name <> "!" 7 + } 8 + 9 + pub fn same(a: String, b: String) -> Bool { 10 + a == b 11 + } 12 + 13 + pub fn empty() -> String { 14 + "" 3 15 }