|
| 1 | +import { describe, expect } from 'vitest'; |
| 2 | +import { abstractFloat, abstractInt } from '../../src/data/numeric.ts'; |
| 3 | +import { getBestConversion } from '../../src/tgsl/conversion.ts'; |
| 4 | +import { it } from 'typegpu-testing-utility'; |
| 5 | +import { INTERNAL_createPtr } from '../../src/data/ptr.ts'; |
| 6 | +import { d } from '../../src/index.js'; |
| 7 | + |
| 8 | +describe('getBestConversion', () => { |
| 9 | + // d.ptrPrivate(d.f32) |
| 10 | + const ptrF32 = INTERNAL_createPtr('private', d.f32, 'read-write', /* implicit */ true); |
| 11 | + |
| 12 | + it('returns result for identical types', () => { |
| 13 | + const res = getBestConversion([d.f32, d.f32]); |
| 14 | + expect(res?.targetType).toBe(d.f32); |
| 15 | + expect(res?.actions).toEqual([ |
| 16 | + { sourceIndex: 0, action: 'none' }, |
| 17 | + { sourceIndex: 1, action: 'none' }, |
| 18 | + ]); |
| 19 | + expect(res?.hasImplicitConversions).toBeFalsy(); |
| 20 | + }); |
| 21 | + |
| 22 | + it('handles abstract types automatically', () => { |
| 23 | + const resFloat = getBestConversion([abstractFloat, d.f32]); |
| 24 | + expect(resFloat?.targetType).toBe(d.f32); |
| 25 | + expect(resFloat?.actions).toEqual([ |
| 26 | + { sourceIndex: 0, action: 'none' }, |
| 27 | + { sourceIndex: 1, action: 'none' }, |
| 28 | + ]); |
| 29 | + expect(resFloat?.hasImplicitConversions).toBeFalsy(); |
| 30 | + |
| 31 | + const resInt = getBestConversion([abstractInt, d.i32]); |
| 32 | + expect(resInt?.targetType).toBe(d.i32); |
| 33 | + expect(resInt?.actions).toEqual([ |
| 34 | + { sourceIndex: 0, action: 'none' }, |
| 35 | + { sourceIndex: 1, action: 'none' }, |
| 36 | + ]); |
| 37 | + expect(resInt?.hasImplicitConversions).toBeFalsy(); |
| 38 | + |
| 39 | + const resMixed = getBestConversion([abstractInt, d.f32]); |
| 40 | + expect(resMixed?.targetType).toBe(d.f32); // abstractInt -> f32 (rank 6) |
| 41 | + expect(resMixed?.actions).toEqual([ |
| 42 | + { sourceIndex: 0, action: 'none' }, |
| 43 | + { sourceIndex: 1, action: 'none' }, |
| 44 | + ]); |
| 45 | + expect(resMixed?.hasImplicitConversions).toBeFalsy(); |
| 46 | + |
| 47 | + const resMixed2 = getBestConversion([abstractInt, abstractFloat, d.f16]); |
| 48 | + expect(resMixed2?.targetType).toBe(d.f16); // abstractInt -> f16 (rank 7), abstractFloat -> f16 (rank 2) |
| 49 | + expect(resMixed2?.actions).toEqual([ |
| 50 | + { sourceIndex: 0, action: 'none' }, |
| 51 | + { sourceIndex: 1, action: 'none' }, |
| 52 | + { sourceIndex: 2, action: 'none' }, |
| 53 | + ]); |
| 54 | + expect(resMixed2?.hasImplicitConversions).toBeFalsy(); |
| 55 | + }); |
| 56 | + |
| 57 | + it('handles implicit casts', () => { |
| 58 | + const res = getBestConversion([d.i32, d.f32]); |
| 59 | + expect(res?.targetType).toBe(d.f32); |
| 60 | + expect(res?.actions).toEqual([ |
| 61 | + { sourceIndex: 0, action: 'cast', targetType: d.f32 }, |
| 62 | + { sourceIndex: 1, action: 'none' }, |
| 63 | + ]); |
| 64 | + expect(res?.hasImplicitConversions).toBe(true); |
| 65 | + |
| 66 | + // Test case: [u32, f16, i32] |
| 67 | + // Potential targets (from input): u32, f16, i32 |
| 68 | + // Preference: f32(0) > f16(1) > i32(2) > u32(3) |
| 69 | + // |
| 70 | + // Target f16 (pref 1): |
| 71 | + // u32 (3) -> f16 (1): dest < src => rank 10 |
| 72 | + // f16 (1) -> f16 (1): rank 0 |
| 73 | + // i32 (2) -> f16 (1): dest < src => rank 10 |
| 74 | + // Total Rank = 10 + 0 + 10 = 20 |
| 75 | + // |
| 76 | + // Target i32 (pref 2): |
| 77 | + // u32 (3) -> i32 (2): dest < src => rank 10 |
| 78 | + // f16 (1) -> i32 (2): dest >= src => rank 20 |
| 79 | + // i32 (2) -> i32 (2): rank 0 |
| 80 | + // Total Rank = 10 + 20 + 0 = 30 |
| 81 | + // |
| 82 | + // Target u32 (pref 3): |
| 83 | + // u32 (3) -> u32 (3): rank 0 |
| 84 | + // f16 (1) -> u32 (3): dest >= src => rank 20 |
| 85 | + // i32 (2) -> u32 (3): dest >= src => rank 20 |
| 86 | + // Total Rank = 0 + 20 + 20 = 40 |
| 87 | + // |
| 88 | + // Lowest rank is 20 for target f16. |
| 89 | + const res2Result = getBestConversion([d.u32, d.f16, d.i32]); |
| 90 | + expect(res2Result?.targetType).toBe(d.f16); |
| 91 | + expect(res2Result?.actions).toEqual([ |
| 92 | + // Order corresponds to input [u32, f16, i32] |
| 93 | + { sourceIndex: 0, action: 'cast', targetType: d.f16 }, // u32 -> f16 |
| 94 | + { sourceIndex: 1, action: 'none' }, // f16 -> f16 |
| 95 | + { sourceIndex: 2, action: 'cast', targetType: d.f16 }, // i32 -> f16 |
| 96 | + ]); |
| 97 | + expect(res2Result?.hasImplicitConversions).toBe(true); |
| 98 | + }); |
| 99 | + |
| 100 | + it('handles pointer dereferencing', () => { |
| 101 | + const res = getBestConversion([ptrF32, d.f32]); |
| 102 | + expect(res?.targetType).toBe(d.f32); |
| 103 | + expect(res?.actions).toEqual([ |
| 104 | + { sourceIndex: 0, action: 'deref' }, |
| 105 | + { sourceIndex: 1, action: 'none' }, |
| 106 | + ]); |
| 107 | + expect(res?.hasImplicitConversions).toBeFalsy(); |
| 108 | + |
| 109 | + const res2 = getBestConversion([ptrF32, d.i32, d.f32]); // Target f32: deref, cast, none |
| 110 | + expect(res2?.targetType).toBe(d.f32); |
| 111 | + expect(res2?.actions).toEqual([ |
| 112 | + { sourceIndex: 0, action: 'deref' }, |
| 113 | + { sourceIndex: 1, action: 'cast', targetType: d.f32 }, // Implicitly derefs then casts |
| 114 | + { sourceIndex: 2, action: 'none' }, |
| 115 | + ]); |
| 116 | + expect(res2?.hasImplicitConversions).toBe(true); // Because of the cast |
| 117 | + }); |
| 118 | + |
| 119 | + it('returns undefined for incompatible types', () => { |
| 120 | + expect(getBestConversion([d.f32, d.vec2f])).toBeUndefined(); |
| 121 | + expect(getBestConversion([d.struct({ a: d.f32 }), d.f32])).toBeUndefined(); |
| 122 | + }); |
| 123 | + |
| 124 | + it('respects targetTypes restriction', () => { |
| 125 | + // abstractInt -> i32 (rank 3), u32 (rank 4), f32 (rank 6), f16 (rank 7) |
| 126 | + // i32 -> i32 (rank 0) |
| 127 | + // Common types without restriction: i32 |
| 128 | + // Restrict to f32: |
| 129 | + const res = getBestConversion([abstractInt, d.i32], [d.f32]); |
| 130 | + expect(res?.targetType).toBe(d.f32); |
| 131 | + expect(res?.actions).toEqual([ |
| 132 | + { sourceIndex: 0, action: 'none' }, // abstractInt -> f32 is auto |
| 133 | + { sourceIndex: 1, action: 'cast', targetType: d.f32 }, // i32 -> f32 is cast |
| 134 | + ]); |
| 135 | + expect(res?.hasImplicitConversions).toBe(true); |
| 136 | + |
| 137 | + // Restrict to incompatible type |
| 138 | + const resFail = getBestConversion([abstractInt, d.i32], [d.vec2f]); |
| 139 | + expect(resFail).toBeUndefined(); |
| 140 | + |
| 141 | + // Restrict to a type requiring implicit conversion for all |
| 142 | + const resImplicit = getBestConversion([d.i32, d.u32], [d.f32]); |
| 143 | + expect(resImplicit?.targetType).toBe(d.f32); |
| 144 | + expect(resImplicit?.actions).toEqual([ |
| 145 | + { sourceIndex: 0, action: 'cast', targetType: d.f32 }, |
| 146 | + { sourceIndex: 1, action: 'cast', targetType: d.f32 }, |
| 147 | + ]); |
| 148 | + expect(resImplicit?.hasImplicitConversions).toBe(true); |
| 149 | + }); |
| 150 | + |
| 151 | + it('can restrict abstractFloat to u32', () => { |
| 152 | + const res = getBestConversion([abstractFloat], [d.u32]); |
| 153 | + expect(res).toBeDefined(); |
| 154 | + expect(res?.targetType).toBe(d.u32); |
| 155 | + expect(res?.actions).toEqual([{ sourceIndex: 0, action: 'cast', targetType: d.u32 }]); |
| 156 | + expect(res?.hasImplicitConversions).toBe(true); |
| 157 | + }); |
| 158 | + |
| 159 | + it('handles void gracefully', () => { |
| 160 | + const resFail = getBestConversion([d.f32, d.Void]); |
| 161 | + expect(resFail).toBeUndefined(); |
| 162 | + }); |
| 163 | + |
| 164 | + it('handles void as target type gracefully', () => { |
| 165 | + const resFail = getBestConversion([d.f32], [d.Void]); |
| 166 | + expect(resFail).toBeUndefined(); |
| 167 | + }); |
| 168 | + |
| 169 | + // TODO(#2519): This would require multiple passes of the conversion algorithm - maybe something to consider in the future |
| 170 | + // it('handles types needing deref and cast', () => { |
| 171 | + // const res = getBestConversion([ptrI32, f32]); |
| 172 | + // expect(res?.targetType).toBe(f32); |
| 173 | + // expect(res?.actions).toEqual([ |
| 174 | + // { sourceIndex: 0, action: 'cast', targetType: f32 }, // Implicit deref + cast |
| 175 | + // { sourceIndex: 1, action: 'none' }, |
| 176 | + // ]); |
| 177 | + // expect(res?.hasImplicitConversions).toBe(true); |
| 178 | + // }); |
| 179 | +}); |
0 commit comments