From e6b0fb7cdfd6537acfc0a65d36c3d9a577563aeb Mon Sep 17 00:00:00 2001 From: Judah Caruso Date: Mon, 19 May 2025 16:56:53 -0600 Subject: [PATCH] unroll macro --- _run_all_tests.jai | 4 +- macros.jai | 150 ++++++++++++++++++++++++++++++++++++++++----- math/vec.jai | 19 ++++++ 3 files changed, 157 insertions(+), 16 deletions(-) diff --git a/_run_all_tests.jai b/_run_all_tests.jai index d8fb2f3..90c5c9a 100644 --- a/_run_all_tests.jai +++ b/_run_all_tests.jai @@ -7,8 +7,8 @@ #import,file "./hash/module.jai"(true); rmath :: #import,file "./math/module.jai"(.radians, true); - dmath :: #import,file "./math/module.jai"(.degrees, true); - tmath :: #import,file "./math/module.jai"(.turns, true); + // dmath :: #import,file "./math/module.jai"(.degrees, true); + // tmath :: #import,file "./math/module.jai"(.turns, true); } diff --git a/macros.jai b/macros.jai index 6b33cd7..0003d26 100644 --- a/macros.jai +++ b/macros.jai @@ -71,25 +71,49 @@ operator | :: with; /* -Creates a named block that can exit early (via 'break' or 'continue'). + Creates a named block that can exit early (via 'break' or 'continue'). -This mostly replaces the case where you'd like to jump to -the end of a scope based on some logic within. Without -gotos, this is the next best thing. + This mostly replaces the case where you'd like to jump to + the end of a scope based on some logic within. Without + gotos, this is the next best thing. -Usage: - // within a loop - for this_block() { // this is named 'block' by default - if !moving break; - // do movement here - } - for this_block("render_player") { - if invisible break render_player; - // do rendering here - } + Usage: + // within a loop + for this_block() { // this is named 'block' by default + if !moving break; + // do movement here + } + for this_block("render_player") { + if invisible break render_player; + // do rendering here + } */ this_block :: ($name: string = Default_Name) -> Named_Block(name) #expand { return .{}; } +/* + Drop-in loop unrolling macro. + + Usage: + for unroll(5) { + // duplicates this body 5 times exactly + } + + known_size: [3]float; + for unroll(known_size) { + // duplicates this body 3 times exactly + } + + var_size: []float; + for unroll(var_size) { + // duplicates this body a set number of times, + // falling back to a regular for loop to handle + // the remaining iterations. + } +*/ +unroll :: ($count: int) -> Unrolled_Loop(count) { return .{}; } +unroll :: (arr: [$N]$T) -> Unrolled_Loop(N, T) { return .{ array = arr }; } +unroll :: (arr: []$T) -> Unrolled_Loop(-1, T) { return .{ array = arr }; } + // Call #c_call procedures inline with the current context: 'c_call(some_c_call_proc(10, 20))' c_call :: (call: Code) #expand { push_context context { #insert,scope(call) call; } @@ -118,6 +142,104 @@ for_expansion :: (v: *Named_Block, code: Code, _: For_Flags) #expand { ifx v.NAME.count != 0 v.NAME else Default_Name); } +Unrolled_Loop :: struct(N: int, T: Type = void) { + // Only store arrays when we absolutely have to. + #if T != void { + // @todo(judah): because this will only be created via 'unroll', + // should these be pointers to the underlying arrays so we don't + // pay for a copy? + #if N == -1 { + array: []T = ---; + } + else { + array: [N]T = ---; + } + } +} + +for_expansion :: (loop: *Unrolled_Loop, body: Code, flags: For_Flags, loc := #caller_location) #expand { + #assert flags & .REVERSE == 0 "reverse iteration not supported with loop unrolling (for now)"; + #assert flags & .POINTER == 0 "pointer iteration not supported with loop unrolling (for now)"; + + // runtime unroll + #if loop.N == -1 { + for i: 0..loop.array.count - 1 { + `it := #no_abc loop.array[i]; + `it_index := i; + #insert,scope(body) body; + } + + // @todo(judah): below doesn't properly handle counts not divisible by 4, + // so we end up going over the bounds of the array. + + // UNROLL_AMOUNT :: 4; // @todo(judah): make this configurable? + // unrolled_loops := loop.array.count / UNROLL_AMOUNT; + // remainder_loops := loop.array.count % UNROLL_AMOUNT; + // `it: loop.T; + // for i: 0..unrolled_loops #no_abc { + // basic.print("I: %\n", i); + // index := i * UNROLL_AMOUNT; + + // it_index = index + 0; + // it = loop.array[it_index]; + // #insert,scope(body) body; + + // it_index = index + 1; + // it = loop.array[it_index]; + // #insert,scope(body) body; + + // it_index = index + 2; + // it = loop.array[it_index]; + // #insert,scope(body) body; + + // it_index = index + 3; + // it = loop.array[it_index]; + // #insert,scope(body) body; + // } + + // after_big_loop := it_index; + // for i: 0..remainder_loops #no_abc { + // it_index = after_big_loop + i; + // it = loop.array[it_index]; + // #insert,scope(body) body; + // } + } + // compile-time unroll + else { + `it_index := 0; + + #insert -> string { + b: basic.String_Builder; + basic.print_to_builder(*b, "// inserted unrolled loop (N = %) at %:%\n", loop.N, loc.fully_pathed_filename, loc.line_number); + + if loop.T == void { + basic.append(*b, "`it: int = ---;\n"); + } + else { + basic.append(*b, "`it: loop.T = ---;\n"); + } + + for 0..loop.N - 1 { + basic.append(*b, "{\n"); + + if loop.T == void { + basic.print_to_builder(*b, "\tit = %;\n", it); + } + else { + basic.print_to_builder(*b, "\tit = #no_abc loop.array[%];\n", it); + } + + basic.print_to_builder(*b, "\tit_index = %;\n", it); + basic.append(*b, "\t#insert,scope(body) body;\n"); + basic.append(*b, "}\n"); + } + + return basic.builder_to_string(*b); + } + } +} + + pp :: #import "Program_Print"; compiler :: #import "Compiler"; diff --git a/math/vec.jai b/math/vec.jai index 75b7f98..c7c13a1 100644 --- a/math/vec.jai +++ b/math/vec.jai @@ -97,24 +97,28 @@ for_expansion :: (v: *Vec, body: Code, flags: For_Flags) #expand { operator + :: inline (l: Vec, r: Vec(l.N, l.T)) -> Vec(l.N, l.T) #no_abc { res: Vec(l.N, l.T) = ---; + // @todo(judah): unroll for N <= 4 for l res[it_index] = it + r[it_index]; return res; } operator - :: inline (l: Vec, r: Vec(l.N, l.T)) -> Vec(l.N, l.T) #no_abc { res: Vec(l.N, l.T) = ---; + // @todo(judah): unroll for N <= 4 for l res[it_index] = it - r[it_index]; return res; } operator * :: inline (l: Vec, r: Vec(l.N, l.T)) -> Vec(l.N, l.T) #no_abc { res: Vec(l.N, l.T) = ---; + // @todo(judah): unroll for N <= 4 for l res[it_index] = it * r[it_index]; return res; } operator / :: inline (l: Vec, r: Vec(l.N, l.T)) -> Vec(l.N, l.T) #no_abc { res: Vec(l.N, l.T) = ---; + // @todo(judah): unroll for N <= 4 for l res[it_index] = it / r[it_index]; return res; } @@ -165,3 +169,18 @@ v4i :: (x: $T = 0, y: T = 0, z: T = 0, w: T = 0) -> Vec(4, T) quat :: (x: float = 0, y: float = 0, z: float = 0, w: float = 0) -> Quat #expand { return .{ x = x, y = y, z = z, w = w }; } + + +#if RUN_TESTS #run,stallable { + test.run("vec2:ops", t => { + a := v2f(10.0, 1); + b := v2f(20.0, 2); + c := a + b; + + test.expect(t, c.x == 30 && c.y == 3); + }); +} + +#scope_file; + +jx :: #import,file "../module.jai";