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 @@
11## 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.
36- fix(sema): miscompilation using left side of all binary expression for both sides
47- fix(sema): handle recursive function calls
58- fix(sema): quit block analysis when hitting `noreturn`
9- fix(sema): binary type resolution with opaque and numeric literals no longer crash
610
711## 0.1.3
812
README.md+8-3
......@@ -1,15 +1,20 @@
11# 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
79## 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
1115 zig build --release
1216
1317To run the standard packaging procedure, which builds the SDK for all officially supported targets:
1418
1519 zig build package
20
build.zig+18-6
......@@ -34,8 +34,14 @@ pub fn build(b: *std.Build) void {
3434 "Enable tracing",
3535 ) orelse .none;
3636
37 const native_debug = buildWithTarget(b, native_target, .Debug, .none);
38 const main = buildWithTarget(b, target, optimize, tracing);
37 const no_llvm = b.option(
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
4046 b.installArtifact(main.compiler);
4147
......@@ -46,7 +52,6 @@ pub fn build(b: *std.Build) void {
4652 const artifact, const name = entry;
4753
4854 const run_cmd = b.addRunArtifact(artifact);
49 run_cmd.step.dependOn(b.getInstallStep());
5055 if (b.args) |args| {
5156 run_cmd.addArgs(args);
5257 }
......@@ -128,7 +133,7 @@ pub fn build(b: *std.Build) void {
128133 },
129134 ) 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
133138 inline for (.{
134139 .{ artifacts.compiler, "compiler" },
......@@ -168,8 +173,11 @@ fn buildWithTarget(
168173 target: Build.ResolvedTarget,
169174 optimize: std.builtin.OptimizeMode,
170175 tracing: TracingMode,
176 no_llvm: bool,
171177) Artifacts {
172178 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
174182 // Dependencies
175183 const zg = b.dependency("zg", dep_opts);
......@@ -195,6 +203,8 @@ fn buildWithTarget(
195203 .root_source_file = b.path("src/core/mod.zig"),
196204 .target = target,
197205 .optimize = optimize,
206 .use_lld = use_lld,
207 .use_llvm = use_llvm,
198208 });
199209 core_tests.root_module.addImport("build_options", opts_mod);
200210 core_tests.root_module.addImport("tracer", tracer.module("tracer"));
......@@ -206,7 +216,8 @@ fn buildWithTarget(
206216 .root_source_file = b.path("src/core/main.zig"),
207217 .target = target,
208218 .optimize = optimize,
209 .link_libc = true,
219 .use_lld = use_lld,
220 .use_llvm = use_llvm,
210221 });
211222 compiler_exe.root_module.addImport("build_options", opts_mod);
212223 compiler_exe.root_module.addImport("core", core);
......@@ -218,7 +229,8 @@ fn buildWithTarget(
218229 .root_source_file = b.path("src/lsp/lsp.zig"),
219230 .target = target,
220231 .optimize = optimize,
221 .link_libc = true,
232 .use_lld = use_lld,
233 .use_llvm = use_llvm,
222234 });
223235 lsp_exe.root_module.addImport("build_options", opts_mod);
224236 lsp_exe.root_module.addImport("core", core);
doc/architecture.md+10-4
......@@ -1,7 +1,13 @@
1# compiler architecture
1# Architecture
22
33This 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]
6- [**Standard Library**][std]
7- [**Language Server**][lsp]
5- [**Core**][core] (`src/core`)
6- [**SDK Manual Generator**][manual] (`src/manual`)
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 {
158158
159159print(currentYear());
160160```
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 {
116116 try s.dumpValue(w, memory.ty.value());
117117 try w.writeAll(")");
118118 },
119 .field => {
120 const field: Air.Field = air.instData(.field, inst);
119 .field_opaque => {
120 const field: Air.FieldOpaque = air.instData(.field_opaque, inst);
121121 try w.writeAll("(");
122122 try s.dumpRef(w, field.base);
123123 try w.writeAll(", ");
......@@ -126,6 +126,22 @@ pub const State = struct {
126126 try tty.setColor(w, .reset);
127127 try w.writeAll(")");
128128 },
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 },
129145 inline .call, .new => |t| {
130146 const call: Air.Call = air.instData(t, inst);
131147 const call_data = call.call_data.get(air);
......@@ -152,6 +168,23 @@ pub const State = struct {
152168 try s.newline(w);
153169 try w.writeAll("}");
154170 },
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 },
155188 }
156189 }
157190
......@@ -188,14 +221,14 @@ pub const State = struct {
188221 try w.print("{s}", .{@tagName(t)});
189222 try tty.setColor(w, .reset);
190223 },
191 .type_function => |ty| {
224 .function_type => |ty| {
192225 try tty.setColor(w, .blue);
193226 try w.writeAll("fn(");
194227 try tty.setColor(w, .reset);
195228 for (ty.param_types, 0..) |param, i| {
196229 if (i != 0) {
197230 try tty.setColor(w, .blue);
198 try w.writeAll(",");
231 try w.writeAll(", ");
199232 try tty.setColor(w, .reset);
200233 }
201234 try s.dumpValue(w, param.value());
......@@ -245,6 +278,65 @@ pub const State = struct {
245278 try w.writeAll(">");
246279 try tty.setColor(w, .reset);
247280 },
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 },
248340 }
249341 }
250342
src/core/Air.zig+101-24
......@@ -20,11 +20,15 @@ pub const Inst = struct {
2020 @"break",
2121 @"if",
2222 arg,
23 field,
2423
2524 memory,
2625 store,
2726
27 struct_init,
28
29 field_opaque,
30 field_struct,
31
2832 // bin
2933 add,
3034 sub,
......@@ -48,9 +52,11 @@ pub const Inst = struct {
4852 .@"if" => If,
4953 .call, .new => Call,
5054 .arg => Arg,
51 .field => Field,
5255 .memory => Memory,
5356 .store => Store,
57 .struct_init => StructInit,
58 .field_opaque => FieldOpaque,
59 .field_struct => FieldStruct,
5460 .add,
5561 .sub,
5662 .mul,
......@@ -83,14 +89,26 @@ pub const Inst = struct {
8389 };
8490 }
8591
86 pub fn toOptional(ref: ?Ref) Optional {
87 return if (ref) |r| @enumFromInt(@as(u32, @bitCast(r))) else .none;
92 pub fn value(ref: Ref) ?Value {
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)));
88102 }
89103
90104 pub const Optional = enum(u32) {
91105 none = std.math.maxInt(u32),
92106 _,
93107
108 pub fn init(ref: ?Ref) Optional {
109 return if (ref) |r| @enumFromInt(@as(u32, @bitCast(r))) else .none;
110 }
111
94112 pub fn unwrap(opt: Optional) ?Ref {
95113 return if (opt != .none) @bitCast(@intFromEnum(opt)) else null;
96114 }
......@@ -104,6 +122,24 @@ pub const Inst = struct {
104122 pub fn eql(ref: Ref, other: Ref) bool {
105123 return @as(u32, @bitCast(ref)) == @as(u32, @bitCast(other));
106124 }
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 }
107143 };
108144
109145 /// reference to another instruction in this air
......@@ -189,7 +225,7 @@ pub const Un = struct {
189225 unused: Unused = .zero,
190226};
191227
192pub const Field = struct {
228pub const FieldOpaque = struct {
193229 base: Inst.Ref,
194230 field: InternPool.NullTerminatedString,
195231};
......@@ -224,6 +260,23 @@ pub const Memory = struct {
224260 ty: Type,
225261};
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
227280pub fn deinit(air: *Air, gpa: std.mem.Allocator) void {
228281 defer air.* = undefined;
229282 air.insts.deinit(gpa);
......@@ -250,46 +303,70 @@ pub fn instPayload(air: *const Air, comptime tag: Inst.Tag, index: Inst.Index) t
250303 return air.instData(tag, index).extra_index.get(air);
251304}
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 {
254307 return switch (ref.decode()) {
255308 .value => |v| v.typeOf(ip),
256309 .index => |i| air.typeOfIndex(i, ip),
257310 };
258311}
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 {
261314 return switch (air.instTag(index)) {
315 // These values are by definition, opaque
262316 .js_global_ref, .new => .@"opaque",
263317
264 .block => {
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
318 // These operations always produce known types
271319 .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,
273329 .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
275345 .negate => air.typeOf(air.instData(.negate, index).operand, ip),
276346
277 .@"break" => .noreturn,
278
279 // todo:
280 .field => .@"opaque",
347 // TODO: taking the last instruction is likely wrong.
348 // The last instruction is going to be a .break and be of type .noreturn
349 .block => {
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.
282356 .call => {
283357 const base_type = air.typeOf(air.instData(.call, index).base, ip);
284358 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;
286360 },
287361
288 .memory => air.instData(.memory, index).ty,
289
290 .store => air.instData(.memory, air.instData(.store, index).memory).ty,
291
292 .@"if" => air.instData(.@"if", index).data.get(air).union_ty,
362 // Field accesses on structs are special-cased by storing the field
363 // index instead of the name string. This avoids creating hashmaps.
364 .field_struct => {
365 const inst = air.instData(.field_struct, index);
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 },
293370 };
294371}
295372
src/core/Ast.Dump.zig+56-26
......@@ -81,33 +81,41 @@ fn dumpNode(writer: anytype, state: *State, node: NodeIndex) @TypeOf(writer).Err
8181 }
8282 try tty.setColor(writer, .reset);
8383 },
84 .fn_decl => {
85 try writer.writeAll(" ");
86 const fn_decl = state.ast.fullFnDecl(node);
87 if (fn_decl.public) {
88 try writer.writeAll("[pub] ");
89 }
90 try tty.setColor(writer, .magenta);
91 try writer.writeAll(fn_decl.id);
92 try tty.setColor(writer, .reset);
93 try writer.writeAll(" {");
94 {
95 state.indent += 1;
96 defer state.indent -= 1;
97 try state.newline(writer);
98 try writer.writeAll("params:");
99 try dumpNodeList(writer, state, fn_decl.params, '[', ']');
100 if (fn_decl.return_type) |return_type| {
84 .function => switch (tag) {
85 else => unreachable,
86 inline .fn_decl, .fn_expr => |comptime_tag| {
87 const fn_decl = if (comptime_tag == .fn_decl)
88 state.ast.fullFnDecl(node)
89 else
90 state.ast.fullFnExpr(node);
91 if (comptime_tag == .fn_decl) {
92 try writer.writeAll(" ");
93 if (fn_decl.public) {
94 try writer.writeAll("[pub] ");
95 }
96 try tty.setColor(writer, .magenta);
97 try writer.writeAll(fn_decl.id);
98 try tty.setColor(writer, .reset);
99 }
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 }
101112 try state.newline(writer);
102 try writer.writeAll("return_type: ");
103 try dumpNode(writer, state, return_type);
113 try writer.writeAll("body: ");
114 try dumpNode(writer, state, fn_decl.body);
104115 }
105116 try state.newline(writer);
106 try writer.writeAll("body: ");
107 try dumpNode(writer, state, fn_decl.body);
108 }
109 try state.newline(writer);
110 try writer.writeAll("}");
117 try writer.writeAll("}");
118 },
111119 },
112120 .list => {
113121 const list = data.list.slice(state.ast);
......@@ -115,6 +123,7 @@ fn dumpNode(writer: anytype, state: *State, node: NodeIndex) @TypeOf(writer).Err
115123 .root,
116124 .block_void,
117125 .block_implicit_return,
126 .struct_decl,
118127 => try dumpNodeList(writer, state, list, '{', '}'),
119128 .builtin_call,
120129 => try dumpNodeList(writer, state, list, '(', ')'),
......@@ -155,8 +164,10 @@ fn dumpNode(writer: anytype, state: *State, node: NodeIndex) @TypeOf(writer).Err
155164 try tty.setColor(writer, .magenta);
156165 try writer.writeAll(param_decl.id);
157166 try tty.setColor(writer, .reset);
158 try writer.writeAll(": ");
159 try dumpNode(writer, state, param_decl.type);
167 if (param_decl.type) |ty| {
168 try writer.writeAll(": ");
169 try dumpNode(writer, state, ty);
170 }
160171 },
161172 .bin => {
162173 try writer.writeAll(" (");
......@@ -244,6 +255,25 @@ fn dumpNode(writer: anytype, state: *State, node: NodeIndex) @TypeOf(writer).Err
244255 try dumpNodeList(writer, state, arg_list, '[', ']');
245256 try writer.writeAll(")");
246257 },
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 },
247277 .@"if" => {
248278 const branch = data.@"if";
249279 const if_data = state.ast.extraData(branch.extra, Ast.If);
src/core/Ast.zig+169-55
......@@ -75,6 +75,10 @@ pub const Node = struct {
7575 /// `fn foo(x: i32) -> i32 { rhs }`
7676 /// Params and return type are stored in extra data
7777 fn_decl,
78 /// Function expression
79 /// `fn (x, y) { x + y }`
80 /// Params and return type are stored in extra data
81 fn_expr,
7882 /// 'name: type' declaration
7983 /// Main token is the identifier
8084 param_decl,
......@@ -208,6 +212,12 @@ pub const Node = struct {
208212 pure,
209213 /// `assert expr`. main_token is the operator.
210214 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
212222 pub fn isAssignBin(op: Tag) bool {
213223 return switch (op) {
......@@ -264,71 +274,77 @@ pub const Node = struct {
264274 };
265275
266276 pub const data_tags = std.enums.directEnumArray(Tag, Data.FieldEnum, 0, .{
267 .root = .list,
268 .block_void = .list,
269 .block_implicit_return = .list,
270 .var_decl = .var_decl,
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,
277 // sort-lines:start
278 .@"and" = .bin,
279 .@"break" = .@"break",
280 .@"comptime" = .un,
280281 .@"if" = .@"if",
281 .field = .field,
282 .index = .bin,
282 .@"or" = .bin,
283283 .@"return" = .@"return",
284 .@"break" = .@"break",
285 .group = .un,
286284 .add = .bin,
287 .sub = .bin,
288 .mul = .bin,
289 .div = .bin,
290 .rem = .bin,
291 .pow = .bin,
292 .@"and" = .bin,
293 .@"or" = .bin,
285 .add_assign = .bin,
286 .and_assign = .bin,
287 .assert = .un,
288 .assign = .bin,
294289 .bit_and = .bin,
290 .bit_and_assign = .bin,
291 .bit_not = .un,
295292 .bit_or = .bin,
293 .bit_or_assign = .bin,
296294 .bit_xor = .bin,
297 .shl = .bin,
298 .shr = .bin,
295 .bit_xor_assign = .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,
299303 .eq = .bin,
300 .ne = .bin,
301 .lt = .bin,
302 .le = .bin,
303 .gt = .bin,
304 .field = .field,
305 .fn_decl = .function,
306 .fn_expr = .function,
304307 .ge = .bin,
305 .assign = .bin,
306 .add_assign = .bin,
307 .sub_assign = .bin,
308 .group = .un,
309 .gt = .bin,
310 .id = .none,
311 .index = .bin,
312 .label = .label,
313 .le = .bin,
314 .lt = .bin,
315 .mul = .bin,
308316 .mul_assign = .bin,
309 .div_assign = .bin,
310 .rem_assign = .bin,
311 .pow_assign = .bin,
312 .and_assign = .bin,
317 .multiline_string = .list,
318 .ne = .bin,
319 .negate = .un,
320 .number_float = .none,
321 .number_int = .none,
313322 .or_assign = .bin,
314 .bit_and_assign = .bin,
315 .bit_or_assign = .bin,
316 .bit_xor_assign = .bin,
323 .param_decl = .param_decl,
324 .pow = .bin,
325 .pow_assign = .bin,
326 .pure = .un,
327 .rem = .bin,
328 .rem_assign = .bin,
329 .root = .list,
330 .shl = .bin,
317331 .shl_assign = .bin,
332 .shr = .bin,
318333 .shr_assign = .bin,
319 .bool_not = .un,
320 .negate = .un,
321 .bit_not = .un,
322 .@"comptime" = .un,
323 .pure = .un,
324 .assert = .un,
325 .label = .label,
334 .string = .none,
335 .struct_decl = .list,
336 .struct_field = .un,
337 .struct_init = .struct_init,
338 .sub = .bin,
339 .sub_assign = .bin,
340 .var_decl = .var_decl,
341 // sort-lines:end
326342 });
327343
328344 pub const Data = union {
329345 none: struct { Unused = .zero, Unused = .zero },
330 fn_decl: struct {
331 // A `FnDecl`
346 function: struct {
347 /// A `Fn`
332348 extra: ExtraIndex,
333349 body: NodeIndex,
334350 },
......@@ -339,7 +355,7 @@ pub const Node = struct {
339355 init: NodeIndex,
340356 },
341357 param_decl: struct {
342 type: NodeIndex,
358 type: OptionalNodeIndex,
343359 unused: Unused = .zero,
344360 },
345361 bin: struct {
......@@ -371,6 +387,13 @@ pub const Node = struct {
371387 /// A `Call`
372388 extra: ExtraIndex,
373389 },
390 struct_init: struct {
391 /// .none => '.{ }'
392 /// else => 'base{ ... }'
393 base: OptionalNodeIndex,
394 /// A `SubList`
395 extra: ExtraIndex,
396 },
374397 @"if": struct {
375398 cond: NodeIndex,
376399 /// A `If`
......@@ -457,12 +480,14 @@ pub const SubList = struct {
457480 start: u32,
458481 len: u32,
459482
483 pub const empty: SubList = .{ .len = 0, .start = 0 };
484
460485 pub fn slice(self: SubList, ast: *const Ast) []const u32 {
461486 return ast.extra[self.start..][0..self.len];
462487 }
463488};
464489
465pub const FnDecl = struct {
490pub const Fn = struct {
466491 params: SubList,
467492 return_type: OptionalNodeIndex,
468493};
......@@ -489,6 +514,13 @@ pub const full = struct {
489514 body: NodeIndex,
490515 };
491516
517 pub const FnExpr = struct {
518 start_token: TokenIndex,
519 params: []const NodeIndex,
520 return_type: ?NodeIndex,
521 body: NodeIndex,
522 };
523
492524 pub const VarDecl = struct {
493525 start_token: TokenIndex,
494526 public: bool,
......@@ -498,11 +530,29 @@ pub const full = struct {
498530 init: NodeIndex,
499531 };
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 {
502541 start_token: TokenIndex,
503542 id: []const u8,
504543 type: NodeIndex,
505544 };
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 };
506556};
507557
508558pub fn deinit(ast: *Ast, gpa: std.mem.Allocator) void {
......@@ -531,10 +581,32 @@ pub fn fullFnDecl(ast: Ast, node: NodeIndex) full.FnDecl {
531581 const public = tok_tags[main_token -| 1] == .kw_pub;
532582 assert(tok_tags[main_token] == .kw_fn);
533583 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);
534606 const id = ast.tokenSlice(main_token + 1);
535607
536 const data = ast.nodes.items(.data)[node].fn_decl;
537 const extra = ast.extraData(data.extra, FnDecl);
608 const data = ast.nodes.items(.data)[node].function;
609 const extra = ast.extraData(data.extra, Fn);
538610
539611 return .{
540612 .start_token = if (public) main_token - 1 else main_token,
......@@ -546,6 +618,25 @@ pub fn fullFnDecl(ast: Ast, node: NodeIndex) full.FnDecl {
546618 };
547619}
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
549640pub fn fullVarDecl(ast: Ast, node: NodeIndex) full.VarDecl {
550641 const tok_tags = ast.tokens.items(.tag);
551642
......@@ -584,7 +675,30 @@ pub fn fullParamDecl(ast: Ast, node: NodeIndex) full.ParamDecl {
584675 return .{
585676 .start_token = main_token,
586677 .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),
588702 };
589703}
590704
src/core/Comp.zig+156-95
......@@ -1,4 +1,7 @@
1//! Compilation
1//! 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
36/// General purpose allocator
47gpa: Allocator,
......@@ -7,13 +10,9 @@ intern_pool: InternPool = .{},
710/// All files in the compilation
811files: std.StringArrayHashMapUnmanaged(*File) = .{},
912
10/// # of sema instructions in this compilation cycle
11analyzed_instructions: usize = 0,
12
1313options: Options = .{},
1414
15// TODO: consider computing this dynamically per-link
16current_compile_decls: std.ArrayListUnmanaged(Decl.Index) = .{},
15backend: Backend,
1716
1817pub fn init(comp: *Comp) !void {
1918 try comp.intern_pool.init(comp.gpa);
......@@ -25,9 +24,47 @@ pub fn deinit(comp: *Comp) void {
2524 file.deinit(comp.gpa);
2625 }
2726 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;
2965}
3066
67/// Add or retrieve a file to the compilation via it's file path.
3168pub fn getFile(comp: *Comp, absolute_file_path: []const u8) !*File {
3269 assert(std.fs.path.isAbsolute(absolute_file_path));
3370 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 {
3875 file.* = .{
3976 .file_path = absolute_file_path,
4077 .path_digest = null,
41 .root_module = .none,
42 .status = .never_loader,
78 .root_namespace = .none,
79 .status = .never_loaded,
4380 .source = null,
4481 .ast = null,
4582 .teg = null,
......@@ -53,13 +90,15 @@ pub const Source = struct {
5390 stat: Stat,
5491};
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`.
5695pub const File = struct {
5796 /// TODO: change this to a URL
5897 file_path: []const u8,
5998
60 root_module: Decl.Index.Optional,
99 root_namespace: Decl.Index.Optional,
61100 status: enum {
62 never_loader,
101 never_loaded,
63102 /// A failure occurred while loading the file. Retrying might succeed.
64103 retryable_failure,
65104 /// Errors during parse
......@@ -110,7 +149,7 @@ pub const File = struct {
110149 // used for error reporting. We need to keep the stat fields stale so that
111150 // transformFile can know to regenerate Teg.
112151
113 const loaded_source = Source{
152 const loaded_source: Source = .{
114153 .code = source,
115154 .stat = .{
116155 .size = stat.size,
......@@ -212,24 +251,34 @@ pub const File = struct {
212251 }
213252};
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.
215260pub const Decl = struct {
216261 name: InternPool.NullTerminatedString,
217 val: ?InternPool.Value,
262
263 /// The Value that this declaration evaluates to.
264 raw_value: ?InternPool.Value,
265
218266 /// The direct parent namespace of the Decl. In the case of the Decl
219267 /// corresponding to a file, this is the namespace of the struct, since
220268 /// there is no parent.
221269 src_namespace: Namespace.Index,
222270
223 /// The AST node index of this declaration.
224 /// Must be recomputed when the corresponding source file is modified.
271 // /// The AST node index of this declaration.
272 // /// Must be recomputed when the corresponding source file is modified.
225273 // src_node: Ast.NodeIndex,
226 /// Line number corresponding to `src_node`. Stored separately so that source files
227 /// do not need to be loaded into memory in order to compute debug line numbers.
228 /// This value is absolute.
274
275 // /// Line number corresponding to `src_node`. Stored separately so that source files
276 // /// do not need to be loaded into memory in order to compute debug line numbers.
277 // /// This value is absolute.
229278 // src_line: u32,
230 /// Index of the ZIR `declaration` instruction from which this `Decl` was created.
231 /// For the root `Decl` of a `File` and legacy anonymous decls, this is `.none`.
232 teg_decl_index: InternPool.TrackedInst.Index.Optional,
279
280 /// Index of the Teg `declaration` instruction from which this `Decl` was created.
281 teg_decl_index: InternPool.TrackedInst.Index,
233282
234283 /// Represents the "shallow" analysis status. For example, for decls that are functions,
235284 /// the function type is analyzed with this set to `in_progress`; however, the semantic
......@@ -242,20 +291,21 @@ pub const Decl = struct {
242291 /// Semantic analysis for this Decl is running right now.
243292 /// This state detects dependency loops.
244293 in_progress,
245 /// Semantic analysis for this Decl is running right now
246 /// But .value is valid
247 in_progress_prototype_available,
294 /// Semantic analysis for this Decl is running right now, and has
295 /// completed enough to reify the type of the result, but not the result
296 /// itself.
297 in_progress_type_available,
248298 /// The file corresponding to this Decl had a parse error or ZIR error.
249299 /// There will be a corresponding ErrorMsg in Zcu.failed_files.
250 file_failure,
300 // file_failure,
251301 /// This Decl might be OK but it depends on another one which did not
252302 /// successfully complete semantic analysis.
253 dependency_failure,
303 // dependency_failure,
254304 /// Semantic analysis failure.
255305 /// There will be a corresponding ErrorMsg in Zcu.failed_decls.
256 sema_failure,
306 // sema_failure,
257307 /// There will be a corresponding ErrorMsg in Zcu.failed_decls.
258 codegen_failure,
308 // codegen_failure,
259309 /// Semantic analysis and constant value codegen of this Decl has
260310 /// succeeded. However, the Decl may be outdated due to an in-progress
261311 /// update. Note that for a function, this does not mean codegen of the
......@@ -264,25 +314,35 @@ pub const Decl = struct {
264314 complete,
265315 },
266316
267 codegen: ?JsTree = null,
268
269 is_pub: bool,
317 pub fn value(decl: *const Decl) InternPool.Value {
318 assert(decl.analysis == .complete);
319 return decl.raw_value orelse unreachable;
320 }
270321
271 /// What kind of a declaration this is.
272 kind: Kind,
322 pub fn @"type"(decl: *const Decl, ip: *const InternPool) InternPool.Type {
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
274331 pub fn deinit(decl: *Decl, gpa: Allocator) void {
332 _ = gpa; // autofix
275333 defer decl.* = undefined;
276 if (decl.codegen) |*cg| cg.deinit(gpa);
334 // if (decl.codegen) |*cg| cg.deinit(gpa);
277335 }
278336
279 pub const Kind = enum {
280 named,
281 };
282
283337 pub const Index = InternPool.DeclIndex;
284338};
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.
286346pub const Namespace = struct {
287347 parent: Index.Optional,
288348 file_scope: *File,
......@@ -300,7 +360,12 @@ pub const Namespace = struct {
300360 return std.hash.uint32(@intFromEnum(decl.name));
301361 }
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 {
304369 _ = b_index;
305370 const a_decl = ctx.comp.intern_pool.declPtr(a_decl_index);
306371 const b_decl = ctx.comp.intern_pool.declPtr(b_decl_index);
......@@ -316,22 +381,30 @@ pub const Namespace = struct {
316381 return std.hash.uint32(@intFromEnum(s));
317382 }
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 {
320390 _ = b_index;
321391 return a == self.comp.intern_pool.declPtr(b_decl_index).name;
322392 }
323393 };
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 {
326400 return ns.decls.getKeyAdapted(name, Namespace.DeclNameAdapter{ .comp = comp });
327401 }
328402
329403 pub const Index = InternPool.NamespaceIndex;
330404};
331405
332/// Index a module in a Teg.
333/// When a File's Teg is loaded, we call this on the root module.
334/// submodules are analyzed lazily.
406/// Index a Teg's module scope as a namespace. This prepares `Decl` objects for
407/// every declaration, but does not begin analysis of them.
335408pub fn indexTegModule(comp: *Comp, file: *File, module_index: Teg.Inst.Index) !Namespace.Index {
336409 const teg = file.teg.?;
337410
......@@ -351,12 +424,10 @@ pub fn indexTegModule(comp: *Comp, file: *File, module_index: Teg.Inst.Index) !N
351424 const tracked_inst = try comp.intern_pool.trackInst(comp.gpa, file, decl_index);
352425 const decl = try comp.intern_pool.createDecl(comp.gpa, .{
353426 .name = name,
354 .val = null,
427 .raw_value = null,
355428 .src_namespace = ns,
356 .teg_decl_index = tracked_inst.toOptional(),
429 .teg_decl_index = tracked_inst,
357430 .analysis = .unreferenced,
358 .is_pub = false, // todo:
359 .kind = .named,
360431 });
361432 try ns_ptr.decls.putNoClobberContext(comp.gpa, decl, void{}, .{ .comp = comp });
362433 }
......@@ -364,42 +435,7 @@ pub fn indexTegModule(comp: *Comp, file: *File, module_index: Teg.Inst.Index) !N
364435 return ns;
365436}
366437
367pub fn semaEntryPoint(comp: *Comp, file: *File) !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 {
438pub fn codeGenDecl(c: *Comp, value: InternPool.Value, air: *const Air) !void {
403439 const t = tracer.trace(@src());
404440 defer t.end();
405441
......@@ -407,20 +443,48 @@ pub fn codeGenDecl(c: *Comp, decl: *Decl, air: *const Air) !void {
407443 defer arena_state.deinit();
408444 const arena = arena_state.allocator();
409445
410 var cg = try JsNodeGen.init(c.gpa, arena, &c.intern_pool, air);
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);
446 _ = try c.backend.handleFunctionAir(c, value, air, arena);
418447}
419448
420449const Options = struct {
421450 dump_ir: bool = false,
422451};
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
424488const Comp = @This();
425489
426490const std = @import("std");
......@@ -437,9 +501,6 @@ const Teg = @import("Teg.zig");
437501const Cache = @import("Cache.zig");
438502const Sema = @import("Sema.zig");
439503const Transform = @import("Transform.zig");
440const JsNodeGen = @import("JsNodeGen.zig");
441const JsTree = @import("JsTree.zig");
442const JsLink = @import("JsLink.zig");
443504const Report = @import("Report.zig");
444505const Parse = @import("Parse.zig");
445506
src/core/InternPool.zig+241-102
......@@ -1,8 +1,21 @@
11//! Large parts of this file are based on the Zig compiler's InternPool.
22//! https://github.com/ziglang/zig/blob/0.12.0/src/InternPool.zig
3
4/// Maps `Key` to `Index`. `Key` objects are not stored anywhere; they are
5/// constructed lazily.
3//!
4//! All interned objects have both a Value and a Type. Type is a subset of
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`.
619map: std.AutoArrayHashMapUnmanaged(void, void) = .{},
720items: std.MultiArrayList(Item) = .{},
821extra: ExtraStore = .{},
......@@ -45,29 +58,6 @@ allocated_namespaces: std.SegmentedList(Comp.Namespace, 0) = .{},
4558/// Same pattern as with `decls_free_list`.
4659namespaces_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
7161pub const Tag = enum(u8) {
7262 /// This special tag represents a value that was removed from this pool via
7363 /// `InternPool.remove`. The item remains allocated to preserve indices, but
......@@ -76,10 +66,10 @@ pub const Tag = enum(u8) {
7666 removed,
7767
7868 /// A function type
79 /// data is an extra index into a `FuncType`
80 type_function,
69 function_type,
70 /// A struct type
71 struct_type,
8172 /// A type that can be represented with only an enum tag.
82 /// data is SimpleType enum value.
8373 simple_type,
8474
8575 // --- before this are types ---
......@@ -90,21 +80,18 @@ pub const Tag = enum(u8) {
9080 simple_value,
9181 /// A numeric literal that can be represented as a float.
9282 /// Integers greater than 2^53 known at compile time are currently not supported.
93 /// data is an extra index into a `Float`
9483 comptime_float,
9584 /// An int32 value
96 /// data is i32
9785 int32,
9886 /// A float value
99 /// data is an extra index into a `Float`
10087 float,
10188 /// A function
102 /// data is an extra index into a `FuncDecl`
10389 func,
10490 /// A string
105 /// data is an extra index into a `String`
10691 string,
10792
93 struct_value,
94
10895 pub fn Data(comptime tag: Tag) type {
10996 return switch (tag) {
11097 .removed,
......@@ -128,8 +115,10 @@ pub const Tag = enum(u8) {
128115 => unreachable, // does not have a payload
129116 .float, .comptime_float => Float,
130117 .func => FuncDecl,
131 .type_function => TypeFunction,
118 .function_type => TypeFunction,
132119 .string => String.WithLen,
120 .struct_value => StructValue,
121 .struct_type => TypeStruct,
133122 };
134123 }
135124
......@@ -147,8 +136,23 @@ pub const Tag = enum(u8) {
147136 params_len: u32,
148137 return_type: Type,
149138 };
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 };
150153};
151154
155/// This is an index into .items
152156pub const Value = enum(u32) {
153157 pub const first_type: Value = .opaque_type;
154158 pub const last_type: Value = .noreturn_type;
......@@ -176,25 +180,26 @@ pub const Value = enum(u32) {
176180 none = std.math.maxInt(u32),
177181 _,
178182
179 /// An array of `Index` existing within the `extra` array.
183 /// An array of `Value` existing within the `extra` array.
180184 /// This type exists to provide a struct with lifetime that is
181185 /// not invalidated when items are added to the `InternPool`.
186 /// TODO: Replace with ExtraStore.Slice(Value)
182187 pub const Slice = struct {
183188 start: u32,
184189 len: u32,
185190
186 pub fn get(slice: Slice, ip: *const InternPool) []Value {
191 pub fn get(slice: Slice, ip: *const InternPool) []const Value {
187192 return @ptrCast(ip.extra.list.items[slice.start..][0..slice.len]);
188193 }
189194 };
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.
192197 const Adapter = struct {
193 indexes: []const Value,
198 values: []const Value,
194199
195200 pub fn eql(ctx: @This(), a: Value, b_void: void, b_map_index: usize) bool {
196201 _ = b_void;
197 return a == ctx.indexes[b_map_index];
202 return a == ctx.values[b_map_index];
198203 }
199204
200205 pub fn hash(ctx: @This(), a: Value) u32 {
......@@ -216,7 +221,7 @@ pub const Value = enum(u32) {
216221 }
217222
218223 pub fn toKey(value: Value, ip: *const InternPool) Key {
219 return ip.indexToKey(value);
224 return ip.valueToKey(value);
220225 }
221226
222227 pub fn dump(value: Value, ip: *const InternPool) std.fmt.Formatter(Value.formatValue) {
......@@ -240,8 +245,9 @@ pub const Value = enum(u32) {
240245 return switch (value.valueTag(ip)) {
241246 .removed => unreachable, // UAF
242247
243 .type_function => .type,
248 .function_type => .type,
244249 .simple_type => .type,
250 .struct_type => .type,
245251
246252 .simple_value => switch (value.valueData(ip, .simple_value)) {
247253 .true => .bool,
......@@ -254,6 +260,7 @@ pub const Value = enum(u32) {
254260 .float => .float,
255261 .string => .string,
256262 .func => value.toKey(ip).func.ty,
263 .struct_value => value.toKey(ip).struct_value.ty,
257264 };
258265 }
259266
......@@ -263,7 +270,8 @@ pub const Value = enum(u32) {
263270 return switch (value.valueTag(ip)) {
264271 .removed => unreachable, // UAF
265272
266 .type_function,
273 .function_type,
274 .struct_type,
267275 .simple_type,
268276 => @enumFromInt(@intFromEnum(value)),
269277
......@@ -276,7 +284,8 @@ pub const Value = enum(u32) {
276284
277285 pub fn toTypeUnsafe(value: Value, proof_ip: *const InternPool) Type {
278286 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)});
280289 }
281290 return @enumFromInt(@intFromEnum(value));
282291 }
......@@ -301,6 +310,8 @@ pub const Value = enum(u32) {
301310 }
302311};
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
304315pub const Type = enum(u32) {
305316 @"opaque" = @intFromEnum(Value.opaque_type),
306317 bool = @intFromEnum(Value.bool_type),
......@@ -315,7 +326,8 @@ pub const Type = enum(u32) {
315326
316327 // Restricted version of `InternPool.Tag`
317328 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),
319331 simple_type = @intFromEnum(InternPool.Tag.simple_type),
320332 };
321333
......@@ -336,12 +348,18 @@ pub const Type = enum(u32) {
336348 }
337349
338350 pub fn tag(ty: Type, ip: *const InternPool) Type.Tag {
351 if (ty == .void) return .simple_type;
339352 return @enumFromInt(@intFromEnum(ty.value().valueTag(ip)));
340353 }
341354
342 pub fn toFunctionPrototype(ty: Type, ip: *const InternPool) ?Key.TypeFunction {
343 if (ty.tag(ip) != .type_function) return null;
344 return ip.indexToKey(ty.value()).type_function;
355 pub fn toFunctionPrototype(ty: Type, ip: *const InternPool) ?Key.FunctionType {
356 if (ty.tag(ip) != .function_type) return null;
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;
345363 }
346364
347365 pub fn hasRuntimeRepresentation(ty: Type, ip: *const InternPool) bool {
......@@ -359,7 +377,8 @@ pub const Type = enum(u32) {
359377 => false,
360378 else => switch (ty.tag(ip)) {
361379 .simple_type => unreachable, // all simple types are already printed
362 .type_function => true,
380 .function_type => true,
381 .struct_type => true,
363382 },
364383 };
365384 }
......@@ -396,20 +415,46 @@ pub const Type = enum(u32) {
396415
397416 switch (ty.tag(ip)) {
398417 .simple_type => unreachable, // all simple types are already printed
399 .type_function => {
400 const type_function = ty.toFunctionPrototype(ip).?;
418 .function_type => {
419 const function_type = ty.toFunctionPrototype(ip).?;
401420 try writer.writeAll("fn(");
402 for (type_function.param_types, 0..) |param, i| {
421 for (function_type.param_types, 0..) |param, i| {
403422 if (i != 0) try writer.writeAll(", ");
404423 try writer.print("{}", .{param.displayName(ip)});
405424 }
406425 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]");
408430 },
409431 }
410432 }
411433};
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
413458pub const static_keys = [_]Key{
414459 SimpleType.@"opaque".key(),
415460 SimpleType.bool.key(),
......@@ -434,29 +479,27 @@ pub const static_keys = [_]Key{
434479pub const FuncAnalysis = packed struct(u32) {
435480 state: State,
436481
437 _: u24 = 0,
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.
482 pub const State = enum(u32) {
442483 none,
443 /// Analysis for this function has been queued, but not yet completed.
444 queued,
445 /// This function intentionally only has ZIR generated because it is marked
446 /// inline, which means no runtime version of the function will be generated.
447 inline_only,
448 in_progress,
449 /// There will be a corresponding ErrorMsg in Module.failed_decls
450 sema_failure,
451 /// This function might be OK but it depends on another Decl which did not
452 /// successfully complete semantic analysis.
453 dependency_failure,
454 /// There will be a corresponding ErrorMsg in Module.failed_decls.
455 /// Indicates that semantic analysis succeeded, but code generation for
456 /// this function failed.
457 codegen_failure,
458 /// Semantic analysis and code generation of this function succeeded.
459 success,
484 // /// This function has not yet undergone analysis, because we have not
485 // /// seen a potential runtime call. It may be analyzed in future. none,
486 // /// Analysis for this function has been queued, but not yet completed.
487 // queued,
488 // /// This function intentionally only has ZIR generated because it is marked
489 // /// inline, which means no runtime version of the function will be generated.
490 // inline_only,
491 // in_progress,
492 // /// There will be a corresponding ErrorMsg in Module.failed_decls
493 // sema_failure,
494 // /// This function might be OK but it depends on another Decl which did not
495 // /// successfully complete semantic analysis.
496 // dependency_failure,
497 // /// There will be a corresponding ErrorMsg in Module.failed_decls.
498 // /// Indicates that semantic analysis succeeded, but code generation for
499 // /// this function failed.
500 // codegen_failure,
501 // /// Semantic analysis and code generation of this function succeeded.
502 // success,
460503 };
461504};
462505
......@@ -557,6 +600,7 @@ pub const NullTerminatedString = enum(u32) {
557600 string: NullTerminatedString,
558601 ip: *const InternPool,
559602 };
603
560604 fn format(
561605 data: FormatData,
562606 comptime specifier: []const u8,
......@@ -574,6 +618,15 @@ pub const NullTerminatedString = enum(u32) {
574618 pub fn fmt(self: NullTerminatedString, ip: *const InternPool) std.fmt.Formatter(format) {
575619 return .{ .data = .{ .string = self, .ip = ip } };
576620 }
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 };
577630};
578631
579632pub const TrackedInst = extern struct {
......@@ -721,14 +774,18 @@ const SimpleType = enum {
721774};
722775
723776pub const Key = union(enum) {
777 // Values
724778 comptime_float: f64,
725779 int32: i32,
726780 float: f64,
727781 string: []const u8,
728782 simple_value: SimpleValue,
729 simple_type: SimpleType,
730 type_function: TypeFunction,
783 struct_value: StructValue,
731784 func: Func,
785 // Types
786 simple_type: SimpleType,
787 function_type: FunctionType,
788 struct_type: StructType,
732789
733790 const Adapter = struct {
734791 intern_pool: *const InternPool,
......@@ -736,7 +793,7 @@ pub const Key = union(enum) {
736793 pub fn eql(ctx: @This(), a: Key, b_void: void, b_map_index: usize) bool {
737794 _ = b_void;
738795 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);
740797 }
741798
742799 pub fn hash(ctx: @This(), a: Key) u32 {
......@@ -744,11 +801,11 @@ pub const Key = union(enum) {
744801 }
745802 };
746803
747 pub const TypeFunction = struct {
804 pub const FunctionType = struct {
748805 param_types: []Type,
749806 return_type: Type,
750807
751 pub fn hash(self: TypeFunction, hasher: *Hash) void {
808 pub fn hash(self: FunctionType, hasher: *Hash) void {
752809 const t = tracer.trace(@src());
753810 defer t.end();
754811
......@@ -758,12 +815,32 @@ pub const Key = union(enum) {
758815 std.hash.autoHash(hasher, self.return_type);
759816 }
760817
761 pub fn eql(a: TypeFunction, b: TypeFunction) bool {
818 pub fn eql(a: FunctionType, b: FunctionType) bool {
762819 return std.mem.eql(Type, a.param_types, b.param_types) and
763820 a.return_type == b.return_type;
764821 }
765822 };
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
767844 pub const Func = struct {
768845 ty: Type,
769846 owner_decl: DeclIndex,
......@@ -781,10 +858,10 @@ pub const Key = union(enum) {
781858 }
782859
783860 pub fn hash64(key: Key, ip: *const InternPool) u64 {
861 _ = ip; // autofix
784862 const t = tracer.trace(@src());
785863 defer t.end();
786864
787 _ = ip; // autofix
788865 const asBytes = std.mem.asBytes;
789866 const KeyTag = @typeInfo(Key).Union.tag_type.?;
790867 const seed = @intFromEnum(@as(KeyTag, key));
......@@ -797,7 +874,7 @@ pub const Key = union(enum) {
797874 .string,
798875 => |x| Hash.hash(seed, x),
799876 .float, .comptime_float => |x| Hash.hash(seed, asBytes(&Float.assertPure(x))),
800 .type_function => |x| {
877 .function_type => |x| {
801878 var hasher = Hash.init(seed);
802879 x.hash(&hasher);
803880 return hasher.final();
......@@ -807,14 +884,24 @@ pub const Key = union(enum) {
807884 x.hash(&hasher);
808885 return hasher.final();
809886 },
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 },
810897 };
811898 }
812899
813900 pub fn eql(a: Key, b: Key, ip: *const InternPool) bool {
901 _ = ip; // autofix
814902 const t = tracer.trace(@src());
815903 defer t.end();
816904
817 _ = ip; // autofix
818905 const KeyTag = @typeInfo(Key).Union.tag_type.?;
819906 const a_tag: KeyTag = a;
820907 const b_tag: KeyTag = b;
......@@ -828,7 +915,7 @@ pub const Key = union(enum) {
828915 => |x| std.mem.eql(u8, x, b.string),
829916 .float => |x| Float.assertPure(x) == Float.assertPure(b.float),
830917 .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),
832919 .func => |a_info| {
833920 const b_info = b.func;
834921 if (a_info.owner_decl != b_info.owner_decl)
......@@ -839,6 +926,14 @@ pub const Key = union(enum) {
839926 // TODO: zig has something extra here for inferred error sets
840927 return false;
841928 },
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 },
842937 };
843938 }
844939};
......@@ -890,8 +985,8 @@ pub fn init(ip: *InternPool, gpa: Allocator) !void {
890985
891986 if (std.debug.runtime_safety) {
892987 // Sanity check.
893 assert(ip.indexToKey(.bool_true).simple_value == .true);
894 assert(ip.indexToKey(.bool_false).simple_value == .false);
988 assert(ip.valueToKey(.bool_true).simple_value == .true);
989 assert(ip.valueToKey(.bool_false).simple_value == .false);
895990 }
896991
897992 assert(ip.items.len == static_keys.len);
......@@ -944,22 +1039,20 @@ pub fn deinit(ip: *InternPool, gpa: Allocator) void {
9441039 ip.* = undefined;
9451040}
9461041
947pub fn indexToKey(ip: *const InternPool, index: Value) Key {
1042pub fn valueToKey(ip: *const InternPool, index: Value) Key {
9481043 assert(index != .none);
9491044 const item: Item = ip.items.get(@intFromEnum(index));
9501045 const data = item.data;
9511046 return switch (item.tag) {
9521047 .removed => unreachable,
9531048
954 .type_function => .{
955 .type_function = ip.extraTypeFunction(data.decode(.type_function, item.tag)),
956 },
957
9581049 .simple_type => .{ .simple_type = data.decode(.simple_type, item.tag) },
9591050 .simple_value => .{ .simple_value = data.decode(.simple_value, item.tag) },
9601051 .int32 => .{ .int32 = data.decode(.int32, item.tag) },
9611052 .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 },
9631056 .string => .{ .string = data.decode(.string, item.tag).get(ip).toSlice(ip) },
9641057 .func => {
9651058 const func = data.decode(.func, item.tag).get(ip);
......@@ -969,6 +1062,29 @@ pub fn indexToKey(ip: *const InternPool, index: Value) Key {
9691062 .teg_body_inst = func.teg_body_inst,
9701063 } };
9711064 },
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 },
9721088 };
9731089}
9741090
......@@ -1020,20 +1136,39 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) !Value {
10201136
10211137 break :brk Item.encode(.func, extra);
10221138 },
1023 .type_function => |f| brk: {
1139 .function_type => |f| brk: {
10241140 const extra, var trail = try ip.extra.appendTrailing(gpa, Tag.TypeFunction, .{
10251141 .params_len = @intCast(f.param_types.len),
10261142 .return_type = f.return_type,
10271143 }, f.param_types.len);
10281144 defer trail.end();
10291145 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);
10311165 },
10321166 };
10331167 ip.items.appendAssumeCapacity(item);
10341168 const index: Value = @enumFromInt(ip.items.len - 1);
10351169 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));
10371172 }
10381173 return index;
10391174}
......@@ -1041,11 +1176,11 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) !Value {
10411176fn extraTypeFunction(
10421177 ip: *const InternPool,
10431178 extra_index: ExtraIndex(Tag.TypeFunction),
1044) Key.TypeFunction {
1045 const type_function, var trail = extra_index.getTrail(ip);
1046 const param_types = trail.readTypeSlice(type_function.params_len);
1047 const result: Key.TypeFunction = .{
1048 .return_type = type_function.return_type,
1179) Key.FunctionType {
1180 const function_type, var trail = extra_index.getTrail(ip);
1181 const param_types = trail.readTypeSlice(function_type.params_len);
1182 const result: Key.FunctionType = .{
1183 .return_type = function_type.return_type,
10491184 .param_types = param_types.get(ip),
10501185 };
10511186 return result;
......@@ -1130,6 +1265,10 @@ pub fn namespacePtr(ip: *InternPool, index: NamespaceIndex) *Comp.Namespace {
11301265 return ip.allocated_namespaces.at(@intFromEnum(index));
11311266}
11321267
1268pub fn namespacePtrConst(ip: *const InternPool, index: NamespaceIndex) *const Comp.Namespace {
1269 return ip.allocated_namespaces.at(@intFromEnum(index));
1270}
1271
11331272pub fn createDecl(
11341273 ip: *InternPool,
11351274 gpa: Allocator,
......@@ -1222,7 +1361,7 @@ test InternPool {
12221361 try testing.expectEqual(str, str2);
12231362
12241363 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);
12261365 const int2 = try ip.get(a, .{ .int32 = 21483 });
12271366 try testing.expectEqual(int1, int2);
12281367}
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 {
103103 statement,
104104 expression,
105105 else_clause,
106 struct_field,
107 param_list,
106108
107109 pub fn description(tag: ExpectedContext) []const u8 {
108110 return switch (tag) {
109111 .param => "a parameter",
112 .param_list => "function parameters",
110113 .argument => "an argument",
111114 .statement => "a statement",
112115 .expression => "an expression",
113116 .else_clause => "an else clause",
117 .struct_field => "a field definition",
114118 };
115119 }
116120};
......@@ -233,6 +237,8 @@ fn parseModuleMembers(p: *Parse) Error!SubList {
233237 => try p.scratch.append(p.gpa, try p.parseModuleDecl()),
234238
235239 .kw_struct,
240 => try p.scratch.append(p.gpa, try p.parseStructDecl(.declaration)),
241
236242 .kw_enum,
237243 .kw_trait,
238244 .kw_impl,
......@@ -247,7 +253,7 @@ fn parseModuleMembers(p: *Parse) Error!SubList {
247253
248254 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
252258 switch (first_tag) {
253259 // some control flow and blocks are allowed to not have
......@@ -280,6 +286,14 @@ fn parseModuleDecl(p: *Parse) Error!NodeIndex {
280286 };
281287}
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
283297fn parseFnDecl(p: *Parse) Error!NodeIndex {
284298 const trace = t.trace(@src());
285299 defer trace.end();
......@@ -290,20 +304,20 @@ fn parseFnDecl(p: *Parse) Error!NodeIndex {
290304
291305 _ = try p.expect(.start_paren);
292306
293 const params = try p.parseParamList();
307 const params = try p.parseParamList(.types_required);
294308
295309 const return_type = if (p.eatToken(.minus_lt)) |_|
296310 if (!p.isExpressionStart())
297311 return p.failUnexpectedToken(.expression)
298312 else
299 try p.parseExpression(false)
313 try p.parseExpression(.preceding_block)
300314 else
301315 null;
302316
303317 try p.expectWithoutAdvance(.start_brace);
304318 const body = try p.parseBlock();
305319
306 const extra = try p.addExtra(Ast.FnDecl, .{
320 const extra = try p.addExtra(Ast.Fn, .{
307321 .params = params,
308322 .return_type = optionalNodeIndex(return_type),
309323 });
......@@ -311,14 +325,16 @@ fn parseFnDecl(p: *Parse) Error!NodeIndex {
311325 return try p.addNode(.{
312326 .tag = .fn_decl,
313327 .main_token = main_token,
314 .data = .{ .fn_decl = .{
328 .data = .{ .function = .{
315329 .body = body,
316330 .extra = extra,
317331 } },
318332 });
319333}
320334
321fn parseParamList(p: *Parse) Error!SubList {
335const TypePresence = enum { types_optional, types_required };
336
337fn parseParamList(p: *Parse, types: TypePresence) Error!SubList {
322338 const trace = t.trace(@src());
323339 defer trace.end();
324340
......@@ -331,7 +347,7 @@ fn parseParamList(p: *Parse) Error!SubList {
331347
332348 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
336352 switch (p.currentTag()) {
337353 .comma => {
......@@ -347,25 +363,92 @@ fn parseParamList(p: *Parse) Error!SubList {
347363 return p.scratchToSubList(scratch_top);
348364}
349365
350fn parseParamDecl(p: *Parse) Error!NodeIndex {
366fn parseParamDecl(p: *Parse, types: TypePresence) Error!NodeIndex {
351367 const trace = t.trace(@src());
352368 defer trace.end();
353369
354370 const main_token = p.assertToken(.id);
355371
356 _ = try p.expect(.colon);
357
358 const ty = try p.parseExpression(false);
372 const ty = if (types == .types_required or p.currentTag() == .colon) brk: {
373 _ = try p.expect(.colon);
374 break :brk try p.parseExpression(.expression);
375 } else null;
359376
360377 return try p.addNode(.{
361378 .tag = .param_decl,
362379 .main_token = main_token,
363380 .data = .{ .param_decl = .{
364 .type = ty,
381 .type = optionalNodeIndex(ty),
365382 } },
366383 });
367384}
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
369452fn parseBlock(p: *Parse) Error!NodeIndex {
370453 const trace = t.trace(@src());
371454 defer trace.end();
......@@ -390,7 +473,8 @@ fn parseLabelExpression(p: *Parse) Error!NodeIndex {
390473
391474 const main_token = p.assertToken(.id);
392475 _ = try p.expect(.colon);
393 const expr = try p.parseExpression(false);
476 const expr = try p.parseExpression(.expression);
477
394478 return try p.addNode(.{
395479 .tag = .label,
396480 .main_token = main_token,
......@@ -422,7 +506,7 @@ fn parseBlockContents(p: *Parse) Error!struct { Node.Tag, SubList } {
422506
423507 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
427511 switch (first_tag) {
428512 // some control flow and blocks are allowed to not have
......@@ -466,13 +550,13 @@ fn parseVarDecl(p: *Parse) Error!NodeIndex {
466550 _ = try p.expect(.id);
467551
468552 const data_type: OptionalNodeIndex = if (p.eatToken(.colon)) |_|
469 optionalNodeIndex(try p.tryParseExpression(false))
553 optionalNodeIndex(try p.tryParseExpression(.expression))
470554 else
471555 .none;
472556
473557 _ = try p.expect(.equal);
474558
475 const data_init = try p.tryParseExpression(true);
559 const data_init = try p.tryParseExpression(.expression);
476560
477561 _ = try p.expect(.semi);
478562
......@@ -490,6 +574,7 @@ fn isExpressionStart(p: *Parse) bool {
490574 return switch (p.currentTag()) {
491575 .bang,
492576 .builtin_id,
577 .dot,
493578 .id,
494579 .kw_assert,
495580 .kw_break,
......@@ -504,6 +589,7 @@ fn isExpressionStart(p: *Parse) bool {
504589 .start_paren,
505590 .string,
506591 .tilde,
592 .kw_fn,
507593 => true,
508594 else => false,
509595 };
......@@ -692,7 +778,17 @@ const Operator = enum(u8) {
692778 }
693779};
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 {
696792 const trace = t.trace(@src());
697793 defer trace.end();
698794
......@@ -711,24 +807,24 @@ fn parseExpression(p: *Parse, allow_assign_operators: bool) Error!NodeIndex {
711807 else => {},
712808 }
713809
714 var expr = try p.parseExpressionPart();
810 var expr = try p.parseExpressionPart(mode);
715811
716812 while (Operator.fromToken(p.currentTag())) |op| {
717 if (!allow_assign_operators and op.isAssign()) break;
718 expr = try p.parseBinarySuffix(expr, op, allow_assign_operators);
813 if (mode != .statement and op.isAssign()) break;
814 expr = try p.parseBinarySuffix(expr, op, mode);
719815 }
720816
721817 return expr;
722818}
723819
724fn tryParseExpression(p: *Parse, allow_assign_operators: bool) Error!NodeIndex {
820fn tryParseExpression(p: *Parse, mode: ParseExprMode) Error!NodeIndex {
725821 return if (p.isExpressionStart())
726 p.parseExpression(allow_assign_operators)
822 p.parseExpression(mode)
727823 else
728824 p.failUnexpectedToken(.expression);
729825}
730826
731fn parseExpressionPart(p: *Parse) Error!NodeIndex {
827fn parseExpressionPart(p: *Parse, mode: ParseExprMode) Error!NodeIndex {
732828 const trace = t.trace(@src());
733829 defer trace.end();
734830
......@@ -749,6 +845,8 @@ fn parseExpressionPart(p: *Parse) Error!NodeIndex {
749845 .kw_if => try p.parseIf(),
750846 .start_paren => try p.parseGroup(),
751847 .start_brace => try p.parseBlock(),
848 .dot => try p.parseAnonInstantiation(),
849 .kw_fn => try p.parseAnonFunction(),
752850
753851 // Unary operators
754852 inline .bang,
......@@ -759,7 +857,7 @@ fn parseExpressionPart(p: *Parse) Error!NodeIndex {
759857 .kw_assert,
760858 => |tag| unary: {
761859 const main_token = p.nextToken();
762 const expr = try p.parseExpressionPart();
860 const expr = try p.parseExpressionPart(mode);
763861
764862 break :unary try p.addNode(.{
765863 .tag = switch (tag) {
......@@ -819,7 +917,7 @@ fn parseExpressionPart(p: *Parse) Error!NodeIndex {
819917 },
820918 .start_bracket => {
821919 const main_token = p.nextToken();
822 const index = try p.parseExpression(false);
920 const index = try p.parseExpression(.expression);
823921
824922 expr = try p.addNode(.{
825923 .tag = .index,
......@@ -830,7 +928,20 @@ fn parseExpressionPart(p: *Parse) Error!NodeIndex {
830928 } },
831929 });
832930 },
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 },
834945 else => break,
835946 }
836947 }
......@@ -838,16 +949,16 @@ fn parseExpressionPart(p: *Parse) Error!NodeIndex {
838949 return expr;
839950}
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 {
842953 const trace = t.trace(@src());
843954 defer trace.end();
844955
845956 const main_token = p.nextToken();
846957 const level = op.level();
847 var rhs = try p.parseExpressionPart();
958 var rhs = try p.parseExpressionPart(mode);
848959
849960 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
852963 const new_level = next_op.level();
853964 const use = switch (op.direction()) {
......@@ -856,7 +967,7 @@ fn parseBinarySuffix(p: *Parse, lhs: NodeIndex, op: Operator, allow_assign_opera
856967 };
857968 if (!use) break;
858969
859 rhs = try p.parseBinarySuffix(rhs, next_op, allow_assign_operators);
970 rhs = try p.parseBinarySuffix(rhs, next_op, mode);
860971 }
861972
862973 return try p.addNode(.{
......@@ -876,7 +987,7 @@ fn parseReturn(p: *Parse) Error!NodeIndex {
876987 const main_token = p.assertToken(.kw_return);
877988
878989 const expr = if (p.isExpressionStart())
879 try p.parseExpression(false)
990 try p.parseExpression(.expression)
880991 else
881992 null;
882993
......@@ -901,7 +1012,7 @@ fn parseBreak(p: *Parse) Error!NodeIndex {
9011012 null;
9021013
9031014 const expr = if (p.isExpressionStart())
904 try p.parseExpression(false)
1015 try p.parseExpression(.expression)
9051016 else
9061017 null;
9071018
......@@ -940,7 +1051,7 @@ fn parseCallArgs(p: *Parse) Error!SubList {
9401051 defer p.scratch.shrinkRetainingCapacity(scratch_top);
9411052
9421053 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
9451056 switch (p.currentTag()) {
9461057 .end_paren => {
......@@ -966,7 +1077,7 @@ fn parseIf(p: *Parse) Error!NodeIndex {
9661077
9671078 const main_token = p.assertToken(.kw_if);
9681079
969 const cond = try p.parseExpression(false);
1080 const cond = try p.parseExpression(.preceding_block);
9701081
9711082 _ = try p.expectWithoutAdvance(.start_brace);
9721083 const yes = try p.parseBlock();
......@@ -1012,7 +1123,7 @@ fn parseGroup(p: *Parse) Error!NodeIndex {
10121123
10131124 const main_token = p.assertToken(.start_paren);
10141125
1015 const expr = try p.parseExpression(false);
1126 const expr = try p.parseExpression(.expression);
10161127
10171128 _ = try p.expect(.end_paren);
10181129
......@@ -1025,6 +1136,74 @@ fn parseGroup(p: *Parse) Error!NodeIndex {
10251136 });
10261137}
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
10281207// helpers
10291208
10301209/// Append a node and return its index.
src/core/Report.zig+139-17
......@@ -174,8 +174,8 @@ pub const Err = union(enum) {
174174 fn build(data: @This(), ctx: *MsgContext) !void {
175175 const node = try data.src.get(ctx);
176176 try ctx.msgNode(node, "expected \"{s}\", got \"{s}\"", .{
177 data.to.displayName(&ctx.comp.intern_pool),
178 data.from.displayName(&ctx.comp.intern_pool),
177 data.to.displayName(ctx.ip()),
178 data.from.displayName(ctx.ip()),
179179 });
180180 }
181181 },
......@@ -186,7 +186,7 @@ pub const Err = union(enum) {
186186 fn build(data: @This(), ctx: *MsgContext) !void {
187187 const node = try data.call.get(ctx);
188188 try ctx.msgNode(node, "\"{s}\" is not callable", .{
189 data.ty.displayName(&ctx.comp.intern_pool),
189 data.ty.displayName(ctx.ip()),
190190 });
191191 }
192192 },
......@@ -210,7 +210,7 @@ pub const Err = union(enum) {
210210 fn build(data: @This(), ctx: *MsgContext) !void {
211211 const node = try data.src.get(ctx);
212212 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()),
214214 });
215215 }
216216 },
......@@ -221,7 +221,7 @@ pub const Err = union(enum) {
221221 fn build(data: @This(), ctx: *MsgContext) !void {
222222 const node = try data.src.get(ctx);
223223 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()),
225225 });
226226 }
227227 },
......@@ -275,8 +275,24 @@ pub const Err = union(enum) {
275275 "\"{s}\" operator is not compatible with types \"{}\" and \"{}\"",
276276 .{
277277 ctx.ast.tokenSlice(ctx.ast.nodeToken(node)),
278 data.lhs.displayName(&ctx.comp.intern_pool),
279 data.rhs.displayName(&ctx.comp.intern_pool),
278 data.lhs.displayName(ctx.ip()),
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()),
280296 },
281297 );
282298 }
......@@ -293,6 +309,96 @@ pub const Err = union(enum) {
293309 );
294310 }
295311 },
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 },
296402};
297403
298404pub const Warn = union(enum) {
......@@ -783,8 +889,8 @@ pub const Highlight = union(enum) {
783889
784890/// This serves as a small API to make creating messages easier.
785891pub const MsgContext = struct {
786 file: *File,
787 comp: *Comp,
892 file: *const File,
893 comp: *const Comp,
788894 ast: *const Ast,
789895 report: *const Report,
790896 utf8_source: [:0]const u8,
......@@ -819,6 +925,10 @@ pub const MsgContext = struct {
819925 pub fn msgAlone(m: *MsgContext, comptime fmt: []const u8, args: anytype) !void {
820926 return m.msg(.alone, fmt, args);
821927 }
928
929 pub fn ip(ctx: *const MsgContext) *const InternPool {
930 return &ctx.comp.intern_pool;
931 }
822932};
823933
824934pub const LazyNode = struct {
......@@ -828,7 +938,7 @@ pub const LazyNode = struct {
828938
829939 pub fn get(lazy: LazyNode, ctx: *MsgContext) !NodeIndex {
830940 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
833943 // TODO: This might change if we have errors like
834944 // - function arguments not matching at <callsite>
......@@ -845,10 +955,9 @@ pub const LazyNode = struct {
845955 // this will be required once incremental compilation hits
846956 @panic("TODO: teg was unloaded"));
847957
848 const decl_inst = decl.teg_decl_index.unwrap() orelse
849 unreachable; // file was not analyzed, yet teg loaded?
958 const decl_inst = decl.teg_decl_index;
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
853962 const node_offset = teg.instNodeOffset(lazy.inst);
854963
......@@ -903,7 +1012,7 @@ pub const LazyNode = struct {
9031012 },
9041013 .declaration => {
9051014 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);
9071016 return switch (lazy.operand) {
9081017 .return_type => fn_decl.return_type.unwrap().?,
9091018 else => fn_decl.params.slice(ctx.ast)[lazy.operand.getArgId()],
......@@ -912,11 +1021,24 @@ pub const LazyNode = struct {
9121021 .js => {
9131022 return base_node;
9141023 },
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 },
9151039 else => {},
9161040 }
9171041
918 if (!std.debug.runtime_safety) unreachable;
919
9201042 std.debug.panic("TODO: operand lookup: .{s} + .{s} + {}", .{
9211043 @tagName(teg.instTag(lazy.inst)),
9221044 @tagName(ctx.ast.nodeTag(base_node)),
......@@ -1020,7 +1142,7 @@ fn formatIdImpl(
10201142 // differences in the string formatters. Ideally these are merged
10211143 // under a common function that takes an argument of the 'mode' to
10221144 // print the string in.
1023 const JSLink = @import("JsLink.zig");
1145 const JSLink = @import("mod.zig").Js.Link;
10241146 const indexOfNeedsEscape = JSLink.indexOfNeedsEscape;
10251147 const canPrintWithoutEscape = JSLink.canPrintWithoutEscape;
10261148
src/core/Sema.zig+424-95
......@@ -1,5 +1,28 @@
11/// absolutely stealing zig on this one haha
22/// 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
427// inputs:
528comp: *Comp,
......@@ -16,12 +39,14 @@ inst_map: InstMap = undefined,
1639/// scratch memory. can be used in any place you might temporarily allocate an arraylist
1740/// this is most notably used to store block contents before the block is finalized
1841scratch: std.ArrayListUnmanaged(u32) = .{},
42
43/// This is only set for function declarations
1944params: []Type = undefined,
2045
2146// outputs
2247report: Report.Builder = undefined,
2348air_instructions: std.MultiArrayList(Air.Inst) = .{},
24air_extra: dod.ExtraStore(.air, .@"var") = .{},
49air_extra: store.ExtraStore(.air, .@"var") = .{},
2550
2651temp_result_map: std.AutoArrayHashMapUnmanaged(Teg.Inst.Index, Type) = .{},
2752
......@@ -41,7 +66,7 @@ pub const Error = error{
4166 OutOfMemory,
4267} || 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.
4570/// This is fine because all instructions are going to be adjacent in a single declaration.
4671const InstMap = struct {
4772 items: []Air.Inst.Ref.Optional,
......@@ -70,10 +95,11 @@ const InstMap = struct {
7095 }
7196
7297 pub fn add(map: *InstMap, from: Teg.Inst.Index, to: Air.Inst.Ref) !void {
73 if (assertWithMessage(map.get(from) == null)) {
74 std.debug.panic("%{d} is already mapped (to #{d})", .{
98 if (assertFailed(map.get(from) == null)) {
99 const s: *const Sema = @fieldParentPtr("inst_map", map);
100 std.debug.panic("%{d} is already mapped (to {})", .{
75101 @intFromEnum(from),
76 @intFromEnum(map.get(from).?.decode().index),
102 map.get(from).?.dump(s.ip),
77103 });
78104 }
79105 const source_i = @intFromEnum(from);
......@@ -91,12 +117,10 @@ pub fn init(s: *Sema) !void {
91117 .current_file = s.ip.namespacePtr(s.namespace_index).file_scope,
92118 };
93119
94 try s.comp.current_compile_decls.append(s.gpa, s.decl_index);
95
96120 s.inst_map = try InstMap.init(
97121 s.arena,
98122 &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),
100124 );
101125}
102126
......@@ -117,21 +141,55 @@ pub fn deinit(s: *Sema) void {
117141}
118142
119143pub fn finalize(s: *Sema) !void {
144 s.decl.analysis = .complete;
145
120146 const air: Air = s.tempAir();
121147
122 if (s.comp.options.dump_ir) {
123 std.debug.print("=" ** 20, .{});
124 std.debug.print("\nAir for @ {d}\n", .{@intFromEnum(s.decl.teg_decl_index)});
125 air.dumpToWriter(s.ip, std.io.getStdErr().writer()) catch {};
126 }
148 if (s.decl.value().valueTag(s.ip) == .func) {
149 if (s.comp.options.dump_ir) {
150 std.debug.print("=" ** 20, .{});
151 std.debug.print("\nAir for {}\n", .{s.decl.value().dump(s.ip)});
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 }
129157}
130158
131159// --- analyze functions ---
132160
133161/// This is the entrypoint to semantic analysis: It all starts at a declaration.
134162pub 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 {
135193 const t = tracer.trace(@src());
136194 defer t.end();
137195
......@@ -164,7 +222,7 @@ pub fn analyzeDeclaration(s: *Sema) Error!Value {
164222 );
165223
166224 const func_prototype = (try s.ip.get(s.gpa, .{
167 .type_function = .{
225 .function_type = .{
168226 .param_types = s.params,
169227 .return_type = return_type,
170228 },
......@@ -173,14 +231,14 @@ pub fn analyzeDeclaration(s: *Sema) Error!Value {
173231 .func = .{
174232 .ty = func_prototype,
175233 .owner_decl = s.decl_index,
176 .teg_body_inst = s.decl.teg_decl_index.unwrap().?,
234 .teg_body_inst = s.decl.teg_decl_index,
177235 },
178236 });
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;
184242 switch (root_block.decode()) {
185243 .value => |val| {
186244 const coerced = try s.coerceValue(
......@@ -200,6 +258,9 @@ pub fn analyzeDeclaration(s: *Sema) Error!Value {
200258 },
201259 }
202260
261 s.decl.analysis = .complete;
262 s.decl.raw_value = value;
263
203264 return value;
204265}
205266
......@@ -268,31 +329,73 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {
268329 return s.makeValueAir(.{ .string = string.slice(s.code.string_bytes) });
269330 },
270331 .number => {
271 const number: dod.Float = s.code.instPayload(.number, in);
332 const number: store.Float = s.code.instPayload(.number, in);
272333 return s.makeValueAir(.{ .comptime_float = number.decode() });
273334 },
274335 .js => {
275336 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);
277338 return s.addInstRef(.js_global_ref, operand.asString(s.ip));
278339 },
279340 .field => {
280341 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);
284345 const string = rhs.asString(s.ip);
285346
286 const base_type = s.typeOfRef(base);
287 switch (base_type) {
288 .@"opaque" => {
289 return s.addInstRef(.field, .{
290 .base = base,
291 .field = try string.tryToNullTerminatedString(s.ip, s.gpa),
347 const ty = s.typeOfRef(base);
348
349 if (ty == .@"opaque") {
350 return s.addInstRef(.field_opaque, .{
351 .base = base,
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,
292378 });
293 },
294 else => |tag| std.debug.panic("TODO: field analyze with base .{s}", .{@tagName(tag)}),
379 };
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 }
295393 }
394
395 return s.fail(.field_invalid_type, .{
396 .ty = ty,
397 .src = in.op(.rhs).lazyNode(s.decl_index),
398 });
296399 },
297400 .load_decl => {
298401 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 {
326429 const bin: Teg.Inst.Bin = s.code.instPayload(tag, in);
327430 const op = @field(BinaryOp, @tagName(tag));
328431
329 const lhs = try s.resolveRefRuntime(bin.lhs);
330 const rhs = try s.resolveRefRuntime(bin.rhs);
432 const lhs = try s.resolveRef(bin.lhs);
433 const rhs = try s.resolveRef(bin.rhs);
331434
332435 const left_ty = s.typeOfRef(lhs);
333436 const right_ty = s.typeOfRef(rhs);
......@@ -350,23 +453,47 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {
350453 .rhs = right,
351454 });
352455 },
353 inline .negate, .bool_not => |tag| {
354 const un: Teg.Inst.RefNode = s.code.instData(tag, in);
456 .bool_not => {
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);
359 _ = operand_type; // autofix
461 // Constant propagation
462 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 operation
470 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(), .{
364 .operand = operand,
365 });
475 const operand = try s.resolveRef(un.operand);
476
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 });
366493 },
367494 .@"break" => {
368495 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);
370497 const block_ref = s.inst_map.get(brk.block).?.decode().index; // block ref is resolved by teg
371498
372499 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 {
381508 },
382509 .call => {
383510 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);
385512 const base_type = s.typeOfRef(base);
386513
387514 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 {
405532
406533 // validate function args
407534 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);
409536 const coerced = try s.coerceRef(
410537 in.op(Report.Operand.argument(@intCast(i))),
411538 ref,
......@@ -417,7 +544,7 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {
417544 for (call.args.slice(&s.code), 0..) |arg, i| {
418545 const ref = try s.ensureValueRuntime(
419546 in.op(Report.Operand.argument(@intCast(i))),
420 try s.resolveRefRuntime(arg),
547 try s.resolveRef(arg),
421548 );
422549 scratch[i] = ref;
423550 }
......@@ -438,14 +565,14 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {
438565 },
439566 .js_new => {
440567 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
443570 const scratch = try s.arena.alloc(Air.Inst.Ref, call.args.len);
444571 defer s.arena.free(scratch);
445572 for (call.args.slice(&s.code), 0..) |arg, i| {
446573 const ref = try s.ensureValueRuntime(
447574 in.op(Report.Operand.argument(@intCast(i))),
448 try s.resolveRefRuntime(arg),
575 try s.resolveRef(arg),
449576 );
450577 scratch[i] = ref;
451578 }
......@@ -462,7 +589,7 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {
462589 });
463590 },
464591 .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);
466593 const t = s.typeOfRef(operand);
467594 _ = t;
468595 // if (!t.isEnsureVoid(s.ip)) {
......@@ -473,7 +600,7 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {
473600 return InternPool.Value.void.toAir();
474601 },
475602 .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);
477604 const t = s.typeOfRef(operand);
478605 if (t == .void) {
479606 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 {
483610 .@"if" => {
484611 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);
487614 const cond = try s.coerceRef(in.op(.operand), cond_raw, .bool);
488615
489616 // 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 {
517644 .memory => {
518645 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
522649 const runtime_known = try s.ensureValueRuntime(
523650 in.op(.operand),
......@@ -535,7 +662,7 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {
535662
536663 const location = s.mapTeg(data.memory.toRef()).decode().index;
537664 s.markInst(location);
538 const operand = try s.resolveRefRuntime(data.operand);
665 const operand = try s.resolveRef(data.operand);
539666 const operand_coerced = try s.coerceRef(
540667 in.op(.rhs),
541668 operand,
......@@ -548,23 +675,23 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {
548675 },
549676 .as => {
550677 const as: Teg.Inst.As = s.code.instPayload(.as, in);
551 const coerce_to = try s.resolveRefComptimeType(in.op(.lhs), as.type, .type);
552 const operand = try s.resolveRefRuntime(as.operand);
678 const coerce_to = try s.resolveCoerceValue(in.op(.lhs), as.type, .type);
679 const operand = try s.resolveRef(as.operand);
553680 return try s.coerceRef(in.op(.rhs), operand, coerce_to.toType(s.ip).?);
554681 },
555682 .@"comptime" => {
556683 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);
558685 return value.toAir();
559686 },
560687 .type_of => {
561688 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);
563690 return value.typeOf(s.ip).value().toAir();
564691 },
565692 .compile_error => {
566693 const node: Teg.Inst.RefNode = s.code.instData(.compile_error, in);
567 const value = try s.resolveRefComptimeType(
694 const value = try s.resolveCoerceValue(
568695 in.op(Report.Operand.argument(0)),
569696 node.operand,
570697 .string,
......@@ -575,6 +702,162 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {
575702 .msg = try s.report.addString(str),
576703 });
577704 },
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 },
578861 else => |tag| std.debug.panic("TODO: analyze of .{s}", .{@tagName(tag)}),
579862 }
580863}
......@@ -591,7 +874,7 @@ fn analyzeRefToBlock(s: *Sema, in: Teg.Inst.Ref) Error!Air.Inst.Ref {
591874 const scratch_top = s.scratch.items.len;
592875 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) {
595878 error.HitNoReturn => Value.void.toAir(),
596879 else => return err,
597880 };
......@@ -614,7 +897,7 @@ fn analyzeRefToBlock(s: *Sema, in: Teg.Inst.Ref) Error!Air.Inst.Ref {
614897 return (try block.finish(s, scratch_top)).toRef();
615898}
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 {
618901 const index = ref.toIndex() orelse {
619902 return ref.toValue().?.toAir();
620903 };
......@@ -632,11 +915,11 @@ fn resolveRefRuntimeExtra(s: *Sema, ref: Teg.Inst.Ref, mark: bool) Error!Air.Ins
632915 return s.analyzeCode(index);
633916}
634917
635fn resolveRefRuntime(s: *Sema, ref: Teg.Inst.Ref) Error!Air.Inst.Ref {
636 return resolveRefRuntimeExtra(s, ref, true);
918fn resolveRef(s: *Sema, ref: Teg.Inst.Ref) Error!Air.Inst.Ref {
919 return resolveRefExtra(s, ref, true);
637920}
638921
639fn resolveRefComptimeAny(s: *Sema, ref: Teg.Inst.Ref) !Value {
922fn resolveValue(s: *Sema, ref: Teg.Inst.Ref) !Value {
640923 const index = switch (ref.decode()) {
641924 .value => |value| return value,
642925 .index => |index| index,
......@@ -655,21 +938,69 @@ fn resolveRefComptimeAny(s: *Sema, ref: Teg.Inst.Ref) !Value {
655938 return s.makeValue(.{ .string = string.slice(s.code.string_bytes) });
656939 },
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) }),
659982 }
660983}
661984
662fn resolveRefComptimeType(s: *Sema, src: Teg.InstOp, ref: Teg.Inst.Ref, ty: Type) !Value {
663 const any = try s.resolveRefComptimeAny(ref);
985fn resolveCoerceValue(s: *Sema, src: Teg.InstOp, ref: Teg.Inst.Ref, ty: Type) !Value {
986 const any = try s.resolveValue(ref);
664987 return try s.coerceValue(src, any, ty);
665988}
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
667995fn 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);
669997}
670998
671/// Coerce `value` runtime ref to `coerce_type`, and emit an error at `teg_ref`'s location
999/// Coerce `value` runtime ref to `coerce_type`, and emit an error at `src`'s location
6721000fn 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
6731004 const from = s.typeOfRef(value);
6741005 if (from == to) {
6751006 return value;
......@@ -721,6 +1052,7 @@ fn coerceRef(s: *Sema, src: InstOp, value: Air.Inst.Ref, to: Type) !Air.Inst.Ref
7211052 return s.coercionError(src, from, to);
7221053}
7231054
1055/// Same as `coerceRef` but on a compile-time known value
7241056fn coerceValue(s: *Sema, src: InstOp, value: Value, to: Type) !Value {
7251057 const from = value.typeOf(s.ip);
7261058 if (from == to) {
......@@ -728,11 +1060,10 @@ fn coerceValue(s: *Sema, src: InstOp, value: Value, to: Type) !Value {
7281060 }
7291061 switch (to) {
7301062 .@"opaque" => {
731
732 //
1063 // TODO:
7331064 },
7341065 .bool => {
735 //
1066 // TODO:
7361067 },
7371068 .int32 => switch (from) {
7381069 .int32 => unreachable, // same type handled
......@@ -818,8 +1149,10 @@ fn ensureValueRuntime(s: *Sema, src: InstOp, ref: Air.Inst.Ref) !Air.Inst.Ref {
8181149 => {},
8191150 _ => switch (ty.tag(s.ip)) {
8201151 .simple_type => unreachable, // handled above
1152
8211153 // Allowed at runtime
822 .type_function => {},
1154 .function_type => {},
1155 .struct_type => {},
8231156 },
8241157 }
8251158 },
......@@ -867,7 +1200,7 @@ fn resolveDecl(s: *Sema, src: InstOp, decl_index: Decl.Index) Error!Value {
8671200 });
8681201 },
8691202 else => {
870 return decl.val.?;
1203 return decl.value();
8711204 },
8721205 }
8731206}
......@@ -904,8 +1237,12 @@ const BinaryOp = enum {
9041237};
9051238
9061239fn binTypeResolution(s: *Sema, src: Teg.Inst.Index, op: BinaryOp, lhs: Type, rhs: Type) !Type {
907 if (lhs == .@"opaque") return rhs;
908 if (rhs == .@"opaque") return rhs;
1240 if (lhs == .@"opaque") {
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
9101247 switch (lhs) {
9111248 .string => if (op == .add) switch (rhs) {
......@@ -1016,9 +1353,7 @@ fn comptimeBin(s: *Sema, op: BinaryOp, lhs: Value, rhs: Value) !Value {
10161353 },
10171354 else => unreachable,
10181355 },
1019 inline .lt,
1020 .gt,
1021 => |tag| {
1356 inline .lt, .gt => |tag| {
10221357 const l = lhs.toFloat(s.ip) orelse
10231358 unreachable; // must be coerced
10241359 const r = rhs.toFloat(s.ip) orelse
......@@ -1034,8 +1369,7 @@ fn comptimeBin(s: *Sema, op: BinaryOp, lhs: Value, rhs: Value) !Value {
10341369 if (result) .bool_true else .bool_false,
10351370 );
10361371 },
1037 .eq,
1038 => {
1372 .eq => {
10391373 const result = lhs == rhs;
10401374 return @as(
10411375 InternPool.Value,
......@@ -1045,7 +1379,6 @@ fn comptimeBin(s: *Sema, op: BinaryOp, lhs: Value, rhs: Value) !Value {
10451379 }
10461380}
10471381
1048/// --- helper ---
10491382const IncompleteBlock = struct {
10501383 block_index: Air.Inst.Index,
10511384
......@@ -1177,30 +1510,26 @@ const math = std.math;
11771510const Allocator = std.mem.Allocator;
11781511const assert = std.debug.assert;
11791512const log = std.log.scoped(.sema);
1513const DynamicBitSetUnmanaged = std.DynamicBitSetUnmanaged;
11801514
1181inline fn assertWithMessage(cond: bool) bool {
1182 if (std.debug.runtime_safety) {
1183 return !cond;
1184 } else if (!cond) {
1185 unreachable; // assertion failure
1186 }
1187}
1515const core = @import("mod.zig");
1516const assertFailed = core.assertFailed;
11881517
1189const Comp = @import("Comp.zig");
1518const Comp = core.Comp;
11901519const File = Comp.File;
11911520const Decl = Comp.Decl;
11921521const Namespace = Comp.Namespace;
11931522
1194const Teg = @import("Teg.zig");
1523const Teg = core.Teg;
11951524const InstOp = Teg.InstOp;
11961525
1197const Air = @import("Air.zig");
1198const Report = @import("Report.zig");
1199const Ast = @import("Ast.zig");
1200const dod = @import("array.zig");
1526const Air = core.Air;
1527const Report = core.Report;
1528const Ast = core.Ast;
1529const store = core.array;
12011530const tracer = @import("tracer");
12021531
1203const InternPool = @import("InternPool.zig");
1532const InternPool = core.InternPool;
12041533const Value = InternPool.Value;
12051534const Type = InternPool.Type;
12061535const NullTerminatedString = InternPool.NullTerminatedString;
src/core/Teg.Dump.zig+40-8
......@@ -46,10 +46,10 @@ const State = struct {
4646 try s.dumpInstList(writer, teg.moduleDeclarations(inst), '{', '}');
4747 },
4848 .declaration => {
49 const decl_info = teg.declData(inst);
50 try writer.print(" ({s}) ", .{@tagName(decl_info.kind)});
49 const decl = teg.fullDecl(inst);
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);
5353 if (name.len == 0) {
5454 try tty.setColor(writer, .bold);
5555 try writer.writeAll("<top level code>");
......@@ -63,19 +63,19 @@ const State = struct {
6363 s.indent += 1;
6464 try s.newline(writer);
6565
66 switch (decl_info.kind) {
67 .function => {
68 const fn_data: Teg.Inst.DeclFn = teg.fullFuncDecl(inst).@"1";
66 switch (decl.data) {
67 .function => |fn_data| {
6968 try writer.writeAll("params: ");
7069 try s.dumpRefList(writer, fn_data.params.slice(teg), '[', ']');
7170 try s.newline(writer);
7271 try writer.writeAll("return_type: ");
7372 try s.dumpRef(writer, fn_data.return_type);
7473 try s.newline(writer);
75 try writer.writeAll("body: ");
76 try s.dumpRef(writer, fn_data.body);
7774 },
75 .immutable => {},
7876 }
77 try writer.writeAll("body: ");
78 try s.dumpRef(writer, decl.body);
7979 s.indent -= 1;
8080 try s.newline(writer);
8181 try writer.writeAll("}");
......@@ -223,6 +223,38 @@ const State = struct {
223223 try s.dumpRef(writer, data.operand);
224224 try writer.writeAll(")");
225225 },
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 },
226258 }
227259 }
228260
src/core/Teg.zig+94-4
......@@ -16,7 +16,7 @@ pub const Inst = struct {
1616 tag: Tag,
1717 data: EncodedData,
1818
19 // todo: fully document every thing
19 // TODO: fully document every thing
2020 pub const Tag = enum {
2121 module,
2222 declaration,
......@@ -43,6 +43,9 @@ pub const Inst = struct {
4343
4444 @"if",
4545
46 struct_decl,
47 struct_init,
48
4649 add,
4750 sub,
4851 mul,
......@@ -98,6 +101,8 @@ pub const Inst = struct {
98101 .param => DataNode,
99102 .store => PlNode(Store),
100103 .js_new => PlNode(JsNew),
104 .struct_decl => PlNode(StructDecl),
105 .struct_init => PlNode(StructInit),
101106
102107 // Single instruction operand
103108 .memory,
......@@ -271,7 +276,7 @@ pub const Inst = struct {
271276 pub fn toValue(inst: Ref) ?InternPool.Value {
272277 const ref_int = @intFromEnum(inst);
273278
274 // todo: is this > or >=
279 // TODO: is this > or >=
275280 if (ref_int > Index.static_len)
276281 return null;
277282
......@@ -315,16 +320,57 @@ pub const Inst = struct {
315320 args: RefList,
316321 };
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
318362 /// This will contain trailing data depending on the .kind
319363 pub const Decl = struct {
320364 name: NullTerminatedString,
321365 hash: Cache.BinDigest,
322366 kind: Kind,
323367 instruction_count: u32,
368 body: Ref,
324369
325370 pub const Kind = enum {
371 // TODO: remove function, see #45
326372 function,
327 // future: struct, let, var, etc
373 immutable,
328374 };
329375 };
330376
......@@ -332,7 +378,6 @@ pub const Inst = struct {
332378 pub const DeclFn = struct {
333379 params: RefList,
334380 return_type: Ref,
335 body: Ref,
336381 };
337382};
338383
......@@ -371,6 +416,8 @@ pub const SlicedString = struct {
371416 }
372417};
373418
419// TODO: Make the next three structs using a generic comptime thing
420
374421pub const RefList = struct {
375422 start: u32,
376423 len: u32,
......@@ -389,6 +436,15 @@ pub const IndexList = struct {
389436 }
390437};
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
392448pub fn deinit(teg: *Teg, gpa: std.mem.Allocator) void {
393449 gpa.free(teg.string_bytes);
394450 teg.extra.deinit(gpa);
......@@ -426,10 +482,44 @@ pub fn blockInstructions(teg: *const Teg, index: Inst.Index) []const Inst.Index
426482
427483pub fn fullFuncDecl(teg: *const Teg, index: Inst.Index) struct { Inst.Decl, Inst.DeclFn } {
428484 const decl, var trail = teg.instData(.declaration, index).extra_index.getTrail(teg);
485 assert(decl.kind == .function);
429486 const decl_fn = trail.read(&teg.extra, Inst.DeclFn);
430487 return .{ decl, decl_fn };
431488}
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
433523pub fn declData(teg: *const Teg, index: Inst.Index) Inst.Decl {
434524 return teg.instData(.declaration, index).extra_index.get(teg);
435525}
src/core/Tokenizer.zig+2-2
......@@ -68,11 +68,10 @@ pub const Token = struct {
6868 kw_else,
6969 kw_enum,
7070 kw_fn,
71 kw_infer,
7271 kw_for,
73 kw_while,
7472 kw_if,
7573 kw_impl,
74 kw_infer,
7675 kw_json,
7776 kw_let,
7877 kw_match,
......@@ -83,6 +82,7 @@ pub const Token = struct {
8382 kw_struct,
8483 kw_trait,
8584 kw_var,
85 kw_while,
8686 // sort-lines: end
8787
8888 // sort-lines: start
src/core/Transform.zig+154-35
......@@ -1,4 +1,13 @@
1//! Transform pass
1//! 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.
211gpa: Allocator,
312ast: Ast,
413insts: Inst.List = .{},
......@@ -8,11 +17,6 @@ extra: ExtraStore = .{},
817scratch: std.ArrayListUnmanaged(u32) = .{},
918id_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) = .{},
1620scopes: std.ArrayListUnmanaged(Scope) = .{},
1721
1822report: Report.Builder,
......@@ -67,7 +71,6 @@ pub fn transform(
6771
6872pub fn deinit(t: *Transform) void {
6973 t.scratch.deinit(t.gpa);
70 t.call_args.deinit(t.gpa);
7174 assert(t.scopes.items.len == 0);
7275 t.scopes.deinit(t.gpa);
7376 t.string_bytes.deinit(t.gpa);
......@@ -268,7 +271,7 @@ fn transformModule(t: *Transform, module_node: NodeIndex) Allocator.Error!Inst.I
268271 s.symbols.lockPointers();
269272 defer s.symbols.unlockPointers();
270273 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);
272275 }
273276 }
274277
......@@ -316,12 +319,12 @@ fn transformModule(t: *Transform, module_node: NodeIndex) Allocator.Error!Inst.I
316319 .name = NullTerminatedString.empty,
317320 .hash = .{0} ** 16,
318321 .instruction_count = @as(u32, @intCast(t.insts.len)) - @intFromEnum(top_level_fn),
322 .body = top_level_code_ref,
319323 }, dod.extraSizeOf(Inst.DeclFn));
320324
321325 trail.writeStruct(Inst.DeclFn, .{
322326 .params = .{ .start = 0, .len = 0 },
323327 .return_type = Inst.Ref.void,
324 .body = top_level_code_ref,
325328 });
326329
327330 t.getInstData(top_level_fn).* = EncodedData.encode(.declaration, .{
......@@ -346,13 +349,13 @@ fn previsitModuleStatement(t: *Transform, node: NodeIndex) Allocator.Error!void
346349 const tag = t.ast.nodes.items(.tag)[node];
347350 switch (tag) {
348351 .fn_decl => {
349 const fn_decl = t.ast.fullFnDecl(node);
350 var id, const id_allocated = try t.decodeIdentifierExtra(fn_decl.id);
352 const decl = t.ast.fullFnDecl(node);
353 var id, const id_allocated = try t.decodeIdentifierExtra(decl.id);
351354 if (id_allocated)
352355 id = try t.gpa.dupe(u8, id);
353356 const gop = try t.scope().symbols.getOrPut(t.gpa, id);
354357 if (gop.found_existing) {
355 t.gpa.free(id);
358 if (id_allocated) t.gpa.free(id);
356359 try t.report.err(.duplicate_function, .{
357360 .node = node,
358361 .prev = gop.value_ptr.node,
......@@ -364,6 +367,22 @@ fn previsitModuleStatement(t: *Transform, node: NodeIndex) Allocator.Error!void
364367 .data = .declaration,
365368 };
366369 },
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 },
367386 else => {},
368387 }
369388}
......@@ -378,12 +397,22 @@ fn transformModuleStatement(t: *Transform, decl: BlockContext, node: NodeIndex)
378397 .param_decl,
379398 => unreachable,
380399
381 .fn_decl => {}, // functions analyzed separately
400 .fn_decl,
401 .struct_decl,
402 => {}, // declarations are analyzed separately
382403
383404 else => _ = try t.transformBlockStatement(decl, node, false),
384405 }
385406}
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
387416fn transformFunction(t: *Transform, outer_decl: BlockContext, node: NodeIndex) Allocator.Error!Inst.Index {
388417 const trace = tracer.trace(@src());
389418 defer trace.end();
......@@ -391,7 +420,7 @@ fn transformFunction(t: *Transform, outer_decl: BlockContext, node: NodeIndex) A
391420 const decl = outer_decl.declOffset(node);
392421 const fn_decl = t.ast.fullFnDecl(node);
393422
394 const func = try t.addRootInst(.{
423 const decl_inst = try t.addRootInst(.{
395424 .tag = .declaration,
396425 .data = undefined,
397426 });
......@@ -421,23 +450,53 @@ fn transformFunction(t: *Transform, outer_decl: BlockContext, node: NodeIndex) A
421450 .kind = .function,
422451 .name = try t.addNullTerminatedString(fn_decl.id),
423452 .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,
425455 });
426456
427457 _ = try t.extra.append(t.gpa, Inst.DeclFn, .{
428458 .params = param_list,
429459 .return_type = return_type,
430 .body = body,
431460 });
432461
433 t.getInstData(func).* = EncodedData.encode(.declaration, .{
462 t.getInstData(decl_inst).* = EncodedData.encode(.declaration, .{
434463 .src_node = outer_decl.nodeOffset(node),
435464 .extra_index = decl_extra,
436465 });
437466
438467 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;
441500}
442501
443502const TransformFunctionCtx = struct {
......@@ -555,7 +614,7 @@ fn transformParamDecl(t: *Transform, decl: BlockContext, param: NodeIndex) Alloc
555614 defer trace.end();
556615
557616 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
560619 var id, const id_allocated = try t.decodeIdentifierExtra(param_decl.id);
561620 if (id_allocated)
......@@ -699,7 +758,6 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc
699758 .root,
700759 .param_decl,
701760 .var_decl,
702 .fn_decl,
703761 => unreachable, // not an expression
704762
705763 .id => {
......@@ -1157,29 +1215,91 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc
11571215 return try t.transformExpression(decl, group_node.expr);
11581216 },
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
11601288 else => |tag| std.debug.panic("TODO: expr {s}", .{@tagName(tag)}),
11611289 }
11621290}
11631291
11641292fn transformCallArgs(t: *Transform, decl: BlockContext, args: []const NodeIndex) !?RefList {
1165 const args_top = t.call_args.items.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 }
1293 const extra_top = try t.extra.alloc(t.gpa, args.len);
11751294
1176 const items = t.call_args.items[args_top..];
1177 const extra_top = t.extra.list.items.len;
1178 try t.extra.list.appendSlice(t.gpa, @ptrCast(items));
1295 for (args, extra_top..) |arg, i| {
1296 t.extra.setU32(i, @intFromEnum(try t.transformExpression(decl, arg) orelse
1297 return null));
1298 }
11791299
11801300 return .{
11811301 .start = @intCast(extra_top),
1182 .len = @intCast(items.len),
1302 .len = @intCast(args.len),
11831303 };
11841304}
11851305
......@@ -1193,7 +1313,6 @@ fn transformAssignmentTarget(t: *Transform, decl: BlockContext, node: NodeIndex)
11931313 .root,
11941314 .param_decl,
11951315 .var_decl,
1196 .fn_decl,
11971316 => unreachable, // not an expression
11981317
11991318 .id => {
src/core/array.zig+63-28
......@@ -40,44 +40,46 @@ fn setAsU32(items: []u32, comptime T: type, index: usize, payload: T) void {
4040}
4141
4242fn getAsU32(items: []const u32, comptime T: type, index: usize) T {
43 const fields = std.meta.fields(T);
44 var i = index;
45 var result: T = undefined;
46 inline for (fields) |field| {
47 @field(result, field.name) = switch (field.type) {
48 u32 => items[i],
49 i32 => @bitCast(items[i]),
43 return switch (T) {
44 u32 => items[index],
45 i32 => @bitCast(items[index]),
5046
51 [16]u8 => @bitCast(items[i..][0..4].*),
47 [16]u8 => @bitCast(items[index..][0..4].*),
5248
53 else => brk: {
54 const info = @typeInfo(field.type);
55 if (info == .Enum) {
56 break :brk @enumFromInt(items[i]);
57 }
49 else => brk: {
50 const info = @typeInfo(T);
51 if (info == .Enum) {
52 break :brk @enumFromInt(items[index]);
53 }
5854
59 const struct_data = info.Struct;
60 if (struct_data.layout == .@"packed") {
61 break :brk @bitCast(items[i]);
62 }
55 const struct_data = info.Struct;
56 if (struct_data.layout == .@"packed") {
57 break :brk @bitCast(items[index]);
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(
6566 .always_inline,
6667 getAsU32,
6768 .{ items, field.type, i },
6869 );
69 },
70 };
71 i += extraSizeOf(field.type);
72 }
73 return result;
70 i += comptime extraSizeOf(field.type);
71 }
72
73 break :brk result;
74 },
75 };
7476}
7577
7678pub const StoreKind = enum {
7779 intern_pool,
7880 teg,
7981 air,
80 tfr,
82 js_tree,
8183
8284 pub fn resolveStoreConst(
8385 comptime kind: StoreKind,
......@@ -204,6 +206,7 @@ pub fn extraSizeOf(comptime T: type) usize {
204206 };
205207}
206208
209// TODO: rename to Store
207210pub fn ExtraStore(comptime set_kind: StoreKind, comptime set_mutability: Mutability) type {
208211 return struct {
209212 list: List = .{},
......@@ -240,7 +243,11 @@ pub fn ExtraStore(comptime set_kind: StoreKind, comptime set_mutability: Mutabil
240243 return ip.get(T, trail.index);
241244 }
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 {
244251 defer trail.index += len;
245252 return .{
246253 .start = @intCast(trail.index),
......@@ -248,7 +255,18 @@ pub fn ExtraStore(comptime set_kind: StoreKind, comptime set_mutability: Mutabil
248255 };
249256 }
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 {
252270 defer trail.index += len;
253271 return .{
254272 .start = @intCast(trail.index),
......@@ -321,12 +339,18 @@ pub fn ExtraStore(comptime set_kind: StoreKind, comptime set_mutability: Mutabil
321339 };
322340 }
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 {
325349 comptime assert(mutability == .@"var");
326350 setAsU32(store.list.items, T, index, payload);
327351 }
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 {
330354 return getAsU32(store.list.items, T, index);
331355 }
332356
......@@ -338,6 +362,16 @@ pub fn ExtraStore(comptime set_kind: StoreKind, comptime set_mutability: Mutabil
338362 pub fn constant(store: *const Store) ExtraStore(set_kind, .@"const") {
339363 return .{ .list = .{ .items = store.list.items } };
340364 }
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 }
341375 };
342376}
343377
......@@ -388,3 +422,4 @@ pub const Float = struct {
388422const std = @import("std");
389423const assert = std.debug.assert;
390424const 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 {
101101 std.process.exit(1);
102102 };
103103
104 var js_backend = core.Js{};
105
104106 var comp: Comp = .{
105107 .gpa = tracer.tracingAllocator(&alloc),
106108 .options = .{
107109 .dump_ir = dump_ir,
108110 },
111 .backend = .{ .js = &js_backend },
109112 };
110113 try comp.init();
111114 defer comp.deinit();
......@@ -130,7 +133,7 @@ pub fn main() !void {
130133 try teg.dumpToWriter(stderr);
131134 }
132135
133 comp.semaEntryPoint(main_file) catch |err| switch (err) {
136 const entry_point = comp.semaEntryPoint(main_file) catch |err| switch (err) {
134137 error.AnalysisFail => std.process.exit(1),
135138 else => return err,
136139 };
......@@ -138,7 +141,7 @@ pub fn main() !void {
138141 const output = std.io.getStdOut().writer();
139142 var bufw = std.io.bufferedWriter(output);
140143 const writer = bufw.writer();
141 try comp.link(writer);
144 try js_backend.link(&comp, entry_point, writer.any());
142145 try bufw.flush();
143146
144147 std.process.cleanExit();
src/core/mod.zig+59-2
......@@ -1,18 +1,75 @@
1//! Core language compiler module.
2
3// sort-lines: start
4
5/// Analyzed Intermediate Representation.
16pub const Air = @import("Air.zig");
7
28pub const array = @import("array.zig");
9
10/// Abstract Syntax Tree - Preserves all syntax forms.
11pub const Ast = @import("Ast.zig");
12
313pub const Cache = @import("Cache.zig");
14
15/// Language Compilation Unit
416pub const Comp = @import("Comp.zig");
17
18/// Storage for shared objects (`Value`/`Decl`/`Namespace`) in a compilation
519pub 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`
725pub const Parse = @import("Parse.zig");
26
827pub const Report = @import("Report.zig");
28
29/// Semantic Analysis visits a `Teg` declaration and produces `Air`
930pub const Sema = @import("Sema.zig");
31
32/// Untyped Intermediate Representation. Flat instruction list
1033pub const Teg = @import("Teg.zig");
34
35/// Iterate tokens from a `[:0]const u8`. Includes code to decode/encode string escapes.
1136pub const Tokenizer = @import("Tokenizer.zig");
37
38/// Transform pass converts `Ast` into `Teg`
1239pub const Transform = @import("Transform.zig");
40
1341pub const unicode_props = @import("unicode_props.zig");
42
1443pub 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
1673comptime {
17 @import("std").testing.refAllDecls(@This());
74 std.testing.refAllDecls(@This());
1875}
src/core/util.zig+2-9
......@@ -1,3 +1,5 @@
1// TODO: move all decls in this file into mod.zig
2
13pub fn detectTTYFromWriter(writer: anytype) std.io.tty.Config {
24 if (@hasField(@TypeOf(writer), "unbuffered_writer")) {
35 return detectTTYFromWriter(writer.unbuffered_writer);
......@@ -40,15 +42,6 @@ pub fn Ordinal(Int: type) type {
4042 pub fn add(ord: @This(), inc: Int) @This() {
4143 return fromZeroBased(ord.zeroBased() + inc);
4244 }
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 }
5245 };
5346}
5447