Add indirect function calls (function pointers)
authorBrendan Hansen <brendan.f.hansen@gmail.com>
Mon, 27 Jul 2020 20:29:26 +0000 (15:29 -0500)
committerBrendan Hansen <brendan.f.hansen@gmail.com>
Mon, 27 Jul 2020 20:29:26 +0000 (15:29 -0500)
.vimspector.json
include/onyxastnodes.h
include/onyxwasm.h
onyx
progs/fcf.onyx [new file with mode: 0644]
src/onyxchecker.c
src/onyxparser.c
src/onyxsymres.c
src/onyxtypes.c
src/onyxwasm.c

index 8a5ecd8927fad2ed3b2eaf075292cd2ec89be3f3..d4b26ba989abfdc90c1fe38a05eee36162d5d5ca 100644 (file)
@@ -6,7 +6,7 @@
                 "type": "cppdbg",
                 "request": "launch",
                 "program": "${workspaceFolder}/onyx",
-                "args": ["progs/ufc.onyx"],
+                "args": ["progs/fcf.onyx"],
                 "stopAtEntry": true,
                 "cwd": "${workspaceFolder}",
                 "environment": [],
index 1238eed7f68c106a8b4b7b229a6ee7849c943b1a..72fb15eb02c774a84c96b291a3bc7c62e29ae75e 100644 (file)
@@ -137,7 +137,7 @@ typedef enum AstFlags {
     Ast_Flag_Expr_Ignored     = BH_BIT(8),
     Ast_Flag_Param_Splatted   = BH_BIT(9),
     Ast_Flag_Param_Use        = BH_BIT(10),
-    
+
     // Type flags
     Ast_Flag_Type_Is_Resolved = BH_BIT(8),
 
@@ -230,7 +230,7 @@ typedef enum OnyxIntrinsic {
 #define AstNode_base struct AstNode_members;
 struct AstNode AstNode_members;
 
-// NOTE: 'type_node' is filled out by the parser.                                               
+// NOTE: 'type_node' is filled out by the parser.
 // For a type such as '^^i32', the tree would look something like
 //
 //      Typed Thing -> AstPointerType -> AstPointerType -> AstNode (symbol node)
@@ -255,7 +255,7 @@ struct AstUnaryOp       { AstTyped_base; UnaryOp operation; AstTyped *expr; };
 struct AstNumLit        { AstTyped_base; union { i32 i; i64 l; f32 f; f64 d; } value; };
 struct AstStrLit        { AstTyped_base; u64 addr; };
 struct AstLocal         { AstTyped_base; AstLocal *prev_local; };
-struct AstCall          { AstTyped_base; AstArgument *arguments; u64 arg_count; AstNode *callee; };
+struct AstCall          { AstTyped_base; AstArgument *arguments; u64 arg_count; AstTyped *callee; };
 struct AstIntrinsicCall { AstTyped_base; AstArgument *arguments; u64 arg_count; OnyxIntrinsic intrinsic; };
 struct AstArgument      { AstTyped_base; AstTyped *value; };
 struct AstAddressOf     { AstTyped_base; AstTyped *expr; };
index 4a5dfcb71b6157f1401954bc9707ee222dd3f5a9..72a5493e5c214f1f54b90753d9e0bc8f94d56c4d 100644 (file)
@@ -311,6 +311,7 @@ typedef struct OnyxWasmModule {
 
     u32 next_type_idx;
     u32 export_count;
+    u32 element_count;
     u32 next_func_idx;
     u32 next_foreign_func_idx;
     u32 next_global_idx;
diff --git a/onyx b/onyx
index cbe7e0ba7aaa9e05cc1ea80521cf270b7a02437b..c90b1111f5debb7bc5536bfa7ea449cdcdf3ff50 100755 (executable)
Binary files a/onyx and b/onyx differ
diff --git a/progs/fcf.onyx b/progs/fcf.onyx
new file mode 100644 (file)
index 0000000..f130389
--- /dev/null
@@ -0,0 +1,44 @@
+use "progs/print_funcs"
+use "progs/intrinsics"
+
+use package printing
+
+call_me :: proc (f: proc (i32), val: i32) {
+       f(val);
+}
+
+funcs : [5] proc (i32, i32) -> i32
+
+add :: proc (a: i32, b: i32) -> i32 { return a + b; }
+sub :: proc (a: i32, b: i32) -> i32 { return a - b; }
+mul :: proc (a: i32, b: i32) -> i32 { return a * b; }
+div :: proc (a: i32, b: i32) -> i32 { return a / b; }
+mod :: proc (a: i32, b: i32) -> i32 { return a % b; }
+
+DeferredCall :: struct {
+    func  : proc (i32, i32) -> i32;
+    left  : i32;
+    right : i32;
+}
+
+execute :: proc (use dc: ^DeferredCall) -> i32 {
+    return func(left, right);
+}
+
+proc #export "main" {
+       call_me(print_i32, 10);
+
+    funcs[0] = add;
+    funcs[1] = sub;
+    funcs[3] = div;
+    funcs[4] = mod;
+
+    print(funcs[0](10, 3));
+
+    dc := __heap_start as ^DeferredCall;
+    dc.func = mod;
+    dc.left = 40;
+    dc.right = 19;
+
+    print(execute(dc));
+}
index 232e498441a54f96fd48a4af435d970a6ff16920..5d5c7c10f983a9671d5f06051c70c498979df73b 100644 (file)
@@ -165,6 +165,8 @@ CHECK(call, AstCall* call) {
         return 1;
     }
 
+    if (check_expression(&call->callee)) return 1;
+
     // NOTE: Check arguments and splat structs
     AstNode** prev_param = (AstNode **) &call->arguments;
     AstArgument* actual_param = call->arguments;
@@ -212,7 +214,7 @@ CHECK(call, AstCall* call) {
     }
 
     if (callee->kind == Ast_Kind_Overloaded_Function) {
-        call->callee = (AstNode *) match_overloaded_function(call, (AstOverloadedFunction *) callee);
+        call->callee = match_overloaded_function(call, (AstOverloadedFunction *) callee);
         callee = (AstFunction *) call->callee;
 
         if (callee == NULL) return 1;
@@ -293,36 +295,34 @@ CHECK(call, AstCall* call) {
 
     call->type = callee->type->Function.return_type;
 
-    AstLocal* formal_param = callee->params;
+    Type** formal_param = &callee->type->Function.params[0];
     actual_param = call->arguments;
 
     i32 arg_pos = 0;
     while (formal_param != NULL && actual_param != NULL) {
-        fill_in_type((AstTyped *) formal_param);
-
-        if (!types_are_compatible(formal_param->type, actual_param->type)) {
+        if (!types_are_compatible(*formal_param, actual_param->type)) {
             onyx_message_add(Msg_Type_Function_Param_Mismatch,
                     actual_param->token->pos,
                     callee->token->text, callee->token->length,
-                    type_get_name(formal_param->type),
+                    type_get_name(*formal_param),
                     arg_pos,
                     type_get_name(actual_param->type));
             return 1;
         }
 
         arg_pos++;
-        formal_param = (AstLocal *) formal_param->next;
+        formal_param++;
         actual_param = (AstArgument *) actual_param->next;
     }
 
-    if (formal_param != NULL && actual_param == NULL) {
+    if (arg_pos < callee->type->Function.param_count) {
         onyx_message_add(Msg_Type_Literal,
                 call->token->pos,
                 "too few arguments to function call");
         return 1;
     }
 
-    if (formal_param == NULL && actual_param != NULL) {
+    if (arg_pos > callee->type->Function.param_count) {
         onyx_message_add(Msg_Type_Literal,
                 call->token->pos,
                 "too many arguments to function call");
@@ -652,8 +652,11 @@ CHECK(expression, AstTyped** pexpr) {
             assert(expr->type != NULL);
             break;
 
+        case Ast_Kind_Function:
+            expr->flags |= Ast_Flag_Function_Used;
+            break;
+
         case Ast_Kind_StrLit: break;
-        case Ast_Kind_Function: break;
         case Ast_Kind_Overloaded_Function: break;
         case Ast_Kind_Enum_Value: break;
 
@@ -919,7 +922,7 @@ void onyx_type_check() {
             case Entity_Type_Function_Header:
                 if (entity->function->flags & Ast_Flag_Foreign)
                     semstate.program->foreign_func_count++;
-                
+
                 if (check_function_header(entity->function)) return;
                 break;
 
index 79958c575e6eacd3c24f297dbba6ac968775de54..7555e23789f4a14383b9175541459ab5e1b171bb 100644 (file)
@@ -302,7 +302,7 @@ static AstTyped* parse_factor(OnyxParser* parser) {
             case '(': {
                 AstCall* call_node = make_node(AstCall, Ast_Kind_Call);
                 call_node->token = expect_token(parser, '(');
-                call_node->callee = (AstNode *) retval;
+                call_node->callee = retval;
                 call_node->arg_count = 0;
 
                 AstArgument** prev = &call_node->arguments;
@@ -807,6 +807,42 @@ static AstType* parse_type(OnyxParser* parser) {
            next_insertion = &new->elem;
         }
 
+        else if (parser->curr->type == Token_Type_Keyword_Proc) {
+            OnyxToken* proc_token = expect_token(parser, Token_Type_Keyword_Proc);
+
+            bh_arr(AstType *) params = NULL;
+            bh_arr_new(global_scratch_allocator, params, 4);
+
+            expect_token(parser, '(');
+            while (parser->curr->type != ')') {
+                AstType* param_type = parse_type(parser);
+                bh_arr_push(params, param_type);
+
+                if (parser->curr->type != ')')
+                    expect_token(parser, ',');
+            }
+            consume_token(parser);
+
+            AstType* return_type = (AstType *) &basic_type_void;
+            if (parser->curr->type == Token_Type_Right_Arrow) {
+                consume_token(parser);
+                return_type = parse_type(parser);
+            }
+
+            u64 param_count = bh_arr_length(params);
+            AstFunctionType* new = onyx_ast_node_new(parser->allocator,
+                    sizeof(AstFunctionType) + sizeof(AstType) * param_count,
+                    Ast_Kind_Function_Type);
+            new->token = proc_token;
+            new->param_count = param_count;
+            new->return_type = return_type;
+
+            fori (i, 0, param_count - 1) new->params[i] = params[i];
+
+            *next_insertion = (AstType *) new;
+            next_insertion = NULL;
+        }
+
         else if (parser->curr->type == Token_Type_Symbol) {
             AstNode* symbol_node = make_node(AstNode, Ast_Kind_Symbol);
             symbol_node->token = expect_token(parser, Token_Type_Symbol);
@@ -1124,7 +1160,7 @@ static AstEnumType* parse_enum_declaration(OnyxParser* parser) {
         if (parser->curr->type == ':') {
             consume_token(parser);
             expect_token(parser, ':');
-            
+
             evalue->value = parse_numeric_literal(parser);
         }
 
@@ -1213,7 +1249,7 @@ static AstNode* parse_top_level_statement(OnyxParser* parser) {
 
                 add_node_to_process(parser, (AstNode *) upack);
                 return NULL;
-                
+
             } else {
                 AstIncludeFile* include = make_node(AstIncludeFile, Ast_Kind_Include_File);
                 include->token = use_token;
index f3fe21bf9def25e94c5d0d3af7a0f5739b0b10a2..1d286f98f5a31326f973a56d05014232d5d47533 100644 (file)
@@ -85,7 +85,7 @@ static AstType* symres_type(AstType* type) {
     if (type->kind == Ast_Kind_Field_Access) {
         AstFieldAccess* field = (AstFieldAccess *) type;
         symres_field_access(&field);
-        
+
         if (!node_is_type((AstNode *) field)) {
             onyx_message_add(Msg_Type_Literal,
                     type->token->pos,
@@ -154,19 +154,23 @@ static void symres_call(AstCall* call) {
     symres_expression((AstTyped **) &call->callee);
     if (call->callee == NULL) return;
 
-    if (call->callee->kind == Ast_Kind_Field_Access) {
-        AstFieldAccess* fa = (AstFieldAccess *) call->callee;
-        if (fa->expr == NULL) return;
+    // NOTE: Disabling UFC now. Causes too many problems.
+    // Especially since structs can have function pointers as members,
+    // so x.y(...) is ambiguous.
+    //
+    // if (call->callee->kind == Ast_Kind_Field_Access) {
+    //     AstFieldAccess* fa = (AstFieldAccess *) call->callee;
+    //     if (fa->expr == NULL) return;
 
-        AstArgument* implicit_arg = onyx_ast_node_new(semstate.node_allocator, sizeof(AstArgument), Ast_Kind_Argument);
-        implicit_arg->value = fa->expr;
-        implicit_arg->token = fa->expr->token;
-        implicit_arg->next = (AstNode *) call->arguments;
+    //     AstArgument* implicit_arg = onyx_ast_node_new(semstate.node_allocator, sizeof(AstArgument), Ast_Kind_Argument);
+    //     implicit_arg->value = fa->expr;
+    //     implicit_arg->token = fa->expr->token;
+    //     implicit_arg->next = (AstNode *) call->arguments;
 
-        call->callee = symbol_resolve(semstate.curr_scope, fa->token);
-        call->arguments = implicit_arg;
-        call->arg_count++;
-    }
+    //     call->callee = (AstTyped *) symbol_resolve(semstate.curr_scope, fa->token);
+    //     call->arguments = implicit_arg;
+    //     call->arg_count++;
+    // }
 
     symres_statement_chain((AstNode *) call->arguments, (AstNode **) &call->arguments);
 }
@@ -217,39 +221,35 @@ static void symres_unaryop(AstUnaryOp** unaryop) {
 
 static void symres_expression(AstTyped** expr) {
     switch ((*expr)->kind) {
-        case Ast_Kind_Binary_Op:
-            symres_expression(&((AstBinaryOp *)(*expr))->left);
-            symres_expression(&((AstBinaryOp *)(*expr))->right);
-            break;
-
-        case Ast_Kind_Unary_Op: symres_unaryop((AstUnaryOp **) expr); break;
-        case Ast_Kind_Call: symres_call((AstCall *) *expr); break;
-        case Ast_Kind_Block: symres_block((AstBlock *) *expr); break;
-
         case Ast_Kind_Symbol:
             *expr = (AstTyped *) symbol_resolve(semstate.curr_scope, ((AstNode *) *expr)->token);
             break;
 
-
-        case Ast_Kind_Function:
-        case Ast_Kind_NumLit:
-        case Ast_Kind_StrLit:
-            (*expr)->type_node = symres_type((*expr)->type_node);
+        case Ast_Kind_Binary_Op:
+            symres_expression(&((AstBinaryOp *)(*expr))->left);
+            symres_expression(&((AstBinaryOp *)(*expr))->right);
             break;
 
+        case Ast_Kind_Unary_Op:     symres_unaryop((AstUnaryOp **) expr); break;
+        case Ast_Kind_Call:         symres_call((AstCall *) *expr); break;
+        case Ast_Kind_Block:        symres_block((AstBlock *) *expr); break;
         case Ast_Kind_Address_Of:   symres_expression(&((AstAddressOf *)(*expr))->expr); break;
         case Ast_Kind_Dereference:  symres_expression(&((AstDereference *)(*expr))->expr); break;
         case Ast_Kind_Field_Access: symres_field_access((AstFieldAccess **) expr); break;
         case Ast_Kind_Size_Of:      symres_size_of((AstSizeOf *)*expr); break;
         case Ast_Kind_Align_Of:     symres_align_of((AstAlignOf *)*expr); break;
 
+        case Ast_Kind_Function:
+        case Ast_Kind_NumLit:
+        case Ast_Kind_StrLit:
+            (*expr)->type_node = symres_type((*expr)->type_node);
+            break;
+
         case Ast_Kind_Array_Access:
             symres_expression(&((AstArrayAccess *)(*expr))->addr);
             symres_expression(&((AstArrayAccess *)(*expr))->expr);
             break;
 
-        // NOTE: This is a good case, since it means the symbol is already resolved
-        case Ast_Kind_Local:
         default: break;
     }
 }
@@ -347,12 +347,12 @@ static void symres_function(AstFunction* func) {
 
     for (AstLocal *param = func->params; param != NULL; param = (AstLocal *) param->next) {
         param->type_node = symres_type(param->type_node);
+        param->type = type_build_from_ast(semstate.allocator, param->type_node);
 
         symbol_introduce(semstate.curr_scope, param->token, (AstNode *) param);
 
         if (param->flags & Ast_Flag_Param_Use) {
-            if (param->type_node->kind != Ast_Kind_Pointer_Type
-                || ((AstPointerType *) param->type_node)->elem->kind != Ast_Kind_Struct_Type) {
+            if (param->type->kind != Type_Kind_Pointer || param->type->Pointer.elem->kind != Type_Kind_Struct) {
                 onyx_message_add(Msg_Type_Literal,
                         param->token->pos,
                         "can only 'use' pointers to structures.");
@@ -474,7 +474,7 @@ static void symres_enum(AstEnumType* enum_node) {
 
         if (enum_node->flags & Ast_Flag_Enum_Is_Flags) {
             next_assign_value <<= 1;
-        } else { 
+        } else {
             next_assign_value++;
         }
     }
index 85664e2cc7ce1cb83a7e079663c7208397693da2..71cab6619157a339910436682a23d3adc69e3a73 100644 (file)
@@ -1,3 +1,4 @@
+#define BH_DEBUG
 #include "onyxtypes.h"
 #include "onyxastnodes.h"
 #include "onyxutils.h"
@@ -144,8 +145,20 @@ b32 types_are_compatible(Type* t1, Type* t2) {
         }
 
         case Type_Kind_Enum: {
-            if (t2->kind != Type_Kind_Enum) return 0;
-            return t1 == t2;
+            return 0;
+            // if (t2->kind != Type_Kind_Enum) return 0;
+            // return t1 == t2;
+        }
+
+        case Type_Kind_Function: {
+            if (t2->kind != Type_Kind_Function) return 0;
+            if (t1->Function.param_count != t2->Function.param_count) return 0;
+
+            fori (i, 0, t1->Function.param_count - 1) {
+                if (!types_are_compatible(t1->Function.params[i], t2->Function.params[i])) return 0;
+            }
+
+            return 1;
         }
 
         default:
@@ -162,7 +175,7 @@ u32 type_size_of(Type* type) {
     switch (type->kind) {
         case Type_Kind_Basic:    return type->Basic.size;
         case Type_Kind_Pointer:  return 4;
-        case Type_Kind_Function: return 0;
+        case Type_Kind_Function: return 4;
         case Type_Kind_Array:    return type->Array.size;
         case Type_Kind_Struct:   return type->Struct.size;
         case Type_Kind_Enum:     return type_size_of(type->Enum.backing);
@@ -176,7 +189,7 @@ u32 type_alignment_of(Type* type) {
     switch (type->kind) {
         case Type_Kind_Basic:    return type->Basic.alignment;
         case Type_Kind_Pointer:  return 4;
-        case Type_Kind_Function: return 1;
+        case Type_Kind_Function: return 4;
         case Type_Kind_Array:    return type_alignment_of(type->Array.elem);
         case Type_Kind_Struct:   return type->Struct.aligment;
         case Type_Kind_Enum:     return type_alignment_of(type->Enum.backing);
@@ -280,7 +293,7 @@ Type* type_build_from_ast(bh_allocator alloc, AstType* type_node) {
             }
 
             s_type->Struct.aligment = alignment;
-            
+
             if (offset % alignment != 0) {
                 offset += alignment - (offset % alignment);
             }
@@ -371,6 +384,9 @@ const char* type_get_name(Type* type) {
                 return type->Enum.name;
             else
                 return "<anonymous enum>";
+
+        case Type_Kind_Function: return bh_aprintf(global_scratch_allocator, "proc (...) -> %s", type_get_name(type->Function.return_type));
+
         default: return "unknown";
     }
 }
index 409fb1d0cded0fab91c4875772ea88f7559d87c8..966b9ca82dd4a9abef9a3b749bd06d4f7b1e8e65 100644 (file)
@@ -217,6 +217,10 @@ static WasmType onyx_type_to_wasm_type(Type* type) {
         return WASM_TYPE_INT32;
     }
 
+    if (type->kind == Type_Kind_Function) {
+        return WASM_TYPE_INT32;
+    }
+
     if (type->kind == Type_Kind_Basic) {
         TypeBasic* basic = &type->Basic;
         if (basic->flags & Basic_Flag_Boolean) return WASM_TYPE_INT32;
@@ -235,6 +239,8 @@ static WasmType onyx_type_to_wasm_type(Type* type) {
     return WASM_TYPE_VOID;
 }
 
+static i32 generate_type_idx(OnyxWasmModule* mod, Type* ft);
+
 #define WI(instr) bh_arr_push(code, ((WasmInstruction){ instr, 0x00 }))
 #define WID(instr, data) bh_arr_push(code, ((WasmInstruction){ instr, data }))
 #define COMPILE_FUNC(kind, ...) static void compile_ ## kind (OnyxWasmModule* mod, bh_arr(WasmInstruction)* pcode, __VA_ARGS__)
@@ -398,6 +404,10 @@ COMPILE_FUNC(store_instruction, Type* type, u32 alignment, u32 offset) {
         type = type->Enum.backing;
     }
 
+    if (type->kind == Type_Kind_Function) {
+        type = &basic_types[Basic_Kind_U32];
+    }
+
     i32 store_size = type_size_of(type);
     i32 is_basic    = type->kind == Type_Kind_Basic || type->kind == Type_Kind_Pointer;
     i32 is_pointer  = is_basic && (type->Basic.flags & Basic_Flag_Pointer);
@@ -430,6 +440,10 @@ COMPILE_FUNC(load_instruction, Type* type, u32 offset) {
         type = type->Enum.backing;
     }
 
+    if (type->kind == Type_Kind_Function) {
+        type = &basic_types[Basic_Kind_U32];
+    }
+
     i32 load_size   = type_size_of(type);
     i32 is_basic    = type->kind == Type_Kind_Basic || type->kind == Type_Kind_Pointer;
     i32 is_pointer  = is_basic && (type->Basic.flags & Basic_Flag_Pointer);
@@ -718,8 +732,15 @@ COMPILE_FUNC(call, AstCall* call) {
         compile_expression(mod, &code, arg->value);
     }
 
-    i32 func_idx = (i32) bh_imap_get(&mod->index_map, (u64) call->callee);
-    bh_arr_push(code, ((WasmInstruction){ WI_CALL, func_idx }));
+    if (call->callee->kind == Ast_Kind_Function) {
+        i32 func_idx = (i32) bh_imap_get(&mod->index_map, (u64) call->callee);
+        bh_arr_push(code, ((WasmInstruction){ WI_CALL, func_idx }));
+    } else {
+        compile_expression(mod, &code, call->callee);
+
+        i32 type_idx = generate_type_idx(mod, call->callee->type);
+        WID(WI_CALL_INDIRECT, ((WasmInstructionData) { type_idx, 0x00 }));
+    }
 
     *pcode = code;
 }
@@ -893,6 +914,12 @@ COMPILE_FUNC(expression, AstTyped* expr) {
             break;
         }
 
+        case Ast_Kind_Function: {
+            i32 funcidx = bh_imap_get(&mod->index_map, (u64) expr);
+            WID(WI_I32_CONST, funcidx);
+            break;
+        }
+
         case Ast_Kind_Block:          compile_block(mod, &code, (AstBlock *) expr); break;
         case Ast_Kind_Call:           compile_call(mod, &code, (AstCall *) expr); break;
         case Ast_Kind_Intrinsic_Call: compile_intrinsic_call(mod, &code, (AstIntrinsicCall *) expr); break;
@@ -1113,12 +1140,14 @@ COMPILE_FUNC(return, AstReturn* ret) {
     *pcode = code;
 }
 
-static i32 generate_type_idx(OnyxWasmModule* mod, AstFunction* fd) {
+static i32 generate_type_idx(OnyxWasmModule* mod, Type* ft) {
+    if (ft->kind != Type_Kind_Function) return -1;
+
     static char type_repr_buf[128];
 
     char* t = type_repr_buf;
-    Type** param_type = fd->type->Function.params;
-    i32 param_count = fd->type->Function.param_count;
+    Type** param_type = ft->Function.params;
+    i32 param_count = ft->Function.param_count;
     i32 params_left = param_count;
     while (params_left-- > 0) {
         // HACK: Using these directly as part of a string feels weird but they are
@@ -1128,7 +1157,7 @@ static i32 generate_type_idx(OnyxWasmModule* mod, AstFunction* fd) {
     }
     *(t++) = ':';
 
-    WasmType return_type = onyx_type_to_wasm_type(fd->type->Function.return_type);
+    WasmType return_type = onyx_type_to_wasm_type(ft->Function.return_type);
     *(t++) = (char) return_type;
     *t = '\0';
 
@@ -1177,7 +1206,7 @@ static inline b32 should_compile_function(AstFunction* fd) {
 static void compile_function(OnyxWasmModule* mod, AstFunction* fd) {
     if (!should_compile_function(fd)) return;
 
-    i32 type_idx = generate_type_idx(mod, fd);
+    i32 type_idx = generate_type_idx(mod, fd->type);
 
     if (fd->flags & Ast_Flag_Foreign) {
         WasmImport import = {
@@ -1394,6 +1423,8 @@ OnyxWasmModule onyx_wasm_module_create(bh_allocator alloc) {
         .data = NULL,
         .next_datum_offset = 0,
 
+        .element_count = 0,
+
         .structured_jump_target = NULL,
     };
 
@@ -1431,6 +1462,7 @@ void onyx_wasm_module_compile(OnyxWasmModule* module, ProgramInfo* program) {
         switch (entity->type) {
             case Entity_Type_Function_Header: {
                 if (!should_compile_function(entity->function)) break;
+                module->element_count++;
 
                 u64 func_idx;
                 if ((entity->function->flags & Ast_Flag_Foreign) != 0)
@@ -1467,7 +1499,7 @@ void onyx_wasm_module_compile(OnyxWasmModule* module, ProgramInfo* program) {
 
             case Entity_Type_Memory_Reservation: {
                 compile_memory_reservation(module, (AstMemRes *) entity->mem_res);
-                
+
                 // HACK: To put this here
                 // NOTE: Round up to the nearest multiple of 16
                 builtin_heap_start.value.i =
@@ -1629,6 +1661,30 @@ static i32 output_funcsection(OnyxWasmModule* module, bh_buffer* buff) {
     return buff->length - prev_len;
 }
 
+static i32 output_tablesection(OnyxWasmModule* module, bh_buffer* buff) {
+    i32 prev_len = buff->length;
+    bh_buffer_write_byte(buff, WASM_SECTION_ID_TABLE);
+
+    bh_buffer vec_buff;
+    bh_buffer_init(&vec_buff, buff->allocator, 128);
+
+    i32 leb_len;
+    u8* leb = uint_to_uleb128((u64) 1, &leb_len);
+    bh_buffer_append(&vec_buff, leb, leb_len);
+
+    // NOTE: funcrefs are the only valid table element type
+    bh_buffer_write_byte(&vec_buff, 0x70);
+    output_limits(module->element_count, -1, &vec_buff);
+
+    leb = uint_to_uleb128((u64) (vec_buff.length), &leb_len);
+    bh_buffer_append(buff, leb, leb_len);
+
+    bh_buffer_concat(buff, vec_buff);
+    bh_buffer_free(&vec_buff);
+
+    return buff->length - prev_len;
+}
+
 static i32 output_memorysection(OnyxWasmModule* module, bh_buffer* buff) {
     i32 prev_len = buff->length;
     bh_buffer_write_byte(buff, WASM_SECTION_ID_MEMORY);
@@ -1774,6 +1830,46 @@ static i32 output_startsection(OnyxWasmModule* module, bh_buffer* buff) {
     return buff->length - prev_len;
 }
 
+static i32 output_elemsection(OnyxWasmModule* module, bh_buffer* buff) {
+    i32 prev_len = buff->length;
+
+    bh_buffer_write_byte(buff, WASM_SECTION_ID_ELEMENT);
+
+    bh_buffer vec_buff;
+    bh_buffer_init(&vec_buff, buff->allocator, 128);
+
+    u32 func_count = bh_arr_length(module->funcs) + module->next_foreign_func_idx;
+
+    i32 leb_len;
+    u8* leb;
+
+    // NOTE: 0x01 count of elems
+    bh_buffer_write_byte(&vec_buff, 0x01);
+
+    // NOTE: 0x00 table index
+    bh_buffer_write_byte(&vec_buff, 0x00);
+
+    bh_buffer_write_byte(&vec_buff, WI_I32_CONST);
+    bh_buffer_write_byte(&vec_buff, 0x00);
+    bh_buffer_write_byte(&vec_buff, WI_BLOCK_END);
+
+    leb = uint_to_uleb128((u64) func_count, &leb_len);
+    bh_buffer_append(&vec_buff, leb, leb_len);
+
+    fori (funcidx, 0, func_count - 1) {
+        leb = uint_to_uleb128((u64) funcidx, &leb_len);
+        bh_buffer_append(&vec_buff, leb, leb_len);
+    }
+
+    leb = uint_to_uleb128((u64) (vec_buff.length), &leb_len);
+    bh_buffer_append(buff, leb, leb_len);
+
+    bh_buffer_concat(buff, vec_buff);
+    bh_buffer_free(&vec_buff);
+
+    return buff->length - prev_len;
+}
+
 static i32 output_locals(WasmFunc* func, bh_buffer* buff) {
     i32 prev_len = buff->length;
 
@@ -1836,6 +1932,7 @@ static void output_instruction(WasmInstruction* instr, bh_buffer* buff) {
             bh_buffer_append(buff, leb, leb_len);
             break;
 
+        case WI_CALL_INDIRECT:
         case WI_I32_STORE:
         case WI_I32_STORE_8:
         case WI_I32_STORE_16:
@@ -1978,10 +2075,12 @@ void onyx_wasm_module_write_to_file(OnyxWasmModule* module, bh_file file) {
     output_typesection(module, &master_buffer);
     output_importsection(module, &master_buffer);
     output_funcsection(module, &master_buffer);
+    output_tablesection(module, &master_buffer);
     output_memorysection(module, &master_buffer);
     output_globalsection(module, &master_buffer);
     output_exportsection(module, &master_buffer);
     output_startsection(module, &master_buffer);
+    output_elemsection(module, &master_buffer);
     output_codesection(module, &master_buffer);
     output_datasection(module, &master_buffer);