Browse Source

`#soa[]Type` (Experimental)

gingerBill 5 years ago
parent
commit
2c5a84bb78
7 changed files with 481 additions and 106 deletions
  1. 39 8
      core/fmt/fmt.odin
  2. 88 68
      examples/demo/demo.odin
  3. 24 2
      src/check_expr.cpp
  4. 117 2
      src/check_type.cpp
  5. 4 2
      src/entity.cpp
  6. 204 23
      src/ir.cpp
  7. 5 1
      src/types.cpp

+ 39 - 8
core/fmt/fmt.odin

@@ -1350,7 +1350,25 @@ fmt_value :: proc(fi: ^Info, v: any, verb: rune) {
 				base_type_name = v.name;
 			}
 
-			for index in 0..<uintptr(info.soa_len) {
+			actual_field_count := len(info.names);
+
+			n := uintptr(info.soa_len);
+
+			fields_are_ptrs := false;
+			if info.soa_kind == .Slice {
+				actual_field_count = len(info.names)-1; // len
+
+				n = uintptr((^int)(uintptr(v.data) + info.offsets[actual_field_count])^);
+
+			} else if info.soa_kind == .Dynamic {
+				actual_field_count = len(info.names)-3; // len, cap, allocator
+
+				n = uintptr((^int)(uintptr(v.data) + info.offsets[actual_field_count])^);
+			}
+
+
+
+			for index in 0..<n {
 				if !hash && index > 0 do strings.write_string(fi.buf, ", ");
 
 				field_count := -1;
@@ -1361,7 +1379,8 @@ fmt_value :: proc(fi: ^Info, v: any, verb: rune) {
 				strings.write_byte(fi.buf, '{');
 				defer strings.write_byte(fi.buf, '}');
 
-				for name, i in info.names {
+				for i in 0..<actual_field_count {
+					name := info.names[i];
 					field_count += 1;
 
 					if !hash && field_count > 0 do strings.write_string(fi.buf, ", ");
@@ -1370,13 +1389,25 @@ fmt_value :: proc(fi: ^Info, v: any, verb: rune) {
 					strings.write_string(fi.buf, name);
 					strings.write_string(fi.buf, " = ");
 
-					t := info.types[i].variant.(runtime.Type_Info_Array).elem;
-					t_size := uintptr(t.size);
-					if reflect.is_any(t) {
-						strings.write_string(fi.buf, "any{}");
+					if info.soa_kind == .Fixed {
+						t := info.types[i].variant.(runtime.Type_Info_Array).elem;
+						t_size := uintptr(t.size);
+						if reflect.is_any(t) {
+							strings.write_string(fi.buf, "any{}");
+						} else {
+							data := rawptr(uintptr(v.data) + info.offsets[i] + index*t_size);
+							fmt_arg(fi, any{data, t.id}, 'v');
+						}
 					} else {
-						data := rawptr(uintptr(v.data) + info.offsets[i] + index*t_size);
-						fmt_arg(fi, any{data, t.id}, 'v');
+						t := info.types[i].variant.(runtime.Type_Info_Pointer).elem;
+						t_size := uintptr(t.size);
+						if reflect.is_any(t) {
+							strings.write_string(fi.buf, "any{}");
+						} else {
+							field_ptr := (^^byte)(uintptr(v.data) + info.offsets[i])^;
+							data := rawptr(uintptr(field_ptr) + index*t_size);
+							fmt_arg(fi, any{data, t.id}, 'v');
+						}
 					}
 
 					if hash do strings.write_string(fi.buf, ",\n");

+ 88 - 68
examples/demo/demo.odin

@@ -7,7 +7,7 @@ import "core:reflect"
 import "intrinsics"
 
 /*
-	The Odin programming language is fast, concise, readable, pragmatic and open sourced. 
+	The Odin programming language is fast, concise, readable, pragmatic and open sourced.
 	It is designed with the intent of replacing C with the following goals:
 	 * simplicity
 	 * high performance
@@ -36,7 +36,7 @@ the_basics :: proc() {
 
 		my_integer_variable: int; // A comment for documentaton
 
-		// Multi-line comments begin with /* and end with */. Multi-line comments can 
+		// Multi-line comments begin with /* and end with */. Multi-line comments can
 		// also be nested (unlike in C):
 		/*
 			You can have any text or code here and
@@ -63,16 +63,16 @@ the_basics :: proc() {
 
 		// Numbers
 
-		// Numerical literals are written similar to most other programming languages. 
-		// A useful feature in Odin is that underscores are allowed for better 
-		// readability: 1_000_000_000 (one billion). A number that contains a dot is a 
-		// floating point literal: 1.0e9 (one billion). If a number literal is suffixed 
+		// Numerical literals are written similar to most other programming languages.
+		// A useful feature in Odin is that underscores are allowed for better
+		// readability: 1_000_000_000 (one billion). A number that contains a dot is a
+		// floating point literal: 1.0e9 (one billion). If a number literal is suffixed
 		// with i, is an imaginary number literal: 2i (2 multiply the square root of -1).
 
-		// Binary literals are prefixed with 0b, octal literals with 0o, and hexadecimal 
+		// Binary literals are prefixed with 0b, octal literals with 0o, and hexadecimal
 		// literals 0x. A leading zero does not produce an octal constant (unlike C).
 
-		// In Odin, if a number constant is possible to be represented by a type without 
+		// In Odin, if a number constant is possible to be represented by a type without
 		// precision loss, it will automatically convert to that type.
 
 		x: int = 1.0; // A float literal but it can be represented by an integer without precision loss
@@ -105,8 +105,8 @@ the_basics :: proc() {
 		*/
 
 		// Constant declarations
-		// Constants are entities (symbols) which have an assigned value. 
-		// The constant’s value cannot be changed. 
+		// Constants are entities (symbols) which have an assigned value.
+		// The constant’s value cannot be changed.
 		// The constant’s value must be able to be evaluated at compile time:
 		X :: "what"; // constant `X` has the untyped string value "what"
 
@@ -234,7 +234,7 @@ control_flow :: proc() {
 		}
 
 		// Switch statement
-		// A switch statement is another way to write a sequence of if-else statements. 
+		// A switch statement is another way to write a sequence of if-else statements.
 		// In Odin, the default case is denoted as a case without any expression.
 
 		switch arch := ODIN_ARCH; arch {
@@ -246,12 +246,12 @@ control_flow :: proc() {
 			fmt.println("Unsupported architecture");
 		}
 
-		// Odin’s `switch` is like one in C or C++, except that Odin only runs the selected case. 
-		// This means that a `break` statement is not needed at the end of each case. 
+		// Odin’s `switch` is like one in C or C++, except that Odin only runs the selected case.
+		// This means that a `break` statement is not needed at the end of each case.
 		// Another important difference is that the case values need not be integers nor constants.
 
 		// To achieve a C-like fall through into the next case block, the keyword `fallthrough` can be used.
-		one_angry_dwarf :: proc() -> int { 
+		one_angry_dwarf :: proc() -> int {
 			fmt.println("one_angry_dwarf was called");
 			return 1;
 		}
@@ -261,8 +261,8 @@ control_flow :: proc() {
 		case one_angry_dwarf():
 		}
 
-		// A switch statement without a condition is the same as `switch true`. 
-		// This can be used to write a clean and long if-else chain and have the 
+		// A switch statement without a condition is the same as `switch true`.
+		// This can be used to write a clean and long if-else chain and have the
 		// ability to break if needed
 
 		switch {
@@ -293,7 +293,7 @@ control_flow :: proc() {
 	}
 
 	{ // Defer statement
-		// A defer statement defers the execution of a statement until the end of 
+		// A defer statement defers the execution of a statement until the end of
 		// the scope it is in.
 
 		// The following will print 4 then 234:
@@ -318,11 +318,11 @@ control_flow :: proc() {
 				fmt.println("2");
 			}
 
-			cond := false; 
+			cond := false;
 			defer if cond {
 				bar();
 			}
-		}	
+		}
 
 		// Defer statements are executed in the reverse order that they were declared:
 		{
@@ -343,13 +343,13 @@ control_flow :: proc() {
 	}
 
 	{ // When statement
-		/* 
+		/*
 			The when statement is almost identical to the if statement but with some differences:
 
-			* Each condition must be a constant expression as a when 
+			* Each condition must be a constant expression as a when
 			  statement is evaluated at compile time.
 			* The statements within a branch do not create a new scope
-			* The compiler checks the semantics and code only for statements 
+			* The compiler checks the semantics and code only for statements
 			  that belong to the first condition that is true
 			* An initial statement is not allowed in a when statement
 			* when statements are allowed at file scope
@@ -363,8 +363,8 @@ control_flow :: proc() {
 		} else {
 			fmt.println("Unsupported architecture");
 		}
-		// The when statement is very useful for writing platform specific code. 
-		// This is akin to the #if construct in C’s preprocessor however, in Odin, 
+		// The when statement is very useful for writing platform specific code.
+		// This is akin to the #if construct in C’s preprocessor however, in Odin,
 		// it is type checked.
 	}
 
@@ -401,9 +401,9 @@ control_flow :: proc() {
 
 		// Fallthrough statement
 
-		// Odin’s switch is like one in C or C++, except that Odin only runs the selected 
-		// case. This means that a break statement is not needed at the end of each case. 
-		// Another important difference is that the case values need not be integers nor 
+		// Odin’s switch is like one in C or C++, except that Odin only runs the selected
+		// case. This means that a break statement is not needed at the end of each case.
+		// Another important difference is that the case values need not be integers nor
 		// constants.
 
 		// fallthrough can be used to explicitly fall through into the next case block:
@@ -477,8 +477,8 @@ explicit_procedure_overloading :: proc() {
 
 struct_type :: proc() {
 	fmt.println("\n# struct type");
-	// A struct is a record type in Odin. It is a collection of fields. 
-	// Struct fields are accessed by using a dot:	
+	// A struct is a record type in Odin. It is a collection of fields.
+	// Struct fields are accessed by using a dot:
 	{
 		Vector2 :: struct {
 			x: f32,
@@ -495,13 +495,13 @@ struct_type :: proc() {
 		p.x = 1335;
 		fmt.println(v);
 
-		// We could write p^.x, however, it is to nice abstract the ability 
-		// to not explicitly dereference the pointer. This is very useful when 
+		// We could write p^.x, however, it is to nice abstract the ability
+		// to not explicitly dereference the pointer. This is very useful when
 		// refactoring code to use a pointer rather than a value, and vice versa.
 	}
 	{
-		// A struct literal can be denoted by providing the struct’s type 
-		// followed by {}. A struct literal must either provide all the 
+		// A struct literal can be denoted by providing the struct’s type
+		// followed by {}. A struct literal must either provide all the
 		// arguments or none:
 		Vector3 :: struct {
 			x, y, z: f32,
@@ -510,12 +510,12 @@ struct_type :: proc() {
 		v = Vector3{}; // Zero value
 		v = Vector3{1, 4, 9};
 
-		// You can list just a subset of the fields if you specify the 
+		// You can list just a subset of the fields if you specify the
 		// field by name (the order of the named fields does not matter):
 		v = Vector3{z=1, y=2};
 		assert(v.x == 0);
 		assert(v.y == 2);
-		assert(v.z == 1);		
+		assert(v.z == 1);
 	}
 	{
 		// Structs can tagged with different memory layout and alignment requirements:
@@ -704,8 +704,8 @@ union_type :: proc() {
 
 using_statement :: proc() {
 	fmt.println("\n# using statement");
-	// using can used to bring entities declared in a scope/namespace 
-	// into the current scope. This can be applied to import declarations, 
+	// using can used to bring entities declared in a scope/namespace
+	// into the current scope. This can be applied to import declarations,
 	// import names, struct fields, procedure fields, and struct values.
 
 	Vector3 :: struct{x, y, z: f32};
@@ -738,7 +738,7 @@ using_statement :: proc() {
 		}
 	}
 	{
-		// We can also apply the using statement to the struct fields directly, 
+		// We can also apply the using statement to the struct fields directly,
 		// making all the fields of position appear as if they on Entity itself:
 		Entity :: struct {
 			using position: Vector3,
@@ -747,11 +747,11 @@ using_statement :: proc() {
 		foo :: proc(entity: ^Entity) {
 			fmt.println(entity.x, entity.y, entity.z);
 		}
-		
+
 
 		// Subtype polymorphism
-		// It is possible to get subtype polymorphism, similar to inheritance-like 
-		// functionality in C++, but without the requirement of vtables or unknown 
+		// It is possible to get subtype polymorphism, similar to inheritance-like
+		// functionality in C++, but without the requirement of vtables or unknown
 		// struct layout:
 
 		Colour :: struct {r, g, b, a: u8};
@@ -767,7 +767,7 @@ using_statement :: proc() {
 		foo(&frog);
 		frog.x = 123;
 
-		// Note: using can be applied to arbitrarily many things, which allows 
+		// Note: using can be applied to arbitrarily many things, which allows
 		// the ability to have multiple subtype polymorphism (but also its issues).
 
 		// Note: using’d fields can still be referred by name.
@@ -787,19 +787,19 @@ using_statement :: proc() {
 
 implicit_context_system :: proc() {
 	fmt.println("\n# implicit context system");
-	// In each scope, there is an implicit value named context. This 
-	// context variable is local to each scope and is implicitly passed 
-	// by pointer to any procedure call in that scope (if the procedure 
+	// In each scope, there is an implicit value named context. This
+	// context variable is local to each scope and is implicitly passed
+	// by pointer to any procedure call in that scope (if the procedure
 	// has the Odin calling convention).
 
-	// The main purpose of the implicit context system is for the ability 
-	// to intercept third-party code and libraries and modify their 
+	// The main purpose of the implicit context system is for the ability
+	// to intercept third-party code and libraries and modify their
 	// functionality. One such case is modifying how a library allocates
-	// something or logs something. In C, this was usually achieved with 
-	// the library defining macros which could be overridden so that the 
-	// user could define what he wanted. However, not many libraries 
-	// supported this in many languages by default which meant intercepting 
-	// third-party code to see what it does and to change how it does it is 
+	// something or logs something. In C, this was usually achieved with
+	// the library defining macros which could be overridden so that the
+	// user could define what he wanted. However, not many libraries
+	// supported this in many languages by default which meant intercepting
+	// third-party code to see what it does and to change how it does it is
 	// not possible.
 
 	c := context; // copy the current scope's context
@@ -820,7 +820,7 @@ implicit_context_system :: proc() {
 		// An context.allocator is assigned to the return value of `my_custom_allocator()`
 		assert(context.user_index == 123);
 
-		// The memory management procedure use the `context.allocator` by 
+		// The memory management procedure use the `context.allocator` by
 		// default unless explicitly specified otherwise
 		china_grove := new(int);
 		free(china_grove);
@@ -828,10 +828,10 @@ implicit_context_system :: proc() {
 
 	my_custom_allocator :: mem.nil_allocator;
 
-	// By default, the context value has default values for its parameters which is 
+	// By default, the context value has default values for its parameters which is
 	// decided in the package runtime. What the defaults are are compiler specific.
 
-	// To see what the implicit context value contains, please see the following 
+	// To see what the implicit context value contains, please see the following
 	// definition in package runtime.
 }
 
@@ -1133,14 +1133,14 @@ map_type :: proc() {
 	m := make(map[string]int);
 	defer delete(m);
 
-	m["Bob"] = 2; 
+	m["Bob"] = 2;
 	m["Ted"] = 5;
 	fmt.println(m["Bob"]);
 
 	delete_key(&m, "Ted");
 
-	// If an element of a key does not exist, the zero value of the 
-	// element will be returned. To check to see if an element exists 
+	// If an element of a key does not exist, the zero value of the
+	// element will be returned. To check to see if an element exists
 	// can be done in two ways:
 	elem, ok := m["Bob"];
 	exists := "Bob" in m;
@@ -1508,26 +1508,26 @@ when ODIN_OS == "windows" do foreign import kernel32 "system:kernel32.lib"
 foreign_system :: proc() {
 	fmt.println("\n#foreign system");
 	when ODIN_OS == "windows" {
-		// It is sometimes necessarily to interface with foreign code, 
-		// such as a C library. In Odin, this is achieved through the 
-		// foreign system. You can “import” a library into the code 
+		// It is sometimes necessarily to interface with foreign code,
+		// such as a C library. In Odin, this is achieved through the
+		// foreign system. You can “import” a library into the code
 		// using the same semantics as a normal import declaration.
 
-		// This foreign import declaration will create a 
-		// “foreign import name” which can then be used to associate 
+		// This foreign import declaration will create a
+		// “foreign import name” which can then be used to associate
 		// entities within a foreign block.
 
 		foreign kernel32 {
 			ExitProcess :: proc "stdcall" (exit_code: u32) ---
 		}
 
-		// Foreign procedure declarations have the cdecl/c calling 
-		// convention by default unless specified otherwise. Due to 
-		// foreign procedures do not have a body declared within this 
-		// code, you need append the --- symbol to the end to distinguish 
+		// Foreign procedure declarations have the cdecl/c calling
+		// convention by default unless specified otherwise. Due to
+		// foreign procedures do not have a body declared within this
+		// code, you need append the --- symbol to the end to distinguish
 		// it as a procedure literal without a body and not a procedure type.
 
-		// The attributes system can be used to change specific properties 
+		// The attributes system can be used to change specific properties
 		// of entities declared within a block:
 
 		@(default_calling_convention = "std")
@@ -1718,6 +1718,26 @@ soa_struct_layout :: proc() {
 		v_soa[0].y = 4;
 		v_soa[0].z = 9;
 	}
+	{
+		// SOA Slices
+		Vector3 :: struct {x, y, z: f32};
+
+		N :: 3;
+		v: #soa[N]Vector3;
+		v[0].x = 1;
+		v[0].y = 4;
+		v[0].z = 9;
+
+		s: #soa[]Vector3;
+		s = v[:];
+		assert(len(s) == N);
+		fmt.println(s);
+		fmt.println(s[0].x);
+
+		a := s[1:2];
+		assert(len(a) == 1);
+		fmt.println(a);
+	}
 }
 
 

+ 24 - 2
src/check_expr.cpp

@@ -92,6 +92,10 @@ bool abi_compat_return_by_pointer(gbAllocator a, ProcCallingConvention cc, Type
 void set_procedure_abi_types(gbAllocator a, Type *type);
 void check_assignment_error_suggestion(CheckerContext *c, Operand *o, Type *type);
 
+
+Type *make_soa_struct_slice(CheckerContext *ctx, Ast *array_typ_expr, Ast *elem_expr, Type *elem);
+
+
 Entity *entity_from_expr(Ast *expr) {
 	expr = unparen_expr(expr);
 	switch (expr->kind) {
@@ -3228,6 +3232,8 @@ Entity *check_selector(CheckerContext *c, Operand *operand, Ast *node, Type *typ
 			}
 		} else if (operand->mode == Addressing_MapIndex) {
 			operand->mode = Addressing_Value;
+		} else if (entity->flags & EntityFlag_SoaPtrField) {
+			operand->mode = Addressing_SoaVariable;
 		} else if (sel.indirect || operand->mode != Addressing_Value || operand->mode == Addressing_SoaVariable) {
 			operand->mode = Addressing_Variable;
 		} else {
@@ -6739,7 +6745,7 @@ void check_expr_with_type_hint(CheckerContext *c, Operand *o, Ast *e, Type *t) {
 	}
 }
 
-bool check_set_index_data(Operand *o, Type *t, bool indirection, i64 *max_count) {
+bool check_set_index_data(Operand *o, Type *t, bool indirection, i64 *max_count, Type *original_type) {
 	switch (t->kind) {
 	case Type_Basic:
 		if (t->Basic.kind == Basic_string) {
@@ -6796,6 +6802,15 @@ bool check_set_index_data(Operand *o, Type *t, bool indirection, i64 *max_count)
 		return false;
 	}
 
+	if (is_type_pointer(original_type) && indirection) {
+		Type *ptr = base_type(original_type);
+		if (ptr->kind == Type_Pointer && o->mode == Addressing_SoaVariable) {
+			o->type = ptr->Pointer.elem;
+			o->mode = Addressing_Value;
+			return true;
+		}
+	}
+
 	return false;
 }
 
@@ -7973,7 +7988,7 @@ ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast *node, Type
 		}
 
 		i64 max_count = -1;
-		bool valid = check_set_index_data(o, t, is_ptr, &max_count);
+		bool valid = check_set_index_data(o, t, is_ptr, &max_count, o->type);
 
 		if (is_const) {
 			valid = false;
@@ -8055,6 +8070,13 @@ ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast *node, Type
 			valid = true;
 			o->type = alloc_type_slice(t->DynamicArray.elem);
 			break;
+
+		case Type_Struct:
+			if (is_type_soa_struct(t)) {
+				valid = true;
+				o->type = make_soa_struct_slice(c, nullptr, nullptr, t->Struct.soa_elem);
+			}
+			break;
 		}
 
 		if (!valid) {

+ 117 - 2
src/check_type.cpp

@@ -2702,6 +2702,108 @@ void check_map_type(CheckerContext *ctx, Type *type, Ast *node) {
 	// error(node, "'map' types are not yet implemented");
 }
 
+Type *make_soa_struct_slice(CheckerContext *ctx, Ast *array_typ_expr, Ast *elem_expr, Type *elem) {
+	Type *bt_elem = base_type(elem);
+	if (!is_type_struct(elem) && !is_type_raw_union(elem) && !(is_type_array(elem) && bt_elem->Array.count <= 4)) {
+		GB_ASSERT(elem_expr != nullptr);
+
+		gbString str = type_to_string(elem);
+		error(elem_expr, "Invalid type for an #soa array, expected a struct or array of length 4 or below, got '%s'", str);
+		gb_string_free(str);
+		return alloc_type_slice(elem);
+	}
+
+	Type *soa_struct = nullptr;
+	Scope *scope = nullptr;
+
+	if (is_type_array(elem)) {
+		Type *old_array = base_type(elem);
+		isize field_count = old_array->Array.count;
+
+		soa_struct = alloc_type_struct();
+		soa_struct->Struct.fields = array_make<Entity *>(heap_allocator(), field_count+1);
+		soa_struct->Struct.tags = array_make<String>(heap_allocator(), field_count+1);
+		soa_struct->Struct.node = array_typ_expr;
+		soa_struct->Struct.soa_kind = StructSoa_Slice;
+		soa_struct->Struct.soa_elem = elem;
+		soa_struct->Struct.soa_count = 0;
+
+		scope = create_scope(ctx->scope, ctx->allocator);
+		soa_struct->Struct.scope = scope;
+
+		String params_xyzw[4] = {
+			str_lit("x"),
+			str_lit("y"),
+			str_lit("z"),
+			str_lit("w")
+		};
+
+		for (i64 i = 0; i < field_count; i++) {
+			Type *array_type = alloc_type_pointer(old_array->Array.elem);
+			Token token = {};
+			token.string = params_xyzw[i];
+
+			Entity *new_field = alloc_entity_field(scope, token, array_type, false, cast(i32)i);
+			new_field->flags |= EntityFlag_SoaPtrField;
+			soa_struct->Struct.fields[i] = new_field;
+			add_entity(ctx->checker, scope, nullptr, new_field);
+			add_entity_use(ctx, nullptr, new_field);
+		}
+
+		Entity *len_field = alloc_entity_field(scope, empty_token, t_int, false, cast(i32)field_count);
+		soa_struct->Struct.fields[field_count] = len_field;
+		add_entity(ctx->checker, scope, nullptr, len_field);
+		add_entity_use(ctx, nullptr, len_field);
+
+
+	} else {
+		GB_ASSERT(is_type_struct(elem));
+
+		Type *old_struct = base_type(elem);
+		isize field_count = old_struct->Struct.fields.count;
+
+		soa_struct = alloc_type_struct();
+		soa_struct->Struct.fields = array_make<Entity *>(heap_allocator(), field_count+1);
+		soa_struct->Struct.tags = array_make<String>(heap_allocator(), old_struct->Struct.tags.count+1);
+		soa_struct->Struct.node = array_typ_expr;
+		soa_struct->Struct.soa_kind = StructSoa_Slice;
+		soa_struct->Struct.soa_elem = elem;
+		soa_struct->Struct.soa_count = 0;
+
+		scope = create_scope(old_struct->Struct.scope->parent, ctx->allocator);
+		soa_struct->Struct.scope = scope;
+
+		for_array(i, old_struct->Struct.fields) {
+			Entity *old_field = old_struct->Struct.fields[i];
+			if (old_field->kind == Entity_Variable) {
+				Type *array_type = alloc_type_pointer(old_field->type);
+				Entity *new_field = alloc_entity_field(scope, old_field->token, array_type, false, old_field->Variable.field_src_index);
+				new_field->flags |= EntityFlag_SoaPtrField;
+				soa_struct->Struct.fields[i] = new_field;
+				add_entity(ctx->checker, scope, nullptr, new_field);
+			} else {
+				soa_struct->Struct.fields[i] = old_field;
+			}
+
+			soa_struct->Struct.tags[i] = old_struct->Struct.tags[i];
+		}
+
+		Entity *len_field = alloc_entity_field(scope, empty_token, t_int, false, cast(i32)field_count);
+		soa_struct->Struct.fields[field_count] = len_field;
+		add_entity(ctx->checker, scope, nullptr, len_field);
+		add_entity_use(ctx, nullptr, len_field);
+	}
+
+	Token token = {};
+	token.string = str_lit("Base_Type");
+	Entity *base_type_entity = alloc_entity_type_name(scope, token, elem, EntityState_Resolved);
+	add_entity(ctx->checker, scope, nullptr, base_type_entity);
+
+	add_type_info_type(ctx, soa_struct);
+
+	return soa_struct;
+}
+
 
 
 bool check_type_internal(CheckerContext *ctx, Ast *e, Type **type, Type *named_type) {
@@ -2983,14 +3085,27 @@ bool check_type_internal(CheckerContext *ctx, Ast *e, Type **type, Type *named_t
 
 					*type = alloc_type_simd_vector(count, elem);
 				} else {
-					GB_PANIC("Unhandled array type tag %.*s", LIT(name));
+					error(at->tag, "Invalid tag applied to array, got #%.*s", LIT(name));
+					*type = alloc_type_array(elem, count, generic_type);
 				}
 			} else {
 				*type = alloc_type_array(elem, count, generic_type);
 			}
 		} else {
 			Type *elem = check_type(ctx, at->elem);
-			*type = alloc_type_slice(elem);
+
+			if (at->tag != nullptr) {
+				GB_ASSERT(at->tag->kind == Ast_BasicDirective);
+				String name = at->tag->BasicDirective.name;
+				if (name == "soa") {
+					*type = make_soa_struct_slice(ctx, e, at->elem, elem);
+				} else {
+					error(at->tag, "Invalid tag applied to array, got #%.*s", LIT(name));
+					*type = alloc_type_slice(elem);
+				}
+			} else {
+				*type = alloc_type_slice(elem);
+			}
 		}
 	array_end:
 		set_base_type(named_type, *type);

+ 4 - 2
src/entity.cpp

@@ -51,8 +51,10 @@ enum EntityFlag {
 
 	EntityFlag_ImplicitReference = 1<<17, // NOTE(bill): equivalent to `const &` in C++
 
-	EntityFlag_CVarArg       = 1<<20,
-	EntityFlag_AutoCast      = 1<<21,
+	EntityFlag_SoaPtrField   = 1<<19, // to allow s.x[0] where `s.x` is a pointer rather than a slice
+
+	EntityFlag_CVarArg       = 1<<21,
+	EntityFlag_AutoCast      = 1<<22,
 };
 
 enum EntityState {

+ 204 - 23
src/ir.cpp

@@ -3447,6 +3447,73 @@ irValue *ir_insert_dynamic_map_key_and_value(irProcedure *proc, irValue *addr, T
 
 
 
+irValue *ir_soa_struct_len(irProcedure *proc, irValue *value) {
+	Type *t = base_type(ir_type(value));
+	bool is_ptr = false;
+	if (is_type_pointer(t)) {
+		is_ptr = true;
+		t = base_type(type_deref(t));
+	}
+
+
+	if (t->Struct.soa_kind == StructSoa_Fixed) {
+		return ir_const_int(t->Struct.soa_count);
+	}
+
+	GB_ASSERT(t->Struct.soa_kind == StructSoa_Slice ||
+	          t->Struct.soa_kind == StructSoa_Dynamic);
+
+	isize n = 0;
+	Type *elem = base_type(t->Struct.soa_elem);
+	if (elem->kind == Type_Struct) {
+		n = elem->Struct.fields.count;
+	} else if (elem->kind == Type_Array) {
+		n = elem->Array.count;
+	} else {
+		GB_PANIC("Unreachable");
+	}
+
+	if (is_ptr) {
+		irValue *v = ir_emit_struct_ep(proc, value, cast(i32)n);
+		return ir_emit_load(proc, v);
+	}
+	return ir_emit_struct_ev(proc, value, cast(i32)n);
+}
+
+irValue *ir_soa_struct_cap(irProcedure *proc, irValue *value) {
+	Type *t = base_type(ir_type(value));
+
+	bool is_ptr = false;
+	if (is_type_pointer(t)) {
+		is_ptr = true;
+		t = base_type(type_deref(t));
+	}
+
+	if (t->Struct.soa_kind == StructSoa_Fixed) {
+		return ir_const_int(t->Struct.soa_count);
+	}
+
+	GB_ASSERT(t->Struct.soa_kind == StructSoa_Dynamic);
+
+	isize n = 0;
+	Type *elem = base_type(t->Struct.soa_elem);
+	if (elem->kind == Type_Struct) {
+		n = elem->Struct.fields.count+1;
+	} else if (elem->kind == Type_Array) {
+		n = elem->Array.count+1;
+	} else {
+		GB_PANIC("Unreachable");
+	}
+
+	if (is_ptr) {
+		irValue *v = ir_emit_struct_ep(proc, value, cast(i32)n);
+		return ir_emit_load(proc, v);
+	}
+	return ir_emit_struct_ev(proc, value, cast(i32)n);
+}
+
+
+
 void ir_addr_store(irProcedure *proc, irAddr const &addr, irValue *value) {
 	if (addr.addr == nullptr) {
 		return;
@@ -3568,7 +3635,7 @@ void ir_addr_store(irProcedure *proc, irAddr const &addr, irValue *value) {
 		value = ir_emit_conv(proc, value, t->Struct.soa_elem);
 
 		irValue *index = addr.soa.index;
-		if (index->kind != irValue_Constant) {
+		if (index->kind != irValue_Constant || t->Struct.soa_kind != StructSoa_Fixed) {
 			Type *t = base_type(type_deref(ir_type(addr.addr)));
 			GB_ASSERT(t->kind == Type_Struct && t->Struct.soa_kind != StructSoa_None);
 			i64 count = t->Struct.soa_count;
@@ -3697,27 +3764,53 @@ irValue *ir_addr_load(irProcedure *proc, irAddr const &addr) {
 		t = base_type(t);
 		GB_ASSERT(t->kind == Type_Struct && t->Struct.soa_kind != StructSoa_None);
 		Type *elem = t->Struct.soa_elem;
-		i32 count = cast(i32)t->Struct.soa_count;
+
+		irValue *len = nullptr;
+		if (t->Struct.soa_kind == StructSoa_Fixed) {
+			len = ir_const_int(t->Struct.soa_count);
+		} else {
+			irValue *v = ir_emit_load(proc, addr.addr);
+			len = ir_soa_struct_len(proc, v);
+		}
 
 		irValue *res = ir_add_local_generated(proc, elem, true);
 
-		if (addr.soa.index->kind != irValue_Constant) {
-			irValue *len = ir_const_int(count);
+		if (addr.soa.index->kind != irValue_Constant || t->Struct.soa_kind != StructSoa_Fixed) {
 			ir_emit_bounds_check(proc, ast_token(addr.soa.index_expr), addr.soa.index, len);
 		}
 
-		for_array(i, t->Struct.fields) {
-			Entity *field = t->Struct.fields[i];
-			Type *base_type = field->type;
-			GB_ASSERT(base_type->kind == Type_Array);
-			Type *elem = base_type->Array.elem;
+		if (t->Struct.soa_kind == StructSoa_Fixed) {
+			for_array(i, t->Struct.fields) {
+				Entity *field = t->Struct.fields[i];
+				Type *base_type = field->type;
+				GB_ASSERT(base_type->kind == Type_Array);
 
+				irValue *dst = ir_emit_struct_ep(proc, res, cast(i32)i);
+				irValue *src_ptr = ir_emit_struct_ep(proc, addr.addr, cast(i32)i);
+				src_ptr = ir_emit_array_ep(proc, src_ptr, addr.soa.index);
+				irValue *src = ir_emit_load(proc, src_ptr);
+				ir_emit_store(proc, dst, src);
+			}
+		} else {
+			isize field_count = t->Struct.fields.count;
+			if (t->Struct.soa_kind == StructSoa_Slice) {
+				field_count -= 1;
+			} else if (t->Struct.soa_kind == StructSoa_Dynamic) {
+				field_count -= 3;
+			}
+			for (isize i = 0; i < field_count; i++) {
+				Entity *field = t->Struct.fields[i];
+				Type *base_type = field->type;
+				GB_ASSERT(base_type->kind == Type_Pointer);
+				Type *elem = base_type->Pointer.elem;
 
-			irValue *dst = ir_emit_struct_ep(proc, res, cast(i32)i);
-			irValue *src_ptr = ir_emit_struct_ep(proc, addr.addr, cast(i32)i);
-			src_ptr = ir_emit_array_ep(proc, src_ptr, addr.soa.index);
-			irValue *src = ir_emit_load(proc, src_ptr);
-			ir_emit_store(proc, dst, src);
+				irValue *dst = ir_emit_struct_ep(proc, res, cast(i32)i);
+				irValue *src_ptr = ir_emit_struct_ep(proc, addr.addr, cast(i32)i);
+				src_ptr = ir_emit_ptr_offset(proc, src_ptr, addr.soa.index);
+				irValue *src = ir_emit_load(proc, src_ptr);
+				src = ir_emit_load(proc, src);
+				ir_emit_store(proc, dst, src);
+			}
 		}
 
 		return ir_emit_load(proc, res);
@@ -3796,6 +3889,7 @@ irValue *ir_map_cap(irProcedure *proc, irValue *value) {
 
 
 
+
 struct irLoopData {
 	irValue *idx_addr;
 	irValue *idx;
@@ -6297,6 +6391,8 @@ irValue *ir_build_builtin_proc(irProcedure *proc, Ast *expr, TypeAndValue tv, Bu
 			return ir_dynamic_array_len(proc, v);
 		} else if (is_type_map(t)) {
 			return ir_map_len(proc, v);
+		} else if (is_type_soa_struct(t)) {
+			return ir_soa_struct_len(proc, v);
 		}
 
 		GB_PANIC("Unreachable");
@@ -6321,6 +6417,8 @@ irValue *ir_build_builtin_proc(irProcedure *proc, Ast *expr, TypeAndValue tv, Bu
 			return ir_dynamic_array_cap(proc, v);
 		} else if (is_type_map(t)) {
 			return ir_map_cap(proc, v);
+		} else if (is_type_soa_struct(t)) {
+			return ir_soa_struct_cap(proc, v);
 		}
 
 		GB_PANIC("Unreachable");
@@ -7557,15 +7655,21 @@ irAddr ir_build_addr(irProcedure *proc, Ast *expr) {
 
 					irValue *arr = ir_emit_struct_ep(proc, addr.addr, first_index);
 
-					if (addr.soa.index->kind != irValue_Constant) {
-						Type *t = base_type(type_deref(ir_type(addr.addr)));
-						GB_ASSERT(t->kind == Type_Struct && t->Struct.soa_kind != StructSoa_None);
-						i64 count = t->Struct.soa_count;
-						irValue *len = ir_const_int(count);
+					Type *t = base_type(type_deref(ir_type(addr.addr)));
+					GB_ASSERT(is_type_soa_struct(t));
+
+					if (addr.soa.index->kind != irValue_Constant || t->Struct.soa_kind != StructSoa_Fixed) {
+						irValue *len = ir_soa_struct_len(proc, addr.addr);
 						ir_emit_bounds_check(proc, ast_token(addr.soa.index_expr), addr.soa.index, len);
 					}
 
-					irValue *item = ir_emit_array_ep(proc, arr, index);
+					irValue *item = nullptr;
+
+					if (t->Struct.soa_kind == StructSoa_Fixed) {
+						item = ir_emit_array_ep(proc, arr, index);
+					} else {
+						item = ir_emit_load(proc, ir_emit_ptr_offset(proc, arr, index));
+					}
 					if (sub_sel.index.count > 0) {
 						item = ir_emit_deep_field_gep(proc, item, sub_sel);
 					}
@@ -7626,10 +7730,8 @@ irAddr ir_build_addr(irProcedure *proc, Ast *expr) {
 
 		bool deref = is_type_pointer(t);
 		t = base_type(type_deref(t));
-		if (t->kind == Type_Struct && t->Struct.soa_kind != StructSoa_None) {
+		if (is_type_soa_struct(t)) {
 			// SOA STRUCTURES!!!!
-			Type *elem = t->Struct.soa_elem;
-
 			irValue *val = ir_build_addr_ptr(proc, ie->expr);
 			if (deref) {
 				val = ir_emit_load(proc, val);
@@ -7639,6 +7741,36 @@ irAddr ir_build_addr(irProcedure *proc, Ast *expr) {
 			return ir_addr_soa_variable(val, index, ie->index);
 		}
 
+		if (ie->expr->tav.mode == Addressing_SoaVariable) {
+			// SOA Structures for slices/dynamic arrays
+			GB_ASSERT(is_type_pointer(type_of_expr(ie->expr)));
+
+			irValue *field = ir_build_expr(proc, ie->expr);
+			irValue *index = ir_build_expr(proc, ie->index);
+
+
+			if (!build_context.no_bounds_check) {
+				// TODO HACK(bill): Clean up this hack to get the length for bounds checking
+				GB_ASSERT(field->kind == irValue_Instr);
+				irInstr *instr = &field->Instr;
+
+				GB_ASSERT(instr->kind == irInstr_Load);
+				irValue *a = instr->Load.address;
+
+				GB_ASSERT(a->kind == irValue_Instr);
+				irInstr *b = &a->Instr;
+				GB_ASSERT(b->kind == irInstr_StructElementPtr);
+				irValue *base_struct = b->StructElementPtr.address;
+
+				GB_ASSERT(is_type_soa_struct(type_deref(ir_type(base_struct))));
+				irValue *len = ir_soa_struct_len(proc, base_struct);
+				ir_emit_bounds_check(proc, ast_token(ie->index), index, len);
+			}
+
+			irValue *val = ir_emit_ptr_offset(proc, field, index);
+			return ir_addr(val);
+		}
+
 		GB_ASSERT_MSG(is_type_indexable(t), "%s %s", type_to_string(t), expr_to_string(expr));
 
 		if (is_type_map(t)) {
@@ -7838,6 +7970,55 @@ irAddr ir_build_addr(irProcedure *proc, Ast *expr) {
 			ir_fill_string(proc, str, elem, new_len);
 			return ir_addr(str);
 		}
+
+
+		case Type_Struct:
+			if (is_type_soa_struct(type)) {
+				irValue *len = ir_soa_struct_len(proc, addr);
+				if (high == nullptr) high = len;
+
+				if (!no_indices) {
+					ir_emit_slice_bounds_check(proc, se->open, low, high, len, se->low != nullptr);
+				}
+
+				irValue *dst = ir_add_local_generated(proc, type_of_expr(expr), true);
+				if (type->Struct.soa_kind == StructSoa_Fixed) {
+					i32 field_count = cast(i32)type->Struct.fields.count;
+					for (i32 i = 0; i < field_count; i++) {
+						irValue *field_dst = ir_emit_struct_ep(proc, dst, i);
+						irValue *field_src = ir_emit_struct_ep(proc, addr, i);
+						field_src = ir_emit_array_ep(proc, field_src, low);
+						ir_emit_store(proc, field_dst, field_src);
+					}
+
+					irValue *len_dst = ir_emit_struct_ep(proc, dst, field_count);
+					irValue *new_len = ir_emit_arith(proc, Token_Sub, high, low, t_int);
+					ir_emit_store(proc, len_dst, new_len);
+				} else if (type->Struct.soa_kind == StructSoa_Slice) {
+					if (no_indices) {
+						ir_emit_store(proc, dst, base);
+					} else {
+						i32 field_count = cast(i32)type->Struct.fields.count - 1;
+						for (i32 i = 0; i < field_count; i++) {
+							irValue *field_dst = ir_emit_struct_ep(proc, dst, i);
+							irValue *field_src = ir_emit_struct_ev(proc, base, i);
+							field_src = ir_emit_ptr_offset(proc, field_src, low);
+							ir_emit_store(proc, field_dst, field_src);
+						}
+
+
+						irValue *len_dst = ir_emit_struct_ep(proc, dst, field_count);
+						irValue *new_len = ir_emit_arith(proc, Token_Sub, high, low, t_int);
+						ir_emit_store(proc, len_dst, new_len);
+					}
+				} else {
+					GB_PANIC("TODO #soa[dynamic]T");
+				}
+
+				return ir_addr(dst);
+			}
+			break;
+
 		}
 
 		GB_PANIC("Unknown slicable type");

+ 5 - 1
src/types.cpp

@@ -811,6 +811,7 @@ Type *alloc_type_simd_vector(i64 count, Type *elem) {
 
 
 
+
 ////////////////////////////////////////////////////////////////
 
 
@@ -1757,7 +1758,10 @@ bool are_types_identical(Type *x, Type *y) {
 			if (x->Struct.is_raw_union == y->Struct.is_raw_union &&
 			    x->Struct.fields.count == y->Struct.fields.count &&
 			    x->Struct.is_packed    == y->Struct.is_packed &&
-			    x->Struct.custom_align == y->Struct.custom_align) {
+			    x->Struct.custom_align == y->Struct.custom_align &&
+			    x->Struct.soa_kind == y->Struct.soa_kind &&
+			    x->Struct.soa_count == y->Struct.soa_count &&
+			    are_types_identical(x->Struct.soa_elem, y->Struct.soa_elem)) {
 				// TODO(bill); Fix the custom alignment rule
 				for_array(i, x->Struct.fields) {
 					Entity *xf = x->Struct.fields[i];