@@ -49,8 +49,9 @@ use ephapax_syntax::{
4949} ;
5050use std:: collections:: HashMap ;
5151use wasm_encoder:: {
52- CodeSection , ExportKind , ExportSection , Function , FunctionSection , ImportSection , Instruction ,
53- MemorySection , MemoryType , Module as WasmModule , TypeSection , ValType ,
52+ CodeSection , ConstExpr , ElementSection , Elements , ExportKind , ExportSection , Function ,
53+ FunctionSection , ImportSection , Instruction , MemorySection , MemoryType , Module as WasmModule ,
54+ RefType , TableSection , TableType , TypeSection , ValType ,
5455} ;
5556
5657// ---------------------------------------------------------------------------
@@ -432,10 +433,12 @@ impl Codegen {
432433 self . append_lambda_funcs ( & mut func_sec, & mut code_sec) ?;
433434
434435 // Emit sections in WASM-required order:
435- // Type, Import, Function, Memory, Export, Code, Data
436+ // Type, Import, Function, Table, Memory, Export, Element , Code, Data
436437 self . module . section ( & func_sec) ;
438+ self . emit_table ( ) ;
437439 self . emit_memory ( ) ;
438440 self . emit_module_exports ( ast) ;
441+ self . emit_elements ( ) ;
439442 self . module . section ( & code_sec) ;
440443 self . emit_data_section ( ) ;
441444
@@ -632,6 +635,58 @@ impl Codegen {
632635 self . module . section ( & memories) ;
633636 }
634637
638+ /// Emit the table section for indirect function calls (lambdas).
639+ /// Creates a table with funcref elements sized to fit all lambda functions.
640+ fn emit_table ( & mut self ) {
641+ if self . lambda_fns . is_empty ( ) {
642+ return ; // No lambdas, no table needed
643+ }
644+
645+ let mut tables = TableSection :: new ( ) ;
646+ // Create a table that can hold all lambda functions plus user functions
647+ let table_size = ( self . user_fns . len ( ) + self . lambda_fns . len ( ) ) as u64 ;
648+ tables. table ( TableType {
649+ element_type : RefType :: FUNCREF ,
650+ minimum : table_size,
651+ maximum : Some ( table_size) ,
652+ table64 : false ,
653+ shared : false ,
654+ } ) ;
655+ self . module . section ( & tables) ;
656+ }
657+
658+ /// Emit the element section to populate the function table.
659+ /// Maps lambda function indices into table slots for call_indirect.
660+ fn emit_elements ( & mut self ) {
661+ if self . lambda_fns . is_empty ( ) {
662+ return ; // No lambdas, no elements needed
663+ }
664+
665+ let mut elements = ElementSection :: new ( ) ;
666+
667+ // Collect all function indices (user functions + lambdas)
668+ let mut func_indices = Vec :: new ( ) ;
669+
670+ // Add user function indices
671+ for info in self . user_fns . values ( ) {
672+ func_indices. push ( info. wasm_fn_idx ) ;
673+ }
674+
675+ // Add lambda function indices
676+ for lambda in & self . lambda_fns {
677+ func_indices. push ( lambda. wasm_fn_idx ) ;
678+ }
679+
680+ // Populate table starting at offset 0
681+ elements. active (
682+ Some ( 0 ) , // table index (we only have one table)
683+ & ConstExpr :: i32_const ( 0 ) , // offset in the table
684+ Elements :: Functions ( std:: borrow:: Cow :: Borrowed ( & func_indices) ) ,
685+ ) ;
686+
687+ self . module . section ( & elements) ;
688+ }
689+
635690 /// Append the 7 runtime helper functions to the given sections.
636691 fn append_runtime_funcs (
637692 & self ,
@@ -1266,37 +1321,130 @@ impl Codegen {
12661321 }
12671322 }
12681323
1324+ /// Find free variables in an expression.
1325+ /// A free variable is one that is referenced but not bound in the current scope.
1326+ fn find_free_vars ( & self , expr : & Expr , bound_vars : & std:: collections:: HashSet < String > ) -> Vec < String > {
1327+ use std:: collections:: HashSet ;
1328+ let mut free_vars = Vec :: new ( ) ;
1329+ let mut seen = HashSet :: new ( ) ;
1330+
1331+ fn collect (
1332+ expr : & Expr ,
1333+ bound : & HashSet < String > ,
1334+ free : & mut Vec < String > ,
1335+ seen : & mut HashSet < String > ,
1336+ ) {
1337+ match & expr. kind {
1338+ ExprKind :: Var ( name) => {
1339+ if !bound. contains ( name. as_str ( ) ) && !seen. contains ( name. as_str ( ) ) {
1340+ free. push ( name. to_string ( ) ) ;
1341+ seen. insert ( name. to_string ( ) ) ;
1342+ }
1343+ }
1344+ ExprKind :: Let { name, value, body, .. } | ExprKind :: LetLin { name, value, body, .. } => {
1345+ collect ( value, bound, free, seen) ;
1346+ let mut new_bound = bound. clone ( ) ;
1347+ new_bound. insert ( name. to_string ( ) ) ;
1348+ collect ( body, & new_bound, free, seen) ;
1349+ }
1350+ ExprKind :: Lambda { param, body, .. } => {
1351+ let mut new_bound = bound. clone ( ) ;
1352+ new_bound. insert ( param. to_string ( ) ) ;
1353+ collect ( body, & new_bound, free, seen) ;
1354+ }
1355+ ExprKind :: App { func, arg } => {
1356+ collect ( func, bound, free, seen) ;
1357+ collect ( arg, bound, free, seen) ;
1358+ }
1359+ ExprKind :: If { cond, then_branch, else_branch } => {
1360+ collect ( cond, bound, free, seen) ;
1361+ collect ( then_branch, bound, free, seen) ;
1362+ collect ( else_branch, bound, free, seen) ;
1363+ }
1364+ ExprKind :: Pair { left, right } => {
1365+ collect ( left, bound, free, seen) ;
1366+ collect ( right, bound, free, seen) ;
1367+ }
1368+ ExprKind :: Fst ( inner) | ExprKind :: Snd ( inner) | ExprKind :: Inl { value : inner, .. } | ExprKind :: Inr { value : inner, .. } => {
1369+ collect ( inner, bound, free, seen) ;
1370+ }
1371+ ExprKind :: Case { scrutinee, left_var, left_body, right_var, right_body } => {
1372+ collect ( scrutinee, bound, free, seen) ;
1373+ let mut left_bound = bound. clone ( ) ;
1374+ left_bound. insert ( left_var. to_string ( ) ) ;
1375+ collect ( left_body, & left_bound, free, seen) ;
1376+ let mut right_bound = bound. clone ( ) ;
1377+ right_bound. insert ( right_var. to_string ( ) ) ;
1378+ collect ( right_body, & right_bound, free, seen) ;
1379+ }
1380+ ExprKind :: BinOp { left, right, .. } => {
1381+ collect ( left, bound, free, seen) ;
1382+ collect ( right, bound, free, seen) ;
1383+ }
1384+ ExprKind :: UnaryOp { operand, .. } => {
1385+ collect ( operand, bound, free, seen) ;
1386+ }
1387+ ExprKind :: Region { body, .. } => {
1388+ collect ( body, bound, free, seen) ;
1389+ }
1390+ ExprKind :: Borrow ( inner) | ExprKind :: Deref ( inner) | ExprKind :: Drop ( inner) => {
1391+ collect ( inner, bound, free, seen) ;
1392+ }
1393+ ExprKind :: StringConcat { left, right } => {
1394+ collect ( left, bound, free, seen) ;
1395+ collect ( right, bound, free, seen) ;
1396+ }
1397+ ExprKind :: StringLen ( inner) => {
1398+ collect ( inner, bound, free, seen) ;
1399+ }
1400+ ExprKind :: Copy ( inner) => {
1401+ collect ( inner, bound, free, seen) ;
1402+ }
1403+ ExprKind :: Block ( exprs) => {
1404+ for expr in exprs {
1405+ collect ( expr, bound, free, seen) ;
1406+ }
1407+ }
1408+ ExprKind :: Lit ( _) | ExprKind :: StringNew { .. } => { }
1409+ }
1410+ }
1411+
1412+ collect ( expr, bound_vars, & mut free_vars, & mut seen) ;
1413+ free_vars
1414+ }
1415+
12691416 fn compile_lambda (
12701417 & mut self ,
12711418 func : & mut Function ,
12721419 param : & str ,
12731420 param_ty : & Ty ,
12741421 body : & Expr ,
12751422 ) {
1276- // For now, implement closed lambdas (no captured variables).
1277- // Full closure support with environment capture will be added incrementally.
1423+ // 1. Find free variables in the lambda body
1424+ let mut bound_vars = std:: collections:: HashSet :: new ( ) ;
1425+ bound_vars. insert ( param. to_string ( ) ) ;
1426+ let captured_vars = self . find_free_vars ( body, & bound_vars) ;
12781427
1279- // 1 . Determine the lambda's WASM type
1428+ // 2 . Determine the lambda's WASM type
12801429 // For simplicity, assume all lambdas take i32 and return i32
1281- // (matching the common case in Ephapax)
1282- let lambda_type_idx = 0 ; // Type 0 is (i32) -> i32
1430+ let lambda_type_idx = TYPE_I32_TO_I32 ; // Type 6 is (i32) -> i32
12831431
1284- // 2 . Calculate function index for this lambda
1432+ // 3 . Calculate function index for this lambda
12851433 // Function indices: imports (2) + runtime helpers (7) + user functions + lambdas
12861434 let lambda_fn_idx = NUM_IMPORTS + NUM_RUNTIME_FNS + self . user_fns . len ( ) as u32 + self . lambda_fns . len ( ) as u32 ;
12871435
1288- // 3 . Record this lambda for later emission
1436+ // 4 . Record this lambda for later emission
12891437 self . lambda_fns . push ( LambdaInfo {
12901438 wasm_fn_idx : lambda_fn_idx,
12911439 wasm_type_idx : lambda_type_idx,
1292- captured_vars : Vec :: new ( ) , // No captures for now
1440+ captured_vars,
12931441 param : param. to_string ( ) ,
12941442 param_ty : param_ty. clone ( ) ,
12951443 body : body. clone ( ) ,
12961444 } ) ;
12971445
1298- // 4 . Emit the closure representation (for now, just the function index)
1299- // In full closure support, this would be (fn_idx, env_ptr) pair
1446+ // 5 . Emit the closure representation (just the function index for now )
1447+ // Full closure support would allocate environment and emit (fn_idx, env_ptr) pair
13001448 func. instruction ( & Instruction :: I32Const ( lambda_fn_idx as i32 ) ) ;
13011449 }
13021450
@@ -1316,13 +1464,17 @@ impl Codegen {
13161464 }
13171465 }
13181466
1319- // Fallback: indirect call / unknown -- not yet supported, emit 0.
1320- // A full implementation would use `call_indirect` via a function table.
1321- self . compile_expr ( func, fn_expr) ;
1322- func. instruction ( & Instruction :: Drop ) ; // drop the function value
1323- self . compile_expr ( func, arg) ;
1324- func. instruction ( & Instruction :: Drop ) ; // drop the argument
1325- func. instruction ( & Instruction :: I32Const ( 0 ) ) ;
1467+ // Indirect call via function table (for lambdas and first-class functions)
1468+ // Stack layout: [fn_index] [arg] -> call_indirect -> [result]
1469+ self . compile_expr ( func, arg) ; // Push argument
1470+ self . compile_expr ( func, fn_expr) ; // Push function index
1471+
1472+ // Use call_indirect with type signature (i32) -> i32
1473+ // Type 6 is TYPE_I32_TO_I32 defined at the top
1474+ func. instruction ( & Instruction :: CallIndirect {
1475+ type_index : TYPE_I32_TO_I32 ,
1476+ table_index : 0 , // We only have one table (index 0)
1477+ } ) ;
13261478 }
13271479
13281480 fn compile_pair (
@@ -2592,6 +2744,38 @@ mod tests {
25922744 assert_eq ! ( codegen. lambda_fns[ 0 ] . param, "x" ) ;
25932745 }
25942746
2747+ #[ test]
2748+ fn compile_lambda_application ( ) {
2749+ // Test: ((lambda (x : I32) (+ x 1)) 5)
2750+ // Expected result: 6
2751+ let module = AstModule {
2752+ name : "test" . into ( ) ,
2753+ decls : vec ! [ Decl :: Fn {
2754+ name: "main" . into( ) ,
2755+ params: vec![ ] ,
2756+ ret_ty: Ty :: Base ( BaseTy :: I32 ) ,
2757+ body: e( ExprKind :: App {
2758+ func: Box :: new( e( ExprKind :: Lambda {
2759+ param: "x" . into( ) ,
2760+ param_ty: Ty :: Base ( BaseTy :: I32 ) ,
2761+ body: Box :: new( e( ExprKind :: BinOp {
2762+ op: BinOp :: Add ,
2763+ left: Box :: new( e( ExprKind :: Var ( "x" . into( ) ) ) ) ,
2764+ right: Box :: new( e( ExprKind :: Lit ( Literal :: I32 ( 1 ) ) ) ) ,
2765+ } ) ) ,
2766+ } ) ) ,
2767+ arg: Box :: new( e( ExprKind :: Lit ( Literal :: I32 ( 5 ) ) ) ) ,
2768+ } ) ,
2769+ } ] ,
2770+ } ;
2771+ let wasm = compile_module ( & module) . expect ( "compilation failed" ) ;
2772+ assert_wasm_header ( & wasm) ;
2773+ validate_wasm ( & wasm) ;
2774+
2775+ // The WASM module should be valid and contain a lambda function
2776+ // (The validation above checks that the WASM is well-formed)
2777+ }
2778+
25952779 // -----------------------------------------------------------------------
25962780 // Type mapping
25972781 // -----------------------------------------------------------------------
0 commit comments