Commit 419e75eb23

Andrew Kelley <superjoe30@gmail.com>
2017-02-05 03:49:27
remove volatileStore builtin; add volatile pointers
closes #238
1 parent 0d7abc6
doc/langref.md
@@ -143,7 +143,7 @@ ContainerInitBody = list(StructLiteralField, ",") | list(Expression, ",")
 
 StructLiteralField = "." Symbol "=" Expression
 
-PrefixOp = "!" | "-" | "~" | "*" | ("&" option("const")) | "?" | "%" | "%%" | "??" | "-%"
+PrefixOp = "!" | "-" | "~" | "*" | ("&" option("const") option("volatile")) | "?" | "%" | "%%" | "??" | "-%"
 
 PrimaryExpression = Number | String | CharLiteral | KeywordLiteral | GroupedExpression | GotoExpression | BlockExpression | Symbol | ("@" Symbol FnCallExpression) | ArrayType | (option("extern") FnProto) | AsmExpression | ("error" "." Symbol) | ContainerDecl
 
src/all_types.hpp
@@ -490,6 +490,8 @@ enum PrefixOp {
     PrefixOpNegationWrap,
     PrefixOpAddressOf,
     PrefixOpConstAddressOf,
+    PrefixOpVolatileAddressOf,
+    PrefixOpConstVolatileAddressOf,
     PrefixOpDereference,
     PrefixOpMaybe,
     PrefixOpError,
@@ -806,6 +808,7 @@ bool fn_type_id_eql(FnTypeId *a, FnTypeId *b);
 struct TypeTableEntryPointer {
     TypeTableEntry *child_type;
     bool is_const;
+    bool is_volatile;
 };
 
 struct TypeTableEntryInt {
@@ -991,7 +994,7 @@ struct TypeTableEntry {
     } data;
 
     // use these fields to make sure we don't duplicate type table entries for the same type
-    TypeTableEntry *pointer_parent[2];
+    TypeTableEntry *pointer_parent[2][2]; // [0 - mut, 1 - const][0 - normal, 1 - volatile]
     TypeTableEntry *unknown_size_array_parent[2];
     HashMap<uint64_t, TypeTableEntry *, uint64_hash, uint64_eq> arrays_by_size;
     TypeTableEntry *maybe_parent;
@@ -1113,7 +1116,6 @@ enum BuiltinFnId {
     BuiltinFnIdCanImplicitCast,
     BuiltinFnIdSetGlobalAlign,
     BuiltinFnIdSetGlobalSection,
-    BuiltinFnIdVolatileStore,
 };
 
 struct BuiltinFnEntry {
@@ -1692,7 +1694,6 @@ struct IrInstructionStorePtr {
 
     IrInstruction *ptr;
     IrInstruction *value;
-    bool is_volatile;
 };
 
 struct IrInstructionFieldPtr {
@@ -1943,6 +1944,7 @@ struct IrInstructionRef {
     IrInstruction *value;
     LLVMValueRef tmp_ptr;
     bool is_const;
+    bool is_volatile;
 };
 
 struct IrInstructionMinValue {
@@ -2062,6 +2064,7 @@ struct IrInstructionMemset {
     IrInstruction *dest_ptr;
     IrInstruction *byte;
     IrInstruction *count;
+    bool is_volatile;
 };
 
 struct IrInstructionMemcpy {
@@ -2070,6 +2073,7 @@ struct IrInstructionMemcpy {
     IrInstruction *dest_ptr;
     IrInstruction *src_ptr;
     IrInstruction *count;
+    bool is_volatile;
 };
 
 struct IrInstructionSlice {
@@ -2267,13 +2271,6 @@ struct IrInstructionSetGlobalSection {
     IrInstruction *value;
 };
 
-enum LValPurpose {
-    LValPurposeNone,
-    LValPurposeAssign,
-    LValPurposeAddressOf,
-    LValPurposeAddressOfConst,
-};
-
 static const size_t slice_ptr_index = 0;
 static const size_t slice_len_index = 1;
 
src/analyze.cpp
@@ -269,43 +269,48 @@ TypeTableEntry *get_smallest_unsigned_int_type(CodeGen *g, uint64_t x) {
     return get_int_type(g, false, bits_needed_for_unsigned(x));
 }
 
-TypeTableEntry *get_pointer_to_type(CodeGen *g, TypeTableEntry *child_type, bool is_const) {
+TypeTableEntry *get_pointer_to_type_volatile(CodeGen *g, TypeTableEntry *child_type, bool is_const, bool is_volatile) {
     assert(child_type->id != TypeTableEntryIdInvalid);
-    TypeTableEntry **parent_pointer = &child_type->pointer_parent[(is_const ? 1 : 0)];
-    if (*parent_pointer) {
+    TypeTableEntry **parent_pointer = &child_type->pointer_parent[(is_const ? 1 : 0)][(is_volatile ? 1 : 0)];
+    if (*parent_pointer)
         return *parent_pointer;
-    } else {
-        type_ensure_zero_bits_known(g, child_type);
 
-        TypeTableEntry *entry = new_type_table_entry(TypeTableEntryIdPointer);
+    type_ensure_zero_bits_known(g, child_type);
 
-        const char *const_str = is_const ? "const " : "";
-        buf_resize(&entry->name, 0);
-        buf_appendf(&entry->name, "&%s%s", const_str, buf_ptr(&child_type->name));
-
-        TypeTableEntry *canon_child_type = get_underlying_type(child_type);
-        assert(canon_child_type->id != TypeTableEntryIdInvalid);
+    TypeTableEntry *entry = new_type_table_entry(TypeTableEntryIdPointer);
 
-        entry->zero_bits = !type_has_bits(canon_child_type);
+    const char *const_str = is_const ? "const " : "";
+    const char *volatile_str = is_volatile ? "volatile " : "";
+    buf_resize(&entry->name, 0);
+    buf_appendf(&entry->name, "&%s%s%s", const_str, volatile_str, buf_ptr(&child_type->name));
 
-        if (!entry->zero_bits) {
-            entry->type_ref = LLVMPointerType(child_type->type_ref, 0);
+    TypeTableEntry *canon_child_type = get_underlying_type(child_type);
+    assert(canon_child_type->id != TypeTableEntryIdInvalid);
 
-            uint64_t debug_size_in_bits = 8*LLVMStoreSizeOfType(g->target_data_ref, entry->type_ref);
-            uint64_t debug_align_in_bits = 8*LLVMABISizeOfType(g->target_data_ref, entry->type_ref);
-            assert(child_type->di_type);
-            entry->di_type = ZigLLVMCreateDebugPointerType(g->dbuilder, child_type->di_type,
-                    debug_size_in_bits, debug_align_in_bits, buf_ptr(&entry->name));
-        } else {
-            entry->di_type = g->builtin_types.entry_void->di_type;
-        }
+    entry->zero_bits = !type_has_bits(canon_child_type);
 
-        entry->data.pointer.child_type = child_type;
-        entry->data.pointer.is_const = is_const;
+    if (!entry->zero_bits) {
+        entry->type_ref = LLVMPointerType(child_type->type_ref, 0);
 
-        *parent_pointer = entry;
-        return entry;
+        uint64_t debug_size_in_bits = 8*LLVMStoreSizeOfType(g->target_data_ref, entry->type_ref);
+        uint64_t debug_align_in_bits = 8*LLVMABISizeOfType(g->target_data_ref, entry->type_ref);
+        assert(child_type->di_type);
+        entry->di_type = ZigLLVMCreateDebugPointerType(g->dbuilder, child_type->di_type,
+                debug_size_in_bits, debug_align_in_bits, buf_ptr(&entry->name));
+    } else {
+        entry->di_type = g->builtin_types.entry_void->di_type;
     }
+
+    entry->data.pointer.child_type = child_type;
+    entry->data.pointer.is_const = is_const;
+    entry->data.pointer.is_volatile = is_volatile;
+
+    *parent_pointer = entry;
+    return entry;
+}
+
+TypeTableEntry *get_pointer_to_type(CodeGen *g, TypeTableEntry *child_type, bool is_const) {
+    return get_pointer_to_type_volatile(g, child_type, is_const, false);
 }
 
 TypeTableEntry *get_maybe_type(CodeGen *g, TypeTableEntry *child_type) {
src/analyze.hpp
@@ -15,6 +15,7 @@ ErrorMsg *add_node_error(CodeGen *g, AstNode *node, Buf *msg);
 ErrorMsg *add_error_note(CodeGen *g, ErrorMsg *parent_msg, AstNode *node, Buf *msg);
 TypeTableEntry *new_type_table_entry(TypeTableEntryId id);
 TypeTableEntry *get_pointer_to_type(CodeGen *g, TypeTableEntry *child_type, bool is_const);
+TypeTableEntry *get_pointer_to_type_volatile(CodeGen *g, TypeTableEntry *child_type, bool is_const, bool is_volatile);
 bool is_node_void_expr(AstNode *node);
 uint64_t type_size(CodeGen *g, TypeTableEntry *type_entry);
 TypeTableEntry **get_int_type_ptr(CodeGen *g, bool is_signed, size_t size_in_bits);
src/ast_render.cpp
@@ -68,6 +68,8 @@ static const char *prefix_op_str(PrefixOp prefix_op) {
         case PrefixOpBinNot: return "~";
         case PrefixOpAddressOf: return "&";
         case PrefixOpConstAddressOf: return "&const ";
+        case PrefixOpVolatileAddressOf: return "&volatile ";
+        case PrefixOpConstVolatileAddressOf: return "&const volatile ";
         case PrefixOpDereference: return "*";
         case PrefixOpMaybe: return "?";
         case PrefixOpError: return "%";
src/codegen.cpp
@@ -1294,18 +1294,19 @@ static LLVMValueRef ir_render_store_ptr(CodeGen *g, IrExecutable *executable, Ir
     LLVMValueRef value = ir_llvm_value(g, instruction->value);
 
     assert(instruction->ptr->value.type->id == TypeTableEntryIdPointer);
-    TypeTableEntry *op1_type = instruction->ptr->value.type->data.pointer.child_type;
+    TypeTableEntry *ptr_type = get_underlying_type(instruction->ptr->value.type);
+    TypeTableEntry *child_type = get_underlying_type(ptr_type->data.pointer.child_type);
 
-    if (!type_has_bits(op1_type)) {
+    if (!type_has_bits(child_type)) {
         return nullptr;
     }
-    if (handle_is_ptr(op1_type)) {
-        return gen_struct_memcpy(g, value, ptr, op1_type);
+    if (handle_is_ptr(child_type)) {
+        return gen_struct_memcpy(g, value, ptr, child_type);
     }
 
     LLVMValueRef llvm_instruction = LLVMBuildStore(g->builder, value, ptr);
 
-    LLVMSetVolatile(llvm_instruction, instruction->is_volatile);
+    LLVMSetVolatile(llvm_instruction, ptr_type->data.pointer.is_volatile);
 
     return nullptr;
 }
@@ -1803,12 +1804,15 @@ static LLVMValueRef ir_render_memset(CodeGen *g, IrExecutable *executable, IrIns
 
     LLVMValueRef dest_ptr_casted = LLVMBuildBitCast(g->builder, dest_ptr, ptr_u8, "");
 
+    LLVMValueRef is_volatile = instruction->is_volatile ?
+        LLVMConstAllOnes(LLVMInt1Type()) : LLVMConstNull(LLVMInt1Type());
+
     LLVMValueRef params[] = {
         dest_ptr_casted, // dest pointer
         char_val, // source pointer
         len_val, // byte count
         LLVMConstInt(LLVMInt32Type(), 1, false), // align in bytes
-        LLVMConstNull(LLVMInt1Type()), // is volatile
+        is_volatile,
     };
 
     LLVMBuildCall(g->builder, g->memset_fn_val, params, 5, "");
@@ -1825,12 +1829,15 @@ static LLVMValueRef ir_render_memcpy(CodeGen *g, IrExecutable *executable, IrIns
     LLVMValueRef dest_ptr_casted = LLVMBuildBitCast(g->builder, dest_ptr, ptr_u8, "");
     LLVMValueRef src_ptr_casted = LLVMBuildBitCast(g->builder, src_ptr, ptr_u8, "");
 
+    LLVMValueRef is_volatile = instruction->is_volatile ?
+        LLVMConstAllOnes(LLVMInt1Type()) : LLVMConstNull(LLVMInt1Type());
+
     LLVMValueRef params[] = {
         dest_ptr_casted, // dest pointer
         src_ptr_casted, // source pointer
         len_val, // byte count
         LLVMConstInt(LLVMInt32Type(), 1, false), // align in bytes
-        LLVMConstNull(LLVMInt1Type()), // is volatile
+        is_volatile,
     };
 
     LLVMBuildCall(g->builder, g->memcpy_fn_val, params, 5, "");
@@ -3690,7 +3697,6 @@ static void define_builtin_fns(CodeGen *g) {
     create_builtin_fn(g, BuiltinFnIdAlloca, "alloca", 2);
     create_builtin_fn(g, BuiltinFnIdSetGlobalAlign, "setGlobalAlign", 2);
     create_builtin_fn(g, BuiltinFnIdSetGlobalSection, "setGlobalSection", 2);
-    create_builtin_fn(g, BuiltinFnIdVolatileStore, "volatileStore", 2);
 }
 
 static void init(CodeGen *g, Buf *source_path) {
src/ir.cpp
@@ -44,9 +44,21 @@ struct IrAnalyze {
     IrBasicBlock *const_predecessor_bb;
 };
 
+struct LVal {
+    bool is_ptr;
+    bool is_const;
+    bool is_volatile;
+};
+
+static const LVal LVAL_NONE = { false, false, false };
+static const LVal LVAL_PTR = { true, false, false };
+
+static LVal make_lval_addr(bool is_const, bool is_volatile) {
+    return { true, is_const, is_volatile };
+}
+
 static IrInstruction *ir_gen_node(IrBuilder *irb, AstNode *node, Scope *scope);
-static IrInstruction *ir_gen_node_extra(IrBuilder *irb, AstNode *node, Scope *scope,
-        LValPurpose lval);
+static IrInstruction *ir_gen_node_extra(IrBuilder *irb, AstNode *node, Scope *scope, LVal lval);
 static TypeTableEntry *ir_analyze_instruction(IrAnalyze *ira, IrInstruction *instruction);
 static IrInstruction *ir_implicit_cast(IrAnalyze *ira, IrInstruction *value, TypeTableEntry *expected_type);
 
@@ -1042,14 +1054,13 @@ static IrInstruction *ir_build_unreachable_from(IrBuilder *irb, IrInstruction *o
 }
 
 static IrInstruction *ir_build_store_ptr(IrBuilder *irb, Scope *scope, AstNode *source_node,
-        IrInstruction *ptr, IrInstruction *value, bool is_volatile)
+        IrInstruction *ptr, IrInstruction *value)
 {
     IrInstructionStorePtr *instruction = ir_build_instruction<IrInstructionStorePtr>(irb, scope, source_node);
     instruction->base.value.special = ConstValSpecialStatic;
     instruction->base.value.type = irb->codegen->builtin_types.entry_void;
     instruction->ptr = ptr;
     instruction->value = value;
-    instruction->is_volatile = is_volatile;
 
     ir_ref_instruction(ptr, irb->current_basic_block);
     ir_ref_instruction(value, irb->current_basic_block);
@@ -1058,10 +1069,10 @@ static IrInstruction *ir_build_store_ptr(IrBuilder *irb, Scope *scope, AstNode *
 }
 
 static IrInstruction *ir_build_store_ptr_from(IrBuilder *irb, IrInstruction *old_instruction,
-        IrInstruction *ptr, IrInstruction *value, bool is_volatile)
+        IrInstruction *ptr, IrInstruction *value)
 {
     IrInstruction *new_instruction = ir_build_store_ptr(irb, old_instruction->scope,
-            old_instruction->source_node, ptr, value, is_volatile);
+            old_instruction->source_node, ptr, value);
     ir_link_new_instruction(new_instruction, old_instruction);
     return new_instruction;
 }
@@ -1450,11 +1461,12 @@ static IrInstruction *ir_build_array_len(IrBuilder *irb, Scope *scope, AstNode *
 }
 
 static IrInstruction *ir_build_ref(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *value,
-        bool is_const)
+        bool is_const, bool is_volatile)
 {
     IrInstructionRef *instruction = ir_build_instruction<IrInstructionRef>(irb, scope, source_node);
     instruction->value = value;
     instruction->is_const = is_const;
+    instruction->is_volatile = is_volatile;
 
     ir_ref_instruction(value, irb->current_basic_block);
 
@@ -1462,10 +1474,10 @@ static IrInstruction *ir_build_ref(IrBuilder *irb, Scope *scope, AstNode *source
 }
 
 static IrInstruction *ir_build_ref_from(IrBuilder *irb, IrInstruction *old_instruction, IrInstruction *value,
-        bool is_const)
+        bool is_const, bool is_volatile)
 {
     IrInstruction *new_instruction = ir_build_ref(irb, old_instruction->scope, old_instruction->source_node,
-            value, is_const);
+            value, is_const, is_volatile);
     ir_link_new_instruction(new_instruction, old_instruction);
     return new_instruction;
 }
@@ -2980,7 +2992,7 @@ static ScopeDeferExpr *get_scope_defer_expr(Scope *scope) {
     return nullptr;
 }
 
-static IrInstruction *ir_gen_return(IrBuilder *irb, Scope *scope, AstNode *node, LValPurpose lval) {
+static IrInstruction *ir_gen_return(IrBuilder *irb, Scope *scope, AstNode *node, LVal lval) {
     assert(node->type == NodeTypeReturnExpr);
 
     FnTableEntry *fn_entry = exec_fn_entry(irb->exec);
@@ -3068,7 +3080,7 @@ static IrInstruction *ir_gen_return(IrBuilder *irb, Scope *scope, AstNode *node,
         case ReturnKindError:
             {
                 assert(expr_node);
-                IrInstruction *err_union_ptr = ir_gen_node_extra(irb, expr_node, scope, LValPurposeAddressOf);
+                IrInstruction *err_union_ptr = ir_gen_node_extra(irb, expr_node, scope, LVAL_PTR);
                 if (err_union_ptr == irb->codegen->invalid_instruction)
                     return irb->codegen->invalid_instruction;
                 IrInstruction *err_union_val = ir_build_load_ptr(irb, scope, node, err_union_ptr);
@@ -3086,7 +3098,7 @@ static IrInstruction *ir_gen_return(IrBuilder *irb, Scope *scope, AstNode *node,
 
                 ir_set_cursor_at_end(irb, continue_block);
                 IrInstruction *unwrapped_ptr = ir_build_unwrap_err_payload(irb, scope, node, err_union_ptr, false);
-                if (lval != LValPurposeNone)
+                if (lval.is_ptr)
                     return unwrapped_ptr;
                 else
                     return ir_build_load_ptr(irb, scope, node, unwrapped_ptr);
@@ -3094,7 +3106,7 @@ static IrInstruction *ir_gen_return(IrBuilder *irb, Scope *scope, AstNode *node,
         case ReturnKindMaybe:
             {
                 assert(expr_node);
-                IrInstruction *maybe_val_ptr = ir_gen_node_extra(irb, expr_node, scope, LValPurposeAddressOf);
+                IrInstruction *maybe_val_ptr = ir_gen_node_extra(irb, expr_node, scope, LVAL_PTR);
                 if (maybe_val_ptr == irb->codegen->invalid_instruction)
                     return irb->codegen->invalid_instruction;
                 IrInstruction *maybe_val = ir_build_load_ptr(irb, scope, node, maybe_val_ptr);
@@ -3112,7 +3124,7 @@ static IrInstruction *ir_gen_return(IrBuilder *irb, Scope *scope, AstNode *node,
 
                 ir_set_cursor_at_end(irb, continue_block);
                 IrInstruction *unwrapped_ptr = ir_build_unwrap_maybe(irb, scope, node, maybe_val_ptr, false);
-                if (lval != LValPurposeNone)
+                if (lval.is_ptr)
                     return unwrapped_ptr;
                 else
                     return ir_build_load_ptr(irb, scope, node, unwrapped_ptr);
@@ -3293,18 +3305,18 @@ static IrInstruction *ir_gen_bin_op_id(IrBuilder *irb, Scope *scope, AstNode *no
 }
 
 static IrInstruction *ir_gen_assign(IrBuilder *irb, Scope *scope, AstNode *node) {
-    IrInstruction *lvalue = ir_gen_node_extra(irb, node->data.bin_op_expr.op1, scope, LValPurposeAssign);
+    IrInstruction *lvalue = ir_gen_node_extra(irb, node->data.bin_op_expr.op1, scope, LVAL_PTR);
     IrInstruction *rvalue = ir_gen_node(irb, node->data.bin_op_expr.op2, scope);
 
     if (lvalue == irb->codegen->invalid_instruction || rvalue == irb->codegen->invalid_instruction)
         return irb->codegen->invalid_instruction;
 
-    ir_build_store_ptr(irb, scope, node, lvalue, rvalue, false);
+    ir_build_store_ptr(irb, scope, node, lvalue, rvalue);
     return ir_build_const_void(irb, scope, node);
 }
 
 static IrInstruction *ir_gen_assign_op(IrBuilder *irb, Scope *scope, AstNode *node, IrBinOp op_id) {
-    IrInstruction *lvalue = ir_gen_node_extra(irb, node->data.bin_op_expr.op1, scope, LValPurposeAssign);
+    IrInstruction *lvalue = ir_gen_node_extra(irb, node->data.bin_op_expr.op1, scope, LVAL_PTR);
     if (lvalue == irb->codegen->invalid_instruction)
         return lvalue;
     IrInstruction *op1 = ir_build_load_ptr(irb, scope, node->data.bin_op_expr.op1, lvalue);
@@ -3312,7 +3324,7 @@ static IrInstruction *ir_gen_assign_op(IrBuilder *irb, Scope *scope, AstNode *no
     if (op2 == irb->codegen->invalid_instruction)
         return op2;
     IrInstruction *result = ir_build_bin_op(irb, scope, node, op_id, op1, op2, true);
-    ir_build_store_ptr(irb, scope, node, lvalue, result, false);
+    ir_build_store_ptr(irb, scope, node, lvalue, result);
     return ir_build_const_void(irb, scope, node);
 }
 
@@ -3406,7 +3418,7 @@ static IrInstruction *ir_gen_maybe_ok_or(IrBuilder *irb, Scope *parent_scope, As
     AstNode *op1_node = node->data.bin_op_expr.op1;
     AstNode *op2_node = node->data.bin_op_expr.op2;
 
-    IrInstruction *maybe_ptr = ir_gen_node_extra(irb, op1_node, parent_scope, LValPurposeAddressOf);
+    IrInstruction *maybe_ptr = ir_gen_node_extra(irb, op1_node, parent_scope, LVAL_PTR);
     if (maybe_ptr == irb->codegen->invalid_instruction)
         return irb->codegen->invalid_instruction;
 
@@ -3574,9 +3586,9 @@ static IrInstruction *ir_gen_var_literal(IrBuilder *irb, Scope *scope, AstNode *
 }
 
 static IrInstruction *ir_gen_decl_ref(IrBuilder *irb, AstNode *source_node, Tld *tld,
-        LValPurpose lval, Scope *scope)
+        LVal lval, Scope *scope)
 {
-    resolve_top_level_decl(irb->codegen, tld, lval != LValPurposeNone);
+    resolve_top_level_decl(irb->codegen, tld, lval.is_ptr);
     if (tld->resolution == TldResolutionInvalid)
         return irb->codegen->invalid_instruction;
 
@@ -3587,8 +3599,8 @@ static IrInstruction *ir_gen_decl_ref(IrBuilder *irb, AstNode *source_node, Tld
         {
             TldVar *tld_var = (TldVar *)tld;
             VariableTableEntry *var = tld_var->var;
-            IrInstruction *var_ptr = ir_build_var_ptr(irb, scope, source_node, var, lval == LValPurposeAddressOfConst);
-            if (lval != LValPurposeNone)
+            IrInstruction *var_ptr = ir_build_var_ptr(irb, scope, source_node, var, !lval.is_ptr || lval.is_const);
+            if (lval.is_ptr)
                 return var_ptr;
             else
                 return ir_build_load_ptr(irb, scope, source_node, var_ptr);
@@ -3599,8 +3611,8 @@ static IrInstruction *ir_gen_decl_ref(IrBuilder *irb, AstNode *source_node, Tld
             FnTableEntry *fn_entry = tld_fn->fn_entry;
             assert(fn_entry->type_entry);
             IrInstruction *ref_instruction = ir_build_const_fn(irb, scope, source_node, fn_entry);
-            if (lval != LValPurposeNone)
-                return ir_build_ref(irb, scope, source_node, ref_instruction, true);
+            if (lval.is_ptr)
+                return ir_build_ref(irb, scope, source_node, ref_instruction, true, false);
             else
                 return ref_instruction;
         }
@@ -3609,8 +3621,8 @@ static IrInstruction *ir_gen_decl_ref(IrBuilder *irb, AstNode *source_node, Tld
             TldTypeDef *tld_typedef = (TldTypeDef *)tld;
             TypeTableEntry *typedef_type = tld_typedef->type_entry;
             IrInstruction *ref_instruction = ir_build_const_type(irb, scope, source_node, typedef_type, false);
-            if (lval != LValPurposeNone)
-                return ir_build_ref(irb, scope, source_node, ref_instruction, true);
+            if (lval.is_ptr)
+                return ir_build_ref(irb, scope, source_node, ref_instruction, true, false);
             else
                 return ref_instruction;
         }
@@ -3618,7 +3630,7 @@ static IrInstruction *ir_gen_decl_ref(IrBuilder *irb, AstNode *source_node, Tld
     zig_unreachable();
 }
 
-static IrInstruction *ir_gen_symbol(IrBuilder *irb, Scope *scope, AstNode *node, LValPurpose lval) {
+static IrInstruction *ir_gen_symbol(IrBuilder *irb, Scope *scope, AstNode *node, LVal lval) {
     assert(node->type == NodeTypeSymbol);
 
     Buf *variable_name = node->data.symbol_expr.symbol;
@@ -3626,8 +3638,8 @@ static IrInstruction *ir_gen_symbol(IrBuilder *irb, Scope *scope, AstNode *node,
     auto primitive_table_entry = irb->codegen->primitive_type_table.maybe_get(variable_name);
     if (primitive_table_entry) {
         IrInstruction *value = ir_build_const_type(irb, scope, node, primitive_table_entry->value, false);
-        if (lval != LValPurposeNone) {
-            return ir_build_ref(irb, scope, node, value, lval == LValPurposeAddressOfConst);
+        if (lval.is_ptr) {
+            return ir_build_ref(irb, scope, node, value, lval.is_const, lval.is_volatile);
         } else {
             return value;
         }
@@ -3635,8 +3647,9 @@ static IrInstruction *ir_gen_symbol(IrBuilder *irb, Scope *scope, AstNode *node,
 
     VariableTableEntry *var = find_variable(irb->codegen, scope, variable_name);
     if (var) {
-        IrInstruction *var_ptr = ir_build_var_ptr(irb, scope, node, var, lval == LValPurposeAddressOfConst);
-        if (lval != LValPurposeNone)
+        IrInstruction *var_ptr = ir_build_var_ptr(irb, scope, node, var,
+                !lval.is_ptr || lval.is_const);
+        if (lval.is_ptr)
             return var_ptr;
         else
             return ir_build_load_ptr(irb, scope, node, var_ptr);
@@ -3658,12 +3671,11 @@ static IrInstruction *ir_gen_symbol(IrBuilder *irb, Scope *scope, AstNode *node,
     return irb->codegen->invalid_instruction;
 }
 
-static IrInstruction *ir_gen_array_access(IrBuilder *irb, Scope *scope, AstNode *node, LValPurpose lval) {
+static IrInstruction *ir_gen_array_access(IrBuilder *irb, Scope *scope, AstNode *node, LVal lval) {
     assert(node->type == NodeTypeArrayAccessExpr);
 
     AstNode *array_ref_node = node->data.array_access_expr.array_ref_expr;
-    IrInstruction *array_ref_instruction = ir_gen_node_extra(irb, array_ref_node, scope,
-            LValPurposeAddressOf);
+    IrInstruction *array_ref_instruction = ir_gen_node_extra(irb, array_ref_node, scope, LVAL_PTR);
     if (array_ref_instruction == irb->codegen->invalid_instruction)
         return array_ref_instruction;
 
@@ -3674,25 +3686,24 @@ static IrInstruction *ir_gen_array_access(IrBuilder *irb, Scope *scope, AstNode
 
     IrInstruction *ptr_instruction = ir_build_elem_ptr(irb, scope, node, array_ref_instruction,
             subscript_instruction, true);
-    if (lval != LValPurposeNone)
+    if (lval.is_ptr)
         return ptr_instruction;
 
     return ir_build_load_ptr(irb, scope, node, ptr_instruction);
 }
 
-static IrInstruction *ir_gen_field_access(IrBuilder *irb, Scope *scope, AstNode *node, LValPurpose lval) {
+static IrInstruction *ir_gen_field_access(IrBuilder *irb, Scope *scope, AstNode *node, LVal lval) {
     assert(node->type == NodeTypeFieldAccessExpr);
 
     AstNode *container_ref_node = node->data.field_access_expr.struct_expr;
     Buf *field_name = node->data.field_access_expr.field_name;
 
-    IrInstruction *container_ref_instruction = ir_gen_node_extra(irb, container_ref_node, scope,
-            LValPurposeAddressOf);
+    IrInstruction *container_ref_instruction = ir_gen_node_extra(irb, container_ref_node, scope, LVAL_PTR);
     if (container_ref_instruction == irb->codegen->invalid_instruction)
         return container_ref_instruction;
 
     IrInstruction *ptr_instruction = ir_build_field_ptr(irb, scope, node, container_ref_instruction, field_name);
-    if (lval != LValPurposeNone)
+    if (lval.is_ptr)
         return ptr_instruction;
 
     return ir_build_load_ptr(irb, scope, node, ptr_instruction);
@@ -4199,20 +4210,6 @@ static IrInstruction *ir_gen_builtin_fn_call(IrBuilder *irb, Scope *scope, AstNo
                     return ir_build_set_global_section(irb, scope, node, tld_var, arg1_value);
                 }
             }
-        case BuiltinFnIdVolatileStore:
-            {
-                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
-                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope);
-                if (arg0_value == irb->codegen->invalid_instruction)
-                    return arg0_value;
-
-                AstNode *arg1_node = node->data.fn_call_expr.params.at(1);
-                IrInstruction *arg1_value = ir_gen_node(irb, arg1_node, scope);
-                if (arg1_value == irb->codegen->invalid_instruction)
-                    return arg1_value;
-
-                return ir_build_store_ptr(irb, scope, node, arg0_value, arg1_value, true);
-            }
     }
     zig_unreachable();
 }
@@ -4295,7 +4292,7 @@ static IrInstruction *ir_gen_if_bool_expr(IrBuilder *irb, Scope *scope, AstNode
     return ir_build_phi(irb, scope, node, 2, incoming_blocks, incoming_values);
 }
 
-static IrInstruction *ir_gen_prefix_op_id_lval(IrBuilder *irb, Scope *scope, AstNode *node, IrUnOp op_id, LValPurpose lval) {
+static IrInstruction *ir_gen_prefix_op_id_lval(IrBuilder *irb, Scope *scope, AstNode *node, IrUnOp op_id, LVal lval) {
     assert(node->type == NodeTypePrefixOpExpr);
     AstNode *expr_node = node->data.prefix_op_expr.primary_expr;
 
@@ -4307,38 +4304,38 @@ static IrInstruction *ir_gen_prefix_op_id_lval(IrBuilder *irb, Scope *scope, Ast
 }
 
 static IrInstruction *ir_gen_prefix_op_id(IrBuilder *irb, Scope *scope, AstNode *node, IrUnOp op_id) {
-    return ir_gen_prefix_op_id_lval(irb, scope, node, op_id, LValPurposeNone);
+    return ir_gen_prefix_op_id_lval(irb, scope, node, op_id, LVAL_NONE);
 }
 
-static IrInstruction *ir_lval_wrap(IrBuilder *irb, Scope *scope, IrInstruction *value, LValPurpose lval) {
-    if (lval == LValPurposeNone)
+static IrInstruction *ir_lval_wrap(IrBuilder *irb, Scope *scope, IrInstruction *value, LVal lval) {
+    if (!lval.is_ptr)
         return value;
     if (value == irb->codegen->invalid_instruction)
         return value;
 
     // We needed a pointer to a value, but we got a value. So we create
     // an instruction which just makes a const pointer of it.
-    return ir_build_ref(irb, scope, value->source_node, value, true);
+    return ir_build_ref(irb, scope, value->source_node, value, true, false);
 }
 
-static IrInstruction *ir_gen_address_of(IrBuilder *irb, Scope *scope, AstNode *node, bool is_const, LValPurpose lval) {
+static IrInstruction *ir_gen_address_of(IrBuilder *irb, Scope *scope, AstNode *node,
+        bool is_const, bool is_volatile, LVal lval)
+{
     assert(node->type == NodeTypePrefixOpExpr);
     AstNode *expr_node = node->data.prefix_op_expr.primary_expr;
 
-    IrInstruction *value = ir_gen_node_extra(irb, expr_node, scope,
-            is_const ? LValPurposeAddressOfConst : LValPurposeAddressOf);
+    IrInstruction *value = ir_gen_node_extra(irb, expr_node, scope, make_lval_addr(is_const, is_volatile));
     if (value == irb->codegen->invalid_instruction)
         return value;
 
-
     return ir_lval_wrap(irb, scope, value, lval);
 }
 
-static IrInstruction *ir_gen_err_assert_ok(IrBuilder *irb, Scope *scope, AstNode *node, LValPurpose lval) {
+static IrInstruction *ir_gen_err_assert_ok(IrBuilder *irb, Scope *scope, AstNode *node, LVal lval) {
     assert(node->type == NodeTypePrefixOpExpr);
     AstNode *expr_node = node->data.prefix_op_expr.primary_expr;
 
-    IrInstruction *err_union_ptr = ir_gen_node_extra(irb, expr_node, scope, LValPurposeAddressOf);
+    IrInstruction *err_union_ptr = ir_gen_node_extra(irb, expr_node, scope, LVAL_PTR);
     if (err_union_ptr == irb->codegen->invalid_instruction)
         return irb->codegen->invalid_instruction;
 
@@ -4346,25 +4343,25 @@ static IrInstruction *ir_gen_err_assert_ok(IrBuilder *irb, Scope *scope, AstNode
     if (payload_ptr == irb->codegen->invalid_instruction)
         return irb->codegen->invalid_instruction;
 
-    if (lval == LValPurposeNone)
-        return ir_build_load_ptr(irb, scope, node, payload_ptr);
-    else
+    if (lval.is_ptr)
         return payload_ptr;
+
+    return ir_build_load_ptr(irb, scope, node, payload_ptr);
 }
 
-static IrInstruction *ir_gen_maybe_assert_ok(IrBuilder *irb, Scope *scope, AstNode *node, LValPurpose lval) {
+static IrInstruction *ir_gen_maybe_assert_ok(IrBuilder *irb, Scope *scope, AstNode *node, LVal lval) {
     assert(node->type == NodeTypePrefixOpExpr);
     AstNode *expr_node = node->data.prefix_op_expr.primary_expr;
 
-    IrInstruction *maybe_ptr = ir_gen_node_extra(irb, expr_node, scope, LValPurposeAddressOf);
+    IrInstruction *maybe_ptr = ir_gen_node_extra(irb, expr_node, scope, LVAL_PTR);
     if (maybe_ptr == irb->codegen->invalid_instruction)
         return irb->codegen->invalid_instruction;
 
     IrInstruction *unwrapped_ptr = ir_build_unwrap_maybe(irb, scope, node, maybe_ptr, true);
-    if (lval == LValPurposeNone)
-        return ir_build_load_ptr(irb, scope, node, unwrapped_ptr);
-    else
+    if (lval.is_ptr)
         return unwrapped_ptr;
+
+    return ir_build_load_ptr(irb, scope, node, unwrapped_ptr);
 }
 
 static IrInstruction *ir_gen_bool_not(IrBuilder *irb, Scope *scope, AstNode *node) {
@@ -4378,7 +4375,7 @@ static IrInstruction *ir_gen_bool_not(IrBuilder *irb, Scope *scope, AstNode *nod
     return ir_build_bool_not(irb, scope, node, value);
 }
 
-static IrInstruction *ir_gen_prefix_op_expr(IrBuilder *irb, Scope *scope, AstNode *node, LValPurpose lval) {
+static IrInstruction *ir_gen_prefix_op_expr(IrBuilder *irb, Scope *scope, AstNode *node, LVal lval) {
     assert(node->type == NodeTypePrefixOpExpr);
 
     PrefixOp prefix_op = node->data.prefix_op_expr.prefix_op;
@@ -4395,9 +4392,13 @@ static IrInstruction *ir_gen_prefix_op_expr(IrBuilder *irb, Scope *scope, AstNod
         case PrefixOpNegationWrap:
             return ir_lval_wrap(irb, scope, ir_gen_prefix_op_id(irb, scope, node, IrUnOpNegationWrap), lval);
         case PrefixOpAddressOf:
-            return ir_gen_address_of(irb, scope, node, false, lval);
+            return ir_gen_address_of(irb, scope, node, false, false, lval);
         case PrefixOpConstAddressOf:
-            return ir_gen_address_of(irb, scope, node, true, lval);
+            return ir_gen_address_of(irb, scope, node, true, false, lval);
+        case PrefixOpVolatileAddressOf:
+            return ir_gen_address_of(irb, scope, node, false, true, lval);
+        case PrefixOpConstVolatileAddressOf:
+            return ir_gen_address_of(irb, scope, node, true, true, lval);
         case PrefixOpDereference:
             return ir_gen_prefix_op_id_lval(irb, scope, node, IrUnOpDereference, lval);
         case PrefixOpMaybe:
@@ -4559,7 +4560,7 @@ static IrInstruction *ir_gen_for_expr(IrBuilder *irb, Scope *parent_scope, AstNo
     }
     assert(elem_node->type == NodeTypeSymbol);
 
-    IrInstruction *array_val_ptr = ir_gen_node_extra(irb, array_node, parent_scope, LValPurposeAddressOf);
+    IrInstruction *array_val_ptr = ir_gen_node_extra(irb, array_node, parent_scope, LVAL_PTR);
     if (array_val_ptr == irb->codegen->invalid_instruction)
         return array_val_ptr;
 
@@ -4627,7 +4628,7 @@ static IrInstruction *ir_gen_for_expr(IrBuilder *irb, Scope *parent_scope, AstNo
     } else {
         elem_val = ir_build_load_ptr(irb, child_scope, node, elem_ptr);
     }
-    ir_mark_gen(ir_build_store_ptr(irb, child_scope, node, elem_var_ptr, elem_val, false));
+    ir_mark_gen(ir_build_store_ptr(irb, child_scope, node, elem_var_ptr, elem_val));
 
     LoopStackItem *loop_stack_item = irb->loop_stack.add_one();
     loop_stack_item->break_block = end_block;
@@ -4641,7 +4642,7 @@ static IrInstruction *ir_gen_for_expr(IrBuilder *irb, Scope *parent_scope, AstNo
 
     ir_set_cursor_at_end(irb, continue_block);
     IrInstruction *new_index_val = ir_build_bin_op(irb, child_scope, node, IrBinOpAdd, index_val, one, false);
-    ir_mark_gen(ir_build_store_ptr(irb, child_scope, node, index_ptr, new_index_val, false));
+    ir_mark_gen(ir_build_store_ptr(irb, child_scope, node, index_ptr, new_index_val));
     ir_build_br(irb, child_scope, node, cond_block, is_comptime);
 
     ir_set_cursor_at_end(irb, end_block);
@@ -4783,7 +4784,7 @@ static IrInstruction *ir_gen_if_var_expr(IrBuilder *irb, Scope *scope, AstNode *
     AstNode *else_node = node->data.if_var_expr.else_node;
     bool var_is_ptr = node->data.if_var_expr.var_is_ptr;
 
-    IrInstruction *maybe_val_ptr = ir_gen_node_extra(irb, expr_node, scope, LValPurposeAddressOf);
+    IrInstruction *maybe_val_ptr = ir_gen_node_extra(irb, expr_node, scope, LVAL_PTR);
     if (maybe_val_ptr == irb->codegen->invalid_instruction)
         return maybe_val_ptr;
 
@@ -4859,7 +4860,7 @@ static IrInstruction *ir_gen_try_expr(IrBuilder *irb, Scope *scope, AstNode *nod
     Buf *var_symbol = node->data.try_expr.var_symbol;
     Buf *err_symbol = node->data.try_expr.err_symbol;
 
-    IrInstruction *err_val_ptr = ir_gen_node_extra(irb, target_node, scope, LValPurposeAddressOf);
+    IrInstruction *err_val_ptr = ir_gen_node_extra(irb, target_node, scope, LVAL_PTR);
     if (err_val_ptr == irb->codegen->invalid_instruction)
         return err_val_ptr;
 
@@ -4987,7 +4988,7 @@ static IrInstruction *ir_gen_switch_expr(IrBuilder *irb, Scope *scope, AstNode *
     assert(node->type == NodeTypeSwitchExpr);
 
     AstNode *target_node = node->data.switch_expr.expr;
-    IrInstruction *target_value_ptr = ir_gen_node_extra(irb, target_node, scope, LValPurposeAddressOf);
+    IrInstruction *target_value_ptr = ir_gen_node_extra(irb, target_node, scope, LVAL_PTR);
     if (target_value_ptr == irb->codegen->invalid_instruction)
         return target_value_ptr;
     IrInstruction *target_value = ir_build_switch_target(irb, scope, node, target_value_ptr);
@@ -5175,7 +5176,7 @@ static IrInstruction *ir_gen_goto(IrBuilder *irb, Scope *scope, AstNode *node) {
     return ir_build_unreachable(irb, scope, node);
 }
 
-static IrInstruction *ir_gen_comptime(IrBuilder *irb, Scope *parent_scope, AstNode *node, LValPurpose lval) {
+static IrInstruction *ir_gen_comptime(IrBuilder *irb, Scope *parent_scope, AstNode *node, LVal lval) {
     assert(node->type == NodeTypeCompTime);
 
     Scope *child_scope = create_comptime_scope(node, parent_scope);
@@ -5285,7 +5286,7 @@ static IrInstruction *ir_gen_err_ok_or(IrBuilder *irb, Scope *parent_scope, AstN
     AstNode *op2_node = node->data.unwrap_err_expr.op2;
     AstNode *var_node = node->data.unwrap_err_expr.symbol;
 
-    IrInstruction *err_union_ptr = ir_gen_node_extra(irb, op1_node, parent_scope, LValPurposeAddressOf);
+    IrInstruction *err_union_ptr = ir_gen_node_extra(irb, op1_node, parent_scope, LVAL_PTR);
     if (err_union_ptr == irb->codegen->invalid_instruction)
         return irb->codegen->invalid_instruction;
 
@@ -5423,7 +5424,7 @@ static IrInstruction *ir_gen_fn_proto(IrBuilder *irb, Scope *parent_scope, AstNo
 }
 
 static IrInstruction *ir_gen_node_raw(IrBuilder *irb, AstNode *node, Scope *scope,
-        LValPurpose lval)
+        LVal lval)
 {
     assert(scope);
     switch (node->type) {
@@ -5522,16 +5523,14 @@ static IrInstruction *ir_gen_node_raw(IrBuilder *irb, AstNode *node, Scope *scop
     zig_unreachable();
 }
 
-static IrInstruction *ir_gen_node_extra(IrBuilder *irb, AstNode *node, Scope *scope,
-        LValPurpose lval)
-{
+static IrInstruction *ir_gen_node_extra(IrBuilder *irb, AstNode *node, Scope *scope, LVal lval) {
     IrInstruction *result = ir_gen_node_raw(irb, node, scope, lval);
     irb->exec->invalid = irb->exec->invalid || (result == irb->codegen->invalid_instruction);
     return result;
 }
 
 static IrInstruction *ir_gen_node(IrBuilder *irb, AstNode *node, Scope *scope) {
-    return ir_gen_node_extra(irb, node, scope, LValPurposeNone);
+    return ir_gen_node_extra(irb, node, scope, LVAL_NONE);
 }
 
 static bool ir_goto_pass2(IrBuilder *irb) {
@@ -5589,7 +5588,7 @@ bool ir_gen(CodeGen *codegen, AstNode *node, Scope *scope, IrExecutable *ir_exec
     // Entry block gets a reference because we enter it to begin.
     ir_ref_bb(irb->current_basic_block);
 
-    IrInstruction *result = ir_gen_node_extra(irb, node, scope, LValPurposeNone);
+    IrInstruction *result = ir_gen_node_extra(irb, node, scope, LVAL_NONE);
     assert(result);
     if (irb->exec->invalid)
         return false;
@@ -6178,7 +6177,7 @@ static TypeTableEntry *ir_analyze_void(IrAnalyze *ira, IrInstruction *instructio
 
 static TypeTableEntry *ir_analyze_const_ptr(IrAnalyze *ira, IrInstruction *instruction,
         ConstExprValue *pointee, TypeTableEntry *pointee_type, bool depends_on_compile_var,
-        ConstPtrSpecial special, bool ptr_is_const)
+        ConstPtrSpecial special, bool ptr_is_const, bool ptr_is_volatile)
 {
     if (pointee_type->id == TypeTableEntryIdMetaType) {
         TypeTableEntry *type_entry = pointee->data.x_type;
@@ -6190,10 +6189,12 @@ static TypeTableEntry *ir_analyze_const_ptr(IrAnalyze *ira, IrInstruction *instr
         ConstExprValue *const_val = ir_build_const_from(ira, instruction,
                 depends_on_compile_var || pointee->depends_on_compile_var);
         type_ensure_zero_bits_known(ira->codegen, type_entry);
-        const_val->data.x_type = get_pointer_to_type(ira->codegen, type_entry, ptr_is_const);
+        const_val->data.x_type = get_pointer_to_type_volatile(ira->codegen, type_entry,
+                ptr_is_const, ptr_is_volatile);
         return pointee_type;
     } else {
-        TypeTableEntry *ptr_type = get_pointer_to_type(ira->codegen, pointee_type, ptr_is_const);
+        TypeTableEntry *ptr_type = get_pointer_to_type_volatile(ira->codegen, pointee_type,
+                ptr_is_const, ptr_is_volatile);
         ConstExprValue *const_val = ir_build_const_from(ira, instruction,
                 depends_on_compile_var || pointee->depends_on_compile_var);
         const_val->data.x_ptr.base_ptr = pointee;
@@ -6433,7 +6434,9 @@ static IrInstruction *ir_analyze_err_wrap_code(IrAnalyze *ira, IrInstruction *so
     return result;
 }
 
-static IrInstruction *ir_analyze_cast_ref(IrAnalyze *ira, IrInstruction *source_instr, IrInstruction *value, TypeTableEntry *wanted_type) {
+static IrInstruction *ir_analyze_cast_ref(IrAnalyze *ira, IrInstruction *source_instr,
+        IrInstruction *value, TypeTableEntry *wanted_type)
+{
     if (instr_is_comptime(value)) {
         ConstExprValue *val = ir_resolve_const(ira, value, UndefBad);
         if (!val)
@@ -6454,7 +6457,7 @@ static IrInstruction *ir_analyze_cast_ref(IrAnalyze *ira, IrInstruction *source_
         return load_ptr_inst->ptr;
     } else {
         IrInstruction *new_instruction = ir_build_ref(&ira->new_irb, source_instr->scope,
-                source_instr->source_node, value, true);
+                source_instr->source_node, value, true, false);
         new_instruction->value.type = wanted_type;
 
         TypeTableEntry *child_type = wanted_type->data.pointer.child_type;
@@ -6997,7 +7000,7 @@ static IrInstruction *ir_get_deref(IrAnalyze *ira, IrInstruction *source_instruc
 }
 
 static TypeTableEntry *ir_analyze_ref(IrAnalyze *ira, IrInstruction *source_instruction, IrInstruction *value,
-        bool is_const)
+        bool is_const, bool is_volatile)
 {
     if (value->value.type->id == TypeTableEntryIdInvalid)
         return ira->codegen->builtin_types.entry_invalid;
@@ -7007,13 +7010,14 @@ static TypeTableEntry *ir_analyze_ref(IrAnalyze *ira, IrInstruction *source_inst
         if (!val)
             return ira->codegen->builtin_types.entry_invalid;
         return ir_analyze_const_ptr(ira, source_instruction, val, value->value.type,
-                value->value.depends_on_compile_var, ConstPtrSpecialNone, is_const);
+                value->value.depends_on_compile_var, ConstPtrSpecialNone, is_const, is_volatile);
     }
 
-    TypeTableEntry *ptr_type = get_pointer_to_type(ira->codegen, value->value.type, true);
+    TypeTableEntry *ptr_type = get_pointer_to_type_volatile(ira->codegen, value->value.type, is_const, is_volatile);
     FnTableEntry *fn_entry = exec_fn_entry(ira->new_irb.exec);
     assert(fn_entry);
-    IrInstruction *new_instruction = ir_build_ref_from(&ira->new_irb, source_instruction, value, is_const);
+    IrInstruction *new_instruction = ir_build_ref_from(&ira->new_irb, source_instruction,
+            value, is_const, is_volatile);
     fn_entry->alloca_list.append(new_instruction);
     return ptr_type;
 }
@@ -8700,7 +8704,7 @@ static TypeTableEntry *ir_analyze_var_ptr(IrAnalyze *ira, IrInstruction *instruc
         bool is_const = (var->value.type->id == TypeTableEntryIdMetaType) ? is_const_ptr : var->src_is_const;
         depends_on_compile_var = mem_slot->depends_on_compile_var || depends_on_compile_var || is_comptime;
         return ir_analyze_const_ptr(ira, instruction, mem_slot, var->value.type,
-                depends_on_compile_var, ptr_special, is_const);
+                depends_on_compile_var, ptr_special, is_const, false);
     } else {
         ir_build_var_ptr_from(&ira->new_irb, instruction, var, false);
         type_ensure_zero_bits_known(ira->codegen, var->value.type);
@@ -8786,7 +8790,8 @@ static TypeTableEntry *ir_analyze_instruction_elem_ptr(IrAnalyze *ira, IrInstruc
             return ir_analyze_var_ptr(ira, &elem_ptr_instruction->base, var, true, depends_on_compile_var);
         } else {
             return ir_analyze_const_ptr(ira, &elem_ptr_instruction->base, &ira->codegen->const_void_val,
-                    ira->codegen->builtin_types.entry_void, depends_on_compile_var, ConstPtrSpecialNone, true);
+                    ira->codegen->builtin_types.entry_void, depends_on_compile_var, ConstPtrSpecialNone,
+                    true, false);
         }
     } else {
         ir_add_error_node(ira, elem_ptr_instruction->base.source_node,
@@ -8894,7 +8899,7 @@ static TypeTableEntry *ir_analyze_container_member_access_inner(IrAnalyze *ira,
             bool depends_on_compile_var = container_ptr->value.depends_on_compile_var;
             IrInstruction *bound_fn_value = ir_build_const_bound_fn(&ira->new_irb, field_ptr_instruction->base.scope,
                 field_ptr_instruction->base.source_node, fn_entry, container_ptr, depends_on_compile_var);
-            return ir_analyze_ref(ira, &field_ptr_instruction->base, bound_fn_value, true);
+            return ir_analyze_ref(ira, &field_ptr_instruction->base, bound_fn_value, true, false);
         }
     }
     ir_add_error_node(ira, field_ptr_instruction->base.source_node,
@@ -8911,6 +8916,7 @@ static TypeTableEntry *ir_analyze_container_field_ptr(IrAnalyze *ira, Buf *field
 
     assert(container_ptr->value.type->id == TypeTableEntryIdPointer);
     bool is_const = container_ptr->value.type->data.pointer.is_const;
+    bool is_volatile = container_ptr->value.type->data.pointer.is_volatile;
     if (bare_type->id == TypeTableEntryIdStruct) {
         if (bare_type->data.structure.is_invalid)
             return ira->codegen->builtin_types.entry_invalid;
@@ -8929,12 +8935,12 @@ static TypeTableEntry *ir_analyze_container_field_ptr(IrAnalyze *ira, Buf *field
                         bool depends_on_compile_var = field_val->depends_on_compile_var ||
                             struct_val->depends_on_compile_var || ptr_val->depends_on_compile_var;
                         return ir_analyze_const_ptr(ira, &field_ptr_instruction->base, field_val,
-                                field_val->type, depends_on_compile_var, ConstPtrSpecialNone, is_const);
+                                field_val->type, depends_on_compile_var, ConstPtrSpecialNone, is_const, is_volatile);
                     }
                 }
             }
             ir_build_struct_field_ptr_from(&ira->new_irb, &field_ptr_instruction->base, container_ptr, field);
-            return get_pointer_to_type(ira->codegen, field->type_entry, is_const);
+            return get_pointer_to_type_volatile(ira->codegen, field->type_entry, is_const, is_volatile);
         } else {
             return ir_analyze_container_member_access_inner(ira, bare_type, field_name,
                 field_ptr_instruction, container_ptr, container_type);
@@ -8946,7 +8952,7 @@ static TypeTableEntry *ir_analyze_container_field_ptr(IrAnalyze *ira, Buf *field
         TypeEnumField *field = find_enum_type_field(bare_type, field_name);
         if (field) {
             ir_build_enum_field_ptr_from(&ira->new_irb, &field_ptr_instruction->base, container_ptr, field);
-            return get_pointer_to_type(ira->codegen, field->type_entry, is_const);
+            return get_pointer_to_type_volatile(ira->codegen, field->type_entry, is_const, is_volatile);
         } else {
             return ir_analyze_container_member_access_inner(ira, bare_type, field_name,
                 field_ptr_instruction, container_ptr, container_type);
@@ -8992,8 +8998,9 @@ static TypeTableEntry *ir_analyze_decl_ref(IrAnalyze *ira, IrInstruction *source
             const_val->data.x_fn = fn_entry;
 
             bool ptr_is_const = true;
+            bool ptr_is_volatile = false;
             return ir_analyze_const_ptr(ira, source_instruction, const_val, fn_entry->type_entry,
-                    depends_on_compile_var, ConstPtrSpecialNone, ptr_is_const);
+                    depends_on_compile_var, ConstPtrSpecialNone, ptr_is_const, ptr_is_volatile);
         }
         case TldIdTypeDef:
         {
@@ -9008,8 +9015,9 @@ static TypeTableEntry *ir_analyze_decl_ref(IrAnalyze *ira, IrInstruction *source
             const_val->data.x_type = tld_typedef->type_entry;
 
             bool ptr_is_const = true;
+            bool ptr_is_volatile = false;
             return ir_analyze_const_ptr(ira, source_instruction, const_val, ira->codegen->builtin_types.entry_type,
-                    depends_on_compile_var, ConstPtrSpecialNone, ptr_is_const);
+                    depends_on_compile_var, ConstPtrSpecialNone, ptr_is_const, ptr_is_volatile);
         }
     }
     zig_unreachable();
@@ -9051,8 +9059,9 @@ static TypeTableEntry *ir_analyze_instruction_field_ptr(IrAnalyze *ira, IrInstru
 
             TypeTableEntry *usize = ira->codegen->builtin_types.entry_usize;
             bool ptr_is_const = true;
+            bool ptr_is_volatile = false;
             return ir_analyze_const_ptr(ira, &field_ptr_instruction->base, len_val,
-                    usize, false, ConstPtrSpecialNone, ptr_is_const);
+                    usize, false, ConstPtrSpecialNone, ptr_is_const, ptr_is_volatile);
         } else {
             ir_add_error_node(ira, source_node,
                 buf_sprintf("no member named '%s' in '%s'", buf_ptr(field_name),
@@ -9074,8 +9083,9 @@ static TypeTableEntry *ir_analyze_instruction_field_ptr(IrAnalyze *ira, IrInstru
 
             TypeTableEntry *usize = ira->codegen->builtin_types.entry_usize;
             bool ptr_is_const = true;
+            bool ptr_is_volatile = false;
             return ir_analyze_const_ptr(ira, &field_ptr_instruction->base, len_val,
-                    usize, false, ConstPtrSpecialNone, ptr_is_const);
+                    usize, false, ConstPtrSpecialNone, ptr_is_const, ptr_is_volatile);
         } else {
             ir_add_error_node(ira, source_node,
                 buf_sprintf("no member named '%s' in '%s'", buf_ptr(field_name),
@@ -9111,15 +9121,17 @@ static TypeTableEntry *ir_analyze_instruction_field_ptr(IrAnalyze *ira, IrInstru
                 if (field) {
                     if (field->type_entry->id == TypeTableEntryIdVoid) {
                         bool ptr_is_const = true;
+                        bool ptr_is_volatile = false;
                         return ir_analyze_const_ptr(ira, &field_ptr_instruction->base,
                                 create_const_enum_tag(child_type, field->value), child_type, depends_on_compile_var,
-                                ConstPtrSpecialNone, ptr_is_const);
+                                ConstPtrSpecialNone, ptr_is_const, ptr_is_volatile);
                     } else {
                         bool ptr_is_const = true;
+                        bool ptr_is_volatile = false;
                         return ir_analyze_const_ptr(ira, &field_ptr_instruction->base,
                             create_const_unsigned_negative(child_type->data.enumeration.tag_type, field->value, false),
                             child_type->data.enumeration.tag_type, depends_on_compile_var,
-                            ConstPtrSpecialNone, ptr_is_const);
+                            ConstPtrSpecialNone, ptr_is_const, ptr_is_volatile);
                     }
                 }
             }
@@ -9142,8 +9154,9 @@ static TypeTableEntry *ir_analyze_instruction_field_ptr(IrAnalyze *ira, IrInstru
                 const_val->data.x_pure_err = err_table_entry->value;
 
                 bool ptr_is_const = true;
+                bool ptr_is_volatile = false;
                 return ir_analyze_const_ptr(ira, &field_ptr_instruction->base, const_val,
-                        child_type, depends_on_compile_var, ConstPtrSpecialNone, ptr_is_const);
+                        child_type, depends_on_compile_var, ConstPtrSpecialNone, ptr_is_const, ptr_is_volatile);
             }
 
             ir_add_error(ira, &field_ptr_instruction->base,
@@ -9152,17 +9165,19 @@ static TypeTableEntry *ir_analyze_instruction_field_ptr(IrAnalyze *ira, IrInstru
         } else if (child_type->id == TypeTableEntryIdInt) {
             if (buf_eql_str(field_name, "bit_count")) {
                 bool ptr_is_const = true;
+                bool ptr_is_volatile = false;
                 return ir_analyze_const_ptr(ira, &field_ptr_instruction->base,
                     create_const_unsigned_negative(ira->codegen->builtin_types.entry_num_lit_int,
                         child_type->data.integral.bit_count, false),
                     ira->codegen->builtin_types.entry_num_lit_int, depends_on_compile_var,
-                    ConstPtrSpecialNone, ptr_is_const);
+                    ConstPtrSpecialNone, ptr_is_const, ptr_is_volatile);
             } else if (buf_eql_str(field_name, "is_signed")) {
                 bool ptr_is_const = true;
+                bool ptr_is_volatile = false;
                 return ir_analyze_const_ptr(ira, &field_ptr_instruction->base,
                     create_const_bool(ira->codegen, child_type->data.integral.is_signed),
                     ira->codegen->builtin_types.entry_bool, depends_on_compile_var,
-                    ConstPtrSpecialNone, ptr_is_const);
+                    ConstPtrSpecialNone, ptr_is_const, ptr_is_volatile);
             } else {
                 ir_add_error(ira, &field_ptr_instruction->base,
                     buf_sprintf("type '%s' has no member called '%s'",
@@ -9288,12 +9303,11 @@ static TypeTableEntry *ir_analyze_instruction_store_ptr(IrAnalyze *ira, IrInstru
         }
         new_ptr_inst->value.type = ptr->value.type;
         ir_build_store_ptr(&ira->new_irb, store_ptr_instruction->base.scope,
-            store_ptr_instruction->base.source_node, new_ptr_inst, casted_value, false);
+            store_ptr_instruction->base.source_node, new_ptr_inst, casted_value);
         return ir_analyze_void(ira, &store_ptr_instruction->base);
     }
 
-    ir_build_store_ptr_from(&ira->new_irb, &store_ptr_instruction->base, ptr, casted_value,
-            store_ptr_instruction->is_volatile);
+    ir_build_store_ptr_from(&ira->new_irb, &store_ptr_instruction->base, ptr, casted_value);
     return ira->codegen->builtin_types.entry_void;
 }
 
@@ -10337,7 +10351,7 @@ static TypeTableEntry *ir_analyze_instruction_array_len(IrAnalyze *ira,
 
 static TypeTableEntry *ir_analyze_instruction_ref(IrAnalyze *ira, IrInstructionRef *ref_instruction) {
     IrInstruction *value = ref_instruction->value->other;
-    return ir_analyze_ref(ira, &ref_instruction->base, value, ref_instruction->is_const);
+    return ir_analyze_ref(ira, &ref_instruction->base, value, ref_instruction->is_const, ref_instruction->is_volatile);
 }
 
 static TypeTableEntry *ir_analyze_container_init_fields(IrAnalyze *ira, IrInstruction *instruction,
src/ir_print.cpp
@@ -299,9 +299,6 @@ static void ir_print_store_ptr(IrPrint *irp, IrInstructionStorePtr *instruction)
     ir_print_var_instruction(irp, instruction->ptr);
     fprintf(irp->f, " = ");
     ir_print_other_instruction(irp, instruction->value);
-    if (instruction->is_volatile) {
-        fprintf(irp->f, " // volatile");
-    }
 }
 
 static void ir_print_typeof(IrPrint *irp, IrInstructionTypeOf *instruction) {
@@ -512,7 +509,8 @@ static void ir_print_array_len(IrPrint *irp, IrInstructionArrayLen *instruction)
 
 static void ir_print_ref(IrPrint *irp, IrInstructionRef *instruction) {
     const char *const_str = instruction->is_const ? "const " : "";
-    fprintf(irp->f, "%sref ", const_str);
+    const char *volatile_str = instruction->is_volatile ? "volatile " : "";
+    fprintf(irp->f, "%s%sref ", const_str, volatile_str);
     ir_print_other_instruction(irp, instruction->value);
 }
 
src/parser.cpp
@@ -996,7 +996,7 @@ static PrefixOp tok_to_prefix_op(Token *token) {
 
 /*
 PrefixOpExpression : PrefixOp PrefixOpExpression | SuffixOpExpression
-PrefixOp = "!" | "-" | "~" | "*" | ("&" option("const")) | "?" | "%" | "%%" | "??" | "-%"
+PrefixOp = "!" | "-" | "~" | "*" | ("&" option("const") option("volatile")) | "?" | "%" | "%%" | "??" | "-%"
 */
 static AstNode *ast_parse_prefix_op_expr(ParseContext *pc, size_t *token_index, bool mandatory) {
     Token *token = &pc->tokens->at(*token_index);
@@ -1036,10 +1036,19 @@ static AstNode *ast_parse_prefix_op_expr(ParseContext *pc, size_t *token_index,
     }
 
     if (prefix_op == PrefixOpAddressOf) {
-        Token *token = &pc->tokens->at(*token_index);
-        if (token->id == TokenIdKeywordConst) {
+        Token *const_or_volatile_tok = &pc->tokens->at(*token_index);
+        if (const_or_volatile_tok->id == TokenIdKeywordConst) {
+            *token_index += 1;
+            Token *volatile_token = &pc->tokens->at(*token_index);
+            if (volatile_token->id == TokenIdKeywordVolatile) {
+                *token_index += 1;
+                prefix_op = PrefixOpConstVolatileAddressOf;
+            } else {
+                prefix_op = PrefixOpConstAddressOf;
+            }
+        } else if (const_or_volatile_tok->id == TokenIdKeywordVolatile) {
+            prefix_op = PrefixOpVolatileAddressOf;
             *token_index += 1;
-            prefix_op = PrefixOpConstAddressOf;
         }
     }
 
test/cases/misc.zig
@@ -566,3 +566,12 @@ fn typeName() {
         assert(str.eql(@typeName(&usize), "&usize"));
     }
 }
+
+fn volatileLoadAndStore() {
+    @setFnTest(this);
+
+    var number: i32 = 1234;
+    const ptr = &volatile number;
+    *ptr += 1;
+    assert(*ptr == 1235);
+}