Commit 545064c1d9

Andrew Kelley <andrew@ziglang.org>
2019-01-31 05:36:52
introduce vector type for SIMD
See #903 * create with `@Vector(len, ElemType)` * only wrapping addition is implemented This feature is far from complete; this is only the beginning.
1 parent 169a789
doc/langref.html.in
@@ -1531,6 +1531,29 @@ test "array initialization with function calls" {
       {#code_end#}
       {#see_also|for|Slices#}
       {#header_close#}
+
+      {#header_open|Vectors#}
+      <p>
+      A vector is a group of {#link|Integers#}, {#link|Floats#}, or {#link|Pointers#} which are operated on
+      in parallel using a single instruction ({#link|SIMD#}). Vector types are created with the builtin
+      function {#link|@Vector#}.
+      </p>
+      <p>
+      TODO talk about C ABI interop
+      </p>
+      {#header_open|SIMD#}
+      <p>
+      TODO Zig's SIMD abilities are just beginning to be fleshed out. Here are some talking points to update the
+      docs with:
+      * What kind of operations can you do? All the operations on integers and floats? What about mixing scalar and vector?
+      * How to convert to/from vectors/arrays
+      * How to access individual elements from vectors, how to loop over the elements
+      * "shuffle"
+      * Advice on writing high perf software, how to abstract the best way
+      </p>
+      {#header_close#}
+      {#header_close#}
+
       {#header_open|Pointers#}
       <p>
       Zig has two kinds of pointers:
@@ -6607,6 +6630,17 @@ pub const TypeInfo = union(TypeId) {
       expression passed as an argument. The expression is evaluated.
       </p>
 
+      {#header_close#}
+
+      {#header_open|@Vector#}
+      <pre>{#syntax#}@Vector(comptime len: u32, comptime ElemType: type) type{#endsyntax#}</pre>
+      <p>
+      This function returns a vector type for {#link|SIMD#}.
+      </p>
+      <p>
+      {#syntax#}ElemType{#endsyntax#} must be an {#link|integer|Integers#}, a {#link|float|Floats#}, or a
+      {#link|pointer|Pointers#}.
+      </p>
       {#header_close#}
       {#header_close#}
 
src/all_types.hpp
@@ -252,6 +252,10 @@ struct ConstArgTuple {
     size_t end_index;
 };
 
+struct ConstVector {
+    ConstExprValue *elements;
+};
+
 enum ConstValSpecial {
     ConstValSpecialRuntime,
     ConstValSpecialStatic,
@@ -318,6 +322,7 @@ struct ConstExprValue {
         ConstPtrValue x_ptr;
         ImportTableEntry *x_import;
         ConstArgTuple x_arg_tuple;
+        ConstVector x_vector;
 
         // populated if special == ConstValSpecialRuntime
         RuntimeHintErrorUnion rh_error_union;
@@ -1210,6 +1215,12 @@ struct ZigTypePromise {
     ZigType *result_type;
 };
 
+struct ZigTypeVector {
+    // The type must be a pointer, integer, or float
+    ZigType *elem_type;
+    uint32_t len;
+};
+
 enum ZigTypeId {
     ZigTypeIdInvalid,
     ZigTypeIdMetaType,
@@ -1236,6 +1247,7 @@ enum ZigTypeId {
     ZigTypeIdArgTuple,
     ZigTypeIdOpaque,
     ZigTypeIdPromise,
+    ZigTypeIdVector,
 };
 
 struct ZigType {
@@ -1262,6 +1274,7 @@ struct ZigType {
         ZigTypeFn fn;
         ZigTypeBoundFn bound_fn;
         ZigTypePromise promise;
+        ZigTypeVector vector;
     } data;
 
     // use these fields to make sure we don't duplicate type table entries for the same type
@@ -1415,6 +1428,7 @@ enum BuiltinFnId {
     BuiltinFnIdEnumToInt,
     BuiltinFnIdIntToEnum,
     BuiltinFnIdIntType,
+    BuiltinFnIdVectorType,
     BuiltinFnIdSetCold,
     BuiltinFnIdSetRuntimeSafety,
     BuiltinFnIdSetFloatMode,
@@ -1505,6 +1519,10 @@ struct TypeId {
             ZigType *err_set_type;
             ZigType *payload_type;
         } error_union;
+        struct {
+            ZigType *elem_type;
+            uint32_t len;
+        } vector;
     } data;
 };
 
@@ -2139,6 +2157,7 @@ enum IrInstructionId {
     IrInstructionIdFloatToInt,
     IrInstructionIdBoolToInt,
     IrInstructionIdIntType,
+    IrInstructionIdVectorType,
     IrInstructionIdBoolNot,
     IrInstructionIdMemset,
     IrInstructionIdMemcpy,
@@ -2807,6 +2826,13 @@ struct IrInstructionIntType {
     IrInstruction *bit_count;
 };
 
+struct IrInstructionVectorType {
+    IrInstruction base;
+
+    IrInstruction *len;
+    IrInstruction *elem_type;
+};
+
 struct IrInstructionBoolNot {
     IrInstruction base;
 
src/analyze.cpp
@@ -250,6 +250,7 @@ AstNode *type_decl_node(ZigType *type_entry) {
         case ZigTypeIdBoundFn:
         case ZigTypeIdArgTuple:
         case ZigTypeIdPromise:
+        case ZigTypeIdVector:
             return nullptr;
     }
     zig_unreachable();
@@ -311,6 +312,7 @@ bool type_is_resolved(ZigType *type_entry, ResolveStatus status) {
         case ZigTypeIdBoundFn:
         case ZigTypeIdArgTuple:
         case ZigTypeIdPromise:
+        case ZigTypeIdVector:
             return true;
     }
     zig_unreachable();
@@ -1055,11 +1057,7 @@ bool want_first_arg_sret(CodeGen *g, FnTypeId *fn_type_id) {
     }
     if (g->zig_target.arch.arch == ZigLLVM_x86_64) {
         X64CABIClass abi_class = type_c_abi_x86_64_class(g, fn_type_id->return_type);
-        if (abi_class == X64CABIClass_MEMORY) {
-            return true;
-        }
-        zig_panic("TODO implement C ABI for x86_64 return types. type '%s'\nSee https://github.com/ziglang/zig/issues/1481",
-                buf_ptr(&fn_type_id->return_type->name));
+        return abi_class == X64CABIClass_MEMORY;
     } else if (target_is_arm(&g->zig_target)) {
         return type_size(g, fn_type_id->return_type) > 16;
     }
@@ -1424,6 +1422,7 @@ static bool type_allowed_in_packed_struct(ZigType *type_entry) {
         case ZigTypeIdPointer:
         case ZigTypeIdArray:
         case ZigTypeIdFn:
+        case ZigTypeIdVector:
             return true;
         case ZigTypeIdStruct:
             return type_entry->data.structure.layout == ContainerLayoutPacked;
@@ -1472,6 +1471,8 @@ static bool type_allowed_in_extern(CodeGen *g, ZigType *type_entry) {
                 default:
                     return false;
             }
+        case ZigTypeIdVector:
+            return type_allowed_in_extern(g, type_entry->data.vector.elem_type);
         case ZigTypeIdFloat:
             return true;
         case ZigTypeIdArray:
@@ -1625,6 +1626,7 @@ static ZigType *analyze_fn_type(CodeGen *g, AstNode *proto_node, Scope *child_sc
             case ZigTypeIdUnion:
             case ZigTypeIdFn:
             case ZigTypeIdPromise:
+            case ZigTypeIdVector:
                 switch (type_requires_comptime(g, type_entry)) {
                     case ReqCompTimeNo:
                         break;
@@ -1720,6 +1722,7 @@ static ZigType *analyze_fn_type(CodeGen *g, AstNode *proto_node, Scope *child_sc
         case ZigTypeIdUnion:
         case ZigTypeIdFn:
         case ZigTypeIdPromise:
+        case ZigTypeIdVector:
             switch (type_requires_comptime(g, fn_type_id.return_type)) {
                 case ReqCompTimeInvalid:
                     return g->builtin_types.entry_invalid;
@@ -3577,6 +3580,7 @@ ZigType *validate_var_type(CodeGen *g, AstNode *source_node, ZigType *type_entry
         case ZigTypeIdFn:
         case ZigTypeIdBoundFn:
         case ZigTypeIdPromise:
+        case ZigTypeIdVector:
             return type_entry;
     }
     zig_unreachable();
@@ -3943,6 +3947,7 @@ static bool is_container(ZigType *type_entry) {
         case ZigTypeIdArgTuple:
         case ZigTypeIdOpaque:
         case ZigTypeIdPromise:
+        case ZigTypeIdVector:
             return false;
     }
     zig_unreachable();
@@ -4002,6 +4007,7 @@ void resolve_container_type(CodeGen *g, ZigType *type_entry) {
         case ZigTypeIdArgTuple:
         case ZigTypeIdOpaque:
         case ZigTypeIdPromise:
+        case ZigTypeIdVector:
             zig_unreachable();
     }
 }
@@ -4451,6 +4457,34 @@ ZigType *get_int_type(CodeGen *g, bool is_signed, uint32_t size_in_bits) {
     return new_entry;
 }
 
+ZigType *get_vector_type(CodeGen *g, uint32_t len, ZigType *elem_type) {
+    TypeId type_id = {};
+    type_id.id = ZigTypeIdVector;
+    type_id.data.vector.len = len;
+    type_id.data.vector.elem_type = elem_type;
+
+    {
+        auto entry = g->type_table.maybe_get(type_id);
+        if (entry)
+            return entry->value;
+    }
+
+    ZigType *entry = new_type_table_entry(ZigTypeIdVector);
+    entry->zero_bits = (len == 0) || !type_has_bits(elem_type);
+    entry->type_ref = entry->zero_bits ? LLVMVoidType() : LLVMVectorType(elem_type->type_ref, len);
+    entry->data.vector.len = len;
+    entry->data.vector.elem_type = elem_type;
+
+    buf_resize(&entry->name, 0);
+    buf_appendf(&entry->name, "@Vector(%u, %s)", len, buf_ptr(&elem_type->name));
+
+    entry->di_type = ZigLLVMDIBuilderCreateVectorType(g->dbuilder, len,
+            LLVMABIAlignmentOfType(g->target_data_ref, entry->type_ref), elem_type->di_type);
+
+    g->type_table.put(type_id, entry);
+    return entry;
+}
+
 ZigType **get_c_int_type_ptr(CodeGen *g, CIntType c_int_type) {
     return &g->builtin_types.entry_c_int[c_int_type];
 }
@@ -4482,6 +4516,7 @@ bool handle_is_ptr(ZigType *type_entry) {
         case ZigTypeIdFn:
         case ZigTypeIdEnum:
         case ZigTypeIdPromise:
+        case ZigTypeIdVector:
              return false;
         case ZigTypeIdArray:
         case ZigTypeIdStruct:
@@ -4914,6 +4949,9 @@ static uint32_t hash_const_val(ConstExprValue *const_val) {
             return hash_const_val_error_set(const_val);
         case ZigTypeIdNamespace:
             return hash_ptr(const_val->data.x_import);
+        case ZigTypeIdVector:
+            // TODO better hashing algorithm
+            return 3647867726;
         case ZigTypeIdBoundFn:
         case ZigTypeIdInvalid:
         case ZigTypeIdUnreachable:
@@ -4966,6 +5004,7 @@ static bool can_mutate_comptime_var_state(ConstExprValue *value) {
         case ZigTypeIdBool:
         case ZigTypeIdUnreachable:
         case ZigTypeIdInt:
+        case ZigTypeIdVector:
         case ZigTypeIdFloat:
         case ZigTypeIdComptimeFloat:
         case ZigTypeIdComptimeInt:
@@ -5049,6 +5088,7 @@ static bool return_type_is_cacheable(ZigType *return_type) {
         case ZigTypeIdErrorSet:
         case ZigTypeIdEnum:
         case ZigTypeIdPointer:
+        case ZigTypeIdVector:
             return true;
 
         case ZigTypeIdArray:
@@ -5201,6 +5241,7 @@ OnePossibleValue type_has_one_possible_value(CodeGen *g, ZigType *type_entry) {
         case ZigTypeIdErrorSet:
         case ZigTypeIdEnum:
         case ZigTypeIdInt:
+        case ZigTypeIdVector:
             return type_has_bits(type_entry) ? OnePossibleValueNo : OnePossibleValueYes;
         case ZigTypeIdPointer:
             return type_has_one_possible_value(g, type_entry->data.pointer.child_type);
@@ -5251,6 +5292,7 @@ ReqCompTime type_requires_comptime(CodeGen *g, ZigType *type_entry) {
         case ZigTypeIdErrorSet:
         case ZigTypeIdBool:
         case ZigTypeIdInt:
+        case ZigTypeIdVector:
         case ZigTypeIdFloat:
         case ZigTypeIdVoid:
         case ZigTypeIdUnreachable:
@@ -5777,7 +5819,7 @@ bool const_values_equal(CodeGen *g, ConstExprValue *a, ConstExprValue *b) {
             ConstExprValue *a_elems = a->data.x_array.data.s_none.elements;
             ConstExprValue *b_elems = b->data.x_array.data.s_none.elements;
 
-            for (size_t i = 0; i < len; ++i) {
+            for (size_t i = 0; i < len; i += 1) {
                 if (!const_values_equal(g, &a_elems[i], &b_elems[i]))
                     return false;
             }
@@ -5811,6 +5853,20 @@ bool const_values_equal(CodeGen *g, ConstExprValue *a, ConstExprValue *b) {
         case ZigTypeIdArgTuple:
             return a->data.x_arg_tuple.start_index == b->data.x_arg_tuple.start_index &&
                    a->data.x_arg_tuple.end_index == b->data.x_arg_tuple.end_index;
+        case ZigTypeIdVector: {
+            assert(a->type->data.vector.len == b->type->data.vector.len);
+
+            size_t len = a->type->data.vector.len;
+            ConstExprValue *a_elems = a->data.x_vector.elements;
+            ConstExprValue *b_elems = b->data.x_vector.elements;
+
+            for (size_t i = 0; i < len; i += 1) {
+                if (!const_values_equal(g, &a_elems[i], &b_elems[i]))
+                    return false;
+            }
+
+            return true;
+        }
         case ZigTypeIdBoundFn:
         case ZigTypeIdInvalid:
         case ZigTypeIdUnreachable:
@@ -6042,6 +6098,18 @@ void render_const_value(CodeGen *g, Buf *buf, ConstExprValue *const_val) {
                 }
             }
             zig_unreachable();
+        case ZigTypeIdVector: {
+            buf_appendf(buf, "%s{", buf_ptr(&type_entry->name));
+            uint64_t len = type_entry->data.vector.len;
+            for (uint32_t i = 0; i < len; i += 1) {
+                if (i != 0)
+                    buf_appendf(buf, ",");
+                ConstExprValue *child_value = &const_val->data.x_vector.elements[i];
+                render_const_value(g, buf, child_value);
+            }
+            buf_appendf(buf, "}");
+            return;
+        }
         case ZigTypeIdNull:
             {
                 buf_appendf(buf, "null");
@@ -6200,6 +6268,8 @@ uint32_t type_id_hash(TypeId x) {
         case ZigTypeIdInt:
             return (x.data.integer.is_signed ? (uint32_t)2652528194 : (uint32_t)163929201) +
                     (((uint32_t)x.data.integer.bit_count) ^ (uint32_t)2998081557);
+        case ZigTypeIdVector:
+            return hash_ptr(x.data.vector.elem_type) * (x.data.vector.len * 526582681);
     }
     zig_unreachable();
 }
@@ -6248,6 +6318,9 @@ bool type_id_eql(TypeId a, TypeId b) {
         case ZigTypeIdInt:
             return a.data.integer.is_signed == b.data.integer.is_signed &&
                 a.data.integer.bit_count == b.data.integer.bit_count;
+        case ZigTypeIdVector:
+            return a.data.vector.elem_type == b.data.vector.elem_type &&
+                a.data.vector.len == b.data.vector.len;
     }
     zig_unreachable();
 }
@@ -6382,6 +6455,7 @@ static const ZigTypeId all_type_ids[] = {
     ZigTypeIdArgTuple,
     ZigTypeIdOpaque,
     ZigTypeIdPromise,
+    ZigTypeIdVector,
 };
 
 ZigTypeId type_id_at_index(size_t index) {
@@ -6447,6 +6521,8 @@ size_t type_id_index(ZigType *entry) {
             return 22;
         case ZigTypeIdPromise:
             return 23;
+        case ZigTypeIdVector:
+            return 24;
     }
     zig_unreachable();
 }
@@ -6503,6 +6579,8 @@ const char *type_id_name(ZigTypeId id) {
             return "Opaque";
         case ZigTypeIdPromise:
             return "Promise";
+        case ZigTypeIdVector:
+            return "Vector";
     }
     zig_unreachable();
 }
@@ -6658,6 +6736,7 @@ X64CABIClass type_c_abi_x86_64_class(CodeGen *g, ZigType *ty) {
         case ZigTypeIdBool:
             return X64CABIClass_INTEGER;
         case ZigTypeIdFloat:
+        case ZigTypeIdVector:
             return X64CABIClass_SSE;
         case ZigTypeIdStruct: {
             // "If the size of an object is larger than four eightbytes, or it contains unaligned
src/analyze.hpp
@@ -20,6 +20,7 @@ ZigType *get_pointer_to_type_extra(CodeGen *g, ZigType *child_type, bool is_cons
 uint64_t type_size(CodeGen *g, ZigType *type_entry);
 uint64_t type_size_bits(CodeGen *g, ZigType *type_entry);
 ZigType *get_int_type(CodeGen *g, bool is_signed, uint32_t size_in_bits);
+ZigType *get_vector_type(CodeGen *g, uint32_t len, ZigType *elem_type);
 ZigType **get_c_int_type_ptr(CodeGen *g, CIntType c_int_type);
 ZigType *get_c_int_type(CodeGen *g, CIntType c_int_type);
 ZigType *get_fn_type(CodeGen *g, FnTypeId *fn_type_id);
src/codegen.cpp
@@ -1980,7 +1980,7 @@ static bool iter_function_params_c_abi(CodeGen *g, ZigType *fn_type, FnWalk *fn_
             break;
     }
 
-    if (type_is_c_abi_int(g, ty) || ty->id == ZigTypeIdFloat ||
+    if (type_is_c_abi_int(g, ty) || ty->id == ZigTypeIdFloat || ty->id == ZigTypeIdVector ||
         ty->id == ZigTypeIdInt // TODO investigate if we need to change this
     ) {
         switch (fn_walk->id) {
@@ -2660,6 +2660,27 @@ static LLVMValueRef ir_render_bin_op(CodeGen *g, IrExecutable *executable,
                 } else {
                     return LLVMBuildNUWAdd(g->builder, op1_value, op2_value, "");
                 }
+            } else if (type_entry->id == ZigTypeIdVector) {
+                ZigType *elem_type = type_entry->data.vector.elem_type;
+                if (elem_type->id == ZigTypeIdFloat) {
+                    ZigLLVMSetFastMath(g->builder, ir_want_fast_math(g, &bin_op_instruction->base));
+                    return LLVMBuildFAdd(g->builder, op1_value, op2_value, "");
+                } else if (elem_type->id == ZigTypeIdPointer) {
+                    zig_panic("TODO codegen for pointers in vectors");
+                } else if (elem_type->id == ZigTypeIdInt) {
+                    bool is_wrapping = (op_id == IrBinOpAddWrap);
+                    if (is_wrapping) {
+                        return LLVMBuildAdd(g->builder, op1_value, op2_value, "");
+                    } else if (want_runtime_safety) {
+                        zig_panic("TODO runtime safety for vector integer addition");
+                    } else if (elem_type->data.integral.is_signed) {
+                        return LLVMBuildNSWAdd(g->builder, op1_value, op2_value, "");
+                    } else {
+                        return LLVMBuildNUWAdd(g->builder, op1_value, op2_value, "");
+                    }
+                } else {
+                    zig_unreachable();
+                }
             } else {
                 zig_unreachable();
             }
@@ -5211,6 +5232,7 @@ static LLVMValueRef ir_render_instruction(CodeGen *g, IrExecutable *executable,
         case IrInstructionIdCUndef:
         case IrInstructionIdEmbedFile:
         case IrInstructionIdIntType:
+        case IrInstructionIdVectorType:
         case IrInstructionIdMemberCount:
         case IrInstructionIdMemberType:
         case IrInstructionIdMemberName:
@@ -5620,6 +5642,8 @@ static LLVMValueRef pack_const_int(CodeGen *g, LLVMTypeRef big_int_type_ref, Con
             }
         case ZigTypeIdArray:
             zig_panic("TODO bit pack an array");
+        case ZigTypeIdVector:
+            zig_panic("TODO bit pack a vector");
         case ZigTypeIdUnion:
             zig_panic("TODO bit pack a union");
         case ZigTypeIdStruct:
@@ -5992,6 +6016,14 @@ static LLVMValueRef gen_const_val(CodeGen *g, ConstExprValue *const_val, const c
                     }
                 }
             }
+        case ZigTypeIdVector: {
+            uint32_t len = type_entry->data.vector.len;
+            LLVMValueRef *values = allocate<LLVMValueRef>(len);
+            for (uint32_t i = 0; i < len; i += 1) {
+                values[i] = gen_const_val(g, &const_val->data.x_vector.elements[i], "");
+            }
+            return LLVMConstVector(values, len);
+        }
         case ZigTypeIdUnion:
             {
                 LLVMTypeRef union_type_ref = type_entry->data.unionation.union_type_ref;
@@ -6927,6 +6959,7 @@ static void define_builtin_fns(CodeGen *g) {
     create_builtin_fn(g, BuiltinFnIdCompileErr, "compileError", 1);
     create_builtin_fn(g, BuiltinFnIdCompileLog, "compileLog", SIZE_MAX);
     create_builtin_fn(g, BuiltinFnIdIntType, "IntType", 2); // TODO rename to Int
+    create_builtin_fn(g, BuiltinFnIdVectorType, "Vector", 2);
     create_builtin_fn(g, BuiltinFnIdSetCold, "setCold", 1);
     create_builtin_fn(g, BuiltinFnIdSetRuntimeSafety, "setRuntimeSafety", 1);
     create_builtin_fn(g, BuiltinFnIdSetFloatMode, "setFloatMode", 1);
@@ -7152,6 +7185,7 @@ Buf *codegen_generate_builtin_source(CodeGen *g) {
             "    ArgTuple: void,\n"
             "    Opaque: void,\n"
             "    Promise: Promise,\n"
+            "    Vector: Vector,\n"
             "\n\n"
             "    pub const Int = struct {\n"
             "        is_signed: bool,\n"
@@ -7270,6 +7304,11 @@ Buf *codegen_generate_builtin_source(CodeGen *g) {
             "        child: ?type,\n"
             "    };\n"
             "\n"
+            "    pub const Vector = struct {\n"
+            "        len: u32,\n"
+            "        child: type,\n"
+            "    };\n"
+            "\n"
             "    pub const Definition = struct {\n"
             "        name: []const u8,\n"
             "        is_pub: bool,\n"
@@ -7841,6 +7880,9 @@ static void prepend_c_type_to_decl_list(CodeGen *g, GenH *gen_h, ZigType *type_e
         case ZigTypeIdArray:
             prepend_c_type_to_decl_list(g, gen_h, type_entry->data.array.child_type);
             return;
+        case ZigTypeIdVector:
+            prepend_c_type_to_decl_list(g, gen_h, type_entry->data.vector.elem_type);
+            return;
         case ZigTypeIdOptional:
             prepend_c_type_to_decl_list(g, gen_h, type_entry->data.maybe.child_type);
             return;
@@ -7972,6 +8014,8 @@ static void get_c_type(CodeGen *g, GenH *gen_h, ZigType *type_entry, Buf *out_bu
                 buf_appendf(out_buf, "%s", buf_ptr(child_buf));
                 return;
             }
+        case ZigTypeIdVector:
+            zig_panic("TODO implement get_c_type for vector types");
         case ZigTypeIdErrorUnion:
         case ZigTypeIdErrorSet:
         case ZigTypeIdFn:
@@ -8137,6 +8181,7 @@ static void gen_h_file(CodeGen *g) {
             case ZigTypeIdOptional:
             case ZigTypeIdFn:
             case ZigTypeIdPromise:
+            case ZigTypeIdVector:
                 zig_unreachable();
             case ZigTypeIdEnum:
                 if (type_entry->data.enumeration.layout == ContainerLayoutExtern) {
src/ir.cpp
@@ -587,6 +587,10 @@ static constexpr IrInstructionId ir_instruction_id(IrInstructionIntType *) {
     return IrInstructionIdIntType;
 }
 
+static constexpr IrInstructionId ir_instruction_id(IrInstructionVectorType *) {
+    return IrInstructionIdVectorType;
+}
+
 static constexpr IrInstructionId ir_instruction_id(IrInstructionBoolNot *) {
     return IrInstructionIdBoolNot;
 }
@@ -1953,6 +1957,19 @@ static IrInstruction *ir_build_int_type(IrBuilder *irb, Scope *scope, AstNode *s
     return &instruction->base;
 }
 
+static IrInstruction *ir_build_vector_type(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *len,
+        IrInstruction *elem_type)
+{
+    IrInstructionVectorType *instruction = ir_build_instruction<IrInstructionVectorType>(irb, scope, source_node);
+    instruction->len = len;
+    instruction->elem_type = elem_type;
+
+    ir_ref_instruction(len, irb->current_basic_block);
+    ir_ref_instruction(elem_type, irb->current_basic_block);
+
+    return &instruction->base;
+}
+
 static IrInstruction *ir_build_bool_not(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *value) {
     IrInstructionBoolNot *instruction = ir_build_instruction<IrInstructionBoolNot>(irb, scope, source_node);
     instruction->value = value;
@@ -4230,6 +4247,21 @@ static IrInstruction *ir_gen_builtin_fn_call(IrBuilder *irb, Scope *scope, AstNo
                 IrInstruction *int_type = ir_build_int_type(irb, scope, node, arg0_value, arg1_value);
                 return ir_lval_wrap(irb, scope, int_type, lval);
             }
+        case BuiltinFnIdVectorType:
+            {
+                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;
+
+                IrInstruction *vector_type = ir_build_vector_type(irb, scope, node, arg0_value, arg1_value);
+                return ir_lval_wrap(irb, scope, vector_type, lval);
+            }
         case BuiltinFnIdMemcpy:
             {
                 AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
@@ -11617,6 +11649,7 @@ static IrInstruction *ir_analyze_bin_op_cmp(IrAnalyze *ira, IrInstructionBinOp *
         case ZigTypeIdComptimeInt:
         case ZigTypeIdInt:
         case ZigTypeIdFloat:
+        case ZigTypeIdVector:
             operator_allowed = true;
             break;
 
@@ -12032,6 +12065,48 @@ static IrInstruction *ir_analyze_bit_shift(IrAnalyze *ira, IrInstructionBinOp *b
     return result;
 }
 
+static bool ok_float_op(IrBinOp op) {
+    switch (op) {
+        case IrBinOpInvalid:
+            zig_unreachable();
+        case IrBinOpAdd:
+        case IrBinOpSub:
+        case IrBinOpMult:
+        case IrBinOpDivUnspecified:
+        case IrBinOpDivTrunc:
+        case IrBinOpDivFloor:
+        case IrBinOpDivExact:
+        case IrBinOpRemRem:
+        case IrBinOpRemMod:
+            return true;
+
+        case IrBinOpBoolOr:
+        case IrBinOpBoolAnd:
+        case IrBinOpCmpEq:
+        case IrBinOpCmpNotEq:
+        case IrBinOpCmpLessThan:
+        case IrBinOpCmpGreaterThan:
+        case IrBinOpCmpLessOrEq:
+        case IrBinOpCmpGreaterOrEq:
+        case IrBinOpBinOr:
+        case IrBinOpBinXor:
+        case IrBinOpBinAnd:
+        case IrBinOpBitShiftLeftLossy:
+        case IrBinOpBitShiftLeftExact:
+        case IrBinOpBitShiftRightLossy:
+        case IrBinOpBitShiftRightExact:
+        case IrBinOpAddWrap:
+        case IrBinOpSubWrap:
+        case IrBinOpMultWrap:
+        case IrBinOpRemUnspecified:
+        case IrBinOpArrayCat:
+        case IrBinOpArrayMult:
+        case IrBinOpMergeErrorSets:
+            return false;
+    }
+    zig_unreachable();
+}
+
 static IrInstruction *ir_analyze_bin_op_math(IrAnalyze *ira, IrInstructionBinOp *instruction) {
     IrInstruction *op1 = instruction->op1->child;
     if (type_is_invalid(op1->value.type))
@@ -12169,21 +12244,20 @@ static IrInstruction *ir_analyze_bin_op_math(IrAnalyze *ira, IrInstructionBinOp
         op_id = IrBinOpRemRem;
     }
 
+    bool ok = false;
     if (is_int) {
-        // int
-    } else if (is_float &&
-        (op_id == IrBinOpAdd ||
-        op_id == IrBinOpSub ||
-        op_id == IrBinOpMult ||
-        op_id == IrBinOpDivUnspecified ||
-        op_id == IrBinOpDivTrunc ||
-        op_id == IrBinOpDivFloor ||
-        op_id == IrBinOpDivExact ||
-        op_id == IrBinOpRemRem ||
-        op_id == IrBinOpRemMod))
-    {
-        // float
-    } else {
+        ok = true;
+    } else if (is_float && ok_float_op(op_id)) {
+        ok = true;
+    } else if (resolved_type->id == ZigTypeIdVector) {
+        ZigType *elem_type = resolved_type->data.vector.elem_type;
+        if (elem_type->id == ZigTypeIdInt || elem_type->id == ZigTypeIdComptimeInt) {
+            ok = true;
+        } else if ((elem_type->id == ZigTypeIdFloat || elem_type->id == ZigTypeIdComptimeFloat) && ok_float_op(op_id)) {
+            ok = true;
+        }
+    }
+    if (!ok) {
         AstNode *source_node = instruction->base.source_node;
         ir_add_error_node(ira, source_node,
             buf_sprintf("invalid operands to binary expression: '%s' and '%s'",
@@ -12817,6 +12891,7 @@ static IrInstruction *ir_analyze_instruction_export(IrAnalyze *ira, IrInstructio
                 case ZigTypeIdPointer:
                 case ZigTypeIdArray:
                 case ZigTypeIdBool:
+                case ZigTypeIdVector:
                     break;
                 case ZigTypeIdMetaType:
                 case ZigTypeIdVoid:
@@ -12851,6 +12926,7 @@ static IrInstruction *ir_analyze_instruction_export(IrAnalyze *ira, IrInstructio
         case ZigTypeIdOptional:
         case ZigTypeIdErrorUnion:
         case ZigTypeIdErrorSet:
+        case ZigTypeIdVector:
             zig_panic("TODO export const value of type %s", buf_ptr(&target->value.type->name));
         case ZigTypeIdNamespace:
         case ZigTypeIdBoundFn:
@@ -14009,6 +14085,7 @@ static IrInstruction *ir_analyze_maybe(IrAnalyze *ira, IrInstructionUnOp *un_op_
         case ZigTypeIdVoid:
         case ZigTypeIdBool:
         case ZigTypeIdInt:
+        case ZigTypeIdVector:
         case ZigTypeIdFloat:
         case ZigTypeIdPointer:
         case ZigTypeIdArray:
@@ -15383,37 +15460,7 @@ static IrInstruction *ir_analyze_instruction_typeof(IrAnalyze *ira, IrInstructio
     ZigType *type_entry = expr_value->value.type;
     if (type_is_invalid(type_entry))
         return ira->codegen->invalid_instruction;
-    switch (type_entry->id) {
-        case ZigTypeIdInvalid:
-            zig_unreachable(); // handled above
-        case ZigTypeIdComptimeFloat:
-        case ZigTypeIdComptimeInt:
-        case ZigTypeIdUndefined:
-        case ZigTypeIdNull:
-        case ZigTypeIdNamespace:
-        case ZigTypeIdBoundFn:
-        case ZigTypeIdMetaType:
-        case ZigTypeIdVoid:
-        case ZigTypeIdBool:
-        case ZigTypeIdUnreachable:
-        case ZigTypeIdInt:
-        case ZigTypeIdFloat:
-        case ZigTypeIdPointer:
-        case ZigTypeIdArray:
-        case ZigTypeIdStruct:
-        case ZigTypeIdOptional:
-        case ZigTypeIdErrorUnion:
-        case ZigTypeIdErrorSet:
-        case ZigTypeIdEnum:
-        case ZigTypeIdUnion:
-        case ZigTypeIdFn:
-        case ZigTypeIdArgTuple:
-        case ZigTypeIdOpaque:
-        case ZigTypeIdPromise:
-            return ir_const_type(ira, &typeof_instruction->base, type_entry);
-    }
-
-    zig_unreachable();
+    return ir_const_type(ira, &typeof_instruction->base, type_entry);
 }
 
 static IrInstruction *ir_analyze_instruction_to_ptr_type(IrAnalyze *ira,
@@ -15652,6 +15699,7 @@ static IrInstruction *ir_analyze_instruction_slice_type(IrAnalyze *ira,
         case ZigTypeIdNamespace:
         case ZigTypeIdBoundFn:
         case ZigTypeIdPromise:
+        case ZigTypeIdVector:
             {
                 if ((err = type_resolve(ira->codegen, child_type, ResolveStatusZeroBitsKnown)))
                     return ira->codegen->invalid_instruction;
@@ -15772,6 +15820,7 @@ static IrInstruction *ir_analyze_instruction_array_type(IrAnalyze *ira,
         case ZigTypeIdNamespace:
         case ZigTypeIdBoundFn:
         case ZigTypeIdPromise:
+        case ZigTypeIdVector:
             {
                 if ((err = ensure_complete_type(ira->codegen, child_type)))
                     return ira->codegen->invalid_instruction;
@@ -15838,6 +15887,7 @@ static IrInstruction *ir_analyze_instruction_size_of(IrAnalyze *ira,
         case ZigTypeIdUnion:
         case ZigTypeIdFn:
         case ZigTypeIdPromise:
+        case ZigTypeIdVector:
             {
                 uint64_t size_in_bytes = type_size(ira->codegen, type_entry);
                 return ir_const_unsigned(ira, &size_of_instruction->base, size_in_bytes);
@@ -16307,6 +16357,7 @@ static IrInstruction *ir_analyze_instruction_switch_target(IrAnalyze *ira,
         case ZigTypeIdBoundFn:
         case ZigTypeIdArgTuple:
         case ZigTypeIdOpaque:
+        case ZigTypeIdVector:
             ir_add_error(ira, &switch_target_instruction->base,
                 buf_sprintf("invalid switch target type '%s'", buf_ptr(&target_type->name)));
             return ira->codegen->invalid_instruction;
@@ -17496,6 +17547,27 @@ static Error ir_make_type_info_value(IrAnalyze *ira, ZigType *type_entry, ConstE
 
                 break;
             }
+        case ZigTypeIdVector: {
+            result = create_const_vals(1);
+            result->special = ConstValSpecialStatic;
+            result->type = ir_type_info_get_type(ira, "Vector", nullptr);
+
+            ConstExprValue *fields = create_const_vals(2);
+            result->data.x_struct.fields = fields;
+
+            // len: usize
+            ensure_field_index(result->type, "len", 0);
+            fields[0].special = ConstValSpecialStatic;
+            fields[0].type = ira->codegen->builtin_types.entry_u32;
+            bigint_init_unsigned(&fields[0].data.x_bigint, type_entry->data.vector.len);
+            // child: type
+            ensure_field_index(result->type, "child", 1);
+            fields[1].special = ConstValSpecialStatic;
+            fields[1].type = ira->codegen->builtin_types.entry_type;
+            fields[1].data.x_type = type_entry->data.vector.elem_type;
+
+            break;
+        }
         case ZigTypeIdOptional:
             {
                 result = create_const_vals(1);
@@ -18671,6 +18743,30 @@ static IrInstruction *ir_analyze_instruction_int_type(IrAnalyze *ira, IrInstruct
     return ir_const_type(ira, &instruction->base, get_int_type(ira->codegen, is_signed, (uint32_t)bit_count));
 }
 
+static IrInstruction *ir_analyze_instruction_vector_type(IrAnalyze *ira, IrInstructionVectorType *instruction) {
+    uint64_t len;
+    if (!ir_resolve_unsigned(ira, instruction->len->child, ira->codegen->builtin_types.entry_u32, &len))
+        return ira->codegen->invalid_instruction;
+
+    ZigType *elem_type = ir_resolve_type(ira, instruction->elem_type->child);
+    if (type_is_invalid(elem_type))
+        return ira->codegen->invalid_instruction;
+
+    if (elem_type->id != ZigTypeIdInt &&
+        elem_type->id != ZigTypeIdFloat &&
+        get_codegen_ptr_type(elem_type) == nullptr)
+    {
+        ir_add_error(ira, instruction->elem_type,
+            buf_sprintf("vector element type must be integer, float, or pointer; '%s' is invalid",
+                buf_ptr(&elem_type->name)));
+        return ira->codegen->invalid_instruction;
+    }
+
+    ZigType *vector_type = get_vector_type(ira->codegen, len, elem_type);
+
+    return ir_const_type(ira, &instruction->base, vector_type);
+}
+
 static IrInstruction *ir_analyze_instruction_bool_not(IrAnalyze *ira, IrInstructionBoolNot *instruction) {
     IrInstruction *value = instruction->value->child;
     if (type_is_invalid(value->value.type))
@@ -19474,6 +19570,7 @@ static IrInstruction *ir_analyze_instruction_align_of(IrAnalyze *ira, IrInstruct
         case ZigTypeIdEnum:
         case ZigTypeIdUnion:
         case ZigTypeIdFn:
+        case ZigTypeIdVector:
             {
                 uint64_t align_in_bytes = get_abi_alignment(ira->codegen, type_entry);
                 return ir_const_unsigned(ira, &instruction->base, align_in_bytes);
@@ -20311,6 +20408,15 @@ static void buf_write_value_bytes(CodeGen *codegen, uint8_t *buf, ConstExprValue
                 }
             }
             return;
+        case ZigTypeIdVector: {
+            size_t buf_i = 0;
+            for (uint32_t elem_i = 0; elem_i < val->type->data.vector.len; elem_i += 1) {
+                ConstExprValue *elem = &val->data.x_vector.elements[elem_i];
+                buf_write_value_bytes(codegen, &buf[buf_i], elem);
+                buf_i += type_size(codegen, elem->type);
+            }
+            return;
+        }
         case ZigTypeIdStruct:
             zig_panic("TODO buf_write_value_bytes struct type");
         case ZigTypeIdOptional:
@@ -20387,6 +20493,20 @@ static Error buf_read_value_bytes(IrAnalyze *ira, CodeGen *codegen, AstNode *sou
             }
             zig_unreachable();
         }
+        case ZigTypeIdVector: {
+            uint64_t elem_size = type_size(codegen, val->type->data.vector.elem_type);
+            uint32_t len = val->type->data.vector.len;
+
+            val->data.x_vector.elements = create_const_vals(len);
+            for (uint32_t i = 0; i < len; i += 1) {
+                ConstExprValue *elem = &val->data.x_vector.elements[i];
+                elem->special = ConstValSpecialStatic;
+                elem->type = val->type->data.vector.elem_type;
+                if ((err = buf_read_value_bytes(ira, codegen, source_node, buf + (elem_size * i), elem)))
+                    return err;
+            }
+            return ErrorNone;
+        }
         case ZigTypeIdEnum:
             switch (val->type->data.enumeration.layout) {
                 case ContainerLayoutAuto:
@@ -21633,6 +21753,8 @@ static IrInstruction *ir_analyze_instruction_nocast(IrAnalyze *ira, IrInstructio
             return ir_analyze_instruction_bool_to_int(ira, (IrInstructionBoolToInt *)instruction);
         case IrInstructionIdIntType:
             return ir_analyze_instruction_int_type(ira, (IrInstructionIntType *)instruction);
+        case IrInstructionIdVectorType:
+            return ir_analyze_instruction_vector_type(ira, (IrInstructionVectorType *)instruction);
         case IrInstructionIdBoolNot:
             return ir_analyze_instruction_bool_not(ira, (IrInstructionBoolNot *)instruction);
         case IrInstructionIdMemset:
@@ -21943,6 +22065,7 @@ bool ir_has_side_effects(IrInstruction *instruction) {
         case IrInstructionIdEmbedFile:
         case IrInstructionIdTruncate:
         case IrInstructionIdIntType:
+        case IrInstructionIdVectorType:
         case IrInstructionIdBoolNot:
         case IrInstructionIdSlice:
         case IrInstructionIdMemberCount:
src/ir_print.cpp
@@ -719,6 +719,14 @@ static void ir_print_int_type(IrPrint *irp, IrInstructionIntType *instruction) {
     fprintf(irp->f, ")");
 }
 
+static void ir_print_vector_type(IrPrint *irp, IrInstructionVectorType *instruction) {
+    fprintf(irp->f, "@Vector(");
+    ir_print_other_instruction(irp, instruction->len);
+    fprintf(irp->f, ", ");
+    ir_print_other_instruction(irp, instruction->elem_type);
+    fprintf(irp->f, ")");
+}
+
 static void ir_print_bool_not(IrPrint *irp, IrInstructionBoolNot *instruction) {
     fprintf(irp->f, "! ");
     ir_print_other_instruction(irp, instruction->value);
@@ -1577,6 +1585,9 @@ static void ir_print_instruction(IrPrint *irp, IrInstruction *instruction) {
         case IrInstructionIdIntType:
             ir_print_int_type(irp, (IrInstructionIntType *)instruction);
             break;
+        case IrInstructionIdVectorType:
+            ir_print_vector_type(irp, (IrInstructionVectorType *)instruction);
+            break;
         case IrInstructionIdBoolNot:
             ir_print_bool_not(irp, (IrInstructionBoolNot *)instruction);
             break;
src/zig_llvm.cpp
@@ -263,6 +263,19 @@ ZigLLVMDIType *ZigLLVMCreateDebugBasicType(ZigLLVMDIBuilder *dibuilder, const ch
     return reinterpret_cast<ZigLLVMDIType*>(di_type);
 }
 
+struct ZigLLVMDIType *ZigLLVMDIBuilderCreateVectorType(struct ZigLLVMDIBuilder *dibuilder,
+        uint64_t Size, uint32_t AlignInBits, struct ZigLLVMDIType *Ty)
+{
+    SmallVector<Metadata *, 1> subrange;
+    subrange.push_back(reinterpret_cast<DIBuilder*>(dibuilder)->getOrCreateSubrange(0, Size));
+    DIType *di_type = reinterpret_cast<DIBuilder*>(dibuilder)->createVectorType(
+            Size,
+            AlignInBits,
+            reinterpret_cast<DIType*>(Ty),
+            reinterpret_cast<DIBuilder*>(dibuilder)->getOrCreateArray(subrange));
+    return reinterpret_cast<ZigLLVMDIType*>(di_type);
+}
+
 ZigLLVMDIType *ZigLLVMCreateDebugArrayType(ZigLLVMDIBuilder *dibuilder, uint64_t size_in_bits,
         uint64_t align_in_bits, ZigLLVMDIType *elem_type, int elem_count)
 {
src/zig_llvm.h
@@ -191,6 +191,9 @@ ZIG_EXTERN_C struct ZigLLVMDISubprogram *ZigLLVMCreateFunction(struct ZigLLVMDIB
         unsigned lineno, struct ZigLLVMDIType *fn_di_type, bool is_local_to_unit, bool is_definition,
         unsigned scope_line, unsigned flags, bool is_optimized, struct ZigLLVMDISubprogram *decl_subprogram);
 
+ZIG_EXTERN_C struct ZigLLVMDIType *ZigLLVMDIBuilderCreateVectorType(struct ZigLLVMDIBuilder *dibuilder,
+        uint64_t Size, uint32_t AlignInBits, struct ZigLLVMDIType *Ty);
+
 ZIG_EXTERN_C void ZigLLVMFnSetSubprogram(LLVMValueRef fn, struct ZigLLVMDISubprogram *subprogram);
 
 ZIG_EXTERN_C void ZigLLVMDIBuilderFinalize(struct ZigLLVMDIBuilder *dibuilder);
src-self-hosted/type.zig
@@ -44,6 +44,7 @@ pub const Type = struct {
             Id.ArgTuple => @fieldParentPtr(ArgTuple, "base", base).destroy(comp),
             Id.Opaque => @fieldParentPtr(Opaque, "base", base).destroy(comp),
             Id.Promise => @fieldParentPtr(Promise, "base", base).destroy(comp),
+            Id.Vector => @fieldParentPtr(Vector, "base", base).destroy(comp),
         }
     }
 
@@ -77,6 +78,7 @@ pub const Type = struct {
             Id.ArgTuple => unreachable,
             Id.Opaque => return @fieldParentPtr(Opaque, "base", base).getLlvmType(allocator, llvm_context),
             Id.Promise => return @fieldParentPtr(Promise, "base", base).getLlvmType(allocator, llvm_context),
+            Id.Vector => return @fieldParentPtr(Vector, "base", base).getLlvmType(allocator, llvm_context),
         }
     }
 
@@ -103,6 +105,7 @@ pub const Type = struct {
             Id.Enum,
             Id.Fn,
             Id.Promise,
+            Id.Vector,
             => return false,
 
             Id.Struct => @panic("TODO"),
@@ -135,6 +138,7 @@ pub const Type = struct {
             Id.Float,
             Id.Fn,
             Id.Promise,
+            Id.Vector,
             => return true,
 
             Id.Pointer => {
@@ -902,6 +906,18 @@ pub const Type = struct {
         }
     };
 
+    pub const Vector = struct {
+        base: Type,
+
+        pub fn destroy(self: *Vector, comp: *Compilation) void {
+            comp.gpa().destroy(self);
+        }
+
+        pub fn getLlvmType(self: *Vector, allocator: *Allocator, llvm_context: llvm.ContextRef) llvm.TypeRef {
+            @panic("TODO");
+        }
+    };
+
     pub const ComptimeFloat = struct {
         base: Type,
 
std/hash_map.zig
@@ -508,6 +508,7 @@ pub fn autoHash(key: var, comptime rng: *std.rand.Random, comptime HashInt: type
 
         builtin.TypeId.Optional => @compileError("TODO auto hash for optionals"),
         builtin.TypeId.Array => @compileError("TODO auto hash for arrays"),
+        builtin.TypeId.Vector => @compileError("TODO auto hash for vectors"),
         builtin.TypeId.Struct => @compileError("TODO auto hash for structs"),
         builtin.TypeId.Union => @compileError("TODO auto hash for unions"),
         builtin.TypeId.ErrorUnion => @compileError("TODO auto hash for unions"),
@@ -555,5 +556,6 @@ pub fn autoEql(a: var, b: @typeOf(a)) bool {
         builtin.TypeId.Struct => @compileError("TODO auto eql for structs"),
         builtin.TypeId.Union => @compileError("TODO auto eql for unions"),
         builtin.TypeId.ErrorUnion => @compileError("TODO auto eql for unions"),
+        builtin.TypeId.Vector => @compileError("TODO auto eql for vectors"),
     }
 }
test/stage1/behavior/type_info.zig
@@ -171,11 +171,11 @@ fn testUnion() void {
     assertOrPanic(TypeId(typeinfo_info) == TypeId.Union);
     assertOrPanic(typeinfo_info.Union.layout == TypeInfo.ContainerLayout.Auto);
     assertOrPanic(typeinfo_info.Union.tag_type.? == TypeId);
-    assertOrPanic(typeinfo_info.Union.fields.len == 24);
+    assertOrPanic(typeinfo_info.Union.fields.len == 25);
     assertOrPanic(typeinfo_info.Union.fields[4].enum_field != null);
     assertOrPanic(typeinfo_info.Union.fields[4].enum_field.?.value == 4);
     assertOrPanic(typeinfo_info.Union.fields[4].field_type == @typeOf(@typeInfo(u8).Int));
-    assertOrPanic(typeinfo_info.Union.defs.len == 20);
+    assertOrPanic(typeinfo_info.Union.defs.len == 21);
 
     const TestNoTagUnion = union {
         Foo: void,
@@ -262,3 +262,15 @@ test "typeInfo with comptime parameter in struct fn def" {
     };
     comptime var info = @typeInfo(S);
 }
+
+test "type info: vectors" {
+    testVector();
+    comptime testVector();
+}
+
+fn testVector() void {
+    const vec_info = @typeInfo(@Vector(4, i32));
+    assertOrPanic(TypeId(vec_info) == TypeId.Vector);
+    assertOrPanic(vec_info.Vector.len == 4);
+    assertOrPanic(vec_info.Vector.child == i32);
+}