Browse Source

Finally fix signed integer conversion and printing

Ginger Bill 8 năm trước cách đây
mục cha
commit
0fcbda951a
8 tập tin đã thay đổi với 253 bổ sung84 xóa
  1. 4 4
      code/demo.odin
  2. 62 31
      core/fmt.odin
  3. 38 6
      core/strconv.odin
  4. 1 1
      src/build_settings.c
  5. 1 0
      src/check_expr.c
  6. 7 14
      src/ir.c
  7. 0 1
      src/main.c
  8. 140 27
      src/ssa.c

+ 4 - 4
code/demo.odin

@@ -9,10 +9,10 @@
 #import "sync.odin";
 
 main :: proc() {
-	x := 2;
-	y := 3;
-	z := x+y;
-	fmt.println(z);
+	a: i8 = -1;
+	fmt.println(a, cast(u64)a, cast(i64)a);
+	b: i64 = -1;
+	fmt.println(b, cast(u64)b, cast(i64)b);
 
 when false {
 	s := new_slice(int, 0, 10);

+ 62 - 31
core/fmt.odin

@@ -102,7 +102,7 @@ write_type :: proc(buf: ^[]byte, ti: ^Type_Info) {
 		default:
 			write_string(buf, info.signed ? "i" : "u");
 			fi := Fmt_Info{buf = buf};
-			fmt_int(^fi, cast(u64)(8*info.size), false, 'd');
+			fmt_int(^fi, cast(u64)(8*info.size), false, 64, 'd');
 		}
 
 	case Float:
@@ -155,7 +155,7 @@ write_type :: proc(buf: ^[]byte, ti: ^Type_Info) {
 	case Array:
 		write_string(buf, "[");
 		fi := Fmt_Info{buf = buf};
-		fmt_int(^fi, cast(u64)info.count, false, 'd');
+		fmt_int(^fi, cast(u64)info.count, false, 64, 'd');
 		write_string(buf, "]");
 		write_type(buf, info.elem);
 	case Dynamic_Array:
@@ -168,7 +168,7 @@ write_type :: proc(buf: ^[]byte, ti: ^Type_Info) {
 	case Vector:
 		write_string(buf, "[vector ");
 		fi := Fmt_Info{buf = buf};
-		fmt_int(^fi, cast(u64)info.count, false, 'd');
+		fmt_int(^fi, cast(u64)info.count, false, 64, 'd');
 		write_string(buf, "]");
 		write_type(buf, info.elem);
 
@@ -185,7 +185,7 @@ write_type :: proc(buf: ^[]byte, ti: ^Type_Info) {
 		if info.custom_align {
 			write_string(buf, "#align ");
 			fi := Fmt_Info{buf = buf};
-			fmt_int(^fi, cast(u64)info.align, false, 'd');
+			fmt_int(^fi, cast(u64)info.align, false, 64, 'd');
 			write_byte(buf, ' ');
 		}
 		write_byte(buf, '{');
@@ -435,16 +435,47 @@ fmt_write_padding :: proc(fi: ^Fmt_Info, width: int) {
 	}
 }
 
-_write_int :: proc(fi: ^Fmt_Info, u: u64, base: int, neg: bool, digits: string) {
-	if neg {
-		u = -u;
+
+is_integer_negative :: proc(u: u64, is_signed: bool, bit_size: int) -> (unsigned: u64, neg: bool) {
+	neg := false;
+	if is_signed {
+		match bit_size {
+		case 8:
+			i := cast(i8)u;
+			neg = i < 0;
+			if neg { i = -i; }
+			u = cast(u64)i;
+		case 16:
+			i := cast(i16)u;
+			neg = i < 0;
+			if neg { i = -i; }
+			u = cast(u64)i;
+		case 32:
+			i := cast(i32)u;
+			neg = i < 0;
+			if neg { i = -i; }
+			u = cast(u64)i;
+		case 64:
+			i := cast(i64)u;
+			neg = i < 0;
+			if neg { i = -i; }
+			u = cast(u64)i;
+		default:
+			panic("is_integer_negative: Unknown integer size");
+		}
 	}
+	return u, neg;
+}
+
+_write_int :: proc(fi: ^Fmt_Info, u: u64, base: int, is_signed: bool, bit_size: int, digits: string) {
+	_, neg := is_integer_negative(u, is_signed, bit_size);
+
 	BUF_SIZE :: 256;
 	if fi.width_set || fi.prec_set {
 		width := fi.width + fi.prec + 3; // 3 extra bytes for sign and prefix
 		if width > BUF_SIZE {
 			// TODO(bill):????
-			panic("_write_int buffer overrun. Width and precision too big");
+			panic("_write_int: buffer overrun. Width and precision too big");
 		}
 	}
 
@@ -475,10 +506,10 @@ _write_int :: proc(fi: ^Fmt_Info, u: u64, base: int, neg: bool, digits: string)
 
 	buf: [256]byte;
 	flags: strconv.Int_Flag;
-	if fi.hash  { flags |= strconv.Int_Flag.PREFIX; }
-	if fi.plus  { flags |= strconv.Int_Flag.PLUS; }
-	if fi.space { flags |= strconv.Int_Flag.SPACE; }
-	s := strconv.append_bits(buf[..0], u, base, neg, digits, flags);
+	if fi.hash   { flags |= strconv.Int_Flag.PREFIX; }
+	if fi.plus   { flags |= strconv.Int_Flag.PLUS; }
+	if fi.space  { flags |= strconv.Int_Flag.SPACE; }
+	s := strconv.append_bits(buf[..0], u, base, is_signed, bit_size, digits, flags);
 
 	prev_zero := fi.zero;
 	defer fi.zero = prev_zero;
@@ -493,14 +524,14 @@ fmt_rune :: proc(fi: ^Fmt_Info, r: rune) {
 	write_rune(fi.buf, r);
 }
 
-fmt_int :: proc(fi: ^Fmt_Info, u: u64, neg: bool, verb: rune) {
+fmt_int :: proc(fi: ^Fmt_Info, u: u64, is_signed: bool, bit_size: int, verb: rune) {
 	match verb {
-	case 'v': _write_int(fi, u, 10, neg, __DIGITS_LOWER);
-	case 'b': _write_int(fi, u,  2, neg, __DIGITS_LOWER);
-	case 'o': _write_int(fi, u,  8, neg, __DIGITS_LOWER);
-	case 'd': _write_int(fi, u, 10, neg, __DIGITS_LOWER);
-	case 'x': _write_int(fi, u, 16, neg, __DIGITS_LOWER);
-	case 'X': _write_int(fi, u, 16, neg, __DIGITS_UPPER);
+	case 'v': _write_int(fi, u, 10, is_signed, bit_size, __DIGITS_LOWER);
+	case 'b': _write_int(fi, u,  2, is_signed, bit_size, __DIGITS_LOWER);
+	case 'o': _write_int(fi, u,  8, is_signed, bit_size, __DIGITS_LOWER);
+	case 'd': _write_int(fi, u, 10, is_signed, bit_size, __DIGITS_LOWER);
+	case 'x': _write_int(fi, u, 16, is_signed, bit_size, __DIGITS_LOWER);
+	case 'X': _write_int(fi, u, 16, is_signed, bit_size, __DIGITS_UPPER);
 	case 'c', 'r':
 		fmt_rune(fi, cast(rune)u);
 	case 'U':
@@ -509,7 +540,7 @@ fmt_int :: proc(fi: ^Fmt_Info, u: u64, neg: bool, verb: rune) {
 			fmt_bad_verb(fi, verb);
 		} else {
 			write_string(fi.buf, "U+");
-			_write_int(fi, u, 16, false, __DIGITS_UPPER);
+			_write_int(fi, u, 16, false, bit_size, __DIGITS_UPPER);
 		}
 
 	default:
@@ -598,7 +629,7 @@ fmt_pointer :: proc(fi: ^Fmt_Info, p: rawptr, verb: rune) {
 	if !fi.hash || verb == 'v' {
 		write_string(fi.buf, "0x");
 	}
-	_write_int(fi, u, 16, false, __DIGITS_UPPER);
+	_write_int(fi, u, 16, false, 8*size_of(rawptr), __DIGITS_UPPER);
 }
 
 fmt_enum :: proc(fi: ^Fmt_Info, v: any, verb: rune) {
@@ -876,16 +907,16 @@ fmt_arg :: proc(fi: ^Fmt_Info, arg: any, verb: rune) {
 	case f32:     fmt_float(fi, cast(f64)a, 32, verb);
 	case f64:     fmt_float(fi, a, 64, verb);
 
-	case int:     fmt_int(fi, cast(u64)a, a < 0, verb);
-	case i8:      fmt_int(fi, cast(u64)a, a < 0, verb);
-	case i16:     fmt_int(fi, cast(u64)a, a < 0, verb);
-	case i32:     fmt_int(fi, cast(u64)a, a < 0, verb);
-	case i64:     fmt_int(fi, cast(u64)a, a < 0, verb);
-	case uint:    fmt_int(fi, cast(u64)a, false, verb);
-	case u8:      fmt_int(fi, cast(u64)a, false, verb);
-	case u16:     fmt_int(fi, cast(u64)a, false, verb);
-	case u32:     fmt_int(fi, cast(u64)a, false, verb);
-	case u64:     fmt_int(fi, cast(u64)a, false, verb);
+	case int:     fmt_int(fi, cast(u64)a, true,  8*size_of(int), verb);
+	case i8:      fmt_int(fi, cast(u64)a, true,  8, verb);
+	case i16:     fmt_int(fi, cast(u64)a, true,  16, verb);
+	case i32:     fmt_int(fi, cast(u64)a, true,  32, verb);
+	case i64:     fmt_int(fi, cast(u64)a, true,  64, verb);
+	case uint:    fmt_int(fi, cast(u64)a, false, 8*size_of(uint), verb);
+	case u8:      fmt_int(fi, cast(u64)a, false, 8, verb);
+	case u16:     fmt_int(fi, cast(u64)a, false, 16, verb);
+	case u32:     fmt_int(fi, cast(u64)a, false, 32, verb);
+	case u64:     fmt_int(fi, cast(u64)a, false, 64, verb);
 	case string:  fmt_string(fi, a, verb);
 	default:      fmt_value(fi, arg, verb);
 	}

+ 38 - 6
core/strconv.odin

@@ -25,10 +25,10 @@ append_bool :: proc(buf: []byte, b: bool) -> string {
 }
 
 append_uint :: proc(buf: []byte, u: u64, base: int) -> string {
-	return append_bits(buf, u, base, false, digits, 0);
+	return append_bits(buf, u, base, false, 8*size_of(uint), digits, 0);
 }
 append_int :: proc(buf: []byte, i: i64, base: int) -> string {
-	return append_bits(buf, cast(u64)i, base, i < 0, digits, 0);
+	return append_bits(buf, cast(u64)i, base, true, 8*size_of(int), digits, 0);
 }
 itoa :: proc(buf: []byte, i: int) -> string {
 	return append_int(buf, cast(i64)i, 10);
@@ -265,7 +265,39 @@ MAX_BASE :: 32;
 immutable digits := "0123456789abcdefghijklmnopqrstuvwxyz";
 
 
-append_bits :: proc(buf: []byte, u: u64, base: int, neg: bool, digits: string, flags: Int_Flag) -> string {
+is_integer_negative :: proc(u: u64, is_signed: bool, bit_size: int) -> (unsigned: u64, neg: bool) {
+	neg := false;
+	if is_signed {
+		match bit_size {
+		case 8:
+			i := cast(i8)u;
+			neg = i < 0;
+			if neg { i = -i; }
+			u = cast(u64)i;
+		case 16:
+			i := cast(i16)u;
+			neg = i < 0;
+			if neg { i = -i; }
+			u = cast(u64)i;
+		case 32:
+			i := cast(i32)u;
+			neg = i < 0;
+			if neg { i = -i; }
+			u = cast(u64)i;
+		case 64:
+			i := cast(i64)u;
+			neg = i < 0;
+			if neg { i = -i; }
+			u = cast(u64)i;
+		default:
+			panic("is_integer_negative: Unknown integer size");
+		}
+	}
+	return u, neg;
+}
+
+
+append_bits :: proc(buf: []byte, u: u64, base: int, is_signed: bool, bit_size: int, digits: string, flags: Int_Flag) -> string {
 	is_pow2 :: proc(x: i64) -> bool {
 		if (x <= 0) {
 			return false;
@@ -279,9 +311,9 @@ append_bits :: proc(buf: []byte, u: u64, base: int, neg: bool, digits: string, f
 
 	a: [65]byte;
 	i := a.count;
-	if neg {
-		u = -u;
-	}
+
+	neg: bool;
+	u, neg = is_integer_negative(u, is_signed, bit_size);
 
 	if is_pow2(cast(i64)base) {
 		b := cast(u64)base;

+ 1 - 1
src/build_settings.c

@@ -209,7 +209,7 @@ String get_fullpath_core(gbAllocator a, String path) {
 void init_build_context(void) {
 	BuildContext *bc = &build_context;
 	bc->ODIN_VENDOR  = str_lit("odin");
-	bc->ODIN_VERSION = str_lit("0.1.1");
+	bc->ODIN_VERSION = str_lit("0.1.2");
 	bc->ODIN_ROOT    = odin_root_dir();
 
 #if defined(GB_SYSTEM_WINDOWS)

+ 1 - 0
src/check_expr.c

@@ -1917,6 +1917,7 @@ void check_comparison(Checker *c, Operand *x, Operand *y, TokenKind op) {
 		} else {
 			x->mode = Addressing_Value;
 
+
 			update_expr_type(c, x->expr, default_type(x->type), true);
 			update_expr_type(c, y->expr, default_type(y->type), true);
 		}

+ 7 - 14
src/ir.c

@@ -2261,22 +2261,18 @@ irValue *ir_emit_conv(irProcedure *proc, irValue *value, Type *t) {
 		i64 sz = type_size_of(proc->module->allocator, src);
 		i64 dz = type_size_of(proc->module->allocator, dst);
 		irConvKind kind = irConv_trunc;
-		if (dz == sz) {
+
+		if (dz < sz) {
+			kind = irConv_trunc;
+		} else if (dz == sz) {
 			// NOTE(bill): In LLVM, all integers are signed and rely upon 2's compliment
 			// NOTE(bill): Copy the value just for type correctness
 			kind = irConv_bitcast;
 		} else if (dz > sz) {
-			kind = irConv_zext;
-
-			// TODO(bill): figure out the rules completely
-			bool ss = !is_type_unsigned(src);
-			bool ds = !is_type_unsigned(dst);
-			if (ss && ds) {
-				kind = irConv_sext;
-			} else if (ss) {
-				kind = irConv_sext;
+			if (is_type_unsigned(src)) {
+				kind = irConv_zext; // zero extent
 			} else {
-				kind = irConv_zext;
+				kind = irConv_sext; // sign extent
 			}
 		}
 
@@ -4900,9 +4896,6 @@ void ir_build_stmt_internal(irProcedure *proc, AstNode *node) {
 
 
 				for_array(i, inits) {
-					if (lvals.e[i].addr == NULL) {
-						continue;
-					}
 					ir_addr_store(proc, lvals.e[i], inits.e[i]);
 				}
 			}

+ 0 - 1
src/main.c

@@ -4,7 +4,6 @@ extern "C" {
 
 #define USE_CUSTOM_BACKEND false
 
-
 #include "common.c"
 #include "timings.c"
 #include "build_settings.c"

+ 140 - 27
src/ssa.c

@@ -6,7 +6,7 @@ typedef struct ssaProc     ssaProc;
 typedef struct ssaEdge     ssaEdge;
 typedef struct ssaRegister ssaRegister;
 typedef enum ssaBlockKind  ssaBlockKind;
-typedef enum ssaBranchPredicition ssaBranchPredicition;
+typedef enum ssaBranchPrediction ssaBranchPrediction;
 
 String ssa_mangle_name(ssaModule *m, String path, Entity *e);
 
@@ -22,6 +22,8 @@ enum ssaOp {
 
 	ssaOp_Unknown,
 
+	ssaOp_Comment, // Does nothing
+
 	ssaOp_SP,    // Stack Pointer
 	ssaOp_SB,    // Stack Base
 	ssaOp_Addr,  // Address of something - special rules for certain types when loading and storing (e.g. Maps)
@@ -38,6 +40,7 @@ enum ssaOp {
 	ssaOp_ArrayIndex, // Index for a fixed array
 	ssaOp_PtrIndex,   // Index for a struct/tuple/etc
 	ssaOp_OffsetPtr,
+	ssaOp_ValueIndex, // Extract for a value from a register
 
 	ssaOp_Phi,
 	ssaOp_Copy,
@@ -309,7 +312,7 @@ enum ssaBlockKind {
 	ssaBlock_Count,
 };
 
-enum ssaBranchPredicition {
+enum ssaBranchPrediction {
 	ssaBranch_Unknown  = 0,
 	ssaBranch_Likely   = +1,
 	ssaBranch_Unlikely = -1,
@@ -332,7 +335,7 @@ struct ssaBlock {
 	ssaProc *            proc; // Containing procedure
 
 	// Likely branch direction
-	ssaBranchPredicition likeliness;
+	ssaBranchPrediction likeliness;
 
 	ssaValueArray values;
 	ssaEdgeArray  preds;
@@ -428,7 +431,7 @@ void ssa_add_to_edge(ssaBlock *b, ssaBlock *c) {
 	ssaEdge s = {c, j};
 	ssaEdge p = {b, i};
 	array_add(&b->succs, s);
-	array_add(&b->preds, p);
+	array_add(&c->preds, p);
 }
 
 
@@ -522,6 +525,24 @@ bool ssa_is_blank_ident(AstNode *node) {
 }
 
 
+typedef enum ssaAddrKind {
+	ssaAddr_Default,
+} ssaAddrKind;
+
+typedef struct ssaAddr {
+	ssaValue *  addr;
+	ssaAddrKind kind;
+} ssaAddr;
+
+ssaAddr ssa_addr(ssaValue *v) {
+	if (v != NULL) {
+		GB_ASSERT(is_type_pointer(v->type));
+	}
+	ssaAddr addr = {0};
+	addr.addr = v;
+	return addr;
+}
+
 
 
 ssaProc *ssa_new_proc(ssaModule *m, String name, Entity *entity, DeclInfo *decl_info) {
@@ -537,7 +558,9 @@ ssaProc *ssa_new_proc(ssaModule *m, String name, Entity *entity, DeclInfo *decl_
 	return p;
 }
 
-ssaValue *ssa_add_local(ssaProc *p, Entity *e, AstNode *expr) {
+ssaAddr ssa_add_local(ssaProc *p, Entity *e, AstNode *expr) {
+	ssaAddr result = {0};
+
 	Type *t = make_type_pointer(p->module->allocator, e->type);
 	ssaValue *local = ssa_new_value0(p->entry, ssaOp_Local, t);
 	map_ssa_value_set(&p->values,         hash_pointer(e), local);
@@ -545,18 +568,21 @@ ssaValue *ssa_add_local(ssaProc *p, Entity *e, AstNode *expr) {
 
 	ssaValue *addr = ssa_new_value1(p->curr_block, ssaOp_Addr, local->type, local);
 	ssa_new_value1(p->curr_block, ssaOp_Zero, t, addr);
-	return addr;
+	result.addr = addr;
+	return result;
 }
-ssaValue *ssa_add_local_for_ident(ssaProc *p, AstNode *name) {
+ssaAddr ssa_add_local_for_ident(ssaProc *p, AstNode *name) {
+	ssaAddr result = {0};
+
 	Entity **found = map_entity_get(&p->module->info->definitions, hash_pointer(name));
 	if (found) {
 		Entity *e = *found;
 		return ssa_add_local(p, e, name);
 	}
-	return NULL;
+	return result;
 }
 
-ssaValue *ssa_add_local_generated(ssaProc *p, Type *t) {
+ssaAddr ssa_add_local_generated(ssaProc *p, Type *t) {
 	GB_ASSERT(t != NULL);
 
 	Scope *scope = NULL;
@@ -567,8 +593,9 @@ ssaValue *ssa_add_local_generated(ssaProc *p, Type *t) {
 	return ssa_add_local(p, e, NULL);
 }
 
-
-
+void ssa_emit_comment(ssaProc *p, String s) {
+	ssa_new_value0v(p->curr_block, ssaOp_Comment, NULL, exact_value_string(s));
+}
 
 
 
@@ -576,9 +603,14 @@ ssaValue *ssa_add_local_generated(ssaProc *p, Type *t) {
 void ssa_build_stmt(ssaProc *p, AstNode *node);
 void ssa_build_stmt_list(ssaProc *p, AstNodeArray nodes);
 
+void ssa_addr_store(ssaProc *p, ssaAddr addr, ssaValue *value) {
+	if (addr.addr == NULL) {
+		return;
+	}
+}
 
-ssaValue *ssa_build_addr(ssaProc *p, AstNode *node) {
-	return NULL;
+ssaAddr ssa_build_addr(ssaProc *p, AstNode *node) {
+	return ssa_addr(NULL);
 }
 
 ssaValue *ssa_build_expr(ssaProc *p, AstNode *expr) {
@@ -633,10 +665,6 @@ void ssa_build_stmt_list(ssaProc *p, AstNodeArray nodes) {
 	}
 }
 
-void ssa_addr_store(ssaProc *p, ssaValue *addr, ssaValue *value) {
-
-}
-
 
 ssaValue *ssa_emit_struct_ep(ssaProc *p, ssaValue *ptr, i32 index) {
 	GB_ASSERT(ptr->type != NULL);
@@ -659,6 +687,13 @@ void ssa_build_stmt(ssaProc *p, AstNode *node) {
 		ssa_build_stmt_list(p, bs->stmts);
 	case_end;
 
+	case_ast_node(us, UsingStmt, node);
+		AstNode *decl = unparen_expr(us->node);
+		if (decl->kind == AstNode_ValueDecl) {
+			ssa_build_stmt(p, decl);
+		}
+	case_end;
+
 	case_ast_node(vd, ValueDecl, node);
 		if (vd->is_var) {
 			ssaModule *m = p->module;
@@ -671,14 +706,14 @@ void ssa_build_stmt(ssaProc *p, AstNode *node) {
 					}
 				}
 			} else {
-				ssaValueArray lvals = {0};
-				ssaValueArray inits = {0};
+				Array(ssaAddr) lvals = {0};
+				ssaValueArray  inits = {0};
 				array_init_reserve(&lvals, m->tmp_allocator, vd->names.count);
 				array_init_reserve(&inits, m->tmp_allocator, vd->names.count);
 
 				for_array(i, vd->names) {
 					AstNode *name = vd->names.e[i];
-					ssaValue *lval = NULL;
+					ssaAddr lval = ssa_addr(NULL);
 					if (!ssa_is_blank_ident(name)) {
 						lval = ssa_add_local_for_ident(p, name);
 					}
@@ -688,11 +723,10 @@ void ssa_build_stmt(ssaProc *p, AstNode *node) {
 
 				for_array(i, vd->values) {
 					ssaValue *init = ssa_build_expr(p, vd->values.e[i]);
-					if (init == NULL || init->type == NULL) {
-						// TODO(bill): remove this
+					if (init == NULL) { // TODO(bill): remove this
 						continue;
 					}
-					Type *t = base_type(init->type);
+					Type *t = type_deref(init->type);
 					if (init->op == ssaOp_Addr && t->kind == Type_Tuple) {
 						for (isize i = 0; i < t->Tuple.variable_count; i++) {
 							Entity *e = t->Tuple.variables[i];
@@ -704,11 +738,7 @@ void ssa_build_stmt(ssaProc *p, AstNode *node) {
 					}
 				}
 
-
 				for_array(i, inits) {
-					if (lvals.e[i] == NULL) {
-						continue;
-					}
 					ssa_addr_store(p, lvals.e[i], inits.e[i]);
 				}
 			}
@@ -716,6 +746,89 @@ void ssa_build_stmt(ssaProc *p, AstNode *node) {
 			gb_temp_arena_memory_end(tmp);
 		}
 	case_end;
+
+	case_ast_node(as, AssignStmt, node);
+		ssa_emit_comment(p, str_lit("AssignStmt"));
+
+		ssaModule *m = p->module;
+		gbTempArenaMemory tmp = gb_temp_arena_memory_begin(&m->tmp_arena);
+
+		switch (as->op.kind) {
+		case Token_Eq: {
+			Array(ssaAddr) lvals = {0};
+			array_init(&lvals, m->tmp_allocator);
+
+			for_array(i, as->lhs) {
+				AstNode *lhs = as->lhs.e[i];
+				ssaAddr lval = {0};
+				if (!ssa_is_blank_ident(lhs)) {
+					lval = ssa_build_addr(p, lhs);
+				}
+				array_add(&lvals, lval);
+			}
+
+			if (as->lhs.count == as->rhs.count) {
+				if (as->lhs.count == 1) {
+					AstNode *rhs = as->rhs.e[0];
+					ssaValue *init = ssa_build_expr(p, rhs);
+					ssa_addr_store(p, lvals.e[0], init);
+				} else {
+					ssaValueArray inits;
+					array_init_reserve(&inits, m->tmp_allocator, lvals.count);
+
+					for_array(i, as->rhs) {
+						ssaValue *init = ssa_build_expr(p, as->rhs.e[i]);
+						array_add(&inits, init);
+					}
+
+					for_array(i, inits) {
+						ssa_addr_store(p, lvals.e[i], inits.e[i]);
+					}
+				}
+			} else {
+				ssaValueArray inits;
+				array_init_reserve(&inits, m->tmp_allocator, lvals.count);
+
+				for_array(i, as->rhs) {
+					ssaValue *init = ssa_build_expr(p, as->rhs.e[i]);
+					Type *t = type_deref(init->type);
+					// TODO(bill): refactor for code reuse as this is repeated a bit
+					if (init->op == ssaOp_Addr && t->kind == Type_Tuple) {
+						for (isize i = 0; i < t->Tuple.variable_count; i++) {
+							Entity *e = t->Tuple.variables[i];
+							ssaValue *v = ssa_emit_struct_ep(p, init, i);
+							array_add(&inits, v);
+						}
+					} else {
+						array_add(&inits, init);
+					}
+				}
+
+				for_array(i, inits) {
+					ssa_addr_store(p, lvals.e[i], inits.e[i]);
+				}
+			}
+		} break;
+
+		default: {
+			GB_PANIC("TODO(bill): assign operations");
+			// NOTE(bill): Only 1 += 1 is allowed, no tuples
+			// +=, -=, etc
+			// i32 op = cast(i32)as->op.kind;
+			// op += Token_Add - Token_AddEq; // Convert += to +
+			// ssaAddr lhs = ssa_build_addr(p, as->lhs.e[0]);
+			// ssaValue *value = ssa_build_expr(p, as->rhs.e[0]);
+			// ssa_build_assign_op(p, lhs, value, cast(TokenKind)op);
+		} break;
+		}
+
+		gb_temp_arena_memory_end(tmp);
+	case_end;
+
+	case_ast_node(es, ExprStmt, node);
+		// NOTE(bill): No need to use return value
+		ssa_build_expr(p, es->expr);
+	case_end;
 	}
 }