diff --git a/lzw.c b/lzw.c
index f9d103fff47e0615d8f15e2cb5f5b27b7b19b4d9..b35f32919781cefe16e7d1ac9cd86e0a88131207 100644
--- a/lzw.c
+++ b/lzw.c
@@ -25,7 +25,7 @@ typedef struct LZW_context_S
 	 * Table for storing sequences represented by an LZW code
 	 * 0-255, and 256 are special, representing literals, and the reset code. We could explicitly pre-fill them, but it's probably not necessary.
 	 */
-	HBytes * lzw_code_table[4096];
+	HBytes lzw_code_table[4096];
 
 	/*
 	 * Holds the next expected LZW code. We also use this for telling LZW_9bitcodeword, LZW_10bitcodeword, etc. apart. Parses fail if "next" is larger than what can be represented on that many bits.
@@ -48,16 +48,12 @@ LZW_clear_table(LZW_context_T *ctx)
 	 */
 	for(int i = 258; i < ctx->next; ++i)
 	{
-		HBytes * sequence = ctx->lzw_code_table[i];
-		if(sequence != NULL)
-		{
-			/*
-			 * Assumption: only the HBytes in the LZW table refer to the particular uint8_t arrays we're freeing.
-			 */
-			free((uint8_t *)sequence->token);
-			free(sequence);
-		}
-		ctx->lzw_code_table[i] = NULL;
+		HBytes sequence = ctx->lzw_code_table[i];
+		/*
+		 * Assumption: only the HBytes in the LZW table refer to the
+		 * particular uint8_t arrays we're freeing.
+		 */
+		free((uint8_t *)sequence.token);
 	}
 }
 
@@ -68,10 +64,7 @@ LZW_clear_table(LZW_context_T *ctx)
 static void
 lzw_table_insert(LZW_context_T *ctx, uint8_t *token, size_t token_len)
 {
-	HBytes * next_entry = malloc(sizeof(HBytes));
-	next_entry->token = token;
-	next_entry->len = token_len;
-	ctx->lzw_code_table[ctx->next] = next_entry;
+	ctx->lzw_code_table[ctx->next] = (HBytes){token, token_len};
 	ctx->next++;
 }
 
@@ -160,8 +153,8 @@ validate_output(HParseResult *p, void *u)
 static HParsedToken*
 act_output(const HParseResult *p, void *u)
 {
-	HBytes * code_str;
-	HBytes * last_str;
+	HBytes code_str;
+	HBytes last_str;
 	uint64_t code = H_CAST_UINT(p->ast);
 	uint8_t * output_token;
 	uint8_t * next_entry_token;
@@ -182,8 +175,7 @@ act_output(const HParseResult *p, void *u)
 	 * This is what we'll wrap in a HBytes for returning.
 	 */
 	code_str = ctx->lzw_code_table[code];
-	assert(code_str != NULL);
-	assert(code_str->len > 0);
+	assert(code_str.len > 0);
 
 	/*
 	 * Fill in the missing last character of a previously assigned code,
@@ -191,8 +183,8 @@ act_output(const HParseResult *p, void *u)
 	 */
 	if (ctx->next > 258) {
 		last_str = ctx->lzw_code_table[ctx->next - 1];
-		((uint8_t *)last_str->token)[last_str->len - 1] =
-		    code_str->token[0];
+		((uint8_t *)last_str.token)[last_str.len - 1] =
+		    code_str.token[0];
 		    // XXX casting away the const. we know what we're doing.
 		    // could avoid HBytes by using our own struct but come on.
 		    // a different design might avoid byte arrays in the
@@ -204,16 +196,16 @@ act_output(const HParseResult *p, void *u)
 	 * Update the dictionary with a new entry that is missing the last
 	 * character which we will only learn when we process the next code.
 	 */
-	next_entry_token = calloc(code_str->len + 1, sizeof(uint8_t));
-	memcpy(next_entry_token, code_str->token, code_str->len);
-	lzw_table_insert(ctx, next_entry_token, code_str->len + 1);
+	next_entry_token = calloc(code_str.len + 1, sizeof(uint8_t));
+	memcpy(next_entry_token, code_str.token, code_str.len);
+	lzw_table_insert(ctx, next_entry_token, code_str.len + 1);
 
 	/*
 	 * Return a copy of the output.
 	 */
-	output_token = h_arena_malloc(p->arena, code_str->len);
-	memcpy(output_token, code_str->token, code_str->len);
-	return H_MAKE_BYTES(output_token, code_str->len);
+	output_token = h_arena_malloc(p->arena, code_str.len);
+	memcpy(output_token, code_str.token, code_str.len);
+	return H_MAKE_BYTES(output_token, code_str.len);
 }
 
 static HParsedToken*
@@ -264,7 +256,7 @@ act_lzwdata(const HParseResult *p, void *u)
 	/*for(int i = 258; i < ctx->next; ++i) // DEBUG
 	{
 		fprintf(debug, "i: %u, str: ", i);
-		fwrite(ctx->lzw_code_table[i]->token, ctx->lzw_code_table[i]->len, 1, debug);
+		fwrite(ctx->lzw_code_table[i].token, ctx->lzw_code_table[i].len, 1, debug);
 		fprintf(debug, "\n");
 	}
 	fflush(debug); // DEBUG */
@@ -283,12 +275,8 @@ void init_LZW_parser()
 	{
 		uint8_t *token = malloc(sizeof(uint8_t));
 		*token = i;
-		HBytes *lit = malloc(sizeof(HBytes));
-		lit->token = token;
-		lit->len = 1;
-		context->lzw_code_table[i] = lit;
+		context->lzw_code_table[i] = (HBytes){token, 1};
 	}
-	context->lzw_code_table[256] = calloc(1, sizeof(HBytes));
 	context->earlychange = 1;
 
 	H_VDRULE(code9,		h_bits(9, false), context);
@@ -321,15 +309,12 @@ HParseResult* parse_LZW_data(const uint8_t* input, size_t length)
 
 void init_LZW_context(int earlychange)
 {
-	for(int i = 258; i < 4096; ++i)
-	{
-		if(context->lzw_code_table[i] != NULL)
-		{
-			free((uint8_t *) context->lzw_code_table[i]->token); // These can be freed without issue, because HParsedTokens containing them have separate deep copies
-			free(context->lzw_code_table[i]);
-		}
-		context->lzw_code_table[i] = NULL;
-	}
+	/*
+	 * Only the HBytes in the LZW table refer to the strings we're freeing,
+	 * since the HParsedTokens created from them get their own copies.
+	 */
+	for(int i = 258; i < context->next; ++i)
+		free((uint8_t *) context->lzw_code_table[i].token);
 	context->next = 258;
 	context->earlychange = earlychange;
 }