Commit 5834ff0cc5

Andrew Kelley <superjoe30@gmail.com>
2018-03-12 13:35:41
don't memoize comptime fn calls that access comptime mutable state
closes #827
1 parent 1bf2810
Changed files (3)
src/all_types.hpp
@@ -1174,13 +1174,6 @@ struct TypeTableEntry {
     bool is_copyable;
     bool gen_h_loop_flag;
 
-    // This is denormalized data. The simplest type that has this
-    // flag set to true is a mutable pointer. A const pointer has
-    // the same value for this flag as the child type.
-    // If a struct has any fields that have this flag true, then
-    // the flag is true for the struct.
-    bool can_mutate_state_through_it;
-
     union {
         TypeTableEntryPointer pointer;
         TypeTableEntryInt integral;
src/analyze.cpp
@@ -398,7 +398,6 @@ TypeTableEntry *get_pointer_to_type_extra(CodeGen *g, TypeTableEntry *child_type
 
     TypeTableEntry *entry = new_type_table_entry(TypeTableEntryIdPointer);
     entry->is_copyable = true;
-    entry->can_mutate_state_through_it = is_const ? child_type->can_mutate_state_through_it : true;
 
     const char *const_str = is_const ? "const " : "";
     const char *volatile_str = is_volatile ? "volatile " : "";
@@ -483,7 +482,6 @@ TypeTableEntry *get_maybe_type(CodeGen *g, TypeTableEntry *child_type) {
         assert(child_type->type_ref || child_type->zero_bits);
         assert(child_type->di_type);
         entry->is_copyable = type_is_copyable(g, child_type);
-        entry->can_mutate_state_through_it = child_type->can_mutate_state_through_it;
 
         buf_resize(&entry->name, 0);
         buf_appendf(&entry->name, "?%s", buf_ptr(&child_type->name));
@@ -574,7 +572,6 @@ TypeTableEntry *get_error_union_type(CodeGen *g, TypeTableEntry *err_set_type, T
     entry->is_copyable = true;
     assert(payload_type->di_type);
     ensure_complete_type(g, payload_type);
-    entry->can_mutate_state_through_it = payload_type->can_mutate_state_through_it;
 
     buf_resize(&entry->name, 0);
     buf_appendf(&entry->name, "%s!%s", buf_ptr(&err_set_type->name), buf_ptr(&payload_type->name));
@@ -733,7 +730,6 @@ TypeTableEntry *get_slice_type(CodeGen *g, TypeTableEntry *ptr_type) {
 
     TypeTableEntry *entry = new_type_table_entry(TypeTableEntryIdStruct);
     entry->is_copyable = true;
-    entry->can_mutate_state_through_it = ptr_type->can_mutate_state_through_it;
 
     // replace the & with [] to go from a ptr type name to a slice type name
     buf_resize(&entry->name, 0);
@@ -1739,8 +1735,6 @@ TypeTableEntry *get_struct_type(CodeGen *g, const char *type_name, const char *f
             struct_type->data.structure.gen_field_count += 1;
         } else {
             field->gen_index = SIZE_MAX;
-            struct_type->can_mutate_state_through_it = struct_type->can_mutate_state_through_it ||
-                field->type_entry->can_mutate_state_through_it;
         }
 
         auto prev_entry = struct_type->data.structure.fields_by_name.put_unique(field->name, field);
@@ -2481,9 +2475,6 @@ static void resolve_struct_zero_bits(CodeGen *g, TypeTableEntry *struct_type) {
         if (!type_has_bits(field_type))
             continue;
 
-        struct_type->can_mutate_state_through_it = struct_type->can_mutate_state_through_it ||
-            field_type->can_mutate_state_through_it;
-
         if (gen_field_index == 0) {
             if (struct_type->data.structure.layout == ContainerLayoutPacked) {
                 struct_type->data.structure.abi_alignment = 1;
@@ -2671,8 +2662,6 @@ static void resolve_union_zero_bits(CodeGen *g, TypeTableEntry *union_type) {
             }
         }
         union_field->type_entry = field_type;
-        union_type->can_mutate_state_through_it = union_type->can_mutate_state_through_it ||
-            field_type->can_mutate_state_through_it;
 
         if (field_node->data.struct_field.value != nullptr && !decl_node->data.container_decl.auto_enum) {
             ErrorMsg *msg = add_node_error(g, field_node->data.struct_field.value,
@@ -4576,11 +4565,77 @@ bool generic_fn_type_id_eql(GenericFnTypeId *a, GenericFnTypeId *b) {
     return true;
 }
 
+static bool can_mutate_comptime_var_state(ConstExprValue *value) {
+    assert(value != nullptr);
+    switch (value->type->id) {
+        case TypeTableEntryIdInvalid:
+            zig_unreachable();
+        case TypeTableEntryIdMetaType:
+        case TypeTableEntryIdVoid:
+        case TypeTableEntryIdBool:
+        case TypeTableEntryIdUnreachable:
+        case TypeTableEntryIdInt:
+        case TypeTableEntryIdFloat:
+        case TypeTableEntryIdNumLitFloat:
+        case TypeTableEntryIdNumLitInt:
+        case TypeTableEntryIdUndefLit:
+        case TypeTableEntryIdNullLit:
+        case TypeTableEntryIdNamespace:
+        case TypeTableEntryIdBoundFn:
+        case TypeTableEntryIdFn:
+        case TypeTableEntryIdBlock:
+        case TypeTableEntryIdOpaque:
+        case TypeTableEntryIdPromise:
+        case TypeTableEntryIdErrorSet:
+        case TypeTableEntryIdEnum:
+            return false;
+
+        case TypeTableEntryIdPointer:
+            return value->data.x_ptr.mut == ConstPtrMutComptimeVar;
+
+        case TypeTableEntryIdArray:
+            if (value->type->data.array.len == 0)
+                return false;
+            if (value->data.x_array.special == ConstArraySpecialUndef)
+                return false;
+            for (uint32_t i = 0; i < value->type->data.array.len; i += 1) {
+                if (can_mutate_comptime_var_state(&value->data.x_array.s_none.elements[i]))
+                    return true;
+            }
+            return false;
+
+        case TypeTableEntryIdStruct:
+            for (uint32_t i = 0; i < value->type->data.structure.src_field_count; i += 1) {
+                if (can_mutate_comptime_var_state(&value->data.x_struct.fields[i]))
+                    return true;
+            }
+            return false;
+
+        case TypeTableEntryIdMaybe:
+            if (value->data.x_maybe == nullptr)
+                return false;
+            return can_mutate_comptime_var_state(value->data.x_maybe);
+
+        case TypeTableEntryIdErrorUnion:
+            if (value->data.x_err_union.err != nullptr)
+                return false;
+            assert(value->data.x_err_union.payload != nullptr);
+            return can_mutate_comptime_var_state(value->data.x_err_union.payload);
+
+        case TypeTableEntryIdUnion:
+            return can_mutate_comptime_var_state(value->data.x_union.payload);
+
+        case TypeTableEntryIdArgTuple:
+            zig_panic("TODO var args at comptime is currently not supported");
+    }
+    zig_unreachable();
+}
+
 bool fn_eval_cacheable(Scope *scope) {
     while (scope) {
         if (scope->id == ScopeIdVarDecl) {
             ScopeVarDecl *var_scope = (ScopeVarDecl *)scope;
-            if (var_scope->var->value->type->can_mutate_state_through_it)
+            if (can_mutate_comptime_var_state(var_scope->var->value))
                 return false;
         } else if (scope->id == ScopeIdFnDef) {
             return true;
test/cases/eval.zig
@@ -486,3 +486,20 @@ test "comptime slice of pointer preserves comptime var" {
         assert(buff[0..][0..][0] == 1);
     }
 }
+
+const SingleFieldStruct = struct {
+    x: i32,
+
+    fn read_x(self: &const SingleFieldStruct) i32 {
+        return self.x;
+    }
+};
+test "const ptr to comptime mutable data is not memoized" {
+
+    comptime {
+        var foo = SingleFieldStruct {.x = 1};
+        assert(foo.read_x() == 1);
+        foo.x = 2;
+        assert(foo.read_x() == 2);
+    }
+}