authorgravatar for me@paperdave.netdave caruso <me@paperdave.net> 2024-09-20 00:13:45-07:00
committergravatar for me@paperdave.netdave caruso <me@paperdave.net> 2024-09-20 00:13:45-07:00
log23604fa655b6a34c07acc49cf9b91f93193f705b
tree2450f4e6a52a62e3513a43dad7e47de6ad44f401
parent9f21c3c63e5825010c82063bee7b3d9164d6b8c2
signature Commit is signed but in an unrecognized format.

fix: coerce results when `if` expression branches merge


4 files changed, 114 insertions(+), 90 deletions(-)

CHANGELOG.md+1
...@@ -2,6 +2,7 @@...@@ -2,6 +2,7 @@
22
3- feat: allow any expression as a function body3- feat: allow any expression as a function body
4- fix: coerce `break` operands and implicit breaks to result type4- fix: coerce `break` operands and implicit breaks to result type
5- fix: coerce results when `if` expression branches merge
56
6## 0.1.67## 0.1.6
78
src/core/Comp.zig+1-2
...@@ -242,7 +242,6 @@ pub fn runCodegenJob(c: *Comp, value: InternPool.Value, air_const: Air) !void {...@@ -242,7 +242,6 @@ pub fn runCodegenJob(c: *Comp, value: InternPool.Value, air_const: Air) !void {
242 defer arena_state.deinit();242 defer arena_state.deinit();
243 const arena = arena_state.allocator();243 const arena = arena_state.allocator();
244244
245 // TODO: put all this work on another thread
246 if (c.options.dump_ir) {245 if (c.options.dump_ir) {
247 air_const.dumpToWriter(&c.intern_pool, c.out) catch {};246 air_const.dumpToWriter(&c.intern_pool, c.out) catch {};
248 }247 }
...@@ -667,7 +666,7 @@ pub fn indexTegModule(comp: *Comp, file: *File, module_index: Teg.Inst.Index) !N...@@ -667,7 +666,7 @@ pub fn indexTegModule(comp: *Comp, file: *File, module_index: Teg.Inst.Index) !N
667666
668const Options = struct {667const Options = struct {
669 dump_ir: bool = false,668 dump_ir: bool = false,
670 verbose_sema: bool = false,669 verbose_sema: bool = true,
671};670};
672671
673/// The compiler backend is stored separately from the compilation, and672/// The compiler backend is stored separately from the compilation, and
src/core/Sema.zig+111-87
...@@ -852,7 +852,7 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag)...@@ -852,7 +852,7 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag)
852852
853 const initial_value = s.getRef(data.operand, .marked);853 const initial_value = s.getRef(data.operand, .marked);
854854
855 const runtime_known = try s.ensureValueRuntime(s.lazyNode(.operand), initial_value);855 const runtime_known = try s.ensureRefRuntime(s.lazyNode(.operand), initial_value);
856856
857 return try s.addRef(.memory, .{857 return try s.addRef(.memory, .{
858 .init = runtime_known,858 .init = runtime_known,
...@@ -1069,19 +1069,22 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag)...@@ -1069,19 +1069,22 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag)
10691069
1070 assert(s.frame.mode == .run_time);1070 assert(s.frame.mode == .run_time);
10711071
1072 const yes = s.getRef(if_data.yes, .no_mark);1072 var yes = s.getRef(if_data.yes, .no_mark);
1073 const no = s.getRef(if_data.no, .no_mark);1073 var no = s.getRef(if_data.no, .no_mark);
10741074
1075 const union_type = s.unionType(s.typeOfRef(yes), s.typeOfRef(no));1075 if (yes.value()) |val| yes = (try s.ensureValueRuntime(s.lazyNode(.lhs), val)).toAir();
1076 if (no.value()) |val| no = (try s.ensureValueRuntime(s.lazyNode(.rhs), val)).toAir();
10761077
1077 // const yes_coerced = try s.coerceValue(s.lazyNode(.lhs), yes, union_type);1078 const union_type = s.peerTypeResolution(&.{ s.typeOfRef(yes), s.typeOfRef(no) });
1078 // const no_coerced = try s.coerceValue(s.lazyNode(.rhs), no, union_type);1079
1080 const yes_coerced = try s.coerceBranch(s.lazyNode(.lhs), yes, union_type);
1081 const no_coerced = try s.coerceBranch(s.lazyNode(.rhs), no, union_type);
10791082
1080 s.inst_map.add(s.frame.currentIndex(), try s.addRef(.@"if", .{1083 s.inst_map.add(s.frame.currentIndex(), try s.addRef(.@"if", .{
1081 .cond = s.getRef(if_data.cond, .no_mark),1084 .cond = s.getRef(if_data.cond, .no_mark),
1082 .data = try s.func.?.extra.append(s.gpa, Air.If.Data, .{1085 .data = try s.func.?.extra.append(s.gpa, Air.If.Data, .{
1083 .yes = yes,1086 .yes = yes_coerced,
1084 .no = no,1087 .no = no_coerced,
1085 .union_ty = union_type,1088 .union_ty = union_type,
1086 }),1089 }),
1087 }));1090 }));
...@@ -1243,7 +1246,10 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag)...@@ -1243,7 +1246,10 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag)
12431246
1244 // When both branches are values, Sema has nowhere1247 // When both branches are values, Sema has nowhere
1245 // to jump to, so only one Air instruction is emitted1248 // to jump to, so only one Air instruction is emitted
1246 const union_type = s.unionType(yn[0].typeOf(s.ip), yn[1].typeOf(s.ip));1249 yn[0] = try s.ensureValueRuntime(s.lazyNode(.lhs), yn[0]);
1250 yn[1] = try s.ensureValueRuntime(s.lazyNode(.lhs), yn[1]);
1251
1252 const union_type = s.peerTypeResolution(&.{ yn[0].typeOf(s.ip), yn[1].typeOf(s.ip) });
12471253
1248 const yes_coerced = try s.coerceValue(s.lazyNode(.lhs), yn[0], union_type);1254 const yes_coerced = try s.coerceValue(s.lazyNode(.lhs), yn[0], union_type);
1249 const no_coerced = try s.coerceValue(s.lazyNode(.rhs), yn[1], union_type);1255 const no_coerced = try s.coerceValue(s.lazyNode(.rhs), yn[1], union_type);
...@@ -1403,7 +1409,7 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag)...@@ -1403,7 +1409,7 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag)
1403 try s.ensureAtRuntime(s.lazyNode(.main_node));1409 try s.ensureAtRuntime(s.lazyNode(.main_node));
14041410
1405 for (scratch.slice(), 0..) |*ref, i| {1411 for (scratch.slice(), 0..) |*ref, i| {
1406 ref.* = try s.ensureValueRuntime(s.lazyNode(.argument(@intCast(i))), ref.*);1412 ref.* = try s.ensureRefRuntime(s.lazyNode(.argument(@intCast(i))), ref.*);
1407 }1413 }
1408 }1414 }
14091415
...@@ -1814,68 +1820,84 @@ fn coerceValue(s: *Sema, src: LazyNode, value: Value, to: Type) Error!Value {...@@ -1814,68 +1820,84 @@ fn coerceValue(s: *Sema, src: LazyNode, value: Value, to: Type) Error!Value {
1814 return s.coercionError(src, from, to);1820 return s.coercionError(src, from, to);
1815}1821}
18161822
1817fn ensureValueRuntime(s: *Sema, src: LazyNode, ref: Air.Inst.Ref) Error!Air.Inst.Ref {1823fn coerceBranch(s: *Sema, src: LazyNode, ref: Air.Inst.Ref, to: Type) !Air.Inst.Ref {
1818 switch (ref.decode()) {1824 return switch (ref.decode()) {
1819 .value => |v| {1825 .index => |block| {
1820 const ty = v.typeOf(s.ip);1826 if (s.typeOfIndex(block) != to) return ref;
1821 switch (ty) {1827 @panic("TODO");
1822 // Allowed at runtime1828 },
1823 .bool,1829 .value => |value| (try s.coerceValue(src, value, to)).toAir(),
1824 .float,1830 };
1825 .int32,1831}
1826 .string,
1827 .void,
1828 .empty_tuple,
1829 => {},
1830 .numeric_literal => {
1831 const float = v.toFloat(s.ip) orelse unreachable;
1832
1833 if (!core.inInt32Range(float)) {
1834 try s.report.warn(.numeric_int_var_ambiguity, .{
1835 .src = src,
1836 });
1837 }
18381832
1839 return (try s.makeValue(.{1833fn ensureValueRuntime(s: *Sema, src: LazyNode, value: Value) Error!Value {
1840 .float = float,1834 const ty = value.typeOf(s.ip);
1841 })).toAir();1835 return switch (ty) {
1842 },1836 // Allowed at runtime
1843 .numeric_literal_array => {1837 .bool,
1844 try s.report.warn(.numeric_int_array_var_ambiguity, .{1838 .float,
1845 .src = src,1839 .int32,
1846 });1840 .string,
1847 return (try s.coerceArrayValue(src, v, .float)).toAir();1841 .void,
1848 },1842 .empty_tuple,
1849 .never_array => {1843 => value,
1850 if (v.valueData(s.ip, .array_value).get(s.ip).items_len != 0) {1844 .numeric_literal => {
1851 return s.fail(.runtime_incompatible_type, .{1845 const float = value.toFloat(s.ip) orelse unreachable;
1852 .src = src,1846
1853 .type = ty,1847 std.debug.print("wtf:{d}\n", .{float});
1854 });1848 if (core.inInt32Range(float)) {
1855 }1849 try s.report.warn(.numeric_int_var_ambiguity, .{
1856 },
1857 .type,
1858 .never,
1859 => return s.fail(.runtime_incompatible_type, .{
1860 .src = src,1850 .src = src,
1861 .type = ty,1851 });
1862 }),1852 }
1863 .@"opaque" => @panic("TODO: represent comptime-known opaques better"),
1864 _ => {
1865 if (!ty.hasPossibleRepresentation(s.ip)) {
1866 return s.fail(.runtime_incompatible_type, .{
1867 .src = src,
1868 .type = ty,
1869 });
1870 }
18711853
1872 // When a function is used as a runtime operand, it needs to1854 return try s.makeValue(.{
1873 // have a resolved return type, so that calling it later is1855 .float = float,
1874 // able to know what it returns.1856 });
1875 _ = try s.resolveFunctionPrototype(ref, ty);1857 },
1876 },1858 .numeric_literal_array => {
1859 try s.report.warn(.numeric_int_array_var_ambiguity, .{
1860 .src = src,
1861 });
1862 return try s.coerceArrayValue(src, value, .float);
1863 },
1864 .never_array => {
1865 if (value.valueData(s.ip, .array_value).get(s.ip).items_len != 0) {
1866 return s.fail(.runtime_incompatible_type, .{
1867 .src = src,
1868 .type = ty,
1869 });
1877 }1870 }
1871 return value;
1878 },1872 },
1873 .type,
1874 .never,
1875 => return s.fail(.runtime_incompatible_type, .{
1876 .src = src,
1877 .type = ty,
1878 }),
1879 .@"opaque" => @panic("TODO: represent comptime-known opaques better"),
1880 _ => {
1881 if (!ty.hasPossibleRepresentation(s.ip)) {
1882 return s.fail(.runtime_incompatible_type, .{
1883 .src = src,
1884 .type = ty,
1885 });
1886 }
1887
1888 // When a function is used as a runtime operand, it needs to
1889 // have a resolved return type, so that calling it later is
1890 // able to know what it returns.
1891 _ = try s.resolveFunctionPrototype(value.toAir(), ty);
1892
1893 return value;
1894 },
1895 };
1896}
1897
1898fn ensureRefRuntime(s: *Sema, src: LazyNode, ref: Air.Inst.Ref) Error!Air.Inst.Ref {
1899 switch (ref.decode()) {
1900 .value => |v| return (try s.ensureValueRuntime(src, v)).toAir(),
1879 .index => {1901 .index => {
1880 const ty = s.typeOfRef(ref);1902 const ty = s.typeOfRef(ref);
1881 if (!ty.hasPossibleRepresentation(s.ip)) {1903 if (!ty.hasPossibleRepresentation(s.ip)) {
...@@ -1884,13 +1906,13 @@ fn ensureValueRuntime(s: *Sema, src: LazyNode, ref: Air.Inst.Ref) Error!Air.Inst...@@ -1884,13 +1906,13 @@ fn ensureValueRuntime(s: *Sema, src: LazyNode, ref: Air.Inst.Ref) Error!Air.Inst
1884 .type = ty,1906 .type = ty,
1885 });1907 });
1886 }1908 }
1909 return ref;
1887 },1910 },
1888 }1911 }
1889 return ref;
1890}1912}
18911913
1892fn ensureValueDefinedRepresentation(s: *Sema, src: LazyNode, ref: Air.Inst.Ref) Error!Air.Inst.Ref {1914fn ensureValueDefinedRepresentation(s: *Sema, src: LazyNode, ref: Air.Inst.Ref) Error!Air.Inst.Ref {
1893 const runtime = try s.ensureValueRuntime(src, ref);1915 const runtime = try s.ensureRefRuntime(src, ref);
1894 const ty = s.typeOfRef(runtime);1916 const ty = s.typeOfRef(runtime);
1895 if (!ty.hasDefinedRepresentation(s.ip)) {1917 if (!ty.hasDefinedRepresentation(s.ip)) {
1896 return s.fail(.type_no_defined_representation, .{1918 return s.fail(.type_no_defined_representation, .{
...@@ -1901,25 +1923,27 @@ fn ensureValueDefinedRepresentation(s: *Sema, src: LazyNode, ref: Air.Inst.Ref)...@@ -1901,25 +1923,27 @@ fn ensureValueDefinedRepresentation(s: *Sema, src: LazyNode, ref: Air.Inst.Ref)
1901 return runtime;1923 return runtime;
1902}1924}
19031925
1904fn unionType(s: *Sema, a: Type, b: Type) Type {1926fn peerTypeResolution(s: *Sema, types: []const Type) Type {
1905 if (a == b) {1927 var a = types[0];
1906 return a;1928 for (types[1..]) |b| {
1929 if (a == b) {
1930 continue;
1931 } else if (b == .never) {
1932 continue;
1933 } else if (a == .never) {
1934 a = b;
1935 } else {
1936 std.debug.panic(
1937 "TODO: when two non-equal types implicitly merge (if/match), this should generate a union type.\na: {}\nb: {}",
1938 .{
1939 a.dump(s.ip),
1940 b.dump(s.ip),
1941 },
1942 );
1943 return s.ip.get(s.gpa, .{ .@"union" = &.{ a, b } });
1944 }
1907 }1945 }
19081946 return a;
1909 if (a == .never)
1910 return b;
1911
1912 if (b == .never)
1913 return a;
1914
1915 std.debug.panic(
1916 "TODO: when two non-equal types implicitly merge (if/match), this should generate a union type.\na: {}\nb: {}",
1917 .{
1918 a.dump(s.ip),
1919 b.dump(s.ip),
1920 },
1921 );
1922 // return s.ip.get(s.gpa, .{ .@"union" = &.{ a, b } });
1923}1947}
19241948
1925fn coerceArrayValue(s: *Sema, src: LazyNode, value: Value, item_type: Type) !Value {1949fn coerceArrayValue(s: *Sema, src: LazyNode, value: Value, item_type: Type) !Value {
test/behavior/function+1-1
...@@ -6,7 +6,7 @@ fn hello(n: int) -> int {...@@ -6,7 +6,7 @@ fn hello(n: int) -> int {
6 if n > 1 {6 if n > 1 {
7 hello(n - 1) + 17 hello(n - 1) + 1
8 } else {8 } else {
9 i9 1
10 }10 }
11}11}
1212