added field accesses on r-values
authorBrendan Hansen <brendan.f.hansen@gmail.com>
Sun, 20 Jun 2021 15:27:12 +0000 (10:27 -0500)
committerBrendan Hansen <brendan.f.hansen@gmail.com>
Sun, 20 Jun 2021 15:27:12 +0000 (10:27 -0500)
bin/onyx
include/onyxtypes.h
src/onyxchecker.c
src/onyxtypes.c
src/onyxwasm.c
tests/better_field_accesses [new file with mode: 0644]
tests/better_field_accesses.onyx [new file with mode: 0644]

index 125c1bc720f47ad6afe8444d1f788648ab2ffdec..b1ce16726ffed35a915b720812578387b2208bf3 100755 (executable)
Binary files a/bin/onyx and b/bin/onyx differ
index 97c5f0bd9230a29668e3efb6dd6e08df5a5f5341..f67bacec696d5c1e1ddb846a90c8a4a41c38454e 100644 (file)
@@ -184,6 +184,7 @@ b32 type_lookup_member_by_idx(Type* type, i32 idx, StructMember* smem);
 
 i32 type_linear_member_count(Type* type);
 b32 type_linear_member_lookup(Type* type, i32 idx, TypeWithOffset* two);
+i32 type_get_idx_of_linear_member_with_offset(Type* type, u32 offset);
 
 b32 type_struct_is_simple(Type* type);
 
index ee2c539582f29f6c5622ce31876eff06f5de0195..0e27fde00a9cfa1fa6149b5a992a3bb64e64129a 100644 (file)
@@ -1378,15 +1378,7 @@ CheckStatus check_field_access(AstFieldAccess** pfield) {
         return Check_Error;
     }
 
-    if (field->expr->type->kind != Type_Kind_Pointer && !is_lval((AstNode *) field->expr)) {
-        onyx_report_error(field->token->pos,
-            "Cannot access field '%b'. Expression is not an lval.",
-            field->token->text,
-            field->token->length);
-        return Check_Error;
-    }
-
-    // HACK: (*foo).bar does not work without this.
+    // Optimization for (*foo).member.
     if (field->expr->kind == Ast_Kind_Dereference) {
         field->expr = ((AstDereference *) field->expr)->expr;
     }
index 0e307106ca01da24433ad81f3b72eb08370bf22b..2cdccb792d907ca857164f3d07534d74a8fc2bb5 100644 (file)
@@ -902,7 +902,7 @@ i32 type_linear_member_count(Type* type) {
         case Type_Kind_DynArray: return 5;
         case Type_Kind_Compound: return bh_arr_length(type->Compound.linear_members);
         case Type_Kind_Struct:   return bh_arr_length(type->Struct.linear_members);
-        default: return 0
+        default: return 1
     }
 }
 
@@ -944,7 +944,50 @@ b32 type_linear_member_lookup(Type* type, i32 idx, TypeWithOffset* two) {
         }
         case Type_Kind_Compound: *two = type->Compound.linear_members[idx]; return 1;
         case Type_Kind_Struct:   *two = type->Struct.linear_members[idx];   return 1;
-        default: return 0; 
+        default: {
+            if (idx > 0) return 0;
+            two->offset = 0;
+            two->type = type;
+            return 1;
+        }
+    }
+}
+
+i32 type_get_idx_of_linear_member_with_offset(Type* type, u32 offset) {
+    switch (type->kind) {
+        case Type_Kind_Slice:
+        case Type_Kind_VarArgs: {
+            if (offset == 0) return 0;
+            if (offset == 8) return 1;
+            return -1;
+        }
+        case Type_Kind_DynArray: {
+            if (offset == 0)   return 0;
+            if (offset == 8)   return 1;
+            if (offset == 12)  return 2;
+            if (offset == 16)  return 3;
+            if (offset == 24)  return 4;
+            return -1;
+        }
+        case Type_Kind_Compound: {
+            i32 idx = 0;
+            bh_arr_each(TypeWithOffset, two, type->Compound.linear_members) {
+                if (two->offset == offset) return idx;
+                idx++;
+            }
+
+            return -1;
+        }
+        case Type_Kind_Struct: {
+            i32 idx = 0;
+            bh_arr_each(TypeWithOffset, two, type->Struct.linear_members) {
+                if (two->offset == offset) return idx;
+                idx++;
+            }
+
+            return -1;
+        }
+        default: return -1;
     }
 }
 
