add 'memory_equal' intrinsic
authorJudah Caruso <judah@tuta.io>
Tue, 5 Dec 2023 06:17:38 +0000 (23:17 -0700)
committerJudah Caruso <judah@tuta.io>
Tue, 5 Dec 2023 06:17:38 +0000 (23:17 -0700)
compiler/include/astnodes.h
compiler/include/wasm_emit.h
compiler/src/builtins.c
compiler/src/parser.c
compiler/src/wasm_emit.c
compiler/src/wasm_intrinsics.h
core/alloc/heap.onyx
core/builtin.onyx
core/intrinsics/wasm.onyx

index 0c294da85b9975e627568688a9eafeaf2a4ef14a..14cb5df72e39750d36b5bcfc21c5b91609342dfe 100644 (file)
@@ -385,6 +385,7 @@ typedef enum OnyxIntrinsic {
 
     ONYX_INTRINSIC_MEMORY_SIZE, ONYX_INTRINSIC_MEMORY_GROW,
     ONYX_INTRINSIC_MEMORY_COPY, ONYX_INTRINSIC_MEMORY_FILL,
+    ONYX_INTRINSIC_MEMORY_EQUAL,
 
     ONYX_INTRINSIC_INITIALIZE,
 
@@ -1293,7 +1294,7 @@ struct AstImport {
     // be imported as. If NULL, the last token of
     // the package path is used. The following
     // imports the core package as C.
-    // 
+    //
     //     use core { C :: package }
     //
     OnyxToken *qualified_package_name;
@@ -1798,7 +1799,7 @@ typedef struct CheckerData {
 
 typedef struct ContextCaches {
     bh_imap implicit_cast_to_bool_cache;
-} ContextCaches; 
+} ContextCaches;
 
 typedef struct DefinedVariable {
     char *key;
index d3cfe966de2d98b3cf822575cf81470d75f27239..81c4ada8d64059b74536934ad83332e06c130153 100644 (file)
@@ -422,8 +422,8 @@ typedef enum WasmInstructionType {
     WI_I32X4_TRUNC_SAT_F32X4_U       = SIMD_INSTR_MASK | 249,
     WI_F32X4_CONVERT_I32X4_S         = SIMD_INSTR_MASK | 250,
     WI_F32X4_CONVERT_I32X4_U         = SIMD_INSTR_MASK | 251,
-    
-    
+
+
     WI_MEMORY_INIT                   = EXT_INSTR_MASK | 0x08,
     WI_MEMORY_COPY                   = EXT_INSTR_MASK | 0x0a,
     WI_MEMORY_FILL                   = EXT_INSTR_MASK | 0x0b,
@@ -699,7 +699,7 @@ typedef struct OnyxWasmModule {
     bh_imap elem_map;
 
     bh_arr(DeferredStmt)   deferred_stmts;
-    bh_arr(AllocatedSpace) local_allocations; 
+    bh_arr(AllocatedSpace) local_allocations;
 
     bh_arr(PatchInfo) stack_leave_patches;
     bh_arr(DatumPatchInfo) data_patches;
@@ -792,7 +792,7 @@ void onyx_wasm_module_free(OnyxWasmModule* module);
 void onyx_wasm_module_write_to_buffer(OnyxWasmModule* module, bh_buffer* buffer);
 void onyx_wasm_module_write_to_file(OnyxWasmModule* module, bh_file file);
 
-#ifdef ONYX_RUNTIME_LIBRARY 
+#ifdef ONYX_RUNTIME_LIBRARY
 void onyx_run_initialize(b32 debug_enabled);
 b32 onyx_run_wasm(bh_buffer code_buffer, int argc, char *argv[]);
 #endif
index c78a7f589b76ce684c426adea6f4cafb6aa38f00..e9d3b5af3ca1c4808d708d5b685dabbc08970f82 100644 (file)
@@ -115,10 +115,11 @@ IntrinsicTable intrinsic_table;
 static IntrinsicMap builtin_intrinsics[] = {
     { "unreachable",  ONYX_INTRINSIC_UNREACHABLE },
 
-    { "memory_size",  ONYX_INTRINSIC_MEMORY_SIZE },
-    { "memory_grow",  ONYX_INTRINSIC_MEMORY_GROW },
-    { "memory_copy",  ONYX_INTRINSIC_MEMORY_COPY },
-    { "memory_fill",  ONYX_INTRINSIC_MEMORY_FILL },
+    { "memory_size",  ONYX_INTRINSIC_MEMORY_SIZE  },
+    { "memory_grow",  ONYX_INTRINSIC_MEMORY_GROW  },
+    { "memory_copy",  ONYX_INTRINSIC_MEMORY_COPY  },
+    { "memory_fill",  ONYX_INTRINSIC_MEMORY_FILL  },
+    { "memory_equal", ONYX_INTRINSIC_MEMORY_EQUAL },
 
     { "__initialize", ONYX_INTRINSIC_INITIALIZE },
 
@@ -551,12 +552,12 @@ void initialize_builtins(bh_allocator a) {
 
     fori (i, 0, Binary_Op_Count) {
         operator_overloads[i] = NULL;
-        bh_arr_new(global_heap_allocator, operator_overloads[i], 4); 
+        bh_arr_new(global_heap_allocator, operator_overloads[i], 4);
     }
 
     fori (i, 0, Unary_Op_Count) {
         unary_operator_overloads[i] = NULL;
-        bh_arr_new(global_heap_allocator, unary_operator_overloads[i], 4); 
+        bh_arr_new(global_heap_allocator, unary_operator_overloads[i], 4);
     }
 
     IntrinsicMap* intrinsic = &builtin_intrinsics[0];
@@ -584,7 +585,7 @@ void initalize_special_globals() {
 
 void introduce_build_options(bh_allocator a) {
     Package* p = package_lookup_or_create("runtime", context.global_scope, a, context.global_scope->created_at);
-    
+
     // HACK creating this for later
     package_lookup_or_create("runtime.vars", p->scope, a, context.global_scope->created_at);
 
index a9ef24a8ae0377386a501631f220599fcdd5411b..ded751fff4604220e7ba9d32ec193a46e8566390 100644 (file)
@@ -713,7 +713,7 @@ static AstTyped* parse_factor(OnyxParser* parser) {
 
                 if (parser->current_function_stack && bh_arr_length(parser->current_function_stack) > 0) {
                     bh_arr_push(bh_arr_last(parser->current_function_stack)->nodes_that_need_entities_after_clone, (AstNode *) fc);
-                    
+
                 } else {
                     ENTITY_SUBMIT(fc);
                 }
@@ -883,7 +883,7 @@ static AstTyped* parse_factor(OnyxParser* parser) {
                 export_name->token = parser->curr - 1;
                 export_name->func  = (AstFunction *) parse_factor(parser);
                 export_name->type_node = builtin_string_type;
-                
+
                 retval = (AstTyped *) export_name;
                 break;
             }
@@ -993,7 +993,7 @@ static AstTyped* parse_factor(OnyxParser* parser) {
                 }
 
                 method_call->right = (AstTyped *) parse_function_call(parser, (AstTyped *) method);
-                
+
                 retval = (AstTyped *) method_call;
                 break;
             }
@@ -1050,7 +1050,7 @@ static inline i32 get_precedence(BinaryOp kind) {
         case Binary_Op_Divide:          return 8;
 
         case Binary_Op_Modulus:         return 9;
-        
+
         case Binary_Op_Coalesce:        return 11;
 
         default:                        return -1;
@@ -1348,10 +1348,10 @@ static AstSwitchCase* parse_case_stmt(OnyxParser* parser) {
             b32 is_pointer = 0;
             if (consume_token_if_next(parser, '&') || consume_token_if_next(parser, '^'))
                 is_pointer = 1;
-            
+
             OnyxToken *capture_symbol = expect_token(parser, Token_Type_Symbol);
             AstLocal *capture = make_local(parser->allocator, capture_symbol, NULL);
-            
+
             sc_node->capture = capture;
             sc_node->capture_is_by_pointer = is_pointer;
 
@@ -2248,7 +2248,7 @@ static AstStructType* parse_struct(OnyxParser* parser) {
     }
 
     expect_token(parser, '{');
-    
+
     parser->current_scope = scope_symbols_in_structures_should_be_bound_to;
 
     b32 member_is_used = 0;
@@ -3668,7 +3668,7 @@ static void parse_top_level_statement(OnyxParser* parser) {
                 //      #match
                 //      something :: (....) {
                 //      }
-                // 
+                //
                 // This will make converting something to a overloaded
                 // function easier and require less copying by the programmer.
                 if (next_tokens_are(parser, 2, ':', ':')) {
@@ -3715,7 +3715,7 @@ static void parse_top_level_statement(OnyxParser* parser) {
                     inject->documentation = parser->last_documentation_token;
                     parser->last_documentation_token = NULL;
                 }
-                
+
                 ENTITY_SUBMIT(inject);
                 return;
             }
index 93d1f395ecd555b70e528acf58b1d5676557ff79..4f0e0d4d02ec6dfceb16a479b4fea3b4b9856223 100644 (file)
@@ -317,7 +317,7 @@ static void debug_emit_instruction(OnyxWasmModule *mod, OnyxToken *token) {
         return;
     }
 
-    DebugContext *ctx = mod->debug_context; 
+    DebugContext *ctx = mod->debug_context;
     assert(ctx && ctx->last_token);
 
     // Sanity check
@@ -337,7 +337,7 @@ static void debug_emit_instruction(OnyxWasmModule *mod, OnyxToken *token) {
             repeat_previous = 1;
         }
     }
-    
+
     if (repeat_previous) {
         // Output / increment REP instruction
         if (ctx->last_op_was_rep) {
@@ -551,6 +551,7 @@ EMIT_FUNC(values_into_contiguous_memory,   u64 base_ptr_local, Type *type, u32 o
 EMIT_FUNC(struct_literal_into_contiguous_memory, AstStructLiteral* sl, u64 base_ptr_local, u32 offset);
 EMIT_FUNC(wasm_copy,                       OnyxToken *token);
 EMIT_FUNC(wasm_fill,                       OnyxToken *token);
+EMIT_FUNC(wasm_memory_equal,               OnyxToken *token);
 
 EMIT_FUNC(enter_structured_block,        StructuredBlockType sbt, OnyxToken* block_token);
 EMIT_FUNC_NO_ARGS(leave_structured_block);
@@ -1316,7 +1317,7 @@ EMIT_FUNC(for_range, AstFor* for_node, u64 iter_local) {
     WIL(for_node->token, WI_LOCAL_GET, step_local);
     WI(for_node->token, WI_I32_ADD);
     WIL(for_node->token, WI_LOCAL_SET, iter_local);
-    
+
     if (for_node->has_first) {
         WIL(for_node->token, WI_I32_CONST, 0);
         WIL(for_node->token, WI_LOCAL_SET, for_node->first_local);
@@ -1743,7 +1744,7 @@ EMIT_FUNC(switch, AstSwitch* switch_node) {
                 WIL(NULL, WI_LOCAL_GET, union_capture_idx);
                 WIL(NULL, WI_PTR_CONST, union_expr_type->Union.alignment);
                 WI(NULL, WI_PTR_ADD);
-                
+
                 WIL(NULL, WI_I32_CONST, type_size_of(sc->capture->type));
                 emit_wasm_copy(mod, &code, NULL);
             }
@@ -2145,7 +2146,7 @@ EMIT_FUNC(call, AstCall* call) {
                 emit_store_instruction(mod, &code, &basic_types[Basic_Kind_Type_Index], reserve_size + 4);
 
                 local_raw_free(mod->local_alloc, WASM_TYPE_PTR);
-                
+
                 WIL(call_token, WI_LOCAL_GET, stack_top_store_local);
                 WIL(call_token, WI_I32_CONST, reserve_size);
                 WI(call_token, WI_I32_ADD);
@@ -2239,7 +2240,7 @@ EMIT_FUNC(call, AstCall* call) {
 
     } else {
         emit_expression(mod, &code, call->callee);
-        
+
         u64 global_closure_base_idx = bh_imap_get(&mod->index_map, (u64) &builtin_closure_base);
         WI(NULL, WI_DROP);
         WIL(NULL, WI_GLOBAL_SET, global_closure_base_idx);
@@ -2326,7 +2327,7 @@ EMIT_FUNC(method_call, AstBinaryOp *mcall) {
     } else {
         first_arg->value = (AstTyped *) tmp_local;
     }
-    
+
     //
     // Actually emit the function call.
     emit_call(mod, &code, call_node);
@@ -2407,7 +2408,7 @@ EMIT_FUNC(intrinsic_call, AstCall* call) {
 
     switch (call->intrinsic) {
         case ONYX_INTRINSIC_UNREACHABLE:  WI(call->token, WI_UNREACHABLE); break;
-        
+
         case ONYX_INTRINSIC_MEMORY_SIZE:  WID(call->token, WI_MEMORY_SIZE, 0x00); break;
         case ONYX_INTRINSIC_MEMORY_GROW:  WID(call->token, WI_MEMORY_GROW, 0x00); break;
         case ONYX_INTRINSIC_MEMORY_COPY:
@@ -2416,6 +2417,9 @@ EMIT_FUNC(intrinsic_call, AstCall* call) {
         case ONYX_INTRINSIC_MEMORY_FILL:
             emit_wasm_fill(mod, &code, call->token);
             break;
+        case ONYX_INTRINSIC_MEMORY_EQUAL:
+            emit_wasm_memory_equal(mod, &code, call->token);
+            break;
 
         case ONYX_INTRINSIC_INITIALIZE: {
             Type* type_to_initialize = ((AstArgument *) call->args.values[0])->value->type->Pointer.elem;
@@ -3007,6 +3011,12 @@ EMIT_FUNC(wasm_fill, OnyxToken *token) {
     *pcode = code;
 }
 
+EMIT_FUNC(wasm_memory_equal, OnyxToken *token) {
+    bh_arr(WasmInstruction) code = *pcode;
+    emit_intrinsic_memory_equal(mod, &code);
+    *pcode = code;
+}
+
 EMIT_FUNC(values_into_contiguous_memory, u64 base_ptr_local, Type *type, u32 offset, i32 value_count, AstTyped **values) {
     bh_arr(WasmInstruction) code = *pcode;
 
@@ -3525,7 +3535,7 @@ EMIT_FUNC(expression, AstTyped* expr) {
 
             u64 capture_block_ptr = local_raw_allocate(mod->local_alloc, WASM_TYPE_PTR);
             WIL(NULL, WI_LOCAL_TEE, capture_block_ptr);
-            
+
             // Populate the block
             bh_arr_each(AstCaptureLocal *, capture, func->captures->captures) {
                 WIL(NULL, WI_LOCAL_GET, capture_block_ptr);
@@ -3538,7 +3548,7 @@ EMIT_FUNC(expression, AstTyped* expr) {
 
                 emit_store_instruction(mod, &code, (*capture)->type, (*capture)->offset);
             }
-            
+
             local_raw_free(mod->local_alloc, WASM_TYPE_PTR);
 
             WIL(NULL, WI_I32_CONST, func->captures->total_size_in_bytes);
@@ -3624,7 +3634,7 @@ EMIT_FUNC(expression, AstTyped* expr) {
                     } else {
                         WIL(NULL, WI_I32_CONST, type_alignment_of(field->expr->type));
                     }
-                    
+
                     WI(NULL, WI_I32_ADD);
 
                     WIL(NULL, WI_I32_CONST, type_size_of(field->type->Union.variants_ordered[1]->type));
@@ -4326,7 +4336,7 @@ static void emit_function(OnyxWasmModule* mod, AstFunction* fd) {
     if (fd == builtin_initialize_data_segments && !mod->doing_linking) {
         // This is a large hack, but is necessary.
         // This particular function (__initialize_data_segments) should not be generated
-        // until the module is in its linking phase. This is because we have to wait 
+        // until the module is in its linking phase. This is because we have to wait
         // until we have reached the linking phase to know every data element that is
         // and will be present. Without this, the generating data segments in the bodies
         // of the functions is not possible.
@@ -4379,7 +4389,7 @@ static void emit_function(OnyxWasmModule* mod, AstFunction* fd) {
         u64 localidx = 0;
         bh_arr_each(AstParam, param, fd->params) {
             switch (type_get_param_pass(param->local->type)) {
-                case Param_Pass_By_Multiple_Values: 
+                case Param_Pass_By_Multiple_Values:
                     debug_introduce_symbol(mod, param->local->token, DSL_REGISTER, localidx | LOCAL_IS_WASM, param->local->type);
                     bh_imap_put(&mod->local_map, (u64) param->local, localidx | LOCAL_IS_WASM);
                     localidx += type_structlike_mem_count(param->local->type);
@@ -4618,7 +4628,7 @@ static void emit_raw_string(OnyxWasmModule* mod, char *data, i32 len, u64 *out_d
         bh_free(global_heap_allocator, strdata);
         return;
     }
-    
+
     // :ProperLinking
     // The length used here is one greater than the string length, because
     // we DO want to include the null-terminator in the outputted string.
@@ -4642,7 +4652,7 @@ static void emit_string_literal(OnyxWasmModule* mod, AstStrLit* strlit) {
 
 static u32 emit_data_entry(OnyxWasmModule *mod, WasmDatum *datum) {
     datum->offset_ = 0;
-    datum->id = NEXT_DATA_ID(mod); 
+    datum->id = NEXT_DATA_ID(mod);
     bh_arr_push(mod->data, *datum);
     return datum->id;
 }
@@ -4933,7 +4943,7 @@ static void emit_file_contents(OnyxWasmModule* mod, AstFileContents* fc) {
         if (parent_file == NULL) parent_file = ".";
 
         char* parent_folder = bh_path_get_parent(parent_file, global_scratch_allocator);
-        
+
         OnyxToken *filename_token = fc->filename_expr->token;
 
         token_toggle_end(filename_token);
@@ -5325,7 +5335,7 @@ b32 onyx_wasm_build_link_options_from_node(OnyxWasmLinkOptions *opts, AstTyped *
     assert(builtin_link_options_type);
 
     Type *link_options_type = type_build_from_ast(context.ast_alloc, builtin_link_options_type);
-    
+
     AstStructLiteral *input = (AstStructLiteral *) node;
 
     StructMember smem;
index f3f72e6ec17325bd7e3dd95205de0ab8f27c5748..8e107f1601d5fc9bb689ab129be2d6b20361a49b 100644 (file)
 // :32BitPointers
 EMIT_FUNC_NO_ARGS(intrinsic_memory_copy) {
     bh_arr(WasmInstruction) code = *pcode;
-    
+
     // The stack should look like this:
     //     <count>
     //     <source>
     //     <dest>
-    
+
     u64 count_local  = local_raw_allocate(mod->local_alloc, WASM_TYPE_INT32);
     u64 source_local = local_raw_allocate(mod->local_alloc, WASM_TYPE_PTR);
     u64 dest_local   = local_raw_allocate(mod->local_alloc, WASM_TYPE_PTR);
-    
+
     WIL(NULL, WI_LOCAL_SET, count_local);
     WIL(NULL, WI_LOCAL_SET, source_local);
     WIL(NULL, WI_LOCAL_SET, dest_local);
-    
+
     // count is greater than 0
     WIL(NULL, WI_LOCAL_GET, count_local);
     WID(NULL, WI_I32_CONST, 0);
     WI(NULL, WI_I32_GT_S);
-    
+
     WID(NULL, WI_IF_START, 0x40);
     WID(NULL, WI_LOOP_START, 0x40);
-    
+
     WIL(NULL, WI_LOCAL_GET, count_local);
     WID(NULL, WI_I32_CONST, 1);
     WI(NULL, WI_I32_SUB);
     WIL(NULL, WI_LOCAL_SET, count_local);
-    
+
     WIL(NULL, WI_LOCAL_GET, dest_local);
     WIL(NULL, WI_LOCAL_GET, count_local);
     WI(NULL, WI_PTR_ADD);
-    
+
     WIL(NULL, WI_LOCAL_GET, source_local);
     WIL(NULL, WI_LOCAL_GET, count_local);
     WI(NULL, WI_PTR_ADD);
-    
+
     WID(NULL, WI_I32_LOAD_8_U, ((WasmInstructionData) { 0, 0 }));
     WID(NULL, WI_I32_STORE_8, ((WasmInstructionData) { 0, 0 }));
-    
+
     WIL(NULL, WI_LOCAL_GET, count_local);
     WID(NULL, WI_I32_CONST, 0);
     WI(NULL, WI_I32_GT_S);
     WID(NULL, WI_COND_JUMP, 0x00);
-    
+
     WI(NULL, WI_LOOP_END);
     WI(NULL, WI_IF_END);
-    
+
     local_raw_free(mod->local_alloc, WASM_TYPE_INT32);
     local_raw_free(mod->local_alloc, WASM_TYPE_PTR);
     local_raw_free(mod->local_alloc, WASM_TYPE_PTR);
-    
+
     *pcode = code;
 }
 
 EMIT_FUNC_NO_ARGS(intrinsic_memory_fill) {
     bh_arr(WasmInstruction) code = *pcode;
-    
+
     // The stack should look like this:
     //     <count>
     //     <byte>
     //     <dest>
-    
+
     u64 count_local = local_raw_allocate(mod->local_alloc, WASM_TYPE_INT32);
     u64 byte_local  = local_raw_allocate(mod->local_alloc, WASM_TYPE_INT32);
     u64 dest_local  = local_raw_allocate(mod->local_alloc, WASM_TYPE_PTR);
-    
+
     WIL(NULL, WI_LOCAL_SET, count_local);
     WIL(NULL, WI_LOCAL_SET, byte_local);
     WIL(NULL, WI_LOCAL_SET, dest_local);
-    
+
     // count is greater than 0
     WIL(NULL, WI_LOCAL_GET, count_local);
     WID(NULL, WI_I32_CONST, 0);
     WI(NULL, WI_I32_GT_S);
-    
+
     WID(NULL, WI_IF_START, 0x40);
     WID(NULL, WI_LOOP_START, 0x40);
-    
+
     WIL(NULL, WI_LOCAL_GET, count_local);
     WID(NULL, WI_I32_CONST, 1);
     WI(NULL, WI_I32_SUB);
     WIL(NULL, WI_LOCAL_SET, count_local);
-    
+
     WIL(NULL, WI_LOCAL_GET, dest_local);
     WIL(NULL, WI_LOCAL_GET, count_local);
     WI(NULL, WI_PTR_ADD);
-    
+
     WIL(NULL, WI_LOCAL_GET, byte_local);
     WID(NULL, WI_I32_STORE_8, ((WasmInstructionData) { 0, 0 }));
-    
+
     WIL(NULL, WI_LOCAL_GET, count_local);
     WID(NULL, WI_I32_CONST, 0);
     WI(NULL, WI_I32_GT_S);
     WID(NULL, WI_COND_JUMP, 0x00);
-    
+
     WI(NULL, WI_LOOP_END);
     WI(NULL, WI_IF_END);
-    
+
     local_raw_free(mod->local_alloc, WASM_TYPE_INT32);
     local_raw_free(mod->local_alloc, WASM_TYPE_INT32);
     local_raw_free(mod->local_alloc, WASM_TYPE_PTR);
-    
+
     *pcode = code;
 }
 
+// IMPROVE: This is a stripped down/unoptimized version of memcmp that only checks equality
+// between two blocks of memory (and doesn't return -1/1). Returns 1 if the blocks of memory are equal,
+// and 0 otherwise.
+// NOTE: This implementation is unsafe and assumes both blocks of memory are at least size_in_bytes long.
+EMIT_FUNC_NO_ARGS(intrinsic_memory_equal) {
+    bh_arr(WasmInstruction) code = *pcode;
+
+    // The stack should look like this:
+    //     <size in bytes>
+    //     <ptr>
+    //     <ptr>
+
+    u64 size_in_bytes = local_raw_allocate(mod->local_alloc, WASM_TYPE_INT32);
+    u64 b_addr        = local_raw_allocate(mod->local_alloc, WASM_TYPE_PTR);
+    u64 a_addr        = local_raw_allocate(mod->local_alloc, WASM_TYPE_PTR);
+
+    WIL(NULL, WI_LOCAL_SET, size_in_bytes);
+    WIL(NULL, WI_LOCAL_SET, b_addr);
+    WIL(NULL, WI_LOCAL_SET, a_addr);
+
+    u64 memory_is_equal = local_raw_allocate(mod->local_alloc, WASM_TYPE_INT32);
+    WID(NULL, WI_I32_CONST, 1); // set to 1 so size_in_bytes == 0 will return true
+    WIL(NULL, WI_LOCAL_SET, memory_is_equal);
+
+    // if size_in_bytes > 0
+    WIL(NULL, WI_LOCAL_GET, size_in_bytes);
+    WID(NULL, WI_I32_CONST, 0);
+    WI (NULL, WI_I32_GT_S);
+    WID(NULL, WI_IF_START, 0x40);
+        u64 loop_idx = local_raw_allocate(mod->local_alloc, WASM_TYPE_INT32);
+        WIL(NULL, WI_I32_CONST, 0);
+        WIL(NULL, WI_LOCAL_SET, loop_idx);
+
+        WID(NULL, WI_LOOP_START, 0x40);
+            // if loop_idx >= size_in_bytes, break
+            WIL(NULL, WI_LOCAL_GET, loop_idx);
+            WIL(NULL, WI_LOCAL_GET, size_in_bytes);
+            WI (NULL, WI_I32_GE_S);
+            WID(NULL, WI_COND_JUMP, 0x01);
+
+            // a = *(a_addr + loop_idx)
+            WIL(NULL, WI_LOCAL_GET, a_addr);
+            WIL(NULL, WI_LOCAL_GET, loop_idx);
+            WI (NULL, WI_PTR_ADD);
+            WID(NULL, WI_I32_LOAD_8_U, ((WasmInstructionData) { 0, 0 }));
+
+            // b = *(b_addr + loop_idx)
+            WIL(NULL, WI_LOCAL_GET, b_addr);
+            WIL(NULL, WI_LOCAL_GET, loop_idx);
+            WI (NULL, WI_PTR_ADD);
+            WID(NULL, WI_I32_LOAD_8_U, ((WasmInstructionData) { 0, 0 }));
+
+            WI (NULL, WI_I32_EQ);
+            WIL(NULL, WI_LOCAL_SET, memory_is_equal);
+
+            // if bytes are not equal, break
+            WIL(NULL, WI_LOCAL_GET, memory_is_equal);
+            WI (NULL, WI_I32_EQZ);
+            WID(NULL, WI_COND_JUMP, 0x01);
+
+            // loop_idx += 1
+            WIL(NULL, WI_LOCAL_GET, loop_idx);
+            WIL(NULL, WI_I32_CONST, 1);
+            WI (NULL, WI_I32_ADD);
+            WIL(NULL, WI_LOCAL_SET, loop_idx);
+
+            WID(NULL, WI_JUMP, 0x00);
+        WI(NULL, WI_LOOP_END);
+    WI(NULL, WI_IF_END);
+
+    WIL(NULL, WI_LOCAL_GET, memory_is_equal);
+
+    local_raw_free(mod->local_alloc, WASM_TYPE_PTR);   // a_addr
+    local_raw_free(mod->local_alloc, WASM_TYPE_PTR);   // b_addr
+    local_raw_free(mod->local_alloc, WASM_TYPE_INT32); // size_in_bytes
+    local_raw_free(mod->local_alloc, WASM_TYPE_INT32); // memory_is_equal
+    local_raw_free(mod->local_alloc, WASM_TYPE_INT32); // loop_idx
+
+    *pcode = code;
+}
 EMIT_FUNC(initialize_type, Type* type, OnyxToken* where) {
     bh_arr(WasmInstruction) code = *pcode;
 
@@ -137,7 +217,7 @@ EMIT_FUNC(initialize_type, Type* type, OnyxToken* where) {
                 emit_expression(mod, &code, *smem->initial_value);
                 emit_store_instruction(mod, &code, smem->type, smem->offset);
             }
-            
+
             local_raw_free(mod->local_alloc, WASM_TYPE_PTR);
             break;
         }
index 1c12f67b604abb268ca71cbe931bf6059aac03a4..f967098b0644708a9cf5674514b4633eb061a18e 100644 (file)
@@ -64,6 +64,7 @@ get_freed_size :: () => {
     use core.intrinsics.wasm {
         memory_size, memory_grow,
         memory_copy, memory_fill,
+        memory_equal,
     }
 
     use core {memory, math}
@@ -190,7 +191,7 @@ get_freed_size :: () => {
 
     heap_free :: (ptr: rawptr) {
         #if Enable_Debug do assert(ptr != null, "Trying to free a null pointer.");
-        
+
         hb_ptr := cast(&heap_freed_block) (cast(uintptr) ptr - sizeof heap_allocated_block);
 
         //
@@ -350,7 +351,7 @@ get_freed_size :: () => {
         hb_ptr.size |= Allocated_Flag;
         new_ptr := heap_alloc(new_size_, align);
         #if runtime.Multi_Threading_Enabled do sync.mutex_lock(&heap_mutex);
-        
+
         memory_copy(new_ptr, ptr, old_size - sizeof heap_block);
         heap_free(ptr);
         return new_ptr;
index cdb3dc8f10248388bbe4193a91f7e0d4a2c68b3b..eed4ad272dcebc535db8fd4979f986d65e0085e4 100644 (file)
@@ -64,9 +64,9 @@ null :: cast(rawptr) 0
     `null_proc` is a special function that breaks the normal rules of type
     checking. `null_proc`, or any procedure marked with `#null`, is assignable
     to any function type, regardless of if the types match. For example,
-    
+
         f: (i32) -> i32 = null_proc;
-    
+
     Even though `null_proc` is a `() -> void` function, it bypasses that check
     and gets assigned to `f`. If f is called, there will be a runtime exception.
     This is by design.
@@ -393,22 +393,22 @@ cfree   :: (ptr: rawptr)            => raw_free(context.allocator, ptr);
 #doc """
    Represents a generic "iterator" or "generator" of a specific
    type. Can be used in a for-loop natively.
-   
+
    `data` is used for contextual information and is passed to
    the `next`, `close`, and `remove` procedures.
-   
+
    `next` is used to extract the next value out of the iterator.
    It returns the next value, and a continuation flag. If the
    flag is false, the value should be ignored and iteration should
    stop.
-   
+
    `close` should called when the iterator has ended. This is
    done automatically in for-loops, and in the `core.iter` library.
    In for-loops, `close` is called no matter which way the for-loop
    exits (`break`, `return`, etc). Using this rule, iterator can
    be used to create "resources" that automatically close when you
    are done with them.
-   
+
    `remove` is used to tell the iterator to remove the last value
    returned from some underlying data store. Invoked automatically
    using the `#remove` directive in a for-loop.
@@ -508,7 +508,7 @@ Link_Options :: struct {
     // But when stack_first is true, it is:
     //   reserved | stack | static-data | heap
     stack_first     := false;
-    
+
     // The size, in bytes of the stack.
     stack_size      := 16 * 65536;  // 16 pages * 65536 bytes per page = 1 MiB stack
 
@@ -563,9 +563,9 @@ Link_Options :: struct {
 #doc """
     Special type used to represent a package at runtime.
     For example,
-   
+
         x: package_id = package main
-   
+
     Currently, there is not much you can do with this; it is
     only used by the runtime.info library if you want to filter
     tags based on which package they are coming from.
@@ -581,7 +581,8 @@ any_package :: cast(package_id) 0
 #operator == macro (p1, p2: package_id) => cast(u32) p1 == cast(u32) p2;
 #operator != macro (p1, p2: package_id) => cast(u32) p1 != cast(u32) p2;
 
-
+#operator == macro (l, r: [$N]$T) => core.intrinsics.wasm.memory_equal(cast(rawptr)l, cast(rawptr)r, N * sizeof T);
+#operator != macro (l, r: [$N]$T) => !(l == r);
 
 //
 // DEPRECATED THINGS
index 596083093b0d630470ac4f85591da56c53aa2f10..cde0b2c90214fc6e16e2299335aca477a34e1f75 100644 (file)
@@ -7,6 +7,7 @@ memory_size  :: () -> u32 #intrinsic ---
 memory_grow  :: (val: u32) -> i32 #intrinsic ---
 memory_copy  :: (dst: rawptr, src: rawptr, count: i32) -> void #intrinsic ---
 memory_fill  :: (dst: rawptr, byte: u8, count: i32) -> void #intrinsic ---
+memory_equal :: (a: rawptr, b: rawptr, count: i32) -> bool #intrinsic --- // Isn't an actual wasm intrinsic right now, but will be if the proposal is accepted.
 
 clz_i32      :: (val: i32) -> i32 #intrinsic ---
 ctz_i32      :: (val: i32) -> i32 #intrinsic ---