Skip to content

Commit 7e6dbd6

Browse files
committed
wasm: Use free-lists for unused locals
When a local is no longer needed (for instance, it was used as a temporary during arithmetic), it can be appended to one of the typed freelists. This allows us to re-use locals and therefore require less locals, reducing the binary size, as well as runtime initialization.
1 parent 9f1f60f commit 7e6dbd6

File tree

1 file changed

+47
-2
lines changed

1 file changed

+47
-2
lines changed

src/arch/wasm/CodeGen.zig

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -601,6 +601,21 @@ stack_size: u32 = 0,
601601
/// However, local variables or the usage of `@setAlignStack` can overwrite this default.
602602
stack_alignment: u32 = 16,
603603

604+
// For each individual Wasm valtype we store a seperate free list which
605+
// allows us to re-use locals that are no longer used. e.g. a temporary local.
606+
/// A list of indexes which represents a local of valtype `i32`.
607+
/// It is illegal to store a non-i32 valtype in this list.
608+
free_locals_i32: std.ArrayListUnmanaged(u32) = .{},
609+
/// A list of indexes which represents a local of valtype `i64`.
610+
/// It is illegal to store a non-i32 valtype in this list.
611+
free_locals_i64: std.ArrayListUnmanaged(u32) = .{},
612+
/// A list of indexes which represents a local of valtype `f32`.
613+
/// It is illegal to store a non-i32 valtype in this list.
614+
free_locals_f32: std.ArrayListUnmanaged(u32) = .{},
615+
/// A list of indexes which represents a local of valtype `f64`.
616+
/// It is illegal to store a non-i32 valtype in this list.
617+
free_locals_f64: std.ArrayListUnmanaged(u32) = .{},
618+
604619
const InnerError = error{
605620
OutOfMemory,
606621
/// An error occurred when trying to lower AIR to MIR.
@@ -781,13 +796,43 @@ fn emitWValue(self: *Self, value: WValue) InnerError!void {
781796
/// Creates one locals for a given `Type`.
782797
/// Returns a corresponding `Wvalue` with `local` as active tag
783798
fn allocLocal(self: *Self, ty: Type) InnerError!WValue {
799+
const valtype = typeToValtype(ty, self.target);
800+
switch (valtype) {
801+
.i32 => if (self.free_locals_i32.popOrNull()) |index| {
802+
return WValue{ .local = index };
803+
},
804+
.i64 => if (self.free_locals_i64.popOrNull()) |index| {
805+
return WValue{ .local = index };
806+
},
807+
.f32 => if (self.free_locals_f32.popOrNull()) |index| {
808+
return WValue{ .local = index };
809+
},
810+
.f64 => if (self.free_locals_f64.popOrNull()) |index| {
811+
return WValue{ .local = index };
812+
},
813+
}
814+
// no local was free to be re-used, so allocate a new local instead
815+
try self.locals.append(self.gpa, wasm.valtype(valtype));
784816
const initial_index = self.local_index;
785-
const valtype = genValtype(ty, self.target);
786-
try self.locals.append(self.gpa, valtype);
787817
self.local_index += 1;
788818
return WValue{ .local = initial_index };
789819
}
790820

821+
/// Marks a local as no longer being referenced and essentially allows
822+
/// us to re-use it somewhere else within the function.
823+
/// The valtype of the local is deducted by using the index of the given.
824+
/// Asserts given `WValue` is a `local`.
825+
fn freeLocal(self: *Self, value: WValue) InnerError!WValue {
826+
const index = value.local;
827+
const valtype = wasm.valtype(self.locals.items[index]);
828+
switch (valtype) {
829+
.i32 => self.free_locals_i32.append(index) catch {}, // It's ok to fail any of those, a new local can be allocated instead
830+
.i64 => self.free_locals_i64.append(index) catch {},
831+
.f32 => self.free_locals_f32.append(index) catch {},
832+
.f64 => self.free_locals_f64.append(index) catch {},
833+
}
834+
}
835+
791836
/// Generates a `wasm.Type` from a given function type.
792837
/// Memory is owned by the caller.
793838
fn genFunctype(gpa: Allocator, cc: std.builtin.CallingConvention, params: []const Type, return_type: Type, target: std.Target) !wasm.Type {

0 commit comments

Comments
 (0)