index e2cc009bb145a511bc64587a4fa3168c8386db20..0fa7eb946138074d5e165a5903a70de94641bcaa 100644 (file)
@@ -1426,8 +1426,10 @@ EMIT_FUNC(call, AstCall* call) {
     switch (call->va_kind) {
         case VA_Kind_Typed: {
             WIL(WI_LOCAL_GET, stack_top_store_local);
-            WID(WI_PTR_CONST, (u32) vararg_offset);
-            WI(WI_PTR_ADD);
+            if (vararg_offset > 0) {
+                WID(WI_PTR_CONST, vararg_offset);
+                WI(WI_PTR_ADD);
+            }
             WID(WI_I32_CONST, vararg_count);
             break;
         }
@@ -1435,8 +1437,10 @@ EMIT_FUNC(call, AstCall* call) {
         case VA_Kind_Untyped: {
             WIL(WI_LOCAL_GET, stack_top_store_local);
             WIL(WI_LOCAL_GET, stack_top_store_local);
-            WID(WI_PTR_CONST, vararg_offset);
-            WI(WI_PTR_ADD);
+            if (vararg_offset > 0) {
+                WID(WI_PTR_CONST, vararg_offset);
+                WI(WI_PTR_ADD);
+            }
             emit_store_instruction(mod, &code, &basic_types[Basic_Kind_Rawptr], reserve_size);
 
             // NOTE: There will be 4 uninitialized bytes here, because pointers are only 4 bytes in WASM.
@@ -1446,8 +1450,10 @@ EMIT_FUNC(call, AstCall* call) {
             emit_store_instruction(mod, &code, &basic_types[Basic_Kind_I32], reserve_size + 8);
 
             WIL(WI_LOCAL_GET, stack_top_store_local);
-            WID(WI_PTR_CONST, reserve_size);
-            WI(WI_PTR_ADD);
+            if (reserve_size > 0) {
+                WID(WI_PTR_CONST, reserve_size);
+                WI(WI_PTR_ADD);
+            }
 
             reserve_size += 12;
             break;
@@ -2020,8 +2026,6 @@ EMIT_FUNC(compound_load, Type* type, u64 offset) {
 
 EMIT_FUNC(compound_store, Type* type, u64 offset, b32 location_first) {
     bh_arr(WasmInstruction) code = *pcode;
-    bh_arr(u64) temp_locals = NULL;
-    bh_arr_new(global_heap_allocator, temp_locals, 4);
 
     TypeWithOffset two;
 
@@ -2029,14 +2033,14 @@ EMIT_FUNC(compound_store, Type* type, u64 offset, b32 location_first) {
     if (location_first) WIL(WI_LOCAL_SET, loc_idx);
 
     i32 elem_count = type_linear_member_count(type);
+    u64 *temp_locals = bh_alloc_array(global_scratch_allocator, u64, elem_count);
+
     forir (i, elem_count - 1, 0) {
         type_linear_member_lookup(type, i, &two);
 
         WasmType wt = onyx_type_to_wasm_type(two.type);
-        u64 tmp_idx = local_raw_allocate(mod->local_alloc, wt);
-
-        bh_arr_push(temp_locals, tmp_idx);
-        WIL(WI_LOCAL_SET, tmp_idx);
+        temp_locals[i] = local_raw_allocate(mod->local_alloc, wt);
+        WIL(WI_LOCAL_SET, temp_locals[i]);
     }
 
     if (!location_first) WIL(WI_LOCAL_SET, loc_idx);
@@ -2044,7 +2048,7 @@ EMIT_FUNC(compound_store, Type* type, u64 offset, b32 location_first) {
     fori (i, 0, elem_count) {
         type_linear_member_lookup(type, i, &two);
 
-        u64 tmp_idx = bh_arr_pop(temp_locals)
+        u64 tmp_idx = temp_locals[i]
         WIL(WI_LOCAL_GET, loc_idx);
         WIL(WI_LOCAL_GET, tmp_idx);
         emit_store_instruction(mod, &code, two.type, offset + two.offset);
@@ -2053,9 +2057,11 @@ EMIT_FUNC(compound_store, Type* type, u64 offset, b32 location_first) {
         local_raw_free(mod->local_alloc, wt);
     }
 
-    bh_arr_free(temp_locals);
     local_raw_free(mod->local_alloc, WASM_TYPE_PTR);
 
+    // This shouldn't be necessary because the scratch allocator doesn't free.
+    bh_free(global_scratch_allocator, temp_locals);
+
     *pcode = code;
 }
 
@@ -2404,46 +2410,55 @@ EMIT_FUNC(expression, AstTyped* expr) {
                 }
             }
 
+            if (is_lval((AstNode *) field->expr)) {
+                u64 offset = 0;
+                emit_field_access_location(mod, &code, field, &offset);
+                emit_load_instruction(mod, &code, field->type, offset);
+
+            } else {
+                emit_expression(mod, &code, field->expr);
 
-            // HACK
-            // This is only used in the weird case where you write something like this:
-            //
-            //    data_ptr := "Test string".data;
-            //    string_count := "Some other string".count;
-            //
-            // I don't think this is every used in the core libraries or anything written in
-            // Onyx yet, so this might go away at some point. Actually, thinking about it,
-            // I think accessing fields on r-values is completely disabled currently, so this
-            // is never possible.
-            //
-            // I think a more general case of accessing fields on r-values is better. For example,
-            //
-            //     returns_foo :: () -> Foo { ... }
-            //
-            //     value := returns_foo().foo_member;
-            //
-            // This does not work in the language at the moment, but maybe should?
-            /*
-            else if (field->expr->kind == Ast_Kind_StrLit) {
-                StructMember smem;
-
-                token_toggle_end(field->token);
-                type_lookup_member(field->expr->type, field->token->text, &smem);
-                token_toggle_end(field->token);
-
-                if (smem.idx == 0)
-                    WID(WI_I32_CONST, ((AstStrLit *) field->expr)->addr);
-
-                if (smem.idx == 1)
-                    WID(WI_I32_CONST, ((AstStrLit *) field->expr)->length);
-
-                break;
+                i32 idx = type_get_idx_of_linear_member_with_offset(field->expr->type, field->offset);
+                i32 field_linear_members = type_linear_member_count(field->type);
+                i32 total_linear_members = type_linear_member_count(field->expr->type);
+
+                if (idx == 0) {
+                    // Easy case: the member is the first one and all other members just have to be dropped.
+                    fori (i, 0, total_linear_members - field_linear_members) WI(WI_DROP);
+
+                } else {
+                    // Tough case: Stack shuffling to make the member the only thing on the stack.
+                    // This is very similar to the compound_load/compound_store procedures but it is different enough
+                    // that I cannot find a good way to factor them all without just introducing a ton of complexity.
+                    fori (i, 0, total_linear_members - idx - field_linear_members) WI(WI_DROP);
+
+                    u64 *temporaries = bh_alloc_array(global_scratch_allocator, u64, field_linear_members);
+                    fori (i, 0, field_linear_members) temporaries[i] = 0;
+
+                    TypeWithOffset two = { 0 };
+                    forir (i, field_linear_members - 1, 0) {
+                        type_linear_member_lookup(field->type, i, &two);
+
+                        WasmType wt = onyx_type_to_wasm_type(two.type);
+                        temporaries[i] = local_raw_allocate(mod->local_alloc, wt);
+                        WIL(WI_LOCAL_SET, temporaries[i]);
+                    }
+
+                    fori (i, 0, idx) WI(WI_DROP);
+
+                    fori (i, 0, field_linear_members) {
+                        type_linear_member_lookup(field->type, i, &two);
+
+                        WIL(WI_LOCAL_GET, temporaries[i]);
+
+                        WasmType wt = onyx_type_to_wasm_type(two.type);
+                        local_raw_free(mod->local_alloc, wt);
+                    }
+
+                    bh_free(global_scratch_allocator, temporaries);
+                }
             }
-            */
 
-            u64 offset = 0;
-            emit_field_access_location(mod, &code, field, &offset);
-            emit_load_instruction(mod, &code, field->type, offset);
             break;
         }
 
diff --git a/tests/better_field_accesses b/tests/better_field_accesses
new file mode 100644 (file)
index 0000000..c737814
--- /dev/null
@@ -0,0 +1,5 @@
+0x1A2
+Foo bar
+10
+42
+Worked!
diff --git a/tests/better_field_accesses.onyx b/tests/better_field_accesses.onyx
new file mode 100644 (file)
index 0000000..caba4d3
--- /dev/null
@@ -0,0 +1,27 @@
+#load "core/std"
+
+use package core
+
+Foo :: struct {
+    x, y: i32;
+    name: str;
+    age: i32;
+}
+
+get_foo :: () -> Foo {
+    return .{
+        x = 10, y = 20,
+        name = "Foo bar",
+        age = 42,
+    };
+}
+
+main :: (args: [] cstr) {
+    printf("%p\n", "Hello World!".data);
+
+    println(get_foo().name);
+    println(get_foo().x);
+    println(get_foo().age);
+
+    println((Foo.{ 0, 0, "Worked!", 0 }).name);
+}
\ No newline at end of file