authorgravatar for me@paperdave.netdave caruso <me@paperdave.net> 2024-09-16 03:06:29-04:00
committergravatar for me@paperdave.netdave caruso <me@paperdave.net> 2024-09-16 03:06:29-04:00
log4c3e0c2241261637620ce10461adfdfd5558e422
treeade4d65fbedef996513b417eb893eec8b43f805a
parentf59b8e991584b1b97a8c3dad81223d2974f4eab5
signature Commit is signed but in an unrecognized format.

finish implementing tuples


12 files changed, 524 insertions(+), 104 deletions(-)

src/cli_compiler.zig+1
......@@ -44,6 +44,7 @@ pub fn main() !u8 {
4444 // Command line argument parsing.
4545 const program_name = "zig build compiler --";
4646 _, const args = try Cli.programArgsAlloc(alloc, program_name);
47 defer Cli.programArgsFree(alloc, args);
4748 const opts = arg_config.parse(program_name, args);
4849
4950 const file_path = try std.fs.path.resolve(alloc, &.{
src/core/Air.Dump.zig+23-1
......@@ -130,6 +130,16 @@ pub const State = struct {
130130 try tty.setColor(w, .reset);
131131 try w.writeAll(")");
132132 },
133 .field_tuple => {
134 const field: Air.FieldStruct = air.instData(.field_tuple, inst);
135 try w.writeAll("(");
136 try s.dumpInstRef(w, field.base);
137 try w.writeAll(", ");
138 try tty.setColor(w, .magenta);
139 try w.print("{d}", .{field.field_index});
140 try tty.setColor(w, .reset);
141 try w.writeAll(")");
142 },
133143 inline .call, .js_new => |t| {
134144 const call: Air.Call = air.instData(t, inst);
135145 const call_data = call.call_data.get(air);
......@@ -182,6 +192,15 @@ pub const State = struct {
182192 try s.dumpRefList(w, array_init.list.get(air).slice(air), '[', ']');
183193 try w.writeAll(")");
184194 },
195 .tuple_init => {
196 // const array_init: Air.ArrayInit = air.instData(.array_init, inst);
197 // const array_type = array_init.ty.toArrayType(s.ip).?;
198
199 // try w.writeAll("(");
200 // try s.dumpValue(w, array_type.item_type.value());
201 // try s.dumpRefList(w, array_init.list.get(air).slice(air), '[', ']');
202 // try w.writeAll(")");
203 },
185204 }
186205 }
187206
......@@ -233,7 +252,10 @@ pub const State = struct {
233252 try tty.setColor(w, .blue);
234253 try w.writeAll(") -> ");
235254 try tty.setColor(w, .reset);
236 try s.dumpValue(w, ty.return_type.value());
255 if (ty.return_type) |return_type|
256 try s.dumpValue(w, return_type.value())
257 else
258 try w.writeAll("[inferred]");
237259 },
238260 inline .float, .int32, .comptime_float => |data, tag| {
239261 try tty.setColor(w, .bright_black);
src/core/Air.zig+28-2
......@@ -25,9 +25,11 @@ pub const Inst = struct {
2525
2626 struct_init,
2727 array_init,
28 tuple_init,
2829
2930 field_opaque,
3031 field_struct,
32 field_tuple,
3133 field_array,
3234
3335 array_len,
......@@ -58,7 +60,8 @@ pub const Inst = struct {
5860 .store => Store,
5961 .struct_init => StructInit,
6062 .array_init => ArrayInit,
61 .field_struct => FieldStruct,
63 .tuple_init => TupleInit,
64 .field_struct, .field_tuple => FieldStruct,
6265 .field_opaque,
6366 .field_array,
6467 .add,
......@@ -286,6 +289,18 @@ pub const ArrayInit = struct {
286289 list: ExtraIndex(RefList),
287290};
288291
292pub const TupleInit = struct {
293 ty: Type,
294 field_values: u32,
295
296 pub fn valuesPtr(
297 init: *const TupleInit,
298 air: *const Air,
299 ) [*]const Air.Inst.Ref {
300 return @ptrCast(air.extra.list.items[init.field_values..].ptr);
301 }
302};
303
289304pub const FieldStruct = struct {
290305 base: Inst.Index,
291306 field_index: u32,
......@@ -342,6 +357,7 @@ pub fn typeOfIndex(air: *const Air, index: Inst.Index, ip: *const InternPool) In
342357 .@"if" => air.instData(.@"if", index).data.get(air).union_ty,
343358 .struct_init => air.instData(.struct_init, index).ty,
344359 .array_init => air.instData(.array_init, index).ty,
360 .tuple_init => air.instData(.tuple_init, index).ty,
345361 .arg => air.instData(.arg, index).type,
346362
347363 // A handful of binary operators are lowered to a form where both sides
......@@ -372,7 +388,9 @@ pub fn typeOfIndex(air: *const Air, index: Inst.Index, ip: *const InternPool) In
372388 .call => {
373389 const base_type = air.typeOf(air.instData(.call, index).base, ip);
374390 if (base_type == .@"opaque") return .@"opaque";
375 return base_type.toFunctionPrototype(ip).?.return_type;
391 return base_type.toFunctionPrototype(ip).?.return_type orelse
392 // unreachable // return type was not resolved
393 @panic("TODO: resolve return type");
376394 },
377395
378396 // Field accesses on structs are special-cased by storing the field
......@@ -391,6 +409,14 @@ pub fn typeOfIndex(air: *const Air, index: Inst.Index, ip: *const InternPool) In
391409 .toArrayType(ip) orelse unreachable; // field_array demands array type
392410 return base_type.item_type;
393411 },
412
413 // Tuple accesses use the tuple they are accessing into.
414 .field_tuple => {
415 const inst = air.instData(.field_tuple, index);
416 const base_type = air.typeOfIndex(inst.base, ip)
417 .toTupleType(ip) orelse unreachable; // field_tuple demands tuple type
418 return base_type.items[inst.field_index];
419 },
394420 };
395421}
396422
src/core/InternPool.zig+28-9
......@@ -158,7 +158,7 @@ pub const Tag = enum(u8) {
158158 => unreachable, // does not have a payload
159159 .float, .comptime_float => Float,
160160 .func => FuncDecl,
161 .function_type => TypeFunction,
161 .function_type => FunctionType,
162162 .string => String.WithLen,
163163 .struct_value => StructValue,
164164 .struct_type => StructType,
......@@ -180,9 +180,9 @@ pub const Tag = enum(u8) {
180180
181181 /// Trailing:
182182 /// 0. param_type: Index to param type. Repeated .params_len
183 pub const TypeFunction = struct {
183 pub const FunctionType = struct {
184184 params_len: u32,
185 return_type: Type,
185 return_type: Type.Optional,
186186 };
187187
188188 /// Trailing:
......@@ -578,7 +578,10 @@ pub const Type = enum(u32) {
578578 try writer.print("{}", .{param.displayName(ip)});
579579 }
580580 try writer.writeAll(") -> ");
581 try writer.print("{}", .{function_type.return_type.displayName(ip)});
581 if (function_type.return_type) |return_type|
582 try writer.print("{}", .{return_type.displayName(ip)})
583 else
584 try writer.writeAll("[inferred]");
582585 },
583586 .struct_type => {
584587 try writer.writeAll("[struct]");
......@@ -598,6 +601,19 @@ pub const Type = enum(u32) {
598601 },
599602 }
600603 }
604
605 pub const Optional = enum(u32) {
606 none = std.math.maxInt(u32),
607 _,
608
609 pub fn init(opt: ?Type) Optional {
610 return if (opt) |o| @enumFromInt(@intFromEnum(o)) else .none;
611 }
612
613 pub fn unwrap(opt: Optional) ?Type {
614 return if (opt != .none) @enumFromInt(@intFromEnum(opt)) else null;
615 }
616 };
601617};
602618
603619pub const Item = struct {
......@@ -956,7 +972,10 @@ pub const Key = union(enum) {
956972
957973 pub const FunctionType = struct {
958974 param_types: []Type,
959 return_type: Type,
975 /// If a return type is missing, it is inferred and not resolved yet.
976 /// When analysis figures out a return type, a new `FunctionType` with
977 /// the resolved return type.
978 return_type: ?Type,
960979
961980 pub fn hash(self: FunctionType, hasher: *Hash) void {
962981 const t = tracer.trace(@src());
......@@ -1491,9 +1510,9 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) !Value {
14911510 .function_type => |f| brk: {
14921511 for (f.param_types) |ty| ty.assertValid(ip);
14931512
1494 const extra, var trail = try ip.extra.appendTrailing(gpa, Tag.TypeFunction, .{
1513 const extra, var trail = try ip.extra.appendTrailing(gpa, Tag.FunctionType, .{
14951514 .params_len = @intCast(f.param_types.len),
1496 .return_type = f.return_type,
1515 .return_type = .init(f.return_type),
14971516 }, f.param_types.len);
14981517 defer trail.end();
14991518 trail.writeSlice(f.param_types);
......@@ -1566,12 +1585,12 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) !Value {
15661585
15671586fn extraTypeFunction(
15681587 ip: *const InternPool,
1569 extra_index: ExtraIndex(Tag.TypeFunction),
1588 extra_index: ExtraIndex(Tag.FunctionType),
15701589) Key.FunctionType {
15711590 const function_type, var trail = extra_index.getTrail(ip);
15721591 const param_types = trail.readTypeSlice(function_type.params_len);
15731592 const result: Key.FunctionType = .{
1574 .return_type = function_type.return_type,
1593 .return_type = function_type.return_type.unwrap(),
15751594 .param_types = param_types.get(ip),
15761595 };
15771596 return result;
src/core/Sema.zig+347-67
......@@ -1,10 +1,13 @@
1//! Semantic Analysis
1//! Semantic Analysis converts `Teg`
22//!
3//! Designed as a nonrecursive pause/resumable state machine, Sema converts
4//! declarations to Intern Pool `Value`s and function bodies to `Air`.
3//! Designed as a nonrecursive state machine which can be paused when an
4//! unfinished dependency is encountered, allowing the thread to work
5//! on other analysis jobs.
56//!
6//! - Compile time resolution of values
7//! - Type checking
7//! Sema's responsibilities include
8//! - Operation validation and reporting of most compile errors.
9//! - Evaluation of `comptime` code.
10//! - Declaring dependencies on other analysis units.
811
912comp: *Comp,
1013ip: *InternPool,
......@@ -175,6 +178,7 @@ const Frame = struct {
175178
176179 undefined: void,
177180 conditional: Conditional,
181 func: Func,
178182 };
179183
180184 const Conditional = enum {
......@@ -183,7 +187,16 @@ const Frame = struct {
183187 runtime_no,
184188 };
185189
190 const Func = union(enum) {
191 return_type,
192 argument: u16,
193 };
194
186195 fn initFromBlock(s: *Sema, block_index: Teg.Inst.Index, mode: AnalysisMode, data: Data) Frame {
196 switch (s.teg.instTag(block_index)) {
197 else => unreachable, // not a block
198 .block, .@"comptime" => {},
199 }
187200 const list = s.teg.instEncodedData(block_index)
188201 .decodeUnsafe(Teg.PlNode(Teg.IndexList), s.teg.instTag(block_index))
189202 .extra_index.get(&s.teg)
......@@ -476,7 +489,8 @@ const InstructionKind = enum {
476489 .@"comptime",
477490 .@"if",
478491 => .control_flow,
479 // these pop frames, which may end the compilation
492 // break is used to pop a frame, which has the
493 // possibility to end analysis
480494 .@"break",
481495 => .pop_control_flow,
482496 // these are statements which do not evaluate to anything (void)
......@@ -501,6 +515,12 @@ const PopControlFlowState = enum { stop, @"continue" };
501515
502516fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag).Result() {
503517 switch (tag) {
518 .block => unreachable, // frame must already be pushed
519 .declaration,
520 .variable_declaration,
521 .test_declaration,
522 => unreachable, // declarations are not analzed by Sema
523
504524 // TODO: pre-evaluate `.param` nodes, since they are always at the start of a function.
505525 // this way we can avoid repeating the non-trivial call into InternPool
506526 .param => {
......@@ -666,6 +686,8 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag)
666686 return try s.fieldAccessArray(lhs, lhs_type, field.rhs);
667687 } else if (lhs_type.toStructType(s.ip)) |struct_type| {
668688 return try s.fieldAccessStruct(lhs, lhs_type, struct_type, field.rhs);
689 } else if (lhs_type.toTupleType(s.ip)) |tuple_type| {
690 return try s.fieldAccessTuple(lhs, lhs_type, tuple_type, field.rhs);
669691 } else if (lhs.value()) |value| {
670692 if (value.valueTag(s.ip) == .external_value) {
671693 const rhs = try s.getCoercedValue(s.lazyNode(.rhs), field.rhs, .string);
......@@ -877,8 +899,6 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag)
877899 // return Value.void.toAir();
878900 // },
879901
880 // control flow
881 .block => unreachable, // frame should already be pushed
882902 .@"comptime" => {
883903 const index = s.frame.currentIndex();
884904 try s.pushFrame();
......@@ -892,50 +912,66 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag)
892912 .{ .params = Type, .captures = Air.Inst.Ref },
893913 .{ data.params.len, data.captures.len },
894914 );
895 defer reserve.deinit();
896 if (data.captures.len > 0) @panic("captures");
915 if (data.captures.len > 0) @panic("TODO: captures");
916
917 var handled_reserve: std.debug.SafetyLock = .{};
918 handled_reserve.lock();
919 defer handled_reserve.assertUnlocked();
920 errdefer handled_reserve.unlock();
897921
898922 for (data.params.slice(&s.teg), 0..) |param, i| {
899923 const src = s.lazyNode(.argument(@intCast(i)));
900 const ty = try s.getType(src, param.unwrap() orelse @panic("TODO: inferred param sets"));
901 if (!ty.hasPossibleRepresentation(s.ip)) {
902 return s.fail(.param_comptime_only, .{
903 .src = src,
904 .type = ty,
905 });
924
925 const known = param.unwrap() orelse @panic("TODO: inferred param sets");
926 switch (known.decode()) {
927 .value => |val| {
928 const ty = (try s.coerceValue(src, val, .type)).toTypeUnsafe(s.ip);
929 if (!ty.hasPossibleRepresentation(s.ip)) {
930 return s.fail(.param_comptime_only, .{
931 .src = src,
932 .type = ty,
933 });
934 }
935 reserve.items(.params)[i] = ty;
936 },
937 .index => |block| {
938 const mode = s.frame.mode;
939 try s.pushFrame();
940 s.frame = Frame.initFromBlock(s, block, mode, .{ .func = .{ .argument = @intCast(i) } });
941 s.frame.mark_index = reserve.index();
942 handled_reserve.unlock();
943 return;
944 },
906945 }
907 reserve.items(.params)[i] = ty;
908946 }
909947
910 const return_type = try s.getType(
911 s.lazyNode(.return_type),
912 data.return_type.unwrap() orelse .void,
913 );
914
915 const tracked_inst = try s.ip.trackInst(s.gpa, s.file, s.frame.currentIndex());
916 const func_type = try s.makeType(.{
917 .function_type = .{
918 .param_types = reserve.items(.params),
919 .return_type = return_type,
920 },
921 });
922 const func_value = try s.makeValue(.{
923 .func = .{
924 .ty = func_type,
925 .owner_decl = s.techDebtDeclIndex(), // TODO: ooh, this is a fun one!
926 .teg_body_inst = tracked_inst,
927 },
928 });
929
930 s.inst_map.add(s.frame.currentIndex(), func_value.toAir());
931
932 s.frame.advance();
948 const return_type = ret: {
949 switch ((data.return_type.unwrap() orelse break :ret null).decode()) {
950 .value => |val| {
951 const ty = (try s.coerceValue(s.lazyNode(.return_type), val, .type)).toTypeUnsafe(s.ip);
952 if (!ty.hasPossibleRepresentation(s.ip)) {
953 return s.fail(.runtime_incompatible_type, .{
954 .src = s.lazyNode(.return_type),
955 .type = ty,
956 });
957 }
958 break :ret ty;
959 },
960 .index => |block| {
961 const mode = s.frame.mode;
962 try s.pushFrame();
963 s.frame = Frame.initFromBlock(s, block, mode, .{ .func = .return_type });
964 s.frame.mark_index = reserve.index();
965 handled_reserve.unlock();
966 return;
967 },
968 }
969 };
933970
934 // TODO: move this queue once we perform one of these
935 // - depend on the return type of an inferred-return-type function
936 // - use the value in any runtime context ()
937 try s.comp.jobs.writeItem(.{ .analysis = .{ .au = .wrap(.{ .func = func_value }) } });
971 try s.postVisitFunctionPrototype(reserve, return_type);
972 handled_reserve.unlock();
938973 },
974
939975 .@"break" => {
940976 const brk: Teg.Break = s.frame.currentPayload(.@"break");
941977 const operand = s.getRef(brk.operand, .marked);
......@@ -978,12 +1014,14 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag)
9781014
9791015 switch (data.conditional) {
9801016 .comptime_condition => {
1017 assert(operand.kind == .value); // must be comptime
9811018 s.inst_map.add(s.frame.currentIndex(), operand);
9821019 s.frame.advance();
9831020 return .@"continue";
9841021 },
9851022 .runtime_yes => {
9861023 try s.emitRuntimeBlock(if_data.yes.decode().index, operand, mark);
1024 mark.deinit();
9871025
9881026 switch (if_data.no.decode()) {
9891027 .value => {
......@@ -1025,6 +1063,79 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag)
10251063 }));
10261064 s.frame.advance();
10271065 },
1066 .func => {
1067 const func_data: Teg.Func = s.frame.currentPayload(.func);
1068
1069 const reserve: FunctionReserve = .fromIndex(
1070 &s.scratch,
1071 mark.index,
1072 .{ func_data.params.len, func_data.captures.len },
1073 );
1074
1075 const return_type = switch (data.func) {
1076 .argument => |arg_num| ret: {
1077 for (
1078 func_data.params.slice(&s.teg)[arg_num + 1 ..],
1079 reserve.items(.params)[arg_num + 1 ..],
1080 arg_num + 1..,
1081 ) |param, *dst, i| {
1082 const known = param.unwrap() orelse @panic("TODO: inferred param sets");
1083 switch (known.decode()) {
1084 .value => |val| {
1085 const ty = (try s.coerceValue(s.lazyNode(.argument(@intCast(i))), val, .type)).toTypeUnsafe(s.ip);
1086 if (!ty.hasPossibleRepresentation(s.ip)) {
1087 return s.fail(.param_comptime_only, .{
1088 .src = s.lazyNode(.argument(@intCast(i))),
1089 .type = ty,
1090 });
1091 }
1092 dst.* = ty;
1093 },
1094 .index => |block| {
1095 const mode = s.frame.mode;
1096 try s.pushFrame();
1097 s.frame = Frame.initFromBlock(s, block, mode, .{ .func = .{ .argument = @intCast(i) } });
1098 return .@"continue";
1099 },
1100 }
1101 }
1102
1103 switch ((func_data.return_type.unwrap() orelse break :ret null).decode()) {
1104 .value => |val| {
1105 const ty = (try s.coerceValue(s.lazyNode(.return_type), val, .type)).toTypeUnsafe(s.ip);
1106 if (!ty.hasPossibleRepresentation(s.ip)) {
1107 return s.fail(.runtime_incompatible_type, .{
1108 .src = s.lazyNode(.return_type),
1109 .type = ty,
1110 });
1111 }
1112 break :ret ty;
1113 },
1114 .index => |block| {
1115 const mode = s.frame.mode;
1116 try s.pushFrame();
1117 s.frame = Frame.initFromBlock(s, block, mode, .{ .func = .return_type });
1118 s.frame.mark_index = reserve.index();
1119 return .@"continue";
1120 },
1121 }
1122 },
1123 .return_type => ret: {
1124 const val = operand.value() orelse unreachable; // must be comptime
1125 const ty = (try s.coerceValue(s.lazyNode(.return_type), val, .type)).toTypeUnsafe(s.ip);
1126 if (!ty.hasPossibleRepresentation(s.ip)) {
1127 return s.fail(.runtime_incompatible_type, .{
1128 .src = s.lazyNode(.return_type),
1129 .type = ty,
1130 });
1131 }
1132 break :ret ty;
1133 },
1134 };
1135
1136 try s.postVisitFunctionPrototype(reserve, return_type);
1137 return .@"continue";
1138 },
10281139 else => |t| std.debug.panic("TODO: post-visit .{s}", .{@tagName(t)}),
10291140 }
10301141
......@@ -1141,7 +1252,6 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag)
11411252 var has_errors = false;
11421253 var is_constant = true;
11431254
1144 // Allocate scratch space to store the values as they are being prepared.
11451255 const scratch = try s.scratch.reserve(
11461256 s.arena(),
11471257 Air.Inst.Ref.Optional,
......@@ -1150,11 +1260,10 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag)
11501260 defer scratch.deinit();
11511261 @memset(scratch.slice(), .none);
11521262
1263 // The ordering of `storage` is defined by the struct's type, which
1264 // may be different than the instantiation. The fields are visited in
1265 // order of the instantiation.
11531266 for (node.keys(&s.teg), node.inits(&s.teg), 0..) |init_key, init_value, i| {
1154 // The order of `storage` is as ordered by the type, but this
1155 // can be in a different ordering than the initialization. We
1156 // guarantee the ordering the initializers are visited is in the
1157 // order they are declared.
11581267 const found_index = index: for (struct_type.field_keys, 0..) |decl_key, j| {
11591268 if (decl_key.eqlSlice(init_key.slice(s.teg.string_bytes), s.ip))
11601269 break :index j;
......@@ -1181,7 +1290,7 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag)
11811290 .marked,
11821291 ) catch |e| switch (e) {
11831292 error.AnalysisFail => {
1184 // Instead of bailing out with `try`, we will continue the loop.
1293 // Instead of bailing out with `try`, all fields will be analyzed
11851294 has_errors = true;
11861295 // Mark something non-null so that duplicate field errors are emitted.
11871296 scratch.at(found_index).* = comptime Value.void.toAir().toOptional();
......@@ -1193,29 +1302,29 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag)
11931302 scratch.at(found_index).* = coerced.toOptional();
11941303 }
11951304
1196 // Check for missing fields
1197 {
1198 const storage = scratch.slice();
1199 var i: usize = 0;
1200 while (mem.indexOfScalarPos(Air.Inst.Ref.Optional, storage, i, .none)) |index| {
1201 try s.addError(.struct_init_missing_field, .{
1202 .ty = ty,
1203 .field = @intCast(index),
1204 .src = s.lazyNode(.main_node),
1205 });
1206 i = index + 1;
1207 }
1208 if (i != 0) has_errors = true;
1305 const storage = scratch.slice();
1306 var i: usize = 0;
1307 while (mem.indexOfScalarPos(Air.Inst.Ref.Optional, storage, i, .none)) |index| {
1308 try s.addError(.struct_init_missing_field, .{
1309 .ty = ty,
1310 .field = @intCast(index),
1311 .src = s.lazyNode(.main_node),
1312 });
1313 i = index + 1;
12091314 }
1315 if (i != 0) has_errors = true;
12101316
12111317 if (has_errors)
12121318 return Error.AnalysisFail;
12131319
1320 // Now that there are definitely no missing fields, it is safe to
1321 // interpret `scratch` as `Air.Inst.Ref`
1322
12141323 if (is_constant) {
1215 // Convert all items in storage into a Value type
12161324 const result_storage = scratch.sliceReinterpret(Value);
1217 for (scratch.slice(), scratch.sliceReinterpret(Value)) |ref, *value| {
1218 value.* = ref.unwrap().?.decode().value;
1325 for (scratch.slice(), result_storage) |ref, *value| {
1326 // `ref` and `value` alias; this coercion is performed in-place
1327 value.* = (ref.unwrap() orelse unreachable).decode().value;
12191328 }
12201329 return try s.makeValueAir(.{ .struct_value = .{
12211330 .ty = ty,
......@@ -1224,7 +1333,6 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag)
12241333 } else {
12251334 try s.ensureAtRuntime(s.lazyNode(.main_node));
12261335
1227 // Create a runtime struct instantiation
12281336 const value_index = try s.func.?.extra.appendSlice(s.gpa, scratch.slice());
12291337 return try s.addRef(.struct_init, .{
12301338 .ty = ty,
......@@ -1239,6 +1347,73 @@ fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag)
12391347 return s.makeValueAir(.{ .array_type = .{ .item_type = operand } });
12401348 },
12411349
1350 .tuple_type => {
1351 const data: Teg.TupleType = s.frame.currentPayload(.tuple_type);
1352
1353 const scratch = try s.scratch.reserve(s.arena(), Type, data.items.len);
1354 defer scratch.deinit();
1355
1356 for (data.items.slice(&s.teg), scratch.slice(), 0..) |src, *dst, i| {
1357 dst.* = try s.getType(s.lazyNode(.argument(@intCast(i))), src);
1358 }
1359
1360 return s.makeValueAir(.{ .tuple_type = .{ .items = scratch.slice() } });
1361 },
1362
1363 .tuple_init => {
1364 const data: Teg.TupleInit = s.frame.currentPayload(.tuple_init);
1365
1366 _ = data.type_hint; // TODO: use type hint
1367
1368 const scratch = try s.scratch.reserve(s.arena(), Air.Inst.Ref, data.items.len);
1369 defer scratch.deinit();
1370
1371 var is_const = true;
1372 for (data.items.slice(&s.teg), scratch.slice()) |src, *dst| {
1373 const result = s.getRef(src, .marked);
1374 dst.* = result;
1375 if (result.kind == .index) is_const = false;
1376 }
1377
1378 if (!is_const) {
1379 try s.ensureAtRuntime(s.lazyNode(.main_node));
1380
1381 for (scratch.slice(), 0..) |*ref, i| {
1382 ref.* = try s.ensureValueRuntime(s.lazyNode(.argument(@intCast(i))), ref.*);
1383 }
1384 }
1385
1386 const ty = ty: {
1387 const type_scratch = try s.scratch.reserve(s.arena(), Type, data.items.len);
1388 defer type_scratch.deinit();
1389
1390 for (scratch.slice(), type_scratch.slice()) |val, *ty| {
1391 ty.* = s.typeOfRef(val);
1392 }
1393
1394 break :ty try s.makeType(.{ .tuple_type = .{ .items = type_scratch.slice() } });
1395 };
1396
1397 if (is_const) {
1398 const result_storage = scratch.sliceReinterpret(Value);
1399 for (scratch.slice(), result_storage) |ref, *value| {
1400 // `ref` and `value` alias; this coercion is performed in-place
1401 value.* = ref.decode().value;
1402 }
1403
1404 return try s.makeValueAir(.{ .tuple_value = .{
1405 .items = result_storage,
1406 .type = ty,
1407 } });
1408 } else {
1409 const value_index = try s.func.?.extra.appendSlice(s.gpa, scratch.slice());
1410 return try s.addRef(.struct_init, .{
1411 .field_values = value_index,
1412 .ty = ty,
1413 });
1414 }
1415 },
1416
12421417 .result_type_argument => {
12431418 const result: Teg.ResultTypeArgument = s.frame.currentData(.result_type_argument);
12441419
......@@ -1605,6 +1780,11 @@ fn coerceValue(s: *Sema, src: LazyNode, value: Value, to: Type) Error!Value {
16051780 return s.coerceArrayValue(src, value, to_array_type.item_type);
16061781 }
16071782 }
1783 if (to.toTupleType(s.ip)) |to_tuple_type| {
1784 if (value.valueTag(s.ip) == .tuple_value) {
1785 return s.coerceTupleValue(src, value, from, to, to_tuple_type);
1786 }
1787 }
16081788 },
16091789 }
16101790 return s.coercionError(src, from, to);
......@@ -1729,6 +1909,44 @@ fn coerceArrayValue(s: *Sema, src: LazyNode, value: Value, item_type: Type) !Val
17291909 } });
17301910}
17311911
1912fn coerceTupleValue(
1913 s: *Sema,
1914 src: LazyNode,
1915 value: Value,
1916 value_type: Type,
1917 target_type: Type,
1918 target_key: InternPool.Key.TupleType,
1919) !Value {
1920 const tuple_value = value.toKey(s.ip).tuple_value;
1921 if (tuple_value.items.len != target_key.items.len)
1922 return s.coercionError(src, value_type, target_type);
1923
1924 const len = tuple_value.items.len;
1925
1926 const scratch = try s.scratch.reserveMulti(
1927 s.arena(),
1928 .{ .values = Value, .types = Type },
1929 .{ @intCast(len), @intCast(len) },
1930 );
1931 defer scratch.deinit();
1932
1933 // This function will append new items to the intern pool, invalidating
1934 // existing ones. Copy all data into a stable slice first.
1935 @memcpy(scratch.items(.values), tuple_value.items);
1936 @memcpy(scratch.items(.types), target_key.items);
1937
1938 for (0..len) |i| {
1939 // `s.scratch` can be invalidated by having a tuple of tuples and coercing it.
1940 const coerced = try s.coerceValue(src, scratch.at(.values, i).*, scratch.at(.types, i).*);
1941 scratch.at(.values, i).* = coerced;
1942 }
1943
1944 return try s.makeValue(.{ .tuple_value = .{
1945 .type = target_type,
1946 .items = scratch.items(.values),
1947 } });
1948}
1949
17321950fn lookupDecl(s: *Sema, name: NullTerminatedString) InternPool.DeclIndex {
17331951 var nsi = s.ns_index;
17341952 while (true) {
......@@ -1862,6 +2080,68 @@ fn fieldAccessArray(s: *Sema, lhs: Air.Inst.Ref, lhs_type: Type, rhs_teg: Teg.In
18622080 }
18632081}
18642082
2083fn fieldAccessTuple(
2084 s: *Sema,
2085 lhs: Air.Inst.Ref,
2086 lhs_type: Type,
2087 tuple_type: InternPool.Key.TupleType,
2088 field: Teg.Inst.Ref,
2089) !Air.Inst.Ref {
2090 const index_value = try s.getCoercedValue(s.lazyNode(.rhs), field, .int32);
2091 const index_signed = index_value.toInt32(s.ip) orelse unreachable;
2092 if (index_signed < 0 or index_signed >= tuple_type.items.len) {
2093 return s.fail(.field_lookup_failed, .{
2094 .ty = lhs_type,
2095 .src = s.lazyNode(.lhs),
2096 .field = index_value,
2097 });
2098 }
2099 const index: u32 = @intCast(index_signed);
2100
2101 switch (lhs.decode()) {
2102 .value => |value| {
2103 return value.toKey(s.ip).tuple_value.items[index].toAir();
2104 },
2105 .index => |lhs_index| {
2106 try s.ensureAtRuntime(s.lazyNode(.lhs));
2107 return s.addRef(.field_tuple, .{
2108 .base = lhs_index,
2109 .field_index = index,
2110 });
2111 },
2112 }
2113}
2114
2115const FunctionReserve = core.U32Stack.ReserveMulti(.{ .params = Type, .captures = Air.Inst.Ref });
2116
2117fn postVisitFunctionPrototype(s: *Sema, reserve: FunctionReserve, return_type: ?Type) !void {
2118 const tracked_inst = try s.ip.trackInst(s.gpa, s.file, s.frame.currentIndex());
2119 const func_type = try s.makeType(.{
2120 .function_type = .{
2121 .param_types = reserve.items(.params),
2122 .return_type = return_type,
2123 },
2124 });
2125 const func_value = try s.makeValue(.{
2126 .func = .{
2127 .ty = func_type,
2128 .owner_decl = s.techDebtDeclIndex(), // TODO: ooh, this is a fun one!
2129 .teg_body_inst = tracked_inst,
2130 },
2131 });
2132
2133 s.inst_map.add(s.frame.currentIndex(), func_value.toAir());
2134
2135 s.frame.advance();
2136
2137 reserve.deinit();
2138
2139 // TODO: move this queue once we perform one of these
2140 // - depend on the return type of an inferred-return-type function
2141 // - use the value in any runtime context ()
2142 try s.comp.jobs.writeItem(.{ .analysis = .{ .au = .wrap(.{ .func = func_value }) } });
2143}
2144
18652145//
18662146
18672147fn tempAir(s: *const Sema) Air {
src/core/Teg.zig+3-2
......@@ -126,6 +126,7 @@ pub const Tag = enum(u8) {
126126 .variable_declaration,
127127 .test_declaration,
128128 => PlNode(Decl),
129
129130 .coercion => PlNode(Coercion),
130131 .@"break" => PlNode(Break),
131132 .number => PlNode(Float),
......@@ -151,7 +152,7 @@ pub const Tag = enum(u8) {
151152 .result_type_tuple_field => ResultTypeTupleField,
152153 .result_type_array_value => ResultTypeArrayValue,
153154
154 // Single instruction operand
155 // One operand instructions
155156 .memory,
156157 .discard,
157158 .ensure_void,
......@@ -167,7 +168,7 @@ pub const Tag = enum(u8) {
167168 .assert,
168169 => RefNode,
169170
170 // Two instruction operands
171 // Two operand instructions
171172 .field,
172173 .index,
173174 .add,
src/core/Tokenizer.zig+3-3
......@@ -135,7 +135,7 @@ pub const Token = struct {
135135
136136 /// Represents a tokenization error. Instead of reporting an error during
137137 /// tokenization, parsing will begin, and an error will be reported when
138 /// this token is hit. That way, the error can tell you what it expected.
138 /// this token is hit. That way, the error .
139139 invalid,
140140 invalid_number,
141141 invalid_string,
......@@ -276,8 +276,8 @@ pub const Token = struct {
276276 /// Instead of storing the length of a token, we can recompute this
277277 /// information on demand, as it is not often needed.
278278 ///
279 /// If you are computing the length of something that is knowable
280 /// without the source code, use token.tag.staticLen() instead.
279 /// When computing the length of something that can be known without
280 /// the source code, use token.tag.staticLen() instead.
281281 pub fn len(token: Token, utf8_source: [:0]const u8) u32 {
282282 return token.tag.staticLen() orelse switch (token.tag) {
283283
src/core/Transform.zig+4-4
......@@ -137,10 +137,9 @@ const ResultType = union(enum) {
137137 .used => unreachable, // Use after free
138138 .ref => |r| r,
139139 .type_of => |r| try t.addRef(.type_of, .{
140 // Result types don't have associated nodes, which is ok because
141 // type_of cannot fail in the context we are using it. Instead,
142 // we will use a predictable poison value, so that an attempt
143 // to print this will crash.
140 // Result types do not have associated nodes which is okay as `type_of` cannot
141 // fail in the context it is used in. A predictable poison value is used instead
142 // so that an attempt to print the following will cause a crash.
144143 .src_node = std.math.maxInt(i32),
145144 .operand = r,
146145 }),
......@@ -281,6 +280,7 @@ const Symbol = struct {
281280 value: union {
282281 mutable: Inst.Index,
283282 immutable: Inst.Ref,
283 param_index: u32,
284284 none: void,
285285 },
286286 result_type_ref: Inst.Ref,
src/core/js/Link.zig+12-1
......@@ -309,7 +309,18 @@ fn printValue(link: *Link, value: InternPool.Value, level: Level, writer: anytyp
309309 if (i != 0)
310310 try writer.writeAll(",");
311311
312 // TODO: handle special identifiers
312 try link.printValue(inner_value, .assignment, writer);
313 }
314 try writer.writeAll("]");
315 },
316
317 .tuple_value => |tuple_value| {
318 try writer.writeAll("[");
319
320 for (tuple_value.items, 0..) |inner_value, i| {
321 if (i != 0)
322 try writer.writeAll(",");
323
313324 try link.printValue(inner_value, .assignment, writer);
314325 }
315326 try writer.writeAll("]");
src/core/js/NodeGen.zig+42-2
......@@ -283,6 +283,15 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void {
283283 try c.markExpression(inst, node);
284284 },
285285
286 .field_tuple => {
287 const data: Air.FieldStruct = c.air.instData(.field_tuple, inst);
288 const node = try c.add(.index, .{
289 .lhs = try c.fetchInstExpression(data.base),
290 .rhs = try c.makeValueInst(.{ .int32 = @intCast(data.field_index) }),
291 });
292 try c.markExpression(inst, node);
293 },
294
286295 .array_len => {
287296 const data: Air.Un = c.air.instData(.array_len, inst);
288297 const node = try c.add(.field, .{
......@@ -459,9 +468,9 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void {
459468 },
460469
461470 .array_init => {
462 const struct_init: Air.ArrayInit = c.air.instData(.array_init, inst);
471 const array_init: Air.ArrayInit = c.air.instData(.array_init, inst);
463472
464 const items = struct_init.list.get(&c.air).slice(&c.air);
473 const items = array_init.list.get(&c.air).slice(&c.air);
465474 const store_offset = try c.extra.alloc(c.gpa, items.len);
466475
467476 for (
......@@ -478,6 +487,28 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void {
478487 });
479488 try c.markExpression(inst, node);
480489 },
490
491 .tuple_init => {
492 const tuple_init: Air.TupleInit = c.air.instData(.tuple_init, inst);
493
494 const field_count = tuple_init.ty.toTupleType(c.ip).?.items.len;
495 const items = tuple_init.field_values;
496 const store_offset = try c.extra.alloc(c.gpa, field_count);
497
498 for (
499 items..items + field_count,
500 store_offset..,
501 ) |in_i, expr_i| {
502 const in = try c.fetchExpression(@bitCast(c.air.extra.list.items[in_i]));
503 c.extra.list.items[expr_i] = @intFromEnum(in);
504 }
505
506 const node = try c.add(.array, .{
507 .start = store_offset,
508 .len = @intCast(field_count),
509 });
510 try c.markExpression(inst, node);
511 },
481512 }
482513}
483514
......@@ -587,6 +618,13 @@ fn valueToExpression(c: *NodeGen, v: Value) !Node.Index {
587618 }
588619}
589620
621fn makeValueInst(c: *NodeGen, k: InternPool.Key) !Node.Index {
622 return try c.valueToExpression(c.ip.get(c.gpa, k) catch |err| switch (err) {
623 error.TooManyValues => @panic("TODO: handle TooManyValues"),
624 else => |e| return e,
625 });
626}
627
590628fn addTemporary(c: *NodeGen) !Node.Index {
591629 const t = tracer.trace(@src());
592630 defer t.end();
......@@ -727,6 +765,7 @@ fn canLowerToExpression(c: *const NodeGen, inst: Air.Inst.Ref) bool {
727765 .arg,
728766 .field_opaque,
729767 .field_struct,
768 .field_tuple,
730769 .field_array,
731770 .add,
732771 .sub,
......@@ -741,6 +780,7 @@ fn canLowerToExpression(c: *const NodeGen, inst: Air.Inst.Ref) bool {
741780 .struct_init,
742781 .array_init,
743782 .array_len,
783 .tuple_init,
744784 => true,
745785
746786 // JavaScript variable declarations require statements.
src/core/stack.zig+28-9
......@@ -15,7 +15,7 @@ pub fn Stack(comptime Base: type) type {
1515
1616 pub const Mark = struct {
1717 stack: *Self,
18 index: usize,
18 index: u32,
1919
2020 pub fn deinit(m: Mark) void {
2121 if (core.runtime_safety)
......@@ -23,8 +23,8 @@ pub fn Stack(comptime Base: type) type {
2323 m.stack.list.shrinkRetainingCapacity(m.index);
2424 }
2525
26 pub fn len(m: Mark) usize {
27 return m.stack.list.items.len - m.index;
26 pub fn len(m: Mark) u32 {
27 return @intCast(m.stack.list.items.len - m.index);
2828 }
2929
3030 pub fn slice(m: Mark, comptime T: type) []T {
......@@ -39,7 +39,7 @@ pub fn Stack(comptime Base: type) type {
3939 pub fn mark(stack: *Self) Mark {
4040 return .{
4141 .stack = stack,
42 .index = stack.list.items.len,
42 .index = @intCast(stack.list.items.len),
4343 };
4444 }
4545
......@@ -101,8 +101,8 @@ pub fn Stack(comptime Base: type) type {
101101
102102 return struct {
103103 stack: *Self,
104 starts: [fields.len]usize,
105 lengths: [fields.len]usize,
104 starts: [fields.len]u32,
105 lengths: [fields.len]u32,
106106
107107 fn Type(comptime field: FieldEnum) type {
108108 return @field(types, @tagName(field));
......@@ -128,6 +128,25 @@ pub fn Stack(comptime Base: type) type {
128128 std.mem.copyForwards(u32, dest, from);
129129 r.stack.list.shrinkRetainingCapacity(start + from.len);
130130 }
131
132 pub fn index(r: @This()) u32 {
133 return r.starts[0];
134 }
135
136 pub fn fromIndex(
137 stack: *Self,
138 start: u32,
139 lengths: [std.meta.fields(@TypeOf(types)).len]u32,
140 ) @This() {
141 var all_lengths: u32 = 0;
142 var starts: [std.meta.fields(@TypeOf(types)).len]u32 = undefined;
143 inline for (lengths, &starts) |len, *slice_start| {
144 slice_start.* = start + all_lengths;
145 all_lengths += len;
146 }
147 stack.list.items.len += all_lengths;
148 return .{ .stack = stack, .starts = starts, .lengths = lengths };
149 }
131150 };
132151 }
133152
......@@ -137,13 +156,13 @@ pub fn Stack(comptime Base: type) type {
137156 stack: *Self,
138157 allocator: Allocator,
139158 comptime types: anytype,
140 lengths: [std.meta.fields(@TypeOf(types)).len]usize,
159 lengths: [std.meta.fields(@TypeOf(types)).len]u32,
141160 ) !ReserveMulti(types) {
142161 var all_lengths: usize = 0;
143 var starts: [std.meta.fields(@TypeOf(types)).len]usize = undefined;
162 var starts: [std.meta.fields(@TypeOf(types)).len]u32 = undefined;
144163 const start = stack.list.items.len;
145164 inline for (lengths, &starts) |len, *slice_start| {
146 slice_start.* = start + all_lengths;
165 slice_start.* = @intCast(start + all_lengths);
147166 all_lengths += len;
148167 }
149168 try stack.list.ensureUnusedCapacity(allocator, all_lengths);
test/behavior/tuple+5-4
......@@ -1,13 +1,14 @@
11let assertEqual = @extern("assertEqual");
22
3fn hello(x: type) -> y(x, float) {
4 (5, 2.5)
3fn hello() -> tuple (int, float) {
4 let x: tuple (int, float) = (5, 2.5);
5 x
56}
67
78fn main {
89 let x = hello();
9 assertEqual(x.0, 5);
10 assertEqual(x.1, 2.5);
10 assertEqual(x.0, i(5));
11 assertEqual(x.1, f(2.5));
1112}
1213
1314fn f(a: float) -> float { a }