authorgravatar for me@paperdave.netdave caruso <me@paperdave.net> 2024-07-21 05:22:11-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2024-07-21 05:22:11-07:00
logb8d2bfd42997aba8f8e403b622fb3c8e4075a3f6
tree03858e3bb72820546a0e23ec7758dbdb970fa801
parent231e17437dd3075f6f42db511ca38181c3c84fb2
signature Signed by PGP key B5690EEEBB952194

feat: implement structs (#42)

In order to implement structs, some architectural changes have been made in the compiler. - Remove `codegen` as a field on `Comp.Decl`, since struct declarations do not have any codegen. This is also good to do now, since codegen truly lives per-`Value`, not per-`Decl`. This is critical for lambda functions, as there will be declarations with multiple codegens. - Abstract the JS backend into it's own container. There are currently no other backends, but since codegen was being re-worked, this felt pretty reasonable too. We now have `core.Js` to represent the js backend, and `JsLink`, `JsTree`, and `JsNodeGen` are all moved into the `Js` namespace with their prefix removed. So Node generation is now in `core.Js.NodeGen`. This `Js` parent struct contains backend state, which is currently just a map of `Value` -> `Js.Tree` (moved from `Decl.codegen`) - Change `Js.Tree.decl_deps` to `Js.Tree.top_level_value_deps`, as the dependencies are not on the declarations, but the associated `Value` objects. - Air `field` was renamed to `field_opaque`, with its behavior being solidified as how fields work for opaque values. Parsing for inferred structs is implemented but there is a TODO at the Transform pass.

29 files changed, 4031 insertions(+), 2247 deletions(-)

CHANGELOG.md+4
...@@ -1,8 +1,12 @@...@@ -1,8 +1,12 @@
1## 0.1.41## 0.1.4
22
3- feat: implement string identifiers
4- feat: implement basic struct types
5- feat: include a `README.html` alongside compiler with documentation and code samples.
3- fix(sema): miscompilation using left side of all binary expression for both sides6- fix(sema): miscompilation using left side of all binary expression for both sides
4- fix(sema): handle recursive function calls7- fix(sema): handle recursive function calls
5- fix(sema): quit block analysis when hitting `noreturn`8- fix(sema): quit block analysis when hitting `noreturn`
9- fix(sema): binary type resolution with opaque and numeric literals no longer crash
610
7## 0.1.311## 0.1.3
812
README.md+8-3
...@@ -1,15 +1,20 @@...@@ -1,15 +1,20 @@
1# New Language Project1# New Language Project
22
3This is a high-level, general purpose programming language. It is a mix of Rust, Zig, and JavaScript's great points.3This is a high-level, general purpose programming language. It is a mix of Rust, Zig, and
4JavaScript's great points.
45
5For a guide on how to use the language, please see the Language Reference document.6For a guide on how to use the language, please see the Language Manual included in the binary
7distribution. (TODO: host a rendered version)
68
7## Building9## Building
810
9The primary dependency of the language SDK is a Zig compiler. If one is not available, the `./build` and `.\build.cmd` scripts can substitute for running `zig build` on POSIX and Windows systems, respectively. With a Zig compiler, a release build can be produced in a single command:11The primary dependency of the language SDK is a Zig compiler. If one is not available, the
12`./build` and `.\build.cmd` scripts can substitute for running `zig build` on POSIX and Windows
13systems, respectively. With a Zig compiler, a release build can be produced in a single command:
1014
11 zig build --release15 zig build --release
1216
13To run the standard packaging procedure, which builds the SDK for all officially supported targets:17To run the standard packaging procedure, which builds the SDK for all officially supported targets:
1418
15 zig build package19 zig build package
20
build.zig+18-6
...@@ -34,8 +34,14 @@ pub fn build(b: *std.Build) void {...@@ -34,8 +34,14 @@ pub fn build(b: *std.Build) void {
34 "Enable tracing",34 "Enable tracing",
35 ) orelse .none;35 ) orelse .none;
3636
37 const native_debug = buildWithTarget(b, native_target, .Debug, .none);37 const no_llvm = b.option(
38 const main = buildWithTarget(b, target, optimize, tracing);38 bool,
39 "no_llvm",
40 "Experiment with the Zig self-hosted backends",
41 ) orelse false;
42
43 const native_debug = buildWithTarget(b, native_target, .Debug, .none, no_llvm);
44 const main = buildWithTarget(b, target, optimize, tracing, no_llvm);
3945
40 b.installArtifact(main.compiler);46 b.installArtifact(main.compiler);
4147
...@@ -46,7 +52,6 @@ pub fn build(b: *std.Build) void {...@@ -46,7 +52,6 @@ pub fn build(b: *std.Build) void {
46 const artifact, const name = entry;52 const artifact, const name = entry;
4753
48 const run_cmd = b.addRunArtifact(artifact);54 const run_cmd = b.addRunArtifact(artifact);
49 run_cmd.step.dependOn(b.getInstallStep());
50 if (b.args) |args| {55 if (b.args) |args| {
51 run_cmd.addArgs(args);56 run_cmd.addArgs(args);
52 }57 }
...@@ -128,7 +133,7 @@ pub fn build(b: *std.Build) void {...@@ -128,7 +133,7 @@ pub fn build(b: *std.Build) void {
128 },133 },
129 ) catch @panic("OOM");134 ) catch @panic("OOM");
130135
131 const artifacts = buildWithTarget(b, cross_target, .ReleaseSafe, .none);136 const artifacts = buildWithTarget(b, cross_target, .ReleaseSafe, .none, no_llvm);
132137
133 inline for (.{138 inline for (.{
134 .{ artifacts.compiler, "compiler" },139 .{ artifacts.compiler, "compiler" },
...@@ -168,8 +173,11 @@ fn buildWithTarget(...@@ -168,8 +173,11 @@ fn buildWithTarget(
168 target: Build.ResolvedTarget,173 target: Build.ResolvedTarget,
169 optimize: std.builtin.OptimizeMode,174 optimize: std.builtin.OptimizeMode,
170 tracing: TracingMode,175 tracing: TracingMode,
176 no_llvm: bool,
171) Artifacts {177) Artifacts {
172 const dep_opts = .{ .target = target, .optimize = optimize };178 const dep_opts = .{ .target = target, .optimize = optimize };
179 const use_llvm = if (no_llvm) false else null;
180 const use_lld = if (no_llvm) false else null;
173181
174 // Dependencies182 // Dependencies
175 const zg = b.dependency("zg", dep_opts);183 const zg = b.dependency("zg", dep_opts);
...@@ -195,6 +203,8 @@ fn buildWithTarget(...@@ -195,6 +203,8 @@ fn buildWithTarget(
195 .root_source_file = b.path("src/core/mod.zig"),203 .root_source_file = b.path("src/core/mod.zig"),
196 .target = target,204 .target = target,
197 .optimize = optimize,205 .optimize = optimize,
206 .use_lld = use_lld,
207 .use_llvm = use_llvm,
198 });208 });
199 core_tests.root_module.addImport("build_options", opts_mod);209 core_tests.root_module.addImport("build_options", opts_mod);
200 core_tests.root_module.addImport("tracer", tracer.module("tracer"));210 core_tests.root_module.addImport("tracer", tracer.module("tracer"));
...@@ -206,7 +216,8 @@ fn buildWithTarget(...@@ -206,7 +216,8 @@ fn buildWithTarget(
206 .root_source_file = b.path("src/core/main.zig"),216 .root_source_file = b.path("src/core/main.zig"),
207 .target = target,217 .target = target,
208 .optimize = optimize,218 .optimize = optimize,
209 .link_libc = true,219 .use_lld = use_lld,
220 .use_llvm = use_llvm,
210 });221 });
211 compiler_exe.root_module.addImport("build_options", opts_mod);222 compiler_exe.root_module.addImport("build_options", opts_mod);
212 compiler_exe.root_module.addImport("core", core);223 compiler_exe.root_module.addImport("core", core);
...@@ -218,7 +229,8 @@ fn buildWithTarget(...@@ -218,7 +229,8 @@ fn buildWithTarget(
218 .root_source_file = b.path("src/lsp/lsp.zig"),229 .root_source_file = b.path("src/lsp/lsp.zig"),
219 .target = target,230 .target = target,
220 .optimize = optimize,231 .optimize = optimize,
221 .link_libc = true,232 .use_lld = use_lld,
233 .use_llvm = use_llvm,
222 });234 });
223 lsp_exe.root_module.addImport("build_options", opts_mod);235 lsp_exe.root_module.addImport("build_options", opts_mod);
224 lsp_exe.root_module.addImport("core", core);236 lsp_exe.root_module.addImport("core", core);
doc/architecture.md+10-4
...@@ -1,7 +1,13 @@...@@ -1,7 +1,13 @@
1# compiler architecture1# Architecture
22
3This document will outline the code structure of the language's SDK as well as the compilation pipeline architecture. The major components of the language are:3This document will outline the code structure of the language's SDK as well as the compilation pipeline architecture. The major components of the language are:
44
5- [**Core**][core]5- [**Core**][core] (`src/core`)
6- [**Standard Library**][std]6- [**SDK Manual Generator**][manual] (`src/manual`)
7- [**Language Server**][lsp]7
8Keep in mind that code is the best documentation for specific details, and this document aims to give an overall explanation of the high level process. The entry point to each component is `mod.zig`.
9
10General code style is to sort the file's contents by importance. Most files will start with an overview comment, followed by the root struct fields, and then important companion types and functions, then helpers. Imports and tests are not as important to a reader, so those are placed at the end of the file.
11
12An example of this organization is `src/core/Tokenizer.zig`, which opens with the tokenizer state, and then `pub fn tokenize` to bring attention to the flow of tokenization. The `Token` struct is directly after this, since it is important, but not as important as visualizing *how* the tokenizer works.
13
doc/language-manual.md+40
...@@ -158,3 +158,43 @@ fn currentYear() -> int {...@@ -158,3 +158,43 @@ fn currentYear() -> int {
158158
159print(currentYear());159print(currentYear());
160```160```
161
162## Structs
163
164To group data together, a `struct` can be used. A struct contains zero or one fields of runtime-compatible types.
165
166```lang:run
167let print = @js("console").log;
168
169struct Position {
170 x: int,
171 y: int,
172}
173
174let center = Position{ x: 0, y: -2 };
175print(center.x, center.y);
176
177fn addPositions(a: Position, b: Position) -> Position {
178 // To allow less verbose code, the struct type can be inferred with `.`
179 // TODO: that is not implemented ;-;
180 Position{ x: a.x + b.x, y: a.y + b.y }
181}
182
183let added = addPositions(
184 Position{ x: 1, y: -4 },
185 Position{ x: 5, y: 8 },
186);
187print("Add result:", added.x, added.y);
188```
189
190To allow optimizations, `struct`s by default do not have a defined representation in JavaScript. This means passing the struct to an opaque function is not allowed.
191
192```lang:build
193struct Person {
194 name: string,
195 age: int,
196}
197
198let print = @js("console").log;
199print(Person{ name: "hello", age: 42 });
200```
src/core/Air.Dump.zig+96-4
...@@ -116,8 +116,8 @@ pub const State = struct {...@@ -116,8 +116,8 @@ pub const State = struct {
116 try s.dumpValue(w, memory.ty.value());116 try s.dumpValue(w, memory.ty.value());
117 try w.writeAll(")");117 try w.writeAll(")");
118 },118 },
119 .field => {119 .field_opaque => {
120 const field: Air.Field = air.instData(.field, inst);120 const field: Air.FieldOpaque = air.instData(.field_opaque, inst);
121 try w.writeAll("(");121 try w.writeAll("(");
122 try s.dumpRef(w, field.base);122 try s.dumpRef(w, field.base);
123 try w.writeAll(", ");123 try w.writeAll(", ");
...@@ -126,6 +126,22 @@ pub const State = struct {...@@ -126,6 +126,22 @@ pub const State = struct {
126 try tty.setColor(w, .reset);126 try tty.setColor(w, .reset);
127 try w.writeAll(")");127 try w.writeAll(")");
128 },128 },
129 .field_struct => {
130 const field: Air.FieldStruct = air.instData(.field_struct, inst);
131 try w.writeAll("(");
132 try s.dumpInstRef(w, field.base);
133 try w.writeAll(", ");
134 try tty.setColor(w, .magenta);
135 try w.print("{s}", .{
136 air
137 .typeOfIndex(field.base, s.ip)
138 .toStructType(s.ip).?
139 .field_keys[field.field_index]
140 .toSlice(s.ip),
141 });
142 try tty.setColor(w, .reset);
143 try w.writeAll(")");
144 },
129 inline .call, .new => |t| {145 inline .call, .new => |t| {
130 const call: Air.Call = air.instData(t, inst);146 const call: Air.Call = air.instData(t, inst);
131 const call_data = call.call_data.get(air);147 const call_data = call.call_data.get(air);
...@@ -152,6 +168,23 @@ pub const State = struct {...@@ -152,6 +168,23 @@ pub const State = struct {
152 try s.newline(w);168 try s.newline(w);
153 try w.writeAll("}");169 try w.writeAll("}");
154 },170 },
171 .struct_init => {
172 const struct_init: Air.StructInit = air.instData(.struct_init, inst);
173 const struct_type = struct_init.ty.toStructType(s.ip).?;
174
175 try w.writeAll("(");
176 try s.dumpValue(w, struct_init.ty.value());
177 try w.writeAll(", {");
178 s.indent += 1;
179 for (struct_type.field_keys, struct_init.valuesPtr(air)) |key, ref| {
180 try s.newline(w);
181 try w.print("{}: ", .{key.fmt(s.ip)});
182 try s.dumpRef(w, ref);
183 }
184 s.indent -= 1;
185 try s.newline(w);
186 try w.writeAll("})");
187 },
155 }188 }
156 }189 }
157190
...@@ -188,14 +221,14 @@ pub const State = struct {...@@ -188,14 +221,14 @@ pub const State = struct {
188 try w.print("{s}", .{@tagName(t)});221 try w.print("{s}", .{@tagName(t)});
189 try tty.setColor(w, .reset);222 try tty.setColor(w, .reset);
190 },223 },
191 .type_function => |ty| {224 .function_type => |ty| {
192 try tty.setColor(w, .blue);225 try tty.setColor(w, .blue);
193 try w.writeAll("fn(");226 try w.writeAll("fn(");
194 try tty.setColor(w, .reset);227 try tty.setColor(w, .reset);
195 for (ty.param_types, 0..) |param, i| {228 for (ty.param_types, 0..) |param, i| {
196 if (i != 0) {229 if (i != 0) {
197 try tty.setColor(w, .blue);230 try tty.setColor(w, .blue);
198 try w.writeAll(",");231 try w.writeAll(", ");
199 try tty.setColor(w, .reset);232 try tty.setColor(w, .reset);
200 }233 }
201 try s.dumpValue(w, param.value());234 try s.dumpValue(w, param.value());
...@@ -245,6 +278,65 @@ pub const State = struct {...@@ -245,6 +278,65 @@ pub const State = struct {
245 try w.writeAll(">");278 try w.writeAll(">");
246 try tty.setColor(w, .reset);279 try tty.setColor(w, .reset);
247 },280 },
281 .struct_type => |struct_type| {
282 try tty.setColor(w, .bright_magenta);
283 try w.writeAll("struct"); // TODO: struct name
284 try tty.setColor(w, .reset);
285 try w.writeAll(" {");
286
287 if (struct_type.field_keys.len > 0)
288 try w.writeAll(" ");
289
290 for (
291 struct_type.field_keys,
292 struct_type.field_types,
293 0..,
294 ) |key, field_type, i| {
295 if (i != 0)
296 try w.writeAll(", ");
297 try w.writeAll(key.toSlice(s.ip));
298 try w.writeAll(": ");
299 try s.dumpValue(w, field_type.value());
300 }
301
302 if (struct_type.field_keys.len > 0)
303 try w.writeAll(" ");
304
305 try w.writeAll("}");
306 },
307 .struct_value => |struct_value| {
308 try tty.setColor(w, .bright_black);
309 try w.writeAll("<");
310 try tty.setColor(w, .bright_magenta);
311 try w.writeAll("struct"); // TODO: struct name
312 try tty.setColor(w, .bright_black);
313 try w.writeAll(", ");
314 try tty.setColor(w, .reset);
315 try w.writeAll("{");
316
317 if (struct_value.field_values.len > 0)
318 try w.writeAll(" ");
319
320 for (
321 struct_value.ty.toStructType(s.ip).?.field_keys,
322 struct_value.field_values,
323 0..,
324 ) |key, value, i| {
325 if (i != 0)
326 try w.writeAll(", ");
327 try w.writeAll(key.toSlice(s.ip));
328 try w.writeAll(": ");
329 try s.dumpValue(w, value);
330 }
331
332 if (struct_value.field_values.len > 0)
333 try w.writeAll(" ");
334
335 try w.writeAll("}");
336 try tty.setColor(w, .bright_black);
337 try w.writeAll(">");
338 try tty.setColor(w, .reset);
339 },
248 }340 }
249 }341 }
250342
src/core/Air.zig+101-24
...@@ -20,11 +20,15 @@ pub const Inst = struct {...@@ -20,11 +20,15 @@ pub const Inst = struct {
20 @"break",20 @"break",
21 @"if",21 @"if",
22 arg,22 arg,
23 field,
2423
25 memory,24 memory,
26 store,25 store,
2726
27 struct_init,
28
29 field_opaque,
30 field_struct,
31
28 // bin32 // bin
29 add,33 add,
30 sub,34 sub,
...@@ -48,9 +52,11 @@ pub const Inst = struct {...@@ -48,9 +52,11 @@ pub const Inst = struct {
48 .@"if" => If,52 .@"if" => If,
49 .call, .new => Call,53 .call, .new => Call,
50 .arg => Arg,54 .arg => Arg,
51 .field => Field,
52 .memory => Memory,55 .memory => Memory,
53 .store => Store,56 .store => Store,
57 .struct_init => StructInit,
58 .field_opaque => FieldOpaque,
59 .field_struct => FieldStruct,
54 .add,60 .add,
55 .sub,61 .sub,
56 .mul,62 .mul,
...@@ -83,14 +89,26 @@ pub const Inst = struct {...@@ -83,14 +89,26 @@ pub const Inst = struct {
83 };89 };
84 }90 }
8591
86 pub fn toOptional(ref: ?Ref) Optional {92 pub fn value(ref: Ref) ?Value {
87 return if (ref) |r| @enumFromInt(@as(u32, @bitCast(r))) else .none;93 return if (ref.kind == .value) return @enumFromInt(ref.data) else null;
94 }
95
96 pub fn index(ref: Ref) ?Value {
97 return if (ref.kind == .value) return @enumFromInt(ref.data) else null;
98 }
99
100 pub fn toOptional(ref: Ref) Optional {
101 return @enumFromInt(@as(u32, @bitCast(ref)));
88 }102 }
89103
90 pub const Optional = enum(u32) {104 pub const Optional = enum(u32) {
91 none = std.math.maxInt(u32),105 none = std.math.maxInt(u32),
92 _,106 _,
93107
108 pub fn init(ref: ?Ref) Optional {
109 return if (ref) |r| @enumFromInt(@as(u32, @bitCast(r))) else .none;
110 }
111
94 pub fn unwrap(opt: Optional) ?Ref {112 pub fn unwrap(opt: Optional) ?Ref {
95 return if (opt != .none) @bitCast(@intFromEnum(opt)) else null;113 return if (opt != .none) @bitCast(@intFromEnum(opt)) else null;
96 }114 }
...@@ -104,6 +122,24 @@ pub const Inst = struct {...@@ -104,6 +122,24 @@ pub const Inst = struct {
104 pub fn eql(ref: Ref, other: Ref) bool {122 pub fn eql(ref: Ref, other: Ref) bool {
105 return @as(u32, @bitCast(ref)) == @as(u32, @bitCast(other));123 return @as(u32, @bitCast(ref)) == @as(u32, @bitCast(other));
106 }124 }
125
126 pub fn dump(ref: Ref, ip: *const InternPool) std.fmt.Formatter(dumpImpl) {
127 return .{ .data = .{ ref, ip } };
128 }
129
130 fn dumpImpl(
131 data: struct { Ref, *const InternPool },
132 comptime fmt: []const u8,
133 _: std.fmt.FormatOptions,
134 w: anytype,
135 ) !void {
136 comptime assert(fmt.len == 0);
137 const ref, const ip = data;
138 switch (ref.decode()) {
139 .value => |v| try w.print("{}", .{v.dump(ip)}),
140 .index => |i| try w.print("#{d}", .{@intFromEnum(i)}),
141 }
142 }
107 };143 };
108144
109 /// reference to another instruction in this air145 /// reference to another instruction in this air
...@@ -189,7 +225,7 @@ pub const Un = struct {...@@ -189,7 +225,7 @@ pub const Un = struct {
189 unused: Unused = .zero,225 unused: Unused = .zero,
190};226};
191227
192pub const Field = struct {228pub const FieldOpaque = struct {
193 base: Inst.Ref,229 base: Inst.Ref,
194 field: InternPool.NullTerminatedString,230 field: InternPool.NullTerminatedString,
195};231};
...@@ -224,6 +260,23 @@ pub const Memory = struct {...@@ -224,6 +260,23 @@ pub const Memory = struct {
224 ty: Type,260 ty: Type,
225};261};
226262
263pub const StructInit = struct {
264 ty: Type,
265 field_values: u32,
266
267 pub fn valuesPtr(
268 init: *const StructInit,
269 air: *const Air,
270 ) [*]const Air.Inst.Ref {
271 return @ptrCast(air.extra.list.items[init.field_values..].ptr);
272 }
273};
274
275pub const FieldStruct = struct {
276 base: Inst.Index,
277 field_index: u32,
278};
279
227pub fn deinit(air: *Air, gpa: std.mem.Allocator) void {280pub fn deinit(air: *Air, gpa: std.mem.Allocator) void {
228 defer air.* = undefined;281 defer air.* = undefined;
229 air.insts.deinit(gpa);282 air.insts.deinit(gpa);
...@@ -250,46 +303,70 @@ pub fn instPayload(air: *const Air, comptime tag: Inst.Tag, index: Inst.Index) t...@@ -250,46 +303,70 @@ pub fn instPayload(air: *const Air, comptime tag: Inst.Tag, index: Inst.Index) t
250 return air.instData(tag, index).extra_index.get(air);303 return air.instData(tag, index).extra_index.get(air);
251}304}
252305
253pub fn typeOf(air: *const Air, ref: Inst.Ref, ip: *InternPool) InternPool.Type {306pub fn typeOf(air: *const Air, ref: Inst.Ref, ip: *const InternPool) InternPool.Type {
254 return switch (ref.decode()) {307 return switch (ref.decode()) {
255 .value => |v| v.typeOf(ip),308 .value => |v| v.typeOf(ip),
256 .index => |i| air.typeOfIndex(i, ip),309 .index => |i| air.typeOfIndex(i, ip),
257 };310 };
258}311}
259312
260pub fn typeOfIndex(air: *const Air, index: Inst.Index, ip: *InternPool) InternPool.Type {313pub fn typeOfIndex(air: *const Air, index: Inst.Index, ip: *const InternPool) InternPool.Type {
261 return switch (air.instTag(index)) {314 return switch (air.instTag(index)) {
315 // These values are by definition, opaque
262 .js_global_ref, .new => .@"opaque",316 .js_global_ref, .new => .@"opaque",
263317
264 .block => {318 // These operations always produce known types
265 const items = air.instData(.block, index).slice(air);
266 return air.typeOfIndex(items[items.len - 1], ip);
267 },
268
269 inline .add, .sub, .mul, .div => |t| air.typeOf(air.instData(t, index).lhs, ip),
270
271 .lt, .gt, .eq, .bool_not => .bool,319 .lt, .gt, .eq, .bool_not => .bool,
320 .@"break" => .noreturn,
321 .field_opaque => .@"opaque",
272322
323 // For many instructions, the type is stored on it's data directly. This
324 // is avoided when possible to reduce the size of Air.
325 .memory => air.instData(.memory, index).ty,
326 .store => air.instData(.memory, air.instData(.store, index).memory).ty,
327 .@"if" => air.instData(.@"if", index).data.get(air).union_ty,
328 .struct_init => air.instData(.struct_init, index).ty,
273 .arg => air.instData(.arg, index).type,329 .arg => air.instData(.arg, index).type,
274330
331 // A handful of binary operators are lowered to a form where both sides
332 // are the same type, so the type of the left side can be used.
333 inline .add,
334 .sub,
335 .mul,
336 .div,
337 => |t| {
338 const left = air.typeOf(air.instData(t, index).lhs, ip);
339 if (std.debug.runtime_safety) {
340 assert(left == air.typeOf(air.instData(t, index).rhs, ip));
341 }
342 return left;
343 },
344 // Negate follows a similar pattern to the above
275 .negate => air.typeOf(air.instData(.negate, index).operand, ip),345 .negate => air.typeOf(air.instData(.negate, index).operand, ip),
276346
277 .@"break" => .noreturn,347 // TODO: taking the last instruction is likely wrong.
278348 // The last instruction is going to be a .break and be of type .noreturn
279 // todo:349 .block => {
280 .field => .@"opaque",350 const items = air.instData(.block, index).slice(air);
351 assert(items.len > 0); // empty blocks should always be `Value.void`
352 return air.typeOfIndex(items[items.len - 1], ip);
353 },
281354
355 // Calls return the return type of the operand.
282 .call => {356 .call => {
283 const base_type = air.typeOf(air.instData(.call, index).base, ip);357 const base_type = air.typeOf(air.instData(.call, index).base, ip);
284 if (base_type == .@"opaque") return .@"opaque";358 if (base_type == .@"opaque") return .@"opaque";
285 return base_type.value().toKey(ip).type_function.return_type;359 return base_type.toFunctionPrototype(ip).?.return_type;
286 },360 },
287361
288 .memory => air.instData(.memory, index).ty,362 // Field accesses on structs are special-cased by storing the field
289363 // index instead of the name string. This avoids creating hashmaps.
290 .store => air.instData(.memory, air.instData(.store, index).memory).ty,364 .field_struct => {
291365 const inst = air.instData(.field_struct, index);
292 .@"if" => air.instData(.@"if", index).data.get(air).union_ty,366 const base_type = air.typeOfIndex(inst.base, ip)
367 .toStructType(ip) orelse unreachable; // not a struct
368 return base_type.field_types[inst.field_index];
369 },
293 };370 };
294}371}
295372
src/core/Ast.Dump.zig+56-26
...@@ -81,33 +81,41 @@ fn dumpNode(writer: anytype, state: *State, node: NodeIndex) @TypeOf(writer).Err...@@ -81,33 +81,41 @@ fn dumpNode(writer: anytype, state: *State, node: NodeIndex) @TypeOf(writer).Err
81 }81 }
82 try tty.setColor(writer, .reset);82 try tty.setColor(writer, .reset);
83 },83 },
84 .fn_decl => {84 .function => switch (tag) {
85 try writer.writeAll(" ");85 else => unreachable,
86 const fn_decl = state.ast.fullFnDecl(node);86 inline .fn_decl, .fn_expr => |comptime_tag| {
87 if (fn_decl.public) {87 const fn_decl = if (comptime_tag == .fn_decl)
88 try writer.writeAll("[pub] ");88 state.ast.fullFnDecl(node)
89 }89 else
90 try tty.setColor(writer, .magenta);90 state.ast.fullFnExpr(node);
91 try writer.writeAll(fn_decl.id);91 if (comptime_tag == .fn_decl) {
92 try tty.setColor(writer, .reset);92 try writer.writeAll(" ");
93 try writer.writeAll(" {");93 if (fn_decl.public) {
94 {94 try writer.writeAll("[pub] ");
95 state.indent += 1;95 }
96 defer state.indent -= 1;96 try tty.setColor(writer, .magenta);
97 try state.newline(writer);97 try writer.writeAll(fn_decl.id);
98 try writer.writeAll("params:");98 try tty.setColor(writer, .reset);
99 try dumpNodeList(writer, state, fn_decl.params, '[', ']');99 }
100 if (fn_decl.return_type) |return_type| {100 try writer.writeAll(" {");
101 {
102 state.indent += 1;
103 defer state.indent -= 1;
104 try state.newline(writer);
105 try writer.writeAll("params:");
106 try dumpNodeList(writer, state, fn_decl.params, '[', ']');
107 if (fn_decl.return_type) |return_type| {
108 try state.newline(writer);
109 try writer.writeAll("return_type: ");
110 try dumpNode(writer, state, return_type);
111 }
101 try state.newline(writer);112 try state.newline(writer);
102 try writer.writeAll("return_type: ");113 try writer.writeAll("body: ");
103 try dumpNode(writer, state, return_type);114 try dumpNode(writer, state, fn_decl.body);
104 }115 }
105 try state.newline(writer);116 try state.newline(writer);
106 try writer.writeAll("body: ");117 try writer.writeAll("}");
107 try dumpNode(writer, state, fn_decl.body);118 },
108 }
109 try state.newline(writer);
110 try writer.writeAll("}");
111 },119 },
112 .list => {120 .list => {
113 const list = data.list.slice(state.ast);121 const list = data.list.slice(state.ast);
...@@ -115,6 +123,7 @@ fn dumpNode(writer: anytype, state: *State, node: NodeIndex) @TypeOf(writer).Err...@@ -115,6 +123,7 @@ fn dumpNode(writer: anytype, state: *State, node: NodeIndex) @TypeOf(writer).Err
115 .root,123 .root,
116 .block_void,124 .block_void,
117 .block_implicit_return,125 .block_implicit_return,
126 .struct_decl,
118 => try dumpNodeList(writer, state, list, '{', '}'),127 => try dumpNodeList(writer, state, list, '{', '}'),
119 .builtin_call,128 .builtin_call,
120 => try dumpNodeList(writer, state, list, '(', ')'),129 => try dumpNodeList(writer, state, list, '(', ')'),
...@@ -155,8 +164,10 @@ fn dumpNode(writer: anytype, state: *State, node: NodeIndex) @TypeOf(writer).Err...@@ -155,8 +164,10 @@ fn dumpNode(writer: anytype, state: *State, node: NodeIndex) @TypeOf(writer).Err
155 try tty.setColor(writer, .magenta);164 try tty.setColor(writer, .magenta);
156 try writer.writeAll(param_decl.id);165 try writer.writeAll(param_decl.id);
157 try tty.setColor(writer, .reset);166 try tty.setColor(writer, .reset);
158 try writer.writeAll(": ");167 if (param_decl.type) |ty| {
159 try dumpNode(writer, state, param_decl.type);168 try writer.writeAll(": ");
169 try dumpNode(writer, state, ty);
170 }
160 },171 },
161 .bin => {172 .bin => {
162 try writer.writeAll(" (");173 try writer.writeAll(" (");
...@@ -244,6 +255,25 @@ fn dumpNode(writer: anytype, state: *State, node: NodeIndex) @TypeOf(writer).Err...@@ -244,6 +255,25 @@ fn dumpNode(writer: anytype, state: *State, node: NodeIndex) @TypeOf(writer).Err
244 try dumpNodeList(writer, state, arg_list, '[', ']');255 try dumpNodeList(writer, state, arg_list, '[', ']');
245 try writer.writeAll(")");256 try writer.writeAll(")");
246 },257 },
258 .struct_init => {
259 const base = data.struct_init.base;
260 const extra = state.ast.extraData(data.struct_init.extra, Ast.SubList);
261 const fields = extra.slice(state.ast);
262
263 try writer.writeAll("(");
264 try writer.writeAll("base: ");
265 if (base.unwrap()) |b| {
266 try dumpNode(writer, state, b);
267 } else {
268 try tty.setColor(writer, .blue);
269 try writer.writeAll("<implicit>");
270 try tty.setColor(writer, .reset);
271 }
272 try writer.writeAll(", ");
273 try writer.writeAll("fields:");
274 try dumpNodeList(writer, state, fields, '[', ']');
275 try writer.writeAll(")");
276 },
247 .@"if" => {277 .@"if" => {
248 const branch = data.@"if";278 const branch = data.@"if";
249 const if_data = state.ast.extraData(branch.extra, Ast.If);279 const if_data = state.ast.extraData(branch.extra, Ast.If);
src/core/Ast.zig+169-55
...@@ -75,6 +75,10 @@ pub const Node = struct {...@@ -75,6 +75,10 @@ pub const Node = struct {
75 /// `fn foo(x: i32) -> i32 { rhs }`75 /// `fn foo(x: i32) -> i32 { rhs }`
76 /// Params and return type are stored in extra data76 /// Params and return type are stored in extra data
77 fn_decl,77 fn_decl,
78 /// Function expression
79 /// `fn (x, y) { x + y }`
80 /// Params and return type are stored in extra data
81 fn_expr,
78 /// 'name: type' declaration82 /// 'name: type' declaration
79 /// Main token is the identifier83 /// Main token is the identifier
80 param_decl,84 param_decl,
...@@ -208,6 +212,12 @@ pub const Node = struct {...@@ -208,6 +212,12 @@ pub const Node = struct {
208 pure,212 pure,
209 /// `assert expr`. main_token is the operator.213 /// `assert expr`. main_token is the operator.
210 assert,214 assert,
215 /// Struct Declaration. main_token is the id if set
216 struct_decl,
217 /// Struct Field.
218 struct_field,
219 /// Struct Init
220 struct_init,
211221
212 pub fn isAssignBin(op: Tag) bool {222 pub fn isAssignBin(op: Tag) bool {
213 return switch (op) {223 return switch (op) {
...@@ -264,71 +274,77 @@ pub const Node = struct {...@@ -264,71 +274,77 @@ pub const Node = struct {
264 };274 };
265275
266 pub const data_tags = std.enums.directEnumArray(Tag, Data.FieldEnum, 0, .{276 pub const data_tags = std.enums.directEnumArray(Tag, Data.FieldEnum, 0, .{
267 .root = .list,277 // sort-lines:start
268 .block_void = .list,278 .@"and" = .bin,
269 .block_implicit_return = .list,279 .@"break" = .@"break",
270 .var_decl = .var_decl,280 .@"comptime" = .un,
271 .fn_decl = .fn_decl,
272 .param_decl = .param_decl,
273 .id = .none,
274 .number_int = .none,
275 .number_float = .none,
276 .string = .none,
277 .multiline_string = .list,
278 .call = .call,
279 .builtin_call = .list,
280 .@"if" = .@"if",281 .@"if" = .@"if",
281 .field = .field,282 .@"or" = .bin,
282 .index = .bin,
283 .@"return" = .@"return",283 .@"return" = .@"return",
284 .@"break" = .@"break",
285 .group = .un,
286 .add = .bin,284 .add = .bin,
287 .sub = .bin,285 .add_assign = .bin,
288 .mul = .bin,286 .and_assign = .bin,
289 .div = .bin,287 .assert = .un,
290 .rem = .bin,288 .assign = .bin,
291 .pow = .bin,
292 .@"and" = .bin,
293 .@"or" = .bin,
294 .bit_and = .bin,289 .bit_and = .bin,
290 .bit_and_assign = .bin,
291 .bit_not = .un,
295 .bit_or = .bin,292 .bit_or = .bin,
293 .bit_or_assign = .bin,
296 .bit_xor = .bin,294 .bit_xor = .bin,
297 .shl = .bin,295 .bit_xor_assign = .bin,
298 .shr = .bin,296 .block_implicit_return = .list,
297 .block_void = .list,
298 .bool_not = .un,
299 .builtin_call = .list,
300 .call = .call,
301 .div = .bin,
302 .div_assign = .bin,
299 .eq = .bin,303 .eq = .bin,
300 .ne = .bin,304 .field = .field,
301 .lt = .bin,305 .fn_decl = .function,
302 .le = .bin,306 .fn_expr = .function,
303 .gt = .bin,
304 .ge = .bin,307 .ge = .bin,
305 .assign = .bin,308 .group = .un,
306 .add_assign = .bin,309 .gt = .bin,
307 .sub_assign = .bin,310 .id = .none,
311 .index = .bin,
312 .label = .label,
313 .le = .bin,
314 .lt = .bin,
315 .mul = .bin,
308 .mul_assign = .bin,316 .mul_assign = .bin,
309 .div_assign = .bin,317 .multiline_string = .list,
310 .rem_assign = .bin,318 .ne = .bin,
311 .pow_assign = .bin,319 .negate = .un,
312 .and_assign = .bin,320 .number_float = .none,
321 .number_int = .none,
313 .or_assign = .bin,322 .or_assign = .bin,
314 .bit_and_assign = .bin,323 .param_decl = .param_decl,
315 .bit_or_assign = .bin,324 .pow = .bin,
316 .bit_xor_assign = .bin,325 .pow_assign = .bin,
326 .pure = .un,
327 .rem = .bin,
328 .rem_assign = .bin,
329 .root = .list,
330 .shl = .bin,
317 .shl_assign = .bin,331 .shl_assign = .bin,
332 .shr = .bin,
318 .shr_assign = .bin,333 .shr_assign = .bin,
319 .bool_not = .un,334 .string = .none,
320 .negate = .un,335 .struct_decl = .list,
321 .bit_not = .un,336 .struct_field = .un,
322 .@"comptime" = .un,337 .struct_init = .struct_init,
323 .pure = .un,338 .sub = .bin,
324 .assert = .un,339 .sub_assign = .bin,
325 .label = .label,340 .var_decl = .var_decl,
341 // sort-lines:end
326 });342 });
327343
328 pub const Data = union {344 pub const Data = union {
329 none: struct { Unused = .zero, Unused = .zero },345 none: struct { Unused = .zero, Unused = .zero },
330 fn_decl: struct {346 function: struct {
331 // A `FnDecl`347 /// A `Fn`
332 extra: ExtraIndex,348 extra: ExtraIndex,
333 body: NodeIndex,349 body: NodeIndex,
334 },350 },
...@@ -339,7 +355,7 @@ pub const Node = struct {...@@ -339,7 +355,7 @@ pub const Node = struct {
339 init: NodeIndex,355 init: NodeIndex,
340 },356 },
341 param_decl: struct {357 param_decl: struct {
342 type: NodeIndex,358 type: OptionalNodeIndex,
343 unused: Unused = .zero,359 unused: Unused = .zero,
344 },360 },
345 bin: struct {361 bin: struct {
...@@ -371,6 +387,13 @@ pub const Node = struct {...@@ -371,6 +387,13 @@ pub const Node = struct {
371 /// A `Call`387 /// A `Call`
372 extra: ExtraIndex,388 extra: ExtraIndex,
373 },389 },
390 struct_init: struct {
391 /// .none => '.{ }'
392 /// else => 'base{ ... }'
393 base: OptionalNodeIndex,
394 /// A `SubList`
395 extra: ExtraIndex,
396 },
374 @"if": struct {397 @"if": struct {
375 cond: NodeIndex,398 cond: NodeIndex,
376 /// A `If`399 /// A `If`
...@@ -457,12 +480,14 @@ pub const SubList = struct {...@@ -457,12 +480,14 @@ pub const SubList = struct {
457 start: u32,480 start: u32,
458 len: u32,481 len: u32,
459482
483 pub const empty: SubList = .{ .len = 0, .start = 0 };
484
460 pub fn slice(self: SubList, ast: *const Ast) []const u32 {485 pub fn slice(self: SubList, ast: *const Ast) []const u32 {
461 return ast.extra[self.start..][0..self.len];486 return ast.extra[self.start..][0..self.len];
462 }487 }
463};488};
464489
465pub const FnDecl = struct {490pub const Fn = struct {
466 params: SubList,491 params: SubList,
467 return_type: OptionalNodeIndex,492 return_type: OptionalNodeIndex,
468};493};
...@@ -489,6 +514,13 @@ pub const full = struct {...@@ -489,6 +514,13 @@ pub const full = struct {
489 body: NodeIndex,514 body: NodeIndex,
490 };515 };
491516
517 pub const FnExpr = struct {
518 start_token: TokenIndex,
519 params: []const NodeIndex,
520 return_type: ?NodeIndex,
521 body: NodeIndex,
522 };
523
492 pub const VarDecl = struct {524 pub const VarDecl = struct {
493 start_token: TokenIndex,525 start_token: TokenIndex,
494 public: bool,526 public: bool,
...@@ -498,11 +530,29 @@ pub const full = struct {...@@ -498,11 +530,29 @@ pub const full = struct {
498 init: NodeIndex,530 init: NodeIndex,
499 };531 };
500532
501 pub const ParamDecl = struct {533 pub const StructDecl = struct {
534 start_token: TokenIndex,
535 public: bool,
536 id: []const u8,
537 fields: []const NodeIndex,
538 };
539
540 pub const StructField = struct {
502 start_token: TokenIndex,541 start_token: TokenIndex,
503 id: []const u8,542 id: []const u8,
504 type: NodeIndex,543 type: NodeIndex,
505 };544 };
545
546 pub const StructInit = struct {
547 type: ?NodeIndex,
548 fields: []const NodeIndex,
549 };
550
551 pub const ParamDecl = struct {
552 start_token: TokenIndex,
553 id: []const u8,
554 type: ?NodeIndex,
555 };
506};556};
507557
508pub fn deinit(ast: *Ast, gpa: std.mem.Allocator) void {558pub fn deinit(ast: *Ast, gpa: std.mem.Allocator) void {
...@@ -531,10 +581,32 @@ pub fn fullFnDecl(ast: Ast, node: NodeIndex) full.FnDecl {...@@ -531,10 +581,32 @@ pub fn fullFnDecl(ast: Ast, node: NodeIndex) full.FnDecl {
531 const public = tok_tags[main_token -| 1] == .kw_pub;581 const public = tok_tags[main_token -| 1] == .kw_pub;
532 assert(tok_tags[main_token] == .kw_fn);582 assert(tok_tags[main_token] == .kw_fn);
533 assert(tok_tags[main_token + 1] == .id);583 assert(tok_tags[main_token + 1] == .id);
584 assert(tok_tags[main_token + 2] == .start_paren);
585 const id = ast.tokenSlice(main_token + 1);
586
587 const data = ast.nodes.items(.data)[node].function;
588 const extra = ast.extraData(data.extra, Fn);
589
590 return .{
591 .start_token = if (public) main_token - 1 else main_token,
592 .public = public,
593 .id = id,
594 .body = data.body,
595 .params = extra.params.slice(&ast),
596 .return_type = extra.return_type.unwrap(),
597 };
598}
599
600pub fn fullFnExpr(ast: Ast, node: NodeIndex) full.FnDecl {
601 const tok_tags = ast.tokens.items(.tag);
602
603 const main_token = ast.nodes.items(.main_token)[node];
604 const public = tok_tags[main_token -| 1] == .kw_pub;
605 assert(tok_tags[main_token] == .kw_fn);
534 const id = ast.tokenSlice(main_token + 1);606 const id = ast.tokenSlice(main_token + 1);
535607
536 const data = ast.nodes.items(.data)[node].fn_decl;608 const data = ast.nodes.items(.data)[node].function;
537 const extra = ast.extraData(data.extra, FnDecl);609 const extra = ast.extraData(data.extra, Fn);
538610
539 return .{611 return .{
540 .start_token = if (public) main_token - 1 else main_token,612 .start_token = if (public) main_token - 1 else main_token,
...@@ -546,6 +618,25 @@ pub fn fullFnDecl(ast: Ast, node: NodeIndex) full.FnDecl {...@@ -546,6 +618,25 @@ pub fn fullFnDecl(ast: Ast, node: NodeIndex) full.FnDecl {
546 };618 };
547}619}
548620
621pub fn fullStructDecl(ast: Ast, node: NodeIndex) full.StructDecl {
622 const tok_tags = ast.tokens.items(.tag);
623
624 const main_token = ast.nodes.items(.main_token)[node];
625 const public = tok_tags[main_token -| 1] == .kw_pub;
626 assert(tok_tags[main_token] == .kw_struct);
627 assert(tok_tags[main_token + 1] == .id);
628 const id = ast.tokenSlice(main_token + 1);
629
630 const data = ast.nodes.items(.data)[node].list;
631
632 return .{
633 .start_token = if (public) main_token - 1 else main_token,
634 .public = public,
635 .id = id,
636 .fields = data.slice(&ast),
637 };
638}
639
549pub fn fullVarDecl(ast: Ast, node: NodeIndex) full.VarDecl {640pub fn fullVarDecl(ast: Ast, node: NodeIndex) full.VarDecl {
550 const tok_tags = ast.tokens.items(.tag);641 const tok_tags = ast.tokens.items(.tag);
551642
...@@ -584,7 +675,30 @@ pub fn fullParamDecl(ast: Ast, node: NodeIndex) full.ParamDecl {...@@ -584,7 +675,30 @@ pub fn fullParamDecl(ast: Ast, node: NodeIndex) full.ParamDecl {
584 return .{675 return .{
585 .start_token = main_token,676 .start_token = main_token,
586 .id = id,677 .id = id,
587 .type = data.type,678 .type = data.type.unwrap(),
679 };
680}
681
682pub fn fullStructField(ast: Ast, node: NodeIndex) full.StructField {
683 const tok_tags = ast.tokens.items(.tag);
684
685 const main_token = ast.nodes.items(.main_token)[node];
686 assert(tok_tags[main_token] == .id);
687 const id = ast.tokenSlice(main_token);
688 const data = ast.nodes.items(.data)[node].un;
689 return .{
690 .start_token = main_token,
691 .id = id,
692 .type = data.expr,
693 };
694}
695
696pub fn fullStructInit(ast: Ast, node: NodeIndex) full.StructInit {
697 assert(ast.nodeTag(node) == .struct_init);
698 const data = ast.nodeData(node).struct_init;
699 return .{
700 .type = data.base.unwrap(),
701 .fields = ast.extraData(data.extra, SubList).slice(&ast),
588 };702 };
589}703}
590704
src/core/Comp.zig+156-95
...@@ -1,4 +1,7 @@...@@ -1,4 +1,7 @@
1//! Compilation1//! Language Compilation Unit
2//!
3//! This is all of the state for a compilation, which starts at one or more URLs
4//! (entry points), and will eventually emit one or more object files.
25
3/// General purpose allocator6/// General purpose allocator
4gpa: Allocator,7gpa: Allocator,
...@@ -7,13 +10,9 @@ intern_pool: InternPool = .{},...@@ -7,13 +10,9 @@ intern_pool: InternPool = .{},
7/// All files in the compilation10/// All files in the compilation
8files: std.StringArrayHashMapUnmanaged(*File) = .{},11files: std.StringArrayHashMapUnmanaged(*File) = .{},
912
10/// # of sema instructions in this compilation cycle
11analyzed_instructions: usize = 0,
12
13options: Options = .{},13options: Options = .{},
1414
15// TODO: consider computing this dynamically per-link15backend: Backend,
16current_compile_decls: std.ArrayListUnmanaged(Decl.Index) = .{},
1716
18pub fn init(comp: *Comp) !void {17pub fn init(comp: *Comp) !void {
19 try comp.intern_pool.init(comp.gpa);18 try comp.intern_pool.init(comp.gpa);
...@@ -25,9 +24,47 @@ pub fn deinit(comp: *Comp) void {...@@ -25,9 +24,47 @@ pub fn deinit(comp: *Comp) void {
25 file.deinit(comp.gpa);24 file.deinit(comp.gpa);
26 }25 }
27 comp.files.deinit(comp.gpa);26 comp.files.deinit(comp.gpa);
28 comp.current_compile_decls.deinit(comp.gpa);27 comp.backend.deinit(comp);
28}
29
30/// This is the entry point for compilation.
31/// Returns the Decl of the top-level code.
32pub fn semaEntryPoint(comp: *Comp, file: *File) !Decl.Index {
33 var report: Report = undefined;
34 const teg = try file.getTeg(comp, &report);
35
36 // TODO, move this elsewhere. we should print all errors and warnings at once
37 if (teg.report.warnings.len > 0) {
38 try teg.report.dumpToStdErr(comp);
39 }
40
41 const ns = try comp.indexTegModule(file, .main_module_inst);
42 const decl_index = comp.intern_pool.namespacePtr(ns).lookupDirect(comp, .empty) orelse
43 unreachable; // Transform should always emit a top-level function
44 const decl = comp.intern_pool.declPtr(decl_index);
45
46 var arena = std.heap.ArenaAllocator.init(comp.gpa);
47 defer arena.deinit();
48
49 var sema: Sema = .{
50 .comp = comp,
51 .gpa = comp.gpa,
52 .code = teg,
53 .arena = arena.allocator(),
54 .ip = &comp.intern_pool,
55 .decl = decl,
56 .decl_index = decl_index,
57 .namespace_index = ns,
58 };
59 try sema.init();
60 defer sema.deinit();
61 _ = try sema.analyzeDeclaration();
62 try sema.finalize();
63
64 return decl_index;
29}65}
3066
67/// Add or retrieve a file to the compilation via it's file path.
31pub fn getFile(comp: *Comp, absolute_file_path: []const u8) !*File {68pub fn getFile(comp: *Comp, absolute_file_path: []const u8) !*File {
32 assert(std.fs.path.isAbsolute(absolute_file_path));69 assert(std.fs.path.isAbsolute(absolute_file_path));
33 const gop = try comp.files.getOrPut(comp.gpa, absolute_file_path);70 const gop = try comp.files.getOrPut(comp.gpa, absolute_file_path);
...@@ -38,8 +75,8 @@ pub fn getFile(comp: *Comp, absolute_file_path: []const u8) !*File {...@@ -38,8 +75,8 @@ pub fn getFile(comp: *Comp, absolute_file_path: []const u8) !*File {
38 file.* = .{75 file.* = .{
39 .file_path = absolute_file_path,76 .file_path = absolute_file_path,
40 .path_digest = null,77 .path_digest = null,
41 .root_module = .none,78 .root_namespace = .none,
42 .status = .never_loader,79 .status = .never_loaded,
43 .source = null,80 .source = null,
44 .ast = null,81 .ast = null,
45 .teg = null,82 .teg = null,
...@@ -53,13 +90,15 @@ pub const Source = struct {...@@ -53,13 +90,15 @@ pub const Source = struct {
53 stat: Stat,90 stat: Stat,
54};91};
5592
93/// A file in the compilation and it's associated data structures. Ast and Teg
94/// are not guaranteed to be loaded, even if it's `status` says `success`.
56pub const File = struct {95pub const File = struct {
57 /// TODO: change this to a URL96 /// TODO: change this to a URL
58 file_path: []const u8,97 file_path: []const u8,
5998
60 root_module: Decl.Index.Optional,99 root_namespace: Decl.Index.Optional,
61 status: enum {100 status: enum {
62 never_loader,101 never_loaded,
63 /// A failure occurred while loading the file. Retrying might succeed.102 /// A failure occurred while loading the file. Retrying might succeed.
64 retryable_failure,103 retryable_failure,
65 /// Errors during parse104 /// Errors during parse
...@@ -110,7 +149,7 @@ pub const File = struct {...@@ -110,7 +149,7 @@ pub const File = struct {
110 // used for error reporting. We need to keep the stat fields stale so that149 // used for error reporting. We need to keep the stat fields stale so that
111 // transformFile can know to regenerate Teg.150 // transformFile can know to regenerate Teg.
112151
113 const loaded_source = Source{152 const loaded_source: Source = .{
114 .code = source,153 .code = source,
115 .stat = .{154 .stat = .{
116 .size = stat.size,155 .size = stat.size,
...@@ -212,24 +251,34 @@ pub const File = struct {...@@ -212,24 +251,34 @@ pub const File = struct {
212 }251 }
213};252};
214253
254/// A declaration is a named value which also represents the smallest unit of
255/// compilation/re-compilation. A declaration contains zero or more `Func`
256/// objects, which represent the different callable values the declaration has.
257///
258/// A special type of declaration is the top-level declaration, which has the
259/// name "" (empty string). This is used to facilitate top-level side effects.
215pub const Decl = struct {260pub const Decl = struct {
216 name: InternPool.NullTerminatedString,261 name: InternPool.NullTerminatedString,
217 val: ?InternPool.Value,262
263 /// The Value that this declaration evaluates to.
264 raw_value: ?InternPool.Value,
265
218 /// The direct parent namespace of the Decl. In the case of the Decl266 /// The direct parent namespace of the Decl. In the case of the Decl
219 /// corresponding to a file, this is the namespace of the struct, since267 /// corresponding to a file, this is the namespace of the struct, since
220 /// there is no parent.268 /// there is no parent.
221 src_namespace: Namespace.Index,269 src_namespace: Namespace.Index,
222270
223 /// The AST node index of this declaration.271 // /// The AST node index of this declaration.
224 /// Must be recomputed when the corresponding source file is modified.272 // /// Must be recomputed when the corresponding source file is modified.
225 // src_node: Ast.NodeIndex,273 // src_node: Ast.NodeIndex,
226 /// Line number corresponding to `src_node`. Stored separately so that source files274
227 /// do not need to be loaded into memory in order to compute debug line numbers.275 // /// Line number corresponding to `src_node`. Stored separately so that source files
228 /// This value is absolute.276 // /// do not need to be loaded into memory in order to compute debug line numbers.
277 // /// This value is absolute.
229 // src_line: u32,278 // src_line: u32,
230 /// Index of the ZIR `declaration` instruction from which this `Decl` was created.279
231 /// For the root `Decl` of a `File` and legacy anonymous decls, this is `.none`.280 /// Index of the Teg `declaration` instruction from which this `Decl` was created.
232 teg_decl_index: InternPool.TrackedInst.Index.Optional,281 teg_decl_index: InternPool.TrackedInst.Index,
233282
234 /// Represents the "shallow" analysis status. For example, for decls that are functions,283 /// Represents the "shallow" analysis status. For example, for decls that are functions,
235 /// the function type is analyzed with this set to `in_progress`; however, the semantic284 /// the function type is analyzed with this set to `in_progress`; however, the semantic
...@@ -242,20 +291,21 @@ pub const Decl = struct {...@@ -242,20 +291,21 @@ pub const Decl = struct {
242 /// Semantic analysis for this Decl is running right now.291 /// Semantic analysis for this Decl is running right now.
243 /// This state detects dependency loops.292 /// This state detects dependency loops.
244 in_progress,293 in_progress,
245 /// Semantic analysis for this Decl is running right now294 /// Semantic analysis for this Decl is running right now, and has
246 /// But .value is valid295 /// completed enough to reify the type of the result, but not the result
247 in_progress_prototype_available,296 /// itself.
297 in_progress_type_available,
248 /// The file corresponding to this Decl had a parse error or ZIR error.298 /// The file corresponding to this Decl had a parse error or ZIR error.
249 /// There will be a corresponding ErrorMsg in Zcu.failed_files.299 /// There will be a corresponding ErrorMsg in Zcu.failed_files.
250 file_failure,300 // file_failure,
251 /// This Decl might be OK but it depends on another one which did not301 /// This Decl might be OK but it depends on another one which did not
252 /// successfully complete semantic analysis.302 /// successfully complete semantic analysis.
253 dependency_failure,303 // dependency_failure,
254 /// Semantic analysis failure.304 /// Semantic analysis failure.
255 /// There will be a corresponding ErrorMsg in Zcu.failed_decls.305 /// There will be a corresponding ErrorMsg in Zcu.failed_decls.
256 sema_failure,306 // sema_failure,
257 /// There will be a corresponding ErrorMsg in Zcu.failed_decls.307 /// There will be a corresponding ErrorMsg in Zcu.failed_decls.
258 codegen_failure,308 // codegen_failure,
259 /// Semantic analysis and constant value codegen of this Decl has309 /// Semantic analysis and constant value codegen of this Decl has
260 /// succeeded. However, the Decl may be outdated due to an in-progress310 /// succeeded. However, the Decl may be outdated due to an in-progress
261 /// update. Note that for a function, this does not mean codegen of the311 /// update. Note that for a function, this does not mean codegen of the
...@@ -264,25 +314,35 @@ pub const Decl = struct {...@@ -264,25 +314,35 @@ pub const Decl = struct {
264 complete,314 complete,
265 },315 },
266316
267 codegen: ?JsTree = null,317 pub fn value(decl: *const Decl) InternPool.Value {
268318 assert(decl.analysis == .complete);
269 is_pub: bool,319 return decl.raw_value orelse unreachable;
320 }
270321
271 /// What kind of a declaration this is.322 pub fn @"type"(decl: *const Decl, ip: *const InternPool) InternPool.Type {
272 kind: Kind,323 return switch (decl.analysis) {
324 .unreferenced => unreachable,
325 .in_progress => unreachable,
326 .in_progress_prototype_available => decl.raw_value.?.toTypeUnsafe(ip),
327 .complete => decl.raw_value.?.typeOf(ip),
328 };
329 }
273330
274 pub fn deinit(decl: *Decl, gpa: Allocator) void {331 pub fn deinit(decl: *Decl, gpa: Allocator) void {
332 _ = gpa; // autofix
275 defer decl.* = undefined;333 defer decl.* = undefined;
276 if (decl.codegen) |*cg| cg.deinit(gpa);334 // if (decl.codegen) |*cg| cg.deinit(gpa);
277 }335 }
278336
279 pub const Kind = enum {
280 named,
281 };
282
283 pub const Index = InternPool.DeclIndex;337 pub const Index = InternPool.DeclIndex;
284};338};
285339
340pub const Func = struct {};
341
342/// A namespace is a container for declarations. Currently the only form of
343/// namespace that exists is file-scope. In the future, the `module` keyword
344/// will create nested namespaces, and it is probable that `struct` and other
345/// constructs will also create namespaces.
286pub const Namespace = struct {346pub const Namespace = struct {
287 parent: Index.Optional,347 parent: Index.Optional,
288 file_scope: *File,348 file_scope: *File,
...@@ -300,7 +360,12 @@ pub const Namespace = struct {...@@ -300,7 +360,12 @@ pub const Namespace = struct {
300 return std.hash.uint32(@intFromEnum(decl.name));360 return std.hash.uint32(@intFromEnum(decl.name));
301 }361 }
302362
303 pub fn eql(ctx: @This(), a_decl_index: Decl.Index, b_decl_index: Decl.Index, b_index: usize) bool {363 pub fn eql(
364 ctx: @This(),
365 a_decl_index: Decl.Index,
366 b_decl_index: Decl.Index,
367 b_index: usize,
368 ) bool {
304 _ = b_index;369 _ = b_index;
305 const a_decl = ctx.comp.intern_pool.declPtr(a_decl_index);370 const a_decl = ctx.comp.intern_pool.declPtr(a_decl_index);
306 const b_decl = ctx.comp.intern_pool.declPtr(b_decl_index);371 const b_decl = ctx.comp.intern_pool.declPtr(b_decl_index);
...@@ -316,22 +381,30 @@ pub const Namespace = struct {...@@ -316,22 +381,30 @@ pub const Namespace = struct {
316 return std.hash.uint32(@intFromEnum(s));381 return std.hash.uint32(@intFromEnum(s));
317 }382 }
318383
319 pub fn eql(self: @This(), a: InternPool.NullTerminatedString, b_decl_index: Decl.Index, b_index: usize) bool {384 pub fn eql(
385 self: @This(),
386 a: InternPool.NullTerminatedString,
387 b_decl_index: Decl.Index,
388 b_index: usize,
389 ) bool {
320 _ = b_index;390 _ = b_index;
321 return a == self.comp.intern_pool.declPtr(b_decl_index).name;391 return a == self.comp.intern_pool.declPtr(b_decl_index).name;
322 }392 }
323 };393 };
324394
325 pub fn lookupDirect(ns: *Namespace, comp: *Comp, name: InternPool.NullTerminatedString) ?Decl.Index {395 pub fn lookupDirect(
396 ns: *Namespace,
397 comp: *Comp,
398 name: InternPool.NullTerminatedString,
399 ) ?Decl.Index {
326 return ns.decls.getKeyAdapted(name, Namespace.DeclNameAdapter{ .comp = comp });400 return ns.decls.getKeyAdapted(name, Namespace.DeclNameAdapter{ .comp = comp });
327 }401 }
328402
329 pub const Index = InternPool.NamespaceIndex;403 pub const Index = InternPool.NamespaceIndex;
330};404};
331405
332/// Index a module in a Teg.406/// Index a Teg's module scope as a namespace. This prepares `Decl` objects for
333/// When a File's Teg is loaded, we call this on the root module.407/// every declaration, but does not begin analysis of them.
334/// submodules are analyzed lazily.
335pub fn indexTegModule(comp: *Comp, file: *File, module_index: Teg.Inst.Index) !Namespace.Index {408pub fn indexTegModule(comp: *Comp, file: *File, module_index: Teg.Inst.Index) !Namespace.Index {
336 const teg = file.teg.?;409 const teg = file.teg.?;
337410
...@@ -351,12 +424,10 @@ pub fn indexTegModule(comp: *Comp, file: *File, module_index: Teg.Inst.Index) !N...@@ -351,12 +424,10 @@ pub fn indexTegModule(comp: *Comp, file: *File, module_index: Teg.Inst.Index) !N
351 const tracked_inst = try comp.intern_pool.trackInst(comp.gpa, file, decl_index);424 const tracked_inst = try comp.intern_pool.trackInst(comp.gpa, file, decl_index);
352 const decl = try comp.intern_pool.createDecl(comp.gpa, .{425 const decl = try comp.intern_pool.createDecl(comp.gpa, .{
353 .name = name,426 .name = name,
354 .val = null,427 .raw_value = null,
355 .src_namespace = ns,428 .src_namespace = ns,
356 .teg_decl_index = tracked_inst.toOptional(),429 .teg_decl_index = tracked_inst,
357 .analysis = .unreferenced,430 .analysis = .unreferenced,
358 .is_pub = false, // todo:
359 .kind = .named,
360 });431 });
361 try ns_ptr.decls.putNoClobberContext(comp.gpa, decl, void{}, .{ .comp = comp });432 try ns_ptr.decls.putNoClobberContext(comp.gpa, decl, void{}, .{ .comp = comp });
362 }433 }
...@@ -364,42 +435,7 @@ pub fn indexTegModule(comp: *Comp, file: *File, module_index: Teg.Inst.Index) !N...@@ -364,42 +435,7 @@ pub fn indexTegModule(comp: *Comp, file: *File, module_index: Teg.Inst.Index) !N
364 return ns;435 return ns;
365}436}
366437
367pub fn semaEntryPoint(comp: *Comp, file: *File) !void {438pub fn codeGenDecl(c: *Comp, value: InternPool.Value, air: *const Air) !void {
368 var report: Report = undefined;
369 const teg = try file.getTeg(comp, &report);
370
371 // TODO, move this elsewhere. we should print all errors and warnings at once
372 if (teg.report.warnings.len > 0) {
373 try teg.report.dumpToStdErr(comp);
374 }
375
376 const ns = try comp.indexTegModule(file, .main_module_inst);
377 const decl_index = comp.intern_pool.namespacePtr(ns).lookupDirect(comp, .empty) orelse
378 @panic("no top level code?");
379 const decl = comp.intern_pool.declPtr(decl_index);
380
381 assert(comp.current_compile_decls.items.len == 0);
382
383 var arena = std.heap.ArenaAllocator.init(comp.gpa);
384 defer arena.deinit();
385
386 var sema: Sema = .{
387 .comp = comp,
388 .gpa = comp.gpa,
389 .code = teg,
390 .arena = arena.allocator(),
391 .ip = &comp.intern_pool,
392 .decl = decl,
393 .decl_index = decl_index,
394 .namespace_index = ns,
395 };
396 try sema.init();
397 defer sema.deinit();
398 _ = try sema.analyzeDeclaration();
399 try sema.finalize();
400}
401
402pub fn codeGenDecl(c: *Comp, decl: *Decl, air: *const Air) !void {
403 const t = tracer.trace(@src());439 const t = tracer.trace(@src());
404 defer t.end();440 defer t.end();
405441
...@@ -407,20 +443,48 @@ pub fn codeGenDecl(c: *Comp, decl: *Decl, air: *const Air) !void {...@@ -407,20 +443,48 @@ pub fn codeGenDecl(c: *Comp, decl: *Decl, air: *const Air) !void {
407 defer arena_state.deinit();443 defer arena_state.deinit();
408 const arena = arena_state.allocator();444 const arena = arena_state.allocator();
409445
410 var cg = try JsNodeGen.init(c.gpa, arena, &c.intern_pool, air);446 _ = try c.backend.handleFunctionAir(c, value, air, arena);
411 errdefer cg.deinit();
412 try cg.build();
413 decl.codegen = try cg.finalize();
414}
415
416pub fn link(c: *Comp, writer: anytype) !void {
417 try JsLink.linkJsCode(c, c.current_compile_decls.items, writer);
418}447}
419448
420const Options = struct {449const Options = struct {
421 dump_ir: bool = false,450 dump_ir: bool = false,
422};451};
423452
453pub const Backend = union(enum) {
454 js: *@import("js/Js.zig"),
455
456 pub fn deinit(backend: Backend, comp: *Comp) void {
457 backend.dispatch("deinit", .{comp});
458 }
459
460 pub const HandleFunctionAirError = error{OutOfMemory};
461
462 /// Once semantic analysis finishes analyzing a Function value, it hands
463 /// `Air` to the backend for code generation. Returned is an opaque integer
464 /// which is stored on the Function value.
465 pub fn handleFunctionAir(
466 backend: Backend,
467 comp: *Comp,
468 value: InternPool.Value,
469 air: *const Air,
470 arena: Allocator,
471 ) HandleFunctionAirError!u32 {
472 return backend.dispatch("handleFunctionAir", .{ comp, value, air, arena });
473 }
474
475 inline fn dispatch(
476 backend: Backend,
477 comptime name: []const u8,
478 args: anytype,
479 ) @typeInfo(@TypeOf(@field(Backend, name))).Fn.return_type.? {
480 return switch (backend) {
481 inline else => |ptr| @call(.auto, @field(@TypeOf(ptr.*), name), .{ptr} ++ args),
482 };
483 }
484
485 pub const Tag = std.meta.FieldEnum(Backend);
486};
487
424const Comp = @This();488const Comp = @This();
425489
426const std = @import("std");490const std = @import("std");
...@@ -437,9 +501,6 @@ const Teg = @import("Teg.zig");...@@ -437,9 +501,6 @@ const Teg = @import("Teg.zig");
437const Cache = @import("Cache.zig");501const Cache = @import("Cache.zig");
438const Sema = @import("Sema.zig");502const Sema = @import("Sema.zig");
439const Transform = @import("Transform.zig");503const Transform = @import("Transform.zig");
440const JsNodeGen = @import("JsNodeGen.zig");
441const JsTree = @import("JsTree.zig");
442const JsLink = @import("JsLink.zig");
443const Report = @import("Report.zig");504const Report = @import("Report.zig");
444const Parse = @import("Parse.zig");505const Parse = @import("Parse.zig");
445506
src/core/InternPool.zig+241-102
...@@ -1,8 +1,21 @@...@@ -1,8 +1,21 @@
1//! Large parts of this file are based on the Zig compiler's InternPool.1//! Large parts of this file are based on the Zig compiler's InternPool.
2//! https://github.com/ziglang/zig/blob/0.12.0/src/InternPool.zig2//! https://github.com/ziglang/zig/blob/0.12.0/src/InternPool.zig
33//!
4/// Maps `Key` to `Index`. `Key` objects are not stored anywhere; they are4//! All interned objects have both a Value and a Type. Type is a subset of
5/// constructed lazily.5//! Value, since the language allows operating on types the same as values.
6//! This data structure is intended to be serializable for incremental
7//! compilation efforts.
8//!
9//! This data structure is self-contained, with the following exceptions:
10//! * Comp.Namespace has a pointer to Comp.File
11
12/// Maps `Key` to `Value`. `Key` objects are not stored anywhere; they are
13/// constructed lazily. The `Value` is also stored out of line in `.items`. The
14/// ArrayHashMap functions as a list of hashes to allow fast lookups, and then
15/// reading it's data out of `.items` and `.extra` via the index lookup.
16///
17/// The preferred way to access these this data through `.get()` and the methods
18/// on `Value` and `Type`.
6map: std.AutoArrayHashMapUnmanaged(void, void) = .{},19map: std.AutoArrayHashMapUnmanaged(void, void) = .{},
7items: std.MultiArrayList(Item) = .{},20items: std.MultiArrayList(Item) = .{},
8extra: ExtraStore = .{},21extra: ExtraStore = .{},
...@@ -45,29 +58,6 @@ allocated_namespaces: std.SegmentedList(Comp.Namespace, 0) = .{},...@@ -45,29 +58,6 @@ allocated_namespaces: std.SegmentedList(Comp.Namespace, 0) = .{},
45/// Same pattern as with `decls_free_list`.58/// Same pattern as with `decls_free_list`.
46namespaces_free_list: std.ArrayListUnmanaged(NamespaceIndex) = .{},59namespaces_free_list: std.ArrayListUnmanaged(NamespaceIndex) = .{},
4760
48/// How many items in the InternPool are statically known.
49/// This is specified with an integer literal and a corresponding comptime
50/// assert below to break an unfortunate and arguably incorrect dependency loop
51/// when compiling.
52pub const static_len = Teg.Inst.Index.static_len;
53comptime {
54 // @compileLog(static_len, static_keys.len);
55 assert(static_len == static_keys.len);
56}
57
58pub const Item = struct {
59 tag: Tag,
60 data: EncodedData,
61
62 pub fn encode(comptime tag: Tag, data: tag.Data()) Item {
63 return .{ .tag = tag, .data = EncodedData.encode(tag, data) };
64 }
65};
66
67pub const ExtraStore = dod.ExtraStore(.intern_pool, .@"var");
68pub const EncodedData = dod.EncodedData(Tag, u32);
69pub const ExtraIndex = ExtraStore.Index;
70
71pub const Tag = enum(u8) {61pub const Tag = enum(u8) {
72 /// This special tag represents a value that was removed from this pool via62 /// This special tag represents a value that was removed from this pool via
73 /// `InternPool.remove`. The item remains allocated to preserve indices, but63 /// `InternPool.remove`. The item remains allocated to preserve indices, but
...@@ -76,10 +66,10 @@ pub const Tag = enum(u8) {...@@ -76,10 +66,10 @@ pub const Tag = enum(u8) {
76 removed,66 removed,
7767
78 /// A function type68 /// A function type
79 /// data is an extra index into a `FuncType`69 function_type,
80 type_function,70 /// A struct type
71 struct_type,
81 /// A type that can be represented with only an enum tag.72 /// A type that can be represented with only an enum tag.
82 /// data is SimpleType enum value.
83 simple_type,73 simple_type,
8474
85 // --- before this are types ---75 // --- before this are types ---
...@@ -90,21 +80,18 @@ pub const Tag = enum(u8) {...@@ -90,21 +80,18 @@ pub const Tag = enum(u8) {
90 simple_value,80 simple_value,
91 /// A numeric literal that can be represented as a float.81 /// A numeric literal that can be represented as a float.
92 /// Integers greater than 2^53 known at compile time are currently not supported.82 /// Integers greater than 2^53 known at compile time are currently not supported.
93 /// data is an extra index into a `Float`
94 comptime_float,83 comptime_float,
95 /// An int32 value84 /// An int32 value
96 /// data is i32
97 int32,85 int32,
98 /// A float value86 /// A float value
99 /// data is an extra index into a `Float`
100 float,87 float,
101 /// A function88 /// A function
102 /// data is an extra index into a `FuncDecl`
103 func,89 func,
104 /// A string90 /// A string
105 /// data is an extra index into a `String`
106 string,91 string,
10792
93 struct_value,
94
108 pub fn Data(comptime tag: Tag) type {95 pub fn Data(comptime tag: Tag) type {
109 return switch (tag) {96 return switch (tag) {
110 .removed,97 .removed,
...@@ -128,8 +115,10 @@ pub const Tag = enum(u8) {...@@ -128,8 +115,10 @@ pub const Tag = enum(u8) {
128 => unreachable, // does not have a payload115 => unreachable, // does not have a payload
129 .float, .comptime_float => Float,116 .float, .comptime_float => Float,
130 .func => FuncDecl,117 .func => FuncDecl,
131 .type_function => TypeFunction,118 .function_type => TypeFunction,
132 .string => String.WithLen,119 .string => String.WithLen,
120 .struct_value => StructValue,
121 .struct_type => TypeStruct,
133 };122 };
134 }123 }
135124
...@@ -147,8 +136,23 @@ pub const Tag = enum(u8) {...@@ -147,8 +136,23 @@ pub const Tag = enum(u8) {
147 params_len: u32,136 params_len: u32,
148 return_type: Type,137 return_type: Type,
149 };138 };
139
140 /// Trailing:
141 /// 0. field_key: NullTerminatedString. Repeated per field_len
142 /// 1. field_type: Type. Repeated per field_len
143 pub const TypeStruct = struct {
144 fields_len: u32,
145 };
146
147 /// Trailing:
148 /// 0. value: Value. Repeated ty.structInfo().field_len
149 pub const StructValue = struct {
150 /// The type of this field
151 ty: Type,
152 };
150};153};
151154
155/// This is an index into .items
152pub const Value = enum(u32) {156pub const Value = enum(u32) {
153 pub const first_type: Value = .opaque_type;157 pub const first_type: Value = .opaque_type;
154 pub const last_type: Value = .noreturn_type;158 pub const last_type: Value = .noreturn_type;
...@@ -176,25 +180,26 @@ pub const Value = enum(u32) {...@@ -176,25 +180,26 @@ pub const Value = enum(u32) {
176 none = std.math.maxInt(u32),180 none = std.math.maxInt(u32),
177 _,181 _,
178182
179 /// An array of `Index` existing within the `extra` array.183 /// An array of `Value` existing within the `extra` array.
180 /// This type exists to provide a struct with lifetime that is184 /// This type exists to provide a struct with lifetime that is
181 /// not invalidated when items are added to the `InternPool`.185 /// not invalidated when items are added to the `InternPool`.
186 /// TODO: Replace with ExtraStore.Slice(Value)
182 pub const Slice = struct {187 pub const Slice = struct {
183 start: u32,188 start: u32,
184 len: u32,189 len: u32,
185190
186 pub fn get(slice: Slice, ip: *const InternPool) []Value {191 pub fn get(slice: Slice, ip: *const InternPool) []const Value {
187 return @ptrCast(ip.extra.list.items[slice.start..][0..slice.len]);192 return @ptrCast(ip.extra.list.items[slice.start..][0..slice.len]);
188 }193 }
189 };194 };
190195
191 /// Used for a map of `Index` values to the index within a list of `Index` values.196 /// Used for a map of `Value` values to the index within a list of `Value` values.
192 const Adapter = struct {197 const Adapter = struct {
193 indexes: []const Value,198 values: []const Value,
194199
195 pub fn eql(ctx: @This(), a: Value, b_void: void, b_map_index: usize) bool {200 pub fn eql(ctx: @This(), a: Value, b_void: void, b_map_index: usize) bool {
196 _ = b_void;201 _ = b_void;
197 return a == ctx.indexes[b_map_index];202 return a == ctx.values[b_map_index];
198 }203 }
199204
200 pub fn hash(ctx: @This(), a: Value) u32 {205 pub fn hash(ctx: @This(), a: Value) u32 {
...@@ -216,7 +221,7 @@ pub const Value = enum(u32) {...@@ -216,7 +221,7 @@ pub const Value = enum(u32) {
216 }221 }
217222
218 pub fn toKey(value: Value, ip: *const InternPool) Key {223 pub fn toKey(value: Value, ip: *const InternPool) Key {
219 return ip.indexToKey(value);224 return ip.valueToKey(value);
220 }225 }
221226
222 pub fn dump(value: Value, ip: *const InternPool) std.fmt.Formatter(Value.formatValue) {227 pub fn dump(value: Value, ip: *const InternPool) std.fmt.Formatter(Value.formatValue) {
...@@ -240,8 +245,9 @@ pub const Value = enum(u32) {...@@ -240,8 +245,9 @@ pub const Value = enum(u32) {
240 return switch (value.valueTag(ip)) {245 return switch (value.valueTag(ip)) {
241 .removed => unreachable, // UAF246 .removed => unreachable, // UAF
242247
243 .type_function => .type,248 .function_type => .type,
244 .simple_type => .type,249 .simple_type => .type,
250 .struct_type => .type,
245251
246 .simple_value => switch (value.valueData(ip, .simple_value)) {252 .simple_value => switch (value.valueData(ip, .simple_value)) {
247 .true => .bool,253 .true => .bool,
...@@ -254,6 +260,7 @@ pub const Value = enum(u32) {...@@ -254,6 +260,7 @@ pub const Value = enum(u32) {
254 .float => .float,260 .float => .float,
255 .string => .string,261 .string => .string,
256 .func => value.toKey(ip).func.ty,262 .func => value.toKey(ip).func.ty,
263 .struct_value => value.toKey(ip).struct_value.ty,
257 };264 };
258 }265 }
259266
...@@ -263,7 +270,8 @@ pub const Value = enum(u32) {...@@ -263,7 +270,8 @@ pub const Value = enum(u32) {
263 return switch (value.valueTag(ip)) {270 return switch (value.valueTag(ip)) {
264 .removed => unreachable, // UAF271 .removed => unreachable, // UAF
265272
266 .type_function,273 .function_type,
274 .struct_type,
267 .simple_type,275 .simple_type,
268 => @enumFromInt(@intFromEnum(value)),276 => @enumFromInt(@intFromEnum(value)),
269277
...@@ -276,7 +284,8 @@ pub const Value = enum(u32) {...@@ -276,7 +284,8 @@ pub const Value = enum(u32) {
276284
277 pub fn toTypeUnsafe(value: Value, proof_ip: *const InternPool) Type {285 pub fn toTypeUnsafe(value: Value, proof_ip: *const InternPool) Type {
278 if (std.debug.runtime_safety) {286 if (std.debug.runtime_safety) {
279 return value.toType(proof_ip).?;287 return value.toType(proof_ip) orelse
288 std.debug.panic("{} is not a type", .{value.dump(proof_ip)});
280 }289 }
281 return @enumFromInt(@intFromEnum(value));290 return @enumFromInt(@intFromEnum(value));
282 }291 }
...@@ -301,6 +310,8 @@ pub const Value = enum(u32) {...@@ -301,6 +310,8 @@ pub const Value = enum(u32) {
301 }310 }
302};311};
303312
313/// Type is subset of Value which represents the values that have typeOf() == Value.type_type
314/// It is a bitcast, so going to and from are only the cost of ensuring the Value is a Type
304pub const Type = enum(u32) {315pub const Type = enum(u32) {
305 @"opaque" = @intFromEnum(Value.opaque_type),316 @"opaque" = @intFromEnum(Value.opaque_type),
306 bool = @intFromEnum(Value.bool_type),317 bool = @intFromEnum(Value.bool_type),
...@@ -315,7 +326,8 @@ pub const Type = enum(u32) {...@@ -315,7 +326,8 @@ pub const Type = enum(u32) {
315326
316 // Restricted version of `InternPool.Tag`327 // Restricted version of `InternPool.Tag`
317 pub const Tag = enum(u32) {328 pub const Tag = enum(u32) {
318 type_function = @intFromEnum(InternPool.Tag.type_function),329 function_type = @intFromEnum(InternPool.Tag.function_type),
330 struct_type = @intFromEnum(InternPool.Tag.struct_type),
319 simple_type = @intFromEnum(InternPool.Tag.simple_type),331 simple_type = @intFromEnum(InternPool.Tag.simple_type),
320 };332 };
321333
...@@ -336,12 +348,18 @@ pub const Type = enum(u32) {...@@ -336,12 +348,18 @@ pub const Type = enum(u32) {
336 }348 }
337349
338 pub fn tag(ty: Type, ip: *const InternPool) Type.Tag {350 pub fn tag(ty: Type, ip: *const InternPool) Type.Tag {
351 if (ty == .void) return .simple_type;
339 return @enumFromInt(@intFromEnum(ty.value().valueTag(ip)));352 return @enumFromInt(@intFromEnum(ty.value().valueTag(ip)));
340 }353 }
341354
342 pub fn toFunctionPrototype(ty: Type, ip: *const InternPool) ?Key.TypeFunction {355 pub fn toFunctionPrototype(ty: Type, ip: *const InternPool) ?Key.FunctionType {
343 if (ty.tag(ip) != .type_function) return null;356 if (ty.tag(ip) != .function_type) return null;
344 return ip.indexToKey(ty.value()).type_function;357 return ip.valueToKey(ty.value()).function_type;
358 }
359
360 pub fn toStructType(ty: Type, ip: *const InternPool) ?Key.StructType {
361 if (ty.tag(ip) != .struct_type) return null;
362 return ip.valueToKey(ty.value()).struct_type;
345 }363 }
346364
347 pub fn hasRuntimeRepresentation(ty: Type, ip: *const InternPool) bool {365 pub fn hasRuntimeRepresentation(ty: Type, ip: *const InternPool) bool {
...@@ -359,7 +377,8 @@ pub const Type = enum(u32) {...@@ -359,7 +377,8 @@ pub const Type = enum(u32) {
359 => false,377 => false,
360 else => switch (ty.tag(ip)) {378 else => switch (ty.tag(ip)) {
361 .simple_type => unreachable, // all simple types are already printed379 .simple_type => unreachable, // all simple types are already printed
362 .type_function => true,380 .function_type => true,
381 .struct_type => true,
363 },382 },
364 };383 };
365 }384 }
...@@ -396,20 +415,46 @@ pub const Type = enum(u32) {...@@ -396,20 +415,46 @@ pub const Type = enum(u32) {
396415
397 switch (ty.tag(ip)) {416 switch (ty.tag(ip)) {
398 .simple_type => unreachable, // all simple types are already printed417 .simple_type => unreachable, // all simple types are already printed
399 .type_function => {418 .function_type => {
400 const type_function = ty.toFunctionPrototype(ip).?;419 const function_type = ty.toFunctionPrototype(ip).?;
401 try writer.writeAll("fn(");420 try writer.writeAll("fn(");
402 for (type_function.param_types, 0..) |param, i| {421 for (function_type.param_types, 0..) |param, i| {
403 if (i != 0) try writer.writeAll(", ");422 if (i != 0) try writer.writeAll(", ");
404 try writer.print("{}", .{param.displayName(ip)});423 try writer.print("{}", .{param.displayName(ip)});
405 }424 }
406 try writer.writeAll(") -> ");425 try writer.writeAll(") -> ");
407 try writer.print("{}", .{type_function.return_type.displayName(ip)});426 try writer.print("{}", .{function_type.return_type.displayName(ip)});
427 },
428 .struct_type => {
429 try writer.writeAll("[struct]");
408 },430 },
409 }431 }
410 }432 }
411};433};
412434
435pub const Item = struct {
436 tag: Tag,
437 data: EncodedData,
438
439 pub fn encode(comptime tag: Tag, data: tag.Data()) Item {
440 return .{ .tag = tag, .data = EncodedData.encode(tag, data) };
441 }
442};
443
444pub const ExtraStore = dod.ExtraStore(.intern_pool, .@"var");
445pub const EncodedData = dod.EncodedData(Tag, u32);
446pub const ExtraIndex = ExtraStore.Index;
447
448/// How many items in the InternPool are statically known.
449/// This is specified with an integer literal and a corresponding comptime
450/// assert below to break an unfortunate and arguably incorrect dependency loop
451/// when compiling.
452pub const static_len = Teg.Inst.Index.static_len;
453comptime {
454 // @compileLog(static_len, static_keys.len);
455 assert(static_len == static_keys.len);
456}
457
413pub const static_keys = [_]Key{458pub const static_keys = [_]Key{
414 SimpleType.@"opaque".key(),459 SimpleType.@"opaque".key(),
415 SimpleType.bool.key(),460 SimpleType.bool.key(),
...@@ -434,29 +479,27 @@ pub const static_keys = [_]Key{...@@ -434,29 +479,27 @@ pub const static_keys = [_]Key{
434pub const FuncAnalysis = packed struct(u32) {479pub const FuncAnalysis = packed struct(u32) {
435 state: State,480 state: State,
436481
437 _: u24 = 0,482 pub const State = enum(u32) {
438
439 pub const State = enum(u8) {
440 /// This function has not yet undergone analysis, because we have not
441 /// seen a potential runtime call. It may be analyzed in future.
442 none,483 none,
443 /// Analysis for this function has been queued, but not yet completed.484 // /// This function has not yet undergone analysis, because we have not
444 queued,485 // /// seen a potential runtime call. It may be analyzed in future. none,
445 /// This function intentionally only has ZIR generated because it is marked486 // /// Analysis for this function has been queued, but not yet completed.
446 /// inline, which means no runtime version of the function will be generated.487 // queued,
447 inline_only,488 // /// This function intentionally only has ZIR generated because it is marked
448 in_progress,489 // /// inline, which means no runtime version of the function will be generated.
449 /// There will be a corresponding ErrorMsg in Module.failed_decls490 // inline_only,
450 sema_failure,491 // in_progress,
451 /// This function might be OK but it depends on another Decl which did not492 // /// There will be a corresponding ErrorMsg in Module.failed_decls
452 /// successfully complete semantic analysis.493 // sema_failure,
453 dependency_failure,494 // /// This function might be OK but it depends on another Decl which did not
454 /// There will be a corresponding ErrorMsg in Module.failed_decls.495 // /// successfully complete semantic analysis.
455 /// Indicates that semantic analysis succeeded, but code generation for496 // dependency_failure,
456 /// this function failed.497 // /// There will be a corresponding ErrorMsg in Module.failed_decls.
457 codegen_failure,498 // /// Indicates that semantic analysis succeeded, but code generation for
458 /// Semantic analysis and code generation of this function succeeded.499 // /// this function failed.
459 success,500 // codegen_failure,
501 // /// Semantic analysis and code generation of this function succeeded.
502 // success,
460 };503 };
461};504};
462505
...@@ -557,6 +600,7 @@ pub const NullTerminatedString = enum(u32) {...@@ -557,6 +600,7 @@ pub const NullTerminatedString = enum(u32) {
557 string: NullTerminatedString,600 string: NullTerminatedString,
558 ip: *const InternPool,601 ip: *const InternPool,
559 };602 };
603
560 fn format(604 fn format(
561 data: FormatData,605 data: FormatData,
562 comptime specifier: []const u8,606 comptime specifier: []const u8,
...@@ -574,6 +618,15 @@ pub const NullTerminatedString = enum(u32) {...@@ -574,6 +618,15 @@ pub const NullTerminatedString = enum(u32) {
574 pub fn fmt(self: NullTerminatedString, ip: *const InternPool) std.fmt.Formatter(format) {618 pub fn fmt(self: NullTerminatedString, ip: *const InternPool) std.fmt.Formatter(format) {
575 return .{ .data = .{ .string = self, .ip = ip } };619 return .{ .data = .{ .string = self, .ip = ip } };
576 }620 }
621
622 pub const Slice = struct {
623 start: u32,
624 len: u32,
625
626 pub fn get(slice: Slice, ip: *const InternPool) []NullTerminatedString {
627 return @ptrCast(ip.extra.list.items[slice.start..][0..slice.len]);
628 }
629 };
577};630};
578631
579pub const TrackedInst = extern struct {632pub const TrackedInst = extern struct {
...@@ -721,14 +774,18 @@ const SimpleType = enum {...@@ -721,14 +774,18 @@ const SimpleType = enum {
721};774};
722775
723pub const Key = union(enum) {776pub const Key = union(enum) {
777 // Values
724 comptime_float: f64,778 comptime_float: f64,
725 int32: i32,779 int32: i32,
726 float: f64,780 float: f64,
727 string: []const u8,781 string: []const u8,
728 simple_value: SimpleValue,782 simple_value: SimpleValue,
729 simple_type: SimpleType,783 struct_value: StructValue,
730 type_function: TypeFunction,
731 func: Func,784 func: Func,
785 // Types
786 simple_type: SimpleType,
787 function_type: FunctionType,
788 struct_type: StructType,
732789
733 const Adapter = struct {790 const Adapter = struct {
734 intern_pool: *const InternPool,791 intern_pool: *const InternPool,
...@@ -736,7 +793,7 @@ pub const Key = union(enum) {...@@ -736,7 +793,7 @@ pub const Key = union(enum) {
736 pub fn eql(ctx: @This(), a: Key, b_void: void, b_map_index: usize) bool {793 pub fn eql(ctx: @This(), a: Key, b_void: void, b_map_index: usize) bool {
737 _ = b_void;794 _ = b_void;
738 if (ctx.intern_pool.items.items(.tag)[b_map_index] == .removed) return false;795 if (ctx.intern_pool.items.items(.tag)[b_map_index] == .removed) return false;
739 return ctx.intern_pool.indexToKey(@enumFromInt(b_map_index)).eql(a, ctx.intern_pool);796 return ctx.intern_pool.valueToKey(@enumFromInt(b_map_index)).eql(a, ctx.intern_pool);
740 }797 }
741798
742 pub fn hash(ctx: @This(), a: Key) u32 {799 pub fn hash(ctx: @This(), a: Key) u32 {
...@@ -744,11 +801,11 @@ pub const Key = union(enum) {...@@ -744,11 +801,11 @@ pub const Key = union(enum) {
744 }801 }
745 };802 };
746803
747 pub const TypeFunction = struct {804 pub const FunctionType = struct {
748 param_types: []Type,805 param_types: []Type,
749 return_type: Type,806 return_type: Type,
750807
751 pub fn hash(self: TypeFunction, hasher: *Hash) void {808 pub fn hash(self: FunctionType, hasher: *Hash) void {
752 const t = tracer.trace(@src());809 const t = tracer.trace(@src());
753 defer t.end();810 defer t.end();
754811
...@@ -758,12 +815,32 @@ pub const Key = union(enum) {...@@ -758,12 +815,32 @@ pub const Key = union(enum) {
758 std.hash.autoHash(hasher, self.return_type);815 std.hash.autoHash(hasher, self.return_type);
759 }816 }
760817
761 pub fn eql(a: TypeFunction, b: TypeFunction) bool {818 pub fn eql(a: FunctionType, b: FunctionType) bool {
762 return std.mem.eql(Type, a.param_types, b.param_types) and819 return std.mem.eql(Type, a.param_types, b.param_types) and
763 a.return_type == b.return_type;820 a.return_type == b.return_type;
764 }821 }
765 };822 };
766823
824 pub const StructType = struct {
825 field_keys: []NullTerminatedString,
826 field_types: []const Type,
827
828 pub fn hash(self: StructType, hasher: *Hash) void {
829 // TODO: remove this function. structural typing is not used
830 std.hash.autoHashStrat(hasher, self, .DeepRecursive);
831 }
832 };
833
834 pub const StructValue = struct {
835 ty: Type,
836 field_values: []const Value,
837
838 pub fn hash(self: StructValue, hasher: *Hash) void {
839 std.hash.autoHash(hasher, self.ty);
840 std.hash.autoHashStrat(hasher, self.field_values, .Deep);
841 }
842 };
843
767 pub const Func = struct {844 pub const Func = struct {
768 ty: Type,845 ty: Type,
769 owner_decl: DeclIndex,846 owner_decl: DeclIndex,
...@@ -781,10 +858,10 @@ pub const Key = union(enum) {...@@ -781,10 +858,10 @@ pub const Key = union(enum) {
781 }858 }
782859
783 pub fn hash64(key: Key, ip: *const InternPool) u64 {860 pub fn hash64(key: Key, ip: *const InternPool) u64 {
861 _ = ip; // autofix
784 const t = tracer.trace(@src());862 const t = tracer.trace(@src());
785 defer t.end();863 defer t.end();
786864
787 _ = ip; // autofix
788 const asBytes = std.mem.asBytes;865 const asBytes = std.mem.asBytes;
789 const KeyTag = @typeInfo(Key).Union.tag_type.?;866 const KeyTag = @typeInfo(Key).Union.tag_type.?;
790 const seed = @intFromEnum(@as(KeyTag, key));867 const seed = @intFromEnum(@as(KeyTag, key));
...@@ -797,7 +874,7 @@ pub const Key = union(enum) {...@@ -797,7 +874,7 @@ pub const Key = union(enum) {
797 .string,874 .string,
798 => |x| Hash.hash(seed, x),875 => |x| Hash.hash(seed, x),
799 .float, .comptime_float => |x| Hash.hash(seed, asBytes(&Float.assertPure(x))),876 .float, .comptime_float => |x| Hash.hash(seed, asBytes(&Float.assertPure(x))),
800 .type_function => |x| {877 .function_type => |x| {
801 var hasher = Hash.init(seed);878 var hasher = Hash.init(seed);
802 x.hash(&hasher);879 x.hash(&hasher);
803 return hasher.final();880 return hasher.final();
...@@ -807,14 +884,24 @@ pub const Key = union(enum) {...@@ -807,14 +884,24 @@ pub const Key = union(enum) {
807 x.hash(&hasher);884 x.hash(&hasher);
808 return hasher.final();885 return hasher.final();
809 },886 },
887 .struct_type => |x| {
888 var hasher = Hash.init(seed);
889 x.hash(&hasher);
890 return hasher.final();
891 },
892 .struct_value => |x| {
893 var hasher = Hash.init(seed);
894 x.hash(&hasher);
895 return hasher.final();
896 },
810 };897 };
811 }898 }
812899
813 pub fn eql(a: Key, b: Key, ip: *const InternPool) bool {900 pub fn eql(a: Key, b: Key, ip: *const InternPool) bool {
901 _ = ip; // autofix
814 const t = tracer.trace(@src());902 const t = tracer.trace(@src());
815 defer t.end();903 defer t.end();
816904
817 _ = ip; // autofix
818 const KeyTag = @typeInfo(Key).Union.tag_type.?;905 const KeyTag = @typeInfo(Key).Union.tag_type.?;
819 const a_tag: KeyTag = a;906 const a_tag: KeyTag = a;
820 const b_tag: KeyTag = b;907 const b_tag: KeyTag = b;
...@@ -828,7 +915,7 @@ pub const Key = union(enum) {...@@ -828,7 +915,7 @@ pub const Key = union(enum) {
828 => |x| std.mem.eql(u8, x, b.string),915 => |x| std.mem.eql(u8, x, b.string),
829 .float => |x| Float.assertPure(x) == Float.assertPure(b.float),916 .float => |x| Float.assertPure(x) == Float.assertPure(b.float),
830 .comptime_float => |x| Float.assertPure(x) == Float.assertPure(b.comptime_float),917 .comptime_float => |x| Float.assertPure(x) == Float.assertPure(b.comptime_float),
831 .type_function => |x| x.eql(b.type_function),918 .function_type => |x| x.eql(b.function_type),
832 .func => |a_info| {919 .func => |a_info| {
833 const b_info = b.func;920 const b_info = b.func;
834 if (a_info.owner_decl != b_info.owner_decl)921 if (a_info.owner_decl != b_info.owner_decl)
...@@ -839,6 +926,14 @@ pub const Key = union(enum) {...@@ -839,6 +926,14 @@ pub const Key = union(enum) {
839 // TODO: zig has something extra here for inferred error sets926 // TODO: zig has something extra here for inferred error sets
840 return false;927 return false;
841 },928 },
929 .struct_type => {
930 return false; // structs are nomial
931 },
932 .struct_value => |a_info| {
933 const b_info = b.struct_value;
934 return a_info.ty == b_info.ty and
935 std.mem.eql(Value, a_info.field_values, b_info.field_values);
936 },
842 };937 };
843 }938 }
844};939};
...@@ -890,8 +985,8 @@ pub fn init(ip: *InternPool, gpa: Allocator) !void {...@@ -890,8 +985,8 @@ pub fn init(ip: *InternPool, gpa: Allocator) !void {
890985
891 if (std.debug.runtime_safety) {986 if (std.debug.runtime_safety) {
892 // Sanity check.987 // Sanity check.
893 assert(ip.indexToKey(.bool_true).simple_value == .true);988 assert(ip.valueToKey(.bool_true).simple_value == .true);
894 assert(ip.indexToKey(.bool_false).simple_value == .false);989 assert(ip.valueToKey(.bool_false).simple_value == .false);
895 }990 }
896991
897 assert(ip.items.len == static_keys.len);992 assert(ip.items.len == static_keys.len);
...@@ -944,22 +1039,20 @@ pub fn deinit(ip: *InternPool, gpa: Allocator) void {...@@ -944,22 +1039,20 @@ pub fn deinit(ip: *InternPool, gpa: Allocator) void {
944 ip.* = undefined;1039 ip.* = undefined;
945}1040}
9461041
947pub fn indexToKey(ip: *const InternPool, index: Value) Key {1042pub fn valueToKey(ip: *const InternPool, index: Value) Key {
948 assert(index != .none);1043 assert(index != .none);
949 const item: Item = ip.items.get(@intFromEnum(index));1044 const item: Item = ip.items.get(@intFromEnum(index));
950 const data = item.data;1045 const data = item.data;
951 return switch (item.tag) {1046 return switch (item.tag) {
952 .removed => unreachable,1047 .removed => unreachable,
9531048
954 .type_function => .{
955 .type_function = ip.extraTypeFunction(data.decode(.type_function, item.tag)),
956 },
957
958 .simple_type => .{ .simple_type = data.decode(.simple_type, item.tag) },1049 .simple_type => .{ .simple_type = data.decode(.simple_type, item.tag) },
959 .simple_value => .{ .simple_value = data.decode(.simple_value, item.tag) },1050 .simple_value => .{ .simple_value = data.decode(.simple_value, item.tag) },
960 .int32 => .{ .int32 = data.decode(.int32, item.tag) },1051 .int32 => .{ .int32 = data.decode(.int32, item.tag) },
961 .float => .{ .float = data.decode(.float, item.tag).get(ip).decode() },1052 .float => .{ .float = data.decode(.float, item.tag).get(ip).decode() },
962 .comptime_float => .{ .comptime_float = data.decode(.comptime_float, item.tag).get(ip).decode() },1053 .comptime_float => .{
1054 .comptime_float = data.decode(.comptime_float, item.tag).get(ip).decode(),
1055 },
963 .string => .{ .string = data.decode(.string, item.tag).get(ip).toSlice(ip) },1056 .string => .{ .string = data.decode(.string, item.tag).get(ip).toSlice(ip) },
964 .func => {1057 .func => {
965 const func = data.decode(.func, item.tag).get(ip);1058 const func = data.decode(.func, item.tag).get(ip);
...@@ -969,6 +1062,29 @@ pub fn indexToKey(ip: *const InternPool, index: Value) Key {...@@ -969,6 +1062,29 @@ pub fn indexToKey(ip: *const InternPool, index: Value) Key {
969 .teg_body_inst = func.teg_body_inst,1062 .teg_body_inst = func.teg_body_inst,
970 } };1063 } };
971 },1064 },
1065 .function_type => .{
1066 .function_type = ip.extraTypeFunction(data.decode(.function_type, item.tag)),
1067 },
1068 .struct_type => {
1069 const payload: Tag.TypeStruct, var trail =
1070 data.decode(.struct_type, item.tag).getTrail(ip);
1071
1072 return .{ .struct_type = .{
1073 .field_keys = trail.readNullTerminatedStringSlice(payload.fields_len).get(ip),
1074 .field_types = trail.readTypeSlice(payload.fields_len).get(ip),
1075 } };
1076 },
1077 .struct_value => {
1078 const payload: Tag.StructValue, var trail =
1079 data.decode(.struct_value, item.tag).getTrail(ip);
1080
1081 return .{ .struct_value = .{
1082 .ty = payload.ty,
1083 .field_values = trail.readValueSlice(
1084 payload.ty.value().valueData(ip, .struct_type).get(ip).fields_len,
1085 ).get(ip),
1086 } };
1087 },
972 };1088 };
973}1089}
9741090
...@@ -1020,20 +1136,39 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) !Value {...@@ -1020,20 +1136,39 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) !Value {
10201136
1021 break :brk Item.encode(.func, extra);1137 break :brk Item.encode(.func, extra);
1022 },1138 },
1023 .type_function => |f| brk: {1139 .function_type => |f| brk: {
1024 const extra, var trail = try ip.extra.appendTrailing(gpa, Tag.TypeFunction, .{1140 const extra, var trail = try ip.extra.appendTrailing(gpa, Tag.TypeFunction, .{
1025 .params_len = @intCast(f.param_types.len),1141 .params_len = @intCast(f.param_types.len),
1026 .return_type = f.return_type,1142 .return_type = f.return_type,
1027 }, f.param_types.len);1143 }, f.param_types.len);
1028 defer trail.end();1144 defer trail.end();
1029 trail.writeSlice(f.param_types);1145 trail.writeSlice(f.param_types);
1030 break :brk Item.encode(.type_function, extra);1146 break :brk Item.encode(.function_type, extra);
1147 },
1148 .struct_type => |s| brk: {
1149 assert(s.field_keys.len == s.field_types.len);
1150 const extra, var trail = try ip.extra.appendTrailing(gpa, Tag.TypeStruct, .{
1151 .fields_len = @intCast(s.field_keys.len),
1152 }, s.field_keys.len * 2);
1153 trail.writeSlice(s.field_keys);
1154 trail.writeSlice(s.field_types);
1155
1156 break :brk Item.encode(.struct_type, extra);
1157 },
1158 .struct_value => |s| brk: {
1159 assert(s.ty.value().toKey(ip).struct_type.field_keys.len == s.field_values.len);
1160 const extra, var trail = try ip.extra.appendTrailing(gpa, Tag.StructValue, .{
1161 .ty = s.ty,
1162 }, s.field_values.len);
1163 trail.writeSlice(s.field_values);
1164 break :brk Item.encode(.struct_value, extra);
1031 },1165 },
1032 };1166 };
1033 ip.items.appendAssumeCapacity(item);1167 ip.items.appendAssumeCapacity(item);
1034 const index: Value = @enumFromInt(ip.items.len - 1);1168 const index: Value = @enumFromInt(ip.items.len - 1);
1035 if (std.debug.runtime_safety) {1169 if (std.debug.runtime_safety) {
1036 assert(ip.indexToKey(index).eql(key, ip));1170 const key_added = ip.valueToKey(index);
1171 assert(key == .struct_type or key_added.eql(key, ip));
1037 }1172 }
1038 return index;1173 return index;
1039}1174}
...@@ -1041,11 +1176,11 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) !Value {...@@ -1041,11 +1176,11 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) !Value {
1041fn extraTypeFunction(1176fn extraTypeFunction(
1042 ip: *const InternPool,1177 ip: *const InternPool,
1043 extra_index: ExtraIndex(Tag.TypeFunction),1178 extra_index: ExtraIndex(Tag.TypeFunction),
1044) Key.TypeFunction {1179) Key.FunctionType {
1045 const type_function, var trail = extra_index.getTrail(ip);1180 const function_type, var trail = extra_index.getTrail(ip);
1046 const param_types = trail.readTypeSlice(type_function.params_len);1181 const param_types = trail.readTypeSlice(function_type.params_len);
1047 const result: Key.TypeFunction = .{1182 const result: Key.FunctionType = .{
1048 .return_type = type_function.return_type,1183 .return_type = function_type.return_type,
1049 .param_types = param_types.get(ip),1184 .param_types = param_types.get(ip),
1050 };1185 };
1051 return result;1186 return result;
...@@ -1130,6 +1265,10 @@ pub fn namespacePtr(ip: *InternPool, index: NamespaceIndex) *Comp.Namespace {...@@ -1130,6 +1265,10 @@ pub fn namespacePtr(ip: *InternPool, index: NamespaceIndex) *Comp.Namespace {
1130 return ip.allocated_namespaces.at(@intFromEnum(index));1265 return ip.allocated_namespaces.at(@intFromEnum(index));
1131}1266}
11321267
1268pub fn namespacePtrConst(ip: *const InternPool, index: NamespaceIndex) *const Comp.Namespace {
1269 return ip.allocated_namespaces.at(@intFromEnum(index));
1270}
1271
1133pub fn createDecl(1272pub fn createDecl(
1134 ip: *InternPool,1273 ip: *InternPool,
1135 gpa: Allocator,1274 gpa: Allocator,
...@@ -1222,7 +1361,7 @@ test InternPool {...@@ -1222,7 +1361,7 @@ test InternPool {
1222 try testing.expectEqual(str, str2);1361 try testing.expectEqual(str, str2);
12231362
1224 const int1 = try ip.get(a, .{ .int32 = 21483 });1363 const int1 = try ip.get(a, .{ .int32 = 21483 });
1225 try testing.expectEqual(@as(i32, 21483), ip.indexToKey(int1).int32);1364 try testing.expectEqual(@as(i32, 21483), ip.valueToKey(int1).int32);
1226 const int2 = try ip.get(a, .{ .int32 = 21483 });1365 const int2 = try ip.get(a, .{ .int32 = 21483 });
1227 try testing.expectEqual(int1, int2);1366 try testing.expectEqual(int1, int2);
1228}1367}
src/core/JsLink.zig deleted-733
...@@ -1,733 +0,0 @@
1/// This is the JavaScript backend's linker.
2/// It takes in a compilation and a list of `JsTree`s and creates a JavaScript
3/// bundle out of them.
4comp: *Comp,
5
6/// Value is an index into `link_strings`
7decls: std.AutoHashMapUnmanaged(Decl.Index, LinkString.Index) = .{},
8link_strings: std.ArrayHashMapUnmanaged(
9 LinkString,
10 GlobalOwner,
11 LinkString.Context,
12 true,
13) = .{},
14string_bytes: std.ArrayListUnmanaged(u8) = .{},
15
16/// The first declaration is the function containing top-level code.
17pub fn linkJsCode(
18 comp: *Comp,
19 decls: []const Decl.Index,
20 writer: anytype,
21) !void {
22 var t = tracer.trace(@src());
23 defer t.end();
24
25 // Arena allocator is used for the main flow
26 var link: JsLink = .{ .comp = comp };
27 defer link.deinit();
28
29 // Temp allocator is used for extremely temporary string work.
30 // var temp_sfa = std.heap.stackFallback(4096, comp.gpa);
31 // const temp_alloc = temp_sfa.get();
32
33 // 1. Resolve all global identifiers. Function names must not shadow external
34 // names. We also need this list prepared so that local identifier names do not
35 // shadow functions or externals.
36 const root_decl = comp.intern_pool.declPtrConst(decls[0]);
37 assert(root_decl.kind == .named);
38 assert(root_decl.name == .empty);
39
40 for (root_decl.codegen.?.extern_symbols) |extern_symbol| {
41 try link.reserveExternSymbol(extern_symbol);
42 }
43
44 for (decls[1..]) |decl_index| {
45 assert(decl_index != decls[0]);
46
47 const decl = comp.intern_pool.declPtrConst(decl_index);
48
49 // todo: add all externs first
50 for (decl.codegen.?.extern_symbols) |extern_symbol| {
51 try link.reserveExternSymbol(extern_symbol);
52 }
53
54 const name = decl.name.toSlice(&comp.intern_pool);
55 assert(name.len > 0);
56
57 const gop = try link.link_strings.getOrPutContextAdapted(
58 comp.gpa,
59 name,
60 LinkString.SliceContext{ .link = &link },
61 .{ .link = &link },
62 );
63 if (gop.found_existing) {
64 @panic("TODO: handle this shadowing case.");
65 }
66 const name_str = try link.appendLinkString(name);
67 gop.key_ptr.* = name_str;
68 gop.value_ptr.* = .decl;
69 try link.decls.putNoClobber(comp.gpa, decl_index, @enumFromInt(gop.index));
70 }
71
72 try writer.print("// Language Compiler v0.1.3. Alpha quality software.\n", .{});
73
74 if (root_decl.codegen.?.nodes.len > 1) {
75 // 2. Print every function decl
76 for (decls[1..]) |decl_index| {
77 try link.printDecl(decl_index, writer);
78 }
79
80 // 3. Print the top-level code
81 try link.printDeclContents(&root_decl.codegen.?, writer);
82 } else {
83 try writer.print("// (program has no observable effects, thus an empty file)\n", .{});
84 }
85}
86
87fn deinit(link: *JsLink) void {
88 var t = tracer.trace(@src());
89 defer t.end();
90
91 defer link.* = undefined;
92 link.decls.deinit(link.comp.gpa);
93 link.link_strings.deinit(link.comp.gpa);
94 link.string_bytes.deinit(link.comp.gpa);
95}
96
97fn reserveExternSymbol(link: *JsLink, extern_symbol: String.WithLen) !void {
98 var t = tracer.trace(@src());
99 defer t.end();
100
101 const name = extern_symbol.toSlice(&link.comp.intern_pool);
102 const gop = try link.link_strings.getOrPutContextAdapted(
103 link.comp.gpa,
104 name,
105 LinkString.SliceContext{ .link = link },
106 .{ .link = link },
107 );
108
109 if (gop.found_existing) {
110 if (gop.value_ptr.* != .external) {
111 @panic("TODO: Rename Decl");
112 }
113 }
114 gop.key_ptr.* = try link.appendLinkString(name);
115 gop.value_ptr.* = .external;
116}
117
118fn appendLinkString(link: *JsLink, string: []const u8) !LinkString {
119 var t = tracer.trace(@src());
120 defer t.end();
121
122 const start = link.string_bytes.items.len;
123 try link.string_bytes.appendSlice(link.comp.gpa, string);
124 return .{ .start = @intCast(start), .len = @intCast(string.len) };
125}
126
127fn declIdentifier(link: *const JsLink, decl: Decl.Index) []const u8 {
128 var t = tracer.trace(@src());
129 defer t.end();
130
131 return (link.decls.get(decl) orelse if (std.debug.runtime_safety) {
132 std.debug.panic("Decl {d} ({}) is not present in link decl map", .{
133 @intFromEnum(decl),
134 link.comp.intern_pool.declPtrConst(decl).name.fmt(&link.comp.intern_pool),
135 });
136 } else unreachable).toString(link).slice(link);
137}
138
139fn printDecl(link: *JsLink, decl_index: Decl.Index, writer: anytype) !void {
140 var t = tracer.trace(@src());
141 defer t.end();
142
143 try writer.writeAll("let ");
144 try writer.writeAll(link.declIdentifier(decl_index));
145 try writer.writeAll(" = ");
146
147 const decl = link.comp.intern_pool.declPtrConst(decl_index);
148 const tree = &decl.codegen.?;
149
150 if (tree.param_count != 1)
151 try writer.writeAll("(");
152 for (0..tree.param_count) |id| {
153 try link.printLocal(@intCast(id), writer);
154 if (id != tree.param_count - 1) try writer.writeAll(", ");
155 }
156 if (tree.param_count != 1)
157 try writer.writeAll(")");
158 try writer.writeAll(" => ");
159
160 const stmts = tree.mainStatements();
161 if (stmts.len == 1 and tree.nodeTag(stmts[0]) == .stmt_return) {
162 if (tree.nodeData(.stmt_return, stmts[0]).operand.unwrap()) |operand| {
163 try link.printExpr(tree, operand, writer);
164 } else {
165 try writer.writeAll("{}");
166 }
167 } else {
168 try writer.writeAll("{\n");
169 try link.printDeclContents(tree, writer);
170 try writer.writeAll("}");
171 }
172
173 try writer.writeAll(";\n");
174}
175
176fn printDeclContents(link: *JsLink, tree: *const JsTree, writer: anytype) !void {
177 var t = tracer.trace(@src());
178 defer t.end();
179
180 for (tree.mainStatements()) |stmt| {
181 try link.printExpr(tree, stmt, writer);
182 try writer.writeAll(";\n");
183 }
184}
185
186fn printBlockContents(
187 link: *JsLink,
188 tree: *const JsTree,
189 list: JsTree.IndexList,
190 writer: anytype,
191) !void {
192 var t = tracer.trace(@src());
193 defer t.end();
194
195 assert(list.slice(tree).len > 0);
196 for (list.slice(tree)) |stmt| {
197 try link.printExpr(tree, stmt, writer);
198 switch (tree.nodeTag(stmt)) {
199 .stmt_block,
200 .stmt_if,
201 => {},
202 else => try writer.writeAll(";"),
203 }
204 try writer.writeAll("\n");
205 }
206}
207
208fn printBlock(link: *JsLink, tree: *const JsTree, list: JsTree.IndexList, writer: anytype) !void {
209 var t = tracer.trace(@src());
210 defer t.end();
211
212 if (list.len != 1) {
213 try writer.writeAll("{");
214 try link.printBlockContents(tree, list, writer);
215 try writer.writeAll("}");
216 } else {
217 try link.printBlockContents(tree, list, writer);
218 }
219}
220
221fn printExpr(
222 link: *JsLink,
223 tree: *const JsTree,
224 node: Node.Index,
225 writer: anytype,
226) @TypeOf(writer).Error!void {
227 return link.printExprLevel(tree, node, .comma, writer);
228}
229
230fn printExprLevel(
231 link: *JsLink,
232 tree: *const JsTree,
233 node: Node.Index,
234 level: Level,
235 writer: anytype,
236) @TypeOf(writer).Error!void {
237 var t = tracer.trace(@src());
238 defer t.end();
239
240 switch (tree.nodeTag(node)) {
241 .root => unreachable,
242
243 .plain_value => {
244 const plain_value: JsTree.PlainValue = tree.nodeData(.plain_value, node);
245 switch (plain_value.value.toKey(&link.comp.intern_pool)) {
246 // TODO: minification
247 .int32 => |val| try writer.print("{d}", .{val}),
248 // TODO: correct printing of many value types
249 .float => |val| try writer.print("{d}", .{val}),
250 .simple_value => |val| try writer.writeAll(switch (val) {
251 .true => "true",
252 .false => "false",
253 // TODO: make codegen decide from null and undefined
254 // instead of emitting void nodes.
255 .void => "undefined",
256 }),
257 .string => |val| try writeString(val, writer),
258
259 .comptime_float => unreachable, // was not lowered to float
260
261 else => unreachable, // this value is not a plain_value
262 }
263 },
264
265 .local => {
266 const ref: JsTree.LocalRef = tree.nodeData(.local, node);
267 try link.printLocal(ref.id, writer);
268 },
269
270 .global => {
271 const ref: JsTree.GlobalRef = tree.nodeData(.global, node);
272 try writer.writeAll(ref.name.toSlice(&link.comp.intern_pool));
273 },
274
275 .decl => {
276 const ref: JsTree.DeclRef = tree.nodeData(.decl, node);
277 try writer.writeAll(link.declIdentifier(ref.index));
278 },
279
280 .stmt_break => {
281 const stmt: JsTree.StmtBreak = tree.nodeData(.stmt_break, node);
282 try writer.writeAll("break");
283 if (true) {
284 try writer.writeAll(" ");
285 try link.printLabelIdentifier(stmt.label, writer);
286 }
287 },
288
289 .stmt_return => {
290 const stmt: JsTree.StmtReturn = tree.nodeData(.stmt_return, node);
291 try writer.writeAll("return");
292 if (stmt.operand.unwrap()) |operand| {
293 try writer.writeAll(" ");
294 try link.printExpr(tree, operand, writer);
295 }
296 },
297
298 .stmt_declare_local => {
299 const stmt: JsTree.StmtDeclareLocal = tree.nodeData(.stmt_declare_local, node);
300 try writer.writeAll("let ");
301 try link.printLocal(stmt.identifier_index, writer);
302 if (stmt.init.unwrap()) |init| {
303 try writer.writeAll(" = ");
304 try link.printExpr(tree, init, writer);
305 }
306 },
307
308 .stmt_if => {
309 const stmt: JsTree.StmtIf = tree.nodeData(.stmt_if, node);
310 const data = stmt.data.get(tree);
311 try writer.writeAll("if (");
312 try link.printExpr(tree, stmt.cond, writer);
313 try writer.writeAll(") ");
314 if (data.flags.yes_label) {
315 try link.printLabelIdentifier(data.flags.block_label, writer);
316 try writer.writeAll(":");
317 }
318 try link.printBlock(tree, data.yes, writer);
319 if (data.no.len > 0) {
320 try writer.writeAll(" else ");
321 if (data.flags.no_label) {
322 try link.printLabelIdentifier(data.flags.block_label, writer);
323 try writer.writeAll(":");
324 }
325 try link.printBlock(tree, data.no, writer);
326 }
327 },
328
329 .stmt_block => {
330 const stmt: JsTree.StmtBlock = tree.nodeData(.stmt_block, node);
331 try link.printLabelIdentifier(stmt.label, writer);
332 try writer.writeAll(": {\n");
333 try link.printBlockContents(tree, stmt.content.get(tree), writer);
334 try writer.writeAll("}");
335 },
336
337 inline .assign,
338 .add,
339 .sub,
340 .mul,
341 .div,
342 .eq,
343 .lt,
344 .gt,
345 .assign_add,
346 .assign_sub,
347 .assign_mul,
348 .assign_div,
349 => |tag| {
350 const bin: JsTree.Bin = tree.nodeData(tag, node);
351 const op: Operator = @field(Operator, @tagName(tag));
352 const inner_level = comptime op.level();
353 if (inner_level.needsParens(level))
354 try writer.writeAll("(");
355 try link.printExprLevel(tree, bin.lhs, inner_level, writer);
356 try writer.writeAll(" ");
357 try writer.writeAll(comptime op.symbol());
358 try writer.writeAll(" ");
359 try link.printExprLevel(tree, bin.rhs, inner_level, writer);
360 if (inner_level.needsParens(level))
361 try writer.writeAll(")");
362 },
363
364 inline .negate,
365 .bool_not,
366 .pre_dec,
367 .pre_inc,
368 => |tag| {
369 const un: JsTree.Un = tree.nodeData(tag, node);
370 const op: Operator = @field(Operator, @tagName(tag));
371 try writer.writeAll(comptime op.symbol());
372 try link.printExpr(tree, un.operand, writer);
373 },
374
375 .ternary => {
376 const ternary: JsTree.Ternary = tree.nodeData(.ternary, node);
377 const data = ternary.data.get(tree);
378 try writer.writeAll("(");
379 try link.printExpr(tree, ternary.cond, writer);
380 try writer.writeAll(" ? ");
381 try link.printExpr(tree, data.yes, writer);
382 try writer.writeAll(" : ");
383 try link.printExpr(tree, data.no, writer);
384 try writer.writeAll(")");
385 },
386
387 .call => {
388 const call: JsTree.Call = tree.nodeData(.call, node);
389 const args = call.args.get(tree);
390
391 try link.printExpr(tree, call.base, writer);
392 try writer.writeAll("(");
393
394 for (args.slice(tree), 0..) |arg, i| {
395 if (i != 0) try writer.writeAll(", ");
396 try link.printExpr(tree, arg, writer);
397 }
398
399 try writer.writeAll(")");
400 },
401 .new => {
402 const call: JsTree.Call = tree.nodeData(.new, node);
403 const args = call.args.get(tree);
404
405 if (Level.new.needsParens(level))
406 try writer.writeAll("(");
407 try writer.writeAll("new ");
408 try link.printExpr(tree, call.base, writer);
409
410 if (args.len > 0) {
411 try writer.writeAll("(");
412
413 for (args.slice(tree), 0..) |arg, i| {
414 if (i != 0) try writer.writeAll(", ");
415 try link.printExpr(tree, arg, writer);
416 }
417
418 try writer.writeAll(")");
419 }
420 if (Level.new.needsParens(level))
421 try writer.writeAll(")");
422 },
423
424 .field => {
425 const field: JsTree.Field = tree.nodeData(.field, node);
426
427 try link.printExpr(tree, field.base, writer);
428 try writer.writeAll(".");
429 try writer.writeAll(field.field.toSlice(&link.comp.intern_pool));
430 },
431 }
432}
433
434pub fn canPrintWithoutEscape(c: u21) bool {
435 const first_ascii_printable = 0x20;
436 const last_ascii_printable = 0x7E;
437 const first_high_surrogate = 0xD800;
438 const last_low_surrogate = 0xDFFF;
439
440 if (c <= last_ascii_printable) {
441 return c >= first_ascii_printable and c != '\\' and c != '"';
442 } else {
443 return c != 0xFEFF and (c < first_high_surrogate or c > last_low_surrogate);
444 }
445}
446
447pub fn indexOfNeedsEscape(slice: []const u8) ?usize {
448 var it = std.unicode.Wtf8Iterator{ .bytes = slice, .i = 0 };
449 var i: usize = 0;
450 while (it.nextCodepoint()) |c| {
451 if (!canPrintWithoutEscape(c))
452 return i;
453 i = it.i;
454 }
455 return null;
456}
457
458// This function is loosely based on Bun's `writeJSONString`, which
459// is a Zig port of esbuild's `printUnquotedUTF16` function.
460// Both projects are licensed under MIT
461fn writeString(decoded: []const u8, writer: anytype) !void {
462 try writer.writeByte('"');
463
464 var text = decoded;
465 while (text.len > 0) {
466 const width = std.unicode.utf8ByteSequenceLength(text[0]) catch
467 unreachable; // string is encoded incorrectly
468
469 const c: u21 = std.unicode.wtf8Decode(text[0..width]) catch
470 unreachable; // string is encoded incorrectly
471
472 if (canPrintWithoutEscape(c)) {
473 if (indexOfNeedsEscape(text)) |j| {
474 try writer.writeAll(text[0..j]);
475 text = text[j..];
476 continue;
477 } else {
478 try writer.writeAll(text);
479 break;
480 }
481 }
482
483 text = text[width..];
484
485 switch (c) {
486 // Special-case the bell character since it may cause dumping this file to
487 // the terminal to make a sound, which is undesirable. Note that we can't
488 // use an octal literal to print this shorter since octal literals are not
489 // allowed in strict mode (or in template strings).
490 0x07 => {
491 try writer.writeAll("\\x07");
492 },
493 0x08 => {
494 try writer.writeAll("\\b");
495 },
496 0x0C => {
497 try writer.writeAll("\\f");
498 },
499 '\n' => {
500 try writer.writeAll("\\n");
501 },
502 std.ascii.control_code.cr => {
503 try writer.writeAll("\\r");
504 },
505 std.ascii.control_code.vt => {
506 try writer.writeAll("\\v");
507 },
508 '\\' => {
509 try writer.writeAll("\\\\");
510 },
511 '"' => {
512 try writer.writeAll("\\\"");
513 },
514 '\t' => {
515 try writer.writeAll("\\t");
516 },
517
518 else => {
519 const hex_chars = "0123456789ABCDEF";
520
521 if (c < 0xFFFF) {
522 const k = @as(usize, @intCast(c));
523
524 try writer.writeAll(&[_]u8{
525 '\\',
526 'u',
527 hex_chars[(k >> 12) & 0xF],
528 hex_chars[(k >> 8) & 0xF],
529 hex_chars[(k >> 4) & 0xF],
530 hex_chars[k & 0xF],
531 });
532 } else {
533 const first_high_surrogate = 0xD800;
534 const first_low_surrogate = 0xDC00;
535
536 const k = c - 0x10000;
537 const lo = @as(usize, @intCast(first_high_surrogate + ((k >> 10) & 0x3FF)));
538 const hi = @as(usize, @intCast(first_low_surrogate + (k & 0x3FF)));
539
540 try writer.writeAll(&[_]u8{
541 '\\',
542 'u',
543 hex_chars[lo >> 12],
544 hex_chars[(lo >> 8) & 15],
545 hex_chars[(lo >> 4) & 15],
546 hex_chars[lo & 15],
547 '\\',
548 'u',
549 hex_chars[hi >> 12],
550 hex_chars[(hi >> 8) & 15],
551 hex_chars[(hi >> 4) & 15],
552 hex_chars[hi & 15],
553 });
554 }
555 },
556 }
557 }
558 try writer.writeByte('"');
559}
560
561fn printLocal(link: *JsLink, local: JsTree.LocalIndex, writer: anytype) !void {
562 var t = tracer.trace(@src());
563 defer t.end();
564
565 _ = link; // todo: be smarter about this
566 try writer.print("v{d}", .{local});
567}
568
569fn printLabelIdentifier(link: *JsLink, local: JsTree.LocalIndex, writer: anytype) !void {
570 var t = tracer.trace(@src());
571 defer t.end();
572
573 _ = link; // todo: be smarter about this
574 try writer.print("label{d}", .{local});
575}
576
577const GlobalOwner = enum {
578 external,
579 decl,
580};
581
582// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_precedence
583const Level = enum {
584 comma,
585 assignment,
586 conditional,
587 logical_or,
588 logical_and,
589 bitwise_or,
590 bitwise_xor,
591 bitwise_and,
592 equality,
593 relational,
594 bitshift,
595 additive,
596 multiplicative,
597 exponentiation,
598 prefix,
599 postfix,
600 new,
601 access_and_call,
602 member,
603
604 pub fn needsParens(inner_level: Level, outer_level: Level) bool {
605 return @intFromEnum(outer_level) > @intFromEnum(inner_level);
606 }
607};
608
609const Operator = enum {
610 assign,
611 assign_add,
612 assign_sub,
613 assign_mul,
614 assign_div,
615 add,
616 sub,
617 mul,
618 div,
619 eq,
620 lt,
621 gt,
622 negate,
623 bool_not,
624 pre_inc,
625 pre_dec,
626
627 pub fn level(op: Operator) Level {
628 return switch (op) {
629 .assign,
630 .assign_add,
631 .assign_sub,
632 .assign_mul,
633 .assign_div,
634 => .assignment,
635 .add, .sub => .additive,
636 .mul, .div => .multiplicative,
637 .eq => .equality,
638 .lt, .gt => .relational,
639
640 .pre_inc,
641 .pre_dec,
642 .bool_not,
643 .negate,
644 => .prefix,
645 };
646 }
647
648 pub fn symbol(op: Operator) []const u8 {
649 return switch (op) {
650 .assign => "=",
651 .assign_add => "+=",
652 .assign_sub => "-=",
653 .assign_mul => "*=",
654 .assign_div => "/=",
655 .add => "+",
656 .sub => "-",
657 .mul => "*",
658 .div => "/",
659 .eq => "===",
660 .lt => "<",
661 .gt => ">",
662
663 .pre_inc => "++",
664 .pre_dec => "++",
665
666 .negate => "-",
667 .bool_not => "!",
668 };
669 }
670};
671
672const LinkString = struct {
673 start: u32,
674 len: u32,
675
676 fn slice(str: LinkString, link: *const JsLink) []const u8 {
677 return link.string_bytes.items[str.start..][0..str.len];
678 }
679
680 const Index = enum(u32) {
681 _,
682
683 pub fn toString(index: Index, link: *const JsLink) LinkString {
684 return link.link_strings.keys()[@intFromEnum(index)];
685 }
686 };
687
688 const Context = struct {
689 link: *const JsLink,
690
691 pub fn hash(ctx: Context, str: LinkString) u32 {
692 // assert(str.start + str.len < ctx.link.string_bytes.items.len);
693
694 return @truncate(Wyhash.hash(0, str.slice(ctx.link)));
695 }
696
697 pub fn eql(ctx: Context, a: LinkString, b: LinkString, _: usize) bool {
698 assert(a.start + a.len < ctx.link.string_bytes.items.len);
699 assert(b.start + b.len < ctx.link.string_bytes.items.len);
700 return a.start == b.start;
701 }
702 };
703
704 const SliceContext = struct {
705 link: *const JsLink,
706
707 pub fn hash(_: SliceContext, str: []const u8) u32 {
708 return @truncate(Wyhash.hash(0, str));
709 }
710
711 pub fn eql(ctx: SliceContext, a: []const u8, b: LinkString, _: usize) bool {
712 // since the given string is not in string_bytes we do comparisons
713 return std.mem.eql(u8, a, b.slice(ctx.link));
714 }
715 };
716};
717
718const JsLink = @This();
719const std = @import("std");
720const assert = std.debug.assert;
721const Wyhash = std.hash.Wyhash;
722const log = std.log.scoped(.js_link);
723
724const Comp = @import("Comp.zig");
725const Decl = Comp.Decl;
726
727const JsTree = @import("JsTree.zig");
728const Node = JsTree.Node;
729
730const InternPool = @import("InternPool.zig");
731const String = InternPool.String;
732
733const tracer = @import("tracer");
src/core/JsNodeGen.zig deleted-701
...@@ -1,701 +0,0 @@
1//! Air -> JsTree
2gpa: Allocator,
3arena: Allocator,
4air: *const Air,
5ip: *const InternPool,
6
7nodes: Node.List,
8extra: dod.ExtraStore(.tfr, .@"var"),
9extern_symbols: std.AutoArrayHashMapUnmanaged(InternPool.String.WithLen, void),
10decl_deps: std.AutoArrayHashMapUnmanaged(Decl.Index, void),
11
12param_count: u32,
13air_node_map: []MapEntry,
14scratch: std.ArrayListUnmanaged(u32),
15next_id: u32,
16next_label: u32,
17
18const Error = error{
19 OutOfMemory,
20};
21
22const MapEntry = union {
23 none: void,
24 node: Node.Index,
25 block: packed struct {
26 used: bool = false,
27 index: u31,
28 },
29};
30
31pub fn init(gpa: Allocator, arena: Allocator, ip: *const InternPool, air: *const Air) !JsNodeGen {
32 const t = tracer.trace(@src());
33 defer t.end();
34
35 const air_node_map = try arena.alloc(MapEntry, air.insts.len);
36 if (std.debug.runtime_safety) {
37 @memset(air_node_map, .{ .none = {} });
38 }
39 const param_count = air.paramCount();
40 return .{
41 .gpa = gpa,
42 .arena = arena,
43 .air = air,
44 .ip = ip,
45 .param_count = param_count,
46 .nodes = .{},
47 .extern_symbols = .{},
48 .decl_deps = .{},
49 .air_node_map = air_node_map,
50 .extra = .{},
51 .scratch = .{},
52 .next_id = param_count,
53 .next_label = 0,
54 };
55}
56
57pub fn deinit(c: *JsNodeGen) void {
58 const t = tracer.trace(@src());
59 defer t.end();
60
61 defer c.* = undefined;
62 c.nodes.deinit(c.gpa);
63 c.extra.deinit(c.gpa);
64 c.extern_symbols.deinit(c.gpa);
65 c.decl_deps.deinit(c.gpa);
66
67 // arena can deinit the rest
68}
69
70pub fn finalize(c: *JsNodeGen) !JsTree {
71 const t = tracer.trace(@src());
72 defer t.end();
73
74 // todo: errdefer will probably leave memory in a fully invalid state
75 try c.nodes.setCapacity(c.gpa, c.nodes.len);
76
77 var extra = try c.extra.toOwnedStore(c.gpa);
78 errdefer extra.deinit(c.gpa);
79
80 // todo: i dont like this dupe
81 const extern_symbols = try c.gpa.dupe(InternPool.String.WithLen, c.extern_symbols.keys());
82 errdefer c.gpa.free(extern_symbols);
83
84 const decl_deps = try c.gpa.dupe(Decl.Index, c.decl_deps.keys());
85 errdefer c.gpa.free(decl_deps);
86
87 defer c.deinit();
88
89 return .{
90 .param_count = c.param_count,
91 .nodes = c.nodes.toOwnedSlice(),
92 .extern_symbols = extern_symbols,
93 .decl_deps = decl_deps,
94 .extra = extra,
95 };
96}
97
98pub fn build(c: *JsNodeGen) !void {
99 const t = tracer.trace(@src());
100 defer t.end();
101
102 assert(c.air.instTag(.main_block) == .block);
103 const statements = c.air.instData(.block, .main_block).slice(c.air);
104
105 const scratch_top = c.scratch.items.len;
106 defer c.scratch.shrinkRetainingCapacity(scratch_top);
107
108 _ = try c.add(.root, undefined);
109
110 if (statements.len > 0) {
111 for (statements[0..c.param_count], 1..) |stmt, expected_id| {
112 assert(@intFromEnum(stmt) == expected_id);
113 assert(c.air.instTag(stmt) == .arg);
114 c.air_node_map[@intFromEnum(stmt)] = .{
115 .node = try c.add(.local, .{ .id = @intFromEnum(stmt) - 1 }),
116 };
117 }
118
119 for (statements[c.param_count..]) |stmt| {
120 try c.codeGenInst(stmt);
121 }
122 }
123
124 const items = c.scratch.items[scratch_top..];
125 const top = c.extra.list.items.len;
126 try c.extra.list.appendSlice(c.gpa, items);
127 c.nodes.items(.data)[0] = EncodedData.encode(.root, .{
128 .start = @intCast(top),
129 .len = @intCast(items.len),
130 });
131}
132
133fn codeGenInst(c: *JsNodeGen, inst: Air.Inst.Index) Error!void {
134 const t = tracer.traceFmt(@src(), "#{d} {s}", .{ @intFromEnum(inst), @tagName(c.air.instTag(inst)) });
135 defer t.end();
136
137 switch (c.air.instTag(inst)) {
138 .arg => unreachable, // codegen must skip over these instructions.
139
140 .block => try c.codeGenBlock(inst),
141
142 .js_global_ref => {
143 const data: InternPool.String.WithLen = c.air.instData(.js_global_ref, inst);
144 try c.extern_symbols.put(c.gpa, data, {});
145 c.air_node_map[@intFromEnum(inst)] = .{
146 .node = try c.add(.global, .{ .name = data }),
147 };
148 },
149
150 .memory => {
151 const i = @intFromEnum(inst);
152 const data: Air.Memory = c.air.instData(.memory, inst);
153 const value: JsTree.Node.Index.Optional = if (data.init.isVoid())
154 .none
155 else
156 (try c.fetchExpression(data.init)).toOptional();
157
158 try c.addStmt(try c.add(.stmt_declare_local, .{
159 .identifier_index = c.next_id,
160 .init = value,
161 }));
162 const id = try c.add(.local, .{ .id = c.next_id });
163 c.next_id += 1;
164 c.air_node_map[i] = .{ .node = id };
165 },
166
167 inline .gt,
168 .lt,
169 .eq,
170 .add,
171 .sub,
172 .mul,
173 .div,
174 => |tag| {
175 const data: Air.Bin = c.air.instData(tag, inst);
176 const node = try c.add(@field(JsTree.Tag, @tagName(tag)), .{
177 .lhs = try c.fetchExpression(data.lhs),
178 .rhs = try c.fetchExpression(data.rhs),
179 });
180 try c.markExpression(inst, node);
181 },
182
183 .store => {
184 const data: Air.Store = c.air.instData(.store, inst);
185
186 const target = try c.fetchInstExpression(data.memory);
187
188 if (data.operand.kind == .index) {
189 const index = data.operand.decode().index;
190 switch (c.air.instTag(index)) {
191 inline .add,
192 .sub,
193 .mul,
194 .div,
195 => |tag| {
196 const inner_data: Air.Bin = c.air.instData(tag, index);
197 if (inner_data.lhs.eql(data.memory.toRef())) {
198 if ((tag == .add or tag == .sub) and
199 inner_data.rhs.kind == .value)
200 {
201 // if (inner_data.rhs.decode().value.toFloat(c.ip)) |f| {
202 // if (f == 1) {
203 // // convert this into ++ or --
204 // const node = try c.add(
205 // if (tag == .add)
206 // .pre_inc
207 // else
208 // .pre_dec,
209 // .{
210 // .operand = target,
211 // },
212 // );
213 // try c.markExpression(inst, node);
214
215 // return;
216 // }
217 // }
218 }
219
220 // convert into a compound assignment
221 const node = try c.add(
222 @field(JsTree.Tag, "assign_" ++ @tagName(tag)),
223 .{
224 .lhs = target,
225 .rhs = try c.fetchExpression(inner_data.rhs),
226 },
227 );
228 try c.markExpression(inst, node);
229
230 return;
231 }
232 },
233 else => {},
234 }
235 }
236
237 const node = try c.add(.assign, .{
238 .lhs = target,
239 .rhs = try c.fetchExpression(data.operand),
240 });
241 try c.markExpression(inst, node);
242 },
243
244 inline .negate,
245 .bool_not,
246 => |tag| {
247 const data: Air.Un = c.air.instData(tag, inst);
248 const node = try c.add(@field(JsTree.Tag, @tagName(tag)), .{
249 .operand = try c.fetchExpression(data.operand),
250 });
251 try c.markExpression(inst, node);
252 },
253
254 .field => {
255 const data: Air.Field = c.air.instData(.field, inst);
256 const node = try c.add(.field, .{
257 .base = try c.fetchExpression(data.base),
258 .field = data.field,
259 });
260 try c.markExpression(inst, node);
261 },
262
263 inline .call, .new => |tag| {
264 const data: Air.Call = c.air.instData(tag, inst);
265 const call_data = data.call_data.get(c.air);
266
267 try c.extra.list.ensureUnusedCapacity(c.gpa, call_data.args.len);
268 const start = c.extra.list.items.len;
269 c.extra.list.items.len += call_data.args.len;
270
271 const items = c.extra.list.items[start..][0..call_data.args.len];
272
273 for (call_data.args.slice(c.air), items) |arg, *dest| {
274 dest.* = @intFromEnum(try c.fetchExpression(arg));
275 }
276
277 const node = try c.add(@field(JsTree.Tag, @tagName(tag)), .{
278 .base = try c.fetchExpression(data.base),
279 .args = try c.extra.append(c.gpa, JsTree.IndexList, .{
280 .start = @intCast(start),
281 .len = call_data.args.len,
282 }),
283 });
284 try c.markExpression(inst, node);
285 },
286
287 .@"if" => {
288 const data: Air.If = c.air.instData(.@"if", inst);
289 const extra = data.data.get(c.air);
290 var cond = try c.fetchExpression(data.cond);
291
292 if (c.canLowerToExpression(extra.yes) and
293 c.canLowerToExpression(extra.no))
294 {
295 const yes = try c.lowerToExpression(extra.yes);
296 const no = try c.lowerToExpression(extra.yes);
297
298 const ternary = try c.add(.ternary, .{
299 .cond = cond,
300 .data = try c.extra.append(c.gpa, JsTree.Ternary.Data, .{
301 .yes = yes,
302 .no = no,
303 }),
304 });
305
306 try c.markExpression(inst, ternary);
307 } else {
308 const flags = c.air.instFlags(inst);
309 const uses = flags.uses;
310
311 const target = if (uses != .none)
312 try c.addTemporary()
313 else
314 null;
315
316 var yes = try c.codeGenBranch(extra.yes, target);
317 var no = try c.codeGenBranch(extra.no, target);
318 assert(!((yes == null) and (no == null))); // no "void else void" branches
319
320 // when `yes` is void, swap the condition so that the `else` is omitted
321 if (yes == null) {
322 const temp_yes = yes;
323 yes = no;
324 no = temp_yes;
325 cond = try c.add(.bool_not, .{
326 .operand = cond,
327 });
328 }
329
330 const main_expr = try c.add(.stmt_if, .{
331 .cond = cond,
332 .data = try c.extra.append(c.gpa, JsTree.StmtIf.Data, .{
333 .yes = yes.?.list,
334 .no = if (no) |branch|
335 branch.list
336 else
337 .{ .start = 0, .len = 0 },
338 .flags = .{
339 .block_label = @intCast(c.next_label + 1),
340 .yes_label = yes.?.requires_label,
341 .no_label = if (no) |branch| branch.requires_label else false,
342 },
343 }),
344 });
345
346 try c.addStmt(main_expr);
347
348 if (target) |node| {
349 try c.markExpression(inst, node);
350 }
351 }
352 },
353
354 .@"break" => {
355 const brk: Air.Break = c.air.instData(.@"break", inst);
356
357 const operand = try c.fetchExpression(brk.value);
358
359 // break #0 -> return
360 if (brk.block_ref == .main_block) {
361 try c.addStmt(try c.add(.stmt_return, .{
362 .operand = operand.toOptional(),
363 }));
364 } else {
365 const block = &c.air_node_map[@intFromEnum(brk.block_ref)].block;
366
367 const block_insts = c.air.instData(.block, brk.block_ref).slice(c.air);
368 if (block_insts[block_insts.len - 1] != inst) {
369 block.used = true;
370 }
371
372 const assignment: Node.Index.Optional = @enumFromInt(c.scratch.items[block.index]);
373 if (assignment.unwrap()) |target| {
374 try c.addStmt(try c.add(.assign, .{
375 .lhs = target,
376 .rhs = operand,
377 }));
378 }
379
380 try c.addStmt(try c.add(.stmt_break, .{
381 .label = c.scratch.items[block.index + 1],
382 }));
383 }
384 },
385 }
386}
387
388fn markExpression(c: *JsNodeGen, inst: Air.Inst.Index, node: Node.Index) !void {
389 const t = tracer.trace(@src());
390 defer t.end();
391
392 const flags: Air.Flags = c.air.instFlags(inst);
393 const i = @intFromEnum(inst);
394 switch (flags.uses) {
395 .none => {
396 try c.addStmt(node);
397 },
398 .single => {
399 // TODO: this is not aware of side effect fences
400 c.air_node_map[i] = .{ .node = node };
401 },
402 .multi => {
403 const decl = try c.add(.stmt_declare_local, .{
404 .identifier_index = c.next_id,
405 .init = node.toOptional(),
406 });
407 try c.addStmt(decl);
408 const id = try c.add(.local, .{ .id = c.next_id });
409 c.next_id += 1;
410 c.air_node_map[i] = .{ .node = id };
411 },
412 }
413}
414
415fn fetchExpression(c: *JsNodeGen, ref: Air.Inst.Ref) !Node.Index {
416 const t = tracer.trace(@src());
417 defer t.end();
418
419 return switch (ref.decode()) {
420 .value => |v| c.valueToExpression(v),
421 .index => |index| fetchInstExpression(c, index),
422 };
423}
424
425fn fetchInstExpression(c: *JsNodeGen, index: Air.Inst.Index) !Node.Index {
426 const t = tracer.trace(@src());
427 defer t.end();
428
429 const data = c.air_node_map[@intFromEnum(index)];
430 if (std.debug.runtime_safety) {
431 const flags: Air.Flags = c.air.instFlags(index);
432 switch (flags.uses) {
433 .none => unreachable, // incorrectly marked instruction as unused,
434 .single => {},
435 .multi => assert(c.nodes.items(.tag)[@intFromEnum(data.node)] == .local),
436 }
437 }
438 return data.node;
439}
440
441fn valueToExpression(c: *JsNodeGen, v: Value) !Node.Index {
442 const t = tracer.trace(@src());
443 defer t.end();
444
445 const intern_tag = v.valueTag(c.ip);
446 switch (intern_tag) {
447 .removed => unreachable, // UAF
448
449 .type_function,
450 .simple_type,
451 => unreachable, // Type values are not allowed here
452
453 .int32,
454 .float,
455 .comptime_float,
456 .string,
457 .simple_value,
458 => return try c.add(.plain_value, .{ .value = v }),
459
460 // funcs always point to decls
461 .func => {
462 const func = v.toKey(c.ip).func;
463 try c.decl_deps.put(c.gpa, func.owner_decl, {});
464 return try c.add(.decl, .{ .index = func.owner_decl });
465 },
466 }
467}
468
469fn addTemporary(c: *JsNodeGen) !Node.Index {
470 const t = tracer.trace(@src());
471 defer t.end();
472
473 try c.addStmt(try c.add(.stmt_declare_local, .{
474 .identifier_index = c.next_id,
475 .init = .none,
476 }));
477 const id = try c.add(.local, .{ .id = c.next_id });
478 c.next_id += 1;
479 return id;
480}
481
482const Branch = struct {
483 requires_label: bool,
484 list: JsTree.IndexList,
485};
486
487fn codeGenBranch(c: *JsNodeGen, inst: Air.Inst.Ref, temporary: ?Node.Index) !?Branch {
488 const t = tracer.trace(@src());
489 defer t.end();
490
491 const scratch_top = c.scratch.items.len;
492 defer c.scratch.shrinkRetainingCapacity(scratch_top);
493
494 c.next_label += 1;
495 defer c.next_label -= 1;
496 const this_label = c.next_label;
497
498 var requires_label = false;
499 var items = switch (inst.decode()) {
500 .value => |v| items: {
501 if (v == .void)
502 return null;
503 if (temporary) |temp| {
504 const node = try c.valueToExpression(v);
505 try c.addStmt(try c.add(.assign, .{
506 .lhs = temp,
507 .rhs = node,
508 }));
509 }
510 break :items c.scratch.items[scratch_top..];
511 },
512 .index => |i| items: {
513 assert(c.air.instTag(i) == .block);
514 c.air_node_map[@intFromEnum(i)] = .{
515 .block = .{ .index = @intCast(scratch_top) },
516 };
517
518 try c.scratch.appendSlice(c.arena, &.{
519 @intFromEnum(if (temporary) |temp| temp.toOptional() else .none),
520 this_label,
521 });
522
523 const block: Air.IndexList = c.air.instData(.block, i);
524 for (block.slice(c.air)) |item| {
525 try c.codeGenInst(item);
526 }
527
528 requires_label = c.air_node_map[@intFromEnum(i)].block.used;
529
530 break :items c.scratch.items[scratch_top + 2 ..];
531 },
532 };
533
534 const last_node = c.scratch.items[c.scratch.items.len - 1];
535 if (c.nodes.items(.tag)[last_node] == .stmt_break and
536 c.nodes.items(.data)[last_node].decode(.stmt_break, .stmt_break).label == this_label)
537 {
538 items.len -= 1;
539 c.nodes.len -= 1;
540 }
541
542 const top = c.extra.list.items.len;
543 try c.extra.list.appendSlice(c.gpa, items);
544 return .{
545 .requires_label = requires_label,
546 .list = .{
547 .start = @intCast(top),
548 .len = @intCast(items.len),
549 },
550 };
551}
552
553fn codeGenBlock(c: *JsNodeGen, inst: Air.Inst.Index) !void {
554 const t = tracer.trace(@src());
555 defer t.end();
556
557 const scratch_top = c.scratch.items.len;
558 errdefer c.scratch.shrinkRetainingCapacity(scratch_top);
559
560 c.next_label += 1;
561 defer c.next_label -= 1;
562
563 const next_label = c.next_label;
564
565 try c.scratch.appendSlice(c.arena, &.{
566 // @intFromEnum(if (temporary) |temp| temp.toOptional() else .none),
567 @intFromEnum(Node.Index.Optional.none),
568 next_label,
569 });
570
571 c.air_node_map[@intFromEnum(inst)] = .{
572 .block = .{ .index = @intCast(scratch_top) },
573 };
574 const block: Air.IndexList = c.air.instData(.block, inst);
575
576 for (block.slice(c.air)) |item| {
577 try c.codeGenInst(item);
578 }
579
580 const items = c.scratch.items[scratch_top + 2 ..];
581 const top = c.extra.list.items.len;
582 try c.extra.list.appendSlice(c.gpa, items);
583 c.scratch.shrinkRetainingCapacity(scratch_top);
584 try c.addStmt(try c.add(.stmt_block, .{
585 .label = c.next_label,
586 .content = try c.extra.append(c.gpa, JsTree.IndexList, .{
587 .start = @intCast(top),
588 .len = @intCast(items.len),
589 }),
590 }));
591}
592
593// Ideally, we convert everything to an expression. This allows for much
594// more compact code. When that isnt possible (`return`, `while`, etc),
595// we detect that here and ensure to lower `if` to actual if statements,
596// and so on.
597fn canLowerToExpression(c: *const JsNodeGen, inst: Air.Inst.Ref) bool {
598 const t = tracer.trace(@src());
599 defer t.end();
600
601 return switch (inst.decode()) {
602 .value => true,
603 .index => |index| switch (c.air.instTag(index)) {
604 .js_global_ref,
605 .call,
606 .new,
607 .arg,
608 .field,
609 .add,
610 .sub,
611 .mul,
612 .div,
613 .eq,
614 .lt,
615 .gt,
616 .negate,
617 .bool_not,
618 .store,
619 => true,
620
621 // JavaScript variable declarations require statements.
622 // Later, this could be made true if we can hoist temporaries up.
623 .memory => false,
624
625 // break/return must be a js statement
626 .@"break",
627 => false,
628
629 .block => {
630 // TODO: blocks that are
631 // - all statements except last are lowerable
632 // - the last statement is lowerable OR a break of this block
633 return false;
634 },
635
636 .@"if" => {
637 const call = c.air.instData(.@"if", index);
638 const data = call.data.get(c.air);
639
640 return c.canLowerToExpression(call.cond) and
641 c.canLowerToExpression(data.yes) and
642 c.canLowerToExpression(data.no);
643 },
644 },
645 };
646}
647
648fn lowerToExpression(c: *JsNodeGen, ref: Air.Inst.Ref) !Node.Index {
649 const t = tracer.trace(@src());
650 defer t.end();
651
652 assert(c.canLowerToExpression(ref));
653 return switch (ref.decode()) {
654 .value => |v| try c.valueToExpression(v),
655 .index => |index| {
656 try c.codeGenInst(index);
657 return c.fetchExpression(ref);
658 },
659 };
660}
661
662fn add(c: *JsNodeGen, comptime tag: JsTree.Tag, data: tag.Data()) !Node.Index {
663 const t = tracer.trace(@src());
664 defer t.end();
665
666 try c.nodes.append(c.gpa, .{
667 .tag = tag,
668 .data = EncodedData.encode(tag, data),
669 });
670 return @enumFromInt(c.nodes.len - 1);
671}
672
673fn addStmt(c: *JsNodeGen, stmt: Node.Index) !void {
674 const t = tracer.trace(@src());
675 defer t.end();
676
677 try c.scratch.append(c.arena, @intFromEnum(stmt));
678}
679
680const JsNodeGen = @This();
681
682const std = @import("std");
683const assert = std.debug.assert;
684const Allocator = std.mem.Allocator;
685
686const InternPool = @import("InternPool.zig");
687const Value = InternPool.Value;
688const NullTerminatedString = InternPool.NullTerminatedString;
689
690const Comp = @import("Comp.zig");
691const Decl = Comp.Decl;
692
693const JsTree = @import("JsTree.zig");
694const EncodedData = JsTree.EncodedData;
695const Node = JsTree.Node;
696const LocalIndex = JsTree.LocalIndex;
697
698const Air = @import("Air.zig");
699
700const dod = @import("array.zig");
701const tracer = @import("tracer");
src/core/JsTree.zig deleted-258
...@@ -1,258 +0,0 @@
1//! JavaScript Function Representation is the last representation before javascript
2//! source bytes are created. One notable thing here is that all local identifiers
3//! are numbered, instead of being named. The first `param_count` local identifiers
4//! refer to parameters, and the rest refer to the respective block that defined them.
5
6param_count: u32,
7nodes: Node.List.Slice,
8extern_symbols: []String.WithLen,
9decl_deps: []Decl.Index,
10extra: ExtraStore,
11
12pub const LocalIndex = u32;
13pub const ExtraStore = dod.ExtraStore(.tfr, .@"const");
14pub const EncodedData = dod.EncodedData(Tag, u64);
15pub const ExtraIndex = ExtraStore.Index;
16
17pub const Tag = enum {
18 root,
19 /// Only allow simple value types: int, float, string, bool
20 plain_value,
21 /// Reference to a local identifier (give an VarId)
22 local,
23 /// Reference to a global identifier (give a NullTerminatedString)
24 global,
25 /// Reference to a declaration (contains a Decl.Index)
26 decl,
27 // bin ops
28 assign,
29 assign_add,
30 assign_sub,
31 assign_mul,
32 assign_div,
33 add,
34 sub,
35 mul,
36 div,
37 eq,
38 lt,
39 gt,
40 // exprs
41 call,
42 new,
43 field,
44 ternary,
45 // prefix ops
46 negate,
47 bool_not,
48 pre_inc,
49 pre_dec,
50 // stmts
51 /// `let ... = ...`
52 stmt_declare_local,
53 /// `if (...) ... else ...`
54 stmt_if,
55 /// `break ... ...`
56 stmt_break,
57 /// `return ...` (break from block #0)
58 stmt_return,
59 /// `...: { ... }`
60 stmt_block,
61
62 pub fn Data(comptime tag: Tag) type {
63 return switch (tag) {
64 .root => IndexList,
65 .plain_value => PlainValue,
66 .local => LocalRef,
67 .global => GlobalRef,
68 .decl => DeclRef,
69 .assign,
70 .assign_add,
71 .assign_sub,
72 .assign_mul,
73 .assign_div,
74 .add,
75 .sub,
76 .mul,
77 .div,
78 .eq,
79 .lt,
80 .gt,
81 => Bin,
82 .ternary => Ternary,
83 .negate,
84 .bool_not,
85 .pre_inc,
86 .pre_dec,
87 => Un,
88 .call, .new => Call,
89 .field => Field,
90 .stmt_if => StmtIf,
91 .stmt_break => StmtBreak,
92 .stmt_return => StmtReturn,
93 .stmt_declare_local => StmtDeclareLocal,
94 .stmt_block => StmtBlock,
95 };
96 }
97};
98
99pub const Node = struct {
100 tag: Tag,
101 data: EncodedData,
102
103 pub const List = std.MultiArrayList(Node);
104
105 pub const Index = enum(u32) {
106 root = 0,
107 _,
108
109 pub fn toOptional(i: Index) Optional {
110 return @enumFromInt(@intFromEnum(i));
111 }
112
113 pub const Optional = enum(u32) {
114 none = std.math.maxInt(u32),
115 _,
116
117 pub fn unwrap(oi: Optional) ?Index {
118 return if (oi == .none)
119 null
120 else
121 @enumFromInt(@intFromEnum(oi));
122 }
123 };
124 };
125};
126
127pub const PlainValue = struct {
128 value: Value,
129 unused: Unused = .zero,
130};
131
132pub const LocalRef = struct {
133 id: LocalIndex,
134 unused: Unused = .zero,
135};
136
137pub const GlobalRef = struct {
138 name: InternPool.String.WithLen,
139};
140
141pub const DeclRef = struct {
142 index: Decl.Index,
143 unused: Unused = .zero,
144};
145
146pub const StmtDeclareLocal = struct {
147 identifier_index: u32,
148 init: Node.Index.Optional,
149};
150
151pub const Bin = struct {
152 lhs: Node.Index,
153 rhs: Node.Index,
154};
155
156pub const Un = struct {
157 operand: Node.Index,
158 unused: Unused = .zero,
159};
160
161pub const Call = struct {
162 base: Node.Index,
163 args: ExtraIndex(IndexList),
164};
165
166pub const Field = struct {
167 base: Node.Index,
168 field: NullTerminatedString,
169};
170
171pub const IndexList = struct {
172 start: u32,
173 len: u32,
174
175 pub fn slice(list: IndexList, tree: *const JsTree) []const Node.Index {
176 return @ptrCast(tree.extra.list.items[list.start..][0..list.len]);
177 }
178};
179
180pub const StmtIf = struct {
181 cond: Node.Index,
182 data: ExtraIndex(Data),
183
184 pub const Data = struct {
185 yes: IndexList,
186 no: IndexList,
187 flags: Flags,
188 };
189
190 pub const Flags = packed struct(u32) {
191 block_label: u16,
192 yes_label: bool,
193 no_label: bool,
194 _: u14 = 0,
195 };
196};
197
198pub const Ternary = struct {
199 cond: Node.Index,
200 data: ExtraIndex(Data),
201
202 pub const Data = struct {
203 yes: Node.Index,
204 no: Node.Index,
205 };
206};
207
208pub const StmtBreak = struct {
209 label: u32,
210 unused: Unused = .zero,
211};
212
213pub const StmtReturn = struct {
214 operand: Node.Index.Optional,
215 unused: Unused = .zero,
216};
217
218pub const StmtBlock = struct {
219 label: u32,
220 content: ExtraIndex(IndexList),
221};
222
223pub fn deinit(tree: *JsTree, gpa: std.mem.Allocator) void {
224 tree.nodes.deinit(gpa);
225 gpa.free(tree.decl_deps);
226 gpa.free(tree.extern_symbols);
227 tree.extra.deinit(gpa);
228}
229
230pub fn nodeTag(tree: JsTree, index: Node.Index) Tag {
231 return tree.nodes.items(.tag)[@intFromEnum(index)];
232}
233
234pub fn nodeData(tree: JsTree, comptime tag: Tag, index: Node.Index) tag.Data() {
235 return tree.nodes.items(.data)[@intFromEnum(index)].decode(tag, tree.nodeTag(index));
236}
237
238pub fn mainStatements(tree: *const JsTree) []const Node.Index {
239 return tree.nodeData(.root, .root).slice(tree);
240}
241
242const JsTree = @This();
243
244const std = @import("std");
245const assert = std.debug.assert;
246
247const InternPool = @import("InternPool.zig");
248const Value = InternPool.Value;
249
250const NullTerminatedString = InternPool.NullTerminatedString;
251const String = InternPool.String;
252
253const Comp = @import("Comp.zig");
254const Decl = Comp.Decl;
255
256const dod = @import("array.zig");
257
258const Unused = @import("Ast.zig").Unused;
src/core/Parse.zig+213-34
...@@ -103,14 +103,18 @@ pub const ExpectedContext = enum {...@@ -103,14 +103,18 @@ pub const ExpectedContext = enum {
103 statement,103 statement,
104 expression,104 expression,
105 else_clause,105 else_clause,
106 struct_field,
107 param_list,
106108
107 pub fn description(tag: ExpectedContext) []const u8 {109 pub fn description(tag: ExpectedContext) []const u8 {
108 return switch (tag) {110 return switch (tag) {
109 .param => "a parameter",111 .param => "a parameter",
112 .param_list => "function parameters",
110 .argument => "an argument",113 .argument => "an argument",
111 .statement => "a statement",114 .statement => "a statement",
112 .expression => "an expression",115 .expression => "an expression",
113 .else_clause => "an else clause",116 .else_clause => "an else clause",
117 .struct_field => "a field definition",
114 };118 };
115 }119 }
116};120};
...@@ -233,6 +237,8 @@ fn parseModuleMembers(p: *Parse) Error!SubList {...@@ -233,6 +237,8 @@ fn parseModuleMembers(p: *Parse) Error!SubList {
233 => try p.scratch.append(p.gpa, try p.parseModuleDecl()),237 => try p.scratch.append(p.gpa, try p.parseModuleDecl()),
234238
235 .kw_struct,239 .kw_struct,
240 => try p.scratch.append(p.gpa, try p.parseStructDecl(.declaration)),
241
236 .kw_enum,242 .kw_enum,
237 .kw_trait,243 .kw_trait,
238 .kw_impl,244 .kw_impl,
...@@ -247,7 +253,7 @@ fn parseModuleMembers(p: *Parse) Error!SubList {...@@ -247,7 +253,7 @@ fn parseModuleMembers(p: *Parse) Error!SubList {
247253
248 const first_tag = p.currentTag();254 const first_tag = p.currentTag();
249255
250 try p.scratch.append(p.gpa, try p.parseExpression(true));256 try p.scratch.append(p.gpa, try p.parseExpression(.statement));
251257
252 switch (first_tag) {258 switch (first_tag) {
253 // some control flow and blocks are allowed to not have259 // some control flow and blocks are allowed to not have
...@@ -280,6 +286,14 @@ fn parseModuleDecl(p: *Parse) Error!NodeIndex {...@@ -280,6 +286,14 @@ fn parseModuleDecl(p: *Parse) Error!NodeIndex {
280 };286 };
281}287}
282288
289/// Some constructs are valid expressions as well as valid top-level
290/// declarations. Top level declarations must have associated names, but the
291/// anonymous versions of these must not have names.
292const ExprParseContext = enum {
293 declaration,
294 expression,
295};
296
283fn parseFnDecl(p: *Parse) Error!NodeIndex {297fn parseFnDecl(p: *Parse) Error!NodeIndex {
284 const trace = t.trace(@src());298 const trace = t.trace(@src());
285 defer trace.end();299 defer trace.end();
...@@ -290,20 +304,20 @@ fn parseFnDecl(p: *Parse) Error!NodeIndex {...@@ -290,20 +304,20 @@ fn parseFnDecl(p: *Parse) Error!NodeIndex {
290304
291 _ = try p.expect(.start_paren);305 _ = try p.expect(.start_paren);
292306
293 const params = try p.parseParamList();307 const params = try p.parseParamList(.types_required);
294308
295 const return_type = if (p.eatToken(.minus_lt)) |_|309 const return_type = if (p.eatToken(.minus_lt)) |_|
296 if (!p.isExpressionStart())310 if (!p.isExpressionStart())
297 return p.failUnexpectedToken(.expression)311 return p.failUnexpectedToken(.expression)
298 else312 else
299 try p.parseExpression(false)313 try p.parseExpression(.preceding_block)
300 else314 else
301 null;315 null;
302316
303 try p.expectWithoutAdvance(.start_brace);317 try p.expectWithoutAdvance(.start_brace);
304 const body = try p.parseBlock();318 const body = try p.parseBlock();
305319
306 const extra = try p.addExtra(Ast.FnDecl, .{320 const extra = try p.addExtra(Ast.Fn, .{
307 .params = params,321 .params = params,
308 .return_type = optionalNodeIndex(return_type),322 .return_type = optionalNodeIndex(return_type),
309 });323 });
...@@ -311,14 +325,16 @@ fn parseFnDecl(p: *Parse) Error!NodeIndex {...@@ -311,14 +325,16 @@ fn parseFnDecl(p: *Parse) Error!NodeIndex {
311 return try p.addNode(.{325 return try p.addNode(.{
312 .tag = .fn_decl,326 .tag = .fn_decl,
313 .main_token = main_token,327 .main_token = main_token,
314 .data = .{ .fn_decl = .{328 .data = .{ .function = .{
315 .body = body,329 .body = body,
316 .extra = extra,330 .extra = extra,
317 } },331 } },
318 });332 });
319}333}
320334
321fn parseParamList(p: *Parse) Error!SubList {335const TypePresence = enum { types_optional, types_required };
336
337fn parseParamList(p: *Parse, types: TypePresence) Error!SubList {
322 const trace = t.trace(@src());338 const trace = t.trace(@src());
323 defer trace.end();339 defer trace.end();
324340
...@@ -331,7 +347,7 @@ fn parseParamList(p: *Parse) Error!SubList {...@@ -331,7 +347,7 @@ fn parseParamList(p: *Parse) Error!SubList {
331347
332 if (p.currentTag() == .end_paren) break;348 if (p.currentTag() == .end_paren) break;
333349
334 try p.scratch.append(p.gpa, try p.parseParamDecl());350 try p.scratch.append(p.gpa, try p.parseParamDecl(types));
335351
336 switch (p.currentTag()) {352 switch (p.currentTag()) {
337 .comma => {353 .comma => {
...@@ -347,25 +363,92 @@ fn parseParamList(p: *Parse) Error!SubList {...@@ -347,25 +363,92 @@ fn parseParamList(p: *Parse) Error!SubList {
347 return p.scratchToSubList(scratch_top);363 return p.scratchToSubList(scratch_top);
348}364}
349365
350fn parseParamDecl(p: *Parse) Error!NodeIndex {366fn parseParamDecl(p: *Parse, types: TypePresence) Error!NodeIndex {
351 const trace = t.trace(@src());367 const trace = t.trace(@src());
352 defer trace.end();368 defer trace.end();
353369
354 const main_token = p.assertToken(.id);370 const main_token = p.assertToken(.id);
355371
356 _ = try p.expect(.colon);372 const ty = if (types == .types_required or p.currentTag() == .colon) brk: {
357373 _ = try p.expect(.colon);
358 const ty = try p.parseExpression(false);374 break :brk try p.parseExpression(.expression);
375 } else null;
359376
360 return try p.addNode(.{377 return try p.addNode(.{
361 .tag = .param_decl,378 .tag = .param_decl,
362 .main_token = main_token,379 .main_token = main_token,
363 .data = .{ .param_decl = .{380 .data = .{ .param_decl = .{
364 .type = ty,381 .type = optionalNodeIndex(ty),
365 } },382 } },
366 });383 });
367}384}
368385
386fn parseStructDecl(p: *Parse, ctx: ExprParseContext) Error!NodeIndex {
387 const trace = t.trace(@src());
388 defer trace.end();
389
390 const main_token = p.assertToken(.kw_struct);
391
392 _ = ctx; // TODO:
393 _ = try p.expect(.id);
394
395 _ = try p.expect(.start_brace);
396
397 const fields = try p.parseStructFieldList();
398
399 return try p.addNode(.{
400 .tag = .struct_decl,
401 .main_token = main_token,
402 .data = .{ .list = fields },
403 });
404}
405
406fn parseStructFieldList(p: *Parse) Error!SubList {
407 const trace = t.trace(@src());
408 defer trace.end();
409
410 const scratch_top = p.scratch.items.len;
411 defer p.scratch.shrinkRetainingCapacity(scratch_top);
412
413 while (true) {
414 const trace_param = t.traceFmt(@src(), "Param {d}", .{p.scratch.items.len - scratch_top});
415 defer trace_param.end();
416
417 if (p.currentTag() == .end_brace) break;
418
419 try p.scratch.append(p.gpa, try p.parseStructField());
420
421 switch (p.currentTag()) {
422 .comma => {
423 _ = p.nextToken();
424 },
425 .end_brace => break,
426 else => return p.failUnexpectedToken(.struct_field),
427 }
428 }
429
430 _ = p.assertToken(.end_brace);
431
432 return p.scratchToSubList(scratch_top);
433}
434
435fn parseStructField(p: *Parse) Error!NodeIndex {
436 const trace = t.trace(@src());
437 defer trace.end();
438
439 const main_token = try p.expect(.id);
440
441 _ = try p.expect(.colon);
442
443 const expr = try p.parseExpression(.expression);
444
445 return try p.addNode(.{
446 .tag = .struct_field,
447 .main_token = main_token,
448 .data = .{ .un = .{ .expr = expr } },
449 });
450}
451
369fn parseBlock(p: *Parse) Error!NodeIndex {452fn parseBlock(p: *Parse) Error!NodeIndex {
370 const trace = t.trace(@src());453 const trace = t.trace(@src());
371 defer trace.end();454 defer trace.end();
...@@ -390,7 +473,8 @@ fn parseLabelExpression(p: *Parse) Error!NodeIndex {...@@ -390,7 +473,8 @@ fn parseLabelExpression(p: *Parse) Error!NodeIndex {
390473
391 const main_token = p.assertToken(.id);474 const main_token = p.assertToken(.id);
392 _ = try p.expect(.colon);475 _ = try p.expect(.colon);
393 const expr = try p.parseExpression(false);476 const expr = try p.parseExpression(.expression);
477
394 return try p.addNode(.{478 return try p.addNode(.{
395 .tag = .label,479 .tag = .label,
396 .main_token = main_token,480 .main_token = main_token,
...@@ -422,7 +506,7 @@ fn parseBlockContents(p: *Parse) Error!struct { Node.Tag, SubList } {...@@ -422,7 +506,7 @@ fn parseBlockContents(p: *Parse) Error!struct { Node.Tag, SubList } {
422506
423 const first_tag = p.currentTag();507 const first_tag = p.currentTag();
424508
425 try p.scratch.append(p.gpa, try p.parseExpression(true));509 try p.scratch.append(p.gpa, try p.parseExpression(.statement));
426510
427 switch (first_tag) {511 switch (first_tag) {
428 // some control flow and blocks are allowed to not have512 // some control flow and blocks are allowed to not have
...@@ -466,13 +550,13 @@ fn parseVarDecl(p: *Parse) Error!NodeIndex {...@@ -466,13 +550,13 @@ fn parseVarDecl(p: *Parse) Error!NodeIndex {
466 _ = try p.expect(.id);550 _ = try p.expect(.id);
467551
468 const data_type: OptionalNodeIndex = if (p.eatToken(.colon)) |_|552 const data_type: OptionalNodeIndex = if (p.eatToken(.colon)) |_|
469 optionalNodeIndex(try p.tryParseExpression(false))553 optionalNodeIndex(try p.tryParseExpression(.expression))
470 else554 else
471 .none;555 .none;
472556
473 _ = try p.expect(.equal);557 _ = try p.expect(.equal);
474558
475 const data_init = try p.tryParseExpression(true);559 const data_init = try p.tryParseExpression(.expression);
476560
477 _ = try p.expect(.semi);561 _ = try p.expect(.semi);
478562
...@@ -490,6 +574,7 @@ fn isExpressionStart(p: *Parse) bool {...@@ -490,6 +574,7 @@ fn isExpressionStart(p: *Parse) bool {
490 return switch (p.currentTag()) {574 return switch (p.currentTag()) {
491 .bang,575 .bang,
492 .builtin_id,576 .builtin_id,
577 .dot,
493 .id,578 .id,
494 .kw_assert,579 .kw_assert,
495 .kw_break,580 .kw_break,
...@@ -504,6 +589,7 @@ fn isExpressionStart(p: *Parse) bool {...@@ -504,6 +589,7 @@ fn isExpressionStart(p: *Parse) bool {
504 .start_paren,589 .start_paren,
505 .string,590 .string,
506 .tilde,591 .tilde,
592 .kw_fn,
507 => true,593 => true,
508 else => false,594 else => false,
509 };595 };
...@@ -692,7 +778,17 @@ const Operator = enum(u8) {...@@ -692,7 +778,17 @@ const Operator = enum(u8) {
692 }778 }
693};779};
694780
695fn parseExpression(p: *Parse, allow_assign_operators: bool) Error!NodeIndex {781const ParseExprMode = enum {
782 /// Standard parsing
783 expression,
784 /// Statements allow assignment binary operators in addition to other expressions.
785 statement,
786 /// This bans struct initializations so that parsing stops at a '{'
787 /// Example: "if hello {" must not be parsed as a struct initialization.
788 preceding_block,
789};
790
791fn parseExpression(p: *Parse, mode: ParseExprMode) Error!NodeIndex {
696 const trace = t.trace(@src());792 const trace = t.trace(@src());
697 defer trace.end();793 defer trace.end();
698794
...@@ -711,24 +807,24 @@ fn parseExpression(p: *Parse, allow_assign_operators: bool) Error!NodeIndex {...@@ -711,24 +807,24 @@ fn parseExpression(p: *Parse, allow_assign_operators: bool) Error!NodeIndex {
711 else => {},807 else => {},
712 }808 }
713809
714 var expr = try p.parseExpressionPart();810 var expr = try p.parseExpressionPart(mode);
715811
716 while (Operator.fromToken(p.currentTag())) |op| {812 while (Operator.fromToken(p.currentTag())) |op| {
717 if (!allow_assign_operators and op.isAssign()) break;813 if (mode != .statement and op.isAssign()) break;
718 expr = try p.parseBinarySuffix(expr, op, allow_assign_operators);814 expr = try p.parseBinarySuffix(expr, op, mode);
719 }815 }
720816
721 return expr;817 return expr;
722}818}
723819
724fn tryParseExpression(p: *Parse, allow_assign_operators: bool) Error!NodeIndex {820fn tryParseExpression(p: *Parse, mode: ParseExprMode) Error!NodeIndex {
725 return if (p.isExpressionStart())821 return if (p.isExpressionStart())
726 p.parseExpression(allow_assign_operators)822 p.parseExpression(mode)
727 else823 else
728 p.failUnexpectedToken(.expression);824 p.failUnexpectedToken(.expression);
729}825}
730826
731fn parseExpressionPart(p: *Parse) Error!NodeIndex {827fn parseExpressionPart(p: *Parse, mode: ParseExprMode) Error!NodeIndex {
732 const trace = t.trace(@src());828 const trace = t.trace(@src());
733 defer trace.end();829 defer trace.end();
734830
...@@ -749,6 +845,8 @@ fn parseExpressionPart(p: *Parse) Error!NodeIndex {...@@ -749,6 +845,8 @@ fn parseExpressionPart(p: *Parse) Error!NodeIndex {
749 .kw_if => try p.parseIf(),845 .kw_if => try p.parseIf(),
750 .start_paren => try p.parseGroup(),846 .start_paren => try p.parseGroup(),
751 .start_brace => try p.parseBlock(),847 .start_brace => try p.parseBlock(),
848 .dot => try p.parseAnonInstantiation(),
849 .kw_fn => try p.parseAnonFunction(),
752850
753 // Unary operators851 // Unary operators
754 inline .bang,852 inline .bang,
...@@ -759,7 +857,7 @@ fn parseExpressionPart(p: *Parse) Error!NodeIndex {...@@ -759,7 +857,7 @@ fn parseExpressionPart(p: *Parse) Error!NodeIndex {
759 .kw_assert,857 .kw_assert,
760 => |tag| unary: {858 => |tag| unary: {
761 const main_token = p.nextToken();859 const main_token = p.nextToken();
762 const expr = try p.parseExpressionPart();860 const expr = try p.parseExpressionPart(mode);
763861
764 break :unary try p.addNode(.{862 break :unary try p.addNode(.{
765 .tag = switch (tag) {863 .tag = switch (tag) {
...@@ -819,7 +917,7 @@ fn parseExpressionPart(p: *Parse) Error!NodeIndex {...@@ -819,7 +917,7 @@ fn parseExpressionPart(p: *Parse) Error!NodeIndex {
819 },917 },
820 .start_bracket => {918 .start_bracket => {
821 const main_token = p.nextToken();919 const main_token = p.nextToken();
822 const index = try p.parseExpression(false);920 const index = try p.parseExpression(.expression);
823921
824 expr = try p.addNode(.{922 expr = try p.addNode(.{
825 .tag = .index,923 .tag = .index,
...@@ -830,7 +928,20 @@ fn parseExpressionPart(p: *Parse) Error!NodeIndex {...@@ -830,7 +928,20 @@ fn parseExpressionPart(p: *Parse) Error!NodeIndex {
830 } },928 } },
831 });929 });
832 },930 },
931 .start_brace => {
932 if (mode == .preceding_block) break;
833933
934 const main_token = p.nextToken();
935 const initializers = try p.parseStructFieldList();
936 expr = try p.addNode(.{
937 .tag = .struct_init,
938 .main_token = main_token,
939 .data = .{ .struct_init = .{
940 .base = optionalNodeIndex(expr),
941 .extra = try p.addExtra(Ast.SubList, initializers),
942 } },
943 });
944 },
834 else => break,945 else => break,
835 }946 }
836 }947 }
...@@ -838,16 +949,16 @@ fn parseExpressionPart(p: *Parse) Error!NodeIndex {...@@ -838,16 +949,16 @@ fn parseExpressionPart(p: *Parse) Error!NodeIndex {
838 return expr;949 return expr;
839}950}
840951
841fn parseBinarySuffix(p: *Parse, lhs: NodeIndex, op: Operator, allow_assign_operators: bool) Error!NodeIndex {952fn parseBinarySuffix(p: *Parse, lhs: NodeIndex, op: Operator, mode: ParseExprMode) Error!NodeIndex {
842 const trace = t.trace(@src());953 const trace = t.trace(@src());
843 defer trace.end();954 defer trace.end();
844955
845 const main_token = p.nextToken();956 const main_token = p.nextToken();
846 const level = op.level();957 const level = op.level();
847 var rhs = try p.parseExpressionPart();958 var rhs = try p.parseExpressionPart(mode);
848959
849 while (Operator.fromToken(p.currentTag())) |next_op| {960 while (Operator.fromToken(p.currentTag())) |next_op| {
850 if (!allow_assign_operators and next_op.isAssign()) break;961 if (mode != .statement and next_op.isAssign()) break;
851962
852 const new_level = next_op.level();963 const new_level = next_op.level();
853 const use = switch (op.direction()) {964 const use = switch (op.direction()) {
...@@ -856,7 +967,7 @@ fn parseBinarySuffix(p: *Parse, lhs: NodeIndex, op: Operator, allow_assign_opera...@@ -856,7 +967,7 @@ fn parseBinarySuffix(p: *Parse, lhs: NodeIndex, op: Operator, allow_assign_opera
856 };967 };
857 if (!use) break;968 if (!use) break;
858969
859 rhs = try p.parseBinarySuffix(rhs, next_op, allow_assign_operators);970 rhs = try p.parseBinarySuffix(rhs, next_op, mode);
860 }971 }
861972
862 return try p.addNode(.{973 return try p.addNode(.{
...@@ -876,7 +987,7 @@ fn parseReturn(p: *Parse) Error!NodeIndex {...@@ -876,7 +987,7 @@ fn parseReturn(p: *Parse) Error!NodeIndex {
876 const main_token = p.assertToken(.kw_return);987 const main_token = p.assertToken(.kw_return);
877988
878 const expr = if (p.isExpressionStart())989 const expr = if (p.isExpressionStart())
879 try p.parseExpression(false)990 try p.parseExpression(.expression)
880 else991 else
881 null;992 null;
882993
...@@ -901,7 +1012,7 @@ fn parseBreak(p: *Parse) Error!NodeIndex {...@@ -901,7 +1012,7 @@ fn parseBreak(p: *Parse) Error!NodeIndex {
901 null;1012 null;
9021013
903 const expr = if (p.isExpressionStart())1014 const expr = if (p.isExpressionStart())
904 try p.parseExpression(false)1015 try p.parseExpression(.expression)
905 else1016 else
906 null;1017 null;
9071018
...@@ -940,7 +1051,7 @@ fn parseCallArgs(p: *Parse) Error!SubList {...@@ -940,7 +1051,7 @@ fn parseCallArgs(p: *Parse) Error!SubList {
940 defer p.scratch.shrinkRetainingCapacity(scratch_top);1051 defer p.scratch.shrinkRetainingCapacity(scratch_top);
9411052
942 while (p.isExpressionStart()) {1053 while (p.isExpressionStart()) {
943 try p.scratch.append(p.gpa, try p.parseExpression(false));1054 try p.scratch.append(p.gpa, try p.parseExpression(.expression));
9441055
945 switch (p.currentTag()) {1056 switch (p.currentTag()) {
946 .end_paren => {1057 .end_paren => {
...@@ -966,7 +1077,7 @@ fn parseIf(p: *Parse) Error!NodeIndex {...@@ -966,7 +1077,7 @@ fn parseIf(p: *Parse) Error!NodeIndex {
9661077
967 const main_token = p.assertToken(.kw_if);1078 const main_token = p.assertToken(.kw_if);
9681079
969 const cond = try p.parseExpression(false);1080 const cond = try p.parseExpression(.preceding_block);
9701081
971 _ = try p.expectWithoutAdvance(.start_brace);1082 _ = try p.expectWithoutAdvance(.start_brace);
972 const yes = try p.parseBlock();1083 const yes = try p.parseBlock();
...@@ -1012,7 +1123,7 @@ fn parseGroup(p: *Parse) Error!NodeIndex {...@@ -1012,7 +1123,7 @@ fn parseGroup(p: *Parse) Error!NodeIndex {
10121123
1013 const main_token = p.assertToken(.start_paren);1124 const main_token = p.assertToken(.start_paren);
10141125
1015 const expr = try p.parseExpression(false);1126 const expr = try p.parseExpression(.expression);
10161127
1017 _ = try p.expect(.end_paren);1128 _ = try p.expect(.end_paren);
10181129
...@@ -1025,6 +1136,74 @@ fn parseGroup(p: *Parse) Error!NodeIndex {...@@ -1025,6 +1136,74 @@ fn parseGroup(p: *Parse) Error!NodeIndex {
1025 });1136 });
1026}1137}
10271138
1139fn parseAnonInstantiation(p: *Parse) Error!NodeIndex {
1140 const trace = t.trace(@src());
1141 defer trace.end();
1142
1143 _ = p.assertToken(.dot);
1144
1145 const main_token = try p.expect(.start_brace);
1146 const initializers = try p.parseStructFieldList();
1147 return try p.addNode(.{
1148 .tag = .struct_init,
1149 .main_token = main_token,
1150 .data = .{ .struct_init = .{
1151 .base = .none,
1152 .extra = try p.addExtra(Ast.SubList, initializers),
1153 } },
1154 });
1155}
1156
1157fn parseAnonFunction(p: *Parse) Error!NodeIndex {
1158 const trace = t.trace(@src());
1159 defer trace.end();
1160
1161 const main_token = p.assertToken(.kw_fn);
1162
1163 const params = switch (p.currentTag()) {
1164 // fn { 1 }
1165 .start_brace => SubList.empty,
1166 // fn (args, here) { 2 }
1167 .start_paren => param_list: {
1168 _ = p.assertToken(.start_paren);
1169 break :param_list try p.parseParamList(.types_optional);
1170 },
1171 // fn arg { 3 }
1172 .id => single_param: {
1173 const scratch_top = p.scratch.items.len;
1174 defer p.scratch.shrinkRetainingCapacity(scratch_top);
1175 try p.scratch.append(p.gpa, try p.blankNode(.id));
1176 break :single_param try p.scratchToSubList(scratch_top);
1177 },
1178 else => return p.failUnexpectedToken(.param_list),
1179 };
1180
1181 const return_type = if (p.eatToken(.minus_lt)) |_|
1182 if (!p.isExpressionStart())
1183 return p.failUnexpectedToken(.expression)
1184 else
1185 try p.parseExpression(.preceding_block)
1186 else
1187 null;
1188
1189 try p.expectWithoutAdvance(.start_brace);
1190 const body = try p.parseBlock();
1191
1192 const extra = try p.addExtra(Ast.Fn, .{
1193 .params = params,
1194 .return_type = optionalNodeIndex(return_type),
1195 });
1196
1197 return try p.addNode(.{
1198 .tag = .fn_expr,
1199 .main_token = main_token,
1200 .data = .{ .function = .{
1201 .extra = extra,
1202 .body = body,
1203 } },
1204 });
1205}
1206
1028// helpers1207// helpers
10291208
1030/// Append a node and return its index.1209/// Append a node and return its index.
src/core/Report.zig+139-17
...@@ -174,8 +174,8 @@ pub const Err = union(enum) {...@@ -174,8 +174,8 @@ pub const Err = union(enum) {
174 fn build(data: @This(), ctx: *MsgContext) !void {174 fn build(data: @This(), ctx: *MsgContext) !void {
175 const node = try data.src.get(ctx);175 const node = try data.src.get(ctx);
176 try ctx.msgNode(node, "expected \"{s}\", got \"{s}\"", .{176 try ctx.msgNode(node, "expected \"{s}\", got \"{s}\"", .{
177 data.to.displayName(&ctx.comp.intern_pool),177 data.to.displayName(ctx.ip()),
178 data.from.displayName(&ctx.comp.intern_pool),178 data.from.displayName(ctx.ip()),
179 });179 });
180 }180 }
181 },181 },
...@@ -186,7 +186,7 @@ pub const Err = union(enum) {...@@ -186,7 +186,7 @@ pub const Err = union(enum) {
186 fn build(data: @This(), ctx: *MsgContext) !void {186 fn build(data: @This(), ctx: *MsgContext) !void {
187 const node = try data.call.get(ctx);187 const node = try data.call.get(ctx);
188 try ctx.msgNode(node, "\"{s}\" is not callable", .{188 try ctx.msgNode(node, "\"{s}\" is not callable", .{
189 data.ty.displayName(&ctx.comp.intern_pool),189 data.ty.displayName(ctx.ip()),
190 });190 });
191 }191 }
192 },192 },
...@@ -210,7 +210,7 @@ pub const Err = union(enum) {...@@ -210,7 +210,7 @@ pub const Err = union(enum) {
210 fn build(data: @This(), ctx: *MsgContext) !void {210 fn build(data: @This(), ctx: *MsgContext) !void {
211 const node = try data.src.get(ctx);211 const node = try data.src.get(ctx);
212 try ctx.msgNode(node, "type \"{}\" cannot be represented as a runtime value", .{212 try ctx.msgNode(node, "type \"{}\" cannot be represented as a runtime value", .{
213 data.type.displayName(&ctx.comp.intern_pool),213 data.type.displayName(ctx.ip()),
214 });214 });
215 }215 }
216 },216 },
...@@ -221,7 +221,7 @@ pub const Err = union(enum) {...@@ -221,7 +221,7 @@ pub const Err = union(enum) {
221 fn build(data: @This(), ctx: *MsgContext) !void {221 fn build(data: @This(), ctx: *MsgContext) !void {
222 const node = try data.src.get(ctx);222 const node = try data.src.get(ctx);
223 try ctx.msgNode(node, "parameter of comptime type \"{s}\" must be marked \"comptime\"", .{223 try ctx.msgNode(node, "parameter of comptime type \"{s}\" must be marked \"comptime\"", .{
224 data.type.displayName(&ctx.comp.intern_pool),224 data.type.displayName(ctx.ip()),
225 });225 });
226 }226 }
227 },227 },
...@@ -275,8 +275,24 @@ pub const Err = union(enum) {...@@ -275,8 +275,24 @@ pub const Err = union(enum) {
275 "\"{s}\" operator is not compatible with types \"{}\" and \"{}\"",275 "\"{s}\" operator is not compatible with types \"{}\" and \"{}\"",
276 .{276 .{
277 ctx.ast.tokenSlice(ctx.ast.nodeToken(node)),277 ctx.ast.tokenSlice(ctx.ast.nodeToken(node)),
278 data.lhs.displayName(&ctx.comp.intern_pool),278 data.lhs.displayName(ctx.ip()),
279 data.rhs.displayName(&ctx.comp.intern_pool),279 data.rhs.displayName(ctx.ip()),
280 },
281 );
282 }
283 },
284 un_op_mismatch: struct {
285 src: LazyNode,
286 ty: Type,
287
288 fn build(data: @This(), ctx: *MsgContext) !void {
289 const node = try data.src.get(ctx);
290 try ctx.msgNode(
291 node,
292 "\"{s}\" operator is not compatible with type \"{}\"",
293 .{
294 ctx.ast.tokenSlice(ctx.ast.nodeToken(node)),
295 data.ty.displayName(ctx.ip()),
280 },296 },
281 );297 );
282 }298 }
...@@ -293,6 +309,96 @@ pub const Err = union(enum) {...@@ -293,6 +309,96 @@ pub const Err = union(enum) {
293 );309 );
294 }310 }
295 },311 },
312 struct_init_invalid_type: struct {
313 ty: Type,
314 src: LazyNode,
315
316 fn build(data: @This(), ctx: *MsgContext) !void {
317 const node = try data.src.get(ctx);
318 try ctx.msgNode(
319 node,
320 "\"{}\" is not constructable via struct initialization syntax",
321 .{data.ty.displayName(ctx.ip())},
322 );
323 }
324 },
325 struct_init_unknown_field: struct {
326 ty: Type,
327 src: LazyNode,
328
329 fn build(data: @This(), ctx: *MsgContext) !void {
330 const node = try data.src.get(ctx);
331 try ctx.msgNode(
332 node,
333 "\"{}\" does not have field {}",
334 .{ data.ty.displayName(ctx.ip()), formatId("field") },
335 );
336 }
337 },
338 struct_init_duplicate_field: struct {
339 ty: Type,
340 src: LazyNode,
341
342 fn build(data: @This(), ctx: *MsgContext) !void {
343 const node = try data.src.get(ctx);
344 try ctx.msgNode(
345 node,
346 "duplicate field {}",
347 .{formatId("field")},
348 );
349 }
350 },
351 struct_init_missing_field: struct {
352 ty: Type,
353 src: LazyNode,
354 field: u32,
355
356 fn build(data: @This(), ctx: *MsgContext) !void {
357 const node = try data.src.get(ctx);
358 try ctx.msgNode(
359 node,
360 "{} is missing field {}",
361 .{
362 data.ty.displayName(ctx.ip()),
363 formatId(
364 data.ty.toStructType(ctx.ip()).?
365 .field_keys[data.field].toSlice(ctx.ip()),
366 ),
367 },
368 );
369 }
370 },
371
372 field_lookup_failed: struct {
373 ty: Type,
374 src: LazyNode,
375 field: Value,
376
377 fn build(data: @This(), ctx: *MsgContext) !void {
378 const node = try data.src.get(ctx);
379 try ctx.msgNode(
380 node,
381 "{} is not a field on \"{}\"",
382 .{
383 formatId(data.field.asString(ctx.ip()).toSlice(ctx.ip())),
384 data.ty.displayName(ctx.ip()),
385 },
386 );
387 }
388 },
389 field_invalid_type: struct {
390 ty: Type,
391 src: LazyNode,
392
393 fn build(data: @This(), ctx: *MsgContext) !void {
394 const node = try data.src.get(ctx);
395 try ctx.msgNode(
396 node,
397 "\"{}\" does not support field access",
398 .{data.ty.displayName(ctx.ip())},
399 );
400 }
401 },
296};402};
297403
298pub const Warn = union(enum) {404pub const Warn = union(enum) {
...@@ -783,8 +889,8 @@ pub const Highlight = union(enum) {...@@ -783,8 +889,8 @@ pub const Highlight = union(enum) {
783889
784/// This serves as a small API to make creating messages easier.890/// This serves as a small API to make creating messages easier.
785pub const MsgContext = struct {891pub const MsgContext = struct {
786 file: *File,892 file: *const File,
787 comp: *Comp,893 comp: *const Comp,
788 ast: *const Ast,894 ast: *const Ast,
789 report: *const Report,895 report: *const Report,
790 utf8_source: [:0]const u8,896 utf8_source: [:0]const u8,
...@@ -819,6 +925,10 @@ pub const MsgContext = struct {...@@ -819,6 +925,10 @@ pub const MsgContext = struct {
819 pub fn msgAlone(m: *MsgContext, comptime fmt: []const u8, args: anytype) !void {925 pub fn msgAlone(m: *MsgContext, comptime fmt: []const u8, args: anytype) !void {
820 return m.msg(.alone, fmt, args);926 return m.msg(.alone, fmt, args);
821 }927 }
928
929 pub fn ip(ctx: *const MsgContext) *const InternPool {
930 return &ctx.comp.intern_pool;
931 }
822};932};
823933
824pub const LazyNode = struct {934pub const LazyNode = struct {
...@@ -828,7 +938,7 @@ pub const LazyNode = struct {...@@ -828,7 +938,7 @@ pub const LazyNode = struct {
828938
829 pub fn get(lazy: LazyNode, ctx: *MsgContext) !NodeIndex {939 pub fn get(lazy: LazyNode, ctx: *MsgContext) !NodeIndex {
830 const decl = ctx.comp.intern_pool.declPtrConst(lazy.decl);940 const decl = ctx.comp.intern_pool.declPtrConst(lazy.decl);
831 const ns = ctx.comp.intern_pool.namespacePtr(decl.src_namespace);941 const ns = ctx.comp.intern_pool.namespacePtrConst(decl.src_namespace);
832942
833 // TODO: This might change if we have errors like943 // TODO: This might change if we have errors like
834 // - function arguments not matching at <callsite>944 // - function arguments not matching at <callsite>
...@@ -845,10 +955,9 @@ pub const LazyNode = struct {...@@ -845,10 +955,9 @@ pub const LazyNode = struct {
845 // this will be required once incremental compilation hits955 // this will be required once incremental compilation hits
846 @panic("TODO: teg was unloaded"));956 @panic("TODO: teg was unloaded"));
847957
848 const decl_inst = decl.teg_decl_index.unwrap() orelse958 const decl_inst = decl.teg_decl_index;
849 unreachable; // file was not analyzed, yet teg loaded?
850959
851 const teg_decl = teg.instData(.declaration, decl_inst.resolve(&ctx.comp.intern_pool));960 const teg_decl = teg.instData(.declaration, decl_inst.resolve(ctx.ip()));
852961
853 const node_offset = teg.instNodeOffset(lazy.inst);962 const node_offset = teg.instNodeOffset(lazy.inst);
854963
...@@ -903,7 +1012,7 @@ pub const LazyNode = struct {...@@ -903,7 +1012,7 @@ pub const LazyNode = struct {
903 },1012 },
904 .declaration => {1013 .declaration => {
905 assert(node_tag == .fn_decl);1014 assert(node_tag == .fn_decl);
906 const fn_decl = ctx.ast.extraData(data.fn_decl.extra, Ast.FnDecl);1015 const fn_decl = ctx.ast.extraData(data.function.extra, Ast.Fn);
907 return switch (lazy.operand) {1016 return switch (lazy.operand) {
908 .return_type => fn_decl.return_type.unwrap().?,1017 .return_type => fn_decl.return_type.unwrap().?,
909 else => fn_decl.params.slice(ctx.ast)[lazy.operand.getArgId()],1018 else => fn_decl.params.slice(ctx.ast)[lazy.operand.getArgId()],
...@@ -912,11 +1021,24 @@ pub const LazyNode = struct {...@@ -912,11 +1021,24 @@ pub const LazyNode = struct {
912 .js => {1021 .js => {
913 return base_node;1022 return base_node;
914 },1023 },
1024 .struct_init => {
1025 assert(node_tag == .struct_init);
1026 switch (lazy.operand) {
1027 .operand => return data.struct_init.base.unwrap() orelse
1028 // for anon struct initializations, this will point at the '.'
1029 base_node,
1030 else => {
1031 const fields = ctx.ast
1032 .extraData(data.struct_init.extra, Ast.SubList)
1033 .slice(ctx.ast);
1034 const field = fields[lazy.operand.getArgId()];
1035 return ctx.ast.nodeData(field).un.expr;
1036 },
1037 }
1038 },
915 else => {},1039 else => {},
916 }1040 }
9171041
918 if (!std.debug.runtime_safety) unreachable;
919
920 std.debug.panic("TODO: operand lookup: .{s} + .{s} + {}", .{1042 std.debug.panic("TODO: operand lookup: .{s} + .{s} + {}", .{
921 @tagName(teg.instTag(lazy.inst)),1043 @tagName(teg.instTag(lazy.inst)),
922 @tagName(ctx.ast.nodeTag(base_node)),1044 @tagName(ctx.ast.nodeTag(base_node)),
...@@ -1020,7 +1142,7 @@ fn formatIdImpl(...@@ -1020,7 +1142,7 @@ fn formatIdImpl(
1020 // differences in the string formatters. Ideally these are merged1142 // differences in the string formatters. Ideally these are merged
1021 // under a common function that takes an argument of the 'mode' to1143 // under a common function that takes an argument of the 'mode' to
1022 // print the string in.1144 // print the string in.
1023 const JSLink = @import("JsLink.zig");1145 const JSLink = @import("mod.zig").Js.Link;
1024 const indexOfNeedsEscape = JSLink.indexOfNeedsEscape;1146 const indexOfNeedsEscape = JSLink.indexOfNeedsEscape;
1025 const canPrintWithoutEscape = JSLink.canPrintWithoutEscape;1147 const canPrintWithoutEscape = JSLink.canPrintWithoutEscape;
10261148
src/core/Sema.zig+424-95
...@@ -1,5 +1,28 @@...@@ -1,5 +1,28 @@
1/// absolutely stealing zig on this one haha1/// absolutely stealing zig on this one haha
2/// This struct is created per declaration and chills2/// This struct is created per declaration and chills
3///
4/// TODO: explain this in more depth. or maybe take this to architecture.md
5///
6/// All things in semantic analysis are either Air.Inst.Index (runtime known
7/// value pointing to the instruction that computed it), or Value (comptime known)
8///
9/// There is a union of these two called Ref [Air.Inst.Ref]. Generally,
10/// functions referring to Ref refer to runtime known values.
11///
12/// Many things have two versions, one for Ref (runtime) + one for Value (comptime)
13///
14/// Example is going from Teg.Inst.Index to the result of it's analysis:
15/// - s.resolveRef()
16/// - s.resolveValue()
17/// The latter will ensure the result is a Value (comptime) and return that.
18///
19/// Many things in Semantic Analysis don't have a way to represent constants
20/// using Air.Inst.Index, and we also want to avoid emitting things like `1 + 2`.
21///
22/// We do that by using Air.Inst.Ref.value() or .decode(), which allows
23/// observing if the value is known at compile time. This makes analysis
24/// lazy and forces constant folding whenever possible. (see .bool_not for a
25/// simple example)
326
4// inputs:27// inputs:
5comp: *Comp,28comp: *Comp,
...@@ -16,12 +39,14 @@ inst_map: InstMap = undefined,...@@ -16,12 +39,14 @@ inst_map: InstMap = undefined,
16/// scratch memory. can be used in any place you might temporarily allocate an arraylist39/// scratch memory. can be used in any place you might temporarily allocate an arraylist
17/// this is most notably used to store block contents before the block is finalized40/// this is most notably used to store block contents before the block is finalized
18scratch: std.ArrayListUnmanaged(u32) = .{},41scratch: std.ArrayListUnmanaged(u32) = .{},
42
43/// This is only set for function declarations
19params: []Type = undefined,44params: []Type = undefined,
2045
21// outputs46// outputs
22report: Report.Builder = undefined,47report: Report.Builder = undefined,
23air_instructions: std.MultiArrayList(Air.Inst) = .{},48air_instructions: std.MultiArrayList(Air.Inst) = .{},
24air_extra: dod.ExtraStore(.air, .@"var") = .{},49air_extra: store.ExtraStore(.air, .@"var") = .{},
2550
26temp_result_map: std.AutoArrayHashMapUnmanaged(Teg.Inst.Index, Type) = .{},51temp_result_map: std.AutoArrayHashMapUnmanaged(Teg.Inst.Index, Type) = .{},
2752
...@@ -41,7 +66,7 @@ pub const Error = error{...@@ -41,7 +66,7 @@ pub const Error = error{
41 OutOfMemory,66 OutOfMemory,
42} || Comp.File.GetTegError;67} || Comp.File.GetTegError;
4368
44/// Instead of using a hash map for this, just pre-allocate a list long enough to hold everything.69/// Instead of using a Hash map for this, just pre-allocate a list long enough to hold everything.
45/// This is fine because all instructions are going to be adjacent in a single declaration.70/// This is fine because all instructions are going to be adjacent in a single declaration.
46const InstMap = struct {71const InstMap = struct {
47 items: []Air.Inst.Ref.Optional,72 items: []Air.Inst.Ref.Optional,
...@@ -70,10 +95,11 @@ const InstMap = struct {...@@ -70,10 +95,11 @@ const InstMap = struct {
70 }95 }
7196
72 pub fn add(map: *InstMap, from: Teg.Inst.Index, to: Air.Inst.Ref) !void {97 pub fn add(map: *InstMap, from: Teg.Inst.Index, to: Air.Inst.Ref) !void {
73 if (assertWithMessage(map.get(from) == null)) {98 if (assertFailed(map.get(from) == null)) {
74 std.debug.panic("%{d} is already mapped (to #{d})", .{99 const s: *const Sema = @fieldParentPtr("inst_map", map);
100 std.debug.panic("%{d} is already mapped (to {})", .{
75 @intFromEnum(from),101 @intFromEnum(from),
76 @intFromEnum(map.get(from).?.decode().index),102 map.get(from).?.dump(s.ip),
77 });103 });
78 }104 }
79 const source_i = @intFromEnum(from);105 const source_i = @intFromEnum(from);
...@@ -91,12 +117,10 @@ pub fn init(s: *Sema) !void {...@@ -91,12 +117,10 @@ pub fn init(s: *Sema) !void {
91 .current_file = s.ip.namespacePtr(s.namespace_index).file_scope,117 .current_file = s.ip.namespacePtr(s.namespace_index).file_scope,
92 };118 };
93119
94 try s.comp.current_compile_decls.append(s.gpa, s.decl_index);
95
96 s.inst_map = try InstMap.init(120 s.inst_map = try InstMap.init(
97 s.arena,121 s.arena,
98 &s.code,122 &s.code,
99 s.ip.declPtr(s.decl_index).teg_decl_index.unwrap().?.resolve(s.ip),123 s.ip.declPtr(s.decl_index).teg_decl_index.resolve(s.ip),
100 );124 );
101}125}
102126
...@@ -117,21 +141,55 @@ pub fn deinit(s: *Sema) void {...@@ -117,21 +141,55 @@ pub fn deinit(s: *Sema) void {
117}141}
118142
119pub fn finalize(s: *Sema) !void {143pub fn finalize(s: *Sema) !void {
144 s.decl.analysis = .complete;
145
120 const air: Air = s.tempAir();146 const air: Air = s.tempAir();
121147
122 if (s.comp.options.dump_ir) {148 if (s.decl.value().valueTag(s.ip) == .func) {
123 std.debug.print("=" ** 20, .{});149 if (s.comp.options.dump_ir) {
124 std.debug.print("\nAir for @ {d}\n", .{@intFromEnum(s.decl.teg_decl_index)});150 std.debug.print("=" ** 20, .{});
125 air.dumpToWriter(s.ip, std.io.getStdErr().writer()) catch {};151 std.debug.print("\nAir for {}\n", .{s.decl.value().dump(s.ip)});
126 }152 air.dumpToWriter(s.ip, std.io.getStdErr().writer()) catch {};
153 }
127154
128 try s.comp.codeGenDecl(s.decl, &air);155 try s.comp.codeGenDecl(s.decl.value(), &air);
156 }
129}157}
130158
131// --- analyze functions ---159// --- analyze functions ---
132160
133/// This is the entrypoint to semantic analysis: It all starts at a declaration.161/// This is the entrypoint to semantic analysis: It all starts at a declaration.
134pub fn analyzeDeclaration(s: *Sema) Error!Value {162pub fn analyzeDeclaration(s: *Sema) Error!Value {
163 const decl = s.code.declData(s.inst_map.start);
164 switch (decl.kind) {
165 .function => return analyzeFunctionDeclaration(s),
166 .immutable => {},
167 }
168
169 const root_block = decl.body;
170 switch (root_block.decode()) {
171 .value => |val| {
172 s.decl.raw_value = val;
173 s.decl.analysis = .complete;
174 },
175 .index => |index| {
176 const result = try s.analyzeBlock(index);
177 switch (result.decode()) {
178 .value => |val| {
179 s.decl.raw_value = val;
180 s.decl.analysis = .complete;
181 },
182 .index => @panic("TODO"),
183 }
184 },
185 }
186
187 return s.decl.value();
188}
189
190/// TODO: Delete this legacy code. This will be replaced by analyzeDeclaration and a lambda
191/// function (See #45)
192pub fn analyzeFunctionDeclaration(s: *Sema) Error!Value {
135 const t = tracer.trace(@src());193 const t = tracer.trace(@src());
136 defer t.end();194 defer t.end();
137195
...@@ -164,7 +222,7 @@ pub fn analyzeDeclaration(s: *Sema) Error!Value {...@@ -164,7 +222,7 @@ pub fn analyzeDeclaration(s: *Sema) Error!Value {
164 );222 );
165223
166 const func_prototype = (try s.ip.get(s.gpa, .{224 const func_prototype = (try s.ip.get(s.gpa, .{
167 .type_function = .{225 .function_type = .{
168 .param_types = s.params,226 .param_types = s.params,
169 .return_type = return_type,227 .return_type = return_type,
170 },228 },
...@@ -173,14 +231,14 @@ pub fn analyzeDeclaration(s: *Sema) Error!Value {...@@ -173,14 +231,14 @@ pub fn analyzeDeclaration(s: *Sema) Error!Value {
173 .func = .{231 .func = .{
174 .ty = func_prototype,232 .ty = func_prototype,
175 .owner_decl = s.decl_index,233 .owner_decl = s.decl_index,
176 .teg_body_inst = s.decl.teg_decl_index.unwrap().?,234 .teg_body_inst = s.decl.teg_decl_index,
177 },235 },
178 });236 });
179 s.decl.val = value;
180237
181 s.decl.analysis = .in_progress_prototype_available;238 s.decl.raw_value = value.typeOf(s.ip).value();
239 s.decl.analysis = .in_progress_type_available;
182240
183 const root_block = fn_decl.body;241 const root_block = decl.body;
184 switch (root_block.decode()) {242 switch (root_block.decode()) {
185 .value => |val| {243 .value => |val| {
186 const coerced = try s.coerceValue(244 const coerced = try s.coerceValue(
...@@ -200,6 +258,9 @@ pub fn analyzeDeclaration(s: *Sema) Error!Value {...@@ -200,6 +258,9 @@ pub fn analyzeDeclaration(s: *Sema) Error!Value {
200 },258 },
201 }259 }
202260
261 s.decl.analysis = .complete;
262 s.decl.raw_value = value;
263
203 return value;264 return value;
204}265}
205266
...@@ -268,31 +329,73 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {...@@ -268,31 +329,73 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {
268 return s.makeValueAir(.{ .string = string.slice(s.code.string_bytes) });329 return s.makeValueAir(.{ .string = string.slice(s.code.string_bytes) });
269 },330 },
270 .number => {331 .number => {
271 const number: dod.Float = s.code.instPayload(.number, in);332 const number: store.Float = s.code.instPayload(.number, in);
272 return s.makeValueAir(.{ .comptime_float = number.decode() });333 return s.makeValueAir(.{ .comptime_float = number.decode() });
273 },334 },
274 .js => {335 .js => {
275 const operand_ref = s.code.instData(.js, in).operand;336 const operand_ref = s.code.instData(.js, in).operand;
276 const operand = try s.resolveRefComptimeType(in.op(.operand), operand_ref, .string);337 const operand = try s.resolveCoerceValue(in.op(.operand), operand_ref, .string);
277 return s.addInstRef(.js_global_ref, operand.asString(s.ip));338 return s.addInstRef(.js_global_ref, operand.asString(s.ip));
278 },339 },
279 .field => {340 .field => {
280 const field: Teg.Inst.Bin = s.code.instPayload(.field, in);341 const field: Teg.Inst.Bin = s.code.instPayload(.field, in);
281 const base = try s.resolveRefRuntime(field.lhs);342 const base = try s.resolveRef(field.lhs);
282343
283 const rhs = try s.resolveRefComptimeType(in.op(.rhs), field.rhs, .string);344 const rhs = try s.resolveCoerceValue(in.op(.rhs), field.rhs, .string);
284 const string = rhs.asString(s.ip);345 const string = rhs.asString(s.ip);
285346
286 const base_type = s.typeOfRef(base);347 const ty = s.typeOfRef(base);
287 switch (base_type) {348
288 .@"opaque" => {349 if (ty == .@"opaque") {
289 return s.addInstRef(.field, .{350 return s.addInstRef(.field_opaque, .{
290 .base = base,351 .base = base,
291 .field = try string.tryToNullTerminatedString(s.ip, s.gpa),352 .field = string.tryToNullTerminatedString(s.ip, s.gpa) catch |e| switch (e) {
353 error.HasNulls => @panic("TODO: handle null"),
354 else => return e,
355 },
356 });
357 }
358
359 if (ty.toStructType(s.ip)) |struct_type| {
360 const found_index: u32 = found_index: {
361 const lookup = string.tryToNullTerminatedString(
362 s.ip,
363 s.gpa,
364 ) catch |e| switch (e) {
365 error.HasNulls => break :found_index null,
366 else => return e,
367 };
368
369 break :found_index for (struct_type.field_keys, 0..) |key, i| {
370 if (key == lookup)
371 break @as(u32, @intCast(i));
372 } else null;
373 } orelse {
374 return s.fail(.field_lookup_failed, .{
375 .ty = ty,
376 .src = in.op(.rhs).lazyNode(s.decl_index),
377 .field = rhs,
292 });378 });
293 },379 };
294 else => |tag| std.debug.panic("TODO: field analyze with base .{s}", .{@tagName(tag)}),380
381 switch (base.decode()) {
382 .value => |value| {
383 _, const trail = value.valueData(s.ip, .struct_value).getTrail(s.ip);
384 return trail.at(&s.ip.extra, Value, found_index).toAir();
385 },
386 .index => |index| {
387 return s.addInstRef(.field_struct, .{
388 .base = index,
389 .field_index = found_index,
390 });
391 },
392 }
295 }393 }
394
395 return s.fail(.field_invalid_type, .{
396 .ty = ty,
397 .src = in.op(.rhs).lazyNode(s.decl_index),
398 });
296 },399 },
297 .load_decl => {400 .load_decl => {
298 const request: Teg.Inst.StrNode = s.code.instData(.load_decl, in);401 const request: Teg.Inst.StrNode = s.code.instData(.load_decl, in);
...@@ -326,8 +429,8 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {...@@ -326,8 +429,8 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {
326 const bin: Teg.Inst.Bin = s.code.instPayload(tag, in);429 const bin: Teg.Inst.Bin = s.code.instPayload(tag, in);
327 const op = @field(BinaryOp, @tagName(tag));430 const op = @field(BinaryOp, @tagName(tag));
328431
329 const lhs = try s.resolveRefRuntime(bin.lhs);432 const lhs = try s.resolveRef(bin.lhs);
330 const rhs = try s.resolveRefRuntime(bin.rhs);433 const rhs = try s.resolveRef(bin.rhs);
331434
332 const left_ty = s.typeOfRef(lhs);435 const left_ty = s.typeOfRef(lhs);
333 const right_ty = s.typeOfRef(rhs);436 const right_ty = s.typeOfRef(rhs);
...@@ -350,23 +453,47 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {...@@ -350,23 +453,47 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {
350 .rhs = right,453 .rhs = right,
351 });454 });
352 },455 },
353 inline .negate, .bool_not => |tag| {456 .bool_not => {
354 const un: Teg.Inst.RefNode = s.code.instData(tag, in);457 const un: Teg.Inst.RefNode = s.code.instData(.bool_not, in);
355458
356 const operand = try s.resolveRefRuntime(un.operand);459 const operand = try s.resolveCoerceRef(in.op(.operand), un.operand, .bool);
357460
358 const operand_type = s.typeOfRef(operand);461 // Constant propagation
359 _ = operand_type; // autofix462 if (operand.value()) |value| {
463 assert(value == .bool_false or value == .bool_true);
464 return if (value == .bool_true)
465 Value.bool_false.toAir()
466 else
467 Value.bool_true.toAir();
468 }
360469
361 // TODO: validate type for operation470 return s.addInstRef(.bool_not, .{ .operand = operand });
471 },
472 .negate => {
473 const un: Teg.Inst.RefNode = s.code.instData(.negate, in);
362474
363 return s.addInstRef(tag.airTag(), .{475 const operand = try s.resolveRef(un.operand);
364 .operand = operand,476
365 });477 const ty = s.typeOfRef(operand);
478 switch (ty) {
479 .numeric_literal, .int32, .float => {},
480 else => return s.fail(.un_op_mismatch, .{
481 .src = in.op(.operand).lazyNode(s.decl_index),
482 .ty = ty,
483 }),
484 }
485
486 if (operand.value()) |value| {
487 return s.makeValueAir(.{
488 .comptime_float = -value.toFloat(s.ip).?,
489 });
490 }
491
492 return s.addInstRef(.negate, .{ .operand = operand });
366 },493 },
367 .@"break" => {494 .@"break" => {
368 const brk: Teg.Inst.Break = s.code.instPayload(.@"break", in);495 const brk: Teg.Inst.Break = s.code.instPayload(.@"break", in);
369 const operand = try s.resolveRefRuntime(brk.operand);496 const operand = try s.resolveRef(brk.operand);
370 const block_ref = s.inst_map.get(brk.block).?.decode().index; // block ref is resolved by teg497 const block_ref = s.inst_map.get(brk.block).?.decode().index; // block ref is resolved by teg
371498
372 const coerced = if (s.temp_result_map.get(brk.block)) |ty|499 const coerced = if (s.temp_result_map.get(brk.block)) |ty|
...@@ -381,7 +508,7 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {...@@ -381,7 +508,7 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {
381 },508 },
382 .call => {509 .call => {
383 const call: Teg.Inst.Call = s.code.instPayload(.call, in);510 const call: Teg.Inst.Call = s.code.instPayload(.call, in);
384 const base = try s.resolveRefRuntime(call.base);511 const base = try s.resolveRef(call.base);
385 const base_type = s.typeOfRef(base);512 const base_type = s.typeOfRef(base);
386513
387 const scratch = try s.arena.alloc(Air.Inst.Ref, call.args.len);514 const scratch = try s.arena.alloc(Air.Inst.Ref, call.args.len);
...@@ -405,7 +532,7 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {...@@ -405,7 +532,7 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {
405532
406 // validate function args533 // validate function args
407 for (proto.param_types, call.args.slice(&s.code), 0..) |param, arg, i| {534 for (proto.param_types, call.args.slice(&s.code), 0..) |param, arg, i| {
408 const ref = try s.resolveRefRuntime(arg);535 const ref = try s.resolveRef(arg);
409 const coerced = try s.coerceRef(536 const coerced = try s.coerceRef(
410 in.op(Report.Operand.argument(@intCast(i))),537 in.op(Report.Operand.argument(@intCast(i))),
411 ref,538 ref,
...@@ -417,7 +544,7 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {...@@ -417,7 +544,7 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {
417 for (call.args.slice(&s.code), 0..) |arg, i| {544 for (call.args.slice(&s.code), 0..) |arg, i| {
418 const ref = try s.ensureValueRuntime(545 const ref = try s.ensureValueRuntime(
419 in.op(Report.Operand.argument(@intCast(i))),546 in.op(Report.Operand.argument(@intCast(i))),
420 try s.resolveRefRuntime(arg),547 try s.resolveRef(arg),
421 );548 );
422 scratch[i] = ref;549 scratch[i] = ref;
423 }550 }
...@@ -438,14 +565,14 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {...@@ -438,14 +565,14 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {
438 },565 },
439 .js_new => {566 .js_new => {
440 const call: Teg.Inst.JsNew = s.code.instPayload(.js_new, in);567 const call: Teg.Inst.JsNew = s.code.instPayload(.js_new, in);
441 const base = try s.resolveRefRuntime(call.base);568 const base = try s.resolveRef(call.base);
442569
443 const scratch = try s.arena.alloc(Air.Inst.Ref, call.args.len);570 const scratch = try s.arena.alloc(Air.Inst.Ref, call.args.len);
444 defer s.arena.free(scratch);571 defer s.arena.free(scratch);
445 for (call.args.slice(&s.code), 0..) |arg, i| {572 for (call.args.slice(&s.code), 0..) |arg, i| {
446 const ref = try s.ensureValueRuntime(573 const ref = try s.ensureValueRuntime(
447 in.op(Report.Operand.argument(@intCast(i))),574 in.op(Report.Operand.argument(@intCast(i))),
448 try s.resolveRefRuntime(arg),575 try s.resolveRef(arg),
449 );576 );
450 scratch[i] = ref;577 scratch[i] = ref;
451 }578 }
...@@ -462,7 +589,7 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {...@@ -462,7 +589,7 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {
462 });589 });
463 },590 },
464 .ensure_void => {591 .ensure_void => {
465 const operand = try s.resolveRefRuntimeExtra(s.code.instData(.ensure_void, in).operand, false);592 const operand = try s.resolveRefExtra(s.code.instData(.ensure_void, in).operand, false);
466 const t = s.typeOfRef(operand);593 const t = s.typeOfRef(operand);
467 _ = t;594 _ = t;
468 // if (!t.isEnsureVoid(s.ip)) {595 // if (!t.isEnsureVoid(s.ip)) {
...@@ -473,7 +600,7 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {...@@ -473,7 +600,7 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {
473 return InternPool.Value.void.toAir();600 return InternPool.Value.void.toAir();
474 },601 },
475 .discard => {602 .discard => {
476 const operand = try s.resolveRefRuntimeExtra(s.code.instData(.discard, in).operand, false);603 const operand = try s.resolveRefExtra(s.code.instData(.discard, in).operand, false);
477 const t = s.typeOfRef(operand);604 const t = s.typeOfRef(operand);
478 if (t == .void) {605 if (t == .void) {
479 std.debug.print("oh no, we have a useless _ = void;\n", .{});606 std.debug.print("oh no, we have a useless _ = void;\n", .{});
...@@ -483,7 +610,7 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {...@@ -483,7 +610,7 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {
483 .@"if" => {610 .@"if" => {
484 const data: Teg.Inst.If = s.code.instData(.@"if", in).extra_index.get(&s.code);611 const data: Teg.Inst.If = s.code.instData(.@"if", in).extra_index.get(&s.code);
485612
486 const cond_raw = try s.resolveRefRuntime(data.cond);613 const cond_raw = try s.resolveRef(data.cond);
487 const cond = try s.coerceRef(in.op(.operand), cond_raw, .bool);614 const cond = try s.coerceRef(in.op(.operand), cond_raw, .bool);
488615
489 // If known at comptime, only analyze the taken path616 // If known at comptime, only analyze the taken path
...@@ -517,7 +644,7 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {...@@ -517,7 +644,7 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {
517 .memory => {644 .memory => {
518 const data = s.code.instData(.memory, in);645 const data = s.code.instData(.memory, in);
519646
520 const initial_value = try s.resolveRefRuntime(data.operand);647 const initial_value = try s.resolveRef(data.operand);
521648
522 const runtime_known = try s.ensureValueRuntime(649 const runtime_known = try s.ensureValueRuntime(
523 in.op(.operand),650 in.op(.operand),
...@@ -535,7 +662,7 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {...@@ -535,7 +662,7 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {
535662
536 const location = s.mapTeg(data.memory.toRef()).decode().index;663 const location = s.mapTeg(data.memory.toRef()).decode().index;
537 s.markInst(location);664 s.markInst(location);
538 const operand = try s.resolveRefRuntime(data.operand);665 const operand = try s.resolveRef(data.operand);
539 const operand_coerced = try s.coerceRef(666 const operand_coerced = try s.coerceRef(
540 in.op(.rhs),667 in.op(.rhs),
541 operand,668 operand,
...@@ -548,23 +675,23 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {...@@ -548,23 +675,23 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {
548 },675 },
549 .as => {676 .as => {
550 const as: Teg.Inst.As = s.code.instPayload(.as, in);677 const as: Teg.Inst.As = s.code.instPayload(.as, in);
551 const coerce_to = try s.resolveRefComptimeType(in.op(.lhs), as.type, .type);678 const coerce_to = try s.resolveCoerceValue(in.op(.lhs), as.type, .type);
552 const operand = try s.resolveRefRuntime(as.operand);679 const operand = try s.resolveRef(as.operand);
553 return try s.coerceRef(in.op(.rhs), operand, coerce_to.toType(s.ip).?);680 return try s.coerceRef(in.op(.rhs), operand, coerce_to.toType(s.ip).?);
554 },681 },
555 .@"comptime" => {682 .@"comptime" => {
556 const node: Teg.Inst.RefNode = s.code.instData(.@"comptime", in);683 const node: Teg.Inst.RefNode = s.code.instData(.@"comptime", in);
557 const value = try s.resolveRefComptimeAny(node.operand);684 const value = try s.resolveValue(node.operand);
558 return value.toAir();685 return value.toAir();
559 },686 },
560 .type_of => {687 .type_of => {
561 const node: Teg.Inst.RefNode = s.code.instData(.type_of, in);688 const node: Teg.Inst.RefNode = s.code.instData(.type_of, in);
562 const value = try s.resolveRefComptimeAny(node.operand);689 const value = try s.resolveValue(node.operand);
563 return value.typeOf(s.ip).value().toAir();690 return value.typeOf(s.ip).value().toAir();
564 },691 },
565 .compile_error => {692 .compile_error => {
566 const node: Teg.Inst.RefNode = s.code.instData(.compile_error, in);693 const node: Teg.Inst.RefNode = s.code.instData(.compile_error, in);
567 const value = try s.resolveRefComptimeType(694 const value = try s.resolveCoerceValue(
568 in.op(Report.Operand.argument(0)),695 in.op(Report.Operand.argument(0)),
569 node.operand,696 node.operand,
570 .string,697 .string,
...@@ -575,6 +702,162 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {...@@ -575,6 +702,162 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {
575 .msg = try s.report.addString(str),702 .msg = try s.report.addString(str),
576 });703 });
577 },704 },
705 .struct_decl => {
706 const node: Teg.Inst.StructDecl = s.code.instPayload(.struct_decl, in);
707
708 const scratch_top = s.scratch.items.len;
709 try s.scratch.ensureUnusedCapacity(s.arena, node.field_len * 2);
710 s.scratch.items.len += node.field_len * 2;
711 defer s.scratch.shrinkRetainingCapacity(scratch_top);
712
713 for (
714 node.keys(&s.code),
715 // Keeping the slice pointer is fine for this loop, since scratch is not resized
716 s.scratch.items[scratch_top..][0..node.field_len],
717 ) |key, *dest| {
718 dest.* = @intFromEnum(try s.ip.getOrPutString(
719 s.gpa,
720 key.slice(s.code.string_bytes),
721 .no_embedded_nulls,
722 ));
723 }
724
725 for (
726 node.types(&s.code),
727 // We cannot reuse the pointer from scratch between calls to sema
728 scratch_top + node.field_len..,
729 0..,
730 ) |ty, i, arg_i| {
731 s.scratch.items[i] = @intFromEnum(try s.resolveCoerceValue(
732 in.op(Report.Operand.argument(@intCast(arg_i))),
733 ty,
734 .type,
735 ));
736 }
737
738 const keys = s.scratch.items[scratch_top..][0..node.field_len];
739 const types = s.scratch.items[scratch_top + node.field_len ..][0..node.field_len];
740
741 return try s.makeValueAir(.{ .struct_type = .{
742 .field_keys = @ptrCast(keys),
743 .field_types = @ptrCast(types),
744 } });
745 },
746 .struct_init => {
747 const node: Teg.Inst.StructInit = s.code.instPayload(.struct_init, in);
748
749 // Ensure we have a valid struct to operate on
750 const ty = try s.resolveType(in.op(.operand), node.base_type);
751 const struct_type = ty.toStructType(s.ip) orelse return s.fail(
752 .struct_init_invalid_type,
753 .{
754 .src = in.op(.operand).lazyNode(s.decl_index),
755 .ty = ty,
756 },
757 );
758
759 var has_errors = false;
760 var is_constant = true;
761
762 // Allocate scratch space to store the values as they are being prepared.
763 // TODO: make this an abstraction
764 const scratch_top = s.scratch.items.len;
765
766 try s.scratch.ensureUnusedCapacity(s.arena, struct_type.field_types.len);
767 const storage: []Air.Inst.Ref.Optional =
768 @ptrCast(s.scratch.unusedCapacitySlice()[0..struct_type.field_types.len]);
769 s.scratch.items.len += storage.len;
770 @memset(storage, .none);
771
772 const scratch_end = s.scratch.items.len;
773 defer {
774 const from = s.scratch.items[scratch_end..];
775 const dest = s.scratch.items[scratch_top..][0..from.len];
776 std.mem.copyForwards(u32, dest, from);
777 s.scratch.shrinkRetainingCapacity(scratch_top + from.len);
778 }
779
780 for (node.keys(&s.code), node.inits(&s.code), 0..) |init_key, init_value, i| {
781 // The order of `storage` is as ordered by the type, but this
782 // can be in a different ordering than the initialization. We
783 // guarantee the ordering the initializers are visited is in the
784 // order they are declared.
785 const found_index = index: for (struct_type.field_keys, 0..) |decl_key, j| {
786 if (decl_key.eqlSlice(init_key.slice(s.code.string_bytes), s.ip))
787 break :index j;
788 } else {
789 has_errors = true;
790 try s.addError(.struct_init_unknown_field, .{
791 .ty = ty,
792 .src = in.op(Report.Operand.argument(@intCast(i))).lazyNode(s.decl_index),
793 });
794 continue;
795 };
796 if (storage[found_index] != .none) {
797 has_errors = true;
798 try s.addError(.struct_init_duplicate_field, .{
799 .ty = ty,
800 .src = in.op(Report.Operand.argument(@intCast(i))).lazyNode(s.decl_index),
801 });
802 continue;
803 }
804 // TODO: calling coerceRef / resolveRefRuntime will clobber the `storage` pointer
805 const coerced = s.resolveCoerceRef(
806 in.op(Report.Operand.argument(@intCast(i))),
807 init_value,
808 struct_type.field_types[found_index],
809 ) catch |e| switch (e) {
810 error.AnalysisFail => {
811 // Instead of bailing out with `try`, we will continue the loop.
812 has_errors = true;
813 // Mark something non-null so that duplicate field errors are emitted.
814 storage[found_index] = comptime Value.void.toAir().toOptional();
815 continue;
816 },
817 else => return e,
818 };
819 if (coerced.kind == .index) is_constant = false;
820 storage[found_index] = coerced.toOptional();
821 }
822
823 // Check for missing fields
824 {
825 var i: usize = 0;
826 while (mem.indexOfScalarPos(Air.Inst.Ref.Optional, storage, i, .none)) |index| {
827 try s.addError(.struct_init_missing_field, .{
828 .ty = ty,
829 .field = @intCast(index),
830 .src = in.op(.main_node).lazyNode(s.decl_index),
831 });
832 i = index + 1;
833 }
834 if (i != 0) has_errors = true;
835 }
836
837 if (has_errors)
838 return Error.AnalysisFail;
839
840 if (is_constant) {
841 // Convert all items in storage into a Value type
842 const result_storage: []Value = @ptrCast(storage);
843 for (storage, result_storage) |ref, *value| {
844 value.* = ref.unwrap().?.decode().value;
845 }
846 return try s.makeValueAir(.{ .struct_value = .{
847 .ty = ty,
848 .field_values = result_storage,
849 } });
850 } else {
851 // TODO: make this less error prone
852 const result_storage: []u32 = @ptrCast(storage);
853 const value_index = try s.air_extra.alloc(s.gpa, result_storage.len);
854 @memcpy(s.air_extra.list.items[value_index..].ptr, result_storage);
855 return try s.addInstRef(.struct_init, .{
856 .ty = ty,
857 .field_values = value_index,
858 });
859 }
860 },
578 else => |tag| std.debug.panic("TODO: analyze of .{s}", .{@tagName(tag)}),861 else => |tag| std.debug.panic("TODO: analyze of .{s}", .{@tagName(tag)}),
579 }862 }
580}863}
...@@ -591,7 +874,7 @@ fn analyzeRefToBlock(s: *Sema, in: Teg.Inst.Ref) Error!Air.Inst.Ref {...@@ -591,7 +874,7 @@ fn analyzeRefToBlock(s: *Sema, in: Teg.Inst.Ref) Error!Air.Inst.Ref {
591 const scratch_top = s.scratch.items.len;874 const scratch_top = s.scratch.items.len;
592 defer s.scratch.shrinkRetainingCapacity(scratch_top);875 defer s.scratch.shrinkRetainingCapacity(scratch_top);
593876
594 const visited = s.resolveRefRuntime(in) catch |err| switch (err) {877 const visited = s.resolveRef(in) catch |err| switch (err) {
595 error.HitNoReturn => Value.void.toAir(),878 error.HitNoReturn => Value.void.toAir(),
596 else => return err,879 else => return err,
597 };880 };
...@@ -614,7 +897,7 @@ fn analyzeRefToBlock(s: *Sema, in: Teg.Inst.Ref) Error!Air.Inst.Ref {...@@ -614,7 +897,7 @@ fn analyzeRefToBlock(s: *Sema, in: Teg.Inst.Ref) Error!Air.Inst.Ref {
614 return (try block.finish(s, scratch_top)).toRef();897 return (try block.finish(s, scratch_top)).toRef();
615}898}
616899
617fn resolveRefRuntimeExtra(s: *Sema, ref: Teg.Inst.Ref, mark: bool) Error!Air.Inst.Ref {900fn resolveRefExtra(s: *Sema, ref: Teg.Inst.Ref, mark: bool) Error!Air.Inst.Ref {
618 const index = ref.toIndex() orelse {901 const index = ref.toIndex() orelse {
619 return ref.toValue().?.toAir();902 return ref.toValue().?.toAir();
620 };903 };
...@@ -632,11 +915,11 @@ fn resolveRefRuntimeExtra(s: *Sema, ref: Teg.Inst.Ref, mark: bool) Error!Air.Ins...@@ -632,11 +915,11 @@ fn resolveRefRuntimeExtra(s: *Sema, ref: Teg.Inst.Ref, mark: bool) Error!Air.Ins
632 return s.analyzeCode(index);915 return s.analyzeCode(index);
633}916}
634917
635fn resolveRefRuntime(s: *Sema, ref: Teg.Inst.Ref) Error!Air.Inst.Ref {918fn resolveRef(s: *Sema, ref: Teg.Inst.Ref) Error!Air.Inst.Ref {
636 return resolveRefRuntimeExtra(s, ref, true);919 return resolveRefExtra(s, ref, true);
637}920}
638921
639fn resolveRefComptimeAny(s: *Sema, ref: Teg.Inst.Ref) !Value {922fn resolveValue(s: *Sema, ref: Teg.Inst.Ref) !Value {
640 const index = switch (ref.decode()) {923 const index = switch (ref.decode()) {
641 .value => |value| return value,924 .value => |value| return value,
642 .index => |index| index,925 .index => |index| index,
...@@ -655,21 +938,69 @@ fn resolveRefComptimeAny(s: *Sema, ref: Teg.Inst.Ref) !Value {...@@ -655,21 +938,69 @@ fn resolveRefComptimeAny(s: *Sema, ref: Teg.Inst.Ref) !Value {
655 return s.makeValue(.{ .string = string.slice(s.code.string_bytes) });938 return s.makeValue(.{ .string = string.slice(s.code.string_bytes) });
656 },939 },
657940
658 else => |tag| std.debug.panic("TODO: comptime analyze of .{s}", .{@tagName(tag)}),941 .block => {
942 // Currently only one pattern is supported:
943 //
944 // %2 = block [
945 // %1 = load_decl X
946 // %3 = break(%3, %2)
947 // ]
948 //
949 // This is to support function arguments with structs.
950 // The rest of this will be implemented in https://github.com/paperdave/wl/issues/44
951 special_cased: {
952 const index_list: Teg.IndexList = s.code.instPayload(.block, index);
953 if (index_list.len != 2) break :special_cased;
954 const indexes = index_list.slice(&s.code);
955 const load_inst = indexes[0];
956 const break_inst = indexes[1];
957
958 if (s.code.instTag(break_inst) != .@"break") break :special_cased;
959 const break_data: Teg.Inst.Break = s.code.instPayload(.@"break", break_inst);
960 if (break_data.operand != load_inst.toRef()) break :special_cased;
961 if (break_data.block != index) break :special_cased;
962
963 if (s.code.instTag(load_inst) != .load_decl) break :special_cased;
964
965 const request: Teg.Inst.StrNode = s.code.instData(.load_decl, load_inst);
966 const decl_name = try s.ip.getOrPutString(
967 s.gpa,
968 request.string.slice(s.code.string_bytes),
969 .no_embedded_nulls,
970 );
971 const decl_index = s.lookupDecl(decl_name) orelse {
972 std.debug.print("did not find {s}\n", .{decl_name.toSlice(s.ip)});
973 return error.AnalysisFail;
974 };
975 return try s.resolveDecl(load_inst.op(.main_node), decl_index);
976 }
977
978 std.debug.panic("TODO: advanced comptime interpreter", .{});
979 },
980
981 else => |tag| std.debug.panic("TODO: comptime analyze of .{s} {d}", .{ @tagName(tag), @intFromEnum(index) }),
659 }982 }
660}983}
661984
662fn resolveRefComptimeType(s: *Sema, src: Teg.InstOp, ref: Teg.Inst.Ref, ty: Type) !Value {985fn resolveCoerceValue(s: *Sema, src: Teg.InstOp, ref: Teg.Inst.Ref, ty: Type) !Value {
663 const any = try s.resolveRefComptimeAny(ref);986 const any = try s.resolveValue(ref);
664 return try s.coerceValue(src, any, ty);987 return try s.coerceValue(src, any, ty);
665}988}
666989
990fn resolveCoerceRef(s: *Sema, src: Teg.InstOp, ref: Teg.Inst.Ref, ty: Type) !Air.Inst.Ref {
991 const any = try s.resolveRef(ref);
992 return try s.coerceRef(src, any, ty);
993}
994
667fn resolveType(s: *Sema, src: Teg.InstOp, ref: Teg.Inst.Ref) !Type {995fn resolveType(s: *Sema, src: Teg.InstOp, ref: Teg.Inst.Ref) !Type {
668 return (try s.resolveRefComptimeType(src, ref, .type)).toTypeUnsafe(s.ip);996 return (try s.resolveCoerceValue(src, ref, .type)).toTypeUnsafe(s.ip);
669}997}
670998
671/// Coerce `value` runtime ref to `coerce_type`, and emit an error at `teg_ref`'s location999/// Coerce `value` runtime ref to `coerce_type`, and emit an error at `src`'s location
672fn coerceRef(s: *Sema, src: InstOp, value: Air.Inst.Ref, to: Type) !Air.Inst.Ref {1000fn coerceRef(s: *Sema, src: InstOp, value: Air.Inst.Ref, to: Type) !Air.Inst.Ref {
1001 const t = tracer.traceFmt(@src(), "{} to {}", .{ value.dump(s.ip), to.dump(s.ip) });
1002 defer t.end();
1003
673 const from = s.typeOfRef(value);1004 const from = s.typeOfRef(value);
674 if (from == to) {1005 if (from == to) {
675 return value;1006 return value;
...@@ -721,6 +1052,7 @@ fn coerceRef(s: *Sema, src: InstOp, value: Air.Inst.Ref, to: Type) !Air.Inst.Ref...@@ -721,6 +1052,7 @@ fn coerceRef(s: *Sema, src: InstOp, value: Air.Inst.Ref, to: Type) !Air.Inst.Ref
721 return s.coercionError(src, from, to);1052 return s.coercionError(src, from, to);
722}1053}
7231054
1055/// Same as `coerceRef` but on a compile-time known value
724fn coerceValue(s: *Sema, src: InstOp, value: Value, to: Type) !Value {1056fn coerceValue(s: *Sema, src: InstOp, value: Value, to: Type) !Value {
725 const from = value.typeOf(s.ip);1057 const from = value.typeOf(s.ip);
726 if (from == to) {1058 if (from == to) {
...@@ -728,11 +1060,10 @@ fn coerceValue(s: *Sema, src: InstOp, value: Value, to: Type) !Value {...@@ -728,11 +1060,10 @@ fn coerceValue(s: *Sema, src: InstOp, value: Value, to: Type) !Value {
728 }1060 }
729 switch (to) {1061 switch (to) {
730 .@"opaque" => {1062 .@"opaque" => {
7311063 // TODO:
732 //
733 },1064 },
734 .bool => {1065 .bool => {
735 //1066 // TODO:
736 },1067 },
737 .int32 => switch (from) {1068 .int32 => switch (from) {
738 .int32 => unreachable, // same type handled1069 .int32 => unreachable, // same type handled
...@@ -818,8 +1149,10 @@ fn ensureValueRuntime(s: *Sema, src: InstOp, ref: Air.Inst.Ref) !Air.Inst.Ref {...@@ -818,8 +1149,10 @@ fn ensureValueRuntime(s: *Sema, src: InstOp, ref: Air.Inst.Ref) !Air.Inst.Ref {
818 => {},1149 => {},
819 _ => switch (ty.tag(s.ip)) {1150 _ => switch (ty.tag(s.ip)) {
820 .simple_type => unreachable, // handled above1151 .simple_type => unreachable, // handled above
1152
821 // Allowed at runtime1153 // Allowed at runtime
822 .type_function => {},1154 .function_type => {},
1155 .struct_type => {},
823 },1156 },
824 }1157 }
825 },1158 },
...@@ -867,7 +1200,7 @@ fn resolveDecl(s: *Sema, src: InstOp, decl_index: Decl.Index) Error!Value {...@@ -867,7 +1200,7 @@ fn resolveDecl(s: *Sema, src: InstOp, decl_index: Decl.Index) Error!Value {
867 });1200 });
868 },1201 },
869 else => {1202 else => {
870 return decl.val.?;1203 return decl.value();
871 },1204 },
872 }1205 }
873}1206}
...@@ -904,8 +1237,12 @@ const BinaryOp = enum {...@@ -904,8 +1237,12 @@ const BinaryOp = enum {
904};1237};
9051238
906fn binTypeResolution(s: *Sema, src: Teg.Inst.Index, op: BinaryOp, lhs: Type, rhs: Type) !Type {1239fn binTypeResolution(s: *Sema, src: Teg.Inst.Index, op: BinaryOp, lhs: Type, rhs: Type) !Type {
907 if (lhs == .@"opaque") return rhs;1240 if (lhs == .@"opaque") {
908 if (rhs == .@"opaque") return rhs;1241 return if (rhs == .numeric_literal) .float else rhs;
1242 }
1243 if (rhs == .@"opaque") {
1244 return if (lhs == .numeric_literal) .float else lhs;
1245 }
9091246
910 switch (lhs) {1247 switch (lhs) {
911 .string => if (op == .add) switch (rhs) {1248 .string => if (op == .add) switch (rhs) {
...@@ -1016,9 +1353,7 @@ fn comptimeBin(s: *Sema, op: BinaryOp, lhs: Value, rhs: Value) !Value {...@@ -1016,9 +1353,7 @@ fn comptimeBin(s: *Sema, op: BinaryOp, lhs: Value, rhs: Value) !Value {
1016 },1353 },
1017 else => unreachable,1354 else => unreachable,
1018 },1355 },
1019 inline .lt,1356 inline .lt, .gt => |tag| {
1020 .gt,
1021 => |tag| {
1022 const l = lhs.toFloat(s.ip) orelse1357 const l = lhs.toFloat(s.ip) orelse
1023 unreachable; // must be coerced1358 unreachable; // must be coerced
1024 const r = rhs.toFloat(s.ip) orelse1359 const r = rhs.toFloat(s.ip) orelse
...@@ -1034,8 +1369,7 @@ fn comptimeBin(s: *Sema, op: BinaryOp, lhs: Value, rhs: Value) !Value {...@@ -1034,8 +1369,7 @@ fn comptimeBin(s: *Sema, op: BinaryOp, lhs: Value, rhs: Value) !Value {
1034 if (result) .bool_true else .bool_false,1369 if (result) .bool_true else .bool_false,
1035 );1370 );
1036 },1371 },
1037 .eq,1372 .eq => {
1038 => {
1039 const result = lhs == rhs;1373 const result = lhs == rhs;
1040 return @as(1374 return @as(
1041 InternPool.Value,1375 InternPool.Value,
...@@ -1045,7 +1379,6 @@ fn comptimeBin(s: *Sema, op: BinaryOp, lhs: Value, rhs: Value) !Value {...@@ -1045,7 +1379,6 @@ fn comptimeBin(s: *Sema, op: BinaryOp, lhs: Value, rhs: Value) !Value {
1045 }1379 }
1046}1380}
10471381
1048/// --- helper ---
1049const IncompleteBlock = struct {1382const IncompleteBlock = struct {
1050 block_index: Air.Inst.Index,1383 block_index: Air.Inst.Index,
10511384
...@@ -1177,30 +1510,26 @@ const math = std.math;...@@ -1177,30 +1510,26 @@ const math = std.math;
1177const Allocator = std.mem.Allocator;1510const Allocator = std.mem.Allocator;
1178const assert = std.debug.assert;1511const assert = std.debug.assert;
1179const log = std.log.scoped(.sema);1512const log = std.log.scoped(.sema);
1513const DynamicBitSetUnmanaged = std.DynamicBitSetUnmanaged;
11801514
1181inline fn assertWithMessage(cond: bool) bool {1515const core = @import("mod.zig");
1182 if (std.debug.runtime_safety) {1516const assertFailed = core.assertFailed;
1183 return !cond;
1184 } else if (!cond) {
1185 unreachable; // assertion failure
1186 }
1187}
11881517
1189const Comp = @import("Comp.zig");1518const Comp = core.Comp;
1190const File = Comp.File;1519const File = Comp.File;
1191const Decl = Comp.Decl;1520const Decl = Comp.Decl;
1192const Namespace = Comp.Namespace;1521const Namespace = Comp.Namespace;
11931522
1194const Teg = @import("Teg.zig");1523const Teg = core.Teg;
1195const InstOp = Teg.InstOp;1524const InstOp = Teg.InstOp;
11961525
1197const Air = @import("Air.zig");1526const Air = core.Air;
1198const Report = @import("Report.zig");1527const Report = core.Report;
1199const Ast = @import("Ast.zig");1528const Ast = core.Ast;
1200const dod = @import("array.zig");1529const store = core.array;
1201const tracer = @import("tracer");1530const tracer = @import("tracer");
12021531
1203const InternPool = @import("InternPool.zig");1532const InternPool = core.InternPool;
1204const Value = InternPool.Value;1533const Value = InternPool.Value;
1205const Type = InternPool.Type;1534const Type = InternPool.Type;
1206const NullTerminatedString = InternPool.NullTerminatedString;1535const NullTerminatedString = InternPool.NullTerminatedString;
src/core/Teg.Dump.zig+40-8
...@@ -46,10 +46,10 @@ const State = struct {...@@ -46,10 +46,10 @@ const State = struct {
46 try s.dumpInstList(writer, teg.moduleDeclarations(inst), '{', '}');46 try s.dumpInstList(writer, teg.moduleDeclarations(inst), '{', '}');
47 },47 },
48 .declaration => {48 .declaration => {
49 const decl_info = teg.declData(inst);49 const decl = teg.fullDecl(inst);
50 try writer.print(" ({s}) ", .{@tagName(decl_info.kind)});50 try writer.print(" ({s}) ", .{@tagName(decl.data)});
5151
52 const name = decl_info.name.slice(teg.string_bytes);52 const name = decl.name.slice(teg.string_bytes);
53 if (name.len == 0) {53 if (name.len == 0) {
54 try tty.setColor(writer, .bold);54 try tty.setColor(writer, .bold);
55 try writer.writeAll("<top level code>");55 try writer.writeAll("<top level code>");
...@@ -63,19 +63,19 @@ const State = struct {...@@ -63,19 +63,19 @@ const State = struct {
63 s.indent += 1;63 s.indent += 1;
64 try s.newline(writer);64 try s.newline(writer);
6565
66 switch (decl_info.kind) {66 switch (decl.data) {
67 .function => {67 .function => |fn_data| {
68 const fn_data: Teg.Inst.DeclFn = teg.fullFuncDecl(inst).@"1";
69 try writer.writeAll("params: ");68 try writer.writeAll("params: ");
70 try s.dumpRefList(writer, fn_data.params.slice(teg), '[', ']');69 try s.dumpRefList(writer, fn_data.params.slice(teg), '[', ']');
71 try s.newline(writer);70 try s.newline(writer);
72 try writer.writeAll("return_type: ");71 try writer.writeAll("return_type: ");
73 try s.dumpRef(writer, fn_data.return_type);72 try s.dumpRef(writer, fn_data.return_type);
74 try s.newline(writer);73 try s.newline(writer);
75 try writer.writeAll("body: ");
76 try s.dumpRef(writer, fn_data.body);
77 },74 },
75 .immutable => {},
78 }76 }
77 try writer.writeAll("body: ");
78 try s.dumpRef(writer, decl.body);
79 s.indent -= 1;79 s.indent -= 1;
80 try s.newline(writer);80 try s.newline(writer);
81 try writer.writeAll("}");81 try writer.writeAll("}");
...@@ -223,6 +223,38 @@ const State = struct {...@@ -223,6 +223,38 @@ const State = struct {
223 try s.dumpRef(writer, data.operand);223 try s.dumpRef(writer, data.operand);
224 try writer.writeAll(")");224 try writer.writeAll(")");
225 },225 },
226 .struct_decl => {
227 const data: Teg.Inst.StructDecl = teg.instPayload(.struct_decl, inst);
228 try writer.writeAll(" {");
229 s.indent += 1;
230 try s.newline(writer);
231 for (data.keys(teg), data.types(teg), 0..) |key, value, i| {
232 if (i != 0)
233 try s.newline(writer);
234 try writer.print("{s}: ", .{key.slice(teg.string_bytes)});
235 try s.dumpRefMarker(writer, value);
236 }
237 s.indent -= 1;
238 try s.newline(writer);
239 try writer.writeAll("}");
240 },
241 .struct_init => {
242 const data: Teg.Inst.StructInit = teg.instPayload(.struct_init, inst);
243 try writer.writeAll("(");
244 try s.dumpRefMarker(writer, data.base_type);
245 try writer.writeAll(") {");
246 s.indent += 1;
247 try s.newline(writer);
248 for (data.keys(teg), data.inits(teg), 0..) |key, value, i| {
249 if (i != 0)
250 try s.newline(writer);
251 try writer.print("{s}: ", .{key.slice(teg.string_bytes)});
252 try s.dumpRefMarker(writer, value);
253 }
254 s.indent -= 1;
255 try s.newline(writer);
256 try writer.writeAll("}");
257 },
226 }258 }
227 }259 }
228260
src/core/Teg.zig+94-4
...@@ -16,7 +16,7 @@ pub const Inst = struct {...@@ -16,7 +16,7 @@ pub const Inst = struct {
16 tag: Tag,16 tag: Tag,
17 data: EncodedData,17 data: EncodedData,
1818
19 // todo: fully document every thing19 // TODO: fully document every thing
20 pub const Tag = enum {20 pub const Tag = enum {
21 module,21 module,
22 declaration,22 declaration,
...@@ -43,6 +43,9 @@ pub const Inst = struct {...@@ -43,6 +43,9 @@ pub const Inst = struct {
4343
44 @"if",44 @"if",
4545
46 struct_decl,
47 struct_init,
48
46 add,49 add,
47 sub,50 sub,
48 mul,51 mul,
...@@ -98,6 +101,8 @@ pub const Inst = struct {...@@ -98,6 +101,8 @@ pub const Inst = struct {
98 .param => DataNode,101 .param => DataNode,
99 .store => PlNode(Store),102 .store => PlNode(Store),
100 .js_new => PlNode(JsNew),103 .js_new => PlNode(JsNew),
104 .struct_decl => PlNode(StructDecl),
105 .struct_init => PlNode(StructInit),
101106
102 // Single instruction operand107 // Single instruction operand
103 .memory,108 .memory,
...@@ -271,7 +276,7 @@ pub const Inst = struct {...@@ -271,7 +276,7 @@ pub const Inst = struct {
271 pub fn toValue(inst: Ref) ?InternPool.Value {276 pub fn toValue(inst: Ref) ?InternPool.Value {
272 const ref_int = @intFromEnum(inst);277 const ref_int = @intFromEnum(inst);
273278
274 // todo: is this > or >=279 // TODO: is this > or >=
275 if (ref_int > Index.static_len)280 if (ref_int > Index.static_len)
276 return null;281 return null;
277282
...@@ -315,16 +320,57 @@ pub const Inst = struct {...@@ -315,16 +320,57 @@ pub const Inst = struct {
315 args: RefList,320 args: RefList,
316 };321 };
317322
323 /// Index into extra which contains `field_len * 2` u32s. The first half
324 /// are the keys (NullTerminatedString) and the second half are field types
325 /// (Ref). This basically is like a specialized MultiArrayList.
326 pub const StructDecl = struct {
327 payload_index: u32,
328 field_len: u32,
329
330 pub fn keys(decl: StructDecl, teg: *const Teg) []const NullTerminatedString {
331 return @ptrCast(
332 teg.extra.list.items[decl.payload_index..][0..decl.field_len],
333 );
334 }
335
336 pub fn types(decl: StructDecl, teg: *const Teg) []const Ref {
337 return @ptrCast(
338 teg.extra.list.items[decl.payload_index + decl.field_len ..][0..decl.field_len],
339 );
340 }
341 };
342
343 /// This memory is laid out with the same strategy described in StructDecl
344 pub const StructInit = struct {
345 base_type: Ref,
346 payload_index: u32,
347 field_len: u32,
348
349 pub fn keys(decl: StructInit, teg: *const Teg) []const NullTerminatedString {
350 return @ptrCast(
351 teg.extra.list.items[decl.payload_index..][0..decl.field_len],
352 );
353 }
354
355 pub fn inits(decl: StructInit, teg: *const Teg) []const Ref {
356 return @ptrCast(
357 teg.extra.list.items[decl.payload_index + decl.field_len ..][0..decl.field_len],
358 );
359 }
360 };
361
318 /// This will contain trailing data depending on the .kind362 /// This will contain trailing data depending on the .kind
319 pub const Decl = struct {363 pub const Decl = struct {
320 name: NullTerminatedString,364 name: NullTerminatedString,
321 hash: Cache.BinDigest,365 hash: Cache.BinDigest,
322 kind: Kind,366 kind: Kind,
323 instruction_count: u32,367 instruction_count: u32,
368 body: Ref,
324369
325 pub const Kind = enum {370 pub const Kind = enum {
371 // TODO: remove function, see #45
326 function,372 function,
327 // future: struct, let, var, etc373 immutable,
328 };374 };
329 };375 };
330376
...@@ -332,7 +378,6 @@ pub const Inst = struct {...@@ -332,7 +378,6 @@ pub const Inst = struct {
332 pub const DeclFn = struct {378 pub const DeclFn = struct {
333 params: RefList,379 params: RefList,
334 return_type: Ref,380 return_type: Ref,
335 body: Ref,
336 };381 };
337};382};
338383
...@@ -371,6 +416,8 @@ pub const SlicedString = struct {...@@ -371,6 +416,8 @@ pub const SlicedString = struct {
371 }416 }
372};417};
373418
419// TODO: Make the next three structs using a generic comptime thing
420
374pub const RefList = struct {421pub const RefList = struct {
375 start: u32,422 start: u32,
376 len: u32,423 len: u32,
...@@ -389,6 +436,15 @@ pub const IndexList = struct {...@@ -389,6 +436,15 @@ pub const IndexList = struct {
389 }436 }
390};437};
391438
439pub const NullTermStringList = struct {
440 start: u32,
441 len: u32,
442
443 pub fn slice(self: IndexList, teg: *const Teg) []const NullTerminatedString {
444 return @ptrCast(teg.extra.list.items[self.start..][0..self.len]);
445 }
446};
447
392pub fn deinit(teg: *Teg, gpa: std.mem.Allocator) void {448pub fn deinit(teg: *Teg, gpa: std.mem.Allocator) void {
393 gpa.free(teg.string_bytes);449 gpa.free(teg.string_bytes);
394 teg.extra.deinit(gpa);450 teg.extra.deinit(gpa);
...@@ -426,10 +482,44 @@ pub fn blockInstructions(teg: *const Teg, index: Inst.Index) []const Inst.Index...@@ -426,10 +482,44 @@ pub fn blockInstructions(teg: *const Teg, index: Inst.Index) []const Inst.Index
426482
427pub fn fullFuncDecl(teg: *const Teg, index: Inst.Index) struct { Inst.Decl, Inst.DeclFn } {483pub fn fullFuncDecl(teg: *const Teg, index: Inst.Index) struct { Inst.Decl, Inst.DeclFn } {
428 const decl, var trail = teg.instData(.declaration, index).extra_index.getTrail(teg);484 const decl, var trail = teg.instData(.declaration, index).extra_index.getTrail(teg);
485 assert(decl.kind == .function);
429 const decl_fn = trail.read(&teg.extra, Inst.DeclFn);486 const decl_fn = trail.read(&teg.extra, Inst.DeclFn);
430 return .{ decl, decl_fn };487 return .{ decl, decl_fn };
431}488}
432489
490pub const FullDecl = struct {
491 name: NullTerminatedString,
492 hash: Cache.BinDigest,
493 instruction_count: u32,
494 data: Data,
495 body: Inst.Ref,
496
497 const Data = union(enum) {
498 function: Inst.DeclFn,
499 immutable: struct {},
500 };
501};
502
503pub fn fullDecl(teg: *const Teg, index: Inst.Index) FullDecl {
504 const decl, var trail = teg.instData(.declaration, index).extra_index.getTrail(teg);
505 return .{
506 .name = decl.name,
507 .hash = decl.hash,
508 .instruction_count = decl.instruction_count,
509 .body = decl.body,
510 .data = switch (decl.kind) {
511 inline else => |tag| @unionInit(
512 FullDecl.Data,
513 @tagName(tag),
514 trail.read(&teg.extra, std.meta.FieldType(
515 FullDecl.Data,
516 @field(std.meta.FieldEnum(FullDecl.Data), @tagName(tag)),
517 )),
518 ),
519 },
520 };
521}
522
433pub fn declData(teg: *const Teg, index: Inst.Index) Inst.Decl {523pub fn declData(teg: *const Teg, index: Inst.Index) Inst.Decl {
434 return teg.instData(.declaration, index).extra_index.get(teg);524 return teg.instData(.declaration, index).extra_index.get(teg);
435}525}
src/core/Tokenizer.zig+2-2
...@@ -68,11 +68,10 @@ pub const Token = struct {...@@ -68,11 +68,10 @@ pub const Token = struct {
68 kw_else,68 kw_else,
69 kw_enum,69 kw_enum,
70 kw_fn,70 kw_fn,
71 kw_infer,
72 kw_for,71 kw_for,
73 kw_while,
74 kw_if,72 kw_if,
75 kw_impl,73 kw_impl,
74 kw_infer,
76 kw_json,75 kw_json,
77 kw_let,76 kw_let,
78 kw_match,77 kw_match,
...@@ -83,6 +82,7 @@ pub const Token = struct {...@@ -83,6 +82,7 @@ pub const Token = struct {
83 kw_struct,82 kw_struct,
84 kw_trait,83 kw_trait,
85 kw_var,84 kw_var,
85 kw_while,
86 // sort-lines: end86 // sort-lines: end
8787
88 // sort-lines: start88 // sort-lines: start
src/core/Transform.zig+154-35
...@@ -1,4 +1,13 @@...@@ -1,4 +1,13 @@
1//! Transform pass1//! Transform pass converts an Ast into a high-level/untyped instruction format.
2//! See Teg.zig for more details about the IR. Transforming handles:
3//!
4//! - Resolving all local variables into reference or declaration names.
5//! - Resolving breaks and returns to their block id.
6//! - Annotating expression statements with `.discard` and `.ensure_void`
7//! - Flattening nested expressions into instruction sequences.
8//!
9//! Some trivial diagnostics such as unused variables, unknown result locations, and
10//! invalid assignment targets are also reported here.
2gpa: Allocator,11gpa: Allocator,
3ast: Ast,12ast: Ast,
4insts: Inst.List = .{},13insts: Inst.List = .{},
...@@ -8,11 +17,6 @@ extra: ExtraStore = .{},...@@ -8,11 +17,6 @@ extra: ExtraStore = .{},
8scratch: std.ArrayListUnmanaged(u32) = .{},17scratch: std.ArrayListUnmanaged(u32) = .{},
9id_decode_buf: std.ArrayList(u8),18id_decode_buf: std.ArrayList(u8),
1019
11// TODO: refactor this away
12// The new home for this memory should be pre-allocated on scratch,
13// but then remove the elements, moving scratch items on top of this
14// backwards a few.
15call_args: std.ArrayListUnmanaged(Inst.Ref) = .{},
16scopes: std.ArrayListUnmanaged(Scope) = .{},20scopes: std.ArrayListUnmanaged(Scope) = .{},
1721
18report: Report.Builder,22report: Report.Builder,
...@@ -67,7 +71,6 @@ pub fn transform(...@@ -67,7 +71,6 @@ pub fn transform(
6771
68pub fn deinit(t: *Transform) void {72pub fn deinit(t: *Transform) void {
69 t.scratch.deinit(t.gpa);73 t.scratch.deinit(t.gpa);
70 t.call_args.deinit(t.gpa);
71 assert(t.scopes.items.len == 0);74 assert(t.scopes.items.len == 0);
72 t.scopes.deinit(t.gpa);75 t.scopes.deinit(t.gpa);
73 t.string_bytes.deinit(t.gpa);76 t.string_bytes.deinit(t.gpa);
...@@ -268,7 +271,7 @@ fn transformModule(t: *Transform, module_node: NodeIndex) Allocator.Error!Inst.I...@@ -268,7 +271,7 @@ fn transformModule(t: *Transform, module_node: NodeIndex) Allocator.Error!Inst.I
268 s.symbols.lockPointers();271 s.symbols.lockPointers();
269 defer s.symbols.unlockPointers();272 defer s.symbols.unlockPointers();
270 for (s.symbols.values(), 0..) |value, i| {273 for (s.symbols.values(), 0..) |value, i| {
271 temp[i] = try t.transformFunction(decl, value.node);274 temp[i] = try t.transformDecl(decl, value.node);
272 }275 }
273 }276 }
274277
...@@ -316,12 +319,12 @@ fn transformModule(t: *Transform, module_node: NodeIndex) Allocator.Error!Inst.I...@@ -316,12 +319,12 @@ fn transformModule(t: *Transform, module_node: NodeIndex) Allocator.Error!Inst.I
316 .name = NullTerminatedString.empty,319 .name = NullTerminatedString.empty,
317 .hash = .{0} ** 16,320 .hash = .{0} ** 16,
318 .instruction_count = @as(u32, @intCast(t.insts.len)) - @intFromEnum(top_level_fn),321 .instruction_count = @as(u32, @intCast(t.insts.len)) - @intFromEnum(top_level_fn),
322 .body = top_level_code_ref,
319 }, dod.extraSizeOf(Inst.DeclFn));323 }, dod.extraSizeOf(Inst.DeclFn));
320324
321 trail.writeStruct(Inst.DeclFn, .{325 trail.writeStruct(Inst.DeclFn, .{
322 .params = .{ .start = 0, .len = 0 },326 .params = .{ .start = 0, .len = 0 },
323 .return_type = Inst.Ref.void,327 .return_type = Inst.Ref.void,
324 .body = top_level_code_ref,
325 });328 });
326329
327 t.getInstData(top_level_fn).* = EncodedData.encode(.declaration, .{330 t.getInstData(top_level_fn).* = EncodedData.encode(.declaration, .{
...@@ -346,13 +349,13 @@ fn previsitModuleStatement(t: *Transform, node: NodeIndex) Allocator.Error!void...@@ -346,13 +349,13 @@ fn previsitModuleStatement(t: *Transform, node: NodeIndex) Allocator.Error!void
346 const tag = t.ast.nodes.items(.tag)[node];349 const tag = t.ast.nodes.items(.tag)[node];
347 switch (tag) {350 switch (tag) {
348 .fn_decl => {351 .fn_decl => {
349 const fn_decl = t.ast.fullFnDecl(node);352 const decl = t.ast.fullFnDecl(node);
350 var id, const id_allocated = try t.decodeIdentifierExtra(fn_decl.id);353 var id, const id_allocated = try t.decodeIdentifierExtra(decl.id);
351 if (id_allocated)354 if (id_allocated)
352 id = try t.gpa.dupe(u8, id);355 id = try t.gpa.dupe(u8, id);
353 const gop = try t.scope().symbols.getOrPut(t.gpa, id);356 const gop = try t.scope().symbols.getOrPut(t.gpa, id);
354 if (gop.found_existing) {357 if (gop.found_existing) {
355 t.gpa.free(id);358 if (id_allocated) t.gpa.free(id);
356 try t.report.err(.duplicate_function, .{359 try t.report.err(.duplicate_function, .{
357 .node = node,360 .node = node,
358 .prev = gop.value_ptr.node,361 .prev = gop.value_ptr.node,
...@@ -364,6 +367,22 @@ fn previsitModuleStatement(t: *Transform, node: NodeIndex) Allocator.Error!void...@@ -364,6 +367,22 @@ fn previsitModuleStatement(t: *Transform, node: NodeIndex) Allocator.Error!void
364 .data = .declaration,367 .data = .declaration,
365 };368 };
366 },369 },
370 .struct_decl => {
371 const decl = t.ast.fullStructDecl(node);
372 const gop = try t.scope().symbols.getOrPut(t.gpa, decl.id);
373 if (gop.found_existing) {
374 try t.report.err(.duplicate_function, .{
375 .node = node,
376 .prev = gop.value_ptr.node,
377 // TODO: put type of both declarations
378 });
379 return;
380 }
381 gop.value_ptr.* = .{
382 .node = node,
383 .data = .declaration,
384 };
385 },
367 else => {},386 else => {},
368 }387 }
369}388}
...@@ -378,12 +397,22 @@ fn transformModuleStatement(t: *Transform, decl: BlockContext, node: NodeIndex)...@@ -378,12 +397,22 @@ fn transformModuleStatement(t: *Transform, decl: BlockContext, node: NodeIndex)
378 .param_decl,397 .param_decl,
379 => unreachable,398 => unreachable,
380399
381 .fn_decl => {}, // functions analyzed separately400 .fn_decl,
401 .struct_decl,
402 => {}, // declarations are analyzed separately
382403
383 else => _ = try t.transformBlockStatement(decl, node, false),404 else => _ = try t.transformBlockStatement(decl, node, false),
384 }405 }
385}406}
386407
408fn transformDecl(t: *Transform, outer_decl: BlockContext, node: NodeIndex) Allocator.Error!Inst.Index {
409 return switch (t.ast.nodeTag(node)) {
410 .fn_decl => t.transformFunction(outer_decl, node),
411 .struct_decl => t.transformStruct(outer_decl, node),
412 else => unreachable, // not a declaration
413 };
414}
415
387fn transformFunction(t: *Transform, outer_decl: BlockContext, node: NodeIndex) Allocator.Error!Inst.Index {416fn transformFunction(t: *Transform, outer_decl: BlockContext, node: NodeIndex) Allocator.Error!Inst.Index {
388 const trace = tracer.trace(@src());417 const trace = tracer.trace(@src());
389 defer trace.end();418 defer trace.end();
...@@ -391,7 +420,7 @@ fn transformFunction(t: *Transform, outer_decl: BlockContext, node: NodeIndex) A...@@ -391,7 +420,7 @@ fn transformFunction(t: *Transform, outer_decl: BlockContext, node: NodeIndex) A
391 const decl = outer_decl.declOffset(node);420 const decl = outer_decl.declOffset(node);
392 const fn_decl = t.ast.fullFnDecl(node);421 const fn_decl = t.ast.fullFnDecl(node);
393422
394 const func = try t.addRootInst(.{423 const decl_inst = try t.addRootInst(.{
395 .tag = .declaration,424 .tag = .declaration,
396 .data = undefined,425 .data = undefined,
397 });426 });
...@@ -421,23 +450,53 @@ fn transformFunction(t: *Transform, outer_decl: BlockContext, node: NodeIndex) A...@@ -421,23 +450,53 @@ fn transformFunction(t: *Transform, outer_decl: BlockContext, node: NodeIndex) A
421 .kind = .function,450 .kind = .function,
422 .name = try t.addNullTerminatedString(fn_decl.id),451 .name = try t.addNullTerminatedString(fn_decl.id),
423 .hash = .{0} ** 16,452 .hash = .{0} ** 16,
424 .instruction_count = @as(u32, @intCast(t.insts.len)) - @intFromEnum(func),453 .instruction_count = @as(u32, @intCast(t.insts.len)) - @intFromEnum(decl_inst),
454 .body = body,
425 });455 });
426456
427 _ = try t.extra.append(t.gpa, Inst.DeclFn, .{457 _ = try t.extra.append(t.gpa, Inst.DeclFn, .{
428 .params = param_list,458 .params = param_list,
429 .return_type = return_type,459 .return_type = return_type,
430 .body = body,
431 });460 });
432461
433 t.getInstData(func).* = EncodedData.encode(.declaration, .{462 t.getInstData(decl_inst).* = EncodedData.encode(.declaration, .{
434 .src_node = outer_decl.nodeOffset(node),463 .src_node = outer_decl.nodeOffset(node),
435 .extra_index = decl_extra,464 .extra_index = decl_extra,
436 });465 });
437466
438 try t.scope().addWarnings(t);467 try t.scope().addWarnings(t);
439468
440 return func;469 return decl_inst;
470}
471
472fn transformStruct(t: *Transform, outer_decl: BlockContext, node: NodeIndex) Allocator.Error!Inst.Index {
473 const trace = tracer.trace(@src());
474 defer trace.end();
475
476 const decl = outer_decl.declOffset(node);
477 const decl_data = t.ast.fullStructDecl(node);
478
479 const decl_inst = try t.addRootInst(.{
480 .tag = .declaration,
481 .data = undefined,
482 });
483
484 const body = try t.transformExpressionToBlock(decl, node, false);
485
486 const decl_extra = try t.extra.append(t.gpa, Inst.Decl, .{
487 .kind = .immutable,
488 .name = try t.addNullTerminatedString(decl_data.id),
489 .hash = .{0} ** 16,
490 .instruction_count = @as(u32, @intCast(t.insts.len)) - @intFromEnum(decl_inst),
491 .body = body,
492 });
493
494 t.getInstData(decl_inst).* = EncodedData.encode(.declaration, .{
495 .src_node = outer_decl.nodeOffset(node),
496 .extra_index = decl_extra,
497 });
498
499 return decl_inst;
441}500}
442501
443const TransformFunctionCtx = struct {502const TransformFunctionCtx = struct {
...@@ -555,7 +614,7 @@ fn transformParamDecl(t: *Transform, decl: BlockContext, param: NodeIndex) Alloc...@@ -555,7 +614,7 @@ fn transformParamDecl(t: *Transform, decl: BlockContext, param: NodeIndex) Alloc
555 defer trace.end();614 defer trace.end();
556615
557 const param_decl = t.ast.fullParamDecl(param);616 const param_decl = t.ast.fullParamDecl(param);
558 const value = try t.transformExpressionToBlock(decl, param_decl.type, false);617 const value = try t.transformExpressionToBlock(decl, param_decl.type orelse @panic("TODO: inferred param types"), false);
559618
560 var id, const id_allocated = try t.decodeIdentifierExtra(param_decl.id);619 var id, const id_allocated = try t.decodeIdentifierExtra(param_decl.id);
561 if (id_allocated)620 if (id_allocated)
...@@ -699,7 +758,6 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc...@@ -699,7 +758,6 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc
699 .root,758 .root,
700 .param_decl,759 .param_decl,
701 .var_decl,760 .var_decl,
702 .fn_decl,
703 => unreachable, // not an expression761 => unreachable, // not an expression
704762
705 .id => {763 .id => {
...@@ -1157,29 +1215,91 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc...@@ -1157,29 +1215,91 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc
1157 return try t.transformExpression(decl, group_node.expr);1215 return try t.transformExpression(decl, group_node.expr);
1158 },1216 },
11591217
1218 .struct_decl => {
1219 const struct_node = t.ast.fullStructDecl(node);
1220 const payload_index = try t.extra.alloc(t.gpa, struct_node.fields.len * 2);
1221
1222 for (
1223 struct_node.fields,
1224 payload_index..,
1225 payload_index + struct_node.fields.len..,
1226 ) |field_node, i, j| {
1227 const field = t.ast.fullStructField(field_node);
1228
1229 if (mem.startsWith(u8, field.id, "@")) {
1230 @panic("TODO: string identifiers");
1231 }
1232
1233 t.extra.setU32(i, @intFromEnum(try t.addNullTerminatedString(field.id)));
1234 t.extra.setU32(j, @intFromEnum(try t.transformExpressionToBlock(decl, field.type, false)));
1235 }
1236
1237 return try t.addInstRef(.{
1238 .tag = .struct_decl,
1239 .data = EncodedData.encode(.struct_decl, .{
1240 .src_node = decl.nodeOffset(node),
1241 .extra_index = try t.extra.append(t.gpa, Inst.StructDecl, .{
1242 .payload_index = payload_index,
1243 .field_len = @intCast(struct_node.fields.len),
1244 }),
1245 }),
1246 });
1247 },
1248
1249 .struct_init => {
1250 const struct_node = t.ast.fullStructInit(node);
1251 const payload_index = try t.extra.alloc(t.gpa, struct_node.fields.len * 2);
1252
1253 const base_type = try t.transformExpression(
1254 decl,
1255 struct_node.type orelse @panic("TODO: TEG implicit struct"),
1256 ) orelse
1257 @panic("TODO: print better error");
1258
1259 for (
1260 struct_node.fields,
1261 payload_index..,
1262 payload_index + struct_node.fields.len..,
1263 ) |field_node, i, j| {
1264 const field = t.ast.fullStructField(field_node);
1265
1266 if (mem.startsWith(u8, field.id, "@")) {
1267 @panic("TODO: string identifiers");
1268 }
1269
1270 t.extra.setU32(i, @intFromEnum(try t.addNullTerminatedString(field.id)));
1271 t.extra.setU32(j, @intFromEnum((try t.transformExpression(decl, field.type)) orelse
1272 @panic("TODO: print better error")));
1273 }
1274
1275 return try t.addInstRef(.{
1276 .tag = .struct_init,
1277 .data = EncodedData.encode(.struct_init, .{
1278 .src_node = decl.nodeOffset(node),
1279 .extra_index = try t.extra.append(t.gpa, Inst.StructInit, .{
1280 .base_type = base_type,
1281 .payload_index = payload_index,
1282 .field_len = @intCast(struct_node.fields.len),
1283 }),
1284 }),
1285 });
1286 },
1287
1160 else => |tag| std.debug.panic("TODO: expr {s}", .{@tagName(tag)}),1288 else => |tag| std.debug.panic("TODO: expr {s}", .{@tagName(tag)}),
1161 }1289 }
1162}1290}
11631291
1164fn transformCallArgs(t: *Transform, decl: BlockContext, args: []const NodeIndex) !?RefList {1292fn transformCallArgs(t: *Transform, decl: BlockContext, args: []const NodeIndex) !?RefList {
1165 const args_top = t.call_args.items.len;1293 const extra_top = try t.extra.alloc(t.gpa, args.len);
1166 defer t.call_args.shrinkRetainingCapacity(args_top);
1167
1168 for (args) |arg| {
1169 try t.call_args.append(
1170 t.gpa,
1171 try t.transformExpression(decl, arg) orelse
1172 return null,
1173 );
1174 }
11751294
1176 const items = t.call_args.items[args_top..];1295 for (args, extra_top..) |arg, i| {
1177 const extra_top = t.extra.list.items.len;1296 t.extra.setU32(i, @intFromEnum(try t.transformExpression(decl, arg) orelse
1178 try t.extra.list.appendSlice(t.gpa, @ptrCast(items));1297 return null));
1298 }
11791299
1180 return .{1300 return .{
1181 .start = @intCast(extra_top),1301 .start = @intCast(extra_top),
1182 .len = @intCast(items.len),1302 .len = @intCast(args.len),
1183 };1303 };
1184}1304}
11851305
...@@ -1193,7 +1313,6 @@ fn transformAssignmentTarget(t: *Transform, decl: BlockContext, node: NodeIndex)...@@ -1193,7 +1313,6 @@ fn transformAssignmentTarget(t: *Transform, decl: BlockContext, node: NodeIndex)
1193 .root,1313 .root,
1194 .param_decl,1314 .param_decl,
1195 .var_decl,1315 .var_decl,
1196 .fn_decl,
1197 => unreachable, // not an expression1316 => unreachable, // not an expression
11981317
1199 .id => {1318 .id => {
src/core/array.zig+63-28
...@@ -40,44 +40,46 @@ fn setAsU32(items: []u32, comptime T: type, index: usize, payload: T) void {...@@ -40,44 +40,46 @@ fn setAsU32(items: []u32, comptime T: type, index: usize, payload: T) void {
40}40}
4141
42fn getAsU32(items: []const u32, comptime T: type, index: usize) T {42fn getAsU32(items: []const u32, comptime T: type, index: usize) T {
43 const fields = std.meta.fields(T);43 return switch (T) {
44 var i = index;44 u32 => items[index],
45 var result: T = undefined;45 i32 => @bitCast(items[index]),
46 inline for (fields) |field| {
47 @field(result, field.name) = switch (field.type) {
48 u32 => items[i],
49 i32 => @bitCast(items[i]),
5046
51 [16]u8 => @bitCast(items[i..][0..4].*),47 [16]u8 => @bitCast(items[index..][0..4].*),
5248
53 else => brk: {49 else => brk: {
54 const info = @typeInfo(field.type);50 const info = @typeInfo(T);
55 if (info == .Enum) {51 if (info == .Enum) {
56 break :brk @enumFromInt(items[i]);52 break :brk @enumFromInt(items[index]);
57 }53 }
5854
59 const struct_data = info.Struct;55 const struct_data = info.Struct;
60 if (struct_data.layout == .@"packed") {56 if (struct_data.layout == .@"packed") {
61 break :brk @bitCast(items[i]);57 break :brk @bitCast(items[index]);
62 }58 }
6359
64 break :brk @call(60 const fields = std.meta.fields(T);
61 var i = index;
62 var result: T = undefined;
63
64 inline for (fields) |field| {
65 @field(result, field.name) = @call(
65 .always_inline,66 .always_inline,
66 getAsU32,67 getAsU32,
67 .{ items, field.type, i },68 .{ items, field.type, i },
68 );69 );
69 },70 i += comptime extraSizeOf(field.type);
70 };71 }
71 i += extraSizeOf(field.type);72
72 }73 break :brk result;
73 return result;74 },
75 };
74}76}
7577
76pub const StoreKind = enum {78pub const StoreKind = enum {
77 intern_pool,79 intern_pool,
78 teg,80 teg,
79 air,81 air,
80 tfr,82 js_tree,
8183
82 pub fn resolveStoreConst(84 pub fn resolveStoreConst(
83 comptime kind: StoreKind,85 comptime kind: StoreKind,
...@@ -204,6 +206,7 @@ pub fn extraSizeOf(comptime T: type) usize {...@@ -204,6 +206,7 @@ pub fn extraSizeOf(comptime T: type) usize {
204 };206 };
205}207}
206208
209// TODO: rename to Store
207pub fn ExtraStore(comptime set_kind: StoreKind, comptime set_mutability: Mutability) type {210pub fn ExtraStore(comptime set_kind: StoreKind, comptime set_mutability: Mutability) type {
208 return struct {211 return struct {
209 list: List = .{},212 list: List = .{},
...@@ -240,7 +243,11 @@ pub fn ExtraStore(comptime set_kind: StoreKind, comptime set_mutability: Mutabil...@@ -240,7 +243,11 @@ pub fn ExtraStore(comptime set_kind: StoreKind, comptime set_mutability: Mutabil
240 return ip.get(T, trail.index);243 return ip.get(T, trail.index);
241 }244 }
242245
243 pub fn readValueSlice(trail: *TrailReader, len: u32) @import("InternPool.zig").Value.Slice {246 pub fn at(trail: *const TrailReader, ip: *const Store, comptime T: type, index: u32) T {
247 return ip.get(T, trail.index + index);
248 }
249
250 pub fn readValueSlice(trail: *TrailReader, len: u32) InternPool.Value.Slice {
244 defer trail.index += len;251 defer trail.index += len;
245 return .{252 return .{
246 .start = @intCast(trail.index),253 .start = @intCast(trail.index),
...@@ -248,7 +255,18 @@ pub fn ExtraStore(comptime set_kind: StoreKind, comptime set_mutability: Mutabil...@@ -248,7 +255,18 @@ pub fn ExtraStore(comptime set_kind: StoreKind, comptime set_mutability: Mutabil
248 };255 };
249 }256 }
250257
251 pub fn readTypeSlice(trail: *TrailReader, len: u32) @import("InternPool.zig").Type.Slice {258 pub fn readTypeSlice(trail: *TrailReader, len: u32) InternPool.Type.Slice {
259 defer trail.index += len;
260 return .{
261 .start = @intCast(trail.index),
262 .len = len,
263 };
264 }
265
266 pub fn readNullTerminatedStringSlice(
267 trail: *TrailReader,
268 len: u32,
269 ) InternPool.NullTerminatedString.Slice {
252 defer trail.index += len;270 defer trail.index += len;
253 return .{271 return .{
254 .start = @intCast(trail.index),272 .start = @intCast(trail.index),
...@@ -321,12 +339,18 @@ pub fn ExtraStore(comptime set_kind: StoreKind, comptime set_mutability: Mutabil...@@ -321,12 +339,18 @@ pub fn ExtraStore(comptime set_kind: StoreKind, comptime set_mutability: Mutabil
321 };339 };
322 }340 }
323341
324 fn set(store: *Store, comptime T: type, index: usize, payload: T) void {342 // TODO: make setAsU32 allow any type
343 pub fn setU32(store: *Store, index: usize, payload: u32) void {
344 comptime assert(mutability == .@"var");
345 store.list.items[index] = payload;
346 }
347
348 pub fn set(store: *Store, comptime T: type, index: usize, payload: T) void {
325 comptime assert(mutability == .@"var");349 comptime assert(mutability == .@"var");
326 setAsU32(store.list.items, T, index, payload);350 setAsU32(store.list.items, T, index, payload);
327 }351 }
328352
329 fn get(store: *const Store, comptime T: type, index: usize) T {353 pub fn get(store: *const Store, comptime T: type, index: usize) T {
330 return getAsU32(store.list.items, T, index);354 return getAsU32(store.list.items, T, index);
331 }355 }
332356
...@@ -338,6 +362,16 @@ pub fn ExtraStore(comptime set_kind: StoreKind, comptime set_mutability: Mutabil...@@ -338,6 +362,16 @@ pub fn ExtraStore(comptime set_kind: StoreKind, comptime set_mutability: Mutabil
338 pub fn constant(store: *const Store) ExtraStore(set_kind, .@"const") {362 pub fn constant(store: *const Store) ExtraStore(set_kind, .@"const") {
339 return .{ .list = .{ .items = store.list.items } };363 return .{ .list = .{ .items = store.list.items } };
340 }364 }
365
366 // TODO: Add some safety to the Index returned from this
367 /// Allocate a number of u32s into this store, but they are left uninitialized.
368 pub fn alloc(store: *Store, gpa: Allocator, len: usize) !u32 {
369 comptime assert(mutability == .@"var");
370 const idx = store.list.items.len;
371 try store.list.ensureUnusedCapacity(gpa, len);
372 store.list.items.len += len;
373 return @intCast(idx);
374 }
341 };375 };
342}376}
343377
...@@ -388,3 +422,4 @@ pub const Float = struct {...@@ -388,3 +422,4 @@ pub const Float = struct {
388const std = @import("std");422const std = @import("std");
389const assert = std.debug.assert;423const assert = std.debug.assert;
390const Allocator = std.mem.Allocator;424const Allocator = std.mem.Allocator;
425const InternPool = @import("InternPool.zig");
src/core/js/Js.zig created+65
...@@ -0,0 +1,65 @@
1//! JavaScript code generation backend
2//!
3//! * Air -(NodeGen)-> JsTree
4//! * []JsTree -(Link)-> []const u8
5//!
6pub const NodeGen = @import("NodeGen.zig");
7pub const Tree = @import("Tree.zig");
8pub const Link = @import("Link.zig");
9
10codegen_map: std.AutoHashMapUnmanaged(Value, Tree) = .{},
11
12pub fn deinit(backend: *Js, comp: *Comp) void {
13 {
14 var it = backend.codegen_map.valueIterator();
15 while (it.next()) |item| {
16 item.deinit(comp.gpa);
17 }
18 }
19
20 backend.codegen_map.deinit(comp.gpa);
21}
22
23pub fn handleFunctionAir(
24 backend: *Js,
25 comp: *Comp,
26 value: Value,
27 air: *const Air,
28 arena: Allocator,
29) !u32 {
30 var ng = try NodeGen.init(comp.gpa, arena, &comp.intern_pool, air);
31 errdefer ng.deinit();
32 try ng.build();
33
34 const tree = try ng.finalize();
35 try backend.codegen_map.put(comp.gpa, value, tree);
36
37 return 0;
38}
39
40pub fn link(
41 backend: *Js,
42 comp: *Comp,
43 entry_point: Comp.Decl.Index,
44 writer: std.io.AnyWriter,
45) !void {
46 var state = Link{
47 .comp = comp,
48 .backend = backend,
49 };
50 defer state.deinit();
51 try state.linkJsCode(entry_point, writer);
52}
53
54const Js = @This();
55
56const std = @import("std");
57const assert = std.debug.assert;
58const log = std.log.scoped(.js);
59const Allocator = std.mem.Allocator;
60
61const core = @import("../mod.zig");
62const Comp = core.Comp;
63const Air = core.Air;
64const InternPool = core.InternPool;
65const Value = InternPool.Value;
src/core/js/Link.zig created+821
...@@ -0,0 +1,821 @@
1//! This is the JavaScript backend's linker. It takes in a set of `Js.Tree`s and
2//! produces a JavaScript bundle out of them.
3//!
4//! This approach is flawed because it combining too much work in one unit.
5//! Printing and Linking should be separate stages, so that more work can be
6//! done in parallel. This approach is used for now to make identifier name
7//! collisions easier to deal with.
8comp: *Comp,
9backend: *Js,
10
11/// Value is an index into `link_strings`
12decls: std.AutoHashMapUnmanaged(InternPool.Value, LinkString.Index) = .{},
13link_strings: std.ArrayHashMapUnmanaged(
14 LinkString,
15 GlobalOwner,
16 LinkString.Context,
17 true,
18) = .{},
19string_bytes: std.ArrayListUnmanaged(u8) = .{},
20
21pub fn linkJsCode(link: *Link, entry_decl: Decl.Index, writer: Writer) !void {
22 var t = tracer.trace(@src());
23 defer t.end();
24
25 // 1. Resolve all global identifiers. Function names must not shadow external
26 // names. We also need this list prepared so that local identifier names do not
27 // shadow functions or externals.
28 const root_decl = link.comp.intern_pool.declPtrConst(entry_decl);
29 assert(root_decl.name == .empty);
30
31 const root_function = link.treeForFunction(root_decl.value());
32
33 for (root_function.extern_symbols) |extern_symbol| {
34 try link.reserveExternSymbol(extern_symbol);
35 }
36
37 var dependency_queue: DependencyQueue = .{};
38 defer dependency_queue.list.deinit(link.comp.gpa);
39 try dependency_queue.list.appendSlice(link.comp.gpa, root_function.top_level_value_deps);
40
41 while (dependency_queue.next()) |value| {
42 const tree = link.treeForFunction(value);
43
44 for (tree.extern_symbols) |extern_symbol| {
45 try link.reserveExternSymbol(extern_symbol);
46 }
47
48 try dependency_queue.list.appendSlice(link.comp.gpa, tree.top_level_value_deps);
49
50 const decl = link.comp.intern_pool.declPtrConst(
51 value.valueData(&link.comp.intern_pool, .func)
52 .get(&link.comp.intern_pool)
53 .owner_decl,
54 );
55 const name = decl.name.toSlice(&link.comp.intern_pool);
56 assert(name.len > 0);
57
58 const gop = try link.link_strings.getOrPutContextAdapted(
59 link.comp.gpa,
60 name,
61 LinkString.SliceContext{ .link = link },
62 .{ .link = link },
63 );
64 if (gop.found_existing) {
65 @panic("TODO: handle this name collision case");
66 }
67 const name_str = try link.appendLinkString(name);
68 gop.key_ptr.* = name_str;
69 gop.value_ptr.* = .decl;
70 try link.decls.putNoClobber(link.comp.gpa, value, @enumFromInt(gop.index));
71 }
72
73 try writer.print("// Language Compiler v0.1.4. Alpha quality software.\n", .{});
74
75 if (root_function.nodes.len > 1) {
76 // 2. Print every function decl
77 dependency_queue.i = 0;
78 while (dependency_queue.next()) |value| {
79 try link.printTopLevelValue(value, writer);
80 }
81
82 // 3. Print the top-level code
83 try link.printDeclContents(root_function, writer);
84 } else {
85 try writer.print("// (program has no observable effects, thus an empty file)\n", .{});
86 }
87}
88
89fn treeForFunction(link: *const Link, value: InternPool.Value) *Tree {
90 assert(value.valueTag(&link.comp.intern_pool) == .func); // must be a function
91 return link.backend.codegen_map.getPtr(value) orelse unreachable;
92}
93
94const DependencyQueue = struct {
95 list: std.ArrayListUnmanaged(InternPool.Value) = .{},
96 i: usize = 0,
97
98 pub fn next(queue: *DependencyQueue) ?InternPool.Value {
99 if (queue.i >= queue.list.items.len) {
100 return null;
101 }
102
103 defer queue.i += 1;
104 return queue.list.items[queue.i];
105 }
106};
107
108pub fn deinit(link: *Link) void {
109 var t = tracer.trace(@src());
110 defer t.end();
111
112 defer link.* = undefined;
113 link.decls.deinit(link.comp.gpa);
114 link.link_strings.deinit(link.comp.gpa);
115 link.string_bytes.deinit(link.comp.gpa);
116}
117
118fn reserveExternSymbol(link: *Link, extern_symbol: String.WithLen) !void {
119 var t = tracer.trace(@src());
120 defer t.end();
121
122 const name = extern_symbol.toSlice(&link.comp.intern_pool);
123 const gop = try link.link_strings.getOrPutContextAdapted(
124 link.comp.gpa,
125 name,
126 LinkString.SliceContext{ .link = link },
127 .{ .link = link },
128 );
129
130 if (gop.found_existing) {
131 if (gop.value_ptr.* != .external) {
132 @panic("TODO: Rename Decl");
133 }
134 }
135 gop.key_ptr.* = try link.appendLinkString(name);
136 gop.value_ptr.* = .external;
137}
138
139fn appendLinkString(link: *Link, string: []const u8) !LinkString {
140 var t = tracer.trace(@src());
141 defer t.end();
142
143 const start = link.string_bytes.items.len;
144 try link.string_bytes.appendSlice(link.comp.gpa, string);
145 return .{ .start = @intCast(start), .len = @intCast(string.len) };
146}
147
148fn valueRefIdentifier(link: *const Link, value: InternPool.Value) []const u8 {
149 var t = tracer.trace(@src());
150 defer t.end();
151
152 return (link.decls.get(value) orelse if (std.debug.runtime_safety) {
153 std.debug.panic("Value \"{}\" is not present in top level value map", .{
154 value.dump(&link.comp.intern_pool),
155 });
156 } else unreachable).toString(link).slice(link);
157}
158
159fn printTopLevelValue(link: *Link, value: InternPool.Value, writer: Writer) !void {
160 var t = tracer.trace(@src());
161 defer t.end();
162
163 try writer.writeAll("let ");
164 try writer.writeAll(link.valueRefIdentifier(value));
165 try writer.writeAll(" = ");
166
167 const tree = link.treeForFunction(value);
168
169 if (tree.param_count != 1)
170 try writer.writeAll("(");
171 for (0..tree.param_count) |id| {
172 try link.printLocal(@intCast(id), writer);
173 if (id != tree.param_count - 1) try writer.writeAll(", ");
174 }
175 if (tree.param_count != 1)
176 try writer.writeAll(")");
177 try writer.writeAll(" => ");
178
179 const stmts = tree.mainStatements();
180 if (stmts.len == 1 and tree.nodeTag(stmts[0]) == .stmt_return) {
181 if (tree.nodeData(.stmt_return, stmts[0]).operand.unwrap()) |operand| {
182 try link.printExprLevel(tree, operand, .statement, writer);
183 } else {
184 try writer.writeAll("{}");
185 }
186 } else {
187 try writer.writeAll("{\n");
188 try link.printDeclContents(tree, writer);
189 try writer.writeAll("}");
190 }
191
192 try writer.writeAll(";\n");
193}
194
195fn printDeclContents(link: *Link, tree: *const Tree, writer: Writer) !void {
196 var t = tracer.trace(@src());
197 defer t.end();
198
199 for (tree.mainStatements()) |stmt| {
200 try link.printExprLevel(tree, stmt, .statement, writer);
201 try writer.writeAll(";\n");
202 }
203}
204
205fn printBlockContents(
206 link: *Link,
207 tree: *const Tree,
208 list: Tree.IndexList,
209 writer: Writer,
210) !void {
211 var t = tracer.trace(@src());
212 defer t.end();
213
214 assert(list.slice(tree).len > 0);
215 for (list.slice(tree)) |stmt| {
216 try link.printExpr(tree, stmt, writer);
217 switch (tree.nodeTag(stmt)) {
218 .stmt_block,
219 .stmt_if,
220 => {},
221 else => try writer.writeAll(";"),
222 }
223 try writer.writeAll("\n");
224 }
225}
226
227fn printBlock(link: *Link, tree: *const Tree, list: Tree.IndexList, writer: Writer) !void {
228 var t = tracer.trace(@src());
229 defer t.end();
230
231 if (list.len != 1) {
232 try writer.writeAll("{");
233 try link.printBlockContents(tree, list, writer);
234 try writer.writeAll("}");
235 } else {
236 try link.printBlockContents(tree, list, writer);
237 }
238}
239
240fn printExpr(
241 link: *Link,
242 tree: *const Tree,
243 node: Node.Index,
244 writer: Writer,
245) @TypeOf(writer).Error!void {
246 return link.printExprLevel(tree, node, .comma, writer);
247}
248
249fn printValue(link: *Link, value: InternPool.Value, level: Level, writer: anytype) !void {
250 switch (value.toKey(&link.comp.intern_pool)) {
251 // TODO: minification
252 .int32 => |val| try writer.print("{d}", .{val}),
253 // TODO: correct printing of many value types
254 .float => |val| try writer.print("{d}", .{val}),
255 .simple_value => |val| try writer.writeAll(switch (val) {
256 .true => "true",
257 .false => "false",
258 // TODO: make codegen decide from null and undefined
259 // instead of emitting void nodes.
260 .void => "/* void */ undefined",
261 }),
262 .string => |val| try writeString(val, writer),
263
264 .comptime_float => unreachable, // was not lowered to float
265
266 .struct_value => |struct_value| {
267 const wrap = level == .statement;
268 if (wrap)
269 try writer.writeAll("(");
270
271 try writer.writeAll("{");
272
273 for (
274 struct_value.ty.toStructType(&link.comp.intern_pool).?.field_keys,
275 struct_value.field_values,
276 0..,
277 ) |key, inner_value, i| {
278 if (i != 0)
279 try writer.writeAll(",");
280
281 // TODO: handle special identifiers
282 try writer.writeAll(key.toSlice(&link.comp.intern_pool));
283 try writer.writeAll(":");
284 try link.printValue(inner_value, .comma, writer);
285 }
286 try writer.writeAll("}");
287
288 if (wrap)
289 try writer.writeAll(")");
290 },
291
292 else => unreachable, // this value is not a plain_value
293 }
294}
295
296fn printExprLevel(
297 link: *Link,
298 tree: *const Tree,
299 node: Node.Index,
300 level: Level,
301 writer: Writer,
302) @TypeOf(writer).Error!void {
303 var t = tracer.trace(@src());
304 defer t.end();
305
306 switch (tree.nodeTag(node)) {
307 .root => unreachable,
308
309 .plain_value => {
310 const plain_value: Tree.PlainValue = tree.nodeData(.plain_value, node);
311 try link.printValue(plain_value.value, level, writer);
312 },
313
314 .local => {
315 const ref: Tree.LocalRef = tree.nodeData(.local, node);
316 try link.printLocal(ref.id, writer);
317 },
318
319 .global => {
320 const ref: Tree.GlobalRef = tree.nodeData(.global, node);
321 try writer.writeAll(ref.name.toSlice(&link.comp.intern_pool));
322 },
323
324 .top_level_value => {
325 const ref: Tree.ValueRef = tree.nodeData(.top_level_value, node);
326 try writer.writeAll(link.valueRefIdentifier(ref.value));
327 },
328
329 .stmt_break => {
330 const stmt: Tree.StmtBreak = tree.nodeData(.stmt_break, node);
331 try writer.writeAll("break");
332 if (true) {
333 try writer.writeAll(" ");
334 try link.printLabelIdentifier(stmt.label, writer);
335 }
336 },
337
338 .stmt_return => {
339 const stmt: Tree.StmtReturn = tree.nodeData(.stmt_return, node);
340 try writer.writeAll("return");
341 if (stmt.operand.unwrap()) |operand| {
342 try writer.writeAll(" ");
343 try link.printExpr(tree, operand, writer);
344 }
345 },
346
347 .stmt_declare_local => {
348 const stmt: Tree.StmtDeclareLocal = tree.nodeData(.stmt_declare_local, node);
349 try writer.writeAll("let ");
350 try link.printLocal(stmt.identifier_index, writer);
351 if (stmt.init.unwrap()) |init| {
352 try writer.writeAll(" = ");
353 try link.printExpr(tree, init, writer);
354 }
355 },
356
357 .stmt_if => {
358 const stmt: Tree.StmtIf = tree.nodeData(.stmt_if, node);
359 const data = stmt.data.get(tree);
360 try writer.writeAll("if (");
361 try link.printExpr(tree, stmt.cond, writer);
362 try writer.writeAll(") ");
363 if (data.flags.yes_label) {
364 try link.printLabelIdentifier(data.flags.block_label, writer);
365 try writer.writeAll(":");
366 }
367 try link.printBlock(tree, data.yes, writer);
368 if (data.no.len > 0) {
369 try writer.writeAll(" else ");
370 if (data.flags.no_label) {
371 try link.printLabelIdentifier(data.flags.block_label, writer);
372 try writer.writeAll(":");
373 }
374 try link.printBlock(tree, data.no, writer);
375 }
376 },
377
378 .stmt_block => {
379 const stmt: Tree.StmtBlock = tree.nodeData(.stmt_block, node);
380 try link.printLabelIdentifier(stmt.label, writer);
381 try writer.writeAll(": {\n");
382 try link.printBlockContents(tree, stmt.content.get(tree), writer);
383 try writer.writeAll("}");
384 },
385
386 inline .assign,
387 .add,
388 .sub,
389 .mul,
390 .div,
391 .eq,
392 .lt,
393 .gt,
394 .assign_add,
395 .assign_sub,
396 .assign_mul,
397 .assign_div,
398 => |tag| {
399 const bin: Tree.Bin = tree.nodeData(tag, node);
400 const op: Operator = @field(Operator, @tagName(tag));
401 const inner_level = comptime op.level();
402 if (inner_level.needsParens(level))
403 try writer.writeAll("(");
404 try link.printExprLevel(tree, bin.lhs, inner_level, writer);
405 try writer.writeAll(" ");
406 try writer.writeAll(comptime op.symbol());
407 try writer.writeAll(" ");
408 try link.printExprLevel(tree, bin.rhs, inner_level, writer);
409 if (inner_level.needsParens(level))
410 try writer.writeAll(")");
411 },
412
413 inline .negate,
414 .bool_not,
415 .pre_dec,
416 .pre_inc,
417 => |tag| {
418 const un: Tree.Un = tree.nodeData(tag, node);
419 const op: Operator = @field(Operator, @tagName(tag));
420 try writer.writeAll(comptime op.symbol());
421 try link.printExpr(tree, un.operand, writer);
422 },
423
424 .ternary => {
425 const ternary: Tree.Ternary = tree.nodeData(.ternary, node);
426 const data = ternary.data.get(tree);
427 try writer.writeAll("(");
428 try link.printExpr(tree, ternary.cond, writer);
429 try writer.writeAll(" ? ");
430 try link.printExpr(tree, data.yes, writer);
431 try writer.writeAll(" : ");
432 try link.printExpr(tree, data.no, writer);
433 try writer.writeAll(")");
434 },
435
436 .call => {
437 const call: Tree.Call = tree.nodeData(.call, node);
438 const args = call.args.get(tree);
439
440 try link.printExpr(tree, call.base, writer);
441 try writer.writeAll("(");
442
443 for (args.slice(tree), 0..) |arg, i| {
444 if (i != 0) try writer.writeAll(", ");
445 try link.printExpr(tree, arg, writer);
446 }
447
448 try writer.writeAll(")");
449 },
450 .new => {
451 const call: Tree.Call = tree.nodeData(.new, node);
452 const args = call.args.get(tree);
453
454 if (Level.new.needsParens(level))
455 try writer.writeAll("(");
456 try writer.writeAll("new ");
457 try link.printExpr(tree, call.base, writer);
458
459 if (args.len > 0) {
460 try writer.writeAll("(");
461
462 for (args.slice(tree), 0..) |arg, i| {
463 if (i != 0) try writer.writeAll(", ");
464 try link.printExpr(tree, arg, writer);
465 }
466
467 try writer.writeAll(")");
468 }
469 if (Level.new.needsParens(level))
470 try writer.writeAll(")");
471 },
472
473 .field => {
474 const field: Tree.Field = tree.nodeData(.field, node);
475
476 try link.printExpr(tree, field.base, writer);
477 try writer.writeAll(".");
478 try writer.writeAll(field.field.toSlice(&link.comp.intern_pool));
479 },
480
481 .object_literal => {
482 const obj: Tree.ObjectLiteral = tree.nodeData(.object_literal, node);
483
484 const keys: []const Tree.ObjectLiteral.Key =
485 @ptrCast(tree.extra.list.items[obj.extra_index..][0..obj.property_count]);
486 const values: []const Node.Index =
487 @ptrCast(tree.extra.list.items[obj.extra_index + obj.property_count ..][0..obj.property_count]);
488
489 if (level == .statement)
490 try writer.writeAll("(");
491
492 try writer.writeAll("{");
493
494 for (keys, values, 0..) |key, value, i| {
495 if (i != 0)
496 try writer.writeAll(",");
497
498 switch (key.decode()) {
499 .static => |str| {
500 // TODO: handle strings that cannot be printed as identifiers.
501 try writer.writeAll(str.toSlice(&link.comp.intern_pool));
502 },
503 .computed => @panic("TODO: computed property"),
504 }
505
506 try writer.writeAll(":");
507
508 try link.printExpr(tree, value, writer);
509 }
510
511 try writer.writeAll("}");
512
513 if (level == .statement)
514 try writer.writeAll(")");
515 },
516 }
517}
518
519pub fn canPrintWithoutEscape(c: u21) bool {
520 const first_ascii_printable = 0x20;
521 const last_ascii_printable = 0x7E;
522 const first_high_surrogate = 0xD800;
523 const last_low_surrogate = 0xDFFF;
524
525 if (c <= last_ascii_printable) {
526 return c >= first_ascii_printable and c != '\\' and c != '"';
527 } else {
528 return c != 0xFEFF and (c < first_high_surrogate or c > last_low_surrogate);
529 }
530}
531
532pub fn indexOfNeedsEscape(slice: []const u8) ?usize {
533 var it = std.unicode.Wtf8Iterator{ .bytes = slice, .i = 0 };
534 var i: usize = 0;
535 while (it.nextCodepoint()) |c| {
536 if (!canPrintWithoutEscape(c))
537 return i;
538 i = it.i;
539 }
540 return null;
541}
542
543// This function is loosely based on Bun's `writeJSONString`, which
544// is a Zig port of esbuild's `printUnquotedUTF16` function.
545// Both projects are licensed under MIT
546fn writeString(decoded: []const u8, writer: Writer) !void {
547 try writer.writeByte('"');
548
549 var text = decoded;
550 while (text.len > 0) {
551 const width = std.unicode.utf8ByteSequenceLength(text[0]) catch
552 unreachable; // string is encoded incorrectly
553
554 const c: u21 = std.unicode.wtf8Decode(text[0..width]) catch
555 unreachable; // string is encoded incorrectly
556
557 if (canPrintWithoutEscape(c)) {
558 if (indexOfNeedsEscape(text)) |j| {
559 try writer.writeAll(text[0..j]);
560 text = text[j..];
561 continue;
562 } else {
563 try writer.writeAll(text);
564 break;
565 }
566 }
567
568 text = text[width..];
569
570 switch (c) {
571 // Special-case the bell character since it may cause dumping this file to
572 // the terminal to make a sound, which is undesirable. Note that we can't
573 // use an octal literal to print this shorter since octal literals are not
574 // allowed in strict mode (or in template strings).
575 0x07 => {
576 try writer.writeAll("\\x07");
577 },
578 0x08 => {
579 try writer.writeAll("\\b");
580 },
581 0x0C => {
582 try writer.writeAll("\\f");
583 },
584 '\n' => {
585 try writer.writeAll("\\n");
586 },
587 std.ascii.control_code.cr => {
588 try writer.writeAll("\\r");
589 },
590 std.ascii.control_code.vt => {
591 try writer.writeAll("\\v");
592 },
593 '\\' => {
594 try writer.writeAll("\\\\");
595 },
596 '"' => {
597 try writer.writeAll("\\\"");
598 },
599 '\t' => {
600 try writer.writeAll("\\t");
601 },
602
603 else => {
604 const hex_chars = "0123456789ABCDEF";
605
606 if (c < 0xFFFF) {
607 const k = @as(usize, @intCast(c));
608
609 try writer.writeAll(&[_]u8{
610 '\\',
611 'u',
612 hex_chars[(k >> 12) & 0xF],
613 hex_chars[(k >> 8) & 0xF],
614 hex_chars[(k >> 4) & 0xF],
615 hex_chars[k & 0xF],
616 });
617 } else {
618 const first_high_surrogate = 0xD800;
619 const first_low_surrogate = 0xDC00;
620
621 const k = c - 0x10000;
622 const lo = @as(usize, @intCast(first_high_surrogate + ((k >> 10) & 0x3FF)));
623 const hi = @as(usize, @intCast(first_low_surrogate + (k & 0x3FF)));
624
625 try writer.writeAll(&[_]u8{
626 '\\',
627 'u',
628 hex_chars[lo >> 12],
629 hex_chars[(lo >> 8) & 15],
630 hex_chars[(lo >> 4) & 15],
631 hex_chars[lo & 15],
632 '\\',
633 'u',
634 hex_chars[hi >> 12],
635 hex_chars[(hi >> 8) & 15],
636 hex_chars[(hi >> 4) & 15],
637 hex_chars[hi & 15],
638 });
639 }
640 },
641 }
642 }
643 try writer.writeByte('"');
644}
645
646fn printLocal(link: *Link, local: Tree.LocalIndex, writer: Writer) !void {
647 var t = tracer.trace(@src());
648 defer t.end();
649
650 _ = link; // todo: be smarter about this
651 try writer.print("v{d}", .{local});
652}
653
654fn printLabelIdentifier(link: *Link, local: Tree.LocalIndex, writer: Writer) !void {
655 var t = tracer.trace(@src());
656 defer t.end();
657
658 _ = link; // todo: be smarter about this
659 try writer.print("label{d}", .{local});
660}
661
662const GlobalOwner = enum {
663 external,
664 decl,
665};
666
667// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_precedence
668const Level = enum {
669 /// Extra level used for object literals in arrow function returns
670 statement,
671 comma,
672 assignment,
673 conditional,
674 logical_or,
675 logical_and,
676 bitwise_or,
677 bitwise_xor,
678 bitwise_and,
679 equality,
680 relational,
681 bitshift,
682 additive,
683 multiplicative,
684 exponentiation,
685 prefix,
686 postfix,
687 new,
688 access_and_call,
689 member,
690
691 pub fn needsParens(inner_level: Level, outer_level: Level) bool {
692 return @intFromEnum(outer_level) > @intFromEnum(inner_level);
693 }
694};
695
696const Operator = enum {
697 assign,
698 assign_add,
699 assign_sub,
700 assign_mul,
701 assign_div,
702 add,
703 sub,
704 mul,
705 div,
706 eq,
707 lt,
708 gt,
709 negate,
710 bool_not,
711 pre_inc,
712 pre_dec,
713
714 pub fn level(op: Operator) Level {
715 return switch (op) {
716 .assign,
717 .assign_add,
718 .assign_sub,
719 .assign_mul,
720 .assign_div,
721 => .assignment,
722 .add, .sub => .additive,
723 .mul, .div => .multiplicative,
724 .eq => .equality,
725 .lt, .gt => .relational,
726
727 .pre_inc,
728 .pre_dec,
729 .bool_not,
730 .negate,
731 => .prefix,
732 };
733 }
734
735 pub fn symbol(op: Operator) []const u8 {
736 return switch (op) {
737 .assign => "=",
738 .assign_add => "+=",
739 .assign_sub => "-=",
740 .assign_mul => "*=",
741 .assign_div => "/=",
742 .add => "+",
743 .sub => "-",
744 .mul => "*",
745 .div => "/",
746 .eq => "===",
747 .lt => "<",
748 .gt => ">",
749
750 .pre_inc => "++",
751 .pre_dec => "++",
752
753 .negate => "-",
754 .bool_not => "!",
755 };
756 }
757};
758
759const LinkString = struct {
760 start: u32,
761 len: u32,
762
763 fn slice(str: LinkString, link: *const Link) []const u8 {
764 return link.string_bytes.items[str.start..][0..str.len];
765 }
766
767 const Index = enum(u32) {
768 _,
769
770 pub fn toString(index: Index, link: *const Link) LinkString {
771 return link.link_strings.keys()[@intFromEnum(index)];
772 }
773 };
774
775 const Context = struct {
776 link: *const Link,
777
778 pub fn hash(ctx: Context, str: LinkString) u32 {
779 assert(str.start + str.len < ctx.link.string_bytes.items.len);
780 return @truncate(Wyhash.hash(0, str.slice(ctx.link)));
781 }
782
783 pub fn eql(ctx: Context, a: LinkString, b: LinkString, _: usize) bool {
784 assert(a.start + a.len < ctx.link.string_bytes.items.len);
785 assert(b.start + b.len < ctx.link.string_bytes.items.len);
786 return a.start == b.start;
787 }
788 };
789
790 const SliceContext = struct {
791 link: *const Link,
792
793 pub fn hash(_: SliceContext, str: []const u8) u32 {
794 return @truncate(Wyhash.hash(0, str));
795 }
796
797 pub fn eql(ctx: SliceContext, a: []const u8, b: LinkString, _: usize) bool {
798 // since the given string is not in string_bytes we do comparisons
799 return std.mem.eql(u8, a, b.slice(ctx.link));
800 }
801 };
802};
803
804const Link = @This();
805const std = @import("std");
806const assert = std.debug.assert;
807const Wyhash = std.hash.Wyhash;
808const log = std.log.scoped(.js_link);
809const Writer = std.io.AnyWriter;
810
811const Comp = @import("../Comp.zig");
812const Decl = Comp.Decl;
813
814const Js = @import("Js.zig");
815const Tree = @import("Tree.zig");
816const Node = Tree.Node;
817
818const InternPool = @import("../InternPool.zig");
819const String = InternPool.String;
820
821const tracer = @import("tracer");
src/core/js/NodeGen.zig created+751
...@@ -0,0 +1,751 @@
1//! Air -> Js.Tree
2gpa: Allocator,
3arena: Allocator,
4air: *const Air,
5ip: *const InternPool,
6
7nodes: Node.List,
8extra: dod.ExtraStore(.js_tree, .@"var"),
9extern_symbols: std.AutoArrayHashMapUnmanaged(InternPool.String.WithLen, void),
10top_level_value_deps: std.AutoArrayHashMapUnmanaged(Value, void),
11
12param_count: u32,
13air_node_map: []MapEntry,
14scratch: std.ArrayListUnmanaged(u32),
15next_id: u32,
16next_label: u32,
17
18const Error = error{
19 OutOfMemory,
20};
21
22const MapEntry = union {
23 none: void,
24 node: Node.Index,
25 block: packed struct {
26 used: bool = false,
27 index: u31,
28 },
29};
30
31pub fn init(gpa: Allocator, arena: Allocator, ip: *const InternPool, air: *const Air) !NodeGen {
32 const t = tracer.trace(@src());
33 defer t.end();
34
35 const air_node_map = try arena.alloc(MapEntry, air.insts.len);
36 if (std.debug.runtime_safety) {
37 @memset(air_node_map, .{ .none = {} });
38 }
39 const param_count = air.paramCount();
40 return .{
41 .gpa = gpa,
42 .arena = arena,
43 .air = air,
44 .ip = ip,
45 .param_count = param_count,
46 .nodes = .{},
47 .extern_symbols = .{},
48 .top_level_value_deps = .{},
49 .air_node_map = air_node_map,
50 .extra = .{},
51 .scratch = .{},
52 .next_id = param_count,
53 .next_label = 0,
54 };
55}
56
57pub fn deinit(c: *NodeGen) void {
58 const t = tracer.trace(@src());
59 defer t.end();
60
61 defer c.* = undefined;
62 c.nodes.deinit(c.gpa);
63 c.extra.deinit(c.gpa);
64 c.extern_symbols.deinit(c.gpa);
65 c.top_level_value_deps.deinit(c.gpa);
66
67 // arena can deinit the rest
68}
69
70pub fn finalize(c: *NodeGen) !Tree {
71 const t = tracer.trace(@src());
72 defer t.end();
73
74 // todo: errdefer will probably leave memory in a fully invalid state
75 try c.nodes.setCapacity(c.gpa, c.nodes.len);
76
77 var extra = try c.extra.toOwnedStore(c.gpa);
78 errdefer extra.deinit(c.gpa);
79
80 // todo: i dont like this dupe
81 const extern_symbols = try c.gpa.dupe(InternPool.String.WithLen, c.extern_symbols.keys());
82 errdefer c.gpa.free(extern_symbols);
83
84 const top_level_value_deps = try c.gpa.dupe(Value, c.top_level_value_deps.keys());
85 errdefer c.gpa.free(top_level_value_deps);
86
87 defer c.deinit();
88
89 return .{
90 .param_count = c.param_count,
91 .nodes = c.nodes.toOwnedSlice(),
92 .extern_symbols = extern_symbols,
93 .top_level_value_deps = top_level_value_deps,
94 .extra = extra,
95 };
96}
97
98pub fn build(c: *NodeGen) !void {
99 const t = tracer.trace(@src());
100 defer t.end();
101
102 assert(c.air.instTag(.main_block) == .block);
103 const statements = c.air.instData(.block, .main_block).slice(c.air);
104
105 const scratch_top = c.scratch.items.len;
106 defer c.scratch.shrinkRetainingCapacity(scratch_top);
107
108 _ = try c.add(.root, undefined);
109
110 if (statements.len > 0) {
111 for (statements[0..c.param_count], 1..) |stmt, expected_id| {
112 assert(@intFromEnum(stmt) == expected_id);
113 assert(c.air.instTag(stmt) == .arg);
114 c.air_node_map[@intFromEnum(stmt)] = .{
115 .node = try c.add(.local, .{ .id = @intFromEnum(stmt) - 1 }),
116 };
117 }
118
119 for (statements[c.param_count..]) |stmt| {
120 try c.codeGenInst(stmt);
121 }
122 }
123
124 const items = c.scratch.items[scratch_top..];
125 const top = c.extra.list.items.len;
126 try c.extra.list.appendSlice(c.gpa, items);
127 c.nodes.items(.data)[0] = EncodedData.encode(.root, .{
128 .start = @intCast(top),
129 .len = @intCast(items.len),
130 });
131}
132
133fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void {
134 const t = tracer.traceFmt(@src(), "#{d} {s}", .{ @intFromEnum(inst), @tagName(c.air.instTag(inst)) });
135 defer t.end();
136
137 switch (c.air.instTag(inst)) {
138 .arg => unreachable, // codegen must skip over these instructions.
139
140 .block => try c.codeGenBlock(inst),
141
142 .js_global_ref => {
143 const data: InternPool.String.WithLen = c.air.instData(.js_global_ref, inst);
144 try c.extern_symbols.put(c.gpa, data, {});
145 c.air_node_map[@intFromEnum(inst)] = .{
146 .node = try c.add(.global, .{ .name = data }),
147 };
148 },
149
150 .memory => {
151 const i = @intFromEnum(inst);
152 const data: Air.Memory = c.air.instData(.memory, inst);
153 const value: Tree.Node.Index.Optional = if (data.init.isVoid())
154 .none
155 else
156 (try c.fetchExpression(data.init)).toOptional();
157
158 try c.addStmt(try c.add(.stmt_declare_local, .{
159 .identifier_index = c.next_id,
160 .init = value,
161 }));
162 const id = try c.add(.local, .{ .id = c.next_id });
163 c.next_id += 1;
164 c.air_node_map[i] = .{ .node = id };
165 },
166
167 inline .gt,
168 .lt,
169 .eq,
170 .add,
171 .sub,
172 .mul,
173 .div,
174 => |tag| {
175 const data: Air.Bin = c.air.instData(tag, inst);
176 const node = try c.add(@field(Tree.Tag, @tagName(tag)), .{
177 .lhs = try c.fetchExpression(data.lhs),
178 .rhs = try c.fetchExpression(data.rhs),
179 });
180 try c.markExpression(inst, node);
181 },
182
183 .store => {
184 const data: Air.Store = c.air.instData(.store, inst);
185
186 const target = try c.fetchInstExpression(data.memory);
187
188 if (data.operand.kind == .index) {
189 const index = data.operand.decode().index;
190 switch (c.air.instTag(index)) {
191 inline .add,
192 .sub,
193 .mul,
194 .div,
195 => |tag| {
196 const inner_data: Air.Bin = c.air.instData(tag, index);
197 if (inner_data.lhs.eql(data.memory.toRef())) {
198 if ((tag == .add or tag == .sub) and
199 inner_data.rhs.kind == .value)
200 {
201 // if (inner_data.rhs.decode().value.toFloat(c.ip)) |f| {
202 // if (f == 1) {
203 // // convert this into ++ or --
204 // const node = try c.add(
205 // if (tag == .add)
206 // .pre_inc
207 // else
208 // .pre_dec,
209 // .{
210 // .operand = target,
211 // },
212 // );
213 // try c.markExpression(inst, node);
214
215 // return;
216 // }
217 // }
218 }
219
220 // convert into a compound assignment
221 const node = try c.add(
222 @field(Tree.Tag, "assign_" ++ @tagName(tag)),
223 .{
224 .lhs = target,
225 .rhs = try c.fetchExpression(inner_data.rhs),
226 },
227 );
228 try c.markExpression(inst, node);
229
230 return;
231 }
232 },
233 else => {},
234 }
235 }
236
237 const node = try c.add(.assign, .{
238 .lhs = target,
239 .rhs = try c.fetchExpression(data.operand),
240 });
241 try c.markExpression(inst, node);
242 },
243
244 inline .negate,
245 .bool_not,
246 => |tag| {
247 const data: Air.Un = c.air.instData(tag, inst);
248 const node = try c.add(@field(Tree.Tag, @tagName(tag)), .{
249 .operand = try c.fetchExpression(data.operand),
250 });
251 try c.markExpression(inst, node);
252 },
253
254 .field_opaque => {
255 const data: Air.FieldOpaque = c.air.instData(.field_opaque, inst);
256 const node = try c.add(.field, .{
257 .base = try c.fetchExpression(data.base),
258 .field = data.field,
259 });
260 try c.markExpression(inst, node);
261 },
262
263 .field_struct => {
264 const data: Air.FieldStruct = c.air.instData(.field_struct, inst);
265 const node = try c.add(.field, .{
266 .base = try c.fetchInstExpression(data.base),
267 .field = field_name: {
268 break :field_name c.air.typeOfIndex(data.base, c.ip)
269 .toStructType(c.ip).?
270 .field_keys[data.field_index];
271 },
272 });
273 try c.markExpression(inst, node);
274 },
275
276 inline .call, .new => |tag| {
277 const data: Air.Call = c.air.instData(tag, inst);
278 const call_data = data.call_data.get(c.air);
279
280 try c.extra.list.ensureUnusedCapacity(c.gpa, call_data.args.len);
281 const start = c.extra.list.items.len;
282 c.extra.list.items.len += call_data.args.len;
283
284 const items = c.extra.list.items[start..][0..call_data.args.len];
285
286 for (call_data.args.slice(c.air), items) |arg, *dest| {
287 dest.* = @intFromEnum(try c.fetchExpression(arg));
288 }
289
290 const node = try c.add(@field(Tree.Tag, @tagName(tag)), .{
291 .base = try c.fetchExpression(data.base),
292 .args = try c.extra.append(c.gpa, Tree.IndexList, .{
293 .start = @intCast(start),
294 .len = call_data.args.len,
295 }),
296 });
297 try c.markExpression(inst, node);
298 },
299
300 .@"if" => {
301 const data: Air.If = c.air.instData(.@"if", inst);
302 const extra = data.data.get(c.air);
303 var cond = try c.fetchExpression(data.cond);
304
305 if (c.canLowerToExpression(extra.yes) and
306 c.canLowerToExpression(extra.no))
307 {
308 const yes = try c.lowerToExpression(extra.yes);
309 const no = try c.lowerToExpression(extra.yes);
310
311 const ternary = try c.add(.ternary, .{
312 .cond = cond,
313 .data = try c.extra.append(c.gpa, Tree.Ternary.Data, .{
314 .yes = yes,
315 .no = no,
316 }),
317 });
318
319 try c.markExpression(inst, ternary);
320 } else {
321 const flags = c.air.instFlags(inst);
322 const uses = flags.uses;
323
324 const target = if (uses != .none)
325 try c.addTemporary()
326 else
327 null;
328
329 var yes = try c.codeGenBranch(extra.yes, target);
330 var no = try c.codeGenBranch(extra.no, target);
331 assert(!((yes == null) and (no == null))); // no "void else void" branches
332
333 // when `yes` is void, swap the condition so that the `else` is omitted
334 if (yes == null) {
335 const temp_yes = yes;
336 yes = no;
337 no = temp_yes;
338 cond = try c.add(.bool_not, .{
339 .operand = cond,
340 });
341 }
342
343 const main_expr = try c.add(.stmt_if, .{
344 .cond = cond,
345 .data = try c.extra.append(c.gpa, Tree.StmtIf.Data, .{
346 .yes = yes.?.list,
347 .no = if (no) |branch|
348 branch.list
349 else
350 .{ .start = 0, .len = 0 },
351 .flags = .{
352 .block_label = @intCast(c.next_label + 1),
353 .yes_label = yes.?.requires_label,
354 .no_label = if (no) |branch| branch.requires_label else false,
355 },
356 }),
357 });
358
359 try c.addStmt(main_expr);
360
361 if (target) |node| {
362 try c.markExpression(inst, node);
363 }
364 }
365 },
366
367 .@"break" => {
368 const brk: Air.Break = c.air.instData(.@"break", inst);
369
370 const operand = try c.fetchExpression(brk.value);
371
372 // break #0 -> return
373 if (brk.block_ref == .main_block) {
374 try c.addStmt(try c.add(.stmt_return, .{
375 .operand = operand.toOptional(),
376 }));
377 } else {
378 const block = &c.air_node_map[@intFromEnum(brk.block_ref)].block;
379
380 const block_insts = c.air.instData(.block, brk.block_ref).slice(c.air);
381 if (block_insts[block_insts.len - 1] != inst) {
382 block.used = true;
383 }
384
385 const assignment: Node.Index.Optional = @enumFromInt(c.scratch.items[block.index]);
386 if (assignment.unwrap()) |target| {
387 try c.addStmt(try c.add(.assign, .{
388 .lhs = target,
389 .rhs = operand,
390 }));
391 }
392
393 try c.addStmt(try c.add(.stmt_break, .{
394 .label = c.scratch.items[block.index + 1],
395 }));
396 }
397 },
398
399 .struct_init => {
400 const struct_init: Air.StructInit = c.air.instData(.struct_init, inst);
401 const struct_type = struct_init.ty.toStructType(c.ip).?;
402
403 const property_count: u32 = @intCast(struct_type.field_types.len);
404 const store_offset = try c.extra.alloc(c.gpa, property_count * 2);
405
406 for (
407 struct_type.field_keys,
408 struct_init.valuesPtr(c.air),
409 store_offset..,
410 store_offset + property_count..,
411 ) |key, value, key_i, value_i| {
412 const key_encoded = Tree.ObjectLiteral.Key.Decoded.encode(.{ .static = key });
413 c.extra.list.items[key_i] = @bitCast(key_encoded);
414 const in = try c.fetchExpression(value);
415 c.extra.list.items[value_i] = @intFromEnum(in);
416 }
417
418 const node = try c.add(.object_literal, .{
419 .extra_index = store_offset,
420 .property_count = property_count,
421 });
422 try c.markExpression(inst, node);
423 },
424
425 // else => |tag| std.debug.panic("TODO JS codeGenInst for .{s}", .{@tagName(tag)}),
426 }
427}
428
429fn markExpression(c: *NodeGen, inst: Air.Inst.Index, node: Node.Index) !void {
430 const t = tracer.trace(@src());
431 defer t.end();
432
433 const flags: Air.Flags = c.air.instFlags(inst);
434 const i = @intFromEnum(inst);
435 switch (flags.uses) {
436 .none => {
437 try c.addStmt(node);
438 },
439 .single => {
440 // TODO: this is not aware of side effect fences
441 c.air_node_map[i] = .{ .node = node };
442 },
443 .multi => {
444 const decl = try c.add(.stmt_declare_local, .{
445 .identifier_index = c.next_id,
446 .init = node.toOptional(),
447 });
448 try c.addStmt(decl);
449 const id = try c.add(.local, .{ .id = c.next_id });
450 c.next_id += 1;
451 c.air_node_map[i] = .{ .node = id };
452 },
453 }
454}
455
456fn fetchExpression(c: *NodeGen, ref: Air.Inst.Ref) !Node.Index {
457 const t = tracer.trace(@src());
458 defer t.end();
459
460 return switch (ref.decode()) {
461 .value => |v| c.valueToExpression(v),
462 .index => |index| fetchInstExpression(c, index),
463 };
464}
465
466fn fetchInstExpression(c: *NodeGen, index: Air.Inst.Index) !Node.Index {
467 const t = tracer.trace(@src());
468 defer t.end();
469
470 const data = c.air_node_map[@intFromEnum(index)];
471 if (std.debug.runtime_safety) {
472 const flags: Air.Flags = c.air.instFlags(index);
473 switch (flags.uses) {
474 .none => if (std.debug.runtime_safety) {
475 // incorrectly marked instruction as unused
476 std.debug.panic("called fetchInstExpression(#{d}), but no expression was generated", .{
477 @intFromEnum(index),
478 });
479 } else unreachable,
480 .single => {},
481 .multi => assert(c.nodes.items(.tag)[@intFromEnum(data.node)] == .local),
482 }
483 }
484 return data.node;
485}
486
487fn valueToExpression(c: *NodeGen, v: Value) !Node.Index {
488 const t = tracer.trace(@src());
489 defer t.end();
490
491 const intern_tag = v.valueTag(c.ip);
492 switch (intern_tag) {
493 .removed => unreachable, // UAF
494
495 .function_type,
496 .simple_type,
497 .struct_type,
498 => unreachable, // Type values are not allowed here
499
500 .int32,
501 .float,
502 .comptime_float,
503 .string,
504 .simple_value,
505 .struct_value,
506 => return try c.add(.plain_value, .{ .value = v }),
507
508 // Hoist functions as top level values
509 // TODO: Consider hoisting large values too
510 .func => {
511 try c.top_level_value_deps.put(c.gpa, v, {});
512 return try c.add(.top_level_value, .{ .value = v });
513 },
514 }
515}
516
517fn addTemporary(c: *NodeGen) !Node.Index {
518 const t = tracer.trace(@src());
519 defer t.end();
520
521 try c.addStmt(try c.add(.stmt_declare_local, .{
522 .identifier_index = c.next_id,
523 .init = .none,
524 }));
525 const id = try c.add(.local, .{ .id = c.next_id });
526 c.next_id += 1;
527 return id;
528}
529
530const Branch = struct {
531 requires_label: bool,
532 list: Tree.IndexList,
533};
534
535fn codeGenBranch(c: *NodeGen, inst: Air.Inst.Ref, temporary: ?Node.Index) !?Branch {
536 const t = tracer.trace(@src());
537 defer t.end();
538
539 const scratch_top = c.scratch.items.len;
540 defer c.scratch.shrinkRetainingCapacity(scratch_top);
541
542 c.next_label += 1;
543 defer c.next_label -= 1;
544 const this_label = c.next_label;
545
546 var requires_label = false;
547 var items = switch (inst.decode()) {
548 .value => |v| items: {
549 if (v == .void)
550 return null;
551 if (temporary) |temp| {
552 const node = try c.valueToExpression(v);
553 try c.addStmt(try c.add(.assign, .{
554 .lhs = temp,
555 .rhs = node,
556 }));
557 }
558 break :items c.scratch.items[scratch_top..];
559 },
560 .index => |i| items: {
561 assert(c.air.instTag(i) == .block);
562 c.air_node_map[@intFromEnum(i)] = .{
563 .block = .{ .index = @intCast(scratch_top) },
564 };
565
566 try c.scratch.appendSlice(c.arena, &.{
567 @intFromEnum(if (temporary) |temp| temp.toOptional() else .none),
568 this_label,
569 });
570
571 const block: Air.IndexList = c.air.instData(.block, i);
572 for (block.slice(c.air)) |item| {
573 try c.codeGenInst(item);
574 }
575
576 requires_label = c.air_node_map[@intFromEnum(i)].block.used;
577
578 break :items c.scratch.items[scratch_top + 2 ..];
579 },
580 };
581
582 const last_node = c.scratch.items[c.scratch.items.len - 1];
583 if (c.nodes.items(.tag)[last_node] == .stmt_break and
584 c.nodes.items(.data)[last_node].decode(.stmt_break, .stmt_break).label == this_label)
585 {
586 items.len -= 1;
587 c.nodes.len -= 1;
588 }
589
590 const top = c.extra.list.items.len;
591 try c.extra.list.appendSlice(c.gpa, items);
592 return .{
593 .requires_label = requires_label,
594 .list = .{
595 .start = @intCast(top),
596 .len = @intCast(items.len),
597 },
598 };
599}
600
601fn codeGenBlock(c: *NodeGen, inst: Air.Inst.Index) !void {
602 const t = tracer.trace(@src());
603 defer t.end();
604
605 const scratch_top = c.scratch.items.len;
606 errdefer c.scratch.shrinkRetainingCapacity(scratch_top);
607
608 c.next_label += 1;
609 defer c.next_label -= 1;
610
611 const next_label = c.next_label;
612
613 try c.scratch.appendSlice(c.arena, &.{
614 // @intFromEnum(if (temporary) |temp| temp.toOptional() else .none),
615 @intFromEnum(Node.Index.Optional.none),
616 next_label,
617 });
618
619 c.air_node_map[@intFromEnum(inst)] = .{
620 .block = .{ .index = @intCast(scratch_top) },
621 };
622 const block: Air.IndexList = c.air.instData(.block, inst);
623
624 for (block.slice(c.air)) |item| {
625 try c.codeGenInst(item);
626 }
627
628 const items = c.scratch.items[scratch_top + 2 ..];
629 const top = c.extra.list.items.len;
630 try c.extra.list.appendSlice(c.gpa, items);
631 c.scratch.shrinkRetainingCapacity(scratch_top);
632 try c.addStmt(try c.add(.stmt_block, .{
633 .label = c.next_label,
634 .content = try c.extra.append(c.gpa, Tree.IndexList, .{
635 .start = @intCast(top),
636 .len = @intCast(items.len),
637 }),
638 }));
639}
640
641// Ideally, we convert everything to an expression. This allows for much
642// more compact code. When that isnt possible (`return`, `while`, etc),
643// we detect that here and ensure to lower `if` to actual if statements,
644// and so on.
645fn canLowerToExpression(c: *const NodeGen, inst: Air.Inst.Ref) bool {
646 const t = tracer.trace(@src());
647 defer t.end();
648
649 return switch (inst.decode()) {
650 .value => true,
651 .index => |index| switch (c.air.instTag(index)) {
652 .js_global_ref,
653 .call,
654 .new,
655 .arg,
656 .field_opaque,
657 .field_struct,
658 .add,
659 .sub,
660 .mul,
661 .div,
662 .eq,
663 .lt,
664 .gt,
665 .negate,
666 .bool_not,
667 .store,
668 .struct_init,
669 => true,
670
671 // JavaScript variable declarations require statements.
672 // Later, this could be made true if we can hoist temporaries up.
673 .memory => false,
674
675 // break/return must be a js statement
676 .@"break",
677 => false,
678
679 .block => {
680 // TODO: blocks that are
681 // - all statements except last are lowerable
682 // - the last statement is lowerable OR a break of this block
683 return false;
684 },
685
686 .@"if" => {
687 const call = c.air.instData(.@"if", index);
688 const data = call.data.get(c.air);
689
690 return c.canLowerToExpression(call.cond) and
691 c.canLowerToExpression(data.yes) and
692 c.canLowerToExpression(data.no);
693 },
694 },
695 };
696}
697
698fn lowerToExpression(c: *NodeGen, ref: Air.Inst.Ref) !Node.Index {
699 const t = tracer.trace(@src());
700 defer t.end();
701
702 assert(c.canLowerToExpression(ref));
703 return switch (ref.decode()) {
704 .value => |v| try c.valueToExpression(v),
705 .index => |index| {
706 try c.codeGenInst(index);
707 return c.fetchExpression(ref);
708 },
709 };
710}
711
712fn add(c: *NodeGen, comptime tag: Tree.Tag, data: tag.Data()) !Node.Index {
713 const t = tracer.trace(@src());
714 defer t.end();
715
716 try c.nodes.append(c.gpa, .{
717 .tag = tag,
718 .data = EncodedData.encode(tag, data),
719 });
720 return @enumFromInt(c.nodes.len - 1);
721}
722
723fn addStmt(c: *NodeGen, stmt: Node.Index) !void {
724 const t = tracer.trace(@src());
725 defer t.end();
726
727 try c.scratch.append(c.arena, @intFromEnum(stmt));
728}
729
730const NodeGen = @This();
731
732const std = @import("std");
733const assert = std.debug.assert;
734const Allocator = std.mem.Allocator;
735
736const InternPool = @import("../InternPool.zig");
737const Value = InternPool.Value;
738const NullTerminatedString = InternPool.NullTerminatedString;
739
740const Comp = @import("../Comp.zig");
741const Decl = Comp.Decl;
742
743const Tree = @import("Tree.zig");
744const EncodedData = Tree.EncodedData;
745const Node = Tree.Node;
746const LocalIndex = Tree.LocalIndex;
747
748const Air = @import("../Air.zig");
749
750const dod = @import("../array.zig");
751const tracer = @import("tracer");
src/core/js/Tree.zig created+300
...@@ -0,0 +1,300 @@
1//! JavaScript Function Representation is the last representation before javascript
2//! source bytes are created. One notable thing here is that all local identifiers
3//! are numbered, instead of being named. The first `param_count` local identifiers
4//! refer to parameters, and the rest refer to the respective block that defined them.
5
6param_count: u32,
7nodes: Node.List.Slice,
8extern_symbols: []String.WithLen,
9top_level_value_deps: []Value,
10extra: ExtraStore,
11
12pub const LocalIndex = u32;
13pub const ExtraStore = extra.ExtraStore(.js_tree, .@"const");
14pub const EncodedData = extra.EncodedData(Tag, u64);
15pub const ExtraIndex = ExtraStore.Index;
16
17pub const Tag = enum {
18 root,
19 /// Only allow simple value types: int, float, string, bool
20 /// This value type is inlined into the call site
21 plain_value,
22 /// Reference to a top level value. (give a Value)
23 /// This is used for function values / large constant objects
24 top_level_value,
25 /// Reference to a local identifier (give an VarId)
26 local,
27 /// Reference to a global identifier (give a NullTerminatedString)
28 global,
29 // Bin ops
30 assign,
31 assign_add,
32 assign_sub,
33 assign_mul,
34 assign_div,
35 add,
36 sub,
37 mul,
38 div,
39 eq,
40 lt,
41 gt,
42 // Exprs
43 call,
44 new,
45 field,
46 ternary,
47 object_literal,
48 // Prefix ops
49 negate,
50 bool_not,
51 pre_inc,
52 pre_dec,
53 // Statements
54 /// `let ... = ...`
55 stmt_declare_local,
56 /// `if (...) ... else ...`
57 stmt_if,
58 /// `break ... ...`
59 stmt_break,
60 /// `return ...` (break from block #0)
61 stmt_return,
62 /// `...: { ... }`
63 stmt_block,
64
65 pub fn Data(comptime tag: Tag) type {
66 return switch (tag) {
67 .root => IndexList,
68 .plain_value => PlainValue,
69 .top_level_value => ValueRef,
70 .local => LocalRef,
71 .global => GlobalRef,
72 .assign,
73 .assign_add,
74 .assign_sub,
75 .assign_mul,
76 .assign_div,
77 .add,
78 .sub,
79 .mul,
80 .div,
81 .eq,
82 .lt,
83 .gt,
84 => Bin,
85 .ternary => Ternary,
86 .negate,
87 .bool_not,
88 .pre_inc,
89 .pre_dec,
90 => Un,
91 .object_literal => ObjectLiteral,
92 .call, .new => Call,
93 .field => Field,
94 .stmt_if => StmtIf,
95 .stmt_break => StmtBreak,
96 .stmt_return => StmtReturn,
97 .stmt_declare_local => StmtDeclareLocal,
98 .stmt_block => StmtBlock,
99 };
100 }
101};
102
103pub const Node = struct {
104 tag: Tag,
105 data: EncodedData,
106
107 pub const List = std.MultiArrayList(Node);
108
109 pub const Index = enum(u32) {
110 root = 0,
111 _,
112
113 pub fn toOptional(i: Index) Optional {
114 return @enumFromInt(@intFromEnum(i));
115 }
116
117 pub const Optional = enum(u32) {
118 none = std.math.maxInt(u32),
119 _,
120
121 pub fn unwrap(oi: Optional) ?Index {
122 return if (oi == .none)
123 null
124 else
125 @enumFromInt(@intFromEnum(oi));
126 }
127 };
128 };
129};
130
131pub const PlainValue = struct {
132 value: Value,
133 unused: Unused = .zero,
134};
135
136pub const LocalRef = struct {
137 id: LocalIndex,
138 unused: Unused = .zero,
139};
140
141pub const GlobalRef = struct {
142 name: InternPool.String.WithLen,
143};
144
145pub const ValueRef = struct {
146 value: Value,
147 unused: Unused = .zero,
148};
149
150pub const StmtDeclareLocal = struct {
151 identifier_index: u32,
152 init: Node.Index.Optional,
153};
154
155pub const Bin = struct {
156 lhs: Node.Index,
157 rhs: Node.Index,
158};
159
160pub const Un = struct {
161 operand: Node.Index,
162 unused: Unused = .zero,
163};
164
165pub const Call = struct {
166 base: Node.Index,
167 args: ExtraIndex(IndexList),
168};
169
170pub const Field = struct {
171 base: Node.Index,
172 field: NullTerminatedString,
173};
174
175/// Extra list is `property_count * 2` items long, with the first half being
176/// `ObjectLiteral.Key`s, and the second being value nodes.
177pub const ObjectLiteral = struct {
178 extra_index: u32,
179 property_count: u32,
180
181 pub const Key = packed struct(u32) {
182 kind: Kind,
183 data: u31,
184
185 pub const Kind = enum(u1) {
186 static,
187 computed,
188 };
189
190 pub const Decoded = union(enum) {
191 static: NullTerminatedString,
192 computed: Node.Index,
193
194 pub fn encode(decoded: Decoded) Key {
195 return switch (decoded) {
196 inline else => |data, tag| .{
197 .kind = @field(Kind, @tagName(tag)),
198 .data = @intCast(@intFromEnum(data)),
199 },
200 };
201 }
202 };
203
204 pub fn decode(key: Key) Decoded {
205 return switch (key.kind) {
206 .static => .{ .static = @enumFromInt(key.data) },
207 .computed => .{ .computed = @enumFromInt(key.data) },
208 };
209 }
210 };
211};
212
213pub const IndexList = struct {
214 start: u32,
215 len: u32,
216
217 pub fn slice(list: IndexList, tree: *const Tree) []const Node.Index {
218 return @ptrCast(tree.extra.list.items[list.start..][0..list.len]);
219 }
220};
221
222pub const StmtIf = struct {
223 cond: Node.Index,
224 data: ExtraIndex(Data),
225
226 pub const Data = struct {
227 yes: IndexList,
228 no: IndexList,
229 flags: Flags,
230 };
231
232 pub const Flags = packed struct(u32) {
233 block_label: u16,
234 yes_label: bool,
235 no_label: bool,
236 _: u14 = 0,
237 };
238};
239
240pub const Ternary = struct {
241 cond: Node.Index,
242 data: ExtraIndex(Data),
243
244 pub const Data = struct {
245 yes: Node.Index,
246 no: Node.Index,
247 };
248};
249
250pub const StmtBreak = struct {
251 label: u32,
252 unused: Unused = .zero,
253};
254
255pub const StmtReturn = struct {
256 operand: Node.Index.Optional,
257 unused: Unused = .zero,
258};
259
260pub const StmtBlock = struct {
261 label: u32,
262 content: ExtraIndex(IndexList),
263};
264
265pub fn deinit(tree: *Tree, gpa: std.mem.Allocator) void {
266 tree.nodes.deinit(gpa);
267 gpa.free(tree.top_level_value_deps);
268 gpa.free(tree.extern_symbols);
269 tree.extra.deinit(gpa);
270}
271
272pub fn nodeTag(tree: Tree, index: Node.Index) Tag {
273 return tree.nodes.items(.tag)[@intFromEnum(index)];
274}
275
276pub fn nodeData(tree: Tree, comptime tag: Tag, index: Node.Index) tag.Data() {
277 return tree.nodes.items(.data)[@intFromEnum(index)].decode(tag, tree.nodeTag(index));
278}
279
280pub fn mainStatements(tree: *const Tree) []const Node.Index {
281 return tree.nodeData(.root, .root).slice(tree);
282}
283
284const Tree = @This();
285
286const std = @import("std");
287const assert = std.debug.assert;
288
289const InternPool = @import("../InternPool.zig");
290const Value = InternPool.Value;
291
292const NullTerminatedString = InternPool.NullTerminatedString;
293const String = InternPool.String;
294
295const Comp = @import("../Comp.zig");
296const Decl = Comp.Decl;
297
298const extra = @import("../array.zig");
299
300const Unused = @import("../Ast.zig").Unused;
src/core/main.zig+5-2
...@@ -101,11 +101,14 @@ pub fn main() !void {...@@ -101,11 +101,14 @@ pub fn main() !void {
101 std.process.exit(1);101 std.process.exit(1);
102 };102 };
103103
104 var js_backend = core.Js{};
105
104 var comp: Comp = .{106 var comp: Comp = .{
105 .gpa = tracer.tracingAllocator(&alloc),107 .gpa = tracer.tracingAllocator(&alloc),
106 .options = .{108 .options = .{
107 .dump_ir = dump_ir,109 .dump_ir = dump_ir,
108 },110 },
111 .backend = .{ .js = &js_backend },
109 };112 };
110 try comp.init();113 try comp.init();
111 defer comp.deinit();114 defer comp.deinit();
...@@ -130,7 +133,7 @@ pub fn main() !void {...@@ -130,7 +133,7 @@ pub fn main() !void {
130 try teg.dumpToWriter(stderr);133 try teg.dumpToWriter(stderr);
131 }134 }
132135
133 comp.semaEntryPoint(main_file) catch |err| switch (err) {136 const entry_point = comp.semaEntryPoint(main_file) catch |err| switch (err) {
134 error.AnalysisFail => std.process.exit(1),137 error.AnalysisFail => std.process.exit(1),
135 else => return err,138 else => return err,
136 };139 };
...@@ -138,7 +141,7 @@ pub fn main() !void {...@@ -138,7 +141,7 @@ pub fn main() !void {
138 const output = std.io.getStdOut().writer();141 const output = std.io.getStdOut().writer();
139 var bufw = std.io.bufferedWriter(output);142 var bufw = std.io.bufferedWriter(output);
140 const writer = bufw.writer();143 const writer = bufw.writer();
141 try comp.link(writer);144 try js_backend.link(&comp, entry_point, writer.any());
142 try bufw.flush();145 try bufw.flush();
143146
144 std.process.cleanExit();147 std.process.cleanExit();
src/core/mod.zig+59-2
...@@ -1,18 +1,75 @@...@@ -1,18 +1,75 @@
1//! Core language compiler module.
2
3// sort-lines: start
4
5/// Analyzed Intermediate Representation.
1pub const Air = @import("Air.zig");6pub const Air = @import("Air.zig");
7
2pub const array = @import("array.zig");8pub const array = @import("array.zig");
9
10/// Abstract Syntax Tree - Preserves all syntax forms.
11pub const Ast = @import("Ast.zig");
12
3pub const Cache = @import("Cache.zig");13pub const Cache = @import("Cache.zig");
14
15/// Language Compilation Unit
4pub const Comp = @import("Comp.zig");16pub const Comp = @import("Comp.zig");
17
18/// Storage for shared objects (`Value`/`Decl`/`Namespace`) in a compilation
5pub const InternPool = @import("InternPool.zig");19pub const InternPool = @import("InternPool.zig");
6pub const JsLink = @import("JsLink.zig");20
21/// JavaScript backend and related utilities
22pub const Js = @import("js/Js.zig");
23
24/// Recursive descent parser. Produces `Ast`
7pub const Parse = @import("Parse.zig");25pub const Parse = @import("Parse.zig");
26
8pub const Report = @import("Report.zig");27pub const Report = @import("Report.zig");
28
29/// Semantic Analysis visits a `Teg` declaration and produces `Air`
9pub const Sema = @import("Sema.zig");30pub const Sema = @import("Sema.zig");
31
32/// Untyped Intermediate Representation. Flat instruction list
10pub const Teg = @import("Teg.zig");33pub const Teg = @import("Teg.zig");
34
35/// Iterate tokens from a `[:0]const u8`. Includes code to decode/encode string escapes.
11pub const Tokenizer = @import("Tokenizer.zig");36pub const Tokenizer = @import("Tokenizer.zig");
37
38/// Transform pass converts `Ast` into `Teg`
12pub const Transform = @import("Transform.zig");39pub const Transform = @import("Transform.zig");
40
13pub const unicode_props = @import("unicode_props.zig");41pub const unicode_props = @import("unicode_props.zig");
42
14pub const util = @import("util.zig");43pub const util = @import("util.zig");
1544
45// sort-lines: end
46
47// The rest of this file is utility functions, which are not specific to any of
48// the above namespaces. Remember Zig discourages namespaces like `utils`
49
50/// This is an alternative to `std.debug.assert`, where you want to print some
51/// debug info instead of "reached unreachable code". Usage is as follows:
52///
53/// if (assertFailed(variantIsTrue())) {
54/// std.debug.panic("...", .{ ... });
55/// }
56///
57/// This allows you to perform any number of side effects in the failure case
58/// with certainty that they are not analyzed in unsafe modes.
59pub inline fn assertFailed(cond: bool) bool {
60 if (std.debug.runtime_safety) {
61 return !cond;
62 } else if (!cond) {
63 unreachable; // assertion failure
64 }
65}
66
67pub const detectTTYFromWriter = util.detectTTYFromWriter;
68pub const Ordinal = util.Ordinal;
69
70const std = @import("std");
71const assert = std.debug.assert;
72
16comptime {73comptime {
17 @import("std").testing.refAllDecls(@This());74 std.testing.refAllDecls(@This());
18}75}
src/core/util.zig+2-9
...@@ -1,3 +1,5 @@...@@ -1,3 +1,5 @@
1// TODO: move all decls in this file into mod.zig
2
1pub fn detectTTYFromWriter(writer: anytype) std.io.tty.Config {3pub fn detectTTYFromWriter(writer: anytype) std.io.tty.Config {
2 if (@hasField(@TypeOf(writer), "unbuffered_writer")) {4 if (@hasField(@TypeOf(writer), "unbuffered_writer")) {
3 return detectTTYFromWriter(writer.unbuffered_writer);5 return detectTTYFromWriter(writer.unbuffered_writer);
...@@ -40,15 +42,6 @@ pub fn Ordinal(Int: type) type {...@@ -40,15 +42,6 @@ pub fn Ordinal(Int: type) type {
40 pub fn add(ord: @This(), inc: Int) @This() {42 pub fn add(ord: @This(), inc: Int) @This() {
41 return fromZeroBased(ord.zeroBased() + inc);43 return fromZeroBased(ord.zeroBased() + inc);
42 }44 }
43
44 pub fn format(
45 _: @This(),
46 comptime _: []const u8,
47 _: std.fmt.FormatOptions,
48 _: anytype,
49 ) !void {
50 // @compileError("Do not format `Ordinal` directly. Call oneBased or zeroBased to specify.");
51 }
52 };45 };
53}46}
5447