Commit 30a824cb9e

Andrew Kelley <andrew@ziglang.org>
2021-01-19 08:38:53
astgen: eliminate rlWrapPtr and all its callsites
The following AST avoids unnecessary derefs now: * error set decl * field access * array access * for loops: replace ensure_indexable and deref on the len_ptr with a special purpose ZIR instruction called indexable_ptr_len. Added an error note when for loop operand is the wrong type. I also accidentally implemented `@field`.
1 parent 7e56028
src/astgen.zig
@@ -278,7 +278,7 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerEr
         .ErrorUnion => return rlWrap(mod, scope, rl, try typeInixOp(mod, scope, node.castTag(.ErrorUnion).?, .error_union_type)),
         .MergeErrorSets => return rlWrap(mod, scope, rl, try typeInixOp(mod, scope, node.castTag(.MergeErrorSets).?, .merge_error_sets)),
         .AnyFrameType => return rlWrap(mod, scope, rl, try anyFrameType(mod, scope, node.castTag(.AnyFrameType).?)),
-        .ErrorSetDecl => return errorSetDecl(mod, scope, rl, node.castTag(.ErrorSetDecl).?),
+        .ErrorSetDecl => return rlWrap(mod, scope, rl, try errorSetDecl(mod, scope, node.castTag(.ErrorSetDecl).?)),
         .ErrorType => return rlWrap(mod, scope, rl, try errorType(mod, scope, node.castTag(.ErrorType).?)),
         .For => return forExpr(mod, scope, rl, node.castTag(.For).?),
         .ArrayAccess => return arrayAccess(mod, scope, rl, node.castTag(.ArrayAccess).?),
@@ -1107,7 +1107,7 @@ fn containerDecl(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.Con
     }
 }
 
-fn errorSetDecl(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.ErrorSetDecl) InnerError!*zir.Inst {
+fn errorSetDecl(mod: *Module, scope: *Scope, node: *ast.Node.ErrorSetDecl) InnerError!*zir.Inst {
     const tree = scope.tree();
     const src = tree.token_locs[node.error_token].start;
     const decls = node.decls();
@@ -1118,9 +1118,7 @@ fn errorSetDecl(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.Erro
         fields[i] = try mod.identifierTokenString(scope, tag.name_token);
     }
 
-    // analyzing the error set results in a decl ref, so we might need to dereference it
-    // TODO remove all callsites to rlWrapPtr
-    return rlWrapPtr(mod, scope, rl, try addZIRInst(mod, scope, src, zir.Inst.ErrorSet, .{ .fields = fields }, .{}));
+    return addZIRInst(mod, scope, src, zir.Inst.ErrorSet, .{ .fields = fields }, .{});
 }
 
 fn errorType(mod: *Module, scope: *Scope, node: *ast.Node.OneToken) InnerError!*zir.Inst {
@@ -1299,35 +1297,72 @@ fn tokenIdentEql(mod: *Module, scope: *Scope, token1: ast.TokenIndex, token2: as
     return mem.eql(u8, ident_name_1, ident_name_2);
 }
 
-pub fn identifierStringInst(mod: *Module, scope: *Scope, node: *ast.Node.OneToken) InnerError!*zir.Inst {
+pub fn field(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.SimpleInfixOp) InnerError!*zir.Inst {
     const tree = scope.tree();
-    const src = tree.token_locs[node.token].start;
-
-    const ident_name = try mod.identifierTokenString(scope, node.token);
-
-    return addZIRInst(mod, scope, src, zir.Inst.Str, .{ .bytes = ident_name }, .{});
+    const src = tree.token_locs[node.op_token].start;
+    // TODO custom AST node for field access so that we don't have to go through a node cast here
+    const field_name = try mod.identifierTokenString(scope, node.rhs.castTag(.Identifier).?.token);
+    if (rl == .ref) {
+        return addZirInstTag(mod, scope, src, .field_ptr, .{
+            .object = try expr(mod, scope, .ref, node.lhs),
+            .field_name = field_name,
+        });
+    }
+    return rlWrap(mod, scope, rl, try addZirInstTag(mod, scope, src, .field_val, .{
+        .object = try expr(mod, scope, .none, node.lhs),
+        .field_name = field_name,
+    }));
 }
 
-fn field(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.SimpleInfixOp) InnerError!*zir.Inst {
+fn namedField(
+    mod: *Module,
+    scope: *Scope,
+    rl: ResultLoc,
+    call: *ast.Node.BuiltinCall,
+) InnerError!*zir.Inst {
+    try ensureBuiltinParamCount(mod, scope, call, 2);
+
     const tree = scope.tree();
-    const src = tree.token_locs[node.op_token].start;
+    const src = tree.token_locs[call.builtin_token].start;
+    const params = call.params();
 
-    const lhs = try expr(mod, scope, .ref, node.lhs);
-    const field_name = try identifierStringInst(mod, scope, node.rhs.castTag(.Identifier).?);
+    const string_type = try addZIRInstConst(mod, scope, src, .{
+        .ty = Type.initTag(.type),
+        .val = Value.initTag(.const_slice_u8_type),
+    });
+    const string_rl: ResultLoc = .{ .ty = string_type };
 
-    // TODO remove all callsites to rlWrapPtr
-    return rlWrapPtr(mod, scope, rl, try addZIRInst(mod, scope, src, zir.Inst.FieldPtr, .{ .object_ptr = lhs, .field_name = field_name }, .{}));
+    if (rl == .ref) {
+        return addZirInstTag(mod, scope, src, .field_ptr_named, .{
+            .object = try expr(mod, scope, .ref, params[0]),
+            .field_name = try comptimeExpr(mod, scope, string_rl, params[1]),
+        });
+    }
+    return rlWrap(mod, scope, rl, try addZirInstTag(mod, scope, src, .field_val_named, .{
+        .object = try expr(mod, scope, .none, params[0]),
+        .field_name = try comptimeExpr(mod, scope, string_rl, params[1]),
+    }));
 }
 
 fn arrayAccess(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.ArrayAccess) InnerError!*zir.Inst {
     const tree = scope.tree();
     const src = tree.token_locs[node.rtoken].start;
+    const usize_type = try addZIRInstConst(mod, scope, src, .{
+        .ty = Type.initTag(.type),
+        .val = Value.initTag(.usize_type),
+    });
+    const index_rl: ResultLoc = .{ .ty = usize_type };
 
-    const array_ptr = try expr(mod, scope, .ref, node.lhs);
-    const index = try expr(mod, scope, .none, node.index_expr);
-
-    // TODO remove all callsites to rlWrapPtr
-    return rlWrapPtr(mod, scope, rl, try addZIRInst(mod, scope, src, zir.Inst.ElemPtr, .{ .array_ptr = array_ptr, .index = index }, .{}));
+    if (rl == .ref) {
+        return addZirInstTag(mod, scope, src, .elem_ptr, .{
+            .array = try expr(mod, scope, .ref, node.lhs),
+            .index = try expr(mod, scope, index_rl, node.index_expr),
+        });
+    }
+    return rlWrap(mod, scope, rl, try addZirInstTag(mod, scope, src, .elem_val, .{
+        .array = try expr(mod, scope, .none, node.lhs),
+        .index = try expr(mod, scope, index_rl, node.index_expr),
+    }));
 }
 
 fn sliceExpr(mod: *Module, scope: *Scope, node: *ast.Node.Slice) InnerError!*zir.Inst {
@@ -1819,12 +1854,8 @@ fn forExpr(
         break :blk index_ptr;
     };
     const array_ptr = try expr(mod, &for_scope.base, .ref, for_node.array_expr);
-    _ = try addZIRUnOp(mod, &for_scope.base, for_node.array_expr.firstToken(), .ensure_indexable, array_ptr);
     const cond_src = tree.token_locs[for_node.array_expr.firstToken()].start;
-    const len_ptr = try addZIRInst(mod, &for_scope.base, cond_src, zir.Inst.FieldPtr, .{
-        .object_ptr = array_ptr,
-        .field_name = try addZIRInst(mod, &for_scope.base, cond_src, zir.Inst.Str, .{ .bytes = "len" }, .{}),
-    }, .{});
+    const len = try addZIRUnOp(mod, &for_scope.base, cond_src, .indexable_ptr_len, array_ptr);
 
     var loop_scope: Scope.GenZIR = .{
         .parent = &for_scope.base,
@@ -1845,7 +1876,6 @@ fn forExpr(
 
     // check condition i < array_expr.len
     const index = try addZIRUnOp(mod, &cond_scope.base, cond_src, .deref, index_ptr);
-    const len = try addZIRUnOp(mod, &cond_scope.base, cond_src, .deref, len_ptr);
     const cond = try addZIRBinOp(mod, &cond_scope.base, cond_src, .cmp_lt, index, len);
 
     const condbr = try addZIRInstSpecial(mod, &cond_scope.base, for_src, zir.Inst.CondBr, .{
@@ -2328,8 +2358,9 @@ fn identifier(mod: *Module, scope: *Scope, rl: ResultLoc, ident: *ast.Node.OneTo
             .local_ptr => {
                 const local_ptr = s.cast(Scope.LocalPtr).?;
                 if (mem.eql(u8, local_ptr.name, ident_name)) {
-                    // TODO remove all callsites to rlWrapPtr
-                    return rlWrapPtr(mod, scope, rl, local_ptr.ptr);
+                    if (rl == .ref) return local_ptr.ptr;
+                    const loaded = try addZIRUnOp(mod, scope, src, .deref, local_ptr.ptr);
+                    return rlWrap(mod, scope, rl, loaded);
                 }
                 s = local_ptr.parent;
             },
@@ -2747,6 +2778,8 @@ fn builtinCall(mod: *Module, scope: *Scope, rl: ResultLoc, call: *ast.Node.Built
         return setEvalBranchQuota(mod, scope, call);
     } else if (mem.eql(u8, builtin_name, "@compileLog")) {
         return compileLog(mod, scope, call);
+    } else if (mem.eql(u8, builtin_name, "@field")) {
+        return namedField(mod, scope, rl, call);
     } else {
         return mod.failTok(scope, call.builtin_token, "invalid builtin function: '{s}'", .{builtin_name});
     }
@@ -3119,6 +3152,28 @@ fn rlWrapPtr(mod: *Module, scope: *Scope, rl: ResultLoc, ptr: *zir.Inst) InnerEr
     return rlWrap(mod, scope, rl, try addZIRUnOp(mod, scope, ptr.src, .deref, ptr));
 }
 
+pub fn addZirInstTag(
+    mod: *Module,
+    scope: *Scope,
+    src: usize,
+    comptime tag: zir.Inst.Tag,
+    positionals: std.meta.fieldInfo(tag.Type(), .positionals).field_type,
+) !*zir.Inst {
+    const gen_zir = scope.getGenZIR();
+    try gen_zir.instructions.ensureCapacity(mod.gpa, gen_zir.instructions.items.len + 1);
+    const inst = try gen_zir.arena.create(tag.Type());
+    inst.* = .{
+        .base = .{
+            .tag = tag,
+            .src = src,
+        },
+        .positionals = positionals,
+        .kw_args = .{},
+    };
+    gen_zir.instructions.appendAssumeCapacity(&inst.base);
+    return &inst.base;
+}
+
 pub fn addZIRInstSpecial(
     mod: *Module,
     scope: *Scope,
src/Module.zig
@@ -2357,6 +2357,11 @@ pub fn lookupDeclName(self: *Module, scope: *Scope, ident_name: []const u8) ?*De
     return self.decl_table.get(name_hash);
 }
 
+pub fn analyzeDeclVal(mod: *Module, scope: *Scope, src: usize, decl: *Decl) InnerError!*Inst {
+    const decl_ref = try mod.analyzeDeclRef(scope, src, decl);
+    return mod.analyzeDeref(scope, src, decl_ref, src);
+}
+
 pub fn analyzeDeclRef(self: *Module, scope: *Scope, src: usize, decl: *Decl) InnerError!*Inst {
     const scope_decl = scope.ownerDecl().?;
     try self.declareDeclDependency(scope_decl, decl);
@@ -2408,6 +2413,20 @@ fn analyzeVarRef(self: *Module, scope: *Scope, src: usize, tv: TypedValue) Inner
     return &inst.base;
 }
 
+pub fn analyzeRef(mod: *Module, scope: *Scope, src: usize, operand: *Inst) InnerError!*Inst {
+    const ptr_type = try mod.simplePtrType(scope, src, operand.ty, false, .One);
+
+    if (operand.value()) |val| {
+        return mod.constInst(scope, src, .{
+            .ty = ptr_type,
+            .val = try Value.Tag.ref_val.create(scope.arena(), val),
+        });
+    }
+
+    const b = try mod.requireRuntimeBlock(scope, src);
+    return mod.addUnOp(b, src, ptr_type, .ref, operand);
+}
+
 pub fn analyzeDeref(self: *Module, scope: *Scope, src: usize, ptr: *Inst, ptr_src: usize) InnerError!*Inst {
     const elem_ty = switch (ptr.ty.zigTypeTag()) {
         .Pointer => ptr.ty.elemType(),
@@ -3543,3 +3562,147 @@ pub fn emitBackwardBranch(mod: *Module, block: *Scope.Block, src: usize) !void {
         });
     }
 }
+
+pub fn namedFieldPtr(
+    mod: *Module,
+    scope: *Scope,
+    src: usize,
+    object_ptr: *Inst,
+    field_name: []const u8,
+    field_name_src: usize,
+) InnerError!*Inst {
+    const elem_ty = switch (object_ptr.ty.zigTypeTag()) {
+        .Pointer => object_ptr.ty.elemType(),
+        else => return mod.fail(scope, object_ptr.src, "expected pointer, found '{}'", .{object_ptr.ty}),
+    };
+    switch (elem_ty.zigTypeTag()) {
+        .Array => {
+            if (mem.eql(u8, field_name, "len")) {
+                return mod.constInst(scope, src, .{
+                    .ty = Type.initTag(.single_const_pointer_to_comptime_int),
+                    .val = try Value.Tag.ref_val.create(
+                        scope.arena(),
+                        try Value.Tag.int_u64.create(scope.arena(), elem_ty.arrayLen()),
+                    ),
+                });
+            } else {
+                return mod.fail(
+                    scope,
+                    field_name_src,
+                    "no member named '{s}' in '{}'",
+                    .{ field_name, elem_ty },
+                );
+            }
+        },
+        .Pointer => {
+            const ptr_child = elem_ty.elemType();
+            switch (ptr_child.zigTypeTag()) {
+                .Array => {
+                    if (mem.eql(u8, field_name, "len")) {
+                        return mod.constInst(scope, src, .{
+                            .ty = Type.initTag(.single_const_pointer_to_comptime_int),
+                            .val = try Value.Tag.ref_val.create(
+                                scope.arena(),
+                                try Value.Tag.int_u64.create(scope.arena(), ptr_child.arrayLen()),
+                            ),
+                        });
+                    } else {
+                        return mod.fail(
+                            scope,
+                            field_name_src,
+                            "no member named '{s}' in '{}'",
+                            .{ field_name, elem_ty },
+                        );
+                    }
+                },
+                else => {},
+            }
+        },
+        .Type => {
+            _ = try mod.resolveConstValue(scope, object_ptr);
+            const result = try mod.analyzeDeref(scope, src, object_ptr, object_ptr.src);
+            const val = result.value().?;
+            const child_type = try val.toType(scope.arena());
+            switch (child_type.zigTypeTag()) {
+                .ErrorSet => {
+                    // TODO resolve inferred error sets
+                    const entry = if (val.castTag(.error_set)) |payload|
+                        (payload.data.fields.getEntry(field_name) orelse
+                            return mod.fail(scope, src, "no error named '{s}' in '{}'", .{ field_name, child_type })).*
+                    else
+                        try mod.getErrorValue(field_name);
+
+                    const result_type = if (child_type.tag() == .anyerror)
+                        try Type.Tag.error_set_single.create(scope.arena(), entry.key)
+                    else
+                        child_type;
+
+                    return mod.constInst(scope, src, .{
+                        .ty = try mod.simplePtrType(scope, src, result_type, false, .One),
+                        .val = try Value.Tag.ref_val.create(
+                            scope.arena(),
+                            try Value.Tag.@"error".create(scope.arena(), .{
+                                .name = entry.key,
+                                .value = entry.value,
+                            }),
+                        ),
+                    });
+                },
+                .Struct => {
+                    const container_scope = child_type.getContainerScope();
+                    if (mod.lookupDeclName(&container_scope.base, field_name)) |decl| {
+                        // TODO if !decl.is_pub and inDifferentFiles() "{} is private"
+                        return mod.analyzeDeclRef(scope, src, decl);
+                    }
+
+                    if (container_scope.file_scope == mod.root_scope) {
+                        return mod.fail(scope, src, "root source file has no member called '{s}'", .{field_name});
+                    } else {
+                        return mod.fail(scope, src, "container '{}' has no member called '{s}'", .{ child_type, field_name });
+                    }
+                },
+                else => return mod.fail(scope, src, "type '{}' does not support field access", .{child_type}),
+            }
+        },
+        else => {},
+    }
+    return mod.fail(scope, src, "type '{}' does not support field access", .{elem_ty});
+}
+
+pub fn elemPtr(
+    mod: *Module,
+    scope: *Scope,
+    src: usize,
+    array_ptr: *Inst,
+    elem_index: *Inst,
+) InnerError!*Inst {
+    const elem_ty = switch (array_ptr.ty.zigTypeTag()) {
+        .Pointer => array_ptr.ty.elemType(),
+        else => return mod.fail(scope, array_ptr.src, "expected pointer, found '{}'", .{array_ptr.ty}),
+    };
+    if (!elem_ty.isIndexable()) {
+        return mod.fail(scope, src, "array access of non-array type '{}'", .{elem_ty});
+    }
+
+    if (elem_ty.isSinglePointer() and elem_ty.elemType().zigTypeTag() == .Array) {
+        // we have to deref the ptr operand to get the actual array pointer
+        const array_ptr_deref = try mod.analyzeDeref(scope, src, array_ptr, array_ptr.src);
+        if (array_ptr_deref.value()) |array_ptr_val| {
+            if (elem_index.value()) |index_val| {
+                // Both array pointer and index are compile-time known.
+                const index_u64 = index_val.toUnsignedInt();
+                // @intCast here because it would have been impossible to construct a value that
+                // required a larger index.
+                const elem_ptr = try array_ptr_val.elemPtr(scope.arena(), @intCast(usize, index_u64));
+                const pointee_type = elem_ty.elemType().elemType();
+
+                return mod.constInst(scope, src, .{
+                    .ty = try Type.Tag.single_const_pointer.create(scope.arena(), pointee_type),
+                    .val = elem_ptr,
+                });
+            }
+        }
+    }
+
+    return mod.fail(scope, src, "TODO implement more analyze elemptr", .{});
+}
src/zir.zig
@@ -47,6 +47,10 @@ pub const Inst = struct {
         array_type,
         /// Create an array type with sentinel
         array_type_sentinel,
+        /// Given a pointer to an indexable object, returns the len property. This is
+        /// used by for loops. This instruction also emits a for-loop specific instruction
+        /// if the indexable object is not indexable.
+        indexable_ptr_len,
         /// Function parameter value. These must be first in a function's main block,
         /// in respective order with the parameters.
         arg,
@@ -142,13 +146,13 @@ pub const Inst = struct {
         div,
         /// Given a pointer to an array, slice, or pointer, returns a pointer to the element at
         /// the provided index.
-        elemptr,
+        elem_ptr,
+        /// Given an array, slice, or pointer, returns the element at the provided index.
+        elem_val,
         /// Emits a compile error if the operand is not `void`.
         ensure_result_used,
         /// Emits a compile error if an error is ignored.
         ensure_result_non_error,
-        /// Emits a compile error if operand cannot be indexed.
-        ensure_indexable,
         /// Create a `E!T` type.
         error_union_type,
         /// Create an error set.
@@ -156,8 +160,17 @@ pub const Inst = struct {
         /// Export the provided Decl as the provided name in the compilation's output object file.
         @"export",
         /// Given a pointer to a struct or object that contains virtual fields, returns a pointer
-        /// to the named field.
-        fieldptr,
+        /// to the named field. The field name is a []const u8. Used by a.b syntax.
+        field_ptr,
+        /// Given a struct or object that contains virtual fields, returns the named field.
+        /// The field name is a []const u8. Used by a.b syntax.
+        field_val,
+        /// Given a pointer to a struct or object that contains virtual fields, returns a pointer
+        /// to the named field. The field name is a comptime instruction. Used by @field.
+        field_ptr_named,
+        /// Given a struct or object that contains virtual fields, returns the named field.
+        /// The field name is a comptime instruction. Used by @field.
+        field_val_named,
         /// Convert a larger float type to any other float type, possibly causing a loss of precision.
         floatcast,
         /// Declare a function body.
@@ -361,7 +374,6 @@ pub const Inst = struct {
                 .ptrtoint,
                 .ensure_result_used,
                 .ensure_result_non_error,
-                .ensure_indexable,
                 .bitcast_result_ptr,
                 .ref,
                 .bitcast_ref,
@@ -391,6 +403,7 @@ pub const Inst = struct {
                 .bitnot,
                 .import,
                 .set_eval_branch_quota,
+                .indexable_ptr_len,
                 => UnOp,
 
                 .add,
@@ -452,14 +465,15 @@ pub const Inst = struct {
                 .str => Str,
                 .int => Int,
                 .inttype => IntType,
-                .fieldptr => FieldPtr,
+                .field_ptr, .field_val => Field,
+                .field_ptr_named, .field_val_named => FieldNamed,
                 .@"asm" => Asm,
                 .@"fn" => Fn,
                 .@"export" => Export,
                 .param_type => ParamType,
                 .primitive => Primitive,
                 .fntype => FnType,
-                .elemptr => ElemPtr,
+                .elem_ptr, .elem_val => Elem,
                 .condbr => CondBr,
                 .ptr_type => PtrType,
                 .enum_literal => EnumLiteral,
@@ -490,6 +504,7 @@ pub const Inst = struct {
                 .array_mul,
                 .array_type,
                 .array_type_sentinel,
+                .indexable_ptr_len,
                 .arg,
                 .as,
                 .@"asm",
@@ -523,13 +538,16 @@ pub const Inst = struct {
                 .declval,
                 .deref,
                 .div,
-                .elemptr,
+                .elem_ptr,
+                .elem_val,
                 .ensure_result_used,
                 .ensure_result_non_error,
-                .ensure_indexable,
                 .@"export",
                 .floatcast,
-                .fieldptr,
+                .field_ptr,
+                .field_val,
+                .field_ptr_named,
+                .field_val_named,
                 .@"fn",
                 .fntype,
                 .int,
@@ -823,12 +841,21 @@ pub const Inst = struct {
         kw_args: struct {},
     };
 
-    pub const FieldPtr = struct {
-        pub const base_tag = Tag.fieldptr;
+    pub const Field = struct {
         base: Inst,
 
         positionals: struct {
-            object_ptr: *Inst,
+            object: *Inst,
+            field_name: []const u8,
+        },
+        kw_args: struct {},
+    };
+
+    pub const FieldNamed = struct {
+        base: Inst,
+
+        positionals: struct {
+            object: *Inst,
             field_name: *Inst,
         },
         kw_args: struct {},
@@ -1000,12 +1027,11 @@ pub const Inst = struct {
         };
     };
 
-    pub const ElemPtr = struct {
-        pub const base_tag = Tag.elemptr;
+    pub const Elem = struct {
         base: Inst,
 
         positionals: struct {
-            array_ptr: *Inst,
+            array: *Inst,
             index: *Inst,
         },
         kw_args: struct {},
src/zir_sema.zig
@@ -43,8 +43,8 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!
             .inferred_alloc_mut,
         ),
         .arg => return analyzeInstArg(mod, scope, old_inst.castTag(.arg).?),
-        .bitcast_ref => return analyzeInstBitCastRef(mod, scope, old_inst.castTag(.bitcast_ref).?),
-        .bitcast_result_ptr => return analyzeInstBitCastResultPtr(mod, scope, old_inst.castTag(.bitcast_result_ptr).?),
+        .bitcast_ref => return bitCastRef(mod, scope, old_inst.castTag(.bitcast_ref).?),
+        .bitcast_result_ptr => return bitCastResultPtr(mod, scope, old_inst.castTag(.bitcast_result_ptr).?),
         .block => return analyzeInstBlock(mod, scope, old_inst.castTag(.block).?, false),
         .block_comptime => return analyzeInstBlock(mod, scope, old_inst.castTag(.block_comptime).?, true),
         .block_flat => return analyzeInstBlockFlat(mod, scope, old_inst.castTag(.block_flat).?, false),
@@ -52,7 +52,7 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!
         .@"break" => return analyzeInstBreak(mod, scope, old_inst.castTag(.@"break").?),
         .breakpoint => return analyzeInstBreakpoint(mod, scope, old_inst.castTag(.breakpoint).?),
         .breakvoid => return analyzeInstBreakVoid(mod, scope, old_inst.castTag(.breakvoid).?),
-        .call => return analyzeInstCall(mod, scope, old_inst.castTag(.call).?),
+        .call => return call(mod, scope, old_inst.castTag(.call).?),
         .coerce_result_block_ptr => return analyzeInstCoerceResultBlockPtr(mod, scope, old_inst.castTag(.coerce_result_block_ptr).?),
         .coerce_result_ptr => return analyzeInstCoerceResultPtr(mod, scope, old_inst.castTag(.coerce_result_ptr).?),
         .coerce_to_ptr_elem => return analyzeInstCoerceToPtrElem(mod, scope, old_inst.castTag(.coerce_to_ptr_elem).?),
@@ -60,13 +60,13 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!
         .compilelog => return analyzeInstCompileLog(mod, scope, old_inst.castTag(.compilelog).?),
         .@"const" => return analyzeInstConst(mod, scope, old_inst.castTag(.@"const").?),
         .dbg_stmt => return analyzeInstDbgStmt(mod, scope, old_inst.castTag(.dbg_stmt).?),
-        .declref => return analyzeInstDeclRef(mod, scope, old_inst.castTag(.declref).?),
+        .declref => return declRef(mod, scope, old_inst.castTag(.declref).?),
         .declref_str => return analyzeInstDeclRefStr(mod, scope, old_inst.castTag(.declref_str).?),
-        .declval => return analyzeInstDeclVal(mod, scope, old_inst.castTag(.declval).?),
+        .declval => return declVal(mod, scope, old_inst.castTag(.declval).?),
         .ensure_result_used => return analyzeInstEnsureResultUsed(mod, scope, old_inst.castTag(.ensure_result_used).?),
         .ensure_result_non_error => return analyzeInstEnsureResultNonError(mod, scope, old_inst.castTag(.ensure_result_non_error).?),
-        .ensure_indexable => return analyzeInstEnsureIndexable(mod, scope, old_inst.castTag(.ensure_indexable).?),
-        .ref => return analyzeInstRef(mod, scope, old_inst.castTag(.ref).?),
+        .indexable_ptr_len => return indexablePtrLen(mod, scope, old_inst.castTag(.indexable_ptr_len).?),
+        .ref => return ref(mod, scope, old_inst.castTag(.ref).?),
         .resolve_inferred_alloc => return analyzeInstResolveInferredAlloc(mod, scope, old_inst.castTag(.resolve_inferred_alloc).?),
         .ret_ptr => return analyzeInstRetPtr(mod, scope, old_inst.castTag(.ret_ptr).?),
         .ret_type => return analyzeInstRetType(mod, scope, old_inst.castTag(.ret_type).?),
@@ -88,7 +88,10 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!
         .loop => return analyzeInstLoop(mod, scope, old_inst.castTag(.loop).?),
         .param_type => return analyzeInstParamType(mod, scope, old_inst.castTag(.param_type).?),
         .ptrtoint => return analyzeInstPtrToInt(mod, scope, old_inst.castTag(.ptrtoint).?),
-        .fieldptr => return analyzeInstFieldPtr(mod, scope, old_inst.castTag(.fieldptr).?),
+        .field_ptr => return fieldPtr(mod, scope, old_inst.castTag(.field_ptr).?),
+        .field_val => return fieldVal(mod, scope, old_inst.castTag(.field_val).?),
+        .field_ptr_named => return fieldPtrNamed(mod, scope, old_inst.castTag(.field_ptr_named).?),
+        .field_val_named => return fieldValNamed(mod, scope, old_inst.castTag(.field_val_named).?),
         .deref => return analyzeInstDeref(mod, scope, old_inst.castTag(.deref).?),
         .as => return analyzeInstAs(mod, scope, old_inst.castTag(.as).?),
         .@"asm" => return analyzeInstAsm(mod, scope, old_inst.castTag(.@"asm").?),
@@ -103,7 +106,8 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!
         .intcast => return analyzeInstIntCast(mod, scope, old_inst.castTag(.intcast).?),
         .bitcast => return analyzeInstBitCast(mod, scope, old_inst.castTag(.bitcast).?),
         .floatcast => return analyzeInstFloatCast(mod, scope, old_inst.castTag(.floatcast).?),
-        .elemptr => return analyzeInstElemPtr(mod, scope, old_inst.castTag(.elemptr).?),
+        .elem_ptr => return elemPtr(mod, scope, old_inst.castTag(.elem_ptr).?),
+        .elem_val => return elemVal(mod, scope, old_inst.castTag(.elem_val).?),
         .add => return analyzeInstArithmetic(mod, scope, old_inst.castTag(.add).?),
         .addwrap => return analyzeInstArithmetic(mod, scope, old_inst.castTag(.addwrap).?),
         .sub => return analyzeInstArithmetic(mod, scope, old_inst.castTag(.sub).?),
@@ -281,16 +285,16 @@ fn analyzeInstCoerceResultBlockPtr(
     return mod.fail(scope, inst.base.src, "TODO implement analyzeInstCoerceResultBlockPtr", .{});
 }
 
-fn analyzeInstBitCastRef(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
+fn bitCastRef(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
     const tracy = trace(@src());
     defer tracy.end();
-    return mod.fail(scope, inst.base.src, "TODO implement analyzeInstBitCastRef", .{});
+    return mod.fail(scope, inst.base.src, "TODO implement zir_sema.bitCastRef", .{});
 }
 
-fn analyzeInstBitCastResultPtr(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
+fn bitCastResultPtr(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
     const tracy = trace(@src());
     defer tracy.end();
-    return mod.fail(scope, inst.base.src, "TODO implement analyzeInstBitCastResultPtr", .{});
+    return mod.fail(scope, inst.base.src, "TODO implement zir_sema.bitCastResultPtr", .{});
 }
 
 fn analyzeInstCoerceResultPtr(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst {
@@ -318,21 +322,12 @@ fn analyzeInstRetPtr(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerErr
     return mod.addNoOp(b, inst.base.src, ptr_type, .alloc);
 }
 
-fn analyzeInstRef(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
+fn ref(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
     const tracy = trace(@src());
     defer tracy.end();
-    const operand = try resolveInst(mod, scope, inst.positionals.operand);
-    const ptr_type = try mod.simplePtrType(scope, inst.base.src, operand.ty, false, .One);
-
-    if (operand.value()) |val| {
-        return mod.constInst(scope, inst.base.src, .{
-            .ty = ptr_type,
-            .val = try Value.Tag.ref_val.create(scope.arena(), val),
-        });
-    }
 
-    const b = try mod.requireRuntimeBlock(scope, inst.base.src);
-    return mod.addUnOp(b, inst.base.src, ptr_type, .ref, operand);
+    const operand = try resolveInst(mod, scope, inst.positionals.operand);
+    return mod.analyzeRef(scope, inst.base.src, operand);
 }
 
 fn analyzeInstRetType(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst {
@@ -364,19 +359,34 @@ fn analyzeInstEnsureResultNonError(mod: *Module, scope: *Scope, inst: *zir.Inst.
     }
 }
 
-fn analyzeInstEnsureIndexable(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
+fn indexablePtrLen(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
     const tracy = trace(@src());
     defer tracy.end();
-    const operand = try resolveInst(mod, scope, inst.positionals.operand);
-    const elem_ty = operand.ty.elemType();
-    if (elem_ty.isIndexable()) {
-        return mod.constVoid(scope, operand.src);
-    } else {
-        // TODO error notes
-        // error: type '{}' does not support indexing
-        // note: for loop operand must be an array, a slice or a tuple
-        return mod.fail(scope, operand.src, "for loop operand must be an array, a slice or a tuple", .{});
+
+    const array_ptr = try resolveInst(mod, scope, inst.positionals.operand);
+    const elem_ty = array_ptr.ty.elemType();
+    if (!elem_ty.isIndexable()) {
+        const msg = msg: {
+            const msg = try mod.errMsg(
+                scope,
+                inst.base.src,
+                "type '{}' does not support indexing",
+                .{elem_ty},
+            );
+            errdefer msg.destroy(mod.gpa);
+            try mod.errNote(
+                scope,
+                inst.base.src,
+                msg,
+                "for loop operand must be an array, slice, tuple, or vector",
+                .{},
+            );
+            break :msg msg;
+        };
+        return mod.failWithOwnedErrorMsg(scope, msg);
     }
+    const result_ptr = try mod.namedFieldPtr(scope, inst.base.src, array_ptr, "len", inst.base.src);
+    return mod.analyzeDeref(scope, inst.base.src, result_ptr, result_ptr.src);
 }
 
 fn analyzeInstAlloc(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst {
@@ -826,21 +836,19 @@ fn analyzeInstDeclRefStr(mod: *Module, scope: *Scope, inst: *zir.Inst.DeclRefStr
     return mod.analyzeDeclRefByName(scope, inst.base.src, decl_name);
 }
 
-fn analyzeInstDeclRef(mod: *Module, scope: *Scope, inst: *zir.Inst.DeclRef) InnerError!*Inst {
+fn declRef(mod: *Module, scope: *Scope, inst: *zir.Inst.DeclRef) InnerError!*Inst {
     const tracy = trace(@src());
     defer tracy.end();
     return mod.analyzeDeclRef(scope, inst.base.src, inst.positionals.decl);
 }
 
-fn analyzeInstDeclVal(mod: *Module, scope: *Scope, inst: *zir.Inst.DeclVal) InnerError!*Inst {
+fn declVal(mod: *Module, scope: *Scope, inst: *zir.Inst.DeclVal) InnerError!*Inst {
     const tracy = trace(@src());
     defer tracy.end();
-    const decl_ref = try mod.analyzeDeclRef(scope, inst.base.src, inst.positionals.decl);
-    // TODO look into avoiding the call to analyzeDeref here
-    return mod.analyzeDeref(scope, inst.base.src, decl_ref, inst.base.src);
+    return mod.analyzeDeclVal(scope, inst.base.src, inst.positionals.decl);
 }
 
-fn analyzeInstCall(mod: *Module, scope: *Scope, inst: *zir.Inst.Call) InnerError!*Inst {
+fn call(mod: *Module, scope: *Scope, inst: *zir.Inst.Call) InnerError!*Inst {
     const tracy = trace(@src());
     defer tracy.end();
 
@@ -1093,7 +1101,7 @@ fn analyzeInstErrorSet(mod: *Module, scope: *Scope, inst: *zir.Inst.ErrorSet) In
         .val = Value.initPayload(&payload.base),
     });
     payload.data.decl = new_decl;
-    return mod.analyzeDeclRef(scope, inst.base.src, new_decl);
+    return mod.analyzeDeclVal(scope, inst.base.src, new_decl);
 }
 
 fn analyzeInstMergeErrorSets(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst {
@@ -1293,108 +1301,46 @@ fn analyzeInstPtrToInt(mod: *Module, scope: *Scope, ptrtoint: *zir.Inst.UnOp) In
     return mod.addUnOp(b, ptrtoint.base.src, ty, .ptrtoint, ptr);
 }
 
-fn analyzeInstFieldPtr(mod: *Module, scope: *Scope, fieldptr: *zir.Inst.FieldPtr) InnerError!*Inst {
+fn fieldVal(mod: *Module, scope: *Scope, inst: *zir.Inst.Field) InnerError!*Inst {
     const tracy = trace(@src());
     defer tracy.end();
-    const object_ptr = try resolveInst(mod, scope, fieldptr.positionals.object_ptr);
-    const field_name = try resolveConstString(mod, scope, fieldptr.positionals.field_name);
 
-    const elem_ty = switch (object_ptr.ty.zigTypeTag()) {
-        .Pointer => object_ptr.ty.elemType(),
-        else => return mod.fail(scope, fieldptr.positionals.object_ptr.src, "expected pointer, found '{}'", .{object_ptr.ty}),
-    };
-    switch (elem_ty.zigTypeTag()) {
-        .Array => {
-            if (mem.eql(u8, field_name, "len")) {
-                return mod.constInst(scope, fieldptr.base.src, .{
-                    .ty = Type.initTag(.single_const_pointer_to_comptime_int),
-                    .val = try Value.Tag.ref_val.create(
-                        scope.arena(),
-                        try Value.Tag.int_u64.create(scope.arena(), elem_ty.arrayLen()),
-                    ),
-                });
-            } else {
-                return mod.fail(
-                    scope,
-                    fieldptr.positionals.field_name.src,
-                    "no member named '{s}' in '{}'",
-                    .{ field_name, elem_ty },
-                );
-            }
-        },
-        .Pointer => {
-            const ptr_child = elem_ty.elemType();
-            switch (ptr_child.zigTypeTag()) {
-                .Array => {
-                    if (mem.eql(u8, field_name, "len")) {
-                        return mod.constInst(scope, fieldptr.base.src, .{
-                            .ty = Type.initTag(.single_const_pointer_to_comptime_int),
-                            .val = try Value.Tag.ref_val.create(
-                                scope.arena(),
-                                try Value.Tag.int_u64.create(scope.arena(), ptr_child.arrayLen()),
-                            ),
-                        });
-                    } else {
-                        return mod.fail(
-                            scope,
-                            fieldptr.positionals.field_name.src,
-                            "no member named '{s}' in '{}'",
-                            .{ field_name, elem_ty },
-                        );
-                    }
-                },
-                else => {},
-            }
-        },
-        .Type => {
-            _ = try mod.resolveConstValue(scope, object_ptr);
-            const result = try mod.analyzeDeref(scope, fieldptr.base.src, object_ptr, object_ptr.src);
-            const val = result.value().?;
-            const child_type = try val.toType(scope.arena());
-            switch (child_type.zigTypeTag()) {
-                .ErrorSet => {
-                    // TODO resolve inferred error sets
-                    const entry = if (val.castTag(.error_set)) |payload|
-                        (payload.data.fields.getEntry(field_name) orelse
-                            return mod.fail(scope, fieldptr.base.src, "no error named '{s}' in '{}'", .{ field_name, child_type })).*
-                    else
-                        try mod.getErrorValue(field_name);
-
-                    const result_type = if (child_type.tag() == .anyerror)
-                        try Type.Tag.error_set_single.create(scope.arena(), entry.key)
-                    else
-                        child_type;
-
-                    return mod.constInst(scope, fieldptr.base.src, .{
-                        .ty = try mod.simplePtrType(scope, fieldptr.base.src, result_type, false, .One),
-                        .val = try Value.Tag.ref_val.create(
-                            scope.arena(),
-                            try Value.Tag.@"error".create(scope.arena(), .{
-                                .name = entry.key,
-                                .value = entry.value,
-                            }),
-                        ),
-                    });
-                },
-                .Struct => {
-                    const container_scope = child_type.getContainerScope();
-                    if (mod.lookupDeclName(&container_scope.base, field_name)) |decl| {
-                        // TODO if !decl.is_pub and inDifferentFiles() "{} is private"
-                        return mod.analyzeDeclRef(scope, fieldptr.base.src, decl);
-                    }
+    const object = try resolveInst(mod, scope, inst.positionals.object);
+    const field_name = inst.positionals.field_name;
+    const object_ptr = try mod.analyzeRef(scope, inst.base.src, object);
+    const result_ptr = try mod.namedFieldPtr(scope, inst.base.src, object_ptr, field_name, inst.base.src);
+    return mod.analyzeDeref(scope, inst.base.src, result_ptr, result_ptr.src);
+}
 
-                    if (container_scope.file_scope == mod.root_scope) {
-                        return mod.fail(scope, fieldptr.base.src, "root source file has no member called '{s}'", .{field_name});
-                    } else {
-                        return mod.fail(scope, fieldptr.base.src, "container '{}' has no member called '{s}'", .{ child_type, field_name });
-                    }
-                },
-                else => return mod.fail(scope, fieldptr.base.src, "type '{}' does not support field access", .{child_type}),
-            }
-        },
-        else => {},
-    }
-    return mod.fail(scope, fieldptr.base.src, "type '{}' does not support field access", .{elem_ty});
+fn fieldPtr(mod: *Module, scope: *Scope, inst: *zir.Inst.Field) InnerError!*Inst {
+    const tracy = trace(@src());
+    defer tracy.end();
+
+    const object_ptr = try resolveInst(mod, scope, inst.positionals.object);
+    const field_name = inst.positionals.field_name;
+    return mod.namedFieldPtr(scope, inst.base.src, object_ptr, field_name, inst.base.src);
+}
+
+fn fieldValNamed(mod: *Module, scope: *Scope, inst: *zir.Inst.FieldNamed) InnerError!*Inst {
+    const tracy = trace(@src());
+    defer tracy.end();
+
+    const object = try resolveInst(mod, scope, inst.positionals.object);
+    const field_name = try resolveConstString(mod, scope, inst.positionals.field_name);
+    const fsrc = inst.positionals.field_name.src;
+    const object_ptr = try mod.analyzeRef(scope, inst.base.src, object);
+    const result_ptr = try mod.namedFieldPtr(scope, inst.base.src, object_ptr, field_name, fsrc);
+    return mod.analyzeDeref(scope, inst.base.src, result_ptr, result_ptr.src);
+}
+
+fn fieldPtrNamed(mod: *Module, scope: *Scope, inst: *zir.Inst.FieldNamed) InnerError!*Inst {
+    const tracy = trace(@src());
+    defer tracy.end();
+
+    const object_ptr = try resolveInst(mod, scope, inst.positionals.object);
+    const field_name = try resolveConstString(mod, scope, inst.positionals.field_name);
+    const fsrc = inst.positionals.field_name.src;
+    return mod.namedFieldPtr(scope, inst.base.src, object_ptr, field_name, fsrc);
 }
 
 fn analyzeInstIntCast(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst {
@@ -1481,42 +1427,24 @@ fn analyzeInstFloatCast(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) Inne
     return mod.fail(scope, inst.base.src, "TODO implement analyze widen or shorten float", .{});
 }
 
-fn analyzeInstElemPtr(mod: *Module, scope: *Scope, inst: *zir.Inst.ElemPtr) InnerError!*Inst {
+fn elemVal(mod: *Module, scope: *Scope, inst: *zir.Inst.Elem) InnerError!*Inst {
     const tracy = trace(@src());
     defer tracy.end();
-    const array_ptr = try resolveInst(mod, scope, inst.positionals.array_ptr);
-    const uncasted_index = try resolveInst(mod, scope, inst.positionals.index);
-    const elem_index = try mod.coerce(scope, Type.initTag(.usize), uncasted_index);
 
-    const elem_ty = switch (array_ptr.ty.zigTypeTag()) {
-        .Pointer => array_ptr.ty.elemType(),
-        else => return mod.fail(scope, inst.positionals.array_ptr.src, "expected pointer, found '{}'", .{array_ptr.ty}),
-    };
-    if (!elem_ty.isIndexable()) {
-        return mod.fail(scope, inst.base.src, "array access of non-array type '{}'", .{elem_ty});
-    }
-
-    if (elem_ty.isSinglePointer() and elem_ty.elemType().zigTypeTag() == .Array) {
-        // we have to deref the ptr operand to get the actual array pointer
-        const array_ptr_deref = try mod.analyzeDeref(scope, inst.base.src, array_ptr, inst.positionals.array_ptr.src);
-        if (array_ptr_deref.value()) |array_ptr_val| {
-            if (elem_index.value()) |index_val| {
-                // Both array pointer and index are compile-time known.
-                const index_u64 = index_val.toUnsignedInt();
-                // @intCast here because it would have been impossible to construct a value that
-                // required a larger index.
-                const elem_ptr = try array_ptr_val.elemPtr(scope.arena(), @intCast(usize, index_u64));
-                const pointee_type = elem_ty.elemType().elemType();
+    const array = try resolveInst(mod, scope, inst.positionals.array);
+    const array_ptr = try mod.analyzeRef(scope, inst.base.src, array);
+    const elem_index = try resolveInst(mod, scope, inst.positionals.index);
+    const result_ptr = try mod.elemPtr(scope, inst.base.src, array_ptr, elem_index);
+    return mod.analyzeDeref(scope, inst.base.src, result_ptr, result_ptr.src);
+}
 
-                return mod.constInst(scope, inst.base.src, .{
-                    .ty = try Type.Tag.single_const_pointer.create(scope.arena(), pointee_type),
-                    .val = elem_ptr,
-                });
-            }
-        }
-    }
+fn elemPtr(mod: *Module, scope: *Scope, inst: *zir.Inst.Elem) InnerError!*Inst {
+    const tracy = trace(@src());
+    defer tracy.end();
 
-    return mod.fail(scope, inst.base.src, "TODO implement more analyze elemptr", .{});
+    const array_ptr = try resolveInst(mod, scope, inst.positionals.array);
+    const elem_index = try resolveInst(mod, scope, inst.positionals.index);
+    return mod.elemPtr(scope, inst.base.src, array_ptr, elem_index);
 }
 
 fn analyzeInstSlice(mod: *Module, scope: *Scope, inst: *zir.Inst.Slice) InnerError!*Inst {
test/stage2/aarch64.zig
@@ -80,7 +80,7 @@ pub fn addCases(ctx: *TestContext) !void {
     }
 
     {
-        var case = ctx.exe("hello world", linux_aarch64);
+        var case = ctx.exe("linux_aarch64 hello world", linux_aarch64);
         // Regular old hello world
         case.addCompareOutput(
             \\export fn _start() noreturn {
test/stage2/arm.zig
@@ -8,7 +8,7 @@ const linux_arm = std.zig.CrossTarget{
 
 pub fn addCases(ctx: *TestContext) !void {
     {
-        var case = ctx.exe("hello world", linux_arm);
+        var case = ctx.exe("linux_arm hello world", linux_arm);
         // Regular old hello world
         case.addCompareOutput(
             \\export fn _start() noreturn {
test/stage2/llvm.zig
@@ -29,7 +29,7 @@ pub fn addCases(ctx: *TestContext) !void {
     }
 
     {
-        var case = ctx.exeUsingLlvmBackend("hello world", linux_x64);
+        var case = ctx.exeUsingLlvmBackend("llvm hello world", linux_x64);
 
         case.addCompareOutput(
             \\extern fn puts(s: [*:0]const u8) c_int;
test/stage2/test.zig
@@ -231,7 +231,7 @@ pub fn addCases(ctx: *TestContext) !void {
     }
 
     {
-        var case = ctx.exe("hello world", linux_riscv64);
+        var case = ctx.exe("riscv64 hello world", linux_riscv64);
         // Regular old hello world
         case.addCompareOutput(
             \\export fn _start() noreturn {