authorchloe caruso <git@paperclover.net> 2024-09-10 01:56:29-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2024-09-10 01:56:29-07:00
log69ab21f0183d56865aa56b6879162e3918a5eba7
tree29bf2fda9311ae600235b8b7349c10ae57905985
parent92acce5584e084a247f337ae3c94833290e8260d

turn semantic analysis into a state machine (#79)


55 files changed, 5220 insertions(+), 2871 deletions(-)

CHANGELOG.md+8
...@@ -1,8 +1,16 @@...@@ -1,8 +1,16 @@
1## 0.1.61## 0.1.6
22
3- breaking: rename `@js` to `@extern`, considering future backends
4- re-write most of the semantic analysis pass (#79)
5 - feat: implement function expressions
6 - feat: implement simple `comptime` evaluation
7 - feat: change all semantic analysis panics into handled errors
8 - fix: no longer analyzes contents past a block end
3- feat: add `--run` to execute the file in QuickJS instead of outputting source9- feat: add `--run` to execute the file in QuickJS instead of outputting source
4- feat: add array index syntax via `x[1]` and `x.1`10- feat: add array index syntax via `x[1]` and `x.1`
5- feat: add `len` property to arrays11- feat: add `len` property to arrays
12- feat: rename `noreturn` to `never`
13- feat: allow instantiating `@extern`
6- fix: crash when printing unused struct declaration error14- fix: crash when printing unused struct declaration error
7- fix: provide array result type to sema15- fix: provide array result type to sema
8- fix: crash when analyzing an empty array without a result type16- fix: crash when analyzing an empty array without a result type
README.md+10-9
...@@ -1,20 +1,21 @@...@@ -1,20 +1,21 @@
1# New Language Project1# New Language Project
22
3This is a high-level, general purpose programming language. It is a mix of Rust, Zig, and 3This is a high-level, general purpose programming language. It is a mix of Rust,
4JavaScript's great points.4Zig, and JavaScript's great points.
55
6For a guide on how to use the language, please see the Language Manual included in the binary6For a guide on how to use the language, please see the Language Manual included
7distribution. (TODO: host a rendered version)7in the binary distribution. (TODO: host a rendered version)
88
9## Building9## Building
1010
11The primary dependency of the language SDK is a Zig compiler. If one is not available, the11The primary dependency of the language SDK is a Zig compiler. If one is not
12`./build` and `.\build.cmd` scripts can substitute for running `zig build` on POSIX and Windows12available, the `./build` and `.\build.cmd` scripts can substitute for running
13systems, respectively. With a Zig compiler, a release build can be produced in a single command:13`zig build` on POSIX and Windows systems, respectively. With a Zig compiler, a
14release build can be produced in a single command:
1415
15 zig build --release16 zig build --release
1617
17To run the standard packaging procedure, which builds the SDK for all officially supported targets:18To run the standard packaging procedure, which builds the SDK for all officially
19supported targets:
1820
19 zig build package21 zig build package
20
build.zig+429-189
...@@ -1,8 +1,207 @@...@@ -1,8 +1,207 @@
1//! To learn more about the Zig build system:
2//! https://ziglang.org/learn/build-system/
3//!
1//! See `architecture.md` for a codebase overview, and `zig-out/doc/README.html`4//! See `architecture.md` for a codebase overview, and `zig-out/doc/README.html`
2//! for information on the project.5//! for information on the project.
36
7const BuildConfig = @import("src/BuildConfig.zig");
8
9pub const Options = struct {
10 target: Build.ResolvedTarget,
11 optimize: std.builtin.OptimizeMode,
12
13 /// Experiment with the Zig self-hosted backends. Build option '-Dno_llvm'
14 no_llvm: bool,
15
16 /// BuildConfig contains most of the configurable aspects of the compiler,
17 /// and can be accessed in most modules with `@import("config")`
18 config: BuildConfig,
19};
20
21/// `createArtifactGroup` multiple times in order to have different
22/// targets and configurations of the SDK at the same time.
23const ArtifactGroup = struct {
24 build: *Build,
25 opts: Options,
26
27 cli: *Step.Compile,
28 format_cli: *Step.Compile,
29 transform_cli: *Step.Compile,
30 loader_cli: *Step.Compile,
31
32 core_mod: *Module,
33 loader_mod: *Module,
34 config_mod: *Module,
35
36 core_mod_tests: *Step.Compile,
37 loader_mod_tests: *Step.Compile,
38 compiler_tests: *Step.Compile,
39};
40
41fn createArtifactGroup(b: *Build, options: Options) ArtifactGroup {
42 const dep_opts = .{ .target = options.target, .optimize = options.optimize };
43 const use_llvm = if (options.no_llvm) false else null;
44 const use_lld = if (options.no_llvm) false else null;
45
46 // Dependencies
47 const zg = b.dependency("zg", dep_opts);
48 const tracer = b.dependency("tracer", dep_opts);
49 const quickjs = b.dependency("quickjs", dep_opts);
50
51 // Access build configuration via @import("config")
52 const config_mod = config_mod: {
53 const config = b.addOptions();
54 inline for (std.meta.fields(BuildConfig)) |field| {
55 config.addOption(field.type, field.name, @field(options.config, field.name));
56 }
57 config.addOption([]const u8, "version_string", b.fmt("{}", .{options.config.version}));
58 break :config_mod config.createModule();
59 };
60
61 // Core
62 const core_mod = b.addModule("core", .{
63 .root_source_file = b.path("src/core/mod.zig"),
64 .optimize = options.optimize,
65 .target = options.target,
66 .strip = false,
67 });
68 core_mod.addImport("config", config_mod);
69 core_mod.addImport("tracer", tracer.module("tracer"));
70 core_mod.addImport("zg:PropsData", zg.module("PropsData"));
71
72 // Format
73 const format_mod = b.addModule("format", .{
74 .root_source_file = b.path("src/format/Format.zig"),
75 .optimize = options.optimize,
76 .target = options.target,
77 .strip = false,
78 });
79 format_mod.addImport("core", core_mod);
80 format_mod.addImport("config", config_mod);
81 format_mod.addImport("tracer", tracer.module("tracer"));
82
83 // Loader
84 const loader_mod = b.addModule("loader", .{
85 .root_source_file = b.path("src/loader/Loader.zig"),
86 .optimize = options.optimize,
87 .target = options.target,
88 .strip = false,
89 });
90 loader_mod.addImport("core", core_mod);
91 loader_mod.addImport("config", config_mod);
92
93 // Core Unit Tests
94 const core_mod_tests = createModuleTests(core_mod, options);
95 const loader_mod_tests = createModuleTests(loader_mod, options);
96
97 // Compiler test CLI
98 const compiler_tests = b.addExecutable(.{
99 .name = "compiler_tests",
100 .root_source_file = b.path("test/compiler.zig"),
101 .target = options.target,
102 .optimize = options.optimize,
103 .use_lld = use_lld,
104 .use_llvm = use_llvm,
105 });
106 compiler_tests.root_module.addImport("core", core_mod);
107 compiler_tests.root_module.addImport("config", config_mod);
108 compiler_tests.root_module.addImport("tracer", tracer.module("tracer"));
109 compiler_tests.root_module.addImport("quickjs", quickjs.module("quickjs"));
110
111 // Main CLI
112 const cli = b.addExecutable(.{
113 .name = "compiler",
114 .root_source_file = b.path("src/cli.zig"),
115 .target = options.target,
116 .optimize = options.optimize,
117 .use_lld = use_lld,
118 .use_llvm = use_llvm,
119 .single_threaded = true,
120 });
121 cli.root_module.addImport("core", core_mod);
122 cli.root_module.addImport("config", config_mod);
123 cli.root_module.addImport("tracer", tracer.module("tracer"));
124 if (options.config.have_embedded_quickjs) {
125 cli.root_module.addImport("quickjs", quickjs.module("quickjs-libc"));
126 }
127
128 const format_cli = b.addExecutable(.{
129 .name = "format",
130 .root_source_file = b.path("src/format/format_cli.zig"),
131 .target = options.target,
132 .optimize = options.optimize,
133 .use_lld = use_lld,
134 .use_llvm = use_llvm,
135 .single_threaded = true,
136 });
137 format_cli.root_module.addImport("core", core_mod);
138 format_cli.root_module.addImport("format", format_mod);
139 format_cli.root_module.addImport("config", config_mod);
140 format_cli.root_module.addImport("tracer", tracer.module("tracer"));
141
142 const loader_cli = b.addExecutable(.{
143 .name = "loader",
144 .root_source_file = b.path("src/loader/main.zig"),
145 .target = options.target,
146 .optimize = options.optimize,
147 .use_lld = use_lld,
148 .use_llvm = use_llvm,
149 .single_threaded = true,
150 });
151 format_cli.root_module.addImport("loader", loader_mod);
152 format_cli.root_module.addImport("config", config_mod);
153 format_cli.root_module.addImport("tracer", tracer.module("tracer"));
154
155 const transform_cli = b.addExecutable(.{
156 .name = "transform",
157 .root_source_file = b.path("src/core/transform_cli.zig"),
158 .target = options.target,
159 .optimize = options.optimize,
160 .use_lld = use_lld,
161 .use_llvm = use_llvm,
162 .single_threaded = true,
163 });
164 transform_cli.root_module.addImport("core", core_mod);
165 transform_cli.root_module.addImport("config", config_mod);
166 transform_cli.root_module.addImport("tracer", tracer.module("tracer"));
167
168 return .{
169 .build = b,
170 .opts = options,
171
172 .cli = cli,
173 .format_cli = format_cli,
174 .loader_cli = loader_cli,
175 .transform_cli = transform_cli,
176
177 .core_mod = core_mod,
178 .loader_mod = loader_mod,
179 .config_mod = config_mod,
180
181 .core_mod_tests = core_mod_tests,
182 .loader_mod_tests = loader_mod_tests,
183 .compiler_tests = compiler_tests,
184 };
185}
186
187fn createModuleTests(module: *Build.Module, options: Options) *Build.Step.Compile {
188 const tests = module.owner.addTest(.{
189 .root_source_file = module.root_source_file.?,
190 .target = options.target,
191 .optimize = options.optimize,
192 .use_lld = if (options.no_llvm) false else null,
193 .use_llvm = if (options.no_llvm) false else null,
194 .strip = false,
195 });
196 var it = module.import_table.iterator();
197 while (it.next()) |entry| {
198 tests.root_module.addImport(entry.key_ptr.*, entry.value_ptr.*);
199 }
200 return tests;
201}
202
4pub fn build(b: *Build) !void {203pub fn build(b: *Build) !void {
5 b.reference_trace = 20;204 b.reference_trace = 32;
6205
7 const target = b.standardTargetOptions(.{});206 const target = b.standardTargetOptions(.{});
8207
...@@ -27,7 +226,7 @@ pub fn build(b: *Build) !void {...@@ -27,7 +226,7 @@ pub fn build(b: *Build) !void {
27 };226 };
28227
29 const tracing = b.option(228 const tracing = b.option(
30 TracingMode,229 BuildConfig.TracingMode,
31 "trace",230 "trace",
32 "Enable tracing",231 "Enable tracing",
33 ) orelse .none;232 ) orelse .none;
...@@ -38,17 +237,11 @@ pub fn build(b: *Build) !void {...@@ -38,17 +237,11 @@ pub fn build(b: *Build) !void {
38 "Experiment with the Zig self-hosted backends",237 "Experiment with the Zig self-hosted backends",
39 ) orelse false;238 ) orelse false;
40239
41 const version = version: {240 const have_embedded_quickjs = b.option(
42 const text = try b.build_root.handle.readFileAlloc(241 bool,
43 b.graph.arena,242 "quickjs",
44 "CHANGELOG.md",243 "Build with QuickJS features",
45 std.math.maxInt(usize),244 ) orelse true;
46 );
47 const first_header = std.mem.indexOf(u8, text, "## ") orelse unreachable;
48 var tokenizer = std.mem.tokenizeAny(u8, text[first_header + 2 ..], " \t\n");
49 const version_text = tokenizer.next() orelse unreachable;
50 break :version std.SemanticVersion.parse(version_text) catch unreachable;
51 };
52245
53 const js_runtime: PathOrString = if (b.option(246 const js_runtime: PathOrString = if (b.option(
54 []const u8,247 []const u8,
...@@ -63,14 +256,126 @@ pub fn build(b: *Build) !void {...@@ -63,14 +256,126 @@ pub fn build(b: *Build) !void {
63 .optimize = .ReleaseSafe,256 .optimize = .ReleaseSafe,
64 }).artifact("qjs").getEmittedBin() };257 }).artifact("qjs").getEmittedBin() };
65258
66 const native_debug = buildWithTarget(b, b.graph.host, .Debug, .none, version, no_llvm);259 // This code is taken from Zig
67 const main = buildWithTarget(b, target, optimize, tracing, version, no_llvm);260 const version_override = b.option(
261 []const u8,
262 "version",
263 "Override version string. Default is to find out with CHANGELOG.md and Git.",
264 );
265 const version = if (version_override) |version|
266 std.SemanticVersion.parse(version) catch {
267 std.debug.panic("Invalid -Dversion value \"{s}\"", .{version});
268 }
269 else version: {
270 const text = try b.build_root.handle.readFileAlloc(
271 b.graph.arena,
272 "CHANGELOG.md",
273 std.math.maxInt(usize),
274 );
275 const first_header = std.mem.indexOf(u8, text, "## ") orelse @panic("Invalid changelog");
276 var tokenizer = std.mem.tokenizeAny(u8, text[first_header + 2 ..], " \t\n");
277 const version_text = tokenizer.next() orelse @panic("Invalid changelog");
278 var version = std.SemanticVersion.parse(version_text) catch
279 std.debug.panic("Invalid version: {s}", .{version_text});
280
281 if (!std.process.can_spawn) {
282 std.debug.print("error: version info cannot be retrieved from git. Version must be provided using -Dversion\n", .{});
283 std.process.exit(1);
284 }
68285
69 b.installArtifact(main.cli(b, .all));286 var code: u8 = undefined;
287 const git_describe_untrimmed = b.runAllowFail(&[_][]const u8{
288 "git",
289 "-C",
290 b.build_root.path orelse ".",
291 "describe",
292 "--match",
293 "*.*.*",
294 "--tags",
295 "--abbrev=9",
296 }, &code, .Ignore) catch {
297 break :version version;
298 };
299 const git_describe = std.mem.trim(u8, git_describe_untrimmed, " \n\r");
300
301 switch (std.mem.count(u8, git_describe, "-")) {
302 0 => {
303 // Tagged release version (e.g. 0.10.0).
304 if (!std.mem.eql(u8, git_describe, b.fmt("{}", .{version}))) {
305 std.debug.print("Version '{}' does not match Git tag '{s}'\n", .{ version, git_describe });
306 std.process.exit(1);
307 }
308 break :version version;
309 },
310 2 => {
311 // Untagged development build (e.g. 0.10.0-dev.2025+ecf0050a9).
312 var it = std.mem.splitScalar(u8, git_describe, '-');
313 const tagged_ancestor = it.first();
314 const commit_height = it.next().?;
315 const commit_id = it.next().?;
316
317 const ancestor_ver = try std.SemanticVersion.parse(tagged_ancestor);
318 if (version.order(ancestor_ver) != .gt) {
319 std.debug.print("Version '{}' must be greater than tagged ancestor '{}'\n", .{ version, ancestor_ver });
320 std.process.exit(1);
321 }
322
323 // Check that the commit hash is prefixed with a 'g' (a Git convention).
324 if (commit_id.len < 1 or commit_id[0] != 'g') {
325 std.debug.print("Unexpected `git describe` output: {s}\n", .{git_describe});
326 break :version version;
327 }
328
329 // The version is reformatted in accordance with the https://semver.org specification.
330 version.pre = b.fmt("dev.{s}", .{commit_height});
331 version.build = commit_id[1..];
332
333 break :version version;
334 },
335 else => {
336 std.debug.print("Unexpected `git describe` output: {s}\n", .{git_describe});
337 break :version version;
338 },
339 }
340 };
341
342 // This artifact group is configured for the host system,
343 // for use in code generators + tests.
344 const native_debug = createArtifactGroup(b, .{
345 .config = .{ .version = version },
346 .optimize = .Debug,
347 .target = b.graph.host,
348 .no_llvm = no_llvm,
349 });
350
351 // This artifact group is used for the separated run steps,
352 // each with a minimal configuration applied.
353 const minimal = createArtifactGroup(b, .{
354 .config = .{ .version = version },
355 .optimize = optimize,
356 .target = b.graph.host,
357 .no_llvm = no_llvm,
358 });
359
360 // This artifact group is configured for the build target.
361 const main = createArtifactGroup(b, .{
362 .config = .{
363 .version = version,
364 .tracing_mode = tracing,
365 .have_embedded_quickjs = have_embedded_quickjs,
366 },
367 .target = target,
368 .optimize = optimize,
369 .no_llvm = no_llvm,
370 });
371
372 b.installArtifact(main.cli);
70373
71 inline for (.{374 inline for (.{
72 .{ main.cli(b, .minimal), "compiler" },375 .{ minimal.cli, "compiler" },
73 .{ main.loader_bin, "loader" },376 .{ minimal.format_cli, "format" },
377 .{ minimal.loader_cli, "loader" },
378 .{ minimal.transform_cli, "transform" },
74 }) |entry| {379 }) |entry| {
75 const artifact, const name = entry;380 const artifact, const name = entry;
76381
...@@ -85,8 +390,9 @@ pub fn build(b: *Build) !void {...@@ -85,8 +390,9 @@ pub fn build(b: *Build) !void {
85390
86 const test_all = b.step("test", "Run all tests");391 const test_all = b.step("test", "Run all tests");
87 inline for (.{392 inline for (.{
88 .{ main.core_tests, "core", "core module" },393 .{ main.core_mod_tests, "core", "core module" },
89 .{ main.behavior_tests, "behavior", "compiler behavior" },394 .{ main.loader_mod_tests, "loader", "loader module" },
395 .{ main.compiler_tests, "compiler", "compiler behavior" },
90 }) |entry| {396 }) |entry| {
91 const artifact, const name, const description = entry;397 const artifact, const name, const description = entry;
92 const run_cmd = b.addRunArtifact(artifact);398 const run_cmd = b.addRunArtifact(artifact);
...@@ -107,10 +413,11 @@ pub fn build(b: *Build) !void {...@@ -107,10 +413,11 @@ pub fn build(b: *Build) !void {
107 .optimize = .Debug,413 .optimize = .Debug,
108 .link_libc = true,414 .link_libc = true,
109 });415 });
110 manual_generator.root_module.addImport("build_options", native_debug.opts_mod);416 manual_generator.root_module.addImport("config", native_debug.config_mod);
111 manual_generator.root_module.addImport("core", native_debug.core);417 manual_generator.root_module.addImport("core", native_debug.core_mod);
418
112 const manual_run = b.addRunArtifact(manual_generator);419 const manual_run = b.addRunArtifact(manual_generator);
113 manual_run.addFileArg(native_debug.cli(b, BuildConfig.minimal).getEmittedBin());420 manual_run.addFileArg(native_debug.cli.getEmittedBin());
114 switch (js_runtime) {421 switch (js_runtime) {
115 .path => |path| manual_run.addFileArg(path),422 .path => |path| manual_run.addFileArg(path),
116 .string => |str| manual_run.addArg(str),423 .string => |str| manual_run.addArg(str),
...@@ -119,53 +426,79 @@ pub fn build(b: *Build) !void {...@@ -119,53 +426,79 @@ pub fn build(b: *Build) !void {
119 manual_run.addFileArg(b.path("src/manual/template.html"));426 manual_run.addFileArg(b.path("src/manual/template.html"));
120 const manual_out = manual_run.addOutputFileArg("language-reference.html");427 const manual_out = manual_run.addOutputFileArg("language-reference.html");
121 const install_manual = b.addInstallFile(manual_out, "doc/README.html");428 const install_manual = b.addInstallFile(manual_out, "doc/README.html");
429
122 b.step("manual", "build language reference").dependOn(&install_manual.step);430 b.step("manual", "build language reference").dependOn(&install_manual.step);
123431
124 // const wasm_api = b.addExecutable(.{432 {
125 // .name = "compiler",433 const wasm_target = b.resolveTargetQuery(.{
126 // .root_source_file = b.path("src/wasm_api.zig"),434 .cpu_arch = .wasm32,
127 // .target = b.resolveTargetQuery(.{435 .os_tag = .freestanding,
128 // .cpu_arch = .wasm32,436 });
129 // .os_tag = .freestanding,437 const wasm_artifacts = createArtifactGroup(b, .{
130 // }),438 .config = .{
131 // .optimize = optimize,439 .version = version,
132 // });440 .have_embedded_quickjs = false,
133 // wasm_api.root_module.export_symbol_names = &[_][]const u8{441 },
134 // "iterateTokens",442 .target = wasm_target,
135 // };443 .optimize = optimize,
136 // wasm_api.entry = .disabled;444 .no_llvm = no_llvm,
137 // const wasm_step = b.step("wasm", "Build wasm api");445 });
138 // b.installArtifact(wasm_api);446 const wasm_api = b.addExecutable(.{
139 // wasm_step.dependOn(&wasm_api.step);447 .name = "sdk",
140 // wasm_api.root_module.addImport("zg:PropsData", zg.module("PropsData"));448 .root_source_file = b.path("src/wasm/wasm_api.zig"),
449 .target = wasm_target,
450 .optimize = optimize,
451 });
452 wasm_api.root_module.addImport("config", wasm_artifacts.config_mod);
453 wasm_api.root_module.addImport("core", wasm_artifacts.core_mod);
454 wasm_api.root_module.export_symbol_names = &.{
455 "iterateTokens",
456 };
457 wasm_api.entry = .disabled;
458 const wasm_step = b.step("wasm", "[WIP] Build wasm api");
459 b.installArtifact(wasm_api);
460 wasm_step.dependOn(&wasm_api.step);
461
462 const wasm_codegen = b.step("wasm-codegen", "[WIP] Build wasm codegen");
463 const wasm_codegen_bin = b.addExecutable(.{
464 .name = "sdk",
465 .root_source_file = b.path("src/wasm/enum_codegen.zig"),
466 .target = b.graph.host,
467 .optimize = .Debug,
468 });
469 wasm_codegen_bin.root_module.addImport("core", native_debug.core_mod);
470 const run_wasm_codegen = b.addRunArtifact(wasm_codegen_bin);
471 wasm_codegen.dependOn(&run_wasm_codegen.step);
472 }
141473
142 // Packaging474 // Packaging
143 const package = b.step("package", "Package a release build");475 const package = b.step("package", "Package a release build");
144 const linux_version: OsVersion = .{ .semver = .{ .major = 1, .minor = 0, .patch = 0 } };
145 const mac_version: OsVersion = .{ .semver = .{ .major = 11, .minor = 0, .patch = 0 } };
146 const win_version: OsVersion = .{ .windows = .win7 };
147476
148 inline for (.{477 for ([_]Build.ResolvedTarget{
149 b.resolveTargetQuery(.{ .cpu_arch = .x86_64, .os_tag = .linux, .abi = .musl, .os_version_min = linux_version }),478 crossTarget(.x86_64, .linux),
150 b.resolveTargetQuery(.{ .cpu_arch = .aarch64, .os_tag = .linux, .abi = .musl, .os_version_min = linux_version }),479 crossTarget(.aarch64, .linux),
151 b.resolveTargetQuery(.{ .cpu_arch = .x86_64, .os_tag = .macos, .os_version_min = mac_version }),480 crossTarget(.x86_64, .macos),
152 b.resolveTargetQuery(.{ .cpu_arch = .aarch64, .os_tag = .macos, .os_version_min = mac_version }),481 crossTarget(.aarch64, .macos),
153 b.resolveTargetQuery(.{ .cpu_arch = .x86_64, .os_tag = .windows, .os_version_min = win_version, .abi = .gnu }),482 crossTarget(.x86_64, .windows),
154 b.resolveTargetQuery(.{ .cpu_arch = .aarch64, .os_tag = .windows, .os_version_min = win_version, .abi = .gnu }),483 crossTarget(.aarch64, .windows),
155 }) |cross_target| {484 }) |cross_target| {
156 const name = std.fmt.allocPrint(485 const name = b.fmt("{s}-{s}", .{
157 b.graph.arena,486 @tagName(cross_target.result.cpu.arch),
158 "{s}-{s}",487 @tagName(cross_target.result.os.tag),
159 .{488 });
160 @tagName(cross_target.result.cpu.arch),
161 @tagName(cross_target.result.os.tag),
162 },
163 ) catch @panic("OOM");
164489
165 const artifacts = buildWithTarget(b, cross_target, .ReleaseSafe, .none, version, no_llvm);490 const artifacts = createArtifactGroup(b, .{
491 .config = .{
492 .version = version,
493 .have_embedded_quickjs = have_embedded_quickjs,
494 },
495 .optimize = optimize,
496 .target = cross_target,
497 .no_llvm = no_llvm,
498 });
166499
167 inline for (.{500 inline for (.{
168 .{ artifacts.cli(b, BuildConfig.all), "compiler" },501 .{ artifacts.cli, "compiler" },
169 }) |entry| {502 }) |entry| {
170 const artifact, const bin_name = entry;503 const artifact, const bin_name = entry;
171 package.dependOn(&b.addInstallFile(504 package.dependOn(&b.addInstallFile(
...@@ -173,10 +506,10 @@ pub fn build(b: *Build) !void {...@@ -173,10 +506,10 @@ pub fn build(b: *Build) !void {
173 b.pathJoin(&.{506 b.pathJoin(&.{
174 "package",507 "package",
175 name,508 name,
176 std.mem.concat(b.graph.arena, u8, &.{509 b.fmt("{s}{s}", .{
177 bin_name,510 bin_name,
178 cross_target.result.exeFileExt(),511 cross_target.result.exeFileExt(),
179 }) catch @panic("OOM"),512 }),
180 }),513 }),
181 ).step);514 ).step);
182 }515 }
...@@ -184,140 +517,46 @@ pub fn build(b: *Build) !void {...@@ -184,140 +517,46 @@ pub fn build(b: *Build) !void {
184 package.dependOn(&b.addInstallFile(b.path("CHANGELOG.md"), "package/CHANGELOG.md").step);517 package.dependOn(&b.addInstallFile(b.path("CHANGELOG.md"), "package/CHANGELOG.md").step);
185 package.dependOn(&b.addInstallFile(manual_out, "package/README.html").step);518 package.dependOn(&b.addInstallFile(manual_out, "package/README.html").step);
186 }519 }
187}
188
189const TracingMode = enum { none, spall, chrome, log };
190
191const Artifacts = struct {
192 no_llvm: bool,
193 target: Build.ResolvedTarget,
194 optimize: std.builtin.OptimizeMode,
195520
196 core: *Build.Module,521 {
197 core_tests: *Build.Step.Compile,522 _ = b.top_level_steps.swapRemove("uninstall"); // this step does not work
198 opts_mod: *Build.Module,
199 behavior_tests: *Build.Step.Compile,
200 loader_bin: *Build.Step.Compile,
201
202 /// Construct a CLI with certain features.
203 /// When developing on a small component, such as the compiler core, you
204 /// don't want to compile every part of the compiler. This returns a CLI
205 /// compilation configured with only the features that are desired.
206 fn cli(a: Artifacts, b: *Build, config: BuildConfig) *Build.Step.Compile {
207 const dep_opts = .{ .target = a.target, .optimize = a.optimize };
208 const use_llvm = if (a.no_llvm) false else null;
209 const use_lld = if (a.no_llvm) false else null;
210
211 // Options
212 const build_config = b.addOptions();
213 build_config.addOption(BuildConfig, "config", config);
214 const build_config_mod = build_config.createModule();
215
216 const exe = b.addExecutable(.{
217 .name = "compiler",
218 .root_source_file = b.path("src/cli.zig"),
219 .target = a.target,
220 .optimize = a.optimize,
221 .use_lld = use_lld,
222 .use_llvm = use_llvm,
223 .strip = false,
224 .omit_frame_pointer = false,
225 .single_threaded = true,
226 });
227 exe.root_module.addImport("build_options", a.opts_mod);
228 exe.root_module.addImport("build_config", build_config_mod);
229 exe.root_module.addImport("core", a.core);
230 exe.root_module.addImport("tracer", b.dependency("tracer", dep_opts).module("tracer"));
231
232 if (config.have_embedded_quickjs) {
233 exe.root_module.addImport("quickjs", b.dependency("quickjs", dep_opts).module("quickjs_libc"));
234 }
235523
236 return exe;524 const all_step = b.step("all", "Execute all of the above");
525 const steps = b.top_level_steps.values();
526 for (steps[0 .. steps.len - 1]) |step| all_step.dependOn(&step.step);
237 }527 }
238};528}
239
240fn buildWithTarget(
241 b: *Build,
242 target: Build.ResolvedTarget,
243 optimize: std.builtin.OptimizeMode,
244 tracing: TracingMode,
245 version: std.SemanticVersion,
246 no_llvm: bool,
247) Artifacts {
248 const dep_opts = .{ .target = target, .optimize = optimize };
249 const use_llvm = if (no_llvm) false else null;
250 const use_lld = if (no_llvm) false else null;
251
252 // Dependencies
253 const zg = b.dependency("zg", dep_opts);
254 const tracer = b.dependency("tracer", dep_opts);
255
256 // Options
257 const opts = b.addOptions();
258 opts.addOption(std.SemanticVersion, "version", version);
259 opts.addOption([]const u8, "version_string", b.fmt("{}", .{version}));
260 opts.addOption([]const u8, "tracing", @tagName(tracing));
261 const opts_mod = opts.createModule();
262
263 // Core
264 const core = b.addModule("core", .{
265 .root_source_file = b.path("src/core/mod.zig"),
266 .optimize = optimize,
267 .target = target,
268 .strip = false,
269 });
270 core.addImport("build_options", opts_mod);
271 core.addImport("tracer", tracer.module("tracer"));
272 core.addImport("zg:PropsData", zg.module("PropsData"));
273529
274 // Core Unit Tests530fn crossTarget(cpu_arch: std.Target.Cpu.Arch, os_tag: std.Target.Os.Tag) Build.ResolvedTarget {
275 const core_tests = b.addTest(.{531 const query: std.Target.Query = .{
276 .root_source_file = b.path("src/core/mod.zig"),532 .cpu_arch = cpu_arch,
277 .target = target,533 .os_tag = os_tag,
278 .optimize = optimize,
279 .use_lld = use_lld,
280 .use_llvm = use_llvm,
281 .strip = false,
282 });
283 core_tests.root_module.addImport("build_options", opts_mod);
284 core_tests.root_module.addImport("tracer", tracer.module("tracer"));
285 core_tests.root_module.addImport("zg:PropsData", zg.module("PropsData"));
286534
287 // Loader535 .cpu_model = switch (cpu_arch) {
288 const loader = b.addExecutable(.{536 .aarch64 => switch (os_tag) {
289 .name = "loader",537 .macos => .{ .explicit = &std.Target.aarch64.cpu.apple_m1 },
290 .root_source_file = b.path("src/loader/main.zig"),538 else => .baseline,
291 .target = target,539 },
292 .optimize = optimize,540 else => .baseline,
293 .use_lld = use_lld,541 },
294 .use_llvm = use_llvm,
295 .strip = false,
296 });
297542
298 const behavior_tests = b.addExecutable(.{543 .os_version_min = switch (os_tag) {
299 .name = "run_behavior_tests",544 .linux => .{ .semver = .{ .major = 4, .minor = 19, .patch = 0 } },
300 .root_source_file = b.path("test/behavior.zig"),545 .macos => .{ .semver = .{ .major = 11, .minor = 0, .patch = 0 } },
301 .target = target,546 .windows => .{ .windows = .win7 },
302 .optimize = optimize,547 else => @panic("unhandled"),
303 .use_lld = use_lld,548 },
304 .use_llvm = use_llvm,
305 });
306 behavior_tests.root_module.addImport("core", core);
307 behavior_tests.root_module.addImport("build_options", opts_mod);
308 behavior_tests.root_module.addImport("tracer", tracer.module("tracer"));
309 behavior_tests.root_module.addImport("quickjs", b.dependency("quickjs", dep_opts).module("quickjs"));
310549
550 .abi = switch (os_tag) {
551 .linux => .musl,
552 .macos => .none,
553 .windows => .gnu, // cannot cross compile .msvc
554 else => @panic("unhandled"),
555 },
556 };
311 return .{557 return .{
312 .no_llvm = no_llvm,558 .query = query,
313 .target = target,559 .result = std.zig.system.resolveTargetQuery(query) catch unreachable,
314 .optimize = optimize,
315
316 .core = core,
317 .core_tests = core_tests,
318 .opts_mod = opts_mod,
319 .behavior_tests = behavior_tests,
320 .loader_bin = loader,
321 };560 };
322}561}
323562
...@@ -328,7 +567,8 @@ pub const PathOrString = union(enum) {...@@ -328,7 +567,8 @@ pub const PathOrString = union(enum) {
328567
329const std = @import("std");568const std = @import("std");
330const Build = std.Build;569const Build = std.Build;
570const Step = Build.Step;
571const Module = Build.Module;
331const LazyPath = Build.LazyPath;572const LazyPath = Build.LazyPath;
332const Target = std.Target;573const Target = std.Target;
333const OsVersion = Target.Query.OsVersion;574const OsVersion = Target.Query.OsVersion;
334const BuildConfig = @import("src/BuildConfig.zig");
build.zig.zon+4-1
...@@ -2,17 +2,20 @@...@@ -2,17 +2,20 @@
2 .name = "",2 .name = "",
3 .version = "0.0.0",3 .version = "0.0.0",
44
5 .minimum_zig_version = "0.13.0",5 .minimum_zig_version = "0.14.0-dev.1472+3929cac15",
66
7 .dependencies = .{7 .dependencies = .{
8 // Used to determine unicode properties to tokenize identifiers
8 .zg = .{9 .zg = .{
9 .url = "https://codeberg.org/dude_the_builder/zg/archive/c425c9c8511bf92e14b8b612d1d16e774b186f2e.tar.gz",10 .url = "https://codeberg.org/dude_the_builder/zg/archive/c425c9c8511bf92e14b8b612d1d16e774b186f2e.tar.gz",
10 .hash = "1220c79009f1e443b015db4de6e5319432538ead2fea10d271f3f35d3ae83c996789",11 .hash = "1220c79009f1e443b015db4de6e5319432538ead2fea10d271f3f35d3ae83c996789",
11 },12 },
13 // Optional: Used for tracing
12 .tracer = .{14 .tracer = .{
13 .url = "https://github.com/paperdave/zig-tracer/archive/d4c2eff040e9e69b9f08d2175b315f3055ad7ba3.tar.gz",15 .url = "https://github.com/paperdave/zig-tracer/archive/d4c2eff040e9e69b9f08d2175b315f3055ad7ba3.tar.gz",
14 .hash = "12202d60766a46ceb4074ce5651a58a11f2f6b32927476a55c6889805073e8e3d261",16 .hash = "12202d60766a46ceb4074ce5651a58a11f2f6b32927476a55c6889805073e8e3d261",
15 },17 },
18 // Optional: Used for JavaScript behavior tests
16 .quickjs = .{19 .quickjs = .{
17 .path = "src/deps/quickjs",20 .path = "src/deps/quickjs",
18 },21 },
doc/architecture.md+55-6
...@@ -1,13 +1,62 @@...@@ -1,13 +1,62 @@
1# Architecture1# Architecture
22
3This document will outline the code structure of the language's SDK as well as the compilation pipeline architecture. The major components of the language are:3This document will outline the code structure of the language's SDK as well as
4the compilation pipeline architecture. The major components of the language are:
45
5- [**Core**][core] (`src/core`)6- [**Core**][#core] (`src/core`) includes the parser, analysis passes, codegen
6- [**SDK Manual Generator**][manual] (`src/manual`)7 backends, and generating all warnings and errors.
8- [**Manual Generator**][#manual] (`src/manual`) is a custom static site
9 generator used for building the SDK's documentation site.
710
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`.11Keep in mind that **code is the best documentation for specific details**, and
12this document aims to give an overall explainer of the codebase as a whole.
913
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.14General code style is to sort the file's contents by importance. Most files will
15start with an overview comment, followed by the root struct fields, and then
16important companion types and functions, then helpers. Imports and tests are not
17as important to a reader, so those are placed at the end of the file.
1118
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.19An example of this organization is `src/core/Tokenizer.zig`, which opens with
20the tokenizer state, and then `pub fn tokenize` to bring attention to the flow
21of tokenization. The `Token` struct is directly after this, since it is
22important, but not as important as visualizing _how_ the tokenizer works.
1323
24## Testing
25
26Aside from tests written using Zig's test runner, there are a large set of tests
27within `test` which are ran using a custom test runner binary. It categorizes
28compiler tests into these categories:
29
30- **Analysis Tests** (`test/analysis/*`) verify the semantic analysis
31 engine, and must not emit any runtime `Air`. Some analysis tests may
32 expect certain compile errors.
33- **Behavior Tests** (`test/behavior/*`) generate code to be executed at
34 runtime, testing both the semantic analysis pass as well as the code
35 generation backend.
36- **Transform Error Tests** (`test/transform_error`) confirm that the transform
37 pass errors are correct. A passing `Teg` fails the test.
38
39All compiler tests can be run with `zig build test`, or the test suite can be
40filtered using flags, see `zig build test --help`
41
42To run Zig tests for an associated module, use `zig build test-{name}`, or run
43`zig build test-all` to run all tests in the codebase.
44
45## Core
46
47The `core` module contains most of the compiler:
48
49- Tokenizing and Parsing files. (`Tokenizer.zig`, `Parse.zig`, `Ast.zig`)
50- Transform Pass, moving from Ast to an untyped intermediate representation
51 (`Transform.zig`)
52- Semantic Analysis, resolving all types and comptime values. (`Sema.zig`)
53- Intern Pool, storing and de-duplicating objects in a compilation.
54 (`InternPool.zig`)
55- JavaScript Code Generation (`js/Js.zig`)
56
57Note that components like the formatter and module fetching code are not here.
58Keeping this module as small as possible is important for keeping compile times
59low. Using `zig build compiler -- --help` instead of the fully featured binary
60will allow for faster and more focused iteration.
61
62## Manual
doc/language-manual.md+65-37
...@@ -4,11 +4,14 @@ This is a high-level, general purpose programming language....@@ -4,11 +4,14 @@ This is a high-level, general purpose programming language.
44
5Key goals of this project are:5Key goals of this project are:
66
7- **Ease of Use**: Use of type inference keeps code concise and easy to read and write.7- **Ease of Use**: Use of type inference keeps code concise and easy to read and
8- **Fast Iteration**: The compiler is designed for quick incremental compilation and hot module reloading. (note: incremental is not yet available)8 write.
9- **Fast Iteration**: The compiler is designed for quick incremental compilation
10 and hot module reloading. (note: incremental is not yet available)
9- TODO: some more11- TODO: some more
1012
11It primarily focuses on merging some great points of Rust, Zig, and JavaScript's language designs:13It primarily focuses on merging some great points of Rust, Zig, and JavaScript's
14language designs:
1215
13- **Rust** - Expressive control flow that is easy to write.16- **Rust** - Expressive control flow that is easy to write.
14- **Zig** - Execute any code at compile time; everything is an expression.17- **Zig** - Execute any code at compile time; everything is an expression.
...@@ -21,29 +24,34 @@ Currently, there is no standard library. One will be introduced later....@@ -21,29 +24,34 @@ Currently, there is no standard library. One will be introduced later.
21## Hello World24## Hello World
2225
23```lang:run26```lang:run
24let print = @js("console").log;27let print = @extern("console").log;
25print("Hello, world!");28
29fn main {
30 print("Hello, world!");
31}
26```32```
2733
28The `print` function is implemented by getting an external reference to the `console` JavaScript global and then retrieving the `log` property from it.34The `print` function is implemented by getting an external reference to the
35`console` JavaScript global and then retrieving the `log` property from it.
2936
30## Primitive Data Types37## Primitive Data Types
3138
32| name | ts equivalent | description |39| name | ts equivalent | description |
33| ---------- | --------------------- | ---------------------------------------------- |40| -------- | --------------------- | ---------------------------------------------- |
34| `int` | `number` | a number in signed 32-bit range |41| `int` | `number` | a number in signed 32-bit range |
35| `float` | `number` | a 64-bit floating point number |42| `float` | `number` | a 64-bit floating point number |
36| `bool` | `boolean` | either `true` or `false` |43| `bool` | `boolean` | either `true` or `false` |
37| `string` | `string` | a sequence of text characters |44| `string` | `string` | a sequence of text characters |
38| `void` | `undefined` or `null` | detectable lack of a value |45| `void` | `undefined` or `null` | detectable lack of a value |
39| `opaque` | `unknown` or `any` | could be any value |46| `opaque` | `unknown` or `any` | could be any value |
40| `noreturn` | `never` | control flow prevents this value from existing |47| `never` | `never` | control flow prevents this value from existing |
4148
42> **note**: `bigint` is planned but not added49> **note**: `bigint` is planned but not added
4350
44## Built-in Constants51## Built-in Constants
4552
46In addition to the above types being built-in constants, the following are available.53In addition to the above types being built-in constants, the following are
54available.
4755
48| name | description |56| name | description |
49| ------- | ------------------------------------- |57| ------- | ------------------------------------- |
...@@ -61,41 +69,49 @@ let true = 1 == 0;...@@ -61,41 +69,49 @@ let true = 1 == 0;
6169
62There are two types of variables, `let` and `var`.70There are two types of variables, `let` and `var`.
6371
64`let` bindings cannot be overridden, and should be thought of as aliases of the expression within. Referring to these identifiers is equivalent to inlining it where it was used, aside from side effect duplication.72`let` bindings cannot be overridden, and should be thought of as aliases of the
73expression within. Referring to these identifiers is equivalent to inlining it
74where it was used, aside from side effect duplication.
6575
66```lang:run76```lang:run
67let hello: int = 1;77let hello: int = 1;
68let world = hello + 2;78let world = hello + 2;
69let hello = hello * world;79let hello = hello * world;
7080
71@js("console").log(hello);81@extern("console").log(hello);
72```82```
7383
74`var` bindings create mutable memory locations at runtime. This means they can be re-assigned to:84`var` bindings create mutable memory locations at runtime. This means they can
85be re-assigned to:
7586
76```lang:run87```lang:run
77var message = "first message";88var message = "first message";
7889
79if @js("Math").random() > 0.5 {90if @extern("Math").random() > 0.5 {
80 message = "second message";91 message = "second message";
81}92}
8293
83@js("console").log(message);94@extern("console").log(message);
84```95```
8596
86Using `let` is preferred whenever possible, as it will lead to more readable code, and the compiler is able to optimize it better. The above can be rewritten as:97Using `let` is preferred whenever possible, as it will lead to more readable
98code, and the compiler is able to optimize it better. The above can be rewritten
99as:
87100
88```lang:run101```lang:run
89let message = if @js("Math").random() > 0.5 {102let message = if @extern("Math").random() > 0.5 {
90 "second message"103 "second message"
91} else {104} else {
92 "first message"105 "first message"
93};106};
94107
95@js("console").log(message);108@extern("console").log(message);
96```109```
97110
98It should be noted that numeric literals are a comptime-only data type, meaning it cannot be assigned to a `var` without an explicit type annotation. Since `int` is a strict subset of valid `float`s, the coercion will implicitly be a float, but a compiler warning will be emitted.111It should be noted that numeric literals are a comptime-only data type, meaning
112it cannot be assigned to a `var` without an explicit type annotation. Since
113`int` is a strict subset of valid `float`s, the coercion will implicitly be a
114float, but a compiler warning will be emitted.
99115
100```lang116```lang
101var number = 0; // `number` is a `float`, but not explicit117var number = 0; // `number` is a `float`, but not explicit
...@@ -105,18 +121,21 @@ var number: float = 0; // `number` is `float`...@@ -105,18 +121,21 @@ var number: float = 0; // `number` is `float`
105121
106## Functions122## Functions
107123
108Functions are declared with the `fn` keyword. Parameter types must be annotated. The function body is a block, which may use implicit return to return.124Functions are declared with the `fn` keyword. Parameter types must be annotated.
125The function body is a block, which may use implicit return to return.
109126
110```lang:run127```lang:run
111fn add(a: int, b: int) -> int {128fn add(a: int, b: int) -> int {
112 a + b129 a + b
113}130}
114131
115let print = @js("console").log;132let print = @extern("console").log;
116print("Sum of 2 and 7:", add(2, 7));133print("Sum of 2 and 7:", add(2, 7));
117```134```
118135
119> **Bug**: Return types are currently required. Omitting the `-> type` syntax currently makes the return type `void`, but this will be changed in the future to be an inferred return type.136> **Bug**: Return types are currently required. Omitting the `-> type` syntax
137> currently makes the return type `void`, but this will be changed in the future
138> to be an inferred return type.
120139
121The `return` expression can be used within functions to return early.140The `return` expression can be used within functions to return early.
122141
...@@ -129,7 +148,7 @@ fn something(a: int) -> bool {...@@ -129,7 +148,7 @@ fn something(a: int) -> bool {
129 a > 100148 a > 100
130}149}
131150
132let print = @js("console").log;151let print = @extern("console").log;
133print(something(0xFF), something(0));152print(something(0xFF), something(0));
134```153```
135154
...@@ -143,28 +162,35 @@ TODO...@@ -143,28 +162,35 @@ TODO
143162
144## JavaScript Interop163## JavaScript Interop
145164
146A global variable handle can be retrieved with `@js`. We've seen this in the hello world. The type of this value is a special type called `opaque`, which disables type-checking.165A global variable handle can be retrieved with `@extern`. We've seen this in the
166hello world. The type of this value is a special type called `opaque`, which
167disables type-checking.
147168
148> **Note**: A future update will improve on the type-safety of `opaque`169> **Note**: A future update will improve on the type-safety of `opaque`
149170
150Many JS-exclusive actions are exposed via built-in functions, such as using `@jsNew` for calling constructors.171Many JS-exclusive actions are exposed via built-in functions, such as using
172`@jsNew` for calling constructors.
151173
152```lang:run174```lang:run
153let print = @js("console").log;175let print = @extern("console").log;
154176
155fn currentYear() -> int {177fn currentYear() -> int {
156 @jsNew(@js("Date")).getFullYear()178 @jsNew(@extern("Date")).getFullYear()
157}179}
158180
159print(currentYear());181print(currentYear());
160```182```
161183
184A field access on an external handle is allowed to be performed at compile time,
185where such access will only crash at runtime when the value is used.
186
162## Structs187## Structs
163188
164To group data together, a `struct` can be used. A struct contains zero or one fields of runtime-compatible types.189To group data together, a `struct` can be used. A struct contains zero or one
190fields of runtime-compatible types.
165191
166```lang:run192```lang:run
167let print = @js("console").log;193let print = @extern("console").log;
168194
169struct Position {195struct Position {
170 x: int,196 x: int,
...@@ -186,7 +212,9 @@ let added = addPositions(...@@ -186,7 +212,9 @@ let added = addPositions(
186print("Add result:", added.x, added.y);212print("Add result:", added.x, added.y);
187```213```
188214
189To 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.215To allow optimizations, `struct`s by default do not have a defined
216representation in JavaScript. This means passing the struct to an opaque
217function is not allowed.
190218
191```lang:build219```lang:build
192struct Person {220struct Person {
...@@ -194,6 +222,6 @@ struct Person {...@@ -194,6 +222,6 @@ struct Person {
194 age: int,222 age: int,
195}223}
196224
197let print = @js("console").log;225let print = @extern("console").log;
198print(Person{ name: "hello", age: 42 });226print(Person{ name: "hello", age: 42 });
199```227```
dprint.jsonc created+26
...@@ -0,0 +1,26 @@
1{
2 "plugins": [
3 // sort-lines: start
4 "https://plugins.dprint.dev/exec-0.5.0.json@8d9972eee71fa1590e04873540421f3eda7674d0f1aae3d7c788615e7b7413d0",
5 "https://plugins.dprint.dev/json-0.19.3.wasm",
6 "https://plugins.dprint.dev/markdown-0.17.2.wasm",
7 // sort-lines: end
8 ],
9 "excludes": [
10 "**/.zig-cache",
11 "**/zig-out",
12 "CHANGELOG.md", // no wrapping, one item per line
13 ],
14 "markdown": {
15 "textWrap": "always",
16 },
17 "exec": {
18 "cwd": "${configDir}",
19 "commands": [
20 {
21 "command": "zig fmt --stdin",
22 "exts": ["zig", "zon"],
23 },
24 ],
25 },
26}
hello created+6
...@@ -0,0 +1,6 @@
1fn runtime() -> int {
2 542
3}
4
5var abc: int = 0;
6abc = runtime();
out.js created+21
...@@ -0,0 +1,21 @@
1// Language Compiler v0.1.6. Alpha quality software.
2let addFloat = (v0, v1) => v0 + v1;
3let f = v0 => v0;
4let subFloat = (v0, v1) => v0 - v1;
5let mulFloat = (v0, v1) => v0 * v1;
6let divFloat = (v0, v1) => v0 / v1;
7let addInt = (v0, v1) => v0 + v1;
8let i = v0 => v0;
9let subInt = (v0, v1) => v0 - v1;
10assertEqual(addFloat(1, 2), f(3));
11assertEqual(addFloat(1.5, 2.2), f(3.7));
12assertEqual(subFloat(1, 2), f(-1));
13assertEqual(subFloat(1.5, 2.2), f(-0.7000000000000002));
14assertEqual(mulFloat(1, 2), f(2));
15assertEqual(mulFloat(1.5, 2.2), f(3.3000000000000003));
16assertEqual(divFloat(1, 2), f(0.5));
17assertEqual(divFloat(1.5, 2.2), f(0.6818181818181818));
18assertEqual(addInt(1, 2), i(3));
19assertEqual(addInt(-421, 592), i(171));
20assertEqual(subInt(1, 2), i(-1));
21assertEqual(subInt(528, -43), i(571));
src/BuildConfig.zig+8-4
...@@ -1,9 +1,13 @@...@@ -1,9 +1,13 @@
1version: std.SemanticVersion,
2tracing_mode: TracingMode = .none,
1have_embedded_quickjs: bool = false,3have_embedded_quickjs: bool = false,
24
3pub const minimal: BuildConfig = .{};5pub const TracingMode = enum {
46 none,
5pub const all: BuildConfig = .{7 spall,
6 .have_embedded_quickjs = true,8 chrome,
9 log,
7};10};
811
9const BuildConfig = @This();12const BuildConfig = @This();
13const std = @import("std");
src/cli.zig+24-13
...@@ -5,7 +5,7 @@...@@ -5,7 +5,7 @@
5//! needing to compile the formatter.5//! needing to compile the formatter.
66
7const have_debug_features = builtin.mode == .Debug;7const have_debug_features = builtin.mode == .Debug;
8const config = @import("build_config").config;8const config = @import("config");
99
10pub fn main() !u8 {10pub fn main() !u8 {
11 try tracer.init();11 try tracer.init();
...@@ -24,7 +24,8 @@ pub fn main() !u8 {...@@ -24,7 +24,8 @@ pub fn main() !u8 {
24 }){}24 }){}
25 else25 else
26 std.heap.ArenaAllocator.init(std.heap.page_allocator);26 std.heap.ArenaAllocator.init(std.heap.page_allocator);
27 defer _ = if (use_gpa) gpa.deinit();27 var no_leak_checks = false;
28 defer _ = if (use_gpa and !no_leak_checks) gpa.deinit();
28 const alloc = gpa.allocator();29 const alloc = gpa.allocator();
2930
30 // Current working directory is required for CLI resolution.31 // Current working directory is required for CLI resolution.
...@@ -48,9 +49,15 @@ pub fn main() !u8 {...@@ -48,9 +49,15 @@ pub fn main() !u8 {
4849
49 while (args.next()) |arg| {50 while (args.next()) |arg| {
50 if (std.mem.startsWith(u8, arg, "-")) {51 if (std.mem.startsWith(u8, arg, "-")) {
51 if (have_debug_features and std.mem.eql(u8, arg, "--report-trace")) {52 if (have_debug_features) {
52 core.Report.debug_show_report_trace = true;53 if (std.mem.eql(u8, arg, "--report-trace")) {
53 continue;54 core.Report.debug_show_report_trace = true;
55 continue;
56 }
57 if (std.mem.eql(u8, arg, "--no-leak-checks")) {
58 no_leak_checks = true;
59 continue;
60 }
54 }61 }
5562
56 if (std.mem.eql(u8, arg, "--dump-ir")) {63 if (std.mem.eql(u8, arg, "--dump-ir")) {
...@@ -107,9 +114,11 @@ pub fn main() !u8 {...@@ -107,9 +114,11 @@ pub fn main() !u8 {
107 \\114 \\
108 \\Debug Mode Flags:115 \\Debug Mode Flags:
109 \\ --report-trace Print stack traces per error116 \\ --report-trace Print stack traces per error
117 \\ --no-leak-checks
110 \\118 \\
111 , .{});119 , .{});
112 }120 }
121 std.debug.print("\nLanguage SDK v" ++ config.version_string ++ "\n", .{});
113 return 1;122 return 1;
114 }123 }
115 const file_path = maybe_file_path.?;124 const file_path = maybe_file_path.?;
...@@ -118,15 +127,13 @@ pub fn main() !u8 {...@@ -118,15 +127,13 @@ pub fn main() !u8 {
118 {127 {
119 var js_backend = core.Js{};128 var js_backend = core.Js{};
120129
121 var comp: core.Comp = .{130 var comp = try core.Comp.init(.{
122 .gpa = tracer.tracingAllocator(&alloc),131 .gpa = tracer.tracingAllocator(&alloc),
123 .options = .{132 .options = .{
124 .dump_ir = dump_ir,133 .dump_ir = dump_ir,
125 },134 },
126 .backend = .{ .js = &js_backend },135 .backend = .{ .js = &js_backend },
127 .out = core.OutStream.init(std.io.getStdOut().writer().any()),136 });
128 };
129 try comp.init();
130 defer comp.deinit();137 defer comp.deinit();
131138
132 defer if (dump_state) |path| {139 defer if (dump_state) |path| {
...@@ -141,7 +148,7 @@ pub fn main() !u8 {...@@ -141,7 +148,7 @@ pub fn main() !u8 {
141 const teg = main_file.getTeg(&comp, &fail_report) catch |err| switch (err) {148 const teg = main_file.getTeg(&comp, &fail_report) catch |err| switch (err) {
142 error.TransformFail => {149 error.TransformFail => {
143 std.debug.print("Transform failed:\n", .{});150 std.debug.print("Transform failed:\n", .{});
144 try fail_report.dumpToStdErr(&comp);151 try fail_report.dump(.initComp(&comp, main_file), comp.out);
145 fail_report.deinit(comp.gpa);152 fail_report.deinit(comp.gpa);
146 return 1;153 return 1;
147 },154 },
...@@ -157,8 +164,12 @@ pub fn main() !u8 {...@@ -157,8 +164,12 @@ pub fn main() !u8 {
157 try teg.dumpToWriter(stderr, null);164 try teg.dumpToWriter(stderr, null);
158 }165 }
159166
160 const entry_point = comp.semaEntryPoint(main_file) catch |err| switch (err) {167 const entry_point = try comp.enqueueEntryModule(main_file);
161 error.AnalysisFail => std.process.exit(1),168 comp.runAllJobs() catch |err| switch (err) {
169 error.AnalysisFail => {
170 // error is already printed
171 return 1;
172 },
162 else => return err,173 else => return err,
163 };174 };
164175
...@@ -229,7 +240,7 @@ const Comp = core.Comp;...@@ -229,7 +240,7 @@ const Comp = core.Comp;
229const Report = core.Report;240const Report = core.Report;
230241
231const tracer = @import("tracer");242const tracer = @import("tracer");
232pub const tracer_impl = @field(tracer, @import("build_options").tracing);243pub const tracer_impl = @field(tracer, @tagName(@import("config").tracing_mode));
233244
234const std = @import("std");245const std = @import("std");
235const builtin = @import("builtin");246const builtin = @import("builtin");
src/core/Air.Dump.zig+11-18
...@@ -55,12 +55,6 @@ pub const State = struct {...@@ -55,12 +55,6 @@ pub const State = struct {
55 const insts = index_list.slice(air);55 const insts = index_list.slice(air);
56 try s.dumpInstList(w, insts, '{', '}');56 try s.dumpInstList(w, insts, '{', '}');
57 },57 },
58 .js_global_ref => {
59 const name: InternPool.String.WithLen = air.instData(.js_global_ref, inst);
60 try tty.setColor(w, .magenta);
61 try w.print(" {s}", .{name.toSlice(s.ip)});
62 try tty.setColor(w, .reset);
63 },
64 .arg => {58 .arg => {
65 const arg: Air.Arg = air.instData(.arg, inst);59 const arg: Air.Arg = air.instData(.arg, inst);
66 try w.writeAll(" ");60 try w.writeAll(" ");
...@@ -73,7 +67,7 @@ pub const State = struct {...@@ -73,7 +67,7 @@ pub const State = struct {
73 .@"break" => {67 .@"break" => {
74 const brk: Air.Break = air.instData(.@"break", inst);68 const brk: Air.Break = air.instData(.@"break", inst);
75 try w.writeAll("(");69 try w.writeAll("(");
76 try s.dumpInstRef(w, brk.block_ref);70 try s.dumpInstRef(w, brk.block);
77 try w.writeAll(", ");71 try w.writeAll(", ");
78 try s.dumpRef(w, brk.value);72 try s.dumpRef(w, brk.value);
79 try w.writeAll(")");73 try w.writeAll(")");
...@@ -93,7 +87,8 @@ pub const State = struct {...@@ -93,7 +87,8 @@ pub const State = struct {
93 .lt,87 .lt,
94 .gt,88 .gt,
95 .eq,89 .eq,
96 .index,90 .field_array,
91 .field_opaque,
97 => |comptime_tag| {92 => |comptime_tag| {
98 const bin: Air.Bin = air.instData(comptime_tag, inst);93 const bin: Air.Bin = air.instData(comptime_tag, inst);
99 try w.writeAll("(");94 try w.writeAll("(");
...@@ -119,16 +114,6 @@ pub const State = struct {...@@ -119,16 +114,6 @@ pub const State = struct {
119 try s.dumpValue(w, memory.ty.value());114 try s.dumpValue(w, memory.ty.value());
120 try w.writeAll(")");115 try w.writeAll(")");
121 },116 },
122 .field_opaque => {
123 const field: Air.FieldOpaque = air.instData(.field_opaque, inst);
124 try w.writeAll("(");
125 try s.dumpRef(w, field.base);
126 try w.writeAll(", ");
127 try tty.setColor(w, .magenta);
128 try w.print("{s}", .{field.field.toSlice(s.ip)});
129 try tty.setColor(w, .reset);
130 try w.writeAll(")");
131 },
132 .field_struct => {117 .field_struct => {
133 const field: Air.FieldStruct = air.instData(.field_struct, inst);118 const field: Air.FieldStruct = air.instData(.field_struct, inst);
134 try w.writeAll("(");119 try w.writeAll("(");
...@@ -374,6 +359,14 @@ pub const State = struct {...@@ -374,6 +359,14 @@ pub const State = struct {
374 try w.writeAll(">");359 try w.writeAll(">");
375 try tty.setColor(w, .reset);360 try tty.setColor(w, .reset);
376 },361 },
362 .external_value => |external_value| {
363 try w.writeAll("external '");
364 for (external_value.key, 0..) |part, i| {
365 if (i != 0) try w.writeAll(".");
366 try w.writeAll(part.toSlice(s.ip));
367 }
368 try w.writeAll("'");
369 },
377 }370 }
378 }371 }
379372
src/core/Air.zig+14-15
...@@ -14,7 +14,6 @@ pub const Inst = struct {...@@ -14,7 +14,6 @@ pub const Inst = struct {
1414
15 pub const Tag = enum {15 pub const Tag = enum {
16 block,16 block,
17 js_global_ref,
18 call,17 call,
19 js_new,18 js_new,
20 @"break",19 @"break",
...@@ -29,11 +28,10 @@ pub const Inst = struct {...@@ -29,11 +28,10 @@ pub const Inst = struct {
2928
30 field_opaque,29 field_opaque,
31 field_struct,30 field_struct,
31 field_array,
3232
33 array_len,33 array_len,
3434
35 index,
36
37 // bin35 // bin
38 add,36 add,
39 sub,37 sub,
...@@ -52,7 +50,6 @@ pub const Inst = struct {...@@ -52,7 +50,6 @@ pub const Inst = struct {
52 pub fn Data(comptime tag: Tag) type {50 pub fn Data(comptime tag: Tag) type {
53 return switch (tag) {51 return switch (tag) {
54 .block => IndexList,52 .block => IndexList,
55 .js_global_ref => InternPool.String.WithLen,
56 .@"break" => Break,53 .@"break" => Break,
57 .@"if" => If,54 .@"if" => If,
58 .call, .js_new => Call,55 .call, .js_new => Call,
...@@ -61,9 +58,9 @@ pub const Inst = struct {...@@ -61,9 +58,9 @@ pub const Inst = struct {
61 .store => Store,58 .store => Store,
62 .struct_init => StructInit,59 .struct_init => StructInit,
63 .array_init => ArrayInit,60 .array_init => ArrayInit,
64 .field_opaque => FieldOpaque,
65 .field_struct => FieldStruct,61 .field_struct => FieldStruct,
66 .index,62 .field_opaque,
63 .field_array,
67 .add,64 .add,
68 .sub,65 .sub,
69 .mul,66 .mul,
...@@ -216,7 +213,8 @@ pub const RefList = struct {...@@ -216,7 +213,8 @@ pub const RefList = struct {
216};213};
217214
218pub const Break = struct {215pub const Break = struct {
219 block_ref: Inst.Index,216 block: Inst.Index,
217 // TODO: rename to operand
220 value: Inst.Ref,218 value: Inst.Ref,
221};219};
222220
...@@ -261,6 +259,7 @@ pub const If = struct {...@@ -261,6 +259,7 @@ pub const If = struct {
261 pub const Data = struct {259 pub const Data = struct {
262 yes: Inst.Ref,260 yes: Inst.Ref,
263 no: Inst.Ref,261 no: Inst.Ref,
262 // TODO: delete this. both refs should be coerced to the common type
264 union_ty: Type,263 union_ty: Type,
265 };264 };
266};265};
...@@ -328,11 +327,11 @@ pub fn typeOf(air: *const Air, ref: Inst.Ref, ip: *const InternPool) InternPool....@@ -328,11 +327,11 @@ pub fn typeOf(air: *const Air, ref: Inst.Ref, ip: *const InternPool) InternPool.
328pub fn typeOfIndex(air: *const Air, index: Inst.Index, ip: *const InternPool) InternPool.Type {327pub fn typeOfIndex(air: *const Air, index: Inst.Index, ip: *const InternPool) InternPool.Type {
329 return switch (air.instTag(index)) {328 return switch (air.instTag(index)) {
330 // These values are by definition, opaque329 // These values are by definition, opaque
331 .js_global_ref, .js_new => .@"opaque",330 .js_new => .@"opaque",
332331
333 // These operations always produce known types332 // These operations always produce known types
334 .lt, .gt, .eq, .bool_not => .bool,333 .lt, .gt, .eq, .bool_not => .bool,
335 .@"break" => .noreturn,334 .@"break" => .never,
336 .field_opaque => .@"opaque",335 .field_opaque => .@"opaque",
337 .array_len => .int32,336 .array_len => .int32,
338337
...@@ -353,7 +352,7 @@ pub fn typeOfIndex(air: *const Air, index: Inst.Index, ip: *const InternPool) In...@@ -353,7 +352,7 @@ pub fn typeOfIndex(air: *const Air, index: Inst.Index, ip: *const InternPool) In
353 .div,352 .div,
354 => |t| {353 => |t| {
355 const left = air.typeOf(air.instData(t, index).lhs, ip);354 const left = air.typeOf(air.instData(t, index).lhs, ip);
356 if (std.debug.runtime_safety) {355 if (core.runtime_safety) {
357 assert(left == air.typeOf(air.instData(t, index).rhs, ip));356 assert(left == air.typeOf(air.instData(t, index).rhs, ip));
358 }357 }
359 return left;358 return left;
...@@ -362,7 +361,7 @@ pub fn typeOfIndex(air: *const Air, index: Inst.Index, ip: *const InternPool) In...@@ -362,7 +361,7 @@ pub fn typeOfIndex(air: *const Air, index: Inst.Index, ip: *const InternPool) In
362 .negate => air.typeOf(air.instData(.negate, index).operand, ip),361 .negate => air.typeOf(air.instData(.negate, index).operand, ip),
363362
364 // TODO: taking the last instruction is likely wrong.363 // TODO: taking the last instruction is likely wrong.
365 // The last instruction is going to be a .break and be of type .noreturn364 // The last instruction is going to be a .break and be of type .never
366 .block => {365 .block => {
367 const items = air.instData(.block, index).slice(air);366 const items = air.instData(.block, index).slice(air);
368 assert(items.len > 0); // empty blocks should always be `Value.void`367 assert(items.len > 0); // empty blocks should always be `Value.void`
...@@ -386,10 +385,10 @@ pub fn typeOfIndex(air: *const Air, index: Inst.Index, ip: *const InternPool) In...@@ -386,10 +385,10 @@ pub fn typeOfIndex(air: *const Air, index: Inst.Index, ip: *const InternPool) In
386 },385 },
387386
388 // Array accesses use the array they are accessing into.387 // Array accesses use the array they are accessing into.
389 .index => {388 .field_array => {
390 const inst = air.instData(.index, index);389 const inst = air.instData(.field_array, index);
391 const base_type = air.typeOf(inst.lhs, ip)390 const base_type = air.typeOf(inst.lhs, ip)
392 .toArrayType(ip) orelse unreachable; // index demands array type391 .toArrayType(ip) orelse unreachable; // field_array demands array type
393 return base_type.item_type;392 return base_type.item_type;
394 },393 },
395 };394 };
...@@ -399,7 +398,7 @@ pub fn paramCount(air: Air) u32 {...@@ -399,7 +398,7 @@ pub fn paramCount(air: Air) u32 {
399 for (air.insts.items(.tag)[1..], 0..) |tag, i| {398 for (air.insts.items(.tag)[1..], 0..) |tag, i| {
400 if (tag != .arg) return @intCast(i);399 if (tag != .arg) return @intCast(i);
401 }400 }
402 return @intCast(air.insts.len);401 return 0;
403}402}
404403
405pub fn dumpToWriter(air: Air, ip: *InternPool, out: core.OutStream) !void {404pub fn dumpToWriter(air: Air, ip: *InternPool, out: core.OutStream) !void {
src/core/Ast.Dump.zig+13
...@@ -188,6 +188,19 @@ fn dumpNode(writer: anytype, state: *State, node: NodeIndex) @TypeOf(writer).Err...@@ -188,6 +188,19 @@ fn dumpNode(writer: anytype, state: *State, node: NodeIndex) @TypeOf(writer).Err
188 try state.newline(writer);188 try state.newline(writer);
189 try writer.writeAll(")");189 try writer.writeAll(")");
190 },190 },
191 .test_decl => {
192 try writer.writeAll(" (");
193 if (data.test_decl.doc_test_name.unwrap()) |doc_test_name| {
194 state.indent += 1;
195 defer state.indent -= 1;
196 try state.newline(writer);
197 try dumpNode(writer, state, doc_test_name);
198 try state.newline(writer);
199 }
200 try dumpNode(writer, state, data.bin.rhs);
201 try state.newline(writer);
202 try writer.writeAll(")");
203 },
191 .un => {204 .un => {
192 try writer.writeAll(" (");205 try writer.writeAll(" (");
193 {206 {
src/core/Ast.zig+17-4
...@@ -51,7 +51,6 @@ pub const Node = struct {...@@ -51,7 +51,6 @@ pub const Node = struct {
51 pub const Tag = enum {51 pub const Tag = enum {
52 /// Read with `ast.rootNodes()`52 /// Read with `ast.rootNodes()`
53 /// data is .list (containing stmt slice)53 /// data is .list (containing stmt slice)
54 /// TODO: rename to `module`
55 root,54 root,
56 /// main_token is the {55 /// main_token is the {
57 /// data is .list (containing stmt slice)56 /// data is .list (containing stmt slice)
...@@ -82,6 +81,8 @@ pub const Node = struct {...@@ -82,6 +81,8 @@ pub const Node = struct {
82 /// 'name: type' declaration81 /// 'name: type' declaration
83 /// Main token is the identifier82 /// Main token is the identifier
84 param_decl,83 param_decl,
84 ///
85 test_decl,
85 /// An identifier. main_token is the identifier.86 /// An identifier. main_token is the identifier.
86 id,87 id,
87 /// Main token is the data. May not fit in i32 range,88 /// Main token is the data. May not fit in i32 range,
...@@ -302,6 +303,7 @@ pub const Node = struct {...@@ -302,6 +303,7 @@ pub const Node = struct {
302 .bool_not = .un,303 .bool_not = .un,
303 .builtin_call = .list,304 .builtin_call = .list,
304 .call = .call,305 .call = .call,
306 .test_decl = .test_decl,
305 .div = .bin,307 .div = .bin,
306 .div_assign = .bin,308 .div_assign = .bin,
307 .eq = .bin,309 .eq = .bin,
...@@ -361,6 +363,10 @@ pub const Node = struct {...@@ -361,6 +363,10 @@ pub const Node = struct {
361 type: OptionalNodeIndex,363 type: OptionalNodeIndex,
362 init: NodeIndex,364 init: NodeIndex,
363 },365 },
366 test_decl: struct {
367 doc_test_name: OptionalNodeIndex,
368 block: NodeIndex,
369 },
364 param_decl: struct {370 param_decl: struct {
365 type: OptionalNodeIndex,371 type: OptionalNodeIndex,
366 unused: Unused = .zero,372 unused: Unused = .zero,
...@@ -588,7 +594,6 @@ pub fn fullFnDecl(ast: Ast, node: NodeIndex) full.FnDecl {...@@ -588,7 +594,6 @@ pub fn fullFnDecl(ast: Ast, node: NodeIndex) full.FnDecl {
588 const public = tok_tags[main_token -| 1] == .kw_pub;594 const public = tok_tags[main_token -| 1] == .kw_pub;
589 assert(tok_tags[main_token] == .kw_fn);595 assert(tok_tags[main_token] == .kw_fn);
590 assert(tok_tags[main_token + 1] == .id);596 assert(tok_tags[main_token + 1] == .id);
591 assert(tok_tags[main_token + 2] == .start_paren);
592 const id = ast.tokenSlice(main_token + 1);597 const id = ast.tokenSlice(main_token + 1);
593598
594 const data = ast.nodes.items(.data)[node].function;599 const data = ast.nodes.items(.data)[node].function;
...@@ -653,7 +658,7 @@ pub fn fullVarDecl(ast: Ast, node: NodeIndex) full.VarDecl {...@@ -653,7 +658,7 @@ pub fn fullVarDecl(ast: Ast, node: NodeIndex) full.VarDecl {
653 .kw_let => true,658 .kw_let => true,
654 .kw_var => false,659 .kw_var => false,
655 else => |tag| {660 else => |tag| {
656 if (std.debug.runtime_safety) {661 if (core.runtime_safety) {
657 std.debug.panic("Expected var decl, got {s}", .{@tagName(tag)});662 std.debug.panic("Expected var decl, got {s}", .{@tagName(tag)});
658 }663 }
659 unreachable;664 unreachable;
...@@ -733,6 +738,14 @@ pub fn tokenLocation(self: Ast, start_offset: ByteOffset, token_index: TokenInde...@@ -733,6 +738,14 @@ pub fn tokenLocation(self: Ast, start_offset: ByteOffset, token_index: TokenInde
733 return Tokenizer.tokenLocation(self.utf8_source, start_offset, token_start);738 return Tokenizer.tokenLocation(self.utf8_source, start_offset, token_start);
734}739}
735740
741pub fn tokenTag(ast: Ast, token_index: TokenIndex) Token.Tag {
742 return ast.tokens.items(.tag)[token_index];
743}
744
745pub fn tokenOffset(ast: Ast, token_index: TokenIndex) u32 {
746 return ast.tokens.items(.offset)[token_index];
747}
748
736pub fn tokenSlice(ast: Ast, token_index: TokenIndex) []const u8 {749pub fn tokenSlice(ast: Ast, token_index: TokenIndex) []const u8 {
737 return ast.tokens.get(token_index).slice(ast.utf8_source);750 return ast.tokens.get(token_index).slice(ast.utf8_source);
738}751}
...@@ -805,7 +818,7 @@ const Report = core.Report;...@@ -805,7 +818,7 @@ const Report = core.Report;
805/// based offsets, use this type to reduce mistakes. A common usage of this818/// based offsets, use this type to reduce mistakes. A common usage of this
806/// would be line and column numbers for `Report.Item`s819/// would be line and column numbers for `Report.Item`s
807pub fn Ordinal(Int: type) type {820pub fn Ordinal(Int: type) type {
808 comptime assert(@typeInfo(Int).Int.signedness == .unsigned);821 comptime assert(@typeInfo(Int).int.signedness == .unsigned);
809 return enum(Int) {822 return enum(Int) {
810 start = 0,823 start = 0,
811 _,824 _,
src/core/Cache.zig+17
...@@ -7,3 +7,20 @@ pub const BinDigest = [bin_digest_len]u8;...@@ -7,3 +7,20 @@ pub const BinDigest = [bin_digest_len]u8;
7pub const HexDigest = [hex_digest_len]u8;7pub const HexDigest = [hex_digest_len]u8;
88
9pub const HashHelper = std.Build.Cache.HashHelper;9pub const HashHelper = std.Build.Cache.HashHelper;
10
11/// From Zig std.Build.Cache:
12/// The type used for hashing file contents. Currently, this is SipHash128(1, 3), because it
13/// provides enough collision resistance for the Manifest use cases, while being one of our
14/// fastest options right now.
15pub const Hasher = std.crypto.auth.siphash.SipHash128(1, 3);
16
17/// From Zig std.Build.Cache:
18/// Initial state with random bytes, that can be copied.
19/// Refresh this with new random bytes when the manifest
20/// format is modified in a non-backwards-compatible way.
21pub const hasher_init: Hasher = Hasher.init(&[_]u8{
22 0x33, 0x52, 0xa2, 0x84,
23 0xcf, 0x17, 0x56, 0x57,
24 0x01, 0xbb, 0xcd, 0xe4,
25 0x77, 0xd6, 0xf0, 0x60,
26});
src/core/Comp.zig+227-117
...@@ -3,72 +3,223 @@...@@ -3,72 +3,223 @@
3//! This is all of the state for a compilation, which starts at one or more URLs3//! 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.4//! (entry points), and will eventually emit one or more object files.
55
6/// General purpose allocator6/// General purpose and thread-safe allocator.
7gpa: Allocator,7gpa: Allocator,
8/// Stores all Type and Value objects.8/// Stores all Type and Value objects.
9intern_pool: InternPool = .{},9intern_pool: InternPool,
10/// All files in the compilation10/// Frontend options.
11/// TODO: turn this into a HashMap(*File, void) as File owns it's key11options: Options,
12files: std.StringHashMapUnmanaged(*File) = .{},12/// All text output is printed here. A typical compiler binary will set this to
1313/// standard error, but a testing or web interface may want stronger control
14/// over the output.
14out: core.OutStream,15out: core.OutStream,
16/// Backend and it's configuration.
17backend: Backend,
1518
16options: Options = .{},19/// All files in the compilation
20// TODO: turn this into a HashMap(*File, void) as File owns it's key
21files: std.StringHashMapUnmanaged(*File),
1722
18backend: Backend,23ongoing_analysis: std.AutoHashMapUnmanaged(InternPool.AnalysisUnit, *Sema),
24
25jobs: LinearFifo(Job, .Dynamic),
1926
20// TODO: remove when it is possible to return `Report`s nicely27// TODO: remove when it is possible to return `Report`s nicely
21temp_flag_had_errors: bool = false,28temp_flag_had_errors: bool,
29
30const WorkItem = union(enum) {
31 sema: *Sema,
32 code_gen: *Air,
33};
34
35const InitOptions = struct {
36 gpa: Allocator,
37 options: Options = .{},
38 backend: Backend,
39 out: ?core.OutStream = null,
40};
41
42pub fn init(opts: InitOptions) !Comp {
43 var comp: Comp = .{
44 .gpa = opts.gpa,
45 .options = opts.options,
46 .backend = opts.backend,
47 .out = opts.out orelse .getStderr(),
48 .files = .empty,
49 .temp_flag_had_errors = false,
50 .intern_pool = .{},
51 .jobs = .init(opts.gpa),
52 .ongoing_analysis = .empty,
53 };
2254
23pub fn init(comp: *Comp) !void {
24 try comp.intern_pool.init(comp.gpa);55 try comp.intern_pool.init(comp.gpa);
56
57 return comp;
25}58}
2659
27pub fn deinit(comp: *Comp) void {60pub fn deinit(comp: *Comp) void {
28 comp.intern_pool.deinit(comp.gpa);61 {
29 var it = comp.files.valueIterator();62 var it = comp.ongoing_analysis.valueIterator();
30 while (it.next()) |file| {63 while (it.next()) |sema| {
31 file.*.deinit(comp.gpa);64 _ = sema.*.deinit(.free_all);
65 comp.gpa.destroy(sema.*);
66 }
67 comp.ongoing_analysis.deinit(comp.gpa);
32 }68 }
33 comp.files.deinit(comp.gpa);69 {
70 var it = comp.files.valueIterator();
71 while (it.next()) |file| {
72 file.*.deinit(comp.gpa);
73 }
74 comp.files.deinit(comp.gpa);
75 }
76 comp.intern_pool.deinit(comp.gpa);
34 comp.backend.deinit(comp);77 comp.backend.deinit(comp);
78 comp.jobs.deinit();
35}79}
3680
37/// This is the entry point for compilation.81const Job = union(enum) {
38/// Returns the Decl of the top-level code.82 analysis: struct {
39pub fn semaEntryPoint(comp: *Comp, file: *File) !Decl.Index {83 au: InternPool.AnalysisUnit,
84 first_blocked_sema: ?*Sema = null,
85 },
86 codegen_func: struct {
87 value: InternPool.Value,
88 air: Air,
89 },
90
91 pub fn dump(job: *const Job, ip: *const InternPool) std.fmt.Formatter(formatImpl) {
92 return .{ .data = .{ .job = job, .ip = ip } };
93 }
94
95 fn formatImpl(
96 data: struct { job: *const Job, ip: *const InternPool },
97 comptime unused: []const u8,
98 _: std.fmt.FormatOptions,
99 w: std.io.AnyWriter,
100 ) !void {
101 comptime assert(unused.len == 0);
102 try w.print(".{s}(", .{@tagName(data.job.*)});
103 switch (data.job.*) {
104 .analysis => |detail| try w.print("{}", .{detail.au.dump(data.ip)}),
105 .codegen_func => |detail| try w.print("{}", .{detail.value.dump(data.ip)}),
106 }
107 try w.writeAll(")");
108 }
109};
110
111pub fn enqueueEntryModule(comp: *Comp, file: *File) !Decl.Index {
40 var report: Report = undefined;112 var report: Report = undefined;
41 const teg = try file.getTeg(comp, &report);113 const teg = try file.getTeg(comp, &report);
42114
43 // TODO, move this elsewhere. we should print all errors and warnings at once115 // TODO, move this elsewhere. we should print all errors and warnings at once
44 if (teg.report.warnings.len > 0) {116 if (teg.report.warnings.len > 0) {
45 try teg.report.dumpToWriter(comp, comp.out.writer, comp.out.tty);117 try teg.report.dump(.initComp(comp, file), comp.out);
46 }118 }
47119
48 const ns = try comp.indexTegModule(file, .main_module_inst);120 const ns = try comp.indexTegModule(file, .main_module_inst);
49 const decl_index = comp.intern_pool.namespacePtr(ns).lookupDirect(comp, .empty) orelse121 const main_str = try comp.intern_pool.getOrPutString(comp.gpa, "main", .no_embedded_nulls);
50 unreachable; // Transform should always emit a top-level function122 const decl_index = comp.intern_pool.namespacePtr(ns).lookupDirect(comp, main_str) orelse
51 const decl = comp.intern_pool.declPtr(decl_index);123 @panic("missing 'fn main' in root module");
52124
53 var arena = std.heap.ArenaAllocator.init(comp.gpa);125 try comp.jobs.writeItem(.{ .analysis = .{ .au = .wrap(.{ .decl = decl_index }) } });
54 defer arena.deinit();126
55127 return decl_index; // root decl is needed for linking
56 var sema: Sema = .{128}
57 .comp = comp,129
58 .gpa = comp.gpa,130pub fn runAllJobs(comp: *Comp) !void {
59 .code = teg,131 while (comp.jobs.readItem()) |job| {
60 .arena = arena.allocator(),132 // log.info("processing {}", .{job.dump(&comp.intern_pool)});
61 .ip = &comp.intern_pool,133 switch (job) {
62 .decl = decl,134 .analysis => |detail| try comp.runAnalysisJob(detail.au, detail.first_blocked_sema),
63 .decl_index = decl_index,135 .codegen_func => |detail| try comp.runCodegenJob(detail.value, detail.air),
64 .namespace_index = ns,136 }
137 }
138
139 assert(comp.ongoing_analysis.size == 0);
140}
141
142const AnalysisJobError = Allocator.Error || error{ AnalysisFail, TooManyValues };
143
144fn runAnalysisJob(comp: *Comp, unit: InternPool.AnalysisUnit, first_blocked_sema: ?*Sema) AnalysisJobError!void {
145 const gop = try comp.ongoing_analysis.getOrPut(comp.gpa, unit);
146 if (!gop.found_existing) {
147 errdefer comp.ongoing_analysis.removeByPtr(gop.key_ptr);
148
149 const sema = try comp.gpa.create(Sema);
150 const decl_index = switch (unit.unwrap()) {
151 .decl => |index| index,
152 .func => |value| comp.intern_pool.valueToKey(value).func.owner_decl,
153 };
154 const decl = comp.intern_pool.declPtr(decl_index);
155 const file = comp.intern_pool.namespacePtr(decl.src_namespace).file_scope;
156 const teg = file.teg orelse @panic("TODO: make sure teg is loaded to avoid annoying error set?");
157
158 try sema.init(
159 comp,
160 file,
161 teg,
162 unit,
163 decl.src_namespace,
164 std.heap.ArenaAllocator.init(comp.gpa),
165 );
166 errdefer _ = sema.deinit(.free_all);
167 gop.value_ptr.* = sema;
168
169 if (first_blocked_sema) |blocked_sema|
170 try sema.blocked_semas.append(comp.gpa, blocked_sema);
171 } else {
172 assert(first_blocked_sema == null);
173 }
174
175 const sema: *Sema = gop.value_ptr.*;
176 sema.run() catch |err| switch (err) {
177 error.Interrupt => return,
178
179 error.OutOfMemory,
180 error.TooManyValues,
181 => |e| return e,
182
183 error.AnalysisFail => |e| {
184 const report = try sema.report.finalize();
185 defer report.deinit(comp.gpa);
186
187 report.dump(.{ .comp = .{ .comp = comp, .file = sema.file } }, comp.out) catch {};
188
189 comp.ongoing_analysis.removeByPtr(gop.key_ptr);
190 _ = sema.deinit(.free_all); // TODO: arena reuse
191 comp.gpa.destroy(sema);
192
193 return e;
194 },
65 };195 };
66 try sema.init();
67 defer sema.deinit();
68 _ = try sema.analyzeDeclaration();
69 try sema.finalize();
70196
71 return decl_index;197 comp.ongoing_analysis.removeByPtr(gop.key_ptr);
198 try comp.jobs.ensureUnusedCapacity(sema.blocked_semas.items.len);
199 for (sema.blocked_semas.items) |other_sema| {
200 comp.jobs.writeItemAssumeCapacity(.{ .analysis = .{ .au = other_sema.owner } });
201 }
202 _ = sema.deinit(.free_all); // TODO: arena reuse
203 comp.gpa.destroy(sema);
204}
205
206pub fn runCodegenJob(c: *Comp, value: InternPool.Value, air_const: Air) !void {
207 const t = tracer.trace(@src());
208 defer t.end();
209
210 var arena_state = std.heap.ArenaAllocator.init(c.gpa);
211 defer arena_state.deinit();
212 const arena = arena_state.allocator();
213
214 // TODO: put all this work on another thread
215 if (c.options.dump_ir) {
216 air_const.dumpToWriter(&c.intern_pool, c.out) catch {};
217 }
218
219 var air = air_const; // this line is a workaround
220 defer air.deinit(c.gpa);
221
222 _ = try c.backend.handleFunctionAir(c, value, air, arena);
72}223}
73224
74/// Add or retrieve a file to the compilation via it's file path.225/// Add or retrieve a file to the compilation via it's file path.
...@@ -83,7 +234,6 @@ pub fn getFile(comp: *Comp, absolute_file_path: []const u8) !*File {...@@ -83,7 +234,6 @@ pub fn getFile(comp: *Comp, absolute_file_path: []const u8) !*File {
83 .file_path = absolute_file_path,234 .file_path = absolute_file_path,
84 .path_digest = null,235 .path_digest = null,
85 .root_namespace = .none,236 .root_namespace = .none,
86 .status = .never_loaded,
87 .source = null,237 .source = null,
88 .ast = null,238 .ast = null,
89 .teg = null,239 .teg = null,
...@@ -104,17 +254,6 @@ pub const File = struct {...@@ -104,17 +254,6 @@ pub const File = struct {
104 file_path: []const u8,254 file_path: []const u8,
105255
106 root_namespace: Namespace.Index.Optional,256 root_namespace: Namespace.Index.Optional,
107 status: enum {
108 never_loaded,
109 /// A failure occurred while loading the file. Retrying might succeed.
110 retryable_failure,
111 /// Errors during parse
112 parse_failure,
113 /// Errors during transform
114 transform_failure,
115 /// Teg is valid
116 success,
117 },
118257
119 source: ?Source,258 source: ?Source,
120 ast: ?Ast,259 ast: ?Ast,
...@@ -170,8 +309,8 @@ pub const File = struct {...@@ -170,8 +309,8 @@ pub const File = struct {
170309
171 pub const GetAstError = GetSourceError || Parse.Error;310 pub const GetAstError = GetSourceError || Parse.Error;
172311
173 pub fn getAst(file: *File, comp: *Comp) GetAstError!Ast {312 pub fn getAst(file: *File, comp: *Comp) GetAstError!*const Ast {
174 if (file.ast) |ast| {313 if (file.ast) |*ast| {
175 return ast;314 return ast;
176 }315 }
177316
...@@ -184,9 +323,9 @@ pub const File = struct {...@@ -184,9 +323,9 @@ pub const File = struct {
184 error.OutOfMemory => return err,323 error.OutOfMemory => return err,
185 error.ParseFail => {324 error.ParseFail => {
186 // TODO: let this merge with other report325 // TODO: let this merge with other report
187 var report = try err_data.toReport(comp, file);326 var report = try err_data.toReport(comp.gpa);
188 report.dumpToWriter(comp, comp.out.writer, comp.out.tty) catch {};327 defer report.deinit(comp.gpa);
189 report.deinit(comp.gpa);328 report.dump(.{ .comp = .{ .comp = comp, .file = file } }, comp.out) catch {};
190 return err;329 return err;
191 },330 },
192 };331 };
...@@ -198,7 +337,7 @@ pub const File = struct {...@@ -198,7 +337,7 @@ pub const File = struct {
198 }337 }
199338
200 file.ast = ast;339 file.ast = ast;
201 return ast;340 return &(file.ast.?);
202 }341 }
203342
204 pub const GetTegError = GetAstError || Transform.Error;343 pub const GetTegError = GetAstError || Transform.Error;
...@@ -210,7 +349,7 @@ pub const File = struct {...@@ -210,7 +349,7 @@ pub const File = struct {
210349
211 const bin_digest = hash: {350 const bin_digest = hash: {
212 var path_hash: Cache.HashHelper = .{};351 var path_hash: Cache.HashHelper = .{};
213 path_hash.addBytes("0.1.0");352 path_hash.addBytes(@import("config").version_string);
214 path_hash.addBytes(file.file_path);353 path_hash.addBytes(file.file_path);
215 var bin: Cache.BinDigest = undefined;354 var bin: Cache.BinDigest = undefined;
216 path_hash.hasher.final(&bin);355 path_hash.hasher.final(&bin);
...@@ -218,12 +357,7 @@ pub const File = struct {...@@ -218,12 +357,7 @@ pub const File = struct {
218 };357 };
219 file.path_digest = bin_digest;358 file.path_digest = bin_digest;
220359
221 const teg = try Transform.transform(360 const teg = try Transform.transform((try file.getAst(comp)).*, comp.gpa, fail_report);
222 try file.getAst(comp),
223 comp.gpa,
224 file,
225 fail_report,
226 );
227361
228 file.teg = teg;362 file.teg = teg;
229 return teg;363 return teg;
...@@ -274,8 +408,6 @@ pub const File = struct {...@@ -274,8 +408,6 @@ pub const File = struct {
274 jw.write(std.fmt.bytesToHex(digest, .lower))408 jw.write(std.fmt.bytesToHex(digest, .lower))
275 else409 else
276 jw.write(null);410 jw.write(null);
277 try jw.objectField("status");
278 try jw.write(ctx.file.status);
279 try jw.objectField("root_namespace");411 try jw.objectField("root_namespace");
280 if (ctx.file.root_namespace.unwrap()) |ns_index| {412 if (ctx.file.root_namespace.unwrap()) |ns_index| {
281 const ns = ctx.comp.intern_pool.namespacePtrConst(ns_index);413 const ns = ctx.comp.intern_pool.namespacePtrConst(ns_index);
...@@ -325,29 +457,8 @@ pub const Decl = struct {...@@ -325,29 +457,8 @@ pub const Decl = struct {
325 /// This Decl corresponds to an AST Node that has not been referenced yet, and therefore457 /// This Decl corresponds to an AST Node that has not been referenced yet, and therefore
326 /// because of Zig's lazy declaration analysis, it will remain unanalyzed until referenced.458 /// because of Zig's lazy declaration analysis, it will remain unanalyzed until referenced.
327 unreferenced,459 unreferenced,
328 /// Semantic analysis for this Decl is running right now.
329 /// This state detects dependency loops.
330 in_progress,460 in_progress,
331 /// Semantic analysis for this Decl is running right now, and has461 waiting,
332 /// completed enough to reify the type of the result, but not the result
333 /// itself.
334 in_progress_type_available,
335 /// The file corresponding to this Decl had a parse error or ZIR error.
336 /// There will be a corresponding ErrorMsg in Zcu.failed_files.
337 // file_failure,
338 /// This Decl might be OK but it depends on another one which did not
339 /// successfully complete semantic analysis.
340 // dependency_failure,
341 /// Semantic analysis failure.
342 /// There will be a corresponding ErrorMsg in Zcu.failed_decls.
343 // sema_failure,
344 /// There will be a corresponding ErrorMsg in Zcu.failed_decls.
345 // codegen_failure,
346 /// Semantic analysis and constant value codegen of this Decl has
347 /// succeeded. However, the Decl may be outdated due to an in-progress
348 /// update. Note that for a function, this does not mean codegen of the
349 /// function body succeeded: that state is indicated by the function's
350 /// `analysis` field.
351 complete,462 complete,
352 },463 },
353464
...@@ -513,38 +624,37 @@ pub fn indexTegModule(comp: *Comp, file: *File, module_index: Teg.Inst.Index) !N...@@ -513,38 +624,37 @@ pub fn indexTegModule(comp: *Comp, file: *File, module_index: Teg.Inst.Index) !N
513 errdefer comp.intern_pool.destroyNamespace(comp.gpa, ns);624 errdefer comp.intern_pool.destroyNamespace(comp.gpa, ns);
514625
515 for (teg.moduleDeclarations(module_index)) |decl_index| {626 for (teg.moduleDeclarations(module_index)) |decl_index| {
516 const name = try comp.intern_pool.getOrPutString(627 switch (teg.instTag(decl_index)) {
517 comp.gpa,628 .declaration => {
518 teg.declName(decl_index),629 const name = try comp.intern_pool.getOrPutString(
519 .no_embedded_nulls,630 comp.gpa,
520 );631 teg.declName(decl_index),
521 const tracked_inst = try comp.intern_pool.trackInst(comp.gpa, file, decl_index);632 .no_embedded_nulls,
522 const decl = try comp.intern_pool.createDecl(comp.gpa, .{633 );
523 .name = name,634 const tracked_inst = try comp.intern_pool.trackInst(comp.gpa, file, decl_index);
524 .raw_value = null,635 const decl = try comp.intern_pool.createDecl(comp.gpa, .{
525 .src_namespace = ns,636 .name = name,
526 .teg_decl_index = tracked_inst,637 .raw_value = null,
527 .analysis = .unreferenced,638 .src_namespace = ns,
528 });639 .teg_decl_index = tracked_inst,
529 try ns_ptr.decls.putNoClobberContext(comp.gpa, decl, void{}, .{ .comp = comp });640 .analysis = .unreferenced,
641 });
642 try ns_ptr.decls.putNoClobberContext(comp.gpa, decl, void{}, .{ .comp = comp });
643 },
644 .variable_declaration => @panic("TODO: variable declarations"),
645 .test_declaration => {
646 // TODO: test mode
647 },
648 else => unreachable,
649 }
530 }650 }
531651
532 return ns;652 return ns;
533}653}
534654
535pub fn codeGenDecl(c: *Comp, value: InternPool.Value, air: *const Air) !void {
536 const t = tracer.trace(@src());
537 defer t.end();
538
539 var arena_state = std.heap.ArenaAllocator.init(c.gpa);
540 defer arena_state.deinit();
541 const arena = arena_state.allocator();
542
543 _ = try c.backend.handleFunctionAir(c, value, air, arena);
544}
545
546const Options = struct {655const Options = struct {
547 dump_ir: bool = false,656 dump_ir: bool = false,
657 verbose_sema: bool = false,
548};658};
549659
550/// The compiler backend is stored separately from the compilation, and660/// The compiler backend is stored separately from the compilation, and
...@@ -565,7 +675,7 @@ pub const Backend = union(enum) {...@@ -565,7 +675,7 @@ pub const Backend = union(enum) {
565 backend: Backend,675 backend: Backend,
566 comp: *Comp,676 comp: *Comp,
567 value: InternPool.Value,677 value: InternPool.Value,
568 air: *const Air,678 air: Air,
569 arena: Allocator,679 arena: Allocator,
570 ) HandleFunctionAirError!u32 {680 ) HandleFunctionAirError!u32 {
571 return backend.dispatch("handleFunctionAir", .{ comp, value, air, arena });681 return backend.dispatch("handleFunctionAir", .{ comp, value, air, arena });
...@@ -575,7 +685,7 @@ pub const Backend = union(enum) {...@@ -575,7 +685,7 @@ pub const Backend = union(enum) {
575 backend: Backend,685 backend: Backend,
576 comptime name: []const u8,686 comptime name: []const u8,
577 args: anytype,687 args: anytype,
578 ) @typeInfo(@TypeOf(@field(Backend, name))).Fn.return_type.? {688 ) @typeInfo(@TypeOf(@field(Backend, name))).@"fn".return_type.? {
579 return switch (backend) {689 return switch (backend) {
580 inline else => |ptr| @call(.auto, @field(@TypeOf(ptr.*), name), .{ptr} ++ args),690 inline else => |ptr| @call(.auto, @field(@TypeOf(ptr.*), name), .{ptr} ++ args),
581 };691 };
...@@ -607,7 +717,7 @@ const std = @import("std");...@@ -607,7 +717,7 @@ const std = @import("std");
607const Allocator = std.mem.Allocator;717const Allocator = std.mem.Allocator;
608const assert = std.debug.assert;718const assert = std.debug.assert;
609const Stat = std.Build.Cache.File.Stat;719const Stat = std.Build.Cache.File.Stat;
610const log = std.log.scoped(.comp);720const LinearFifo = std.fifo.LinearFifo;
611721
612const core = @import("mod.zig");722const core = @import("mod.zig");
613const InternPool = core.InternPool;723const InternPool = core.InternPool;
src/core/InternPool.zig+254-84
...@@ -8,6 +8,11 @@...@@ -8,6 +8,11 @@
8//!8//!
9//! This data structure is self-contained, with the following exceptions:9//! This data structure is self-contained, with the following exceptions:
10//! * Comp.Namespace has a pointer to Comp.File10//! * Comp.Namespace has a pointer to Comp.File
11//!
12//! TODO: This structure does not really consider threads and concurrent
13//! access. Zig 0.14 has some interesting work that I have not yet studied:
14//! https://github.com/ziglang/zig/blob/master/src/InternPool.zig
15//! This file will likely to be rewritten to inherit Zig's improvements.
1116
12/// Maps `Key` to `Value`. `Key` objects are not stored anywhere; they are17/// 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`. The18/// constructed lazily. The `Value` is also stored out of line in `.items`. The
...@@ -25,7 +30,7 @@ extra: ExtraStore = .{},...@@ -25,7 +30,7 @@ extra: ExtraStore = .{},
25/// is referencing the data here whether they want to store both index and length,30/// is referencing the data here whether they want to store both index and length,
26/// thus allowing null bytes, or store only index, and use null-termination. The31/// thus allowing null bytes, or store only index, and use null-termination. The
27/// `string_bytes` array is agnostic to either usage.32/// `string_bytes` array is agnostic to either usage.
28string_bytes: std.ArrayListUnmanaged(u8) = .{},33string_bytes: std.ArrayListUnmanaged(u8) = .empty,
2934
30/// Used for finding the index inside `string_bytes`.35/// Used for finding the index inside `string_bytes`.
31string_table: std.HashMapUnmanaged(36string_table: std.HashMapUnmanaged(
...@@ -33,11 +38,11 @@ string_table: std.HashMapUnmanaged(...@@ -33,11 +38,11 @@ string_table: std.HashMapUnmanaged(
33 void,38 void,
34 std.hash_map.StringIndexContext,39 std.hash_map.StringIndexContext,
35 std.hash_map.default_max_load_percentage,40 std.hash_map.default_max_load_percentage,
36) = .{},41) = .empty,
3742
38/// An index into `tracked_insts` gives a reference to a single ZIR instruction which43/// An index into `tracked_insts` gives a reference to a single ZIR instruction which
39/// persists across incremental updates.44/// persists across incremental updates.
40tracked_insts: std.AutoArrayHashMapUnmanaged(TrackedInst, void) = .{},45tracked_insts: std.AutoArrayHashMapUnmanaged(TrackedInst, void) = .empty,
4146
42/// Rather than allocating Decl objects with an Allocator, we instead allocate47/// Rather than allocating Decl objects with an Allocator, we instead allocate
43/// them with this SegmentedList. This provides four advantages:48/// them with this SegmentedList. This provides four advantages:
...@@ -58,6 +63,31 @@ allocated_namespaces: std.SegmentedList(Comp.Namespace, 0) = .{},...@@ -58,6 +63,31 @@ allocated_namespaces: std.SegmentedList(Comp.Namespace, 0) = .{},
58/// Same pattern as with `decls_free_list`.63/// Same pattern as with `decls_free_list`.
59namespaces_free_list: std.ArrayListUnmanaged(NamespaceIndex) = .{},64namespaces_free_list: std.ArrayListUnmanaged(NamespaceIndex) = .{},
6065
66/// Dependencies on the source code hash associated with a ZIR instruction.
67/// * For a `declaration`, this is the entire declaration body.
68/// * For a `struct_decl`, `union_decl`, etc, this is the source of the fields (but not declarations).
69/// * For a `func`, this is the source of the full function signature.
70/// These are also invalidated if tracking fails for this instruction.
71/// Value is index into `dep_entries` of the first dependency on this hash.
72src_hash_deps: std.AutoArrayHashMapUnmanaged(TrackedInst.Index, DepEntry.Index) = .{},
73/// Dependencies on the value of a Decl.
74/// Value is index into `dep_entries` of the first dependency on this Decl value.
75decl_val_deps: std.AutoArrayHashMapUnmanaged(DeclIndex, DepEntry.Index) = .{},
76
77/// Given a `Depender`, points to an entry in `dep_entries` whose `depender`
78/// matches. The `next_dependee` field can be used to iterate all such entries
79/// and remove them from the corresponding lists.
80first_dependency: std.AutoArrayHashMapUnmanaged(AnalysisUnit, DepEntry.Index) = .{},
81
82/// Stores dependency information. The hashmaps declared above are used to look
83/// up entries in this list as required. This is not stored in `extra` so that
84/// we can use `free_dep_entries` to track free indices, since dependencies are
85/// removed frequently.
86dep_entries: std.ArrayListUnmanaged(DepEntry) = .{},
87/// Stores unused indices in `dep_entries` which can be reused without a full
88/// garbage collection pass.
89free_dep_entries: std.ArrayListUnmanaged(DepEntry.Index) = .{},
90
61pub const Tag = enum(u8) {91pub const Tag = enum(u8) {
62 /// This special tag represents a value that was removed from this pool via92 /// This special tag represents a value that was removed from this pool via
63 /// `InternPool.remove`. The item remains allocated to preserve indices, but93 /// `InternPool.remove`. The item remains allocated to preserve indices, but
...@@ -91,15 +121,16 @@ pub const Tag = enum(u8) {...@@ -91,15 +121,16 @@ pub const Tag = enum(u8) {
91 func,121 func,
92 /// A string122 /// A string
93 string,123 string,
94124 /// An instance of a struct
95 struct_value,125 struct_value,
96126 /// An instance of an array
97 array_value,127 array_value,
128 /// An extern reference. For JS, this can be a property access
129 external_value,
98130
99 pub fn Data(comptime tag: Tag) type {131 pub fn Data(comptime tag: Tag) type {
100 return switch (tag) {132 return switch (tag) {
101 .removed,133 .removed => unreachable, // does not have a data
102 => unreachable, // does not have a data
103134
104 .simple_type => SimpleType,135 .simple_type => SimpleType,
105 .simple_value => SimpleValue,136 .simple_value => SimpleValue,
...@@ -125,6 +156,7 @@ pub const Tag = enum(u8) {...@@ -125,6 +156,7 @@ pub const Tag = enum(u8) {
125 .struct_type => StructType,156 .struct_type => StructType,
126 .array_type => ArrayType,157 .array_type => ArrayType,
127 .array_value => ArrayValue,158 .array_value => ArrayValue,
159 .external_value => ExternalValue,
128 };160 };
129 }161 }
130162
...@@ -137,15 +169,15 @@ pub const Tag = enum(u8) {...@@ -137,15 +169,15 @@ pub const Tag = enum(u8) {
137 };169 };
138170
139 /// Trailing:171 /// Trailing:
140 /// 0. param_type: Index for each params_len172 /// 0. param_type: Index to param type. Repeated .params_len
141 pub const TypeFunction = struct {173 pub const TypeFunction = struct {
142 params_len: u32,174 params_len: u32,
143 return_type: Type,175 return_type: Type,
144 };176 };
145177
146 /// Trailing:178 /// Trailing:
147 /// 0. field_key: NullTerminatedString. Repeated per field_len179 /// 0. field_key: NullTerminatedString. Repeated .field_len
148 /// 1. field_type: Type. Repeated per field_len180 /// 1. field_type: Type. Repeated .field_len
149 pub const StructType = struct {181 pub const StructType = struct {
150 fields_len: u32,182 fields_len: u32,
151 };183 };
...@@ -156,7 +188,7 @@ pub const Tag = enum(u8) {...@@ -156,7 +188,7 @@ pub const Tag = enum(u8) {
156 };188 };
157189
158 /// Trailing:190 /// Trailing:
159 /// 1. item: Type. Repeated per items_len191 /// 1. item: Type. Repeated per .items_len
160 pub const ArrayValue = struct {192 pub const ArrayValue = struct {
161 /// This is the array type, not the item type193 /// This is the array type, not the item type
162 type: Type,194 type: Type,
...@@ -164,11 +196,17 @@ pub const Tag = enum(u8) {...@@ -164,11 +196,17 @@ pub const Tag = enum(u8) {
164 };196 };
165197
166 /// Trailing:198 /// Trailing:
167 /// 0. value: Value. Repeated ty.structInfo().field_len199 /// 0. value: Value. Repeated .ty.structInfo().field_len
168 pub const StructValue = struct {200 pub const StructValue = struct {
169 /// The type of this field201 /// The type of this field
170 ty: Type,202 ty: Type,
171 };203 };
204
205 /// Trailing:
206 /// 0. value: Value. Repeated .key_count
207 pub const ExternalValue = struct {
208 key_count: u32,
209 };
172};210};
173211
174/// This is an index into .items212/// This is an index into .items
...@@ -183,11 +221,11 @@ pub const Value = enum(u32) {...@@ -183,11 +221,11 @@ pub const Value = enum(u32) {
183 int32_type,221 int32_type,
184 float_type,222 float_type,
185 string_type,223 string_type,
186 noreturn_type,224 never_type,
187 type_type,225 type_type,
188 numeric_literal_type,226 numeric_literal_type,
189 numeric_literal_array_type,227 numeric_literal_array_type,
190 noreturn_array_type,228 never_array_type,
191229
192 void,230 void,
193 bool_true,231 bool_true,
...@@ -289,6 +327,7 @@ pub const Value = enum(u32) {...@@ -289,6 +327,7 @@ pub const Value = enum(u32) {
289 .func => value.toKey(ip).func.ty,327 .func => value.toKey(ip).func.ty,
290 .struct_value => value.toKey(ip).struct_value.ty,328 .struct_value => value.toKey(ip).struct_value.ty,
291 .array_value => value.toKey(ip).array_value.type,329 .array_value => value.toKey(ip).array_value.type,
330 .external_value => .@"opaque",
292 };331 };
293 }332 }
294333
...@@ -312,7 +351,7 @@ pub const Value = enum(u32) {...@@ -312,7 +351,7 @@ pub const Value = enum(u32) {
312 }351 }
313352
314 pub fn toTypeUnsafe(value: Value, proof_ip: *const InternPool) Type {353 pub fn toTypeUnsafe(value: Value, proof_ip: *const InternPool) Type {
315 if (std.debug.runtime_safety) {354 if (core.runtime_safety) {
316 return value.toType(proof_ip) orelse355 return value.toType(proof_ip) orelse
317 std.debug.panic("{} is not a type", .{value.dump(proof_ip)});356 std.debug.panic("{} is not a type", .{value.dump(proof_ip)});
318 }357 }
...@@ -347,11 +386,11 @@ pub const Type = enum(u32) {...@@ -347,11 +386,11 @@ pub const Type = enum(u32) {
347 int32 = @intFromEnum(Value.int32_type),386 int32 = @intFromEnum(Value.int32_type),
348 float = @intFromEnum(Value.float_type),387 float = @intFromEnum(Value.float_type),
349 string = @intFromEnum(Value.string_type),388 string = @intFromEnum(Value.string_type),
350 noreturn = @intFromEnum(Value.noreturn_type),389 never = @intFromEnum(Value.never_type),
351 type = @intFromEnum(Value.type_type),390 type = @intFromEnum(Value.type_type),
352 numeric_literal = @intFromEnum(Value.numeric_literal_type),391 numeric_literal = @intFromEnum(Value.numeric_literal_type),
353 numeric_literal_array = @intFromEnum(Value.numeric_literal_array_type),392 numeric_literal_array = @intFromEnum(Value.numeric_literal_array_type),
354 noreturn_array = @intFromEnum(Value.noreturn_array_type),393 never_array = @intFromEnum(Value.never_array_type),
355 void = @intFromEnum(Value.void),394 void = @intFromEnum(Value.void),
356 _,395 _,
357396
...@@ -416,7 +455,7 @@ pub const Type = enum(u32) {...@@ -416,7 +455,7 @@ pub const Type = enum(u32) {
416 => true,455 => true,
417 .numeric_literal,456 .numeric_literal,
418 .type,457 .type,
419 .noreturn,458 .never,
420 => false,459 => false,
421 else => switch (ty.tag(ip)) {460 else => switch (ty.tag(ip)) {
422 .simple_type => unreachable, // all simple types are already printed461 .simple_type => unreachable, // all simple types are already printed
...@@ -441,7 +480,7 @@ pub const Type = enum(u32) {...@@ -441,7 +480,7 @@ pub const Type = enum(u32) {
441 .void,480 .void,
442 .numeric_literal,481 .numeric_literal,
443 .type,482 .type,
444 .noreturn,483 .never,
445 => false,484 => false,
446 else => switch (ty.tag(ip)) {485 else => switch (ty.tag(ip)) {
447 .simple_type => unreachable, // all simple types are already printed486 .simple_type => unreachable, // all simple types are already printed
...@@ -476,7 +515,7 @@ pub const Type = enum(u32) {...@@ -476,7 +515,7 @@ pub const Type = enum(u32) {
476 .int32 => return try writer.writeAll("int"),515 .int32 => return try writer.writeAll("int"),
477 .float => return try writer.writeAll("float"),516 .float => return try writer.writeAll("float"),
478 .string => return try writer.writeAll("string"),517 .string => return try writer.writeAll("string"),
479 .noreturn => return try writer.writeAll("noreturn"),518 .never => return try writer.writeAll("never"),
480 .type => return try writer.writeAll("type"),519 .type => return try writer.writeAll("type"),
481 .numeric_literal => return try writer.writeAll("numeric literal"),520 .numeric_literal => return try writer.writeAll("numeric literal"),
482 .void => return try writer.writeAll("void"),521 .void => return try writer.writeAll("void"),
...@@ -512,7 +551,7 @@ pub const Item = struct {...@@ -512,7 +551,7 @@ pub const Item = struct {
512 data: EncodedData,551 data: EncodedData,
513552
514 pub fn encode(comptime tag: Tag, data: tag.Data()) Item {553 pub fn encode(comptime tag: Tag, data: tag.Data()) Item {
515 return .{ .tag = tag, .data = EncodedData.encode(tag, data) };554 return .{ .tag = tag, .data = .encode(tag, data) };
516 }555 }
517};556};
518557
...@@ -536,11 +575,11 @@ pub const static_keys = [_]Key{...@@ -536,11 +575,11 @@ pub const static_keys = [_]Key{
536 SimpleType.int32.key(),575 SimpleType.int32.key(),
537 SimpleType.float.key(),576 SimpleType.float.key(),
538 SimpleType.string.key(),577 SimpleType.string.key(),
539 SimpleType.noreturn.key(),578 SimpleType.never.key(),
540 SimpleType.type.key(),579 SimpleType.type.key(),
541 SimpleType.numeric_literal.key(),580 SimpleType.numeric_literal.key(),
542 .{ .array_type = .{ .item_type = Type.numeric_literal } },581 .{ .array_type = .{ .item_type = Type.numeric_literal } },
543 .{ .array_type = .{ .item_type = Type.noreturn } },582 .{ .array_type = .{ .item_type = Type.never } },
544583
545 SimpleValue.void.key(),584 SimpleValue.void.key(),
546 SimpleValue.true.key(),585 SimpleValue.true.key(),
...@@ -549,7 +588,7 @@ pub const static_keys = [_]Key{...@@ -549,7 +588,7 @@ pub const static_keys = [_]Key{
549 .{ .float = 0.0 },588 .{ .float = 0.0 },
550 .{ .float = Float.pure_nan.decode() },589 .{ .float = Float.pure_nan.decode() },
551 .{ .array_value = .{590 .{ .array_value = .{
552 .type = Type.noreturn_array,591 .type = Type.never_array,
553 .items = &.{},592 .items = &.{},
554 } },593 } },
555 .{ .string = "" },594 .{ .string = "" },
...@@ -646,10 +685,6 @@ pub const NullTerminatedString = enum(u32) {...@@ -646,10 +685,6 @@ pub const NullTerminatedString = enum(u32) {
646 return @enumFromInt(@intFromEnum(self));685 return @enumFromInt(@intFromEnum(self));
647 }686 }
648687
649 // pub fn toOptional(self: NullTerminatedString) Optional{
650 // return @enumFromInt(@intFromEnum(self));
651 // }
652
653 pub fn toSlice(string: NullTerminatedString, ip: *const InternPool) [:0]const u8 {688 pub fn toSlice(string: NullTerminatedString, ip: *const InternPool) [:0]const u8 {
654 const slice = ip.string_bytes.items[@intFromEnum(string)..];689 const slice = ip.string_bytes.items[@intFromEnum(string)..];
655 return slice[0..std.mem.indexOfScalar(u8, slice, 0).? :0];690 return slice[0..std.mem.indexOfScalar(u8, slice, 0).? :0];
...@@ -801,51 +836,6 @@ pub const NamespaceIndex = enum(u32) {...@@ -801,51 +836,6 @@ pub const NamespaceIndex = enum(u32) {
801 };836 };
802};837};
803838
804/// A single value captured in the closure of a namespace type. This is not a plain
805/// `Index` because we must differentiate between the following cases:
806/// * runtime-known value (where we store the type)
807/// * comptime-known value (where we store the value)
808/// * decl val (so that we can analyze the value lazily)
809/// * decl ref (so that we can analyze the reference lazily)
810pub const Capture = packed struct(u32) {
811 tag: enum(u2) { @"comptime", runtime, decl_val, decl_ref },
812 idx: u30,
813
814 pub fn wrap(val: Unwrapped) Capture {
815 return switch (val) {
816 .@"comptime" => |i| .{ .tag = .@"comptime", .idx = @intCast(@intFromEnum(i)) },
817 .runtime => |i| .{ .tag = .runtime, .idx = @intCast(@intFromEnum(i)) },
818 .decl_val => |i| .{ .tag = .decl_val, .idx = @intCast(@intFromEnum(i)) },
819 .decl_ref => |i| .{ .tag = .decl_ref, .idx = @intCast(@intFromEnum(i)) },
820 };
821 }
822 pub fn unwrap(val: Capture) Unwrapped {
823 return switch (val.tag) {
824 .@"comptime" => .{ .@"comptime" = @enumFromInt(val.idx) },
825 .runtime => .{ .runtime = @enumFromInt(val.idx) },
826 .decl_val => .{ .decl_val = @enumFromInt(val.idx) },
827 .decl_ref => .{ .decl_ref = @enumFromInt(val.idx) },
828 };
829 }
830
831 pub const Unwrapped = union(enum) {
832 /// Index refers to the value.
833 @"comptime": Value,
834 /// Index refers to the type.
835 runtime: Value,
836 decl_val: DeclIndex,
837 decl_ref: DeclIndex,
838 };
839
840 pub const Slice = struct {
841 start: u32,
842 len: u32,
843 pub fn get(slice: Slice, ip: *const InternPool) []Capture {
844 return @ptrCast(ip.extra.list.items[slice.start..][0..slice.len]);
845 }
846 };
847};
848
849const SimpleValue = enum {839const SimpleValue = enum {
850 true,840 true,
851 false,841 false,
...@@ -862,7 +852,7 @@ const SimpleType = enum {...@@ -862,7 +852,7 @@ const SimpleType = enum {
862 float,852 float,
863 string,853 string,
864 bool,854 bool,
865 noreturn,855 never,
866 type,856 type,
867 numeric_literal,857 numeric_literal,
868858
...@@ -886,6 +876,7 @@ pub const Key = union(enum) {...@@ -886,6 +876,7 @@ pub const Key = union(enum) {
886 function_type: FunctionType,876 function_type: FunctionType,
887 struct_type: StructType,877 struct_type: StructType,
888 array_type: ArrayType,878 array_type: ArrayType,
879 external_value: ExternalValue,
889880
890 const Adapter = struct {881 const Adapter = struct {
891 intern_pool: *const InternPool,882 intern_pool: *const InternPool,
...@@ -950,6 +941,16 @@ pub const Key = union(enum) {...@@ -950,6 +941,16 @@ pub const Key = union(enum) {
950 }941 }
951 };942 };
952943
944 pub const ExternalValue = struct {
945 /// A list of strings to generate the property access
946 /// so `console.log` would be .{ "console", "log" }
947 key: []const NullTerminatedString,
948
949 pub fn hash(self: ExternalValue, hasher: *Hash) void {
950 std.hash.autoHashStrat(hasher, self.key, .Shallow);
951 }
952 };
953
953 pub const ArrayValue = struct {954 pub const ArrayValue = struct {
954 /// This is the array type, not the item type955 /// This is the array type, not the item type
955 type: Type,956 type: Type,
...@@ -983,7 +984,7 @@ pub const Key = union(enum) {...@@ -983,7 +984,7 @@ pub const Key = union(enum) {
983 defer t.end();984 defer t.end();
984985
985 const asBytes = std.mem.asBytes;986 const asBytes = std.mem.asBytes;
986 const KeyTag = @typeInfo(Key).Union.tag_type.?;987 const KeyTag = @typeInfo(Key).@"union".tag_type.?;
987 const seed = @intFromEnum(@as(KeyTag, key));988 const seed = @intFromEnum(@as(KeyTag, key));
988989
989 return switch (key) {990 return switch (key) {
...@@ -1000,6 +1001,7 @@ pub const Key = union(enum) {...@@ -1000,6 +1001,7 @@ pub const Key = union(enum) {
1000 .struct_value,1001 .struct_value,
1001 .array_value,1002 .array_value,
1002 .array_type,1003 .array_type,
1004 .external_value,
1003 => |x| {1005 => |x| {
1004 var hasher = Hash.init(seed);1006 var hasher = Hash.init(seed);
1005 x.hash(&hasher);1007 x.hash(&hasher);
...@@ -1013,7 +1015,7 @@ pub const Key = union(enum) {...@@ -1013,7 +1015,7 @@ pub const Key = union(enum) {
1013 const t = tracer.trace(@src());1015 const t = tracer.trace(@src());
1014 defer t.end();1016 defer t.end();
10151017
1016 const KeyTag = @typeInfo(Key).Union.tag_type.?;1018 const KeyTag = @typeInfo(Key).@"union".tag_type.?;
1017 const a_tag: KeyTag = a;1019 const a_tag: KeyTag = a;
1018 const b_tag: KeyTag = b;1020 const b_tag: KeyTag = b;
1019 if (a_tag != b_tag) return false;1021 if (a_tag != b_tag) return false;
...@@ -1053,6 +1055,10 @@ pub const Key = union(enum) {...@@ -1053,6 +1055,10 @@ pub const Key = union(enum) {
1053 return a_info.type == b_info.type and1055 return a_info.type == b_info.type and
1054 std.mem.eql(Value, a_info.items, b_info.items);1056 std.mem.eql(Value, a_info.items, b_info.items);
1055 },1057 },
1058 .external_value => |a_info| {
1059 const b_info = b.external_value;
1060 return std.mem.eql(NullTerminatedString, a_info.key, b_info.key);
1061 },
1056 };1062 };
1057 }1063 }
1058};1064};
...@@ -1079,9 +1085,113 @@ const EmbeddedNulls = enum {...@@ -1079,9 +1085,113 @@ const EmbeddedNulls = enum {
1079 }1085 }
1080};1086};
10811087
1088/// Represents a single entity which undergoes semantic analysis.
1089/// This is the "source" of an incremental dependency edge.
1090pub const AnalysisUnit = packed struct(u32) {
1091 kind: enum(u1) { decl, func },
1092 index: u31,
1093
1094 pub const Unwrapped = union(enum) {
1095 decl: DeclIndex,
1096 func: Value,
1097 };
1098
1099 pub fn unwrap(as: AnalysisUnit) Unwrapped {
1100 return switch (as.kind) {
1101 .decl => .{ .decl = @enumFromInt(as.index) },
1102 .func => .{ .func = @enumFromInt(as.index) },
1103 };
1104 }
1105
1106 pub fn wrap(raw: Unwrapped) AnalysisUnit {
1107 return switch (raw) {
1108 .decl => |decl| .{ .kind = .decl, .index = @intCast(@intFromEnum(decl)) },
1109 .func => |func| .{ .kind = .func, .index = @intCast(@intFromEnum(func)) },
1110 };
1111 }
1112
1113 pub fn toOptional(as: AnalysisUnit) Optional {
1114 return @enumFromInt(@as(u32, @bitCast(as)));
1115 }
1116
1117 pub const Optional = enum(u32) {
1118 none = std.math.maxInt(u32),
1119 _,
1120
1121 pub fn unwrap(opt: Optional) ?AnalysisUnit {
1122 return switch (opt) {
1123 .none => null,
1124 _ => @bitCast(@intFromEnum(opt)),
1125 };
1126 }
1127 };
1128
1129 pub fn dump(au: AnalysisUnit, ip: *const InternPool) std.fmt.Formatter(formatImpl) {
1130 return .{ .data = .{ .au = au, .ip = ip } };
1131 }
1132
1133 fn formatImpl(
1134 data: struct { au: AnalysisUnit, ip: *const InternPool },
1135 comptime unused: []const u8,
1136 _: std.fmt.FormatOptions,
1137 w: std.io.AnyWriter,
1138 ) !void {
1139 comptime assert(unused.len == 0);
1140 try w.print("{s}: ", .{@tagName(data.au.kind)});
1141 switch (data.au.unwrap()) {
1142 .decl => |index| try w.print("^{d}", .{@intFromEnum(index)}),
1143 .func => |value| try w.print("'{s}'", .{
1144 data.ip.declPtrConst(value.toKey(data.ip).func.owner_decl).name.toSlice(data.ip),
1145 }),
1146 }
1147 }
1148};
1149
1150pub const Dependee = union(enum) {
1151 src_hash: TrackedInst.Index,
1152 decl_val: DeclIndex,
1153};
1154
1155pub const DepEntry = extern struct {
1156 /// If null, this is a dummy entry. `next_dependee` is undefined. This is the first
1157 /// entry in one of `*_deps`, and does not appear in any list by `first_dependency`,
1158 /// but is not in `free_dep_entries` since `*_deps` stores a reference to it.
1159 depender: AnalysisUnit.Optional,
1160 /// Index into `dep_entries` forming a doubly linked list of all dependencies on this dependee.
1161 /// Used to iterate all dependers for a given dependee during an update.
1162 /// null if this is the end of the list.
1163 next: DepEntry.Index.Optional,
1164 /// The other link for `next`.
1165 /// null if this is the start of the list.
1166 prev: DepEntry.Index.Optional,
1167 /// Index into `dep_entries` forming a singly linked list of dependencies *of* `depender`.
1168 /// Used to efficiently remove all `DepEntry`s for a single `depender` when it is re-analyzed.
1169 /// null if this is the end of the list.
1170 next_dependee: DepEntry.Index.Optional,
1171
1172 pub const Index = enum(u32) {
1173 _,
1174
1175 pub fn toOptional(dep: DepEntry.Index) Optional {
1176 return @enumFromInt(@intFromEnum(dep));
1177 }
1178
1179 pub const Optional = enum(u32) {
1180 none = std.math.maxInt(u32),
1181 _,
1182 pub fn unwrap(opt: Optional) ?DepEntry.Index {
1183 return switch (opt) {
1184 .none => null,
1185 _ => @enumFromInt(@intFromEnum(opt)),
1186 };
1187 }
1188 };
1189 };
1190};
1191
1082// implementations:1192// implementations:
10831193
1084/// To construct, instantiate `.{ .gpa = gpa }` then call this.1194/// To construct, instantiate `.{}` then call this.
1085pub fn init(ip: *InternPool, gpa: Allocator) !void {1195pub fn init(ip: *InternPool, gpa: Allocator) !void {
1086 const t = tracer.trace(@src());1196 const t = tracer.trace(@src());
1087 defer t.end();1197 defer t.end();
...@@ -1102,7 +1212,7 @@ pub fn init(ip: *InternPool, gpa: Allocator) !void {...@@ -1102,7 +1212,7 @@ pub fn init(ip: *InternPool, gpa: Allocator) !void {
1102 _ = ip.get(gpa, key) catch unreachable;1212 _ = ip.get(gpa, key) catch unreachable;
1103 }1213 }
11041214
1105 if (std.debug.runtime_safety) {1215 if (core.runtime_safety) {
1106 // Sanity check.1216 // Sanity check.
1107 assert(ip.valueToKey(.bool_true).simple_value == .true);1217 assert(ip.valueToKey(.bool_true).simple_value == .true);
1108 assert(ip.valueToKey(.bool_false).simple_value == .false);1218 assert(ip.valueToKey(.bool_false).simple_value == .false);
...@@ -1139,16 +1249,16 @@ pub fn deinit(ip: *InternPool, gpa: Allocator) void {...@@ -1139,16 +1249,16 @@ pub fn deinit(ip: *InternPool, gpa: Allocator) void {
11391249
1140 ip.tracked_insts.deinit(gpa);1250 ip.tracked_insts.deinit(gpa);
11411251
1142 // ip.src_hash_deps.deinit(gpa);1252 ip.src_hash_deps.deinit(gpa);
1143 // ip.decl_val_deps.deinit(gpa);1253 ip.decl_val_deps.deinit(gpa);
1144 // ip.func_ies_deps.deinit(gpa);1254 // ip.func_ies_deps.deinit(gpa);
1145 // ip.namespace_deps.deinit(gpa);1255 // ip.namespace_deps.deinit(gpa);
1146 // ip.namespace_name_deps.deinit(gpa);1256 // ip.namespace_name_deps.deinit(gpa);
11471257
1148 // ip.first_dependency.deinit(gpa);1258 ip.first_dependency.deinit(gpa);
11491259
1150 // ip.dep_entries.deinit(gpa);1260 ip.dep_entries.deinit(gpa);
1151 // ip.free_dep_entries.deinit(gpa);1261 ip.free_dep_entries.deinit(gpa);
11521262
1153 ip.* = undefined;1263 ip.* = undefined;
1154}1264}
...@@ -1212,6 +1322,12 @@ pub fn valueToKey(ip: *const InternPool, index: Value) Key {...@@ -1212,6 +1322,12 @@ pub fn valueToKey(ip: *const InternPool, index: Value) Key {
1212 .items = trail.readValueSlice(payload.items_len).get(ip),1322 .items = trail.readValueSlice(payload.items_len).get(ip),
1213 } };1323 } };
1214 },1324 },
1325 .external_value => {
1326 const payload: Tag.ExternalValue, var trail = data.decode(.external_value, item.tag).getTrail(ip);
1327 return .{ .external_value = .{
1328 .key = trail.readNullTerminatedStringSlice(payload.key_count).get(ip),
1329 } };
1330 },
1215 };1331 };
1216}1332}
12171333
...@@ -1307,10 +1423,17 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) !Value {...@@ -1307,10 +1423,17 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) !Value {
1307 trail.writeSlice(s.items);1423 trail.writeSlice(s.items);
1308 break :brk Item.encode(.array_value, extra);1424 break :brk Item.encode(.array_value, extra);
1309 },1425 },
1426 .external_value => |s| brk: {
1427 const extra, var trail = try ip.extra.appendTrailing(gpa, Tag.ExternalValue, .{
1428 .key_count = @intCast(s.key.len),
1429 }, s.key.len);
1430 trail.writeSlice(s.key);
1431 break :brk Item.encode(.external_value, extra);
1432 },
1310 };1433 };
1311 ip.items.appendAssumeCapacity(item);1434 ip.items.appendAssumeCapacity(item);
1312 const index: Value = @enumFromInt(ip.items.len - 1);1435 const index: Value = @enumFromInt(ip.items.len - 1);
1313 if (std.debug.runtime_safety) {1436 if (core.runtime_safety) {
1314 const key_added = ip.valueToKey(index);1437 const key_added = ip.valueToKey(index);
1315 assert(key == .struct_type or key_added.eql(key, ip));1438 assert(key == .struct_type or key_added.eql(key, ip));
1316 }1439 }
...@@ -1398,6 +1521,53 @@ pub fn trackInst(ip: *InternPool, gpa: Allocator, file: *Comp.File, inst: Teg.In...@@ -1398,6 +1521,53 @@ pub fn trackInst(ip: *InternPool, gpa: Allocator, file: *Comp.File, inst: Teg.In
1398 return @enumFromInt(gop.index);1521 return @enumFromInt(gop.index);
1399}1522}
14001523
1524pub fn addDependency(ip: *InternPool, gpa: Allocator, depender: AnalysisUnit, dependee: Dependee) Allocator.Error!void {
1525 const first_depender_dep: DepEntry.Index.Optional = if (ip.first_dependency.get(depender)) |idx| dep: {
1526 // The entry already exists, so there is capacity to overwrite it later.
1527 break :dep idx.toOptional();
1528 } else none: {
1529 // Ensure there is capacity available to add this dependency later.
1530 try ip.first_dependency.ensureUnusedCapacity(gpa, 1);
1531 break :none .none;
1532 };
1533
1534 // We're very likely to need space for a new entry - reserve it now to avoid
1535 // the need for error cleanup logic.
1536 if (ip.free_dep_entries.items.len == 0) {
1537 try ip.dep_entries.ensureUnusedCapacity(gpa, 1);
1538 }
1539
1540 // This block should allocate an entry and prepend it to the relevant `*_deps` list.
1541 // The `next` field should be correctly initialized; all other fields may be undefined.
1542 const new_index: DepEntry.Index = switch (dependee) {
1543 inline else => |dependee_payload, tag| new_index: {
1544 const gop = try switch (tag) {
1545 .src_hash => ip.src_hash_deps,
1546 .decl_val => ip.decl_val_deps,
1547 }.getOrPut(gpa, dependee_payload);
1548
1549 if (gop.found_existing and ip.dep_entries.items[@intFromEnum(gop.value_ptr.*)].depender == .none) {
1550 // Dummy entry, so we can reuse it rather than allocating a new one!
1551 ip.dep_entries.items[@intFromEnum(gop.value_ptr.*)].next = .none;
1552 break :new_index gop.value_ptr.*;
1553 }
1554
1555 // Prepend a new dependency.
1556 const new_index: DepEntry.Index, const ptr = if (ip.free_dep_entries.popOrNull()) |new_index| new: {
1557 break :new .{ new_index, &ip.dep_entries.items[@intFromEnum(new_index)] };
1558 } else .{ @enumFromInt(ip.dep_entries.items.len), ip.dep_entries.addOneAssumeCapacity() };
1559 ptr.next = if (gop.found_existing) gop.value_ptr.*.toOptional() else .none;
1560 gop.value_ptr.* = new_index;
1561 break :new_index new_index;
1562 },
1563 };
1564
1565 ip.dep_entries.items[@intFromEnum(new_index)].depender = depender.toOptional();
1566 ip.dep_entries.items[@intFromEnum(new_index)].prev = .none;
1567 ip.dep_entries.items[@intFromEnum(new_index)].next_dependee = first_depender_dep;
1568 ip.first_dependency.putAssumeCapacity(depender, new_index);
1569}
1570
1401pub fn declPtr(ip: *InternPool, index: DeclIndex) *Comp.Decl {1571pub fn declPtr(ip: *InternPool, index: DeclIndex) *Comp.Decl {
1402 return ip.allocated_decls.at(@intFromEnum(index));1572 return ip.allocated_decls.at(@intFromEnum(index));
1403}1573}
src/core/Parse.zig+103-71
...@@ -18,24 +18,7 @@ depth: u32 = 0,...@@ -18,24 +18,7 @@ depth: u32 = 0,
18const depth_limit = 4096;18const depth_limit = 4096;
1919
20pub fn parse(gpa: std.mem.Allocator, source: [:0]const u8, ast_error: *AstError.WithTrace) Parse.Error!Ast {20pub fn parse(gpa: std.mem.Allocator, source: [:0]const u8, ast_error: *AstError.WithTrace) Parse.Error!Ast {
21 var err_byte: ByteOffset = 0;21 var tokens = try Tokenizer.tokenize(source, gpa);
22 var tokens = Tokenizer.tokenize(
23 source,
24 gpa,
25 &err_byte,
26 ) catch |err| switch (err) {
27 error.OutOfMemory => return error.OutOfMemory,
28 else => {
29 ast_error.* = .{
30 .err = .{ .tokenization = .{
31 .err = err,
32 .byte = err_byte,
33 } },
34 .trace = DebugStoredTrace.from(@errorReturnTrace()),
35 };
36 return error.ParseFail;
37 },
38 };
39 errdefer tokens.deinit(gpa);22 errdefer tokens.deinit(gpa);
4023
41 var parser = Parse.init(gpa, source, tokens);24 var parser = Parse.init(gpa, source, tokens);
...@@ -125,11 +108,11 @@ pub const ExpectedContext = enum {...@@ -125,11 +108,11 @@ pub const ExpectedContext = enum {
125/// When printing these, token list and Ast is not actually available, so full tokens108/// When printing these, token list and Ast is not actually available, so full tokens
126/// are stored in the errors.109/// are stored in the errors.
127pub const AstError = union(enum) {110pub const AstError = union(enum) {
128 /// Failure during tokenization111 // /// Failure during tokenization
129 tokenization: struct {112 // tokenization: struct {
130 err: Tokenizer.TokenizeError,113 // err: Tokenizer.TokenizeError,
131 byte: ByteOffset,114 // byte: ByteOffset,
132 },115 // },
133 /// Unexpected data, expected a token.116 /// Unexpected data, expected a token.
134 unexpected_token: struct {117 unexpected_token: struct {
135 expected: Token.Tag,118 expected: Token.Tag,
...@@ -145,29 +128,29 @@ pub const AstError = union(enum) {...@@ -145,29 +128,29 @@ pub const AstError = union(enum) {
145128
146 array_type_flipped_hint: Token,129 array_type_flipped_hint: Token,
147130
148 pub fn toReport(err: AstError, comp: *Comp, file: *Comp.File, trace: DebugStoredTrace) !Report {131 pub fn toReport(err: AstError, allocator: Allocator, trace: DebugStoredTrace) !Report {
149 return (Report.Err{ .ast = err }).toReport(comp, file, trace);132 return (Report.Err{ .ast = err }).toReport(allocator, trace);
150 }133 }
151134
152 pub fn build(err: AstError, ctx: *Report.MsgContext) !void {135 pub fn build(err: AstError, ctx: *Report.MsgContext) !void {
153 switch (err) {136 switch (err) {
154 .tokenization => |data| {137 // .tokenization => |data| {
155 // TODO: consider making tokenizer emit AstError more directly,138 // // TODO: consider making tokenizer emit AstError more directly,
156 // so that it can just annotate start and ends139 // // so that it can just annotate start and ends
157 try ctx.msg(switch (data.err) {140 // try ctx.msg(switch (data.err) {
158 error.UnterminatedString => .{ .codepoint_eol = data.byte },141 // error.UnterminatedString => .{ .codepoint_eol = data.byte },
159 else => .{ .codepoint = data.byte },142 // else => .{ .codepoint = data.byte },
160 }, "{s}", .{switch (data.err) {143 // }, "{s}", .{switch (data.err) {
161 error.OutOfMemory => unreachable,144 // error.OutOfMemory => unreachable,
162 error.InvalidSyntax => "unexpected token",145 // error.InvalidSyntax => "unexpected token",
163 error.TrailingNumericSeparator => "trailing numeric separator",146 // error.TrailingNumericSeparator => "trailing numeric separator",
164 error.UnterminatedString => "unterminated string literal",147 // error.UnterminatedString => "unterminated string literal",
165 error.InvalidEscapeSequence => "invalid escape sequence",148 // error.InvalidEscapeSequence => "invalid escape sequence",
166 error.InvalidEscapeCodepoint => "invalid escape codepoint",149 // error.InvalidEscapeCodepoint => "invalid escape codepoint",
167 error.InvalidUtf8 => "file is not valid utf8",150 // error.InvalidUtf8 => "file is not valid utf8",
168 error.GitConflict => "git merge conflict encountered",151 // error.GitConflict => "git merge conflict encountered",
169 }});152 // }});
170 },153 // },
171 .unexpected_token => |data| {154 .unexpected_token => |data| {
172 try ctx.msg(.{ .token_stored = data.got }, "expected {s}, but found {s}", .{155 try ctx.msg(.{ .token_stored = data.got }, "expected {s}, but found {s}", .{
173 data.expected.description(),156 data.expected.description(),
...@@ -197,8 +180,8 @@ pub const AstError = union(enum) {...@@ -197,8 +180,8 @@ pub const AstError = union(enum) {
197 err: AstError,180 err: AstError,
198 trace: DebugStoredTrace,181 trace: DebugStoredTrace,
199182
200 pub fn toReport(err: WithTrace, comp: *Comp, file: *Comp.File) !Report {183 pub fn toReport(wt: WithTrace, allocator: Allocator) !Report {
201 return err.err.toReport(comp, file, err.trace);184 return wt.err.toReport(allocator, wt.trace);
202 }185 }
203 };186 };
204};187};
...@@ -247,8 +230,13 @@ fn parseModuleMembers(p: *Parse) Error!SubList {...@@ -247,8 +230,13 @@ fn parseModuleMembers(p: *Parse) Error!SubList {
247 .kw_pub,230 .kw_pub,
248 => try p.scratch.append(p.gpa, try p.parseModuleDecl()),231 => try p.scratch.append(p.gpa, try p.parseModuleDecl()),
249232
250 .kw_struct,233 .kw_struct => try p.scratch.append(p.gpa, try p.parseStructDecl()),
251 => try p.scratch.append(p.gpa, try p.parseStructDecl()),234
235 .id => if (p.isContextualKeyword("test")) {
236 try p.scratch.append(p.gpa, try p.parseTestDecl());
237 } else {
238 try p.parseExpressionStatement();
239 },
252240
253 .kw_enum,241 .kw_enum,
254 .kw_trait,242 .kw_trait,
...@@ -261,25 +249,32 @@ fn parseModuleMembers(p: *Parse) Error!SubList {...@@ -261,25 +249,32 @@ fn parseModuleMembers(p: *Parse) Error!SubList {
261 if (!p.isExpressionStart()) {249 if (!p.isExpressionStart()) {
262 return p.failUnexpectedToken(.statement);250 return p.failUnexpectedToken(.statement);
263 }251 }
252 try p.parseExpressionStatement();
253 },
254 };
264255
265 const first_tag = p.currentTag();256 return p.scratchToSubList(scratch_top);
257}
266258
267 try p.scratch.append(p.gpa, try p.parseExpression(.statement));259fn parseExpressionStatement(p: *Parse) !void {
260 if (!p.isExpressionStart()) {
261 return p.failUnexpectedToken(.statement);
262 }
268263
269 switch (first_tag) {264 const first_tag = p.currentTag();
270 // some control flow and blocks are allowed to not have
271 // trailing semicolons. this is a stylistic choice.
272 .kw_if,
273 .start_brace,
274 => {},
275265
276 // TODO: make semicolons optional, but emit a warning266 try p.scratch.append(p.gpa, try p.parseExpression(.statement));
277 else => _ = try p.expect(.semi),
278 }
279 },
280 };
281267
282 return p.scratchToSubList(scratch_top);268 switch (first_tag) {
269 // some control flow and blocks are allowed to not have
270 // trailing semicolons. this is a stylistic choice.
271 .kw_if,
272 .start_brace,
273 => {},
274
275 // TODO: make semicolons optional, but emit a warning
276 else => _ = try p.expect(.semi),
277 }
283}278}
284279
285fn parseModuleDecl(p: *Parse) Error!NodeIndex {280fn parseModuleDecl(p: *Parse) Error!NodeIndex {
...@@ -313,9 +308,10 @@ fn parseFnDecl(p: *Parse) Error!NodeIndex {...@@ -313,9 +308,10 @@ fn parseFnDecl(p: *Parse) Error!NodeIndex {
313308
314 _ = try p.expect(.id);309 _ = try p.expect(.id);
315310
316 _ = try p.expect(.start_paren);311 const params = if (p.eatToken(.start_paren) != null)
317312 try p.parseParamList(.types_required)
318 const params = try p.parseParamList(.types_required);313 else
314 null;
319315
320 const return_type = if (p.eatToken(.minus_lt)) |_|316 const return_type = if (p.eatToken(.minus_lt)) |_|
321 if (!p.isExpressionStart())317 if (!p.isExpressionStart())
...@@ -329,7 +325,7 @@ fn parseFnDecl(p: *Parse) Error!NodeIndex {...@@ -329,7 +325,7 @@ fn parseFnDecl(p: *Parse) Error!NodeIndex {
329 const body = try p.parseBlock();325 const body = try p.parseBlock();
330326
331 const extra = try p.addExtra(Ast.Fn, .{327 const extra = try p.addExtra(Ast.Fn, .{
332 .params = params,328 .params = params orelse .empty,
333 .return_type = optionalNodeIndex(return_type),329 .return_type = optionalNodeIndex(return_type),
334 });330 });
335331
...@@ -413,6 +409,28 @@ fn parseStructDecl(p: *Parse) Error!NodeIndex {...@@ -413,6 +409,28 @@ fn parseStructDecl(p: *Parse) Error!NodeIndex {
413 });409 });
414}410}
415411
412fn parseTestDecl(p: *Parse) Error!NodeIndex {
413 const trace = t.trace(@src());
414 defer trace.end();
415
416 const main_token = p.assertToken(.id);
417
418 _ = p.eatToken(.string);
419
420 try p.expectWithoutAdvance(.start_brace);
421
422 const block = try p.parseBlock();
423
424 return try p.addNode(.{
425 .tag = .test_decl,
426 .main_token = main_token,
427 .data = .{ .test_decl = .{
428 .doc_test_name = .none,
429 .block = block,
430 } },
431 });
432}
433
416fn parseStructFieldList(p: *Parse) Error!SubList {434fn parseStructFieldList(p: *Parse) Error!SubList {
417 const trace = t.trace(@src());435 const trace = t.trace(@src());
418 defer trace.end();436 defer trace.end();
...@@ -1255,7 +1273,7 @@ fn parseArrayValueOrType(p: *Parse, mode: ParseExprMode) Error!NodeIndex {...@@ -1255,7 +1273,7 @@ fn parseArrayValueOrType(p: *Parse, mode: ParseExprMode) Error!NodeIndex {
1255}1273}
12561274
1257fn parseTupleType(p: *Parse) Error!NodeIndex {1275fn parseTupleType(p: *Parse) Error!NodeIndex {
1258 const main_token = p.assertToken(.start_bracket);1276 const main_token = p.assertToken(.start_paren);
1259 const list = try p.parseExpressionList(.end_paren, .expression);1277 const list = try p.parseExpressionList(.end_paren, .expression);
1260 return p.addNode(.{1278 return p.addNode(.{
1261 .tag = .tuple_type,1279 .tag = .tuple_type,
...@@ -1334,12 +1352,7 @@ fn eatToken(p: *Parse, tag: Token.Tag) ?TokenIndex {...@@ -1334,12 +1352,7 @@ fn eatToken(p: *Parse, tag: Token.Tag) ?TokenIndex {
1334}1352}
13351353
1336fn expect(p: *Parse, tag: Token.Tag) !TokenIndex {1354fn expect(p: *Parse, tag: Token.Tag) !TokenIndex {
1337 if (p.currentTag() != tag) {1355 try p.expectWithoutAdvance(tag);
1338 return p.fail(.{ .unexpected_token = .{
1339 .got = p.fullToken(p.pos),
1340 .expected = tag,
1341 } });
1342 }
1343 return p.nextToken();1356 return p.nextToken();
1344}1357}
13451358
...@@ -1364,6 +1377,11 @@ fn nextToken(p: *Parse) TokenIndex {...@@ -1364,6 +1377,11 @@ fn nextToken(p: *Parse) TokenIndex {
1364 return result;1377 return result;
1365}1378}
13661379
1380fn isContextualKeyword(p: *Parse, kw: []const u8) bool {
1381 assert(p.currentTag() == .id);
1382 return mem.startsWith(u8, p.source[p.token_starts[p.pos]..], kw);
1383}
1384
1367fn fullToken(p: *Parse, token: TokenIndex) Token {1385fn fullToken(p: *Parse, token: TokenIndex) Token {
1368 return Token{1386 return Token{
1369 .tag = p.token_tags[token],1387 .tag = p.token_tags[token],
...@@ -1417,3 +1435,17 @@ const t = @import("tracer");...@@ -1417,3 +1435,17 @@ const t = @import("tracer");
14171435
1418const Report = core.Report;1436const Report = core.Report;
1419const Comp = core.Comp;1437const Comp = core.Comp;
1438
1439test "fuzz test parser for crashes" {
1440 try core.unicode_props.init();
1441
1442 const input_bytes = std.testing.fuzzInput(.{});
1443
1444 const sentinel = try std.testing.allocator.dupeZ(u8, input_bytes);
1445 defer std.testing.allocator.free(sentinel);
1446
1447 var err: AstError.WithTrace = undefined;
1448 var ast = parse(std.testing.allocator, sentinel, &err) catch
1449 return; // ignore parser errors, only fail on crashes
1450 ast.deinit(std.testing.allocator);
1451}
src/core/Report.zig+233-114
...@@ -5,9 +5,15 @@...@@ -5,9 +5,15 @@
5//!5//!
6//! Across the compiler, `Report.Builder`s' `.err` and `.warn` methods6//! Across the compiler, `Report.Builder`s' `.err` and `.warn` methods
7//! can add messages by ID.7//! can add messages by ID.
8errors: []Item = &.{},8errors: []Item,
9warnings: []Item = &.{},9warnings: []Item,
10string_bytes: []u8 = &.{},10string_bytes: []u8,
11
12pub const empty: Report = .{
13 .errors = &.{},
14 .warnings = &.{},
15 .string_bytes = &.{},
16};
1117
12pub var debug_show_report_trace: if (builtin.mode == .Debug) bool else void =18pub var debug_show_report_trace: if (builtin.mode == .Debug) bool else void =
13 if (builtin.mode == .Debug) false;19 if (builtin.mode == .Debug) false;
...@@ -15,31 +21,23 @@ pub var debug_show_report_trace: if (builtin.mode == .Debug) bool else void =...@@ -15,31 +21,23 @@ pub var debug_show_report_trace: if (builtin.mode == .Debug) bool else void =
15pub const Item = struct {21pub const Item = struct {
16 /// What is this diagnostic22 /// What is this diagnostic
17 data: union { @"error": Err, warn: Warn },23 data: union { @"error": Err, warn: Warn },
18 /// What file was it in
19 url: String,
2024
21 debug_stack_trace: DebugStoredTrace,25 debug_stack_trace: DebugStoredTrace = .empty,
2226
23 /// Type is never stored, inferred from which list it is in.27 /// Type is never stored, inferred from which list it is in.
24 pub const Type = enum { @"error", warn };28 pub const Type = enum { @"error", warn };
25};29};
2630
27pub const Err = union(enum) {31pub const Err = union(enum) {
28 pub const FieldEnum = std.meta.FieldEnum(Err);32 pub const FieldEnum = core.FieldEnum(Err);
2933
30 /// Constructs a `Report` with just a single error.34 /// Constructs a `Report` with just a single error.
31 pub fn toReport(err: Err, comp: *Comp, file: *File, trace: DebugStoredTrace) !Report {35 pub fn toReport(err: Err, alloc: Allocator, trace: DebugStoredTrace) !Report {
32 const string_bytes = try comp.gpa.dupe(u8, file.file_path);36 const errors = try alloc.dupe(Item, &.{.{
33 errdefer comp.gpa.free(string_bytes);
34
35 const errors = try comp.gpa.dupe(Item, &.{.{
36 .data = .{ .@"error" = err },37 .data = .{ .@"error" = err },
37 .url = .{ .start = 0, .len = @intCast(string_bytes.len) },
38 .debug_stack_trace = trace,38 .debug_stack_trace = trace,
39 }});39 }});
40 errdefer comp.gpa.free(errors);40 return .{ .errors = errors, .warnings = &.{}, .string_bytes = &.{} };
41
42 return .{ .errors = errors, .string_bytes = string_bytes };
43 }41 }
4442
45 ast: @import("Parse.zig").AstError,43 ast: @import("Parse.zig").AstError,
...@@ -54,6 +52,20 @@ pub const Err = union(enum) {...@@ -54,6 +52,20 @@ pub const Err = union(enum) {
54 });52 });
55 }53 }
56 },54 },
55 not_comptime_known: struct {
56 src: LazyNode,
57
58 fn build(data: @This(), ctx: *MsgContext) !void {
59 try ctx.msgNode(try data.src.get(ctx), "Value must be known at compile-time", .{});
60 }
61 },
62 cannot_be_comptime: struct {
63 src: LazyNode,
64
65 fn build(data: @This(), ctx: *MsgContext) !void {
66 try ctx.msgNode(try data.src.get(ctx), "Operation cannot be performed at compile-time (TODO: make this error more specific)", .{});
67 }
68 },
57 unknown_identifier: struct {69 unknown_identifier: struct {
58 node: NodeIndex,70 node: NodeIndex,
5971
...@@ -82,8 +94,8 @@ pub const Err = union(enum) {...@@ -82,8 +94,8 @@ pub const Err = union(enum) {
82 fn build(data: @This(), ctx: *MsgContext) !void {94 fn build(data: @This(), ctx: *MsgContext) !void {
83 try ctx.msgToken(95 try ctx.msgToken(
84 ctx.ast.nodeToken(data.node),96 ctx.ast.nodeToken(data.node),
85 "unknown built-in function {}",97 "unknown built-in function {s}",
86 .{formatId(ctx.ast.basicNodeSlice(data.node))},98 .{ctx.ast.basicNodeSlice(data.node)},
87 );99 );
88 }100 }
89 },101 },
...@@ -103,10 +115,11 @@ pub const Err = union(enum) {...@@ -103,10 +115,11 @@ pub const Err = union(enum) {
103 prev: NodeIndex,115 prev: NodeIndex,
104116
105 fn build(data: @This(), ctx: *MsgContext) !void {117 fn build(data: @This(), ctx: *MsgContext) !void {
106 try ctx.msgToken(ctx.ast.fnDeclIdToken(data.node), "re-declaration of {}", .{118 const id_token = ctx.ast.idToken(data.node);
107 formatId(ctx.ast.fullFnDecl(data.node).id),119 try ctx.msgToken(id_token, "re-declaration of {}", .{
120 formatId(ctx.ast.tokenSlice(id_token)),
108 });121 });
109 try ctx.msgToken(ctx.ast.fnDeclIdToken(data.prev), "also declared here", .{});122 try ctx.msgToken(ctx.ast.idToken(data.prev), "also declared here", .{});
110 // TODO: be able to store a full list of other declarations123 // TODO: be able to store a full list of other declarations
111 // and add them here as "and here" and so on124 // and add them here as "and here" and so on
112 }125 }
...@@ -177,6 +190,14 @@ pub const Err = union(enum) {...@@ -177,6 +190,14 @@ pub const Err = union(enum) {
177 try ctx.msgNode(node, "numeric literal \"{d}\" cannot be coerced to \"int\"", .{data.val});190 try ctx.msgNode(node, "numeric literal \"{d}\" cannot be coerced to \"int\"", .{data.val});
178 }191 }
179 },192 },
193 top_level_statement: struct {
194 src: NodeIndex,
195
196 fn build(data: @This(), ctx: *MsgContext) !void {
197 try ctx.msgNode(data.src, "top-level statements are not allowed", .{});
198 try ctx.msgAlone("to run code on application startup, add a 'fn main'", .{});
199 }
200 },
180 coercion: struct {201 coercion: struct {
181 from: Type,202 from: Type,
182 to: Type,203 to: Type,
...@@ -535,6 +556,29 @@ pub const Warn = union(enum) {...@@ -535,6 +556,29 @@ pub const Warn = union(enum) {
535 },556 },
536};557};
537558
559pub const DumpContext = union(enum) {
560 /// Reports generated from Semantic Analysis require full
561 /// compilation state, notably InternPool to read values.
562 comp: struct {
563 comp: *Comp,
564 file: *Comp.File,
565 },
566 /// Reports generated from Parse and Transform are
567 /// single-file, and can be dumped without full state.
568 single_file: struct {
569 allocator: Allocator,
570 url: []const u8,
571 file: union(enum) {
572 utf8_source: [:0]const u8,
573 ast: *const Ast,
574 },
575 },
576
577 pub fn initComp(comp: *Comp, file: *Comp.File) DumpContext {
578 return .{ .comp = .{ .comp = comp, .file = file } };
579 }
580};
581
538pub const UnusedKind = enum {582pub const UnusedKind = enum {
539 param,583 param,
540 constant,584 constant,
...@@ -579,50 +623,96 @@ pub fn deinit(report: Report, gpa: Allocator) void {...@@ -579,50 +623,96 @@ pub fn deinit(report: Report, gpa: Allocator) void {
579 gpa.free(report.string_bytes);623 gpa.free(report.string_bytes);
580}624}
581625
582pub fn dumpToWriter(report: Report, comp: *Comp, writer: anytype, tty: std.io.tty.Config) !void {626pub fn dump(report: Report, ctx: DumpContext, out: core.OutStream) !void {
583 // try writer.print("{d} errors, {d} warnings\n", .{ report.errors.len, report.warnings.len });627 var arena = std.heap.ArenaAllocator.init(switch (ctx) {
628 .comp => |comp_ctx| comp_ctx.comp.gpa,
629 .single_file => |single_file_ctx| single_file_ctx.allocator,
630 });
631 defer arena.deinit();
632
633 var fake_ast_data: Ast = undefined;
634
635 const ast: *const Ast = switch (ctx) {
636 .comp => |comp_ctx|
637 // Ast cannot be queried if the error is in computing the Ast
638 if (report.errors.len > 0 and report.errors[0].data.@"error" == .ast) fake_ast: {
639 fake_ast_data = .{
640 .utf8_source = (comp_ctx.file.getSource(comp_ctx.comp.gpa) catch {
641 @panic("TODO");
642 // try report.dumpErrorDuringDump(report, .get_source, err, @errorReturnTrace()),
643 // return;
644 }).code,
645 .tokens = (Tokenizer.Token.List{}).slice(),
646 .nodes = (Ast.Node.List{}).slice(),
647 .extra = &.{},
648 };
649 break :fake_ast &fake_ast_data;
650 } else comp_ctx.file.getAst(comp_ctx.comp) catch {
651 @panic("TODO");
652 // try report.dumpErrorDuringDump(report, .get_ast, err, @errorReturnTrace()),
653 // return;
654 },
655 .single_file => |single_file_ctx| switch (single_file_ctx.file) {
656 .ast => |ast| ast,
657 .utf8_source => |source| fake_ast: {
658 fake_ast_data = .{
659 .utf8_source = source,
660 .tokens = (Tokenizer.Token.List{}).slice(),
661 .nodes = (Ast.Node.List{}).slice(),
662 .extra = &.{},
663 };
664 break :fake_ast &fake_ast_data;
665 },
666 },
667 };
668
669 const url = switch (ctx) {
670 .comp => |comp_ctx| comp_ctx.file.file_path,
671 .single_file => |file_ctx| file_ctx.url,
672 };
673
674 const msg_ctx: MsgContext = .{
675 .report = &report,
676 .comp = switch (ctx) {
677 .comp => |comp_ctx| comp_ctx.comp,
678 else => null,
679 },
680 .file = switch (ctx) {
681 .comp => |comp_ctx| comp_ctx.file,
682 else => null,
683 },
684 .ast = ast,
685 .utf8_source = ast.utf8_source,
686
687 .arena = undefined,
688 .components = undefined,
689 };
690
584 for (report.errors) |item| {691 for (report.errors) |item| {
585 try report.formatItem(item, .@"error", comp, writer, tty);692 try formatItem(item, .@"error", arena.allocator(), url, msg_ctx, out);
693 _ = arena.reset(.retain_capacity);
586 }694 }
587 for (report.warnings) |item| {695 for (report.warnings) |item| {
588 try report.formatItem(item, .warn, comp, writer, tty);696 try formatItem(item, .warn, arena.allocator(), url, msg_ctx, out);
697 _ = arena.reset(.retain_capacity);
589 }698 }
590}699}
591700
592pub fn formatItem(701fn formatItem(
593 report: *const Report,
594 item: Item,702 item: Item,
595 item_type: Item.Type,703 item_type: Item.Type,
596 comp: *Comp,704 arena: Allocator,
597 writer: anytype,705 url: []const u8,
598 tty: std.io.tty.Config,706 ctx_init: MsgContext,
707 out: core.OutStream,
599) !void {708) !void {
600 var arena = std.heap.ArenaAllocator.init(comp.gpa);709 var sfa = std.heap.stackFallback(16384, arena);
601 defer arena.deinit();
602 var sfa = std.heap.stackFallback(16384, arena.allocator());
603 const alloc = sfa.get();710 const alloc = sfa.get();
604711
605 const file = try comp.getFile(item.url.slice(report));712 var ctx: MsgContext = ctx_init;
606713 ctx.arena = alloc;
607 const need_load_ast = !(item_type == .@"error" and item.data.@"error" == .ast);714 ctx.components = ArrayListUnmanaged(Component).initCapacity(alloc, 16) catch
608 const ast_data = if (need_load_ast)715 unreachable; // stack-fallback allocator has enough space.
609 try file.getAst(comp)
610 else brk: {
611 var ast: Ast = undefined;
612 ast.utf8_source = (try file.getSource(comp.gpa)).code;
613 break :brk ast;
614 };
615
616 var ctx: MsgContext = .{
617 .arena = alloc,
618 .comp = comp,
619 .file = file,
620 .ast = &ast_data,
621 .report = report,
622 .utf8_source = (try file.getSource(comp.gpa)).code,
623 .components = ArrayListUnmanaged(Component).initCapacity(alloc, 16) catch
624 unreachable, // stack-fallback allocator has enough space.
625 };
626716
627 switch (item_type) {717 switch (item_type) {
628 inline else => |tag| switch (@field(item.data, @tagName(tag))) {718 inline else => |tag| switch (@field(item.data, @tagName(tag))) {
...@@ -632,6 +722,9 @@ pub fn formatItem(...@@ -632,6 +722,9 @@ pub fn formatItem(
632722
633 assert(ctx.components.items.len > 0);723 assert(ctx.components.items.len > 0);
634724
725 const writer = out.writer;
726 const tty = out.tty;
727
635 const width = brk: {728 const width = brk: {
636 var max: usize = 0;729 var max: usize = 0;
637 for (ctx.components.items) |component| {730 for (ctx.components.items) |component| {
...@@ -704,7 +797,7 @@ pub fn formatItem(...@@ -704,7 +797,7 @@ pub fn formatItem(
704 try writer.writeByteNTimes(' ', width);797 try writer.writeByteNTimes(' ', width);
705 try writer.print("{s}{s}:{d}:{d}\n", .{798 try writer.print("{s}{s}:{d}:{d}\n", .{
706 if (i == 1) "┬ " else "├ ",799 if (i == 1) "┬ " else "├ ",
707 file.file_path,800 url,
708 at.loc.line.oneBased(),801 at.loc.line.oneBased(),
709 at.loc.column.oneBased(),802 at.loc.column.oneBased(),
710 });803 });
...@@ -740,52 +833,58 @@ pub fn formatItem(...@@ -740,52 +833,58 @@ pub fn formatItem(
740 }833 }
741}834}
742835
743pub fn dumpToStdErr(report: Report, comp: *Comp) !void {836// pub fn dumpToStdErr(report: Report, comp: *Comp) !void {
744 const stderr = std.io.getStdErr();837// const stderr = std.io.getStdErr();
745 const tty = std.io.tty.detectConfig(stderr);838// const tty = std.io.tty.detectConfig(stderr);
746839
747 if (false or840// if (false or
748 // Windows API uses its own API calls, so a buffered writer841// // Windows API uses its own API calls, so a buffered writer
749 // cannot be used :/842// // cannot be used :/
750 (builtin.os.tag == .windows and tty == .windows_api) or843// (builtin.os.tag == .windows and tty == .windows_api) or
751 // Debug stack trace printer writes directly to standard err,844// // Debug stack trace printer writes directly to standard err,
752 // so we need to be unbuffered for that.845// // so we need to be unbuffered for that.
753 (builtin.mode == .Debug and debug_show_report_trace))846// (builtin.mode == .Debug and debug_show_report_trace))
754 {847// {
755 try dumpToWriter(report, comp, stderr.writer(), tty);848// try dumpToWriter(report, comp, stderr.writer(), tty);
756 } else {849// } else {
757 var buf = std.io.bufferedWriter(stderr.writer());850// var buf = std.io.bufferedWriter(stderr.writer());
758 const writer = buf.writer();851// const writer = buf.writer();
759 try dumpToWriter(report, comp, writer, tty);852// try dumpToWriter(report, comp, writer, tty);
760 try buf.flush();853// try buf.flush();
761 }854// }
762}855// }
763856
764pub const Builder = struct {857pub const Builder = struct {
765 gpa: Allocator,858 gpa: Allocator,
766 current_file: *Comp.File,859 errors: ArrayListUnmanaged(Item),
767 errors: ArrayListUnmanaged(Item) = .{},860 warnings: ArrayListUnmanaged(Item),
768 warnings: ArrayListUnmanaged(Item) = .{},861 string_bytes: ArrayListUnmanaged(u8),
769 string_bytes: ArrayListUnmanaged(u8) = .{},
770 current_file_str: ?String = null,
771862
772 pub fn err(b: *Builder, comptime kind: Err.FieldEnum, data: std.meta.FieldType(Err, kind)) !void {863 pub fn init(gpa: Allocator) Builder {
864 return .{
865 .gpa = gpa,
866 .errors = .empty,
867 .warnings = .empty,
868 .string_bytes = .empty,
869 };
870 }
871
872 pub fn err(b: *Builder, comptime kind: Err.FieldEnum, data: core.FieldType(Err, kind)) !void {
773 try errAt(b, kind, data, @returnAddress());873 try errAt(b, kind, data, @returnAddress());
774 }874 }
775875
776 pub fn warn(b: *Builder, comptime kind: Warn.FieldEnum, data: std.meta.FieldType(Warn, kind)) !void {876 pub fn warn(b: *Builder, comptime kind: Warn.FieldEnum, data: core.FieldType(Warn, kind)) !void {
777 try warnAt(b, kind, data, @returnAddress());877 try warnAt(b, kind, data, @returnAddress());
778 }878 }
779879
780 pub fn errAt(880 pub fn errAt(
781 b: *Builder,881 b: *Builder,
782 comptime kind: Err.FieldEnum,882 comptime kind: Err.FieldEnum,
783 data: std.meta.FieldType(Err, kind),883 data: core.FieldType(Err, kind),
784 return_address: usize,884 return_address: usize,
785 ) !void {885 ) !void {
786 try b.errors.append(b.gpa, .{886 try b.errors.append(b.gpa, .{
787 .data = .{ .@"error" = @unionInit(Err, @tagName(kind), data) },887 .data = .{ .@"error" = @unionInit(Err, @tagName(kind), data) },
788 .url = try b.currentFileString(),
789 .debug_stack_trace = DebugStoredTrace.capture(return_address),888 .debug_stack_trace = DebugStoredTrace.capture(return_address),
790 });889 });
791 }890 }
...@@ -793,12 +892,11 @@ pub const Builder = struct {...@@ -793,12 +892,11 @@ pub const Builder = struct {
793 pub fn warnAt(892 pub fn warnAt(
794 b: *Builder,893 b: *Builder,
795 comptime kind: Warn.FieldEnum,894 comptime kind: Warn.FieldEnum,
796 data: std.meta.FieldType(Warn, kind),895 data: core.FieldType(Warn, kind),
797 return_address: usize,896 return_address: usize,
798 ) !void {897 ) !void {
799 try b.warnings.append(b.gpa, .{898 try b.warnings.append(b.gpa, .{
800 .data = .{ .warn = @unionInit(Warn, @tagName(kind), data) },899 .data = .{ .warn = @unionInit(Warn, @tagName(kind), data) },
801 .url = try b.currentFileString(),
802 .debug_stack_trace = DebugStoredTrace.capture(return_address),900 .debug_stack_trace = DebugStoredTrace.capture(return_address),
803 });901 });
804 }902 }
...@@ -823,17 +921,14 @@ pub const Builder = struct {...@@ -823,17 +921,14 @@ pub const Builder = struct {
823 }921 }
824922
825 pub fn finalize(b: *Builder) !Report {923 pub fn finalize(b: *Builder) !Report {
826 defer b.* = undefined;
827 errdefer b.deinit();
828
829 const errors = try b.errors.toOwnedSlice(b.gpa);924 const errors = try b.errors.toOwnedSlice(b.gpa);
830 errdefer b.gpa.free(errors);925 errdefer b.errors = .fromOwnedSlice(errors);
831926
832 const warnings = try b.warnings.toOwnedSlice(b.gpa);927 const warnings = try b.warnings.toOwnedSlice(b.gpa);
833 errdefer b.gpa.free(warnings);928 errdefer b.warnings = .fromOwnedSlice(warnings);
834929
835 const string_bytes = try b.string_bytes.toOwnedSlice(b.gpa);930 const string_bytes = try b.string_bytes.toOwnedSlice(b.gpa);
836 errdefer b.gpa.free(string_bytes);931 errdefer b.string_bytes = .fromOwnedSlice(string_bytes);
837932
838 return .{933 return .{
839 .errors = errors,934 .errors = errors,
...@@ -943,8 +1038,8 @@ pub const Highlight = union(enum) {...@@ -943,8 +1038,8 @@ pub const Highlight = union(enum) {
9431038
944/// This serves as a small API to make creating messages easier.1039/// This serves as a small API to make creating messages easier.
945pub const MsgContext = struct {1040pub const MsgContext = struct {
946 file: *const File,1041 file: ?*const File,
947 comp: *const Comp,1042 comp: ?*const Comp,
948 ast: *const Ast,1043 ast: *const Ast,
949 report: *const Report,1044 report: *const Report,
950 utf8_source: [:0]const u8,1045 utf8_source: [:0]const u8,
...@@ -981,18 +1076,19 @@ pub const MsgContext = struct {...@@ -981,18 +1076,19 @@ pub const MsgContext = struct {
981 }1076 }
9821077
983 pub fn ip(ctx: *const MsgContext) *const InternPool {1078 pub fn ip(ctx: *const MsgContext) *const InternPool {
984 return &ctx.comp.intern_pool;1079 return &ctx.comp.?.intern_pool;
985 }1080 }
986};1081};
9871082
988pub const LazyNode = struct {1083pub const LazyNode = struct {
1084 // TODO: remove this, it should be inferable off of the analysis unit that belongs to the report
989 decl: Comp.Decl.Index,1085 decl: Comp.Decl.Index,
990 inst: Teg.Inst.Index,1086 inst: Teg.Inst.Index,
991 operand: Operand,1087 operand: Operand,
9921088
993 pub fn get(lazy: LazyNode, ctx: *MsgContext) !NodeIndex {1089 pub fn get(lazy: LazyNode, ctx: *MsgContext) !NodeIndex {
994 const decl = ctx.comp.intern_pool.declPtrConst(lazy.decl);1090 const decl = ctx.comp.?.intern_pool.declPtrConst(lazy.decl);
995 const ns = ctx.comp.intern_pool.namespacePtrConst(decl.src_namespace);1091 const ns = ctx.comp.?.intern_pool.namespacePtrConst(decl.src_namespace);
9961092
997 // TODO: This might change if we have errors like1093 // TODO: This might change if we have errors like
998 // - function arguments not matching at <callsite>1094 // - function arguments not matching at <callsite>
...@@ -1005,7 +1101,7 @@ pub const LazyNode = struct {...@@ -1005,7 +1101,7 @@ pub const LazyNode = struct {
1005 @panic("TODO: LazyNode.get when the decl is in a nested namespace");1101 @panic("TODO: LazyNode.get when the decl is in a nested namespace");
1006 }1102 }
10071103
1008 const teg = &(ctx.file.teg orelse1104 const teg = &(ctx.file.?.teg orelse
1009 // this will be required once incremental compilation hits1105 // this will be required once incremental compilation hits
1010 @panic("TODO: teg was unloaded"));1106 @panic("TODO: teg was unloaded"));
10111107
...@@ -1072,7 +1168,7 @@ pub const LazyNode = struct {...@@ -1072,7 +1168,7 @@ pub const LazyNode = struct {
1072 else => fn_decl.params.slice(ctx.ast)[lazy.operand.getArgId()],1168 else => fn_decl.params.slice(ctx.ast)[lazy.operand.getArgId()],
1073 };1169 };
1074 },1170 },
1075 .js => {1171 .@"extern" => {
1076 return base_node;1172 return base_node;
1077 },1173 },
1078 .struct_init => {1174 .struct_init => {
...@@ -1090,6 +1186,11 @@ pub const LazyNode = struct {...@@ -1090,6 +1186,11 @@ pub const LazyNode = struct {
1090 },1186 },
1091 }1187 }
1092 },1188 },
1189 .struct_decl => {
1190 const fields = data.list.slice(ctx.ast);
1191 const field = fields[lazy.operand.getArgId()];
1192 return ctx.ast.nodeData(field).un.expr;
1193 },
1093 .array_init => {1194 .array_init => {
1094 assert(node_tag == .array_init);1195 assert(node_tag == .array_init);
1095 const items = data.list.slice(ctx.ast);1196 const items = data.list.slice(ctx.ast);
...@@ -1101,11 +1202,21 @@ pub const LazyNode = struct {...@@ -1101,11 +1202,21 @@ pub const LazyNode = struct {
1101 else => {},1202 else => {},
1102 }1203 }
11031204
1104 std.debug.panic("TODO: operand lookup: .{s} + .{s} + {}", .{1205 if (builtin.mode == .Debug) {
1105 @tagName(teg.instTag(lazy.inst)),1206 std.debug.panic("TODO: operand lookup: .{s} + .{s} + {}", .{
1106 @tagName(ctx.ast.nodeTag(base_node)),1207 @tagName(teg.instTag(lazy.inst)),
1107 lazy.operand,1208 @tagName(ctx.ast.nodeTag(base_node)),
1108 });1209 lazy.operand,
1210 });
1211 } else {
1212 std.debug.print("TODO: operand lookup: .{s} + .{s} + {}", .{
1213 @tagName(teg.instTag(lazy.inst)),
1214 @tagName(ctx.ast.nodeTag(base_node)),
1215 lazy.operand,
1216 });
1217 }
1218
1219 return base_node;
1109 }1220 }
1110};1221};
11111222
...@@ -1125,18 +1236,29 @@ pub const Operand = enum(u32) {...@@ -1125,18 +1236,29 @@ pub const Operand = enum(u32) {
1125 /// One of:1236 /// One of:
1126 /// - An argument of a .call node1237 /// - An argument of a .call node
1127 /// - A parameter of a .fn_decl node1238 /// - A parameter of a .fn_decl node
1239 /// - A value of a .struct_decl node
1128 pub fn argument(id: u32) Operand {1240 pub fn argument(id: u32) Operand {
1129 return @enumFromInt(id + 5);1241 return @enumFromInt(id + 6);
1130 }1242 }
11311243
1132 fn getArgId(op: Operand) u32 {1244 fn getArgId(op: Operand) u32 {
1133 return @intFromEnum(op) - 5;1245 return @intFromEnum(op) - 6;
1246 }
1247
1248 pub fn format(op: Operand, comptime fmt: []const u8, _: std.fmt.FormatOptions, writer: std.io.AnyWriter) !void {
1249 comptime assert(fmt.len == 0);
1250 if (@intFromEnum(op) <= @intFromEnum(Operand.rhs)) {
1251 try writer.writeAll(".");
1252 try writer.writeAll(@tagName(op));
1253 } else {
1254 try writer.print(".argument({d})", .{op.getArgId()});
1255 }
1134 }1256 }
1135};1257};
11361258
1137/// Non-allocating formatter for taking an id token slice (raw), normalizing it1259/// Non-allocating formatter for taking an id token slice (raw), normalizing it
1138/// into it's canonical form.1260/// into it's canonical form.
1139fn formatId(id: []const u8) std.fmt.Formatter(formatIdImpl) {1261pub fn formatId(id: []const u8) std.fmt.Formatter(formatIdImpl) {
1140 return .{ .data = id };1262 return .{ .data = id };
1141}1263}
11421264
...@@ -1148,7 +1270,7 @@ fn formatIdImpl(...@@ -1148,7 +1270,7 @@ fn formatIdImpl(
1148) !void {1270) !void {
1149 comptime assert(format.len == 0);1271 comptime assert(format.len == 0);
11501272
1151 if (id[0] != '@') {1273 if (id[0] != '@' and !Tokenizer.Token.keywords.has(id)) {
1152 // no work needed1274 // no work needed
1153 try writer.writeByte('"');1275 try writer.writeByte('"');
1154 try writer.writeAll(id);1276 try writer.writeAll(id);
...@@ -1303,14 +1425,14 @@ fn formatIdImpl(...@@ -1303,14 +1425,14 @@ fn formatIdImpl(
1303 try writer.writeByte('"');1425 try writer.writeByte('"');
1304}1426}
13051427
1306fn formatIdCode(id: []const u8) std.fmt.Formatter(formatIdCodeImpl) {1428pub fn formatIdCode(id: []const u8) std.fmt.Formatter(formatIdCodeImpl) {
1307 return .{ .data = id };1429 return .{ .data = id };
1308}1430}
13091431
1310fn formatIdCodeImpl(1432fn formatIdCodeImpl(
1311 id: []const u8,1433 id: []const u8,
1312 comptime format: []const u8,1434 comptime format: []const u8,
1313 _: std.fmt.FormatOptions,1435 opts: std.fmt.FormatOptions,
1314 writer: anytype,1436 writer: anytype,
1315) !void {1437) !void {
1316 comptime assert(format.len == 0);1438 comptime assert(format.len == 0);
...@@ -1319,10 +1441,7 @@ fn formatIdCodeImpl(...@@ -1319,10 +1441,7 @@ fn formatIdCodeImpl(
1319 try writer.writeAll(id);1441 try writer.writeAll(id);
1320 return;1442 return;
1321 }1443 }
13221444 try formatIdImpl(id, format, opts, writer);
1323 try writer.writeAll("@\"");
1324
1325 try writer.writeByte('"');
1326}1445}
13271446
1328const Report = @This();1447const Report = @This();
src/core/Sema.zig+1341-1088
...@@ -1,85 +1,287 @@...@@ -1,85 +1,287 @@
1/// absolutely stealing zig on this one haha1//! Semantic Analysis
2/// This struct is created per declaration and chills2//!
3///3//! Designed as a nonrecursive pause/resumable state machine, Sema converts
4/// TODO: explain this in more depth. or maybe take this to architecture.md4//! declarations to Intern Pool `Value`s and function bodies to `Air`.
5///5//!
6/// All things in semantic analysis are either Air.Inst.Index (runtime known6//! - Compile time resolution of values
7/// value pointing to the instruction that computed it), or Value (comptime known)7//! - Type checking
8///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)
26
27// inputs:
28comp: *Comp,9comp: *Comp,
29gpa: Allocator,
30arena: Allocator,
31code: Teg,
32ip: *InternPool,10ip: *InternPool,
33decl: *Decl,11gpa: Allocator,
34decl_index: Decl.Index,12arena_state: std.heap.ArenaAllocator,
35namespace_index: Namespace.Index,13/// The thing being analyzed.
14/// When this is .decl, we are producing a `Value`
15owner: AnalysisUnit,
16
17/// The namespace index that is used for declaration lookups
18ns_index: Namespace.Index,
19/// Non-owning copy
20teg: Teg,
21/// The file that .teg is from
22file: *File,
23
24/// All errors and warnings from this decl
25report: Report.Builder,
26/// Map of `Teg` instructions to their resulting `Air` Ref
27inst_map: InstMap,
28
29/// `null` when not analyzing function bodies
30func: ?Function,
31
32frame: Frame,
33frame_stack: DynamicArray(Frame),
34
35/// List of `*Sema` that is waiting on this analysis to complete.
36/// Backed by `comp.gpa`
37blocked_semas: DynamicArray(*Sema),
3638
37// temp data:
38inst_map: InstMap = undefined,
39/// Scratch memory for temporary allocations.39/// Scratch memory for temporary allocations.
40/// Block contents are appended for marks.40/// Block contents are appended for marks.
41scratch: core.U32Stack = .{},41scratch: core.U32Stack,
42
43/// This is only set for function declarations
44params: []Type = undefined,
4542
46// outputs43/// To validate that a pointer to `Sema` is not corrupted.
47report: Report.Builder = undefined,44magic: Magic align(1),
48air_instructions: std.MultiArrayList(Air.Inst) = .{},
49air_extra: store.ExtraStore(.air, .@"var") = .{},
5045
51temp_result_map: std.AutoArrayHashMapUnmanaged(Teg.Inst.Index, Type) = .{},46instruction_step_lock: std.debug.SafetyLock,
5247
53pub const Error = error{48pub const Error = error{
54 /// Analysis has failed, see error list49 /// The analysis of this branch or declaration has failed.
50 /// At least one error will be placed in the `Sema`'s report
55 AnalysisFail,51 AnalysisFail,
56 // This analysis is waiting on other dependencies to finish52 /// There is a dependency on another declaration that we are waiting on.
57 // Interrupt,53 Interrupt,
5854 /// Must invoke garbage collection?
59 // TODO: add handling of this
60 TooManyValues,55 TooManyValues,
61 // TODO: add handling of this
62 HasNulls,
63 // TODO: remove this from the primary error set
64 HitNoReturn,
65
66 OutStream,
67 OutOfMemory,56 OutOfMemory,
68} || Comp.File.GetTegError;57};
58
59pub fn init(
60 s: *Sema,
61 comp: *Comp,
62 file: *File,
63 teg: Teg,
64 owner: AnalysisUnit,
65 ns_index: Namespace.Index,
66 arena_state: std.heap.ArenaAllocator,
67) Allocator.Error!void {
68 const t = tracer.trace(@src());
69 defer t.end();
70
71 assert(comp.intern_pool.items.len > 0); // did not initialize intern pool
72 _ = comp.intern_pool.items.bytes[0]; // segfault if intern pool is undefined memory
73
74 s.* = .{
75 .comp = comp,
76 .ip = &comp.intern_pool,
77 .gpa = comp.gpa,
78
79 .arena_state = arena_state,
80 .owner = owner,
81 .ns_index = ns_index,
82 .teg = teg,
83 .file = file,
84
85 .report = .init(comp.gpa),
86 .inst_map = undefined,
87 .func = switch (owner.kind) {
88 .decl => null,
89 .func => .empty,
90 },
91 .frame = undefined,
92 .frame_stack = .empty,
93 .blocked_semas = .empty,
94 .scratch = .empty,
95 .magic = .valid,
96 .instruction_step_lock = .{},
97 };
98
99 const first_instruction, const count = switch (owner.unwrap()) {
100 .decl => |decl| data: {
101 const decl_inst = s.ip.declPtr(decl).teg_decl_index.resolve(s.ip);
102 const teg_decl: Teg.Decl = teg.instPayload(.declaration, decl_inst);
103 break :data .{ decl_inst, teg_decl.instruction_count };
104 },
105 .func => |value| data: {
106 const func_inst = value.toKey(s.ip).func.teg_body_inst.get(s.ip);
107 const func: Teg.Func = teg.instPayload(.func, func_inst.inst);
108 break :data .{ func.body, func.instruction_count };
109 },
110 };
111
112 s.inst_map = try .init(s.arena(), first_instruction, count);
113
114 const block_index = switch (owner.unwrap()) {
115 .decl => switch (s.teg.declData(first_instruction).body.decode()) {
116 .index => |index| index,
117 .value => unreachable, // Sema must not be created for a static value
118 },
119 .func => first_instruction,
120 };
121 s.frame = Frame.initFromBlock(
122 s,
123 block_index,
124 switch (owner.kind) {
125 .decl => .compile_time,
126 .func => .run_time,
127 },
128 .none,
129 );
130
131 if (owner.kind == .func) {
132 assert(try s.addIndexUntracked(.block, undefined) == .main_block);
133 }
134}
135
136/// Does not free the allocated pointer itself. Releases the arena to be used for another analysis.
137pub fn deinit(sema: *Sema, arena_reset_mode: std.heap.ArenaAllocator.ResetMode) std.heap.ArenaAllocator {
138 defer sema.* = undefined;
139 assert(sema.magic == .valid);
140
141 sema.report.deinit();
142 sema.blocked_semas.deinit(sema.gpa);
143
144 if (sema.func) |*func| {
145 func.deinit(sema.gpa);
146 }
147
148 _ = sema.arena_state.reset(arena_reset_mode);
149 return sema.arena_state;
150}
151
152const AnalysisMode = enum {
153 /// Runtime operands are allowed, and emit Air instructions on `sema.func`.
154 run_time,
155 /// Any attempt to emit instructions turn into analysis errors, the result
156 /// is guanteed to be a `Value`.
157 ///
158 /// Compile time analysis can result in a function who's body executes at
159 /// run time, but that body is not queued for analysis until .run_time
160 /// analysis requires the function.
161 compile_time,
162};
163
164const Frame = struct {
165 ip: [*]const Teg.Inst.Index,
166 instructions_left: if (runtime_safety) usize else void,
167
168 mode: AnalysisMode,
169 block_index: Teg.Inst.Index,
170 mark_index: u32,
171 data: Data,
172
173 const Data = union {
174 pub const none = .{ .undefined = {} };
175
176 undefined: void,
177 conditional: Conditional,
178 };
179
180 const Conditional = enum {
181 comptime_condition,
182 runtime_yes,
183 runtime_no,
184 };
185
186 fn initFromBlock(s: *Sema, block_index: Teg.Inst.Index, mode: AnalysisMode, data: Data) Frame {
187 const list = s.teg.instEncodedData(block_index)
188 .decodeUnsafe(Teg.PlNode(Teg.IndexList), s.teg.instTag(block_index))
189 .extra_index.get(&s.teg)
190 .slice(&s.teg);
191 return .{
192 .ip = list[0..].ptr,
193 .instructions_left = if (runtime_safety) list.len,
194 .block_index = block_index,
195 .data = data,
196 .mode = mode,
197 .mark_index = @intCast(s.scratch.mark().index),
198 };
199 }
69200
70/// Instead of using a Hash map for this, all of the information is pre-allocated.201 fn currentIndex(frame: *const Frame) Teg.Inst.Index {
71/// This is only possible because all instructions are adjacent in Teg.202 // validate the pointer is correct
203 if (runtime_safety) {
204 const s: *const Sema = @fieldParentPtr("frame", frame);
205 assert(s.magic == .valid);
206 const extra = s.teg.extra.list.items;
207 assert(@intFromPtr(frame.ip) > @intFromPtr(extra.ptr) and
208 @intFromPtr(frame.ip) <= @intFromPtr(&extra.ptr[extra.len - 1]));
209 }
210
211 return frame.ip[0];
212 }
213
214 fn currentTag(frame: *const Frame) Teg.Tag {
215 const s: *const Sema = @fieldParentPtr("frame", frame);
216 assert(s.magic == .valid);
217 return s.teg.instTag(frame.currentIndex());
218 }
219
220 fn currentData(frame: *const Frame, comptime tag: Teg.Tag) tag.Data() {
221 const s: *const Sema = @fieldParentPtr("frame", frame);
222 assert(s.magic == .valid);
223 return s.teg.instData(tag, frame.currentIndex());
224 }
225
226 fn currentPayload(frame: *const Frame, comptime tag: Teg.Tag) tag.Data().T {
227 const s: *const Sema = @fieldParentPtr("frame", frame);
228 assert(s.magic == .valid);
229 return s.teg.instPayload(tag, frame.currentIndex());
230 }
231
232 fn advance(frame: *Frame) void {
233 if (runtime_safety) {
234 frame.instructions_left -= 1;
235 assert(frame.instructions_left > 0); // block is missing a break/did not stop at break
236 }
237 frame.ip += 1;
238 }
239};
240
241const Function = struct {
242 instructions: std.MultiArrayList(Air.Inst),
243 extra: store.ExtraStore(.air, .@"var"),
244
245 pub const empty: Function = .{
246 .instructions = .{},
247 .extra = .empty,
248 };
249
250 pub fn finalize(func: *Function, gpa: Allocator) !Air {
251 defer func.* = .empty;
252 const air: Air = .{
253 .insts = func.instructions.slice(),
254 .extra = try func.extra.toOwnedStore(gpa),
255 };
256 return air;
257 }
258
259 pub fn deinit(func: *Function, gpa: Allocator) void {
260 defer func.* = undefined;
261 func.instructions.deinit(gpa);
262 func.extra.deinit(gpa);
263 }
264};
265
266/// Instead of using a HashMap, all of the information is pre-allocated, and
267/// lookups are done by subtracing the base index. This is only possible because
268/// all instructions are adjacent, and a declaration knows exactly how many
269/// instructions it has.
72const InstMap = struct {270const InstMap = struct {
73 items: []Air.Inst.Ref.Optional,271 items: []Air.Inst.Ref.Optional,
74 start: Teg.Inst.Index,272 start: Teg.Inst.Index,
75 debug_traces: if (std.debug.runtime_safety) []StoredTrace else void,273 debug_traces: if (runtime_safety) []StoredTrace else void,
76274
77 pub fn init(allocator: Allocator, code: *const Teg, inst_start: Teg.Inst.Index) !InstMap {275 pub fn init(allocator: Allocator, inst_start: Teg.Inst.Index, inst_len: u32) !InstMap {
78 const decl = code.declData(inst_start);276 const insts = try allocator.alloc(Air.Inst.Ref.Optional, inst_len);
79 const insts = try allocator.alloc(Air.Inst.Ref.Optional, decl.instruction_count);277 errdefer allocator.free(insts);
80 const debug_traces = if (std.debug.runtime_safety)278
81 try allocator.alloc(StoredTrace, decl.instruction_count);279 const debug_traces = if (runtime_safety)
82 @memset(insts, .none);280 try allocator.alloc(StoredTrace, inst_len);
281 errdefer if (runtime_safety)
282 allocator.free(debug_traces);
283
284 @memset(insts[0..], .none);
83 return .{285 return .{
84 .items = insts,286 .items = insts,
85 .start = inst_start,287 .start = inst_start,
...@@ -89,435 +291,262 @@ const InstMap = struct {...@@ -89,435 +291,262 @@ const InstMap = struct {
89291
90 pub fn deinit(map: *InstMap, allocator: Allocator) void {292 pub fn deinit(map: *InstMap, allocator: Allocator) void {
91 allocator.free(map.items);293 allocator.free(map.items);
92 if (std.debug.runtime_safety)294 if (runtime_safety)
93 allocator.free(map.debug_traces);295 allocator.free(map.debug_traces);
94 map.* = undefined;296 map.* = undefined;
95 }297 }
96298
97 pub fn get(map: *const InstMap, teg_code: Teg.Inst.Index) ?Air.Inst.Ref {299 pub fn getOrNull(map: *const InstMap, teg_code: Teg.Inst.Index) ?Air.Inst.Ref {
98 const source_i = @intFromEnum(teg_code);300 const source_i = @intFromEnum(teg_code);
99 const start_i = @intFromEnum(map.start);301 const start_i = @intFromEnum(map.start);
100 assert(source_i >= start_i and source_i <= (start_i + map.items.len));302 assert(source_i >= start_i and source_i <= start_i + map.items.len);
303 if (assertFailed(source_i >= start_i and source_i <= start_i + map.items.len)) {
304 std.debug.panic("attempt fetching instruction %{d} which is out of range of %{d}-%{d}", .{
305 source_i,
306 start_i,
307 start_i + map.items.len,
308 });
309 }
101 return map.items[source_i - start_i].unwrap();310 return map.items[source_i - start_i].unwrap();
102 }311 }
103312
104 pub fn add(map: *InstMap, from: Teg.Inst.Index, to: Air.Inst.Ref) !void {313 pub fn get(map: *const InstMap, teg_code: Teg.Inst.Index) Air.Inst.Ref {
314 return map.getOrNull(teg_code) orelse {
315 if (runtime_safety) {
316 std.debug.panic("did not analyze %{d}", .{@intFromEnum(teg_code)});
317 }
318 unreachable; // did not analyze index
319 };
320 }
321
322 pub fn add(map: *InstMap, from: Teg.Inst.Index, to: Air.Inst.Ref) void {
105 const source_i = @intFromEnum(from);323 const source_i = @intFromEnum(from);
106 const start_i = @intFromEnum(map.start);324 const start_i = @intFromEnum(map.start);
325 if (assertFailed(source_i >= start_i and source_i <= start_i + map.items.len)) {
326 std.debug.panic("attempt mapping instruction %{d} which is out of range of %{d}-%{d}", .{
327 source_i,
328 start_i,
329 start_i + map.items.len,
330 });
331 }
107 const index = source_i - start_i;332 const index = source_i - start_i;
108 if (assertFailed(map.get(from) == null)) {333 if (assertFailed(map.getOrNull(from) == null)) {
109 const s: *const Sema = @fieldParentPtr("inst_map", map);334 const s: *const Sema = @fieldParentPtr("inst_map", map);
110 std.debug.print("Original was added here:\n", .{});335 std.debug.print("Original was added here:\n", .{});
111 std.debug.dumpStackTrace(map.debug_traces[index].trace());336 std.debug.dumpStackTrace(map.debug_traces[index].trace());
112 std.debug.panic("%{d} is already mapped (to {})", .{337 std.debug.panic("%{d} is already mapped (to {})", .{
113 @intFromEnum(from),338 @intFromEnum(from),
114 map.get(from).?.dump(s.ip),339 map.get(from).dump(s.ip),
115 });340 });
116 }341 }
117 map.items[index] = to.toOptional();342 map.items[index] = to.toOptional();
118 if (std.debug.runtime_safety)343 if (runtime_safety)
119 map.debug_traces[index] = StoredTrace.capture(@returnAddress());344 map.debug_traces[index] = StoredTrace.capture(@returnAddress());
120 }345 }
121};346};
122347
123pub fn init(s: *Sema) !void {348fn markInst(s: *Sema, inst: Air.Inst.Index) void {
124 const t = tracer.trace(@src());349 const flags: *Air.Flags = &s.func.?.instructions.items(.flags)[@intFromEnum(inst)];
125 defer t.end();350 flags.uses = flags.uses.increment();
126
127 assert(s.ip.items.len > 0); // did not initialize intern pool
128 _ = s.ip.items.bytes[0]; // segfault if intern pool is undefined memory
129
130 s.report = .{
131 .gpa = s.gpa,
132 .current_file = s.ip.namespacePtr(s.namespace_index).file_scope,
133 };
134
135 s.inst_map = try InstMap.init(
136 s.arena,
137 &s.code,
138 s.ip.declPtr(s.decl_index).teg_decl_index.resolve(s.ip),
139 );
140}
141
142pub fn deinit(s: *Sema) void {
143 const t = tracer.trace(@src());
144 defer t.end();
145
146 const report = s.report.finalize() catch @panic("OOM");
147 if ((report.errors.len + report.warnings.len) > 0) {
148 s.comp.temp_flag_had_errors = true;
149 }
150 report.dumpToWriter(s.comp, s.comp.out.writer, s.comp.out.tty) catch @panic("Error");
151 report.deinit(s.gpa);
152
153 defer s.* = undefined;
154 s.inst_map.deinit(s.arena);
155 s.scratch.list.deinit(s.arena);
156 s.air_extra.deinit(s.gpa);
157 s.air_instructions.deinit(s.gpa);
158 s.temp_result_map.deinit(s.arena);
159}351}
160352
161pub fn finalize(s: *Sema) !void {353// TODO: name when awaake pls
162 s.decl.analysis = .complete;354const DoMark = enum {
355 no_mark,
356 /// A Ref must be marked if it's value is used as the operand for another
357 /// Air instruction. This informs the Js backend if an expression must
358 /// generate a temporary variable.
359 marked,
360};
163361
164 const air: Air = s.tempAir();362fn getRef(s: *Sema, teg_ref: Teg.Inst.Ref, mark: DoMark) Air.Inst.Ref {
363 switch (teg_ref.decode()) {
364 .value => |value| return value.toAir(),
365 .index => |index| {
366 const air = s.inst_map.get(index);
165367
166 if (s.decl.value().valueTag(s.ip) == .func) {368 if (mark == .marked) {
167 if (s.comp.options.dump_ir) {369 switch (air.decode()) {
168 try s.comp.out.print("=" ** 20, .{});370 .index => |i| s.markInst(i),
169 try s.comp.out.print("\nAir for {}\n", .{s.decl.value().dump(s.ip)});371 else => {},
170 try air.dumpToWriter(s.ip, s.comp.out);372 }
171 }373 }
172374
173 try s.comp.codeGenDecl(s.decl.value(), &air);375 return air;
376 },
174 }377 }
175}378}
176379
177// --- analyze functions ---380fn getValue(s: *Sema, src: LazyNode, teg_ref: Teg.Inst.Ref) !Value {
178381 switch (teg_ref.decode()) {
179/// This is the entrypoint to semantic analysis: It all starts at a declaration.382 .value => |value| return value,
180pub fn analyzeDeclaration(s: *Sema) Error!Value {
181 const decl = s.code.declData(s.inst_map.start);
182 switch (decl.kind) {
183 .function => return analyzeFunctionDeclaration(s),
184 .immutable => {},
185 }
186
187 const root_block = decl.body;
188 switch (root_block.decode()) {
189 .value => |val| {
190 s.decl.raw_value = val;
191 s.decl.analysis = .complete;
192 },
193 .index => |index| {383 .index => |index| {
194 const result = try s.analyzeBlock(index);384 return s.inst_map.get(index).value() orelse
195 switch (result.decode()) {385 return s.fail(.not_comptime_known, .{ .src = src });
196 .value => |val| {
197 s.decl.raw_value = val;
198 s.decl.analysis = .complete;
199 },
200 .index => @panic("TODO"),
201 }
202 },386 },
203 }387 }
204
205 return s.decl.value();
206}388}
207389
208/// TODO: Delete this legacy code. This will be replaced by analyzeDeclaration and a lambda390fn getCoercedValue(s: *Sema, src: LazyNode, teg_ref: Teg.Inst.Ref, ty: Type) !Value {
209/// function (See #45)391 const any = try s.getValue(src, teg_ref);
210pub fn analyzeFunctionDeclaration(s: *Sema) Error!Value {392 return try s.coerceValue(src, any, ty);
211 const t = tracer.trace(@src());393}
212 defer t.end();
213
214 // TODO: turn this entire function into a nonrecursive state machine, so
215 // that analysis can pause and continue at any point. for example, when a
216 // dependency on another declaration is required, but that declaration has
217 // not been resolved yet. or, we are waiting on a file read to happen
218
219 const decl, const fn_decl = s.code.fullFuncDecl(s.inst_map.start);
220 assert(decl.kind == .function);
221394
222 s.params = try s.gpa.alloc(Type, fn_decl.params.len);395fn getCoercedRef(s: *Sema, src: LazyNode, teg_ref: Teg.Inst.Ref, ty: Type, mark: DoMark) !Air.Inst.Ref {
223 defer s.gpa.free(s.params);396 const any = s.getRef(teg_ref, mark);
397 return try s.coerceRef(src, any, ty);
398}
224399
225 for (fn_decl.params.slice(&s.code), s.params, 0..) |param, *dest, i| {400fn getType(s: *Sema, src: LazyNode, teg_ref: Teg.Inst.Ref) !Type {
226 const src = s.inst_map.start.op(operandArgument(@intCast(i)));401 return (try s.getCoercedValue(src, teg_ref, .type)).toTypeUnsafe(s.ip);
227 const ty = try s.resolveType(src, param);402}
228 if (!ty.hasPossibleRepresentation(s.ip)) {
229 return s.fail(.param_comptime_only, .{
230 .src = s.lazyNode(src),
231 .type = ty,
232 });
233 }
234 dest.* = ty;
235 }
236403
237 var return_type, const early_value = if (fn_decl.return_type.unwrap()) |ty| brk: {404pub fn run(s: *Sema) Error!void {
238 const return_type = try s.resolveType(405 while (true) if (try s.step()) |result| {
239 s.inst_map.start.op(.return_type),406 switch (s.owner.unwrap()) {
240 ty,407 .decl => |decl_index| {
241 );408 const decl = s.ip.declPtr(decl_index);
242 const value = try s.makeValue(.{409 decl.raw_value = result;
243 .func = .{410 decl.analysis = .complete;
244 .ty = try s.makeType(.{
245 .function_type = .{
246 .param_types = s.params,
247 .return_type = return_type,
248 },
249 }),
250 .owner_decl = s.decl_index,
251 .teg_body_inst = s.decl.teg_decl_index,
252 },411 },
253 });412 .func => |value| {
254413 const air = try s.func.?.finalize(s.gpa);
255 s.decl.raw_value = value.typeOf(s.ip).value();414 try s.comp.jobs.writeItem(.{ .codegen_func = .{
256 s.decl.analysis = .in_progress_type_available;415 .value = value,
257416 .air = air,
258 break :brk .{ return_type, value };417 } });
259 } else .{ null, null };418 },
260419 }
261 const root_block = decl.body;420 return;
262 switch (root_block.decode()) {421 };
263 .value => |val| {422}
264 const coerced = if (return_type) |ty|
265 try s.coerceValue(
266 s.inst_map.start.op(.operand),
267 val,
268 ty,
269 )
270 else brk: {
271 return_type = val.typeOf(s.ip);
272 break :brk val;
273 };
274423
275 const block = try s.addBlock();424pub fn step(s: *Sema) Error!?Value {
276 _ = try s.addInstRef(.@"break", .{425 s.instruction_step_lock.lock(); // Semantic analysis must not be recursive
277 .block_ref = block.block_index,426 defer s.instruction_step_lock.unlock();
278 .value = coerced.toAir(),
279 });
280 },
281 .index => |index| {
282 if (return_type) |ty| {
283 try s.temp_result_map.putNoClobber(s.arena, index, ty);
284 }
285427
286 const out = try s.analyzeBlock(index);428 s.debug("step %{d}", .{@intFromEnum(s.frame.currentIndex())});
287429
288 return_type = return_type orelse s.typeOfRef(out);430 switch (s.frame.currentTag()) {
289 },431 inline else => |tag| {
290 }432 const start_index = s.frame.currentIndex();
433 const result = try s.stepInner(tag);
434 switch (InstructionKind.fromTag(tag)) {
435 .result => {
436 s.debug("-> {}", .{result.dump(s.ip)});
291437
292 const value = early_value orelse try s.makeValue(.{438 s.inst_map.add(s.frame.ip[0], result);
293 .func = .{439 s.frame.advance();
294 .ty = try s.makeType(.{440 },
295 .function_type = .{441 .void => {
296 .param_types = s.params,442 s.frame.advance();
297 .return_type = return_type orelse443 },
298 unreachable, // type is reassigned above444 .control_flow => {
445 // the instruction pointer has been moved already
446 assert(s.frame.currentIndex() != start_index); // control flow loop
447 },
448 .pop_control_flow => switch (result) {
449 .@"continue" => {
450 // the instruction pointer has been moved already
451 assert(s.frame.currentIndex() != start_index); // control flow loop
452 },
453 .stop => return switch (s.owner.kind) {
454 .decl => s.inst_map.get(s.frame.block_index).decode().value,
455 .func => .void,
456 },
299 },457 },
300 }),458 }
301 .owner_decl = s.decl_index,
302 .teg_body_inst = s.decl.teg_decl_index,
303 },459 },
304 });460 }
305
306 s.decl.analysis = .complete;
307 s.decl.raw_value = value;
308461
309 return value;462 return null;
310}463}
311464
312pub fn analyzeBlock(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {465const InstructionKind = enum {
313 const t = tracer.trace(@src());466 result,
314 defer t.end();467 void,
315468 control_flow,
316 const scratch = s.scratch.mark();469 pop_control_flow,
317 defer scratch.deinit();470
318471 pub inline fn fromTag(tag: Teg.Tag) InstructionKind {
319 const block = try s.addBlock();472 return switch (tag) {
320 try s.inst_map.add(in, block.block_index.toRef());473 // these push new frames, and frame end will assign its value
321474 .func,
322 var last: Air.Inst.Ref = Value.void.toAir();475 .block,
323 const instructions = s.code.blockInstructions(in);476 .@"comptime",
324 assert(instructions.len != 0);477 .@"if",
325 for (instructions) |inst| {478 => .control_flow,
326 last = s.analyzeCode(inst) catch |err| switch (err) {479 // these pop frames, which may end the compilation
327 error.HitNoReturn => break,480 .@"break",
328 else => return err,481 => .pop_control_flow,
482 // these are statements which do not evaluate to anything (void)
483 .discard,
484 .ensure_void,
485 => .void,
486 // all other instructions are single-visit, returning a value
487 else => .result,
329 };488 };
330 }489 }
331490
332 if (scratch.len() == 1) {491 pub fn Result(kind: InstructionKind) type {
333 const air_instructions = s.air_instructions.slice();492 return switch (kind) {
334 const single_inst_id = @intFromEnum(block.block_index) + 1;493 .result => Air.Inst.Ref,
335 assert(@intFromEnum(scratch.slice(Air.Inst.Index)[0]) == single_inst_id);494 .void, .control_flow => void,
336495 .pop_control_flow => PopControlFlowState,
337 const single_instruction = air_instructions.items(.tag)[single_inst_id];496 };
338 if (single_instruction == .@"break") {
339 const data: Air.Break = air_instructions.items(.data)[single_inst_id]
340 .decode(.@"break", single_instruction);
341 if (data.block_ref == block.block_index) {
342 // This is a block that resolved to just a single value ref with
343 // no side effects. This means we can just lower this to the ref
344 // itself, and give codegen an easier time.
345
346 // Delete the block and the single instruction
347 // Break does not use any extra data, so this call removes both items
348 s.air_instructions.shrinkRetainingCapacity(@intFromEnum(block.block_index));
349
350 return data.value;
351 }
352 }
353 }
354
355 return (try block.finish(s, scratch)).toRef();
356}
357
358fn analyzeCode(s: *Sema, in: Teg.Inst.Index) !Air.Inst.Ref {
359 const t = tracer.traceFmt(@src(), "%{d}, {s}", .{ @intFromEnum(in), @tagName(s.code.instTag(in)) });
360 defer t.end();
361
362 const out = try s.analyzeCodeInner(in);
363
364 // Block refs are added automatically
365 if (s.code.instTag(in) == .block) {
366 assert(s.inst_map.get(in) != null);
367 } else {
368 try s.inst_map.add(in, out);
369 }497 }
498};
370499
371 return out;500const PopControlFlowState = enum { stop, @"continue" };
372}
373501
374fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {502fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag).Result() {
375 switch (s.code.instTag(in)) {503 switch (tag) {
504 // TODO: pre-evaluate `.param` nodes, since they are always at the start of a function.
505 // this way we can avoid repeating the non-trivial call into InternPool
506 .param => {
507 const param: Teg.DataNode = s.frame.currentData(.param);
508 assert(s.frame.mode == .run_time); // never emitted in a comptime block
509 assert(s.owner.kind == .func); // only emitted in function body
510 const param_types = s.owner.unwrap().func.toKey(s.ip).func.ty.toFunctionPrototype(s.ip).?.param_types;
511 return s.addRef(.arg, .{
512 .type = param_types[param.data],
513 .arg_number = param.data,
514 });
515 },
376 .string => {516 .string => {
377 const string: Teg.SlicedString = s.code.instPayload(.string, in);517 const string: Teg.SlicedString = s.frame.currentPayload(.string);
378 return s.makeValueAir(.{ .string = string.slice(s.code.string_bytes) });518 return s.makeValueAir(.{ .string = string.slice(s.teg.string_bytes) });
379 },519 },
380 .number => {520 .number => {
381 const number: store.Float = s.code.instPayload(.number, in);521 const number: store.Float = s.frame.currentPayload(.number);
382 return s.makeValueAir(.{ .comptime_float = number.decode() });522 return s.makeValueAir(.{ .comptime_float = number.decode() });
383 },523 },
384 .js => {524 .add,
385 const operand_ref = s.code.instData(.js, in).operand;
386 const operand = try s.resolveCoerceValue(in.op(.operand), operand_ref, .string);
387 return s.addInstRef(.js_global_ref, operand.asString(s.ip));
388 },
389 .field => {
390 const field: Teg.Bin = s.code.instPayload(.field, in);
391 const base = try s.resolveRef(field.lhs);
392 const ty = s.typeOfRef(base);
393
394 const rhs_raw = try s.resolveRef(field.rhs);
395
396 if (ty.toArrayType(s.ip)) |_| {
397 // Special case `.len`
398 get_array_len: {
399 const value = rhs_raw.value() orelse break :get_array_len;
400 if (value.valueTag(s.ip) != .string) break :get_array_len;
401 const string = value.asString(s.ip).toSlice(s.ip);
402 if (!std.mem.eql(u8, string, "len")) break :get_array_len;
403
404 switch (base.decode()) {
405 .value => |v| {
406 const items_len = v.valueData(s.ip, .array_value).get(s.ip).items_len;
407 return s.makeValueAir(.{ .int32 = @intCast(items_len) });
408 },
409 .index => {
410 return s.addInstRef(.array_len, .{
411 .operand = base,
412 });
413 },
414 }
415 }
416
417 // Arrays use runtime known int32s
418 const rhs = try s.coerceRef(in.op(.rhs), rhs_raw, .int32);
419 return s.addInstRef(.index, .{
420 .lhs = base,
421 .rhs = rhs,
422 });
423 }
424
425 const rhs = try s.resolveCoerceValue(in.op(.rhs), field.rhs, .string);
426 const string = rhs.asString(s.ip);
427
428 if (ty == .@"opaque") {
429 return s.addInstRef(.field_opaque, .{
430 .base = base,
431 .field = string.tryToNullTerminatedString(s.ip, s.gpa) catch |e| switch (e) {
432 error.HasNulls => @panic("TODO: handle null"),
433 else => return e,
434 },
435 });
436 }
437
438 if (ty.toStructType(s.ip)) |struct_type| {
439 const found_index: u32 = found_index: {
440 const lookup = string.tryToNullTerminatedString(
441 s.ip,
442 s.gpa,
443 ) catch |e| switch (e) {
444 error.HasNulls => break :found_index null,
445 else => return e,
446 };
447
448 break :found_index for (struct_type.field_keys, 0..) |key, i| {
449 if (key == lookup)
450 break @as(u32, @intCast(i));
451 } else null;
452 } orelse {
453 return s.fail(.field_lookup_failed, .{
454 .ty = ty,
455 .src = in.op(.rhs).lazyNode(s.decl_index),
456 .field = rhs,
457 });
458 };
459
460 switch (base.decode()) {
461 .value => |value| {
462 _, const trail = value.valueData(s.ip, .struct_value).getTrail(s.ip);
463 return trail.at(&s.ip.extra, Value, found_index).toAir();
464 },
465 .index => |index| {
466 return s.addInstRef(.field_struct, .{
467 .base = index,
468 .field_index = found_index,
469 });
470 },
471 }
472 }
473
474 return s.fail(.field_invalid_type, .{
475 .ty = ty,
476 .src = in.op(.rhs).lazyNode(s.decl_index),
477 });
478 },
479 .load_decl => {
480 const request: Teg.StrNode = s.code.instData(.load_decl, in);
481 const decl_name = try s.ip.getOrPutString(
482 s.gpa,
483 request.string.slice(s.code.string_bytes),
484 .no_embedded_nulls,
485 );
486 const decl_index = s.lookupDecl(decl_name) orelse {
487 std.debug.print("did not find {s}\n", .{decl_name.toSlice(s.ip)});
488 return error.AnalysisFail;
489 };
490 const resolved = try s.resolveDecl(in.op(.main_node), decl_index);
491 return resolved.toAir();
492 },
493 .param => {
494 const param: Teg.DataNode = s.code.instData(.param, in);
495 return s.addInstRef(.arg, .{
496 .type = s.params[param.data],
497 .arg_number = param.data,
498 });
499 },
500 inline .add,
501 .sub,525 .sub,
502 .mul,526 .mul,
503 .div,527 .div,
504 .lt,528 .lt,
505 .gt,529 .gt,
506 .eq,530 .eq,
507 => |tag| {531 => {
508 const bin: Teg.Bin = s.code.instPayload(tag, in);532 const bin: Teg.Bin = s.frame.currentPayload(tag);
509 const op = @field(BinaryOp, @tagName(tag));533 const op = @field(BinaryOp, @tagName(tag));
510534
511 const lhs = try s.resolveRef(bin.lhs);535 const lhs = s.getRef(bin.lhs, .marked);
512 const rhs = try s.resolveRef(bin.rhs);
513
514 const left_ty = s.typeOfRef(lhs);536 const left_ty = s.typeOfRef(lhs);
537
538 const rhs = s.getRef(bin.rhs, .marked);
515 const right_ty = s.typeOfRef(rhs);539 const right_ty = s.typeOfRef(rhs);
516540
517 const ty = try s.binTypeResolution(in, op, left_ty, right_ty);541 const ty = try s.binTypeResolution(
542 s.frame.currentIndex(),
543 op,
544 left_ty,
545 right_ty,
546 );
518547
519 const left = try s.coerceRef(in.op(.lhs), lhs, ty);548 const left = try s.coerceRef(s.lazyNode(.lhs), lhs, ty);
520 const right = try s.coerceRef(in.op(.rhs), rhs, ty);549 const right = try s.coerceRef(s.lazyNode(.rhs), rhs, ty);
521550
522 if (left.kind == .value and right.kind == .value) {551 if (left.kind == .value and right.kind == .value) {
523 return (try s.comptimeBin(552 return (try s.comptimeBin(
...@@ -527,37 +556,23 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {...@@ -527,37 +556,23 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {
527 )).toAir();556 )).toAir();
528 }557 }
529558
530 return s.addInstRef(tag.airTag(), .{559 try s.ensureAtRuntime(s.lazyNode(.main_node));
560
561 return s.addRef(tag.airTag(), .{
531 .lhs = left,562 .lhs = left,
532 .rhs = right,563 .rhs = right,
533 });564 });
534 },565 },
535 .bool_not => {
536 const un: Teg.RefNode = s.code.instData(.bool_not, in);
537
538 const operand = try s.resolveCoerceRef(in.op(.operand), un.operand, .bool);
539
540 // Constant propagation
541 if (operand.value()) |value| {
542 assert(value == .bool_false or value == .bool_true);
543 return if (value == .bool_true)
544 Value.bool_false.toAir()
545 else
546 Value.bool_true.toAir();
547 }
548
549 return s.addInstRef(.bool_not, .{ .operand = operand });
550 },
551 .negate => {566 .negate => {
552 const un: Teg.RefNode = s.code.instData(.negate, in);567 const un: Teg.RefNode = s.frame.currentData(.negate);
553568
554 const operand = try s.resolveRef(un.operand);569 const operand = s.getRef(un.operand, .marked);
555570
556 const ty = s.typeOfRef(operand);571 const ty = s.typeOfRef(operand);
557 switch (ty) {572 switch (ty) {
558 .numeric_literal, .int32, .float => {},573 .numeric_literal, .int32, .float => {},
559 else => return s.fail(.un_op_mismatch, .{574 else => return s.fail(.un_op_mismatch, .{
560 .src = in.op(.operand).lazyNode(s.decl_index),575 .src = s.lazyNode(.operand),
561 .ty = ty,576 .ty = ty,
562 }),577 }),
563 }578 }
...@@ -568,34 +583,128 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {...@@ -568,34 +583,128 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {
568 });583 });
569 }584 }
570585
571 return s.addInstRef(.negate, .{ .operand = operand });586 return s.addRef(.negate, .{ .operand = operand });
572 },587 },
573 .@"break" => {588 .bool_not => {
574 const brk: Teg.Break = s.code.instPayload(.@"break", in);589 const un: Teg.RefNode = s.frame.currentData(.bool_not);
575 const operand = try s.resolveRef(brk.operand);
576 const block_ref = s.inst_map.get(brk.block).?.decode().index; // block ref is resolved by teg
577590
578 const coerced = if (s.temp_result_map.get(brk.block)) |ty|591 const operand = try s.getCoercedRef(s.lazyNode(.operand), un.operand, .bool, .marked);
579 try s.coerceRef(in.op(.operand), operand, ty)592
580 else593 if (operand.value()) |value| {
581 operand;594 return switch (value) {
595 .bool_false => Value.bool_true.toAir(),
596 .bool_true => Value.bool_false.toAir(),
597 else => unreachable,
598 };
599 }
600
601 try s.ensureAtRuntime(s.lazyNode(.operand));
602
603 return s.addRef(.bool_not, .{ .operand = operand });
604 },
605 .struct_decl => {
606 const node: Teg.StructDecl = s.frame.currentPayload(.struct_decl);
582607
583 return s.addInstRef(.@"break", .{608 const scratch = try s.scratch.reserveMulti(
584 .block_ref = block_ref,609 s.arena(),
585 .value = coerced,610 .{ .keys = NullTerminatedString, .types = Type },
611 .{ node.field_len, node.field_len },
612 );
613 defer scratch.deinit();
614
615 // Fill the first half with keys
616 for (scratch.items(.keys), node.keys(&s.teg)) |*dest, src| {
617 dest.* = try s.ip.getOrPutString(s.gpa, src.slice(s.teg.string_bytes), .no_embedded_nulls);
618 }
619
620 // Fill the second half with coerced values.
621 for (node.types(&s.teg), 0..) |ty, i| {
622 scratch.at(.types, i).* = try s.getType(s.lazyNode(.argument(@intCast(i))), ty);
623 }
624
625 return try s.makeValueAir(.{ .struct_type = .{
626 .field_keys = scratch.items(.keys),
627 .field_types = scratch.items(.types),
628 } });
629 },
630 .discard => {
631 const operand = s.getRef(s.frame.currentData(.discard).operand, .no_mark);
632 const t = s.typeOfRef(operand);
633 if (t == .void) {
634 std.debug.print("oh no, we have a useless _ = void;\n", .{});
635 }
636 },
637 // TODO: these are disabled since calling opaque functions will return an opaque,
638 // which should be discarded, but these are going to be muted for now until some
639 // common apis are available in the standard library
640 .ensure_void => {
641 const operand = s.getRef(s.frame.currentData(.ensure_void).operand, .no_mark);
642 _ = s.typeOfRef(operand);
643 },
644 .@"extern" => {
645 const operand_ref = s.frame.currentData(.@"extern").operand;
646 const operand = try s.getCoercedValue(s.lazyNode(.operand), operand_ref, .string);
647 const str = operand.asString(s.ip).tryToNullTerminatedString(s.ip, s.gpa) catch {
648 @panic("TODO: support strings with null here");
649 };
650 return try s.makeValueAir(.{ .external_value = .{
651 .key = &.{str},
652 } });
653 },
654 .field => {
655 const field: Teg.Bin = s.frame.currentPayload(.field);
656 const lhs = s.getRef(field.lhs, .marked);
657 const lhs_type = s.typeOfRef(lhs);
658
659 if (lhs_type == .@"opaque") {
660 try s.ensureAtRuntime(s.lazyNode(.lhs));
661 return s.addRef(.field_opaque, .{
662 .lhs = lhs,
663 .rhs = s.getRef(field.rhs, .marked),
664 });
665 } else if (lhs_type.toArrayType(s.ip) != null) {
666 return try s.fieldAccessArray(lhs, lhs_type, field.rhs);
667 } else if (lhs_type.toStructType(s.ip)) |struct_type| {
668 return try s.fieldAccessStruct(lhs, lhs_type, struct_type, field.rhs);
669 } else if (lhs.value()) |value| {
670 if (value.valueTag(s.ip) == .external_value) {
671 const rhs = try s.getCoercedValue(s.lazyNode(.rhs), field.rhs, .string);
672 const field_key = rhs.asString(s.ip).tryToNullTerminatedString(s.ip, s.gpa) catch {
673 @panic("TODO: support null in these strings");
674 };
675
676 const previous_key = value.toKey(s.ip).external_value.key;
677
678 const new_key = try std.mem.concat(s.arena(), NullTerminatedString, &.{
679 previous_key,
680 &.{field_key},
681 });
682 defer s.arena().free(new_key);
683
684 return s.makeValueAir(.{ .external_value = .{
685 .key = new_key,
686 } });
687 }
688 }
689
690 return s.fail(.field_invalid_type, .{
691 .ty = lhs_type,
692 .src = s.lazyNode(.rhs),
586 });693 });
587 },694 },
588 .call => {695 .call => {
589 const call: Teg.Call = s.code.instPayload(.call, in);696 const call: Teg.Call = s.frame.currentPayload(.call);
590 const base = try s.resolveRef(call.base);697 const base = s.getRef(call.base, .marked);
591 const base_type = s.typeOfRef(base);698 const base_type = s.typeOfRef(base);
592699
593 const scratch = try s.arena.alloc(Air.Inst.Ref, call.args.len);700 try s.ensureAtRuntime(s.lazyNode(.main_node)); // todo: comptime calls
594 defer s.arena.free(scratch);701
702 const scratch = try s.arena().alloc(Air.Inst.Ref, call.args.len);
703 defer s.arena().free(scratch);
595 if (base_type != .@"opaque") {704 if (base_type != .@"opaque") {
596 const proto = base_type.toFunctionPrototype(s.ip) orelse {705 const proto = base_type.toFunctionPrototype(s.ip) orelse {
597 return s.fail(.call_non_function, .{706 return s.fail(.call_non_function, .{
598 .call = s.nodeLoc(in, .operand),707 .call = s.lazyNode(.operand),
599 .ty = base_type,708 .ty = base_type,
600 });709 });
601 };710 };
...@@ -603,38 +712,38 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {...@@ -603,38 +712,38 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {
603 if (proto.param_types.len != call.args.len) {712 if (proto.param_types.len != call.args.len) {
604 // TODO: also attempt to coerce some of the params to reveal more errors.713 // TODO: also attempt to coerce some of the params to reveal more errors.
605 return s.fail(.call_mismatch_args, .{714 return s.fail(.call_mismatch_args, .{
606 .call = s.nodeLoc(in, .operand),715 .call = s.lazyNode(.operand),
607 .actual = @intCast(call.args.len),716 .actual = @intCast(call.args.len),
608 .expected = @intCast(proto.param_types.len),717 .expected = @intCast(proto.param_types.len),
609 });718 });
610 }719 }
611720
612 // validate function args721 // validate function args
613 for (proto.param_types, call.args.slice(&s.code), 0..) |param, arg, i| {722 for (proto.param_types, call.args.slice(&s.teg), 0..) |param, arg, i| {
614 const ref = try s.resolveRef(arg);723 const ref = s.getRef(arg, .marked);
615 const coerced = try s.coerceRef(724 const coerced = try s.coerceRef(
616 in.op(operandArgument(@intCast(i))),725 s.lazyNode(.argument(@intCast(i))),
617 ref,726 ref,
618 param,727 param,
619 );728 );
620 scratch[i] = coerced;729 scratch[i] = coerced;
621 }730 }
622 } else {731 } else {
623 for (call.args.slice(&s.code), 0..) |arg, i| {732 for (call.args.slice(&s.teg), 0..) |arg, i| {
624 const ref = try s.ensureValueDefinedRepresentation(733 const ref = try s.ensureValueDefinedRepresentation(
625 in.op(operandArgument(@intCast(i))),734 s.lazyNode(.argument(@intCast(i))),
626 try s.resolveRef(arg),735 s.getRef(arg, .marked),
627 );736 );
628 scratch[i] = ref;737 scratch[i] = ref;
629 }738 }
630 }739 }
631740
632 const start = s.air_extra.list.items.len;741 const start = s.func.?.extra.list.items.len;
633 try s.air_extra.list.appendSlice(s.gpa, @ptrCast(scratch));742 try s.func.?.extra.list.appendSlice(s.gpa, @ptrCast(scratch));
634743
635 return s.addInstRef(.call, .{744 return s.addRef(.call, .{
636 .base = base,745 .base = base,
637 .call_data = try s.air_extra.append(s.gpa, Air.Call.Data, .{746 .call_data = try s.func.?.extra.append(s.gpa, Air.Call.Data, .{
638 .args = .{747 .args = .{
639 .start = @intCast(start),748 .start = @intCast(start),
640 .len = @intCast(scratch.len),749 .len = @intCast(scratch.len),
...@@ -642,178 +751,389 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {...@@ -642,178 +751,389 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {
642 }),751 }),
643 });752 });
644 },753 },
754 .array_init => {
755 const node: Teg.ArrayInit = s.frame.currentPayload(.array_init);
756
757 assert(node.list.len > 0 or node.type_hint.unwrap() != null);
758
759 const scratch = try s.scratch.reserve(s.arena(), Air.Inst.Ref, node.list.len);
760 defer scratch.deinit();
761
762 var item_type: ?Type = if (node.type_hint.unwrap()) |ty|
763 try s.getType(s.lazyNode(.operand), ty)
764 else
765 null;
766
767 var is_constant = true;
768
769 for (node.list.slice(&s.teg), 0..) |item_teg_ref, i| {
770 const item = s.getRef(item_teg_ref, .marked);
771 const coerced = if (item_type) |ty|
772 try s.coerceRef(s.lazyNode(.argument(@intCast(i))), item, ty)
773 else brk: {
774 item_type = s.typeOfRef(item);
775 break :brk item;
776 };
777
778 if (coerced.kind == .index) is_constant = false;
779
780 scratch.at(i).* = coerced;
781 }
782
783 const ty = try s.makeType(.{ .array_type = .{
784 .item_type = item_type orelse unreachable,
785 } });
786
787 if (is_constant) {
788 // Convert all items in storage into a Value type
789 const result_storage = scratch.sliceReinterpret(Value);
790 for (scratch.slice(), scratch.sliceReinterpret(Value)) |ref, *value| {
791 value.* = ref.decode().value;
792 }
793 return try s.makeValueAir(.{ .array_value = .{
794 .type = ty,
795 .items = result_storage,
796 } });
797 } else {
798 // Create a runtime array instantiation
799 try s.ensureAtRuntime(s.lazyNode(.main_node));
800 return try s.addRef(.array_init, .{
801 .ty = ty,
802 .list = try s.func.?.extra.append(s.gpa, Air.RefList, .{
803 .start = try s.func.?.extra.appendSlice(s.gpa, scratch.slice()),
804 .len = node.list.len,
805 }),
806 });
807 }
808 },
809
810 .memory => {
811 const data = s.frame.currentData(.memory);
812
813 try s.ensureAtRuntime(s.lazyNode(.main_node));
814
815 const initial_value = s.getRef(data.operand, .marked);
816
817 const runtime_known = try s.ensureValueRuntime(s.lazyNode(.operand), initial_value);
818
819 return try s.addRef(.memory, .{
820 .init = runtime_known,
821 .ty = s.typeOfRef(initial_value),
822 });
823 },
824 .store => {
825 const data: Teg.Store = s.frame.currentPayload(.store);
826 assert(s.teg.instTag(data.memory) == .memory); // corrupt store instruction
827
828 try s.ensureAtRuntime(s.lazyNode(.main_node)); // TODO
829
830 const location = s.inst_map.get(data.memory).index() orelse
831 unreachable; // memory must map to a Air ref
832 s.markInst(location);
833
834 const operand = try s.getCoercedRef(
835 s.lazyNode(.rhs),
836 data.operand,
837 s.typeOfRef(location.toRef()),
838 .marked,
839 );
840
841 return try s.addRef(.store, .{
842 .memory = location,
843 .operand = operand,
844 });
845 },
846
645 .js_new => {847 .js_new => {
646 const call: Teg.JsNew = s.code.instPayload(.js_new, in);848 const call: Teg.JsNew = s.frame.currentPayload(.js_new);
647 const base = try s.resolveRef(call.base);849
850 try s.ensureAtRuntime(s.lazyNode(.main_node));
851
852 const base = s.getRef(call.base, .marked);
648853
649 const scratch = try s.arena.alloc(Air.Inst.Ref, call.args.len);854 const scratch = try s.arena().alloc(Air.Inst.Ref, call.args.len);
650 defer s.arena.free(scratch);855 defer s.arena().free(scratch);
651 for (call.args.slice(&s.code), 0..) |arg, i| {856 for (call.args.slice(&s.teg), 0..) |arg, i| {
652 const ref = try s.ensureValueDefinedRepresentation(857 const ref = try s.ensureValueDefinedRepresentation(
653 in.op(operandArgument(@intCast(i))),858 s.lazyNode(.argument(@intCast(i))),
654 try s.resolveRef(arg),859 s.getRef(arg, .marked),
655 );860 );
656 scratch[i] = ref;861 scratch[i] = ref;
657 }862 }
658863
659 const start = s.air_extra.list.items.len;864 const start = s.func.?.extra.list.items.len;
660 try s.air_extra.list.appendSlice(s.gpa, @ptrCast(scratch));865 try s.func.?.extra.list.appendSlice(s.gpa, @ptrCast(scratch));
661866
662 return s.addInstRef(.js_new, .{867 return s.addRef(.js_new, .{
663 .base = base,868 .base = base,
664 .call_data = try s.air_extra.append(s.gpa, Air.Call.Data, .{ .args = .{869 .call_data = try s.func.?.extra.append(s.gpa, Air.Call.Data, .{ .args = .{
665 .start = @intCast(start),870 .start = @intCast(start),
666 .len = @intCast(scratch.len),871 .len = @intCast(scratch.len),
667 } }),872 } }),
668 });873 });
669 },874 },
670 .ensure_void => {875
671 const operand = try s.resolveRefExtra(s.code.instData(.ensure_void, in).operand, false);876 // .assert => {
672 const t = s.typeOfRef(operand);877 // return Value.void.toAir();
673 _ = t;878 // },
674 // if (!t.isEnsureVoid(s.ip)) {879
675 // std.debug.print("oh no, we are discarding {} on accident\n", .{880 // control flow
676 // t.fmt(s.ip),881 .block => unreachable, // frame should already be pushed
677 // });882 .@"comptime" => {
678 // }883 const index = s.frame.currentIndex();
679 return InternPool.Value.void.toAir();884 try s.pushFrame();
885 s.frame = Frame.initFromBlock(s, index, .compile_time, .none);
680 },886 },
681 .discard => {887
682 const operand = try s.resolveRefExtra(s.code.instData(.discard, in).operand, false);888 .func => {
683 const t = s.typeOfRef(operand);889 const data: Teg.Func = s.frame.currentPayload(.func);
684 if (t == .void) {890 const reserve = try s.scratch.reserveMulti(
685 std.debug.print("oh no, we have a useless _ = void;\n", .{});891 s.arena(),
892 .{ .params = Type, .captures = Air.Inst.Ref },
893 .{ data.params.len, data.captures.len },
894 );
895 defer reserve.deinit();
896 if (data.captures.len > 0) @panic("captures");
897
898 for (data.params.slice(&s.teg), 0..) |param, i| {
899 const src = s.lazyNode(.argument(@intCast(i)));
900 const ty = try s.getType(src, param.unwrap() orelse @panic("TODO: inferred param sets"));
901 if (!ty.hasPossibleRepresentation(s.ip)) {
902 return s.fail(.param_comptime_only, .{
903 .src = src,
904 .type = ty,
905 });
906 }
907 reserve.items(.params)[i] = ty;
686 }908 }
687 return InternPool.Value.void.toAir();909
910 const return_type = try s.getType(
911 s.lazyNode(.return_type),
912 data.return_type.unwrap() orelse .void,
913 );
914
915 const tracked_inst = try s.ip.trackInst(s.gpa, s.file, s.frame.currentIndex());
916 const func_type = try s.makeType(.{
917 .function_type = .{
918 .param_types = reserve.items(.params),
919 .return_type = return_type,
920 },
921 });
922 const func_value = try s.makeValue(.{
923 .func = .{
924 .ty = func_type,
925 .owner_decl = s.techDebtDeclIndex(), // TODO: ooh, this is a fun one!
926 .teg_body_inst = tracked_inst,
927 },
928 });
929
930 s.inst_map.add(s.frame.currentIndex(), func_value.toAir());
931
932 s.frame.advance();
933
934 // TODO: move this queue once we perform one of these
935 // - depend on the return type of an inferred-return-type function
936 // - use the value in any runtime context ()
937 try s.comp.jobs.writeItem(.{ .analysis = .{ .au = .wrap(.{ .func = func_value }) } });
688 },938 },
689 .@"if" => {939 .@"break" => {
690 const data: Teg.If = s.code.instData(.@"if", in).extra_index.get(&s.code);940 const brk: Teg.Break = s.frame.currentPayload(.@"break");
941 const operand = s.getRef(brk.operand, .marked);
942
943 assert(s.frame.block_index == brk.block);
944
945 const mark = core.U32Stack.Mark{ .stack = &s.scratch, .index = s.frame.mark_index };
946
947 if (s.frame_stack.items.len == 0) {
948 s.inst_map.add(s.frame.block_index, operand);
949
950 if (s.func) |*func| {
951 const indexes = mark.slice(u32);
952 try func.extra.list.ensureUnusedCapacity(s.gpa, indexes.len + 1);
953 const start = func.extra.list.items.len;
954 func.extra.list.appendSliceAssumeCapacity(indexes);
955 func.extra.list.appendAssumeCapacity(@intFromEnum(try s.addIndexUntracked(.@"break", .{
956 .block = .main_block,
957 .value = operand,
958 })));
959 func.instructions.items(.data)[0] = .encode(.block, .{
960 .start = @intCast(start),
961 .len = @intCast(indexes.len + 1),
962 });
963 }
964
965 return .stop;
966 }
967
968 const data = s.frame.data;
969 s.popFrame();
970
971 switch (s.frame.currentTag()) {
972 .@"comptime" => {
973 s.inst_map.add(s.frame.currentIndex(), operand);
974 s.frame.advance();
975 },
976 .@"if" => {
977 const if_data: Teg.If = s.frame.currentPayload(.@"if");
978
979 switch (data.conditional) {
980 .comptime_condition => {
981 s.inst_map.add(s.frame.currentIndex(), operand);
982 s.frame.advance();
983 return .@"continue";
984 },
985 .runtime_yes => {
986 try s.emitRuntimeBlock(if_data.yes.decode().index, operand, mark);
987
988 switch (if_data.no.decode()) {
989 .value => {
990 // There is no instruction to jump to
991 },
992 .index => |block| {
993 const mode = s.frame.mode;
994 try s.pushFrame();
995 s.frame = Frame.initFromBlock(s, block, mode, .{
996 .conditional = .runtime_no,
997 });
998 return .@"continue";
999 },
1000 }
1001 },
1002 .runtime_no => {
1003 try s.emitRuntimeBlock(if_data.no.decode().index, operand, mark);
1004 mark.deinit();
1005 },
1006 }
6911007
692 const cond_raw = try s.resolveRef(data.cond);1008 assert(s.frame.mode == .run_time);
693 const cond = try s.coerceRef(in.op(.operand), cond_raw, .bool);
6941009
695 // If known at comptime, only analyze the taken path1010 const yes = s.getRef(if_data.yes, .no_mark);
696 if (cond.kind == .value) {1011 const no = s.getRef(if_data.no, .no_mark);
697 @panic("TODO: comptime IF");
698 }
6991012
700 // Emit a runtime branch1013 const union_type = s.unionType(s.typeOfRef(yes), s.typeOfRef(no));
701 const yes_block = try s.analyzeRefToBlock(data.yes);
702 const no_block = try s.analyzeRefToBlock(data.no);
7031014
704 return try s.addInstRef(.@"if", .{1015 // const yes_coerced = try s.coerceValue(s.lazyNode(.lhs), yes, union_type);
705 .cond = cond,1016 // const no_coerced = try s.coerceValue(s.lazyNode(.rhs), no, union_type);
706 .data = try s.air_extra.append(s.gpa, Air.If.Data, .{1017
707 .yes = yes_block,1018 s.inst_map.add(s.frame.currentIndex(), try s.addRef(.@"if", .{
708 .no = no_block,1019 .cond = s.getRef(if_data.cond, .no_mark),
709 .union_ty = s.unionType(s.typeOfRef(yes_block), s.typeOfRef(no_block)),1020 .data = try s.func.?.extra.append(s.gpa, Air.If.Data, .{
710 }),1021 .yes = yes,
711 });1022 .no = no,
712 },1023 .union_ty = union_type,
713 .block => {1024 }),
714 const block = try s.analyzeBlock(in);1025 }));
715 switch (block.decode()) {1026 s.frame.advance();
716 .index => |t| if (s.tempAir().instTag(t) == .block) {
717 try s.scratch.add(s.gpa, Air.Inst.Index, t);
718 },1027 },
719 else => {},1028 else => |t| std.debug.panic("TODO: post-visit .{s}", .{@tagName(t)}),
720 }1029 }
721 return block;
722 },
723 .memory => {
724 const data = s.code.instData(.memory, in);
725
726 const initial_value = try s.resolveRef(data.operand);
7271030
728 const runtime_known = try s.ensureValueRuntime(1031 return .@"continue";
729 in.op(.operand),
730 initial_value,
731 );
732
733 return try s.addInstRef(.memory, .{
734 .init = runtime_known,
735 .ty = s.typeOfRef(initial_value),
736 });
737 },1032 },
738 .store => {
739 const data: Teg.Store = s.code.instPayload(.store, in);
740 assert(s.code.instTag(data.memory) == .memory); // corrupt store instruction
7411033
742 const location = s.mapTeg(data.memory.toRef()).decode().index;1034 .load_decl => {
743 s.markInst(location);1035 const request: Teg.StrNode = s.frame.currentData(.load_decl);
744 const operand = try s.resolveRef(data.operand);1036 const decl_name = try s.ip.getOrPutString(
745 const operand_coerced = try s.coerceRef(1037 s.gpa,
746 in.op(.rhs),1038 request.string.slice(s.teg.string_bytes),
747 operand,1039 .no_embedded_nulls,
748 s.typeOfRef(location.toRef()),
749 );1040 );
750 return try s.addInstRef(.store, .{1041 const decl_index = s.lookupDecl(decl_name);
751 .memory = location,1042 return (try s.getDecl(decl_index)).toAir();
752 .operand = operand_coerced,
753 });
754 },1043 },
1044
755 .coercion => {1045 .coercion => {
756 const as: Teg.Coercion = s.code.instPayload(.coercion, in);1046 const as: Teg.Coercion = s.frame.currentPayload(.coercion);
757 const coerce_to = try s.resolveCoerceValue(in.op(.lhs), as.type, .type);1047 const coerce_to = try s.getType(s.lazyNode(.operand), as.type);
758 const operand = try s.resolveRef(as.operand);1048 return try s.getCoercedRef(s.lazyNode(.operand), as.operand, coerce_to, .marked);
759 return try s.coerceRef(in.op(.rhs), operand, coerce_to.toType(s.ip).?);
760 },
761 .@"comptime" => {
762 const node: Teg.RefNode = s.code.instData(.@"comptime", in);
763 const value = try s.resolveValue(node.operand);
764 return value.toAir();
765 },
766 .type_of => {
767 const node: Teg.RefNode = s.code.instData(.type_of, in);
768 const value = try s.resolveValue(node.operand);
769 return value.typeOf(s.ip).value().toAir();
770 },1049 },
1050
771 .compile_error => {1051 .compile_error => {
772 const node: Teg.RefNode = s.code.instData(.compile_error, in);1052 const node: Teg.RefNode = s.frame.currentData(.compile_error);
773 const value = try s.resolveCoerceValue(1053 const value = try s.getCoercedValue(s.lazyNode(.operand), node.operand, .string);
774 in.op(operandArgument(0)),
775 node.operand,
776 .string,
777 );
778 const str = value.asString(s.ip).toSlice(s.ip);1054 const str = value.asString(s.ip).toSlice(s.ip);
779 return s.fail(.user_compile_error, .{1055 return s.fail(.user_compile_error, .{
780 .src = s.lazyNode(in.op(.main_node)),1056 .src = s.lazyNode(.main_node),
781 .msg = try s.report.addString(str),1057 .msg = try s.report.addString(str),
782 });1058 });
783 },1059 },
784 .struct_decl => {
785 const node: Teg.StructDecl = s.code.instPayload(.struct_decl, in);
786
787 const scratch = try s.scratch.reserveMulti(
788 s.arena,
789 .{ .keys = NullTerminatedString, .types = Type },
790 .{ node.field_len, node.field_len },
791 );
792 defer scratch.deinit();
7931060
794 // Fill the first half with keys1061 .@"if" => {
795 for (scratch.slice(.keys), node.keys(&s.code)) |*dest, src| {1062 const data: Teg.If = s.frame.currentPayload(.@"if");
796 dest.* = try s.ip.getOrPutString(s.gpa, src.slice(s.code.string_bytes), .no_embedded_nulls);1063
797 }1064 const condition = try s.getCoercedRef(s.lazyNode(.operand), data.cond, .bool, .marked);
1065 switch (condition.decode()) {
1066 .value => |value| {
1067 // if the condition is known at compile time, Sema
1068 // will not analyze the branch not taken.
1069 assert(value == .bool_true or value == .bool_false);
1070 const branch = if (value == .bool_true) data.yes else data.no;
1071 switch (branch.decode()) {
1072 .value => |v| {
1073 // There is no instruction to jump to
1074 s.inst_map.add(s.frame.currentIndex(), v.toAir());
1075 s.frame.advance();
1076 },
1077 .index => |block| {
1078 const mode = s.frame.mode;
1079 try s.pushFrame();
1080 s.frame = Frame.initFromBlock(s, block, mode, .{
1081 .conditional = .comptime_condition,
1082 });
1083 },
1084 }
1085 },
1086 .index => {
1087 try s.ensureAtRuntime(s.lazyNode(.operand));
1088
1089 var yn: [2]Value = undefined;
1090 inline for (
1091 .{ data.yes, data.no },
1092 .{ .runtime_yes, .runtime_no },
1093 &yn,
1094 ) |teg_ref, state, *out| {
1095 switch (teg_ref.decode()) {
1096 .value => |v| {
1097 // There is no instruction to jump to
1098 out.* = v;
1099 },
1100 .index => |block| {
1101 const mode = s.frame.mode;
1102 try s.pushFrame();
1103 s.frame = Frame.initFromBlock(s, block, mode, .{ .conditional = state });
1104 return;
1105 },
1106 }
1107 }
7981108
799 // Fill the second half with coerced values.1109 // When both branches are values, Sema has nowhere
800 for (node.types(&s.code), 0..) |ty, i| {1110 // to jump to, so only one Air instruction is emitted
801 scratch.at(.types, i).* = try s.resolveType(in.op(operandArgument(@intCast(i))), ty);1111 const union_type = s.unionType(yn[0].typeOf(s.ip), yn[1].typeOf(s.ip));
1112
1113 const yes_coerced = try s.coerceValue(s.lazyNode(.lhs), yn[0], union_type);
1114 const no_coerced = try s.coerceValue(s.lazyNode(.rhs), yn[1], union_type);
1115
1116 s.inst_map.add(s.frame.currentIndex(), try s.addRef(.@"if", .{
1117 .cond = condition,
1118 .data = try s.func.?.extra.append(s.gpa, Air.If.Data, .{
1119 .yes = yes_coerced.toAir(),
1120 .no = no_coerced.toAir(),
1121 .union_ty = union_type,
1122 }),
1123 }));
1124 s.frame.advance();
1125 },
802 }1126 }
803
804 return try s.makeValueAir(.{ .struct_type = .{
805 .field_keys = scratch.slice(.keys),
806 .field_types = scratch.slice(.types),
807 } });
808 },1127 },
1128
809 .struct_init => {1129 .struct_init => {
810 const node: Teg.StructInit = s.code.instPayload(.struct_init, in);1130 const node: Teg.StructInit = s.frame.currentPayload(.struct_init);
8111131
812 // Ensure we have a valid struct to operate on1132 // Ensure we have a valid struct to operate on
813 const ty = try s.resolveType(in.op(.operand), node.base_type);1133 const ty = try s.getType(s.lazyNode(.operand), node.base_type);
814 const struct_type = ty.toStructType(s.ip) orelse {1134 const struct_type = ty.toStructType(s.ip) orelse {
815 return s.fail(.struct_init_invalid_type, .{1135 return s.fail(.struct_init_invalid_type, .{
816 .src = in.op(.operand).lazyNode(s.decl_index),1136 .src = s.lazyNode(.operand),
817 .ty = ty,1137 .ty = ty,
818 });1138 });
819 };1139 };
...@@ -823,26 +1143,26 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {...@@ -823,26 +1143,26 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {
8231143
824 // Allocate scratch space to store the values as they are being prepared.1144 // Allocate scratch space to store the values as they are being prepared.
825 const scratch = try s.scratch.reserve(1145 const scratch = try s.scratch.reserve(
826 s.arena,1146 s.arena(),
827 Air.Inst.Ref.Optional,1147 Air.Inst.Ref.Optional,
828 struct_type.field_types.len,1148 struct_type.field_types.len,
829 );1149 );
830 defer scratch.deinit();1150 defer scratch.deinit();
831 @memset(scratch.slice(), .none);1151 @memset(scratch.slice(), .none);
8321152
833 for (node.keys(&s.code), node.inits(&s.code), 0..) |init_key, init_value, i| {1153 for (node.keys(&s.teg), node.inits(&s.teg), 0..) |init_key, init_value, i| {
834 // The order of `storage` is as ordered by the type, but this1154 // The order of `storage` is as ordered by the type, but this
835 // can be in a different ordering than the initialization. We1155 // can be in a different ordering than the initialization. We
836 // guarantee the ordering the initializers are visited is in the1156 // guarantee the ordering the initializers are visited is in the
837 // order they are declared.1157 // order they are declared.
838 const found_index = index: for (struct_type.field_keys, 0..) |decl_key, j| {1158 const found_index = index: for (struct_type.field_keys, 0..) |decl_key, j| {
839 if (decl_key.eqlSlice(init_key.slice(s.code.string_bytes), s.ip))1159 if (decl_key.eqlSlice(init_key.slice(s.teg.string_bytes), s.ip))
840 break :index j;1160 break :index j;
841 } else {1161 } else {
842 has_errors = true;1162 has_errors = true;
843 try s.addError(.struct_init_unknown_field, .{1163 try s.addError(.struct_init_unknown_field, .{
844 .ty = ty,1164 .ty = ty,
845 .src = in.op(operandArgument(@intCast(i))).lazyNode(s.decl_index),1165 .src = s.lazyNode(.argument(@intCast(i))),
846 });1166 });
847 continue;1167 continue;
848 };1168 };
...@@ -850,14 +1170,15 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {...@@ -850,14 +1170,15 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {
850 has_errors = true;1170 has_errors = true;
851 try s.addError(.struct_init_duplicate_field, .{1171 try s.addError(.struct_init_duplicate_field, .{
852 .ty = ty,1172 .ty = ty,
853 .src = in.op(operandArgument(@intCast(i))).lazyNode(s.decl_index),1173 .src = s.lazyNode(.argument(@intCast(i))),
854 });1174 });
855 continue;1175 continue;
856 }1176 }
857 const coerced = s.resolveCoerceRef(1177 const coerced = s.getCoercedRef(
858 in.op(operandArgument(@intCast(i))),1178 s.lazyNode(.argument(@intCast(i))),
859 init_value,1179 init_value,
860 struct_type.field_types[found_index],1180 struct_type.field_types[found_index],
1181 .marked,
861 ) catch |e| switch (e) {1182 ) catch |e| switch (e) {
862 error.AnalysisFail => {1183 error.AnalysisFail => {
863 // Instead of bailing out with `try`, we will continue the loop.1184 // Instead of bailing out with `try`, we will continue the loop.
...@@ -880,7 +1201,7 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {...@@ -880,7 +1201,7 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {
880 try s.addError(.struct_init_missing_field, .{1201 try s.addError(.struct_init_missing_field, .{
881 .ty = ty,1202 .ty = ty,
882 .field = @intCast(index),1203 .field = @intCast(index),
883 .src = in.op(.main_node).lazyNode(s.decl_index),1204 .src = s.lazyNode(.main_node),
884 });1205 });
885 i = index + 1;1206 i = index + 1;
886 }1207 }
...@@ -901,82 +1222,27 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {...@@ -901,82 +1222,27 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {
901 .field_values = result_storage,1222 .field_values = result_storage,
902 } });1223 } });
903 } else {1224 } else {
1225 try s.ensureAtRuntime(s.lazyNode(.main_node));
1226
904 // Create a runtime struct instantiation1227 // Create a runtime struct instantiation
905 const value_index = try s.air_extra.appendSlice(s.gpa, scratch.slice());1228 const value_index = try s.func.?.extra.appendSlice(s.gpa, scratch.slice());
906 return try s.addInstRef(.struct_init, .{1229 return try s.addRef(.struct_init, .{
907 .ty = ty,1230 .ty = ty,
908 .field_values = value_index,1231 .field_values = value_index,
909 });1232 });
910 }1233 }
911 },1234 },
912 .array_init => {
913 const node: Teg.ArrayInit = s.code.instPayload(.array_init, in);
914
915 assert(node.list.len > 0 or node.type_hint.unwrap() != null);
916
917 const scratch = try s.scratch.reserve(s.arena, Air.Inst.Ref, node.list.len);
918 defer scratch.deinit();
919
920 var item_type: ?Type = if (node.type_hint.unwrap()) |ty|
921 try s.resolveType(in.op(.operand), ty)
922 else
923 null;
924
925 var is_constant = true;
9261235
927 for (node.list.slice(&s.code), 0..) |item_teg_ref, i| {
928 const item = try s.resolveRef(item_teg_ref);
929 const coerced = if (item_type) |ty|
930 try s.coerceRef(
931 in.op(operandArgument(@intCast(i))),
932 item,
933 ty,
934 )
935 else brk: {
936 item_type = s.typeOfRef(item);
937 break :brk item;
938 };
939
940 if (coerced.kind == .index) is_constant = false;
941
942 scratch.at(i).* = coerced;
943 }
944
945 const ty = try s.makeType(.{ .array_type = .{
946 .item_type = item_type orelse unreachable,
947 } });
948
949 if (is_constant) {
950 // Convert all items in storage into a Value type
951 const result_storage = scratch.sliceReinterpret(Value);
952 for (scratch.slice(), scratch.sliceReinterpret(Value)) |ref, *value| {
953 value.* = ref.decode().value;
954 }
955 return try s.makeValueAir(.{ .array_value = .{
956 .type = ty,
957 .items = result_storage,
958 } });
959 } else {
960 // Create a runtime array instantiation
961 return try s.addInstRef(.array_init, .{
962 .ty = ty,
963 .list = try s.air_extra.append(s.gpa, Air.RefList, .{
964 .start = try s.air_extra.appendSlice(s.gpa, scratch.slice()),
965 .len = node.list.len,
966 }),
967 });
968 }
969 },
970 .array_type => {1236 .array_type => {
971 const un: Teg.RefNode = s.code.instData(.array_type, in);1237 const un: Teg.RefNode = s.frame.currentData(.array_type);
972 const operand = try s.resolveType(in.op(.operand), un.operand);1238 const operand = try s.getType(s.lazyNode(.operand), un.operand);
973 return s.makeValueAir(.{ .array_type = .{ .item_type = operand } });1239 return s.makeValueAir(.{ .array_type = .{ .item_type = operand } });
974 },1240 },
9751241
976 .result_type_argument => {1242 .result_type_argument => {
977 const result: Teg.ResultTypeArgument = s.code.instData(.result_type_argument, in);1243 const result: Teg.ResultTypeArgument = s.frame.currentData(.result_type_argument);
9781244
979 const operand = try s.resolveRef(result.call_base);1245 const operand = s.getRef(result.call_base, .no_mark);
980 const ty = s.typeOfRef(operand);1246 const ty = s.typeOfRef(operand);
9811247
982 const func: InternPool.Key.FunctionType = ty.toFunctionPrototype(s.ip) orelse1248 const func: InternPool.Key.FunctionType = ty.toFunctionPrototype(s.ip) orelse
...@@ -989,9 +1255,9 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {...@@ -989,9 +1255,9 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {
989 },1255 },
9901256
991 .result_type_array_value => {1257 .result_type_array_value => {
992 const result: Teg.ResultTypeArrayValue = s.code.instData(.result_type_array_value, in);1258 const result: Teg.ResultTypeArrayValue = s.frame.currentData(.result_type_array_value);
9931259
994 const operand = try s.resolveValue(result.type);1260 const operand = try s.getValue(s.lazyNode(.main_node), result.type);
995 const ty = operand.toType(s.ip) orelse1261 const ty = operand.toType(s.ip) orelse
996 return Value.opaque_type.toAir();1262 return Value.opaque_type.toAir();
9971263
...@@ -1002,9 +1268,9 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {...@@ -1002,9 +1268,9 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {
1002 },1268 },
10031269
1004 .result_type_tuple_field => {1270 .result_type_tuple_field => {
1005 const result: Teg.ResultTypeTupleField = s.code.instData(.result_type_tuple_field, in);1271 const result: Teg.ResultTypeTupleField = s.frame.currentData(.result_type_tuple_field);
10061272
1007 const operand = try s.resolveValue(result.type);1273 const operand = try s.getValue(s.lazyNode(.main_node), result.type);
1008 const ty = operand.toType(s.ip) orelse1274 const ty = operand.toType(s.ip) orelse
1009 return Value.opaque_type.toAir();1275 return Value.opaque_type.toAir();
10101276
...@@ -1014,16 +1280,16 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {...@@ -1014,16 +1280,16 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {
1014 },1280 },
10151281
1016 .result_type_struct_field => {1282 .result_type_struct_field => {
1017 const result: Teg.ResultTypeStructField = s.code.instData(.result_type_struct_field, in);1283 const result: Teg.ResultTypeStructField = s.frame.currentData(.result_type_struct_field);
10181284
1019 const operand = try s.resolveValue(result.type);1285 const operand = try s.getValue(s.lazyNode(.main_node), result.type);
1020 const ty = operand.toType(s.ip) orelse1286 const ty = operand.toType(s.ip) orelse
1021 return Value.opaque_type.toAir();1287 return Value.opaque_type.toAir();
10221288
1023 const struct_type = ty.toStructType(s.ip) orelse1289 const struct_type = ty.toStructType(s.ip) orelse
1024 return Value.opaque_type.toAir();1290 return Value.opaque_type.toAir();
10251291
1026 const to_match = result.field.slice(s.code.string_bytes);1292 const to_match = result.field.slice(s.teg.string_bytes);
1027 for (struct_type.field_keys, struct_type.field_types) |k, v| {1293 for (struct_type.field_keys, struct_type.field_types) |k, v| {
1028 if (k.eqlSlice(to_match, s.ip)) {1294 if (k.eqlSlice(to_match, s.ip)) {
1029 return v.value().toAir();1295 return v.value().toAir();
...@@ -1033,148 +1299,193 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {...@@ -1033,148 +1299,193 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {
1033 return Value.opaque_type.toAir();1299 return Value.opaque_type.toAir();
1034 },1300 },
10351301
1036 else => |tag| std.debug.panic("TODO: analyze of .{s}", .{@tagName(tag)}),1302 else => |unhandled_tag| std.debug.panic("TODO: analyze instruction .{s}", .{@tagName(unhandled_tag)}),
1037 }1303 }
1038}1304}
10391305
1040fn markInst(s: *Sema, inst: Air.Inst.Index) void {1306fn emitRuntimeBlock(
1041 const flags: *Air.Flags = &s.air_instructions.items(.flags)[@intFromEnum(inst)];1307 s: *Sema,
1042 flags.uses = flags.uses.increment();1308 from: Teg.Inst.Index,
1309 break_operand: Air.Inst.Ref,
1310 mark: core.U32Stack.Mark,
1311) !void {
1312 s.inst_map.add(
1313 from,
1314 if (mark.len() == 0) brk: {
1315 assert(break_operand.kind == .value);
1316 break :brk break_operand;
1317 } else brk: {
1318 assert(s.frame.mode == .run_time);
1319 const func = &s.func.?;
1320 const idx = func.extra.list.items.len;
1321 const block_instructions = mark.slice(u32);
1322 try func.extra.list.ensureUnusedCapacity(s.gpa, block_instructions.len);
1323 const block = try s.addIndexUntracked(.block, .{
1324 .start = @intCast(idx),
1325 .len = @intCast(block_instructions.len + 1),
1326 });
1327 const brk_instruction = try s.addIndexUntracked(.@"break", .{
1328 .block = block,
1329 .value = break_operand,
1330 });
1331 func.extra.list.appendSliceAssumeCapacity(block_instructions);
1332 func.extra.list.appendAssumeCapacity(@intFromEnum(brk_instruction));
1333 break :brk block.toRef();
1334 },
1335 );
1336 mark.deinit();
1043}1337}
10441338
1045fn analyzeRefToBlock(s: *Sema, in: Teg.Inst.Ref) Error!Air.Inst.Ref {1339const BinaryOp = enum {
1046 const t = tracer.trace(@src());1340 add,
1047 defer t.end();1341 sub,
10481342 mul,
1049 const scratch = s.scratch.mark();1343 div,
1050 defer scratch.deinit();1344 lt,
10511345 gt,
1052 const visited = s.resolveRef(in) catch |err| switch (err) {1346 eq,
1053 error.HitNoReturn => Value.void.toAir(),1347};
1054 else => return err,
1055 };
10561348
1057 if (scratch.len() == 0 and visited.kind == .value) {1349fn binTypeResolution(s: *Sema, src: Teg.Inst.Index, op: BinaryOp, lhs: Type, rhs: Type) !Type {
1058 return visited;1350 if (lhs == .@"opaque") {
1351 return if (rhs == .numeric_literal) .float else rhs;
1352 }
1353 if (rhs == .@"opaque") {
1354 return if (lhs == .numeric_literal) .float else lhs;
1059 }1355 }
10601356
1061 if (visited.kind == .index and1357 switch (lhs) {
1062 s.air_instructions.items(.tag)[@intFromEnum(visited.decode().index)] == .block)1358 .string => if (op == .add) switch (rhs) {
1063 {1359 .string => return .string,
1064 return visited;1360 else => {},
1361 },
1362 .numeric_literal => switch (rhs) {
1363 .int32,
1364 .float,
1365 .numeric_literal,
1366 => return rhs,
1367 else => {},
1368 },
1369 .int32 => switch (rhs) {
1370 .int32 => return .int32,
1371 .numeric_literal => return .int32,
1372 else => {},
1373 },
1374 .float => switch (rhs) {
1375 .int32 => return .float,
1376 .numeric_literal => return .float,
1377 .float => return .float,
1378 else => {},
1379 },
1380 else => {},
1065 }1381 }
10661382
1067 const block = try s.addBlock();1383 return s.fail(.bin_op_mismatch, .{
1068 _ = try s.addInstRef(.@"break", .{1384 .src = src.op(.main_node).lazyNode(s.techDebtDeclIndex()),
1069 .block_ref = block.block_index,1385 .lhs = lhs,
1070 .value = visited,1386 .rhs = rhs,
1071 });1387 });
1072 return (try block.finish(s, scratch)).toRef();
1073}1388}
10741389
1075fn resolveRefExtra(s: *Sema, ref: Teg.Inst.Ref, mark: bool) Error!Air.Inst.Ref {1390fn comptimeBin(s: *Sema, op: BinaryOp, lhs: Value, rhs: Value) !Value {
1076 return switch (ref.decode()) {1391 const left_ty = lhs.typeOf(s.ip);
1077 .value => |value| value.toAir(),1392 assert(rhs.typeOf(s.ip) == left_ty);
1078 .index => |index| {
1079 if (s.inst_map.get(index)) |air| {
1080 if (mark) {
1081 switch (air.decode()) {
1082 .index => |i| s.markInst(i),
1083 else => {},
1084 }
1085 }
1086
1087 return air;
1088 }
10891393
1090 return s.analyzeCode(index);1394 switch (op) {
1091 },1395 .add => switch (left_ty) {
1092 };1396 .numeric_literal, .float => {
1093}1397 const l = lhs.toFloat(s.ip) orelse
1398 unreachable; // must be coerced
1399 const r = rhs.toFloat(s.ip) orelse
1400 unreachable; // must be coerced
10941401
1095fn resolveRef(s: *Sema, ref: Teg.Inst.Ref) Error!Air.Inst.Ref {1402 const result = l + r;
1096 return resolveRefExtra(s, ref, true);
1097}
10981403
1099fn resolveValue(s: *Sema, ref: Teg.Inst.Ref) !Value {1404 return try s.makeValue(if (left_ty == .numeric_literal)
1100 const index = switch (ref.decode()) {1405 .{ .comptime_float = result }
1101 .value => |value| return value,1406 else
1102 .index => |index| index,1407 .{ .float = result });
1103 };1408 },
1409 .int32 => {
1410 const l = lhs.toInt32(s.ip) orelse
1411 unreachable; // must be coerced
1412 const r = rhs.toInt32(s.ip) orelse
1413 unreachable; // must be coerced
11041414
1105 if (s.inst_map.get(index)) |air| {1415 const result = std.math.add(i32, l, r) catch
1106 switch (air.decode()) {1416 @panic("TODO: out of range");
1107 .value => |value| return value,
1108 else => {},
1109 }
1110 }
11111417
1112 switch (s.code.instTag(index)) {1418 return try s.makeValue(.{ .int32 = result });
1113 .string => {1419 },
1114 const string: Teg.SlicedString = s.code.instPayload(.string, index);1420 .string => {
1115 return s.makeValue(.{ .string = string.slice(s.code.string_bytes) });1421 const l = lhs.asString(s.ip).toSlice(s.ip);
1422 const r = rhs.asString(s.ip).toSlice(s.ip);
1423 var sfa = std.heap.stackFallback(4096, s.arena());
1424 const alloc = sfa.get();
1425 const joined = try std.mem.concat(alloc, u8, &.{ l, r });
1426 defer alloc.free(joined);
1427 return try s.makeValue(.{ .string = joined });
1428 },
1429 else => unreachable,
1116 },1430 },
1431 inline .sub, .mul, .div => |tag| switch (left_ty) {
1432 .numeric_literal, .float => {
1433 const l = lhs.toFloat(s.ip) orelse
1434 unreachable; // must be coerced
1435 const r = rhs.toFloat(s.ip) orelse
1436 unreachable; // must be coerced
11171437
1118 .block => {1438 const result = switch (tag) {
1119 // Currently only one pattern is supported:1439 .sub => l - r,
1120 //1440 .mul => l * r,
1121 // %2 = block [1441 .div => l / r,
1122 // %1 = load_decl X1442 else => @compileError(unreachable),
1123 // %3 = break(%3, %2)
1124 // ]
1125 //
1126 // This is to support function arguments with structs.
1127 // The rest of this will be implemented in https://github.com/paperdave/wl/issues/44
1128 special_cased: {
1129 const index_list: Teg.IndexList = s.code.instPayload(.block, index);
1130 if (index_list.len != 2) break :special_cased;
1131 const indexes = index_list.slice(&s.code);
1132 const load_inst = indexes[0];
1133 const break_inst = indexes[1];
1134
1135 if (s.code.instTag(break_inst) != .@"break") break :special_cased;
1136 const break_data: Teg.Break = s.code.instPayload(.@"break", break_inst);
1137 if (break_data.operand != load_inst.toRef()) break :special_cased;
1138 if (break_data.block != index) break :special_cased;
1139
1140 if (s.code.instTag(load_inst) != .load_decl) break :special_cased;
1141
1142 const request: Teg.StrNode = s.code.instData(.load_decl, load_inst);
1143 const decl_name = try s.ip.getOrPutString(
1144 s.gpa,
1145 request.string.slice(s.code.string_bytes),
1146 .no_embedded_nulls,
1147 );
1148 const decl_index = s.lookupDecl(decl_name) orelse {
1149 std.debug.print("did not find {s}\n", .{decl_name.toSlice(s.ip)});
1150 return error.AnalysisFail;
1151 };1443 };
1152 return try s.resolveDecl(load_inst.op(.main_node), decl_index);
1153 }
1154
1155 std.debug.panic("TODO: advanced comptime interpreter", .{});
1156 },
11571444
1158 else => |tag| std.debug.panic("TODO: comptime analyze of .{s} {d}", .{ @tagName(tag), @intFromEnum(index) }),1445 return try s.makeValue(if (left_ty == .numeric_literal)
1159 }1446 .{ .comptime_float = result }
1160}1447 else
1448 .{ .float = result });
1449 },
1450 .int32 => {
1451 const l = lhs.toInt32(s.ip) orelse
1452 unreachable; // must be coerced
1453 const r = rhs.toInt32(s.ip) orelse
1454 unreachable; // must be coerced
11611455
1162fn resolveCoerceValue(s: *Sema, src: Teg.InstOp, ref: Teg.Inst.Ref, ty: Type) !Value {1456 const result = switch (tag) {
1163 const any = try s.resolveValue(ref);1457 .sub => std.math.sub(i32, l, r),
1164 return try s.coerceValue(src, any, ty);1458 .mul => std.math.mul(i32, l, r),
1165}1459 .div => return try s.makeValue(.{ .float = @as(f64, @floatFromInt(l)) / @as(f64, @floatFromInt(r)) }),
1460 else => @compileError(unreachable),
1461 } catch @panic("TODO: out of range");
11661462
1167fn resolveCoerceRef(s: *Sema, src: Teg.InstOp, ref: Teg.Inst.Ref, ty: Type) !Air.Inst.Ref {1463 return try s.makeValue(.{ .int32 = result });
1168 const any = try s.resolveRef(ref);1464 },
1169 return try s.coerceRef(src, any, ty);1465 else => unreachable,
1170}1466 },
1467 inline .lt, .gt => |tag| {
1468 const l = lhs.toFloat(s.ip) orelse
1469 unreachable; // must be coerced
1470 const r = rhs.toFloat(s.ip) orelse
1471 unreachable; // must be coerced
11711472
1172fn resolveType(s: *Sema, src: Teg.InstOp, ref: Teg.Inst.Ref) !Type {1473 const result = switch (tag) {
1173 return (try s.resolveCoerceValue(src, ref, .type)).toTypeUnsafe(s.ip);1474 .lt => l < r,
1475 .gt => l > r,
1476 else => @compileError(unreachable),
1477 };
1478 return if (result) .bool_true else .bool_false;
1479 },
1480 .eq => {
1481 const result = lhs == rhs;
1482 return if (result) .bool_true else .bool_false;
1483 },
1484 }
1174}1485}
11751486
1176/// Coerce `value` runtime ref to `coerce_type`, and emit an error at `src`'s location1487/// Coerce `value` runtime ref to `coerce_type`, and emit an error at `src`'s location
1177fn coerceRef(s: *Sema, src: InstOp, ref: Air.Inst.Ref, to: Type) !Air.Inst.Ref {1488fn coerceRef(s: *Sema, src: LazyNode, ref: Air.Inst.Ref, to: Type) !Air.Inst.Ref {
1178 const t = tracer.traceFmt(@src(), "{} to {}", .{ ref.dump(s.ip), to.dump(s.ip) });1489 const t = tracer.traceFmt(@src(), "{} to {}", .{ ref.dump(s.ip), to.dump(s.ip) });
1179 defer t.end();1490 defer t.end();
11801491
...@@ -1182,9 +1493,6 @@ fn coerceRef(s: *Sema, src: InstOp, ref: Air.Inst.Ref, to: Type) !Air.Inst.Ref {...@@ -1182,9 +1493,6 @@ fn coerceRef(s: *Sema, src: InstOp, ref: Air.Inst.Ref, to: Type) !Air.Inst.Ref {
1182 if (from == to) {1493 if (from == to) {
1183 return ref;1494 return ref;
1184 }1495 }
1185 if (from == .noreturn) {
1186 return error.HitNoReturn;
1187 }
1188 if (from == .@"opaque") {1496 if (from == .@"opaque") {
1189 return ref;1497 return ref;
1190 }1498 }
...@@ -1192,6 +1500,9 @@ fn coerceRef(s: *Sema, src: InstOp, ref: Air.Inst.Ref, to: Type) !Air.Inst.Ref {...@@ -1192,6 +1500,9 @@ fn coerceRef(s: *Sema, src: InstOp, ref: Air.Inst.Ref, to: Type) !Air.Inst.Ref {
1192 .value => |v| return (try s.coerceValue(src, v, to)).toAir(),1500 .value => |v| return (try s.coerceValue(src, v, to)).toAir(),
1193 .index => |index| index,1501 .index => |index| index,
1194 };1502 };
1503 if (s.frame.mode == .compile_time) {
1504 return s.fail(.not_comptime_known, .{ .src = src });
1505 }
1195 switch (to) {1506 switch (to) {
1196 .@"opaque" => {1507 .@"opaque" => {
1197 //1508 //
...@@ -1212,7 +1523,7 @@ fn coerceRef(s: *Sema, src: InstOp, ref: Air.Inst.Ref, to: Type) !Air.Inst.Ref {...@@ -1212,7 +1523,7 @@ fn coerceRef(s: *Sema, src: InstOp, ref: Air.Inst.Ref, to: Type) !Air.Inst.Ref {
1212 .string => {1523 .string => {
1213 //1524 //
1214 },1525 },
1215 .noreturn => {1526 .never => {
1216 //1527 //
1217 },1528 },
1218 .type => {1529 .type => {
...@@ -1230,7 +1541,7 @@ fn coerceRef(s: *Sema, src: InstOp, ref: Air.Inst.Ref, to: Type) !Air.Inst.Ref {...@@ -1230,7 +1541,7 @@ fn coerceRef(s: *Sema, src: InstOp, ref: Air.Inst.Ref, to: Type) !Air.Inst.Ref {
1230}1541}
12311542
1232/// Same as `coerceRef` but on a compile-time known value1543/// Same as `coerceRef` but on a compile-time known value
1233fn coerceValue(s: *Sema, src: InstOp, value: Value, to: Type) Error!Value {1544fn coerceValue(s: *Sema, src: LazyNode, value: Value, to: Type) Error!Value {
1234 const from = value.typeOf(s.ip);1545 const from = value.typeOf(s.ip);
1235 if (from == to) {1546 if (from == to) {
1236 return value;1547 return value;
...@@ -1249,7 +1560,7 @@ fn coerceValue(s: *Sema, src: InstOp, value: Value, to: Type) Error!Value {...@@ -1249,7 +1560,7 @@ fn coerceValue(s: *Sema, src: InstOp, value: Value, to: Type) Error!Value {
1249 if (!core.inInt32Range(val)) {1560 if (!core.inInt32Range(val)) {
1250 return s.fail(.comptime_float_to_int, .{1561 return s.fail(.comptime_float_to_int, .{
1251 .val = val,1562 .val = val,
1252 .src = s.lazyNode(src),1563 .src = src,
1253 });1564 });
1254 }1565 }
12551566
...@@ -1272,8 +1583,8 @@ fn coerceValue(s: *Sema, src: InstOp, value: Value, to: Type) Error!Value {...@@ -1272,8 +1583,8 @@ fn coerceValue(s: *Sema, src: InstOp, value: Value, to: Type) Error!Value {
1272 .string => {1583 .string => {
1273 // TODO:1584 // TODO:
1274 },1585 },
1275 .noreturn => {1586 .never => {
1276 // nothing coerces to `noreturn`1587 // nothing coerces to `never`
1277 },1588 },
1278 .numeric_literal => {1589 .numeric_literal => {
1279 if (value.toFloat(s.ip)) |float| {1590 if (value.toFloat(s.ip)) |float| {
...@@ -1299,7 +1610,7 @@ fn coerceValue(s: *Sema, src: InstOp, value: Value, to: Type) Error!Value {...@@ -1299,7 +1610,7 @@ fn coerceValue(s: *Sema, src: InstOp, value: Value, to: Type) Error!Value {
1299 return s.coercionError(src, from, to);1610 return s.coercionError(src, from, to);
1300}1611}
13011612
1302fn ensureValueRuntime(s: *Sema, src: InstOp, ref: Air.Inst.Ref) Error!Air.Inst.Ref {1613fn ensureValueRuntime(s: *Sema, src: LazyNode, ref: Air.Inst.Ref) Error!Air.Inst.Ref {
1303 switch (ref.decode()) {1614 switch (ref.decode()) {
1304 .value => |v| {1615 .value => |v| {
1305 const ty = v.typeOf(s.ip);1616 const ty = v.typeOf(s.ip);
...@@ -1307,9 +1618,9 @@ fn ensureValueRuntime(s: *Sema, src: InstOp, ref: Air.Inst.Ref) Error!Air.Inst.R...@@ -1307,9 +1618,9 @@ fn ensureValueRuntime(s: *Sema, src: InstOp, ref: Air.Inst.Ref) Error!Air.Inst.R
1307 .numeric_literal => {1618 .numeric_literal => {
1308 const float = v.toFloat(s.ip) orelse unreachable;1619 const float = v.toFloat(s.ip) orelse unreachable;
13091620
1310 if (core.inInt32Range(float)) {1621 if (!core.inInt32Range(float)) {
1311 try s.report.warn(.numeric_int_var_ambiguity, .{1622 try s.report.warn(.numeric_int_var_ambiguity, .{
1312 .src = s.lazyNode(src),1623 .src = src,
1313 });1624 });
1314 }1625 }
13151626
...@@ -1319,22 +1630,22 @@ fn ensureValueRuntime(s: *Sema, src: InstOp, ref: Air.Inst.Ref) Error!Air.Inst.R...@@ -1319,22 +1630,22 @@ fn ensureValueRuntime(s: *Sema, src: InstOp, ref: Air.Inst.Ref) Error!Air.Inst.R
1319 },1630 },
1320 .numeric_literal_array => {1631 .numeric_literal_array => {
1321 try s.report.warn(.numeric_int_array_var_ambiguity, .{1632 try s.report.warn(.numeric_int_array_var_ambiguity, .{
1322 .src = s.lazyNode(src),1633 .src = src,
1323 });1634 });
1324 return (try s.coerceArrayValue(src, v, .float)).toAir();1635 return (try s.coerceArrayValue(src, v, .float)).toAir();
1325 },1636 },
1326 .noreturn_array => {1637 .never_array => {
1327 if (v.valueData(s.ip, .array_value).get(s.ip).items_len != 0) {1638 if (v.valueData(s.ip, .array_value).get(s.ip).items_len != 0) {
1328 return s.fail(.runtime_incompatible_type, .{1639 return s.fail(.runtime_incompatible_type, .{
1329 .src = s.lazyNode(src),1640 .src = src,
1330 .type = ty,1641 .type = ty,
1331 });1642 });
1332 }1643 }
1333 },1644 },
1334 .type,1645 .type,
1335 .noreturn,1646 .never,
1336 => return s.fail(.runtime_incompatible_type, .{1647 => return s.fail(.runtime_incompatible_type, .{
1337 .src = s.lazyNode(src),1648 .src = src,
1338 .type = ty,1649 .type = ty,
1339 }),1650 }),
1340 .@"opaque" => @panic("TODO: represent comptime-known opaques better"),1651 .@"opaque" => @panic("TODO: represent comptime-known opaques better"),
...@@ -1347,7 +1658,7 @@ fn ensureValueRuntime(s: *Sema, src: InstOp, ref: Air.Inst.Ref) Error!Air.Inst.R...@@ -1347,7 +1658,7 @@ fn ensureValueRuntime(s: *Sema, src: InstOp, ref: Air.Inst.Ref) Error!Air.Inst.R
1347 => {},1658 => {},
1348 _ => if (!ty.hasPossibleRepresentation(s.ip)) {1659 _ => if (!ty.hasPossibleRepresentation(s.ip)) {
1349 return s.fail(.runtime_incompatible_type, .{1660 return s.fail(.runtime_incompatible_type, .{
1350 .src = s.lazyNode(src),1661 .src = src,
1351 .type = ty,1662 .type = ty,
1352 });1663 });
1353 },1664 },
...@@ -1357,7 +1668,7 @@ fn ensureValueRuntime(s: *Sema, src: InstOp, ref: Air.Inst.Ref) Error!Air.Inst.R...@@ -1357,7 +1668,7 @@ fn ensureValueRuntime(s: *Sema, src: InstOp, ref: Air.Inst.Ref) Error!Air.Inst.R
1357 const ty = s.typeOfRef(ref);1668 const ty = s.typeOfRef(ref);
1358 if (!ty.hasPossibleRepresentation(s.ip)) {1669 if (!ty.hasPossibleRepresentation(s.ip)) {
1359 return s.fail(.runtime_incompatible_type, .{1670 return s.fail(.runtime_incompatible_type, .{
1360 .src = s.lazyNode(src),1671 .src = src,
1361 .type = ty,1672 .type = ty,
1362 });1673 });
1363 }1674 }
...@@ -1366,89 +1677,27 @@ fn ensureValueRuntime(s: *Sema, src: InstOp, ref: Air.Inst.Ref) Error!Air.Inst.R...@@ -1366,89 +1677,27 @@ fn ensureValueRuntime(s: *Sema, src: InstOp, ref: Air.Inst.Ref) Error!Air.Inst.R
1366 return ref;1677 return ref;
1367}1678}
13681679
1369fn ensureValueDefinedRepresentation(s: *Sema, src: InstOp, ref: Air.Inst.Ref) Error!Air.Inst.Ref {1680fn ensureValueDefinedRepresentation(s: *Sema, src: LazyNode, ref: Air.Inst.Ref) Error!Air.Inst.Ref {
1370 const runtime = try s.ensureValueRuntime(src, ref);1681 const runtime = try s.ensureValueRuntime(src, ref);
1371 const ty = s.typeOfRef(runtime);1682 const ty = s.typeOfRef(runtime);
1372 if (!ty.hasDefinedRepresentation(s.ip)) {1683 if (!ty.hasDefinedRepresentation(s.ip)) {
1373 return s.fail(.type_no_defined_representation, .{1684 return s.fail(.type_no_defined_representation, .{
1374 .src = s.lazyNode(src),1685 .src = src,
1375 .type = ty,1686 .type = ty,
1376 });1687 });
1377 }1688 }
1378 return runtime;1689 return runtime;
1379}1690}
13801691
1381fn coerceArrayValue(s: *Sema, src: InstOp, value: Value, item_type: Type) !Value {
1382 const array: InternPool.Tag.ArrayValue, var trail = value.valueData(s.ip, .array_value).getTrail(s.ip);
1383
1384 const scratch = try s.scratch.reserve(s.arena, Value, array.items_len);
1385 defer scratch.deinit();
1386
1387 const from = trail.readValueSlice(array.items_len);
1388
1389 for (0..from.len) |i| {
1390 scratch.at(i).* = try s.coerceValue(src, from.get(s.ip)[i], item_type);
1391 }
1392
1393 return try s.makeValue(.{ .array_value = .{
1394 .type = try s.makeType(.{ .array_type = .{ .item_type = item_type } }),
1395 .items = scratch.slice(),
1396 } });
1397}
1398
1399fn resolveDecl(s: *Sema, src: InstOp, decl_index: Decl.Index) Error!Value {
1400 const decl = s.ip.declPtr(decl_index);
1401 switch (decl.analysis) {
1402 .unreferenced => {
1403 decl.analysis = .in_progress;
1404
1405 // TODO: make this short-circuit the current analysis, move this analyze
1406 // job into a "deferred" list, and then make this thread analyze something else
1407
1408 var arena = std.heap.ArenaAllocator.init(s.gpa);
1409 defer arena.deinit();
1410
1411 var todo: Report = undefined;
1412 var sema: Sema = .{
1413 .comp = s.comp,
1414 .gpa = s.gpa,
1415 .ip = s.ip,
1416 .arena = arena.allocator(),
1417 .code = try s.ip.namespacePtr(decl.src_namespace).file_scope.getTeg(s.comp, &todo),
1418 .decl = decl,
1419 .decl_index = decl_index,
1420 .namespace_index = decl.src_namespace,
1421 };
1422
1423 try sema.init();
1424 defer sema.deinit();
1425 const value = try sema.analyzeDeclaration();
1426 try sema.finalize();
1427
1428 decl.analysis = .complete;
1429
1430 return value;
1431 },
1432 .in_progress => {
1433 return s.fail(.dependency_loop, .{
1434 .src = s.lazyNode(src),
1435 });
1436 },
1437 else => {
1438 return decl.value();
1439 },
1440 }
1441}
1442
1443fn unionType(s: *Sema, a: Type, b: Type) Type {1692fn unionType(s: *Sema, a: Type, b: Type) Type {
1444 if (a == b) {1693 if (a == b) {
1445 return a;1694 return a;
1446 }1695 }
14471696
1448 if (a == .noreturn)1697 if (a == .never)
1449 return b;1698 return b;
14501699
1451 if (b == .noreturn)1700 if (b == .never)
1452 return a;1701 return a;
14531702
1454 std.debug.panic(1703 std.debug.panic(
...@@ -1461,198 +1710,208 @@ fn unionType(s: *Sema, a: Type, b: Type) Type {...@@ -1461,198 +1710,208 @@ fn unionType(s: *Sema, a: Type, b: Type) Type {
1461 // return s.ip.get(s.gpa, .{ .@"union" = &.{ a, b } });1710 // return s.ip.get(s.gpa, .{ .@"union" = &.{ a, b } });
1462}1711}
14631712
1464const BinaryOp = enum {1713fn coerceArrayValue(s: *Sema, src: LazyNode, value: Value, item_type: Type) !Value {
1465 add,1714 const array: InternPool.Tag.ArrayValue, var trail = value.valueData(s.ip, .array_value).getTrail(s.ip);
1466 sub,
1467 mul,
1468 div,
1469 lt,
1470 gt,
1471 eq,
1472};
14731715
1474fn binTypeResolution(s: *Sema, src: Teg.Inst.Index, op: BinaryOp, lhs: Type, rhs: Type) !Type {1716 const scratch = try s.scratch.reserve(s.arena(), Value, array.items_len);
1475 if (lhs == .@"opaque") {1717 defer scratch.deinit();
1476 return if (rhs == .numeric_literal) .float else rhs;1718
1719 const from = trail.readValueSlice(array.items_len);
1720
1721 for (0..from.len) |i| {
1722 scratch.at(i).* = try s.coerceValue(src, from.get(s.ip)[i], item_type);
1477 }1723 }
1478 if (rhs == .@"opaque") {1724
1479 return if (lhs == .numeric_literal) .float else lhs;1725 return try s.makeValue(.{ .array_value = .{
1726 .type = try s.makeType(.{ .array_type = .{ .item_type = item_type } }),
1727 .items = scratch.slice(),
1728 } });
1729}
1730
1731fn lookupDecl(s: *Sema, name: NullTerminatedString) InternPool.DeclIndex {
1732 var nsi = s.ns_index;
1733 while (true) {
1734 const ns = s.ip.namespacePtr(nsi);
1735 if (ns.lookupDirect(s.comp, name)) |decl_id| {
1736 return decl_id;
1737 }
1738 nsi = ns.parent.unwrap() orelse
1739 unreachable; // transform ensures all identifiers get resolved
1480 }1740 }
1741}
14811742
1482 switch (lhs) {1743fn getDecl(sema: *Sema, decl_index: Decl.Index) !Value {
1483 .string => if (op == .add) switch (rhs) {1744 if (sema.owner.kind == .decl) {
1484 .string => return .string,1745 if (decl_index == sema.owner.unwrap().decl) {
1485 else => {},1746 return sema.ip.declPtr(decl_index).value();
1486 },1747 }
1487 .numeric_literal => switch (rhs) {1748 }
1488 .int32,1749 const decl_data = sema.ip.declPtr(decl_index);
1489 .float,1750 try sema.declareDependency(.{ .decl_val = decl_index });
1490 .numeric_literal,1751 const au: AnalysisUnit = .wrap(.{ .decl = decl_index });
1491 => return rhs,1752 switch (decl_data.analysis) {
1492 else => {},1753 .unreferenced => {
1754 try sema.comp.jobs.writeItem(.{ .analysis = .{ .au = au, .first_blocked_sema = sema } });
1755 return error.Interrupt;
1493 },1756 },
1494 .int32 => switch (rhs) {1757 .waiting, .in_progress => {
1495 .int32 => return .int32,1758 const other_sema = sema.comp.ongoing_analysis.get(au) orelse unreachable;
1496 .numeric_literal => return .int32,1759 try other_sema.blocked_semas.append(sema.gpa, sema);
1497 else => {},1760 return error.Interrupt;
1498 },1761 },
1499 .float => switch (rhs) {1762 .complete => {
1500 .int32 => return .float,1763 return decl_data.value();
1501 .numeric_literal => return .float,
1502 .float => return .float,
1503 else => {},
1504 },1764 },
1505 else => {},
1506 }1765 }
1766}
15071767
1508 return s.fail(.bin_op_mismatch, .{1768fn declareDependency(sema: *Sema, dependee: InternPool.Dependee) !void {
1509 .src = s.nodeLoc(src, .main_node),1769 try sema.ip.addDependency(sema.gpa, sema.owner, dependee);
1510 .lhs = lhs,
1511 .rhs = rhs,
1512 });
1513}1770}
15141771
1515fn comptimeBin(s: *Sema, op: BinaryOp, lhs: Value, rhs: Value) !Value {1772fn fieldAccessStruct(
1516 const left_ty = lhs.typeOf(s.ip);1773 s: *Sema,
1517 assert(rhs.typeOf(s.ip) == left_ty);1774 lhs: Air.Inst.Ref,
1775 lhs_type: Type,
1776 struct_type: InternPool.Key.StructType,
1777 field: Teg.Inst.Ref,
1778) !Air.Inst.Ref {
1779 const rhs = try s.getCoercedValue(s.lazyNode(.rhs), field, .string);
1780 const string = rhs.asString(s.ip);
1781
1782 const found_index: u32 = found_index: {
1783 const lookup = string.tryToNullTerminatedString(
1784 s.ip,
1785 s.gpa,
1786 ) catch |err| switch (err) {
1787 error.HasNulls => break :found_index null,
1788 else => |e| return e,
1789 };
15181790
1519 switch (op) {1791 break :found_index for (struct_type.field_keys, 0..) |key, i| {
1520 .add => switch (left_ty) {1792 if (key == lookup)
1521 .numeric_literal, .float => {1793 break @as(u32, @intCast(i));
1522 const l = lhs.toFloat(s.ip) orelse1794 } else null;
1523 unreachable; // must be coerced1795 } orelse {
1524 const r = rhs.toFloat(s.ip) orelse1796 return s.fail(.field_lookup_failed, .{
1525 unreachable; // must be coerced1797 .ty = lhs_type,
1798 .src = s.lazyNode(.lhs),
1799 .field = rhs,
1800 });
1801 };
15261802
1527 const result = l + r;1803 switch (lhs.decode()) {
1804 .value => |value| {
1805 _, const trail = value.valueData(s.ip, .struct_value).getTrail(s.ip);
1806 return trail.at(&s.ip.extra, Value, found_index).toAir();
1807 },
1808 .index => |index| {
1809 try s.ensureAtRuntime(s.lazyNode(.lhs));
1810 return s.addRef(.field_struct, .{
1811 .base = index,
1812 .field_index = found_index,
1813 });
1814 },
1815 }
1816}
15281817
1529 return try s.makeValue(if (left_ty == .numeric_literal)1818fn fieldAccessArray(s: *Sema, lhs: Air.Inst.Ref, lhs_type: Type, rhs_teg: Teg.Inst.Ref) !Air.Inst.Ref {
1530 .{ .comptime_float = result }1819 const rhs = s.getRef(rhs_teg, .marked);
1531 else
1532 .{ .float = result });
1533 },
1534 .int32 => {
1535 const l = lhs.toInt32(s.ip) orelse
1536 unreachable; // must be coerced
1537 const r = rhs.toInt32(s.ip) orelse
1538 unreachable; // must be coerced
15391820
1540 const result = std.math.add(i32, l, r) catch1821 switch (s.typeOfRef(rhs)) {
1541 @panic("TODO: out of range");1822 .string => {
1823 const value = try s.getValue(s.lazyNode(.rhs), rhs_teg);
1824 const string = value.asString(s.ip).toSlice(s.ip);
1825 if (mem.eql(u8, string, "len")) {
1826 switch (lhs.decode()) {
1827 .value => |val| {
1828 return try s.makeValueAir(.{
1829 .int32 = @intCast(val.toKey(s.ip).array_value.items.len),
1830 });
1831 },
1832 .index => {
1833 try s.ensureAtRuntime(s.lazyNode(.main_node));
1834 return s.addRef(.array_len, .{
1835 .operand = lhs,
1836 });
1837 },
1838 }
1839 }
15421840
1543 return try s.makeValue(.{ .int32 = result });1841 return s.fail(.field_lookup_failed, .{
1544 },1842 .ty = lhs_type,
1545 .string => {1843 .src = s.lazyNode(.lhs),
1546 const l = lhs.asString(s.ip).toSlice(s.ip);1844 .field = value,
1547 const r = rhs.asString(s.ip).toSlice(s.ip);1845 });
1548 var sfa = std.heap.stackFallback(4096, s.arena);
1549 const alloc = sfa.get();
1550 const joined = try std.mem.concat(alloc, u8, &.{ l, r });
1551 defer alloc.free(joined);
1552 return try s.makeValue(.{ .string = joined });
1553 },
1554 else => unreachable,
1555 },1846 },
1556 inline .sub, .mul, .div => |tag| switch (left_ty) {1847 else => {
1557 .numeric_literal, .float => {1848 const index = try s.getCoercedRef(s.lazyNode(.rhs), rhs_teg, .int32, .marked);
1558 const l = lhs.toFloat(s.ip) orelse
1559 unreachable; // must be coerced
1560 const r = rhs.toFloat(s.ip) orelse
1561 unreachable; // must be coerced
1562
1563 const result = switch (tag) {
1564 .sub => l - r,
1565 .mul => l * r,
1566 .div => l / r,
1567 else => @compileError(unreachable),
1568 };
1569
1570 return try s.makeValue(if (left_ty == .numeric_literal)
1571 .{ .comptime_float = result }
1572 else
1573 .{ .float = result });
1574 },
1575 .int32 => {
1576 const l = lhs.toInt32(s.ip) orelse
1577 unreachable; // must be coerced
1578 const r = rhs.toInt32(s.ip) orelse
1579 unreachable; // must be coerced
15801849
1581 const result = switch (tag) {1850 if (lhs.kind == .value and index.kind == .value) {
1582 .sub => std.math.sub(i32, l, r),1851 // TODO:
1583 .mul => std.math.mul(i32, l, r),1852 }
1584 .div => return try s.makeValue(.{ .float = @as(f64, @floatFromInt(l)) / @as(f64, @floatFromInt(r)) }),
1585 else => @compileError(unreachable),
1586 } catch @panic("TODO: out of range");
15871853
1588 return try s.makeValue(.{ .int32 = result });1854 try s.ensureAtRuntime(s.lazyNode(.main_node));
1589 },
1590 else => unreachable,
1591 },
1592 inline .lt, .gt => |tag| {
1593 const l = lhs.toFloat(s.ip) orelse
1594 unreachable; // must be coerced
1595 const r = rhs.toFloat(s.ip) orelse
1596 unreachable; // must be coerced
15971855
1598 const result = switch (tag) {1856 return s.addRef(.field_array, .{
1599 .lt => l < r,1857 .lhs = lhs,
1600 .gt => l > r,1858 .rhs = index,
1601 else => @compileError(unreachable),1859 });
1602 };
1603 return @as(
1604 InternPool.Value,
1605 if (result) .bool_true else .bool_false,
1606 );
1607 },
1608 .eq => {
1609 const result = lhs == rhs;
1610 return @as(
1611 InternPool.Value,
1612 if (result) .bool_true else .bool_false,
1613 );
1614 },1860 },
1615 }1861 }
1616}1862}
16171863
1618const IncompleteBlock = struct {1864//
1619 block_index: Air.Inst.Index,
16201865
1621 pub fn finish(block: IncompleteBlock, s: *Sema, scratch_mark: core.U32Stack.Mark) !Air.Inst.Index {1866fn tempAir(s: *const Sema) Air {
1622 s.setInstData(.block, block.block_index, try s.scratchMarkToIndexList(scratch_mark));1867 return .{
1623 return block.block_index;1868 .insts = s.func.?.instructions.slice(),
1624 }1869 .extra = s.func.?.extra.constant(),
1625};1870 };
1871}
16261872
1627fn addBlock(s: *Sema) !IncompleteBlock {1873fn typeOfRef(s: *Sema, ref: Air.Inst.Ref) Type {
1628 try s.air_instructions.append(s.gpa, .{1874 return switch (ref.decode()) {
1629 .tag = .block,1875 .value => |v| v.typeOf(s.ip),
1630 .data = undefined,1876 .index => |i| s.tempAir().typeOfIndex(i, s.ip),
1631 .flags = .{},1877 };
1632 });
1633 return .{ .block_index = @enumFromInt(s.air_instructions.len - 1) };
1634}1878}
16351879
1636fn addInstRef(s: *Sema, comptime tag: Air.Inst.Tag, data: tag.Data()) !Air.Inst.Ref {1880fn pushFrame(s: *Sema) !void {
1637 try s.air_instructions.append(s.gpa, .{1881 (try s.frame_stack.addOne(s.arena())).* = s.frame;
1882 s.frame = undefined;
1883}
1884
1885fn popFrame(s: *Sema) void {
1886 s.frame = s.frame_stack.pop();
1887}
1888
1889fn addIndexUntracked(s: *Sema, comptime tag: Air.Inst.Tag, data: tag.Data()) !Air.Inst.Index {
1890 assert(s.frame.mode == .run_time);
1891 try s.func.?.instructions.append(s.gpa, .{
1638 .tag = tag,1892 .tag = tag,
1639 .data = Air.EncodedData.encode(tag, data),1893 .data = .encode(tag, data),
1640 .flags = .{},1894 .flags = .{},
1641 });1895 });
1642 const index: Air.Inst.Index = @enumFromInt(s.air_instructions.len - 1);1896 return @enumFromInt(s.func.?.instructions.len - 1);
1643 try s.scratch.add(s.arena, Air.Inst.Index, index);
1644 return index.toRef();
1645}1897}
16461898
1647fn setInstData(1899fn addIndex(s: *Sema, comptime tag: Air.Inst.Tag, data: tag.Data()) !Air.Inst.Index {
1648 s: *Sema,1900 const index = try s.addIndexUntracked(tag, data);
1649 comptime tag: Air.Inst.Tag,1901 try s.scratch.add(s.arena(), Air.Inst.Index, index);
1650 index: Air.Inst.Index,1902 return index;
1651 data: tag.Data(),1903}
1652) void {1904
1653 const i = @intFromEnum(index);1905fn addRef(s: *Sema, comptime tag: Air.Inst.Tag, data: tag.Data()) !Air.Inst.Ref {
1654 assert(s.air_instructions.items(.tag)[i] == tag);1906 return (try s.addIndex(tag, data)).toRef();
1655 s.air_instructions.items(.data)[i] = Air.EncodedData.encode(tag, data);1907}
1908
1909fn typeOfIndex(s: *Sema, i: Air.Inst.Index) Type {
1910 return s.tempAir().typeOfIndex(i, s.ip);
1911}
1912
1913fn namespace(s: *const Sema) *Namespace {
1914 return s.ip.namespacePtr(s.ns_index);
1656}1915}
16571916
1658fn makeValue(s: *Sema, key: InternPool.Key) !Value {1917fn makeValue(s: *Sema, key: InternPool.Key) !Value {
...@@ -1667,91 +1926,85 @@ fn makeValueAir(s: *Sema, key: InternPool.Key) !Air.Inst.Ref {...@@ -1667,91 +1926,85 @@ fn makeValueAir(s: *Sema, key: InternPool.Key) !Air.Inst.Ref {
1667 return (try s.makeValue(key)).toAir();1926 return (try s.makeValue(key)).toAir();
1668}1927}
16691928
1670fn mapTeg(s: *Sema, inst: Teg.Inst.Ref) Air.Inst.Ref {1929fn lazyNode(s: *const Sema, op: Report.Operand) LazyNode {
1671 return switch (inst.decode()) {1930 return s.frame.currentIndex().op(op).lazyNode(s.techDebtDeclIndex());
1672 .value => |value| value.toAir(),
1673 .index => |index| s.inst_map.get(index) orelse {
1674 if (std.debug.runtime_safety) {
1675 std.debug.panic("Unmapped operand %{d}", .{@intFromEnum(index)});
1676 }
1677 unreachable;
1678 },
1679 };
1680}1931}
16811932
1682fn scratchMarkToIndexList(s: *Sema, mark: core.U32Stack.Mark) !Air.IndexList {1933/// Using this function is incorrect because as of "Sema v5" rewrite,
1683 const items = mark.slice(u32);1934/// it is incorrect to use a Decl.Index as the source of truth.
1684 const extra_top = s.air_extra.list.items.len;1935fn techDebtDeclIndex(s: *const Sema) Decl.Index {
1685 try s.air_extra.list.appendSlice(s.gpa, items);1936 return switch (s.owner.unwrap()) {
1686 return .{1937 .decl => |decl| decl,
1687 .start = @intCast(extra_top),1938 .func => |func| s.ip.valueToKey(func).func.owner_decl,
1688 .len = @intCast(items.len),
1689 };1939 };
1690}1940}
16911941
1692fn tempAir(s: *const Sema) Air {1942fn addWarn(s: *Sema, comptime kind: Report.Err.FieldEnum, data: core.FieldType(Report.Err, kind)) !void {
1693 return .{1943 try s.report.warn(kind, data);
1694 .insts = s.air_instructions.slice(),
1695 .extra = s.air_extra.constant(),
1696 };
1697}1944}
16981945
1699fn typeOfRef(s: *Sema, ref: Air.Inst.Ref) InternPool.Type {1946fn addError(s: *Sema, comptime kind: Report.Err.FieldEnum, data: core.FieldType(Report.Err, kind)) !void {
1700 return s.tempAir().typeOf(ref, s.ip);1947 try s.report.err(kind, data);
1701}1948}
17021949
1703fn lookupDecl(s: *Sema, name: NullTerminatedString) ?InternPool.DeclIndex {1950fn fail(s: *Sema, comptime kind: Report.Err.FieldEnum, data: core.FieldType(Report.Err, kind)) Error {
1704 var nsi = s.namespace_index;1951 try s.report.err(kind, data);
1705 while (true) {1952 return Error.AnalysisFail;
1706 const ns = s.ip.namespacePtr(nsi);
1707 if (ns.lookupDirect(s.comp, name)) |decl_id| {
1708 return decl_id;
1709 }
1710 nsi = ns.parent.unwrap() orelse
1711 unreachable; // transform ensures all identifiers get resolved
1712 }
1713}1953}
17141954
1715fn nodeLoc(s: *const Sema, in: Teg.Inst.Index, op: Report.Operand) Report.LazyNode {1955fn coercionError(s: *Sema, src: LazyNode, from: Type, to: Type) Error {
1716 return s.lazyNode(in.op(op));1956 return s.fail(.coercion, .{
1957 .from = from,
1958 .to = to,
1959 .src = src,
1960 });
1717}1961}
17181962
1719fn addWarn(s: *Sema, comptime kind: Report.Err.FieldEnum, data: std.meta.FieldType(Report.Err, kind)) !void {1963fn assertHasFunction(s: *const Sema) void {
1720 try s.report.warnAt(kind, data, @returnAddress());1964 assert(s.func_stack_len > 0);
1721}1965}
17221966
1723fn addError(s: *Sema, comptime kind: Report.Err.FieldEnum, data: std.meta.FieldType(Report.Err, kind)) !void {1967/// TODO: This function is designed incorrectly. it should be
1724 try s.report.errAt(kind, data, @returnAddress());1968/// - "cannot be computed at comptime due to runtime operand"
1969/// - <exact operation> cannot be performed at compile time
1970fn ensureAtRuntime(s: *Sema, src: LazyNode) !void {
1971 if (s.frame.mode == .compile_time) {
1972 return s.fail(.cannot_be_comptime, .{ .src = src });
1973 }
1725}1974}
17261975
1727fn fail(s: *Sema, comptime kind: Report.Err.FieldEnum, data: std.meta.FieldType(Report.Err, kind)) Error {1976fn arena(s: *Sema) Allocator {
1728 try s.report.errAt(kind, data, @returnAddress());1977 return s.arena_state.allocator();
1729 return Error.AnalysisFail;
1730}1978}
17311979
1732fn coercionError(s: *Sema, src: InstOp, from: Type, to: Type) Error {1980fn debug(s: *Sema, comptime fmt: []const u8, args: anytype) void {
1733 return s.fail(.coercion, .{1981 if (s.comp.options.verbose_sema)
1734 .from = from,1982 s.comp.out.print(
1735 .to = to,1983 "sema {}: " ++ fmt ++ "\n",
1736 .src = s.lazyNode(src),1984 .{s.owner.dump(s.ip)} ++ args,
1737 });1985 ) catch {};
1738}1986}
17391987
1740fn lazyNode(s: *const Sema, src: InstOp) Report.LazyNode {1988/// Frame uses @fieldParentPtr to do some pointer arithmetic. To ensure every
1741 return src.lazyNode(s.decl_index);1989/// pointer to `Sema` is valid, it has a field set to a specific 128-bit number.
1742}1990/// Failing assert(s.magic == .valid) means you have an invalid pointer.
1991const Magic = if (runtime_safety) enum(u128) {
1992 valid = 0x57453ef77efd3a72c7a0ebe03f80878c,
1993 _,
1994} else enum { valid };
17431995
1744const Sema = @This();1996const Sema = @This();
17451997
1746const std = @import("std");1998const std = @import("std");
1747const mem = std.mem;1999const mem = std.mem;
1748const math = std.math;2000const math = std.math;
1749const Allocator = std.mem.Allocator;
1750const assert = std.debug.assert;2001const assert = std.debug.assert;
1751const log = std.log.scoped(.sema);2002const Allocator = std.mem.Allocator;
1752const DynamicBitSetUnmanaged = std.DynamicBitSetUnmanaged;2003const DynamicBitSetUnmanaged = std.DynamicBitSetUnmanaged;
2004const DynamicArray = std.ArrayListUnmanaged;
17532005
1754const core = @import("mod.zig");2006const core = @import("mod.zig");
2007const runtime_safety = core.runtime_safety;
1755const assertFailed = core.assertFailed;2008const assertFailed = core.assertFailed;
1756const StoredTrace = core.StoredTrace;2009const StoredTrace = core.StoredTrace;
17572010
...@@ -1761,18 +2014,18 @@ const Decl = Comp.Decl;...@@ -1761,18 +2014,18 @@ const Decl = Comp.Decl;
1761const Namespace = Comp.Namespace;2014const Namespace = Comp.Namespace;
17622015
1763const Teg = core.Teg;2016const Teg = core.Teg;
1764const InstOp = Teg.InstOp;
17652017
1766const Air = core.Air;2018const Air = core.Air;
1767const Ast = core.Ast;2019const Ast = core.Ast;
1768const store = core.array;2020const store = core.array;
17692021
1770const Report = core.Report;2022const Report = core.Report;
1771const operandArgument = Report.Operand.argument;2023const LazyNode = Report.LazyNode;
17722024
1773const InternPool = core.InternPool;2025const InternPool = core.InternPool;
1774const Value = InternPool.Value;2026const Value = InternPool.Value;
1775const Type = InternPool.Type;2027const Type = InternPool.Type;
2028const AnalysisUnit = InternPool.AnalysisUnit;
1776const NullTerminatedString = InternPool.NullTerminatedString;2029const NullTerminatedString = InternPool.NullTerminatedString;
17772030
1778const tracer = @import("tracer");2031const tracer = @import("tracer");
src/core/StoredTrace.zig+2
...@@ -41,6 +41,8 @@ pub fn from(stack_trace: ?*std.builtin.StackTrace) StoredTrace {...@@ -41,6 +41,8 @@ pub fn from(stack_trace: ?*std.builtin.StackTrace) StoredTrace {
41}41}
4242
43pub const BlankImpl = struct {43pub const BlankImpl = struct {
44 pub const empty: BlankImpl = .{};
45
44 pub fn trace(_: *BlankImpl) std.builtin.StackTrace {46 pub fn trace(_: *BlankImpl) std.builtin.StackTrace {
45 @compileError("trace is not available with StoredTrace.BlankImpl");47 @compileError("trace is not available with StoredTrace.BlankImpl");
46 }48 }
src/core/Teg.Dump.zig+100-28
...@@ -46,39 +46,57 @@ const State = struct {...@@ -46,39 +46,57 @@ const State = struct {
46 .module => {46 .module => {
47 try s.dumpInstList(writer, teg.moduleDeclarations(inst), '{', '}');47 try s.dumpInstList(writer, teg.moduleDeclarations(inst), '{', '}');
48 },48 },
49 .declaration => {49 .declaration, .variable_declaration, .test_declaration => {
50 const decl = teg.fullDecl(inst);50 const decl = teg.fullDecl(inst);
51 try writer.print(" ({s}) ", .{@tagName(decl.data)});
5251
52 try writer.writeAll(" ");
53 const name = decl.name.slice(teg.string_bytes);53 const name = decl.name.slice(teg.string_bytes);
54 if (name.len == 0) {54 if (name.len == 0) {
55 try tty.setColor(writer, .bold);55 try tty.setColor(writer, .bold);
56 try writer.writeAll("<top level code>");56 try writer.writeAll("[no name]");
57 } else {57 } else {
58 try tty.setColor(writer, .magenta);58 try tty.setColor(writer, .magenta);
59 try writer.writeAll(name);59 try writer.writeAll(name);
60 }60 }
61 try tty.setColor(writer, .reset);61 try tty.setColor(writer, .reset);
6262 try writer.writeAll(" (");
63 try writer.print("{d}", .{decl.instruction_count});
64 try writer.writeAll(")");
65 try tty.setColor(writer, .bright_black);
66 try writer.writeAll(" [");
67 try writer.writeAll(&std.fmt.bytesToHex(decl.hash, .lower));
68 try writer.writeAll("]");
69 try tty.setColor(writer, .reset);
70 try writer.writeAll(" (");
71 s.indent += 1;
72 try s.newline(writer);
73 try s.dumpRef(writer, decl.body);
74 s.indent -= 1;
75 try s.newline(writer);
76 try writer.writeAll(")");
77 },
78 .func => {
79 const func: Teg.Func = teg.instPayload(.func, inst);
63 try writer.writeAll(" {");80 try writer.writeAll(" {");
64 s.indent += 1;81 s.indent += 1;
65 try s.newline(writer);82 try s.newline(writer);
6683 try writer.writeAll("params: ");
67 switch (decl.data) {84 try s.dumpOptionalRefList(writer, func.params.slice(teg), '[', ']');
68 .function => |fn_data| {85 try s.newline(writer);
69 try writer.writeAll("params: ");86 try writer.writeAll("return_type: ");
70 try s.dumpRefList(writer, fn_data.params.slice(teg), '[', ']');87 if (func.return_type.unwrap()) |ret|
71 try s.newline(writer);88 try s.dumpRef(writer, ret)
72 if (fn_data.return_type.unwrap()) |return_type| {89 else {
73 try writer.writeAll("return_type: ");90 try tty.setColor(writer, .bright_black);
74 try s.dumpRef(writer, return_type);91 try writer.writeAll("<inferred>");
75 try s.newline(writer);92 try tty.setColor(writer, .reset);
76 }
77 },
78 .immutable => {},
79 }93 }
94 try s.newline(writer);
95 try writer.writeAll("captures: ");
96 try s.dumpInstList2(writer, func.captures.slice(teg), '[', ']');
97 try s.newline(writer);
80 try writer.writeAll("body: ");98 try writer.writeAll("body: ");
81 try s.dumpRef(writer, decl.body);99 try s.dumpInst(writer, func.body);
82 s.indent -= 1;100 s.indent -= 1;
83 try s.newline(writer);101 try s.newline(writer);
84 try writer.writeAll("}");102 try writer.writeAll("}");
...@@ -91,8 +109,8 @@ const State = struct {...@@ -91,8 +109,8 @@ const State = struct {
91 try tty.setColor(writer, .reset);109 try tty.setColor(writer, .reset);
92 try writer.writeAll(")");110 try writer.writeAll(")");
93 },111 },
94 .block => {112 inline .block, .@"comptime" => |t| {
95 const block = teg.instData(.block, inst);113 const block = teg.instData(t, inst);
96 const index_list = block.extra_index.get(teg);114 const index_list = block.extra_index.get(teg);
97 try s.dumpInstList(writer, index_list.slice(teg), '[', ']');115 try s.dumpInstList(writer, index_list.slice(teg), '[', ']');
98 },116 },
...@@ -156,6 +174,26 @@ const State = struct {...@@ -156,6 +174,26 @@ const State = struct {
156 try s.dumpRefMarker(writer, data.operand);174 try s.dumpRefMarker(writer, data.operand);
157 try writer.writeAll(")");175 try writer.writeAll(")");
158 },176 },
177 .store_field => {
178 const data: Teg.StoreField = teg.instData(.store_field, inst).extra_index.get(teg);
179 try writer.writeAll("(");
180 try s.dumpRefMarker(writer, data.base);
181 try writer.writeAll(", ");
182 try s.dumpRefMarker(writer, data.field);
183 try writer.writeAll(", ");
184 try s.dumpRefMarker(writer, data.operand);
185 try writer.writeAll(")");
186 },
187 .store_decl => {
188 const data: Teg.StoreVarDecl = teg.instData(.store_decl, inst).extra_index.get(teg);
189 try writer.writeAll("(");
190 try tty.setColor(writer, .magenta);
191 try writer.writeAll(data.var_decl.slice(teg.string_bytes));
192 try tty.setColor(writer, .reset);
193 try writer.writeAll(", ");
194 try s.dumpRefMarker(writer, data.operand);
195 try writer.writeAll(")");
196 },
159 .string => {197 .string => {
160 const data: Teg.SlicedString = teg.instData(.string, inst).extra_index.get(teg);198 const data: Teg.SlicedString = teg.instData(.string, inst).extra_index.get(teg);
161 try tty.setColor(writer, .green);199 try tty.setColor(writer, .green);
...@@ -170,12 +208,13 @@ const State = struct {...@@ -170,12 +208,13 @@ const State = struct {
170 .negate,208 .negate,
171 .bool_not,209 .bool_not,
172 .bit_not,210 .bit_not,
173 .js,211 .@"extern",
174 .import,212 .import,
175 .fetch,213 .fetch,
176 .compile_error,214 .compile_error,
177 .type_of,215 .type_of,
178 .array_type,216 .array_type,
217 .assert,
179 => |comptime_tag| {218 => |comptime_tag| {
180 const data = teg.instData(comptime_tag, inst);219 const data = teg.instData(comptime_tag, inst);
181 try writer.writeAll("(");220 try writer.writeAll("(");
...@@ -221,12 +260,6 @@ const State = struct {...@@ -221,12 +260,6 @@ const State = struct {
221 try s.newline(writer);260 try s.newline(writer);
222 try writer.writeAll("}");261 try writer.writeAll("}");
223 },262 },
224 .@"comptime" => {
225 const data: Teg.RefNode = teg.instData(.@"comptime", inst);
226 try writer.writeAll("(");
227 try s.dumpRef(writer, data.operand);
228 try writer.writeAll(")");
229 },
230 .struct_decl => {263 .struct_decl => {
231 const data: Teg.StructDecl = teg.instPayload(.struct_decl, inst);264 const data: Teg.StructDecl = teg.instPayload(.struct_decl, inst);
232 try writer.writeAll(" {");265 try writer.writeAll(" {");
...@@ -274,7 +307,7 @@ const State = struct {...@@ -274,7 +307,7 @@ const State = struct {
274307
275 for (data.list.slice(teg)) |item| {308 for (data.list.slice(teg)) |item| {
276 try s.newline(writer);309 try s.newline(writer);
277 try s.dumpRef(writer, item);310 try s.dumpRefMarker(writer, item);
278 try writer.writeAll(",");311 try writer.writeAll(",");
279 }312 }
280313
...@@ -362,6 +395,22 @@ const State = struct {...@@ -362,6 +395,22 @@ const State = struct {
362 try writer.print("{c}", .{r});395 try writer.print("{c}", .{r});
363 }396 }
364397
398 fn dumpInstList2(state: *State, writer: anytype, list: []const Teg.Inst.Index, l: u8, r: u8) !void {
399 try writer.print(" {c}", .{l});
400 if (list.len > 0) {
401 state.indent += 1;
402
403 for (list) |inst| {
404 try state.newline(writer);
405 try state.dumpRefMarker(writer, inst.toRef());
406 }
407
408 state.indent -= 1;
409 try state.newline(writer);
410 }
411 try writer.print("{c}", .{r});
412 }
413
365 fn dumpRefList(state: *State, writer: anytype, list: []const Teg.Inst.Ref, l: u8, r: u8) !void {414 fn dumpRefList(state: *State, writer: anytype, list: []const Teg.Inst.Ref, l: u8, r: u8) !void {
366 try writer.print(" {c}", .{l});415 try writer.print(" {c}", .{l});
367 if (list.len > 0) {416 if (list.len > 0) {
...@@ -379,6 +428,29 @@ const State = struct {...@@ -379,6 +428,29 @@ const State = struct {
379 try writer.print("{c}", .{r});428 try writer.print("{c}", .{r});
380 }429 }
381430
431 fn dumpOptionalRefList(state: *State, writer: anytype, list: []const Teg.Inst.Ref.Optional, l: u8, r: u8) !void {
432 try writer.print(" {c}", .{l});
433 if (list.len > 0) {
434 state.indent += 1;
435
436 for (list) |optional_ref| {
437 try state.newline(writer);
438 if (optional_ref.unwrap()) |ref|
439 try state.dumpRef(writer, ref)
440 else {
441 try state.tty.setColor(writer, .bright_black);
442 try writer.writeAll("<inferred>");
443 try state.tty.setColor(writer, .reset);
444 }
445 }
446
447 state.indent -= 1;
448
449 try state.newline(writer);
450 }
451 try writer.print("{c}", .{r});
452 }
453
382 fn dumpRefMarkerList(state: *State, writer: anytype, list: []const Teg.Inst.Ref, l: u8, r: u8) !void {454 fn dumpRefMarkerList(state: *State, writer: anytype, list: []const Teg.Inst.Ref, l: u8, r: u8) !void {
383 try writer.print(" {c}", .{l});455 try writer.print(" {c}", .{l});
384 if (list.len > 0) {456 if (list.len > 0) {
src/core/Teg.zig+381-68
...@@ -10,12 +10,16 @@ pub const EncodedData = dod.EncodedData(Tag, u64);...@@ -10,12 +10,16 @@ pub const EncodedData = dod.EncodedData(Tag, u64);
10pub const ExtraIndex = ExtraStore.Index;10pub const ExtraIndex = ExtraStore.Index;
11pub const Dump = @import("Teg.Dump.zig");11pub const Dump = @import("Teg.Dump.zig");
1212
13pub const Tag = enum {13pub const Tag = enum(u8) {
14 /// Either the top level module or a `module` keyword. Contains a list of `.declarations`14 /// Either the top level module or a `module` keyword. Contains a list of `.declarations`
15 /// Every analyzed module gets a `Comp.Namespace`15 /// Every analyzed module gets a `Comp.Namespace`
16 module,16 module,
17 /// Smallest unit of re-compilation. Every analyzed declaration gets a `Comp.Decl`17 /// Smallest unit of re-compilation. Every analyzed declaration gets a `Comp.Decl`
18 declaration,18 declaration,
19 /// Top-level memory location
20 variable_declaration,
21 /// Test declaration
22 test_declaration,
19 /// Retrieves a declaration value via it's name. Since declarations are23 /// Retrieves a declaration value via it's name. Since declarations are
20 /// analyzed in isolation of each other, all references to instructions24 /// analyzed in isolation of each other, all references to instructions
21 /// outside of the current declaration must be in the form of this.25 /// outside of the current declaration must be in the form of this.
...@@ -30,6 +34,10 @@ pub const Tag = enum {...@@ -30,6 +34,10 @@ pub const Tag = enum {
30 memory,34 memory,
31 /// Re-assign a memory cell with a new ref.35 /// Re-assign a memory cell with a new ref.
32 store,36 store,
37 /// Re-assign a global variable decl with a new ref.
38 store_decl,
39 /// Re-assign a field with a new ref
40 store_field,
33 /// Performs a type coercion, such as in variable declarations.41 /// Performs a type coercion, such as in variable declarations.
34 /// The resulting Air Ref will be guaranteed to be the coerced type.42 /// The resulting Air Ref will be guaranteed to be the coerced type.
35 coercion,43 coercion,
...@@ -39,12 +47,14 @@ pub const Tag = enum {...@@ -39,12 +47,14 @@ pub const Tag = enum {
39 discard,47 discard,
40 /// Asserts the operand is `void`. Used for undiscarded expressions.48 /// Asserts the operand is `void`. Used for undiscarded expressions.
41 ensure_void,49 ensure_void,
42 /// Numeric constant.50
43 number,51 /// Branch takes a boolean condition and takes either a yes or no path.
44 /// String literal constant.
45 string,
46 /// Branch takes a boolean condition, and yes and no paths.
47 @"if",52 @"if",
53 /// Given a block instruction
54 @"comptime",
55
56 /// A function expression, with or without captures
57 func,
4858
49 // The following instructions are used to implement `Transform.ResultType`59 // The following instructions are used to implement `Transform.ResultType`
50 result_type_argument,60 result_type_argument,
...@@ -52,10 +62,17 @@ pub const Tag = enum {...@@ -52,10 +62,17 @@ pub const Tag = enum {
52 result_type_tuple_field,62 result_type_tuple_field,
53 result_type_array_value,63 result_type_array_value,
5464
55 struct_decl,65 /// Numeric constant.
56 struct_init,66 number,
67 /// String literal constant.
68 string,
5769
70 /// Constructs a unique struct type.
71 struct_decl,
72 /// Constructs an array type
58 array_type,73 array_type,
74
75 struct_init,
59 array_init,76 array_init,
6077
61 add,78 add,
...@@ -81,13 +98,14 @@ pub const Tag = enum {...@@ -81,13 +98,14 @@ pub const Tag = enum {
81 negate,98 negate,
82 bool_not,99 bool_not,
83 bit_not,100 bit_not,
101 assert,
84 field,102 field,
85 index,103 index,
86 call,104 call,
87 @"comptime",105 type_of,
106 /// Implements the `@extern` builtin
107 @"extern",
88108
89 /// Implements the `@js` builtin
90 js,
91 /// Implements the `@jsNew` builtin109 /// Implements the `@jsNew` builtin
92 js_new,110 js_new,
93 /// Implements the `@import` builtin111 /// Implements the `@import` builtin
...@@ -96,27 +114,32 @@ pub const Tag = enum {...@@ -96,27 +114,32 @@ pub const Tag = enum {
96 fetch,114 fetch,
97 /// Implements the `@compileError` builtin115 /// Implements the `@compileError` builtin
98 compile_error,116 compile_error,
99 /// Implements the `@typeOf` builtin
100 type_of,
101117
102 pub fn Data(comptime tag: Tag) type {118 pub fn Data(comptime tag: Tag) type {
103 return switch (tag) {119 return switch (tag) {
104 .module => PlNode(IndexList),120 .module, .block, .@"comptime" => PlNode(IndexList),
105 .declaration => PlNode(Decl),121
106 .block => PlNode(IndexList),122 .declaration,
123 .variable_declaration,
124 .test_declaration,
125 => PlNode(Decl),
107 .coercion => PlNode(Coercion),126 .coercion => PlNode(Coercion),
108 .@"break" => PlNode(Break),127 .@"break" => PlNode(Break),
109 .number => PlNode(Float),128 .number => PlNode(Float),
110 .string => PlNode(SlicedString),129 .string => PlNode(SlicedString),
111 .call => PlNode(Call),130 .call => PlNode(Call),
112 .@"if" => PlNode(If),131 .@"if" => PlNode(If),
113 .load_decl => StrNode,
114 .param => DataNode,
115 .store => PlNode(Store),132 .store => PlNode(Store),
133 .store_decl => PlNode(StoreVarDecl),
134 .store_field => PlNode(StoreField),
116 .js_new => PlNode(JsNew),135 .js_new => PlNode(JsNew),
117 .struct_decl => PlNode(StructDecl),136 .struct_decl => PlNode(StructDecl),
118 .struct_init => PlNode(StructInit),137 .struct_init => PlNode(StructInit),
119 .array_init => PlNode(ArrayInit),138 .array_init => PlNode(ArrayInit),
139 .func => PlNode(Func),
140
141 .load_decl => StrNode,
142 .param => DataNode,
120143
121 .result_type_argument => ResultTypeArgument,144 .result_type_argument => ResultTypeArgument,
122 .result_type_struct_field => ResultTypeStructField,145 .result_type_struct_field => ResultTypeStructField,
...@@ -130,13 +153,13 @@ pub const Tag = enum {...@@ -130,13 +153,13 @@ pub const Tag = enum {
130 .negate,153 .negate,
131 .bool_not,154 .bool_not,
132 .bit_not,155 .bit_not,
133 .js,156 .@"extern",
134 .import,157 .import,
135 .fetch,158 .fetch,
136 .compile_error,159 .compile_error,
137 .@"comptime",
138 .type_of,160 .type_of,
139 .array_type,161 .array_type,
162 .assert,
140 => RefNode,163 => RefNode,
141164
142 // Two instruction operands165 // Two instruction operands
...@@ -241,11 +264,11 @@ pub const Inst = struct {...@@ -241,11 +264,11 @@ pub const Inst = struct {
241 int32_type,264 int32_type,
242 float_type,265 float_type,
243 string_type,266 string_type,
244 noreturn_type,267 never_type,
245 type_type,268 type_type,
246 numeric_literal_type,269 numeric_literal_type,
247 numeric_literal_array_type,270 numeric_literal_array_type,
248 noreturn_array_type,271 never_array_type,
249272
250 void,273 void,
251 bool_true,274 bool_true,
...@@ -315,6 +338,17 @@ pub const Store = struct {...@@ -315,6 +338,17 @@ pub const Store = struct {
315 operand: Inst.Ref,338 operand: Inst.Ref,
316};339};
317340
341pub const StoreVarDecl = struct {
342 var_decl: NullTerminatedString,
343 operand: Inst.Ref,
344};
345
346pub const StoreField = struct {
347 base: Inst.Ref,
348 field: Inst.Ref,
349 operand: Inst.Ref,
350};
351
318pub const Coercion = struct {352pub const Coercion = struct {
319 type: Inst.Ref,353 type: Inst.Ref,
320 operand: Inst.Ref,354 operand: Inst.Ref,
...@@ -389,25 +423,19 @@ pub const ArrayInit = struct {...@@ -389,25 +423,19 @@ pub const ArrayInit = struct {
389 list: RefList,423 list: RefList,
390};424};
391425
392/// This will contain trailing data depending on the .kind
393pub const Decl = struct {426pub const Decl = struct {
394 name: NullTerminatedString,427 name: NullTerminatedString,
395 hash: Cache.BinDigest,428 hash: Cache.BinDigest,
396 kind: Kind,
397 instruction_count: u32,429 instruction_count: u32,
398 body: Inst.Ref,430 body: Inst.Ref,
399
400 pub const Kind = enum {
401 // TODO: remove function, see #45
402 function,
403 immutable,
404 };
405};431};
406432
407/// Trailing data for function declarations433pub const Func = struct {
408pub const DeclFn = struct {434 params: OptionalRefList,
409 params: RefList,
410 return_type: Inst.Ref.Optional,435 return_type: Inst.Ref.Optional,
436 body: Inst.Index,
437 captures: IndexList,
438 instruction_count: u32,
411};439};
412440
413pub const ResultTypeArgument = struct {441pub const ResultTypeArgument = struct {
...@@ -440,6 +468,7 @@ pub const ResultTypeArrayValue = struct {...@@ -440,6 +468,7 @@ pub const ResultTypeArrayValue = struct {
440// TODO: make this an enum468// TODO: make this an enum
441const NodeOffset = i32;469const NodeOffset = i32;
442470
471// TODO: remove InstOp. LazyNode should not have a decl
443pub const InstOp = struct {472pub const InstOp = struct {
444 index: Inst.Index,473 index: Inst.Index,
445 op: Report.Operand,474 op: Report.Operand,
...@@ -486,6 +515,15 @@ pub const RefList = struct {...@@ -486,6 +515,15 @@ pub const RefList = struct {
486 }515 }
487};516};
488517
518pub const OptionalRefList = struct {
519 start: u32,
520 len: u32,
521
522 pub fn slice(self: OptionalRefList, teg: *const Teg) []const Inst.Ref.Optional {
523 return @ptrCast(teg.extra.list.items[self.start..][0..self.len]);
524 }
525};
526
489pub const IndexList = struct {527pub const IndexList = struct {
490 start: u32,528 start: u32,
491 len: u32,529 len: u32,
...@@ -524,6 +562,10 @@ pub fn instData(teg: *const Teg, comptime tag: Teg.Tag, index: Inst.Index) tag.D...@@ -524,6 +562,10 @@ pub fn instData(teg: *const Teg, comptime tag: Teg.Tag, index: Inst.Index) tag.D
524 return teg.instEncodedData(index).decode(tag, teg.instTag(index));562 return teg.instEncodedData(index).decode(tag, teg.instTag(index));
525}563}
526564
565pub fn instDataUnsafe(teg: *const Teg, comptime T: type, index: Inst.Index) T {
566 return teg.instEncodedData(index).decodeUnsafe(T, teg.instTag(index));
567}
568
527pub fn instPayload(teg: *const Teg, comptime tag: Teg.Tag, index: Inst.Index) tag.Data().T {569pub fn instPayload(teg: *const Teg, comptime tag: Teg.Tag, index: Inst.Index) tag.Data().T {
528 return teg.instData(tag, index).extra_index.get(teg);570 return teg.instData(tag, index).extra_index.get(teg);
529}571}
...@@ -554,52 +596,31 @@ pub fn fullFuncDecl(teg: *const Teg, index: Inst.Index) struct { Teg.Decl, Teg.D...@@ -554,52 +596,31 @@ pub fn fullFuncDecl(teg: *const Teg, index: Inst.Index) struct { Teg.Decl, Teg.D
554 return .{ decl, decl_fn };596 return .{ decl, decl_fn };
555}597}
556598
557pub const FullDecl = struct {599pub fn fullDecl(teg: *const Teg, index: Inst.Index) Decl {
558 name: NullTerminatedString,600 const decl = teg.instEncodedData(index).decodeUnsafe(PlNode(Decl), teg.instTag(index)).extra_index.get(teg);
559 hash: Cache.BinDigest,
560 instruction_count: u32,
561 data: Data,
562 body: Inst.Ref,
563
564 const Data = union(enum) {
565 function: Teg.DeclFn,
566 immutable: struct {},
567 };
568};
569
570pub fn fullDecl(teg: *const Teg, index: Inst.Index) FullDecl {
571 const decl, var trail = teg.instData(.declaration, index).extra_index.getTrail(teg);
572 return .{601 return .{
573 .name = decl.name,602 .name = decl.name,
574 .hash = decl.hash,603 .hash = decl.hash,
575 .instruction_count = decl.instruction_count,604 .instruction_count = decl.instruction_count,
576 .body = decl.body,605 .body = decl.body,
577 .data = switch (decl.kind) {
578 inline else => |tag| @unionInit(
579 FullDecl.Data,
580 @tagName(tag),
581 trail.read(&teg.extra, std.meta.FieldType(
582 FullDecl.Data,
583 @field(std.meta.FieldEnum(FullDecl.Data), @tagName(tag)),
584 )),
585 ),
586 },
587 };606 };
588}607}
589608
590pub fn declData(teg: *const Teg, index: Inst.Index) Teg.Decl {609pub fn declData(teg: *const Teg, index: Inst.Index) Teg.Decl {
591 return teg.instData(.declaration, index).extra_index.get(teg);610 return teg.instDataUnsafe(PlNode(Decl), index).extra_index.get(teg);
592}611}
593612
594pub fn moduleDeclarations(teg: *const Teg, module_index: Inst.Index) []const Inst.Index {613pub fn moduleDeclarations(teg: *const Teg, module_index: Inst.Index) []const Inst.Index {
595 const module = teg.instData(.module, module_index);614 const module = teg.instData(.module, module_index);
596 const module_data: IndexList = module.extra_index.get(teg);615 const module_data: IndexList = module.extra_index.get(teg);
597 const slice = module_data.slice(teg);616 const slice = module_data.slice(teg);
598 if (std.debug.runtime_safety) {617 if (core.runtime_safety) {
599 for (slice) |item| {618 for (slice) |item| {
600 if (teg.instTag(item) != .declaration) {619 switch (teg.instTag(item)) {
601 std.debug.print("\ngot unknown tag {s}\n", .{@tagName(teg.instTag(item))});620 .declaration, .variable_declaration, .test_declaration => {},
602 unreachable;621 else => {
622 std.debug.panic("\ngot unknown tag {s}\n", .{@tagName(teg.instTag(item))});
623 },
603 }624 }
604 }625 }
605 }626 }
...@@ -614,8 +635,8 @@ pub fn rootFunction(teg: *const Teg) Inst.Index {...@@ -614,8 +635,8 @@ pub fn rootFunction(teg: *const Teg) Inst.Index {
614}635}
615636
616pub fn declName(teg: *const Teg, decl_index: Inst.Index) []const u8 {637pub fn declName(teg: *const Teg, decl_index: Inst.Index) []const u8 {
617 const decl = teg.instData(.declaration, decl_index);638 const decl = teg.instDataUnsafe(PlNode(Decl), decl_index);
618 const decl_data: Teg.Decl = decl.extra_index.get(teg);639 const decl_data: Decl = decl.extra_index.get(teg);
619 return decl_data.name.slice(teg.string_bytes);640 return decl_data.name.slice(teg.string_bytes);
620}641}
621642
...@@ -623,11 +644,306 @@ pub fn dumpToWriter(teg: Teg, writer: anytype, tty: ?std.io.tty.Config) !void {...@@ -623,11 +644,306 @@ pub fn dumpToWriter(teg: Teg, writer: anytype, tty: ?std.io.tty.Config) !void {
623 try writer.print("{}", .{Dump{ .teg = teg, .tty = tty }});644 try writer.print("{}", .{Dump{ .teg = teg, .tty = tty }});
624}645}
625646
647/// Used for generating decl hashes
648fn isolatedRefValue(decl_index: Inst.Index, ref_to_hash: Inst.Ref) i32 {
649 return switch (ref_to_hash.decode()) {
650 .value => |value| -@as(i32, @intCast(@intFromEnum(value))),
651 .index => |index| @intCast(@intFromEnum(index) - @intFromEnum(decl_index)),
652 };
653}
654
655/// Used for generating decl hashes
656fn isolatedIndexValue(decl_index: Inst.Index, index_to_hash: Inst.Index) u32 {
657 return @intFromEnum(index_to_hash) - @intFromEnum(decl_index);
658}
659
660pub fn writeToHasher(
661 teg: *const Teg,
662 hasher: *HashHelper,
663 tag_list: []Tag,
664 data_list: []EncodedData,
665 decl_index: Inst.Index,
666) void {
667 // Add all of the tags
668 hasher.addBytes(std.mem.sliceAsBytes(tag_list));
669
670 // Add all of the data
671 for (tag_list, data_list) |tag, data| {
672
673 // This should roughly line up with Teg.Tag.Data
674 switch (tag) {
675 // PlNode(IndexList),
676 .module,
677 .block,
678 .@"comptime",
679 => {
680 const pl_node = data.decodeUnsafe(PlNode(IndexList), tag);
681 hasher.add(pl_node.src_node);
682 writeIndexListToHasher(hasher, pl_node.extra_index.get(teg).slice(teg), decl_index);
683 },
684
685 // PlNode
686 inline .declaration,
687 .variable_declaration,
688 .test_declaration,
689 .coercion,
690 .@"break",
691 .number,
692 .string,
693 .call,
694 .@"if",
695 .store,
696 .js_new,
697 .struct_decl,
698 .struct_init,
699 .array_init,
700 .func,
701 .store_decl,
702 .store_field,
703 => |pl_node_tag| {
704 const pl_node = data.decode(pl_node_tag, tag);
705 hasher.add(pl_node.src_node);
706 teg.writePlNodeInnerToHasher(hasher, pl_node_tag.Data().T, pl_node.extra_index.get(teg), decl_index);
707 },
708 // StrNode
709 .load_decl => {
710 const str_node = data.decodeUnsafe(StrNode, tag);
711 hasher.add(str_node.src_node);
712 hasher.addBytes(str_node.string.slice(teg.string_bytes));
713 },
714 // DataNode
715 .param => {
716 const data_node = data.decodeUnsafe(DataNode, tag);
717 hasher.add(data_node.src_node);
718 hasher.add(data_node.data);
719 },
720 // RefNode
721 .memory,
722 .discard,
723 .ensure_void,
724 .negate,
725 .bool_not,
726 .bit_not,
727 .@"extern",
728 .import,
729 .fetch,
730 .compile_error,
731 .type_of,
732 .array_type,
733 .assert,
734 => {
735 const ref_node = data.decodeUnsafe(RefNode, tag);
736 hasher.add(ref_node.src_node);
737 hasher.add(isolatedRefValue(decl_index, ref_node.operand));
738 },
739 // PlNode(Bin)
740 .field,
741 .index,
742 .add,
743 .sub,
744 .mul,
745 .div,
746 .rem,
747 .pow,
748 .@"and",
749 .@"or",
750 .bit_and,
751 .bit_or,
752 .bit_xor,
753 .shl,
754 .shr,
755 .eq,
756 .ne,
757 .lt,
758 .le,
759 .gt,
760 .ge,
761 => {
762 const pl_node = data.decodeUnsafe(PlNode(Bin), tag);
763 const bin = pl_node.extra_index.get(teg);
764 hasher.add(isolatedRefValue(decl_index, bin.lhs));
765 hasher.add(isolatedRefValue(decl_index, bin.rhs));
766 },
767 // Result Types
768 .result_type_argument => {
769 const arg = data.decodeUnsafe(ResultTypeArgument, tag);
770 hasher.add(isolatedRefValue(decl_index, arg.call_base));
771 hasher.add(arg.argument);
772 },
773 .result_type_struct_field => {
774 const struct_field = data.decodeUnsafe(ResultTypeStructField, tag);
775 hasher.add(isolatedRefValue(decl_index, struct_field.type));
776 hasher.addBytes(struct_field.field.slice(teg.string_bytes));
777 },
778 .result_type_tuple_field => {
779 const tuple_field = data.decodeUnsafe(ResultTypeTupleField, tag);
780 hasher.add(isolatedRefValue(decl_index, tuple_field.type));
781 hasher.add(tuple_field.item);
782 },
783 .result_type_array_value => {
784 const array_value = data.decodeUnsafe(ResultTypeArrayValue, tag);
785 hasher.add(isolatedRefValue(decl_index, array_value.type));
786 },
787 }
788 }
789}
790
791fn writeIndexListToHasher(
792 hasher: *HashHelper,
793 index_list: []const Inst.Index,
794 decl_index: Inst.Index,
795) void {
796 hasher.add(index_list.len);
797 for (index_list) |idx| {
798 hasher.add(isolatedIndexValue(decl_index, idx));
799 }
800}
801
802fn writeRefListToHasher(
803 hasher: *HashHelper,
804 decl_index: Inst.Index,
805 index_list: []const Inst.Ref,
806) void {
807 hasher.add(index_list.len);
808 for (index_list) |idx| {
809 hasher.add(isolatedRefValue(decl_index, idx));
810 }
811}
812
813fn writePlNodeInnerToHasher(
814 teg: *const Teg,
815 hasher: *HashHelper,
816 comptime T: type,
817 data: T,
818 decl_index: Inst.Index,
819) void {
820 switch (T) {
821 Decl => {
822 hasher.addBytes(&data.hash);
823 hasher.addBytes(data.name.slice(teg.string_bytes));
824 },
825 Float => {
826 const as_bytes: [2]u32 = .{
827 data.low,
828 data.high,
829 };
830 hasher.add(@as(u64, @bitCast(as_bytes)));
831 },
832 SlicedString => {
833 hasher.addBytes(data.slice(teg.string_bytes));
834 },
835 Coercion => {
836 hasher.add(isolatedRefValue(decl_index, data.type));
837 hasher.add(isolatedRefValue(decl_index, data.operand));
838 },
839 Break => {
840 hasher.add(isolatedIndexValue(decl_index, data.block));
841 hasher.add(isolatedRefValue(decl_index, data.operand));
842 },
843 Call => {
844 hasher.add(isolatedRefValue(decl_index, data.base));
845 writeRefListToHasher(hasher, decl_index, data.args.slice(teg));
846 },
847 If => {
848 hasher.add(isolatedRefValue(decl_index, data.cond));
849 hasher.add(isolatedRefValue(decl_index, data.yes));
850 hasher.add(isolatedRefValue(decl_index, data.no));
851 },
852 Store => {
853 hasher.add(isolatedIndexValue(decl_index, data.memory));
854 hasher.add(isolatedRefValue(decl_index, data.operand));
855 },
856 StoreField => {
857 hasher.add(isolatedRefValue(decl_index, data.base));
858 hasher.add(isolatedRefValue(decl_index, data.field));
859 hasher.add(isolatedRefValue(decl_index, data.operand));
860 },
861 StoreVarDecl => {
862 hasher.addBytes(data.var_decl.slice(teg.string_bytes));
863 hasher.add(isolatedRefValue(decl_index, data.operand));
864 },
865 JsNew => {
866 hasher.add(isolatedRefValue(decl_index, data.base));
867 writeRefListToHasher(hasher, decl_index, data.args.slice(teg));
868 },
869 StructDecl => {
870 hasher.add(data.field_len);
871 for (data.keys(teg), data.types(teg)) |key, ty| {
872 hasher.addBytes(key.slice(teg.string_bytes));
873 hasher.add(isolatedRefValue(decl_index, ty));
874 }
875 },
876 StructInit => {
877 hasher.add(isolatedRefValue(decl_index, data.base_type));
878 hasher.add(data.field_len);
879 for (data.keys(teg), data.inits(teg)) |key, init| {
880 hasher.addBytes(key.slice(teg.string_bytes));
881 hasher.add(isolatedRefValue(decl_index, init));
882 }
883 },
884 ArrayInit => {
885 if (data.type_hint.unwrap()) |type_hint|
886 hasher.add(isolatedRefValue(decl_index, type_hint))
887 else
888 hasher.add(@as(i32, std.math.maxInt(i32)));
889 writeRefListToHasher(hasher, decl_index, data.list.slice(teg));
890 },
891 Func => {
892 for (data.params.slice(teg)) |optional| {
893 if (optional.unwrap()) |item|
894 hasher.add(isolatedRefValue(decl_index, item));
895 }
896
897 if (data.return_type.unwrap()) |type_hint|
898 hasher.add(isolatedRefValue(decl_index, type_hint))
899 else
900 hasher.add(@as(i32, std.math.maxInt(i32)));
901
902 hasher.add(isolatedIndexValue(decl_index, data.body));
903 hasher.add(data.instruction_count);
904 },
905 else => @compileError("hash not implemented for " ++ @typeName(T)),
906 }
907}
908
909pub const SerializedHeader = extern struct {
910 const version = @import("config").version_string ++ "\x00";
911
912 instruction_len: u32,
913 string_bytes: u32,
914 extra_len: u32,
915 report_err_len: u32,
916 report_warn_len: u32,
917};
918
919pub fn serialize(teg: Teg, target: core.SerializeTarget) !void {
920 const header: SerializedHeader = .{
921 .instruction_len = @intCast(teg.insts.len),
922 .string_bytes = @intCast(teg.string_bytes.len),
923 .extra_len = @intCast(teg.extra.list.items.len),
924 .report_err_len = @intCast(teg.report.errors.len),
925 .report_warn_len = @intCast(teg.report.warnings.len),
926 };
927
928 var iovecs = [_]std.posix.iovec_const{
929 core.iovecConst(SerializedHeader.version),
930 core.iovecConst(std.mem.asBytes(&header)),
931 core.iovecConst(std.mem.sliceAsBytes(teg.insts.items(.tag))),
932 core.iovecConst(std.mem.sliceAsBytes(teg.insts.items(.data))),
933 core.iovecConst(teg.string_bytes),
934 core.iovecConst(std.mem.sliceAsBytes(teg.extra.list.items)),
935 core.iovecConst(std.mem.sliceAsBytes(teg.report.errors)),
936 core.iovecConst(std.mem.sliceAsBytes(teg.report.warnings)),
937 };
938 try target.writev(&iovecs);
939}
940
626const Teg = @This();941const Teg = @This();
627942
628const std = @import("std");943const std = @import("std");
629const mem = std.mem;944const mem = std.mem;
630const assert = std.debug.assert;945const assert = std.debug.assert;
946const HashHelper = std.Build.Cache.HashHelper;
631947
632const core = @import("mod.zig");948const core = @import("mod.zig");
633const Cache = core.Cache;949const Cache = core.Cache;
...@@ -639,9 +955,6 @@ const TokenIndex = Ast.TokenIndex;...@@ -639,9 +955,6 @@ const TokenIndex = Ast.TokenIndex;
639const OptionalNodeIndex = Ast.OptionalNodeIndex;955const OptionalNodeIndex = Ast.OptionalNodeIndex;
640const OptionalTokenIndex = Ast.OptionalTokenIndex;956const OptionalTokenIndex = Ast.OptionalTokenIndex;
641const OptionalExtraIndex = Ast.OptionalExtraIndex;957const OptionalExtraIndex = Ast.OptionalExtraIndex;
642const optionalTokenIndex = OptionalTokenIndex.init;
643const optionalNodeIndex = OptionalNodeIndex.init;
644const optionalExtraIndex = OptionalExtraIndex.init;
645958
646const dod = core.array;959const dod = core.array;
647const Float = dod.Float;960const Float = dod.Float;
src/core/Tokenizer.zig+93-52
...@@ -9,13 +9,8 @@ code: [:0]const u8,...@@ -9,13 +9,8 @@ code: [:0]const u8,
9pos: ByteOffset = 0,9pos: ByteOffset = 0,
1010
11/// Tokenize source code into a list of tokens.11/// Tokenize source code into a list of tokens.
12/// When an error occurs, the position of the error is returned in `error_pos`.
13/// Caller owns returned slice.12/// Caller owns returned slice.
14pub fn tokenize(13pub fn tokenize(source_code: [:0]const u8, gpa: mem.Allocator) mem.Allocator.Error!Token.List.Slice {
15 source_code: [:0]const u8,
16 gpa: mem.Allocator,
17 error_pos: *ByteOffset,
18) TokenizeError!Token.List.Slice {
19 const trace = T.trace(@src());14 const trace = T.trace(@src());
20 defer trace.end();15 defer trace.end();
2116
...@@ -25,11 +20,10 @@ pub fn tokenize(...@@ -25,11 +20,10 @@ pub fn tokenize(
25 // pre-allocate some space based on a very rough guess20 // pre-allocate some space based on a very rough guess
26 try tokens.ensureTotalCapacity(gpa, @divFloor(source_code.len, 8));21 try tokens.ensureTotalCapacity(gpa, @divFloor(source_code.len, 8));
2722
28 var tokenizer: Tokenizer = .{ .code = source_code, .pos = 0 };23 var tokenizer: Tokenizer = .{ .code = source_code };
29 errdefer error_pos.* = tokenizer.pos;
3024
31 tokenizer.skipWhitespace();25 tokenizer.skipWhitespace();
32 while (try tokenizer.next()) |token| {26 while (tokenizer.next()) |token| {
33 try tokens.append(gpa, token);27 try tokens.append(gpa, token);
34 tokenizer.skipWhitespace();28 tokenizer.skipWhitespace();
35 }29 }
...@@ -142,6 +136,11 @@ pub const Token = struct {...@@ -142,6 +136,11 @@ pub const Token = struct {
142 /// tokenization, parsing will begin, and an error will be reported when136 /// tokenization, parsing will begin, and an error will be reported when
143 /// this token is hit. That way, the error can tell you what it expected.137 /// this token is hit. That way, the error can tell you what it expected.
144 invalid,138 invalid,
139 invalid_number,
140 invalid_string,
141 invalid_unterminated_string,
142 invalid_git_conflict,
143 invalid_utf8,
145 /// End of file144 /// End of file
146 /// Used so that parser does not need to do out of bounds checks145 /// Used so that parser does not need to do out of bounds checks
147 eof,146 eof,
...@@ -149,7 +148,16 @@ pub const Token = struct {...@@ -149,7 +148,16 @@ pub const Token = struct {
149 /// Most tokens have statically known lexemes, such as "struct" or "+="148 /// Most tokens have statically known lexemes, such as "struct" or "+="
150 pub fn lexeme(tag: Tag) ?[]const u8 {149 pub fn lexeme(tag: Tag) ?[]const u8 {
151 return switch (tag) {150 return switch (tag) {
152 .invalid => unreachable,151 .eof => unreachable, // always must be special cased
152
153 // invalid sequences do not have static lexemes
154 .invalid,
155 .invalid_number,
156 .invalid_string,
157 .invalid_unterminated_string,
158 .invalid_git_conflict,
159 .invalid_utf8,
160 => null,
153161
154 // do not have a static lexeme162 // do not have a static lexeme
155 .id,163 .id,
...@@ -162,8 +170,6 @@ pub const Token = struct {...@@ -162,8 +170,6 @@ pub const Token = struct {
162 .module_doc_comment,170 .module_doc_comment,
163 => null,171 => null,
164172
165 .eof => unreachable, // always must be special cased
166
167 .@"and" => "&",173 .@"and" => "&",
168 .and_and => "&&",174 .and_and => "&&",
169 .bang => "!",175 .bang => "!",
...@@ -226,8 +232,15 @@ pub const Token = struct {...@@ -226,8 +232,15 @@ pub const Token = struct {
226232
227 pub fn description(tag: Tag) []const u8 {233 pub fn description(tag: Tag) []const u8 {
228 return switch (tag) {234 return switch (tag) {
229 .invalid => unreachable,
230 .eof => "end of file",235 .eof => "end of file",
236
237 .invalid => "unknown character",
238 .invalid_number => "invalid number",
239 .invalid_string => "invalid string literal",
240 .invalid_unterminated_string => "unterminated string literal",
241 .invalid_git_conflict => "git merge conflict marker",
242 .invalid_utf8 => "invalid UTF-8 bytes",
243
231 .id => "an identifier",244 .id => "an identifier",
232 .builtin_id => "a builtin identifier",245 .builtin_id => "a builtin identifier",
233 .number_int => "a numeric literal",246 .number_int => "a numeric literal",
...@@ -242,7 +255,7 @@ pub const Token = struct {...@@ -242,7 +255,7 @@ pub const Token = struct {
242255
243 pub fn isKeyword(tag: Tag) bool {256 pub fn isKeyword(tag: Tag) bool {
244 return switch (tag) {257 return switch (tag) {
245 inline else => |t| mem.startsWith(u8, @tagName(t), "kw_"),258 inline else => |t| comptime mem.startsWith(u8, @tagName(t), "kw_"),
246 };259 };
247 }260 }
248261
...@@ -250,6 +263,13 @@ pub const Token = struct {...@@ -250,6 +263,13 @@ pub const Token = struct {
250 pub fn staticLen(tag: Tag) ?u32 {263 pub fn staticLen(tag: Tag) ?u32 {
251 return if (tag.lexeme()) |str| @intCast(str.len) else null;264 return if (tag.lexeme()) |str| @intCast(str.len) else null;
252 }265 }
266
267 pub fn isInvalid(tag: Tag) bool {
268 return switch (tag) {
269 .invalid => true,
270 inline else => |t| comptime mem.startsWith(u8, @tagName(t), "invalid_"),
271 };
272 }
253 };273 };
254274
255 /// Instead of storing the length of a token, we can recompute this275 /// Instead of storing the length of a token, we can recompute this
...@@ -258,15 +278,27 @@ pub const Token = struct {...@@ -258,15 +278,27 @@ pub const Token = struct {
258 /// If you are computing the length of something that is knowable278 /// If you are computing the length of something that is knowable
259 /// without the source code, use token.tag.staticLen() instead.279 /// without the source code, use token.tag.staticLen() instead.
260 pub fn len(token: Token, utf8_source: [:0]const u8) u32 {280 pub fn len(token: Token, utf8_source: [:0]const u8) u32 {
261 return token.tag.staticLen() orelse {281 return token.tag.staticLen() orelse switch (token.tag) {
262 var tokenizer = Tokenizer{ .code = utf8_source, .pos = token.offset };282
263 assert(283 // TODO: calling len on these tokens is incorrect, as
264 (tokenizer.next() catch284 // error handling code should special case these all
265 unreachable // data corruption285 // to highlight exactly what went wrong.
266 orelse unreachable // data corruption286 .invalid => std.unicode.utf8ByteSequenceLength(utf8_source[token.offset]) catch 1,
267 ).tag == token.tag,287 .invalid_git_conflict => 7,
268 );288 .invalid_utf8 => 1,
269 return tokenizer.pos - token.offset;289
290 else => {
291 @branchHint(.likely);
292 var tokenizer = Tokenizer{ .code = utf8_source, .pos = token.offset };
293 const retokenized = tokenizer.next() orelse unreachable; // data corruption
294 if (retokenized.tag != token.tag and !token.tag.isInvalid()) {
295 std.debug.panic(
296 "len() got wrong tag when re-tokenizing: .{s} != .{s}",
297 .{ @tagName(retokenized.tag), @tagName(token.tag) },
298 );
299 }
300 return tokenizer.pos - token.offset;
301 },
270 };302 };
271 }303 }
272304
...@@ -275,7 +307,7 @@ pub const Token = struct {...@@ -275,7 +307,7 @@ pub const Token = struct {
275 utf8_source[token.offset..][0..token.len(utf8_source)];307 utf8_source[token.offset..][0..token.len(utf8_source)];
276 }308 }
277309
278 const keywords = std.StaticStringMap(Tag).initComptime(keywords: {310 pub const keywords = std.StaticStringMap(Tag).initComptime(keywords: {
279 const fields = std.enums.values(Tag);311 const fields = std.enums.values(Tag);
280 const KV = struct { []const u8, Tag };312 const KV = struct { []const u8, Tag };
281 var kws: [fields.len]KV = undefined;313 var kws: [fields.len]KV = undefined;
...@@ -290,8 +322,8 @@ pub const Token = struct {...@@ -290,8 +322,8 @@ pub const Token = struct {
290 });322 });
291};323};
292324
293/// When any of these errors occurs, the tokenizer's position is set to325/// When any of these errors occurs, the tokenizer's
294/// the start of the offending token.326/// position is set to the start of the offending bytes.
295pub const NextTokenError = error{327pub const NextTokenError = error{
296 /// Generic token syntax error328 /// Generic token syntax error
297 InvalidSyntax,329 InvalidSyntax,
...@@ -310,11 +342,34 @@ pub const NextTokenError = error{...@@ -310,11 +342,34 @@ pub const NextTokenError = error{
310 InvalidUtf8,342 InvalidUtf8,
311};343};
312344
313pub const TokenizeError = mem.Allocator.Error || NextTokenError;
314
315/// Next assumes the position is already at the start of a token.345/// Next assumes the position is already at the start of a token.
316/// You must call `skipWhitespace` before calling this.346/// You must call `skipWhitespace` before calling this.
317pub fn next(t: *Tokenizer) !?Token {347pub fn next(t: *Tokenizer) ?Token {
348 return t.nextErrorable() catch |err| {
349 defer {
350 t.pos = @intCast(mem.indexOfScalarPos(
351 u8,
352 t.code,
353 t.pos + 3,
354 '\n',
355 ) orelse t.code.len);
356 }
357 return .{
358 .offset = t.pos,
359 .tag = switch (err) {
360 error.InvalidSyntax => .invalid,
361 error.TrailingNumericSeparator => .invalid_number,
362 error.UnterminatedString => .invalid_unterminated_string,
363 error.InvalidEscapeSequence => .invalid_string,
364 error.InvalidEscapeCodepoint => .invalid_string,
365 error.GitConflict => .invalid_git_conflict,
366 error.InvalidUtf8 => .invalid_utf8,
367 },
368 };
369 };
370}
371
372pub fn nextErrorable(t: *Tokenizer) NextTokenError!?Token {
318 while (true) {373 while (true) {
319 const trace = T.trace(@src());374 const trace = T.trace(@src());
320 defer trace.end();375 defer trace.end();
...@@ -935,7 +990,7 @@ fn simpleToken(t: *Tokenizer, comptime tag: Token.Tag) Token {...@@ -935,7 +990,7 @@ fn simpleToken(t: *Tokenizer, comptime tag: Token.Tag) Token {
935/// However, the character we ran into might actually be invalid UTF-8.990/// However, the character we ran into might actually be invalid UTF-8.
936/// This utility just checks for that to make sure we don't mask error.InvalidUtf8991/// This utility just checks for that to make sure we don't mask error.InvalidUtf8
937fn maybeUTF8Error(t: *Tokenizer, comptime real_err: anytype) @Type(.{992fn maybeUTF8Error(t: *Tokenizer, comptime real_err: anytype) @Type(.{
938 .ErrorSet = &[_]std.builtin.Type.Error{993 .error_set = &[_]std.builtin.Type.Error{
939 .{ .name = "InvalidUtf8" },994 .{ .name = "InvalidUtf8" },
940 .{ .name = @errorName(real_err) },995 .{ .name = @errorName(real_err) },
941 },996 },
...@@ -950,7 +1005,7 @@ fn maybeUTF8Error(t: *Tokenizer, comptime real_err: anytype) @Type(.{...@@ -950,7 +1005,7 @@ fn maybeUTF8Error(t: *Tokenizer, comptime real_err: anytype) @Type(.{
950}1005}
9511006
952fn maybeUTF8ErrorAt(t: *Tokenizer, comptime real_err: anytype, at: u32) @Type(.{1007fn maybeUTF8ErrorAt(t: *Tokenizer, comptime real_err: anytype, at: u32) @Type(.{
953 .ErrorSet = &[_]std.builtin.Type.Error{1008 .error_set = &[_]std.builtin.Type.Error{
954 .{ .name = "InvalidUtf8" },1009 .{ .name = "InvalidUtf8" },
955 .{ .name = @errorName(real_err) },1010 .{ .name = @errorName(real_err) },
956 },1011 },
...@@ -1240,10 +1295,10 @@ test "basic tokenizer example" {...@@ -1240,10 +1295,10 @@ test "basic tokenizer example" {
1240 var tokenizer = Tokenizer{ .code = source };1295 var tokenizer = Tokenizer{ .code = source };
1241 for (expected_tokens) |tok| {1296 for (expected_tokens) |tok| {
1242 tokenizer.skipWhitespace();1297 tokenizer.skipWhitespace();
1243 try testing.expectEqual(tok, (try tokenizer.next()).?);1298 try testing.expectEqual(tok, tokenizer.next().?);
1244 }1299 }
1245 tokenizer.skipWhitespace();1300 tokenizer.skipWhitespace();
1246 try testing.expectEqual(null, (try tokenizer.next()));1301 try testing.expectEqual(null, tokenizer.next());
1247}1302}
12481303
1249test "field access" {1304test "field access" {
...@@ -1254,25 +1309,11 @@ test "field access" {...@@ -1254,25 +1309,11 @@ test "field access" {
1254 \\.0 hello.1.21309 \\.0 hello.1.2
1255 ;1310 ;
1256 const expected_tokens = [_]Token{1311 const expected_tokens = [_]Token{
1257 .{ .tag = .kw_var, .offset = 0 },1312 .{ .tag = .dot, .offset = 0 },
1258 .{ .tag = .id, .offset = 4 },1313 .{ .tag = .number_int, .offset = 1 },
1259 .{ .tag = .equal, .offset = 10 },1314 .{ .tag = .id, .offset = 3 },
1260 .{ .tag = .number_float, .offset = 12 },1315 .{ .tag = .dot, .offset = 8 },
1261 .{ .tag = .number_int, .offset = 20 },1316 .{ .tag = .number_float, .offset = 9 },
1262 .{ .tag = .semi, .offset = 21 },
1263 .{ .tag = .kw_pub, .offset = 42 },
1264 .{ .tag = .kw_fn, .offset = 46 },
1265 .{ .tag = .id, .offset = 49 },
1266 .{ .tag = .start_paren, .offset = 54 },
1267 .{ .tag = .colon, .offset = 55 },
1268 .{ .tag = .plus, .offset = 56 },
1269 .{ .tag = .id, .offset = 58 },
1270 .{ .tag = .end_paren, .offset = 62 },
1271 .{ .tag = .id, .offset = 64 },
1272 .{ .tag = .kw_return, .offset = 67 },
1273 .{ .tag = .id, .offset = 74 },
1274 .{ .tag = .id, .offset = 77 },
1275 .{ .tag = .string, .offset = 86 },
1276 };1317 };
1277 var tokenizer = Tokenizer{ .code = source };1318 var tokenizer = Tokenizer{ .code = source };
1278 for (expected_tokens) |tok| {1319 for (expected_tokens) |tok| {
src/core/Transform.zig+681-381
...@@ -18,41 +18,46 @@ ast: Ast,...@@ -18,41 +18,46 @@ ast: Ast,
18// these values are allocated through `gpa`, so that resulting IR can be18// these values are allocated through `gpa`, so that resulting IR can be
19// trivially shrunk before returning.19// trivially shrunk before returning.
2020
21insts: Inst.List = .{},21insts: Inst.List,
22extra: ExtraStore = .{},22extra: ExtraStore,
23string_bytes: std.ArrayListUnmanaged(u8) = .{},23string_bytes: std.ArrayListUnmanaged(u8),
24report: Report.Builder,24report: Report.Builder,
2525
26// however, these are allocated through `arena`, since they are always freed at26// however, these are allocated through `arena`, since they are always freed at
27// the end of the pass.27// the end of the pass.
2828
29scratch: core.U32Stack = .{},29scratch: core.U32Stack,
30id_decode_buf: std.ArrayList(u8),30id_decode_buf: std.ArrayList(u8),
31scopes: std.ArrayListUnmanaged(Scope) = .{},31scopes: std.ArrayListUnmanaged(Scope),
32capture_scopes: std.ArrayListUnmanaged(CaptureScope),
33
34has_warned_top_level_statement: bool,
3235
33pub const Error = error{36pub const Error = error{
34 /// A `Report` will contain at least one error.37 /// A `Report` will contain at least one error.
35 TransformFail,38 TransformFail,
36} || Allocator.Error;39} || Allocator.Error;
3740
38pub fn transform(41pub fn transform(ast: Ast, gpa: Allocator, fail_report: *Report) Error!Teg {
39 ast: Ast,
40 gpa: Allocator,
41 file: *Comp.File,
42 fail_report: *Report,
43) Error!Teg {
44 const trace = tracer.trace(@src());42 const trace = tracer.trace(@src());
45 defer trace.end();43 defer trace.end();
4644
47 var sfa = std.heap.stackFallback(4096, gpa);45 var sfa = std.heap.stackFallback(4096, gpa);
4846
49 var t: Transform = .{47 var t: Transform = .{
50 .ast = ast,
51 .gpa = gpa,48 .gpa = gpa,
52 // TODO: Currently this is illusion of free will
53 .arena = gpa,49 .arena = gpa,
54 .report = .{ .gpa = gpa, .current_file = file },50 .ast = ast,
55 .id_decode_buf = std.ArrayList(u8).initCapacity(sfa.get(), 4096) catch unreachable,51 .insts = .{},
52 .extra = .empty,
53 .string_bytes = .empty,
54 .report = .init(gpa),
55 .scratch = .empty,
56 .scopes = .empty,
57 .capture_scopes = .empty,
58 .id_decode_buf = std.ArrayList(u8).initCapacity(sfa.get(), 4096) catch
59 unreachable, // stack-fallback has enough space
60 .has_warned_top_level_statement = false,
56 };61 };
57 defer t.deinit();62 defer t.deinit();
5863
...@@ -83,7 +88,7 @@ fn deinit(t: *Transform) void {...@@ -83,7 +88,7 @@ fn deinit(t: *Transform) void {
83 t.id_decode_buf.deinit();88 t.id_decode_buf.deinit();
84 t.scratch.list.deinit(t.arena);89 t.scratch.list.deinit(t.arena);
85 t.scopes.deinit(t.arena);90 t.scopes.deinit(t.arena);
86 t.id_decode_buf.deinit();91 t.capture_scopes.deinit(t.arena);
87 t.string_bytes.deinit(t.gpa);92 t.string_bytes.deinit(t.gpa);
88 t.insts.deinit(t.gpa);93 t.insts.deinit(t.gpa);
89 t.extra.deinit(t.gpa);94 t.extra.deinit(t.gpa);
...@@ -120,6 +125,9 @@ const ResultType = union(enum) {...@@ -120,6 +125,9 @@ const ResultType = union(enum) {
120 /// The expression is a field in an array initialization125 /// The expression is a field in an array initialization
121 array_value: Inst.Ref,126 array_value: Inst.Ref,
122127
128 // /// The expression is unknown field
129 // field: Teg.ResultTypeField,
130
123 /// This will return an instruction pointing to the the type131 /// This will return an instruction pointing to the the type
124 pub fn consume(result: *ResultType, t: *Transform) !?Inst.Ref {132 pub fn consume(result: *ResultType, t: *Transform) !?Inst.Ref {
125 if (result.* == .none) return null;133 if (result.* == .none) return null;
...@@ -150,7 +158,7 @@ const BlockContext = struct {...@@ -150,7 +158,7 @@ const BlockContext = struct {
150 break_target: ?*BreakTarget = null,158 break_target: ?*BreakTarget = null,
151159
152 const ReturnTarget = struct {160 const ReturnTarget = struct {
153 generated_block: ?Inst.Index = null,161 block_index: Inst.Index,
154 result_type: ResultType,162 result_type: ResultType,
155 };163 };
156164
...@@ -166,14 +174,6 @@ const BlockContext = struct {...@@ -166,14 +174,6 @@ const BlockContext = struct {
166 parent: ?*BreakTarget = null,174 parent: ?*BreakTarget = null,
167 result_type: *ResultType,175 result_type: *ResultType,
168176
169 fn addChild(parent: *BreakTarget, label: ?[]const u8, is_loop: bool) BreakTarget {
170 return .{
171 .parent = parent,
172 .label = label,
173 .is_loop = is_loop,
174 };
175 }
176
177 fn getInst(target: *BreakTarget, t: *Transform) !Inst.Index {177 fn getInst(target: *BreakTarget, t: *Transform) !Inst.Index {
178 return target.block_index orelse {178 return target.block_index orelse {
179 const inst = try t.addUntracked(.block, undefined);179 const inst = try t.addUntracked(.block, undefined);
...@@ -203,10 +203,11 @@ const BlockContext = struct {...@@ -203,10 +203,11 @@ const BlockContext = struct {
203 return .{203 return .{
204 .decl_node_offset = self.decl_node_offset,204 .decl_node_offset = self.decl_node_offset,
205 .return_target = return_target,205 .return_target = return_target,
206 .break_target = null,
206 };207 };
207 }208 }
208209
209 fn withBreak(self: BlockContext, break_target: *BreakTarget) BlockContext {210 fn withBreak(self: BlockContext, break_target: ?*BreakTarget) BlockContext {
210 return .{211 return .{
211 .decl_node_offset = self.decl_node_offset,212 .decl_node_offset = self.decl_node_offset,
212 .return_target = self.return_target,213 .return_target = self.return_target,
...@@ -215,78 +216,29 @@ const BlockContext = struct {...@@ -215,78 +216,29 @@ const BlockContext = struct {
215 }216 }
216217
217 fn lookupNamedBreak(self: BlockContext, label: []const u8) ?*BreakTarget {218 fn lookupNamedBreak(self: BlockContext, label: []const u8) ?*BreakTarget {
218 var break_target = self.break_target;219 var target = self.break_target orelse return null;
219 while (break_target) |target| : (break_target = target.parent) {220 while (true) {
220 if (target.label) |target_label| {221 if (target.label) |target_label| {
221 if (mem.eql(u8, label, target_label)) {222 if (mem.eql(u8, label, target_label)) {
222 return target;223 return target;
223 }224 }
224 }225 }
226
227 target = target.parent orelse return null;
225 }228 }
226 return null;
227 }229 }
228};230};
229231
232// TODO: optimize this structure
233//
234// If Transform does not need to track more than a symbol list for scope
235// (BlockContext kind of does this, so it's likely), this can be refactored to a
236// custom structure which is a single array of { Hash, Key, Symbol }, where each
237// block context stores the index into this array where it's map starts. Lookups
238// are done by scanning the map backwards for a hash, which would allow for
239// trivial memory re-use.
230const Scope = struct {240const Scope = struct {
231 symbols: std.StringArrayHashMapUnmanaged(Symbol) = .{},241 symbols: std.StringArrayHashMapUnmanaged(Symbol) = .empty,
232
233 // TODO: optimize this structure's size.
234 const Symbol = struct {
235 node: NodeIndex,
236 data: Symbol.Data,
237 used: bool = false,
238 mutated: bool = false,
239 discarded: ?NodeIndex = null,
240 type: SymbolResultType,
241
242 const Data = union(enum) {
243 declaration,
244 param,
245 mutable: Inst.Index,
246 immutable: Inst.Ref,
247 };
248
249 const SymbolResultType = union(enum) {
250 /// Points to the resolved result type (.ref), as is.
251 present: Inst.Ref,
252 /// This points to the value. Which means a `type_of` instruction
253 /// must be inserted before it may be used.
254 missing: Inst.Ref,
255
256 pub fn resolve(srt: SymbolResultType) ResultType {
257 return switch (srt) {
258 .present => |type_ref| .{ .ref = type_ref },
259 .missing => |value_ref| .{ .type_of = value_ref },
260 };
261 }
262 };
263
264 pub fn addWarnings(symbol: Symbol, t: *Transform) !void {
265 if (!symbol.mutated and symbol.data == .mutable) {
266 try t.report.warn(.not_mutated_var, .{
267 .node = symbol.node,
268 });
269 }
270 if (!symbol.used) {
271 try t.report.warn(.unused, .{
272 .node = symbol.node,
273 .kind = switch (symbol.data) {
274 .declaration => .declaration,
275 .param => .param,
276 .mutable => .variable,
277 .immutable => switch (t.ast.nodeTag(symbol.node)) {
278 .param_decl => .param,
279 else => .constant,
280 },
281 },
282 });
283 } else if (symbol.discarded) |discard| {
284 try t.report.warn(.unnecessary_discard, .{
285 .node = discard,
286 });
287 }
288 }
289 };
290242
291 fn addWarnings(s: Scope, t: *Transform) !void {243 fn addWarnings(s: Scope, t: *Transform) !void {
292 for (s.symbols.values()) |value| {244 for (s.symbols.values()) |value| {
...@@ -305,9 +257,109 @@ const Scope = struct {...@@ -305,9 +257,109 @@ const Scope = struct {
305 }257 }
306};258};
307259
308const Liveness = enum { alive, dead };260/// Sema requires that function expressions must know their up-front captures,
261/// so it can classify a function as comptime or runtime.
262const CaptureScope = struct {
263 start_index: Teg.Inst.Index,
264 symbol_uses: std.AutoArrayHashMapUnmanaged(Teg.Inst.Index, void) = .{},
265
266 fn deinit(s: *CaptureScope, t: *Transform) void {
267 s.symbol_uses.deinit(t.gpa);
268 s.* = undefined;
269 }
270};
271
272/// Identifier nodes reference a Symbol entry through the Transform's symbol
273/// table. The most important part of this is being able to convert it into the
274/// corresponding Ref or load_decl instruction when an identifier is visited.
275/// Additionally, there is bookkeeping information for warnings like "variable
276/// not mutated", which is reported just before a scope is deleted.
277const Symbol = struct {
278 kind: Kind,
279 flags: Flags,
280 src_node: NodeIndex,
281 value: union {
282 mutable: Inst.Index,
283 immutable: Inst.Ref,
284 none: void,
285 },
286 result_type_ref: Inst.Ref,
287 discarded: OptionalNodeIndex,
288
289 const Flags = packed struct {
290 result_type_kind: enum(u1) { present, missing },
291 used: bool,
292 mutated: bool,
293 };
294
295 const Kind = enum(u8) {
296 /// Named declaration like `let`, `fn`, `struct`, `module`
297 declaration,
298 /// Turns into a .variable_declaration
299 variable_declaration,
300 /// Function parameter
301 param,
302 /// Mutable local variable
303 mutable,
304 /// Immutable local variable
305 immutable,
306 };
307
308 fn init(kind: Kind) Symbol {
309 return .{ .kind = kind };
310 }
311
312 fn resultType(symbol: *const Symbol) ResultType {
313 return switch (symbol.flags.result_type_kind) {
314 .present => .{ .ref = symbol.result_type_ref },
315 .missing => .{ .type_of = symbol.result_type_ref },
316 };
317 }
318
319 const SymbolResultType = union(enum) {
320 /// Points to the resolved result type (.ref), as is.
321 present: Inst.Ref,
322 /// This points to the value. Which means a `type_of` instruction
323 /// must be inserted before it may be used.
324 missing: Inst.Ref,
325
326 pub fn resolve(srt: SymbolResultType) ResultType {
327 return switch (srt) {
328 .present => |type_ref| .{ .ref = type_ref },
329 .missing => |value_ref| .{ .type_of = value_ref },
330 };
331 }
332 };
333
334 pub fn addWarnings(symbol: Symbol, t: *Transform) !void {
335 if (!symbol.flags.mutated and (symbol.kind == .mutable or symbol.kind == .variable_declaration)) {
336 try t.report.warn(.not_mutated_var, .{
337 .node = symbol.src_node,
338 });
339 }
309340
310const BuiltInIds = std.StaticStringMap(Inst.Ref).initComptime(.{341 if (!symbol.flags.used) {
342 try t.report.warn(.unused, .{
343 .node = symbol.src_node,
344 .kind = switch (symbol.kind) {
345 .declaration => .declaration,
346 .param => .param,
347 .mutable, .variable_declaration => .variable,
348 .immutable => switch (t.ast.nodeTag(symbol.src_node)) {
349 .param_decl => .param,
350 else => .constant,
351 },
352 },
353 });
354 } else if (symbol.discarded.unwrap()) |discard| {
355 try t.report.warn(.unnecessary_discard, .{
356 .node = discard,
357 });
358 }
359 }
360};
361
362const built_in_ids = std.StaticStringMap(Inst.Ref).initComptime(.{
311 .{ "void", .void },363 .{ "void", .void },
312 .{ "true", .bool_true },364 .{ "true", .bool_true },
313 .{ "false", .bool_false },365 .{ "false", .bool_false },
...@@ -316,7 +368,7 @@ const BuiltInIds = std.StaticStringMap(Inst.Ref).initComptime(.{...@@ -316,7 +368,7 @@ const BuiltInIds = std.StaticStringMap(Inst.Ref).initComptime(.{
316 .{ "string", .string_type },368 .{ "string", .string_type },
317 // .{ "bigint", .bigint_type },369 // .{ "bigint", .bigint_type },
318 .{ "bool", .bool_type },370 .{ "bool", .bool_type },
319 .{ "noreturn", .noreturn_type },371 .{ "never", .never_type },
320 .{ "type", .type_type },372 .{ "type", .type_type },
321 // .{ "std", .standard_library },373 // .{ "std", .standard_library },
322});374});
...@@ -327,261 +379,235 @@ fn transformModule(t: *Transform, module_node: NodeIndex) Allocator.Error!Inst.I...@@ -327,261 +379,235 @@ fn transformModule(t: *Transform, module_node: NodeIndex) Allocator.Error!Inst.I
327379
328 const module = try t.addUntracked(.module, undefined);380 const module = try t.addUntracked(.module, undefined);
329381
330 const decl = BlockContext.init(module_node);382 const decl: BlockContext = .init(module_node);
331383
332 try t.pushScope();384 try t.pushScope();
333 defer t.popScope();385 defer t.popScope();
334386
387 // Pass 1 is a light visit over every statement in order to extract
388 // declaration names. This "hoists" the names so that declarations can be
389 // referenced before their definition appears in code.
390 var previsit_state: PrevisitModuleState = .{ .test_decls = .empty };
391 defer previsit_state.test_decls.deinit(t.gpa);
335 const statements = t.ast.nodeStatements(module_node);392 const statements = t.ast.nodeStatements(module_node);
336 for (statements) |statement| {393 for (statements) |statement| {
337 try t.previsitModuleStatement(statement);394 try t.previsitModuleStatement(&previsit_state, statement);
338 }395 }
339396
397 // Pass 2 processes all declarations in order, recursively
398 // visiting every Ast node.
340 const s = t.scope();399 const s = t.scope();
341400 const decl_len = s.symbols.count() + previsit_state.test_decls.items.len;
342 const temp = try t.gpa.alloc(Inst.Index, s.symbols.count() + 1);401 const decls = try t.gpa.alloc(Inst.Index, decl_len);
343 defer t.gpa.free(temp);402 defer t.gpa.free(decls);
344403
345 {404 {
346 // prevent concurrent access405 // `transformDecl` is not allowed to mutate symbols as we rely on the stable value.
347 s.symbols.lockPointers();406 s.symbols.lockPointers();
348 defer s.symbols.unlockPointers();407 defer s.symbols.unlockPointers();
408
349 for (s.symbols.values(), 0..) |value, i| {409 for (s.symbols.values(), 0..) |value, i| {
350 temp[i] = try t.transformDecl(decl, value.node);410 decls[i] = try t.transformDecl(decl, value.src_node);
351 }411 }
352 }412 }
353413
354 const top_level_fn = try t.addUntracked(.declaration, undefined);414 for (previsit_state.test_decls.items, s.symbols.count()..) |test_decl_node, i| {
355415 decls[i] = try t.transformTestDecl(decl, test_decl_node);
356 temp[temp.len - 1] = top_level_fn;416 }
357417
358 const declarations: IndexList = .{418 const declarations: IndexList = .{
359 .start = @intCast(t.extra.list.items.len),419 .start = @intCast(t.extra.list.items.len),
360 .len = @intCast(temp.len),420 .len = @intCast(decls.len),
361 };421 };
362 try t.extra.list.appendSlice(t.gpa, @ptrCast(temp));422 try t.extra.list.appendSlice(t.gpa, @ptrCast(decls));
363
364 const top_level_code_ref: Inst.Ref = ref: {
365 const scratch = t.scratch.mark();
366 defer scratch.deinit();
367
368 for (statements) |statement| {
369 try t.transformModuleStatement(decl, statement);
370 }
371
372 // if there are no new instructions, we dont have to create a block context
373 if (scratch.len() == 0) {
374 break :ref .void;
375 }
376
377 const block = try t.addUntracked(.block, undefined);
378423
379 t.getInstData(block).* = EncodedData.encode(.block, .{424 t.getInstData(module).* = .encode(.module, .{
380 .src_node = decl.nodeOffset(module_node),
381 .extra_index = try t.extra.append(t.gpa, IndexList, try t.scratchToIndexList(scratch)),
382 });
383
384 break :ref block.toRef();
385 };
386
387 const decl_extra, var trail = try t.extra.appendTrailing(t.gpa, Teg.Decl, .{
388 .kind = .function,
389 .name = NullTerminatedString.empty,
390 .hash = .{0} ** 16,
391 .instruction_count = @as(u32, @intCast(t.insts.len)) - @intFromEnum(top_level_fn),
392 .body = top_level_code_ref,
393 }, extra.extraSizeOf(Teg.DeclFn));
394
395 trail.writeStruct(Teg.DeclFn, .{
396 .params = .{ .start = 0, .len = 0 },
397 .return_type = Inst.Ref.void.toOptional(),
398 });
399
400 t.getInstData(top_level_fn).* = EncodedData.encode(.declaration, .{
401 .src_node = decl.nodeOffset(module_node),
402 .extra_index = decl_extra,
403 });
404
405 t.getInstData(module).* = EncodedData.encode(.module, .{
406 .src_node = 0,425 .src_node = 0,
407 .extra_index = try t.extra.append(t.gpa, IndexList, declarations),426 .extra_index = try t.extra.append(t.gpa, IndexList, declarations),
408 });427 });
409428
429 // `fn main` is a special function as it does not have to be public
430 // in order for the compiler to use it as the entry point.
431 if (module_node == 0) {
432 if (t.scope().symbols.getPtr("main")) |main| {
433 main.flags.used = true;
434 }
435 }
436
410 try t.scope().addWarnings(t);437 try t.scope().addWarnings(t);
411438
412 return module;439 return module;
413}440}
414441
415fn previsitModuleStatement(t: *Transform, node: NodeIndex) Allocator.Error!void {442const PrevisitModuleState = struct {
443 test_decls: std.ArrayListUnmanaged(NodeIndex),
444};
445
446fn previsitModuleStatement(t: *Transform, state: *PrevisitModuleState, node: NodeIndex) Allocator.Error!void {
416 const trace = tracer.trace(@src());447 const trace = tracer.trace(@src());
417 defer trace.end();448 defer trace.end();
418449
419 const tag = t.ast.nodes.items(.tag)[node];450 switch (t.ast.nodes.items(.tag)[node]) {
420 switch (tag) {451 // The paths of these three are all very similar and
421 .fn_decl => {452 // it is worth grouping them together
422 const decl = t.ast.fullFnDecl(node);453 inline .fn_decl,
423 var id, const id_allocated = try t.decodeIdentifierExtra(decl.id);454 .struct_decl,
455 .var_decl,
456 => |tag| {
457 const data = switch (tag) {
458 .var_decl => t.ast.fullVarDecl(node),
459 .fn_decl => t.ast.fullFnDecl(node),
460 .struct_decl => t.ast.fullStructDecl(node),
461 else => @compileError(unreachable),
462 };
463
464 const new_kind: Symbol.Kind = if (tag == .var_decl and !data.is_const)
465 .variable_declaration
466 else
467 .declaration;
468
469 var id, const id_allocated = try t.decodeIdentifierExtra(data.id);
424 if (id_allocated)470 if (id_allocated)
425 id = try t.gpa.dupe(u8, id);471 id = try t.gpa.dupe(u8, id);
472
426 const gop = try t.scope().symbols.getOrPut(t.gpa, id);473 const gop = try t.scope().symbols.getOrPut(t.gpa, id);
427 if (gop.found_existing) {474 if (gop.found_existing) {
428 if (id_allocated) t.gpa.free(id);475 if (id_allocated) t.gpa.free(id);
476
429 try t.report.err(.duplicate_function, .{477 try t.report.err(.duplicate_function, .{
430 .node = node,478 .node = node,
431 .prev = gop.value_ptr.node,479 .prev = gop.value_ptr.src_node,
432 });480 });
433 return;481 return;
434 }482 }
483
435 gop.value_ptr.* = .{484 gop.value_ptr.* = .{
436 .node = node,485 .kind = new_kind,
437 .data = .declaration,486 .flags = .{
438 .type = .{ .present = .void },487 .result_type_kind = undefined,
488 .used = false,
489 .mutated = false,
490 },
491 .src_node = node,
492 .discarded = .none,
493 .result_type_ref = undefined,
494 .value = undefined,
439 };495 };
440 },496 },
441 .struct_decl => {497 .test_decl => {
442 const decl = t.ast.fullStructDecl(node);498 try state.test_decls.append(t.arena, node);
443 var id, const id_allocated = try t.decodeIdentifierExtra(decl.id);499 },
444 if (id_allocated)500 else => if (!t.has_warned_top_level_statement) {
445 id = try t.gpa.dupe(u8, id);501 t.has_warned_top_level_statement = true;
446 const gop = try t.scope().symbols.getOrPut(t.gpa, decl.id);502 try t.report.err(.top_level_statement, .{ .src = node });
447 if (gop.found_existing) {503
448 try t.report.err(.duplicate_function, .{504 // Visit the statement so that any identifiers get tracked, so that mistakes in the code get caught
449 .node = node,505 const insts_top = t.insts.len;
450 .prev = gop.value_ptr.node,506 defer t.insts.len = insts_top;
451 // TODO: put type of both declarations507 const extra_top = t.extra.list.items.len;
452 });508 defer t.extra.list.items.len = extra_top;
453 return;509 const string_top = t.string_bytes.items.len;
454 }510 defer t.string_bytes.items.len = string_top;
455 gop.value_ptr.* = .{511
456 .node = node,512 _ = transformBlockStatement(t, .init(node), node, null) catch |err| switch (err) {
457 .data = .declaration,513 error.OutOfMemory => return err,
458 .type = .{ .present = .void },
459 };514 };
460 },515 },
461 else => {},
462 }
463}
464
465fn transformModuleStatement(t: *Transform, decl: BlockContext, node: NodeIndex) !void {
466 const trace = tracer.trace(@src());
467 defer trace.end();
468
469 const tag = t.ast.nodes.items(.tag)[node];
470 switch (tag) {
471 .root,
472 .param_decl,
473 => unreachable,
474
475 .fn_decl,
476 .struct_decl,
477 => {}, // declarations are analyzed separately
478
479 else => _ = try t.transformBlockStatement(decl, node, null),
480 }516 }
481}517}
482518
483fn transformDecl(t: *Transform, outer_decl: BlockContext, node: NodeIndex) Allocator.Error!Inst.Index {519fn transformDecl(t: *Transform, outer_decl: BlockContext, node: NodeIndex) Allocator.Error!Inst.Index {
484 return switch (t.ast.nodeTag(node)) {
485 .fn_decl => t.transformFunction(outer_decl, node),
486 .struct_decl => t.transformStructDeclaration(outer_decl, node),
487 else => unreachable, // not a declaration
488 };
489}
490
491fn transformFunction(t: *Transform, outer_decl: BlockContext, node: NodeIndex) Allocator.Error!Inst.Index {
492 const trace = tracer.trace(@src());520 const trace = tracer.trace(@src());
493 defer trace.end();521 defer trace.end();
494522
495 const decl = outer_decl.declOffset(node);523 const decl_index = try t.addUntracked(.declaration, undefined);
496 const fn_decl = t.ast.fullFnDecl(node);
497524
498 const decl_inst = try t.addUntracked(.declaration, undefined);525 const inner_ctx = outer_decl.declOffset(node);
526 const body = try t.transformToBlock(inner_ctx, node, .untracked_result, .none, .run_time);
499527
500 try t.pushScope();528 const inst_count = @as(u32, @intCast(t.insts.len)) - @intFromEnum(decl_index);
501 defer t.popScope();
502529
503 const param_list = brk: {530 const id_encoded = t.ast.tokenSlice(t.ast.idToken(node));
504 const scratch = t.scratch.mark();531 const id = try t.decodeIdentifier(id_encoded);
505 defer scratch.deinit();532 const name = try t.addNullTerminatedString(id);
506533
507 for (fn_decl.params) |param| {534 var hasher: std.Build.Cache.HashHelper = .{ .hasher = core.Cache.hasher_init };
508 try t.scratch.add(t.arena, Inst.Ref, try t.transformParamDecl(decl, param));535 hasher.addBytes(id);
509 }
510536
511 break :brk try t.scratchToRefList(scratch);537 const temp_teg: Teg = .{
538 .insts = t.insts.slice(),
539 .extra = t.extra.constant(),
540 .string_bytes = t.string_bytes.items,
541 .report = undefined,
512 };542 };
513543
514 const return_type = if (fn_decl.return_type) |return_type_node|544 const tag_list = temp_teg.insts.items(.tag)[@intFromEnum(decl_index) + 1 ..];
515 try t.transformToBlock(545 const data_list = temp_teg.insts.items(.data)[@intFromEnum(decl_index) + 1 ..];
516 decl,546
517 return_type_node,547 temp_teg.writeToHasher(&hasher, tag_list, data_list, decl_index);
518 .result_without_coercion,
519 .{ .ref = .type_type },
520 )
521 else
522 null;
523
524 const body = try t.transformToBlock(
525 decl,
526 fn_decl.body,
527 .function_body,
528 if (return_type) |ref| .{ .ref = ref } else .none,
529 );
530548
531 const decl_extra = try t.extra.append(t.gpa, Teg.Decl, .{549 const decl_extra = try t.extra.append(t.gpa, Teg.Decl, .{
532 .kind = .function,550 .name = name,
533 .name = try t.addNullTerminatedString(fn_decl.id),551 .hash = hasher.hasher.finalResult(),
534 .hash = .{0} ** 16,552 .instruction_count = inst_count,
535 .instruction_count = @as(u32, @intCast(t.insts.len)) - @intFromEnum(decl_inst),
536 .body = body,553 .body = body,
537 });554 });
538555
539 _ = try t.extra.append(t.gpa, Teg.DeclFn, .{556 t.getInstData(decl_index).* = .encode(.declaration, .{
540 .params = param_list,
541 .return_type = if (return_type) |r| r.toOptional() else .none,
542 });
543
544 t.getInstData(decl_inst).* = EncodedData.encode(.declaration, .{
545 .src_node = outer_decl.nodeOffset(node),557 .src_node = outer_decl.nodeOffset(node),
546 .extra_index = decl_extra,558 .extra_index = decl_extra,
547 });559 });
548560
549 try t.scope().addWarnings(t);561 return decl_index;
550
551 return decl_inst;
552}562}
553563
554fn transformStructDeclaration(t: *Transform, outer_decl: BlockContext, node: NodeIndex) Allocator.Error!Inst.Index {564fn transformTestDecl(t: *Transform, outer_decl: BlockContext, node: NodeIndex) Allocator.Error!Inst.Index {
555 const trace = tracer.trace(@src());565 const trace = tracer.trace(@src());
556 defer trace.end();566 defer trace.end();
557567
558 const decl = outer_decl.declOffset(node);568 const decl_index = try t.addUntracked(.test_declaration, undefined);
559 const decl_data = t.ast.fullStructDecl(node);569
570 const test_decl = t.ast.nodeData(node).test_decl;
571 if (test_decl.doc_test_name != .none) @panic("TODO");
572 const body_node = test_decl.block;
573
574 const inner_ctx = outer_decl.declOffset(node);
575 const body = try t.transformToBlock(inner_ctx, body_node, .untracked_result, .none, .run_time);
576
577 const inst_count = @as(u32, @intCast(t.insts.len)) - @intFromEnum(decl_index);
578
579 var hasher: std.Build.Cache.HashHelper = .{ .hasher = core.Cache.hasher_init };
580
581 const temp_teg: Teg = .{
582 .insts = t.insts.slice(),
583 .extra = t.extra.constant(),
584 .string_bytes = t.string_bytes.items,
585 .report = undefined,
586 };
560587
561 const decl_inst = try t.addUntracked(.declaration, undefined);588 const tag_list = temp_teg.insts.items(.tag)[@intFromEnum(decl_index) + 1 ..];
589 const data_list = temp_teg.insts.items(.data)[@intFromEnum(decl_index) + 1 ..];
562590
563 const body = try t.transformToBlock(decl, node, .result_without_coercion, .none);591 temp_teg.writeToHasher(&hasher, tag_list, data_list, decl_index);
564592
565 const decl_extra = try t.extra.append(t.gpa, Teg.Decl, .{593 const decl_extra = try t.extra.append(t.gpa, Teg.Decl, .{
566 .kind = .immutable,594 .name = .empty,
567 .name = try t.addNullTerminatedString(decl_data.id),595 .hash = hasher.hasher.finalResult(),
568 .hash = .{0} ** 16,596 .instruction_count = inst_count,
569 .instruction_count = @as(u32, @intCast(t.insts.len)) - @intFromEnum(decl_inst),
570 .body = body,597 .body = body,
571 });598 });
572599
573 t.getInstData(decl_inst).* = EncodedData.encode(.declaration, .{600 t.getInstData(decl_index).* = .encode(.test_declaration, .{
574 .src_node = outer_decl.nodeOffset(node),601 .src_node = outer_decl.nodeOffset(node),
575 .extra_index = decl_extra,602 .extra_index = decl_extra,
576 });603 });
577604
578 return decl_inst;605 return decl_index;
579}606}
580607
581const TransformToBlockMode = enum {608const TransformToBlockMode = enum {
582 tracked_result,609 tracked_result,
583 result_without_coercion,610 untracked_result,
584 coerced_result,
585 function_body,611 function_body,
586612
587 fn ResultType(mode: TransformToBlockMode) type {613 fn ResultType(mode: TransformToBlockMode) type {
...@@ -590,42 +616,63 @@ const TransformToBlockMode = enum {...@@ -590,42 +616,63 @@ const TransformToBlockMode = enum {
590 else => Transform.ResultType,616 else => Transform.ResultType,
591 };617 };
592 }618 }
619
620 fn InstType(mode: TransformToBlockMode) type {
621 return switch (mode) {
622 .function_body => Inst.Index,
623 else => Inst.Ref,
624 };
625 }
593};626};
594627
595/// guh628const BlockType = enum {
629 run_time,
630 compile_time,
631};
632
633// todo: clarify
634/// Visit and convert an expression into either a Value with no instructions, or
635/// a single instruction.
636///
637/// This is important for function bodies, loop bodies, and branches, so that
638/// contents are grouped into a nested block.
596fn transformToBlock(639fn transformToBlock(
597 t: *Transform,640 t: *Transform,
598 ctx: BlockContext,641 ctx: BlockContext,
599 node: NodeIndex,642 node: NodeIndex,
600 comptime mode: TransformToBlockMode,643 comptime mode: TransformToBlockMode,
601 result_type: mode.ResultType(),644 result_type: mode.ResultType(),
602) Allocator.Error!Inst.Ref {645 block_type: BlockType,
646) Allocator.Error!mode.InstType() {
603 const trace = tracer.trace(@src());647 const trace = tracer.trace(@src());
604 defer trace.end();648 defer trace.end();
605649
606 const scratch = t.scratch.mark();650 const scratch = t.scratch.mark();
607 defer scratch.deinit();651 defer scratch.deinit();
608652
653 // The result type is only passed by reference if mode is tracked_result.
654 const copies_result_type = mode.ResultType() == ResultType;
655 var result_type_storage = if (copies_result_type) result_type;
656
657 var ret = if (mode == .function_body)
658 BlockContext.ReturnTarget{
659 .result_type = result_type_storage,
660 .block_index = try t.addUntracked(.block, undefined),
661 };
662
609 // For function blocks, add .param instructions for all of the parameters663 // For function blocks, add .param instructions for all of the parameters
610 if (mode == .function_body) {664 if (mode == .function_body) {
611 const s = t.scopes.getLast();665 const s = t.scopes.getLast();
612 for (s.symbols.values(), 0..) |*param, i| {666 for (s.symbols.values(), 0..) |*param, i| {
613 assert(param.data == .param);667 assert(param.kind == .param);
614 const ref = try t.addRef(.param, .{668 const ref = try t.addRef(.param, .{
615 .src_node = ctx.nodeOffset(node),669 .src_node = ctx.nodeOffset(node),
616 .data = @intCast(i),670 .data = @intCast(i),
617 });671 });
618 param.data = .{ .immutable = ref };672 param.value = .{ .immutable = ref };
619 }673 }
620 }674 }
621675
622 // The result type is only passed by reference if mode is tracked_result.
623 const copies_result_type = mode.ResultType() == ResultType;
624 var result_type_storage = if (copies_result_type) result_type;
625
626 var ret = if (mode == .function_body)
627 BlockContext.ReturnTarget{ .result_type = result_type_storage };
628
629 const expr = try t.transformExpression(676 const expr = try t.transformExpression(
630 // Function blocks are allowed to return, in which677 // Function blocks are allowed to return, in which
631 // it will return to this block.678 // it will return to this block.
...@@ -641,15 +688,15 @@ fn transformToBlock(...@@ -641,15 +688,15 @@ fn transformToBlock(
641 node,688 node,
642 );689 );
643690
644 // if there are no new instructions, we dont have to create a block context691 // if we were given a value without any new instructions, we can just
645 if (scratch.len() == 0) {692 // forward that value.
646 const result = expr orelse693 if (mode != .function_body and scratch.len() == 0) {
647 unreachable; // null means we *added* break or return694 const result = expr orelse {
648695 assert(t.report.errors.items.len > 0);
649 if (result.value() != null) {696 return .void; // null means control flow ended, and with no instructions guarantees a compile error
650 if (mode == .function_body)697 };
651 assert(ret.generated_block == null); // return was called without an instruction
652698
699 if (result.decode() == .value) {
653 return result;700 return result;
654 }701 }
655 }702 }
...@@ -658,14 +705,20 @@ fn transformToBlock(...@@ -658,14 +705,20 @@ fn transformToBlock(
658 if (expr) |result| {705 if (expr) |result| {
659 if (result.index()) |index| {706 if (result.index()) |index| {
660 if (t.insts.items(.tag)[@intFromEnum(index)] == .block) {707 if (t.insts.items(.tag)[@intFromEnum(index)] == .block) {
708 if (mode == .function_body)
709 return index;
661 return result;710 return result;
662 }711 }
663 }712 }
664 }713 }
665714
666 // Otherwise try using the function return block, or create a new one715 // Otherwise try using the function return block, or create a new one
667 const maybe_generated_block = if (mode == .function_body) ret.generated_block else null;716 const ret_block = if (mode == .function_body)
668 const ret_block = maybe_generated_block orelse try t.addUntracked(.block, undefined);717 ret.block_index
718 else switch (block_type) {
719 .run_time => try t.addUntracked(.block, undefined),
720 .compile_time => try t.addUntracked(.@"comptime", undefined),
721 };
669722
670 // The block must end with a break723 // The block must end with a break
671 if (expr) |result| {724 if (expr) |result| {
...@@ -681,12 +734,12 @@ fn transformToBlock(...@@ -681,12 +734,12 @@ fn transformToBlock(
681 const content = try t.scratchToIndexList(scratch);734 const content = try t.scratchToIndexList(scratch);
682 assert(content.len > 0);735 assert(content.len > 0);
683736
684 t.getInstData(ret_block).* = EncodedData.encode(.block, .{737 t.getInstData(ret_block).* = .encode(.block, .{
685 .src_node = ctx.nodeOffset(node),738 .src_node = ctx.nodeOffset(node),
686 .extra_index = try t.extra.append(t.gpa, IndexList, content),739 .extra_index = try t.extra.append(t.gpa, IndexList, content),
687 });740 });
688741
689 return ret_block.toRef();742 return if (mode == .function_body) ret_block else ret_block.toRef();
690}743}
691744
692fn transformParamDecl(t: *Transform, decl: BlockContext, param: NodeIndex) Allocator.Error!Inst.Ref {745fn transformParamDecl(t: *Transform, decl: BlockContext, param: NodeIndex) Allocator.Error!Inst.Ref {
...@@ -698,8 +751,9 @@ fn transformParamDecl(t: *Transform, decl: BlockContext, param: NodeIndex) Alloc...@@ -698,8 +751,9 @@ fn transformParamDecl(t: *Transform, decl: BlockContext, param: NodeIndex) Alloc
698 decl,751 decl,
699 param_decl.type orelse752 param_decl.type orelse
700 @panic("TODO: inferred param types"),753 @panic("TODO: inferred param types"),
701 .result_without_coercion,754 .untracked_result,
702 .{ .ref = .type_type },755 .{ .ref = .type_type },
756 .compile_time,
703 );757 );
704758
705 var id, const id_allocated = try t.decodeIdentifierExtra(param_decl.id);759 var id, const id_allocated = try t.decodeIdentifierExtra(param_decl.id);
...@@ -713,22 +767,29 @@ fn transformParamDecl(t: *Transform, decl: BlockContext, param: NodeIndex) Alloc...@@ -713,22 +767,29 @@ fn transformParamDecl(t: *Transform, decl: BlockContext, param: NodeIndex) Alloc
713 if (id_allocated) t.gpa.free(id);767 if (id_allocated) t.gpa.free(id);
714 try t.report.err(.duplicate_param, .{768 try t.report.err(.duplicate_param, .{
715 .node = param,769 .node = param,
716 .prev = sym.value_ptr.node,770 .prev = sym.value_ptr.src_node,
717 });771 });
718 return .void;772 return .void;
719 }773 }
720774
721 sym.value_ptr.* = .{775 sym.value_ptr.* = .{
722 .node = param,776 .src_node = param,
723 .data = .param,777 .kind = .param,
724 .type = .{ .present = ty },778 .flags = .{
779 .result_type_kind = .present,
780 .used = false,
781 .mutated = false,
782 },
783 .value = .{ .none = {} },
784 .result_type_ref = ty,
785 .discarded = .none,
725 };786 };
726787
727 return ty;788 return ty;
728}789}
729790
730pub const Builtin = enum {791pub const Builtin = enum {
731 js,792 @"extern",
732 js_new,793 js_new,
733 import,794 import,
734 fetch,795 fetch,
...@@ -740,13 +801,13 @@ pub const Builtin = enum {...@@ -740,13 +801,13 @@ pub const Builtin = enum {
740 .{ "compileError", .compile_error },801 .{ "compileError", .compile_error },
741 .{ "fetch", .fetch },802 .{ "fetch", .fetch },
742 .{ "import", .import },803 .{ "import", .import },
743 .{ "js", .js },804 .{ "extern", .@"extern" },
744 .{ "jsNew", .js_new },805 .{ "jsNew", .js_new },
745 .{ "typeOf", .type_of },806 .{ "typeOf", .type_of },
746 // sort-lines: end807 // sort-lines: end
747 });808 });
748809
749 fn builtin_js(t: *Transform, decl: BlockContext, node: NodeIndex, args: []const NodeIndex) !?Inst.Ref {810 fn builtin_extern(t: *Transform, decl: BlockContext, node: NodeIndex, args: []const NodeIndex) !?Inst.Ref {
750 const trace = tracer.trace(@src());811 const trace = tracer.trace(@src());
751 defer trace.end();812 defer trace.end();
752813
...@@ -758,7 +819,7 @@ pub const Builtin = enum {...@@ -758,7 +819,7 @@ pub const Builtin = enum {
758 var result: ResultType = .{ .ref = .string_type };819 var result: ResultType = .{ .ref = .string_type };
759 const source = try t.transformExpression(decl, &result, args[0]) orelse return null;820 const source = try t.transformExpression(decl, &result, args[0]) orelse return null;
760821
761 return try t.addRef(.js, .{822 return try t.addRef(.@"extern", .{
762 .src_node = decl.nodeOffset(args[0]),823 .src_node = decl.nodeOffset(args[0]),
763 .operand = source,824 .operand = source,
764 });825 });
...@@ -841,7 +902,6 @@ fn transformExpression(...@@ -841,7 +902,6 @@ fn transformExpression(
841 switch (node_tag) {902 switch (node_tag) {
842 .root,903 .root,
843 .param_decl,904 .param_decl,
844 .var_decl,
845 => unreachable, // not an expression905 => unreachable, // not an expression
846906
847 .id => {907 .id => {
...@@ -851,7 +911,7 @@ fn transformExpression(...@@ -851,7 +911,7 @@ fn transformExpression(
851 return .void;911 return .void;
852 }912 }
853913
854 if (BuiltInIds.get(id)) |ref| {914 if (built_in_ids.get(id)) |ref| {
855 return ref;915 return ref;
856 }916 }
857917
...@@ -860,17 +920,53 @@ fn transformExpression(...@@ -860,17 +920,53 @@ fn transformExpression(
860 return .void;920 return .void;
861 };921 };
862922
863 lookup.used = true;923 lookup.flags.used = true;
864924
865 return switch (lookup.data) {925 switch (lookup.kind) {
866 .declaration => try t.addRef(.load_decl, .{926 .declaration,
927 .variable_declaration,
928 => return try t.addRef(.load_decl, .{
867 .src_node = ctx.nodeOffset(node),929 .src_node = ctx.nodeOffset(node),
868 .string = try t.addNullTerminatedString(id),930 .string = try t.addNullTerminatedString(id),
869 }),931 }),
870 .mutable => |idx| idx.toRef(),932 .mutable => {
871 .immutable => |ref| ref,933 const ref = lookup.value.mutable.toRef();
872 .param => unreachable, // unresolved param934 try t.markCapturedRef(ref);
873 };935 return ref;
936 },
937 .param, .immutable => {
938 const ref = lookup.value.immutable;
939 try t.markCapturedRef(ref);
940 return ref;
941 },
942 }
943 },
944
945 // var_decl *expression* is special cased for decl contents.
946 // instead of defining a variable, it will just perform the coercion.
947 .var_decl => {
948 const decl = t.ast.fullVarDecl(node);
949 var type_result: ResultType = .{ .ref = .type_type };
950 const type_ref = if (decl.type) |ty|
951 try t.transformExpression(ctx, &type_result, ty) orelse
952 return null
953 else
954 null;
955
956 var value_result: ResultType = if (type_ref) |ty| .{ .ref = ty } else .none;
957 const data_ref = try t.transformExpression(ctx, &value_result, decl.init) orelse
958 return null;
959
960 if (value_result == .used or type_ref == null)
961 return data_ref;
962
963 return try t.addRef(.coercion, .{
964 .src_node = ctx.nodeOffset(node),
965 .extra_index = try t.extra.append(t.gpa, Teg.Coercion, .{
966 .type = type_ref.?,
967 .operand = data_ref,
968 }),
969 });
874 },970 },
875971
876 inline .number_int, .number_float => |tag| {972 inline .number_int, .number_float => |tag| {
...@@ -910,18 +1006,11 @@ fn transformExpression(...@@ -910,18 +1006,11 @@ fn transformExpression(
9101006
911 .assign => {1007 .assign => {
912 const ast_bin = t.ast.nodeData(node).bin;1008 const ast_bin = t.ast.nodeData(node).bin;
913 const lhs, var result_type = try t.transformAssignmentTarget(ctx, ast_bin.lhs) orelse1009 const target, var result_type = try t.transformAssignmentTarget(ctx, ast_bin.lhs) orelse
914 return .void;1010 return .void;
915 const rhs = try t.transformExpression(ctx, &result_type, ast_bin.rhs) orelse1011 const rhs = try t.transformExpression(ctx, &result_type, ast_bin.rhs) orelse
916 return null;1012 return null;
917 const assign_inst = try t.addRef(.store, .{1013 return try target.emit(t, rhs, ctx, node);
918 .src_node = ctx.nodeOffset(node),
919 .extra_index = try t.extra.append(t.gpa, Teg.Store, .{
920 .memory = lhs,
921 .operand = rhs,
922 }),
923 });
924 return assign_inst;
925 },1014 },
9261015
927 inline .add,1016 inline .add,
...@@ -983,20 +1072,13 @@ fn transformExpression(...@@ -983,20 +1072,13 @@ fn transformExpression(
983 const compute_inst = try t.addRef(teg_tag, .{1072 const compute_inst = try t.addRef(teg_tag, .{
984 .src_node = ctx.nodeOffset(node),1073 .src_node = ctx.nodeOffset(node),
985 .extra_index = try t.extra.append(t.gpa, Teg.Bin, .{1074 .extra_index = try t.extra.append(t.gpa, Teg.Bin, .{
986 .lhs = if (is_assign) lhs.toRef() else lhs,1075 .lhs = if (is_assign) try lhs.toRef(t) else lhs,
987 .rhs = rhs,1076 .rhs = rhs,
988 }),1077 }),
989 });1078 });
9901079
991 if (is_assign) {1080 if (is_assign) {
992 const assign_inst = try t.addRef(.store, .{1081 return try lhs.emit(t, compute_inst, ctx, node);
993 .src_node = ctx.nodeOffset(node),
994 .extra_index = try t.extra.append(t.gpa, Teg.Store, .{
995 .memory = lhs,
996 .operand = compute_inst,
997 }),
998 });
999 return assign_inst;
1000 }1082 }
10011083
1002 return compute_inst;1084 return compute_inst;
...@@ -1006,6 +1088,7 @@ fn transformExpression(...@@ -1006,6 +1088,7 @@ fn transformExpression(
1006 .bool_not,1088 .bool_not,
1007 .bit_not,1089 .bit_not,
1008 .array_type,1090 .array_type,
1091 .assert,
1009 => |tag| {1092 => |tag| {
1010 const ast_un = t.ast.nodeData(node).un;1093 const ast_un = t.ast.nodeData(node).un;
1011 const teg_tag = @field(Teg.Tag, @tagName(tag));1094 const teg_tag = @field(Teg.Tag, @tagName(tag));
...@@ -1015,6 +1098,7 @@ fn transformExpression(...@@ -1015,6 +1098,7 @@ fn transformExpression(
1015 .bool_not => .{ .ref = .bool_type },1098 .bool_not => .{ .ref = .bool_type },
1016 .bit_not => .{ .ref = .int32_type },1099 .bit_not => .{ .ref = .int32_type },
1017 .array_type => .{ .ref = .type_type },1100 .array_type => .{ .ref = .type_type },
1101 .assert => .{ .ref = .bool_type },
1018 else => @compileError(unreachable),1102 else => @compileError(unreachable),
1019 };1103 };
10201104
...@@ -1029,11 +1113,20 @@ fn transformExpression(...@@ -1029,11 +1113,20 @@ fn transformExpression(
10291113
1030 .@"comptime" => {1114 .@"comptime" => {
1031 const ast_un = t.ast.nodeData(node).un;1115 const ast_un = t.ast.nodeData(node).un;
1032 const operand = try t.transformToBlock(ctx, ast_un.expr, .tracked_result, result);1116 const operand = try t.transformToBlock(ctx, ast_un.expr, .tracked_result, result, .compile_time);
1033 return try t.addRef(.@"comptime", .{1117 // Move the source index to the `comptime` node
1034 .src_node = ctx.nodeOffset(node),1118 if (operand.index()) |index| {
1035 .operand = operand,1119 const slice = t.insts.slice();
1036 });1120 const data: *EncodedData = &slice.items(.data)[@intFromEnum(index)];
1121 var existing = data.decode(.block, slice.items(.tag)[@intFromEnum(index)]);
1122 existing.src_node = ctx.nodeOffset(node);
1123 data.* = .encode(.block, existing);
1124
1125 // Since the block was added in an untracked fashion, it
1126 // needs to be tracked it before returning.
1127 try t.scratch.add(t.arena, Inst.Index, index);
1128 }
1129 return operand;
1037 },1130 },
10381131
1039 .label => {1132 .label => {
...@@ -1054,8 +1147,7 @@ fn transformExpression(...@@ -1054,8 +1147,7 @@ fn transformExpression(
10541147
1055 // if there are no new instructions, we dont have to create a block context1148 // if there are no new instructions, we dont have to create a block context
1056 if (scratch.len() == 0)1149 if (scratch.len() == 0)
1057 return inner_expr orelse1150 return inner_expr;
1058 unreachable; // null means we added break or return
10591151
1060 if (target.block_index) |block| {1152 if (target.block_index) |block| {
1061 if (inner_expr) |out| {1153 if (inner_expr) |out| {
...@@ -1071,7 +1163,7 @@ fn transformExpression(...@@ -1071,7 +1163,7 @@ fn transformExpression(
1071 const content = try t.scratchToIndexList(scratch);1163 const content = try t.scratchToIndexList(scratch);
1072 scratch.deinit();1164 scratch.deinit();
10731165
1074 t.getInstData(block).* = EncodedData.encode(.block, .{1166 t.getInstData(block).* = .encode(.block, .{
1075 .src_node = ctx.nodeOffset(node),1167 .src_node = ctx.nodeOffset(node),
1076 .extra_index = try t.extra.append(t.gpa, IndexList, content),1168 .extra_index = try t.extra.append(t.gpa, IndexList, content),
1077 });1169 });
...@@ -1080,7 +1172,7 @@ fn transformExpression(...@@ -1080,7 +1172,7 @@ fn transformExpression(
10801172
1081 return block.toRef();1173 return block.toRef();
1082 } else {1174 } else {
1083 std.debug.print("Warn, unused label {s}", .{label});1175 std.debug.print("Warn, unused label {s}\n", .{label});
1084 }1176 }
10851177
1086 return inner_expr;1178 return inner_expr;
...@@ -1119,7 +1211,7 @@ fn transformExpression(...@@ -1119,7 +1211,7 @@ fn transformExpression(
1119 const args = base.slice(&t.ast);1211 const args = base.slice(&t.ast);
11201212
1121 return switch (builtin) {1213 return switch (builtin) {
1122 .js => Builtin.builtin_js(t, ctx, node, args),1214 .@"extern" => Builtin.builtin_extern(t, ctx, node, args),
1123 .js_new => Builtin.builtin_jsNew(t, ctx, node, args),1215 .js_new => Builtin.builtin_jsNew(t, ctx, node, args),
1124 // .import => Builtin.import(t, decl, node, args),1216 // .import => Builtin.import(t, decl, node, args),
1125 // .fetch => Builtin.fetch(t, decl, node, args),1217 // .fetch => Builtin.fetch(t, decl, node, args),
...@@ -1238,17 +1330,11 @@ fn transformExpression(...@@ -1238,17 +1330,11 @@ fn transformExpression(
1238 return null;1330 return null;
12391331
1240 var yes_result = result.*;1332 var yes_result = result.*;
1241 const yes = try t.transformToBlock(ctx, if_data.yes, .tracked_result, &yes_result);1333 const yes = try t.transformToBlock(ctx, if_data.yes, .tracked_result, &yes_result, .run_time);
12421334
1243 // TODO: can this re-use the resolved ref from above
1244 var no_result: ResultType = if (if_data.no == .none) .used else result.*;1335 var no_result: ResultType = if (if_data.no == .none) .used else result.*;
1245 const no = if (if_data.no.unwrap()) |no|1336 const no = if (if_data.no.unwrap()) |no|
1246 try t.transformToBlock(1337 try t.transformToBlock(ctx, no, .tracked_result, &no_result, .run_time)
1247 ctx,
1248 no,
1249 .tracked_result,
1250 &no_result,
1251 )
1252 else1338 else
1253 Inst.Ref.void;1339 Inst.Ref.void;
12541340
...@@ -1275,14 +1361,6 @@ fn transformExpression(...@@ -1275,14 +1361,6 @@ fn transformExpression(
1275 return .void;1361 return .void;
1276 };1362 };
12771363
1278 const ret_block = if (target.generated_block) |block|
1279 block
1280 else append_block: {
1281 const block = try t.addUntracked(.block, undefined);
1282 target.generated_block = block;
1283 break :append_block block;
1284 };
1285
1286 var return_result = target.result_type;1364 var return_result = target.result_type;
12871365
1288 const operand: Inst.Ref = if (ret_node.expr.unwrap()) |expr|1366 const operand: Inst.Ref = if (ret_node.expr.unwrap()) |expr|
...@@ -1297,7 +1375,7 @@ fn transformExpression(...@@ -1297,7 +1375,7 @@ fn transformExpression(
1297 _ = try t.addIndex(.@"break", .{1375 _ = try t.addIndex(.@"break", .{
1298 .src_node = ctx.nodeOffset(node),1376 .src_node = ctx.nodeOffset(node),
1299 .extra_index = try t.extra.append(t.gpa, Teg.Break, .{1377 .extra_index = try t.extra.append(t.gpa, Teg.Break, .{
1300 .block = ret_block,1378 .block = target.block_index,
1301 .operand = operand,1379 .operand = operand,
1302 }),1380 }),
1303 });1381 });
...@@ -1319,7 +1397,7 @@ fn transformExpression(...@@ -1319,7 +1397,7 @@ fn transformExpression(
13191397
1320 var break_result = block.result_type.*;1398 var break_result = block.result_type.*;
1321 const operand: Inst.Ref = if (brk_node.value.unwrap()) |expr|1399 const operand: Inst.Ref = if (brk_node.value.unwrap()) |expr|
1322 try t.transformExpression(ctx.withReturn(null), &break_result, expr) orelse1400 try t.transformExpression(ctx, &break_result, expr) orelse
1323 return null1401 return null
1324 else1402 else
1325 .void;1403 .void;
...@@ -1441,6 +1519,74 @@ fn transformExpression(...@@ -1441,6 +1519,74 @@ fn transformExpression(
1441 });1519 });
1442 },1520 },
14431521
1522 inline .fn_decl, .fn_expr => |tag| {
1523 const data = switch (tag) {
1524 .fn_decl => t.ast.fullFnDecl(node),
1525 .fn_expr => t.ast.fullFnExpr(node),
1526 else => @compileError(unreachable),
1527 };
1528
1529 try t.pushScope();
1530 defer t.popScope();
1531
1532 try t.pushCaptureScope(@enumFromInt(t.insts.len - 1));
1533 defer t.popCaptureScope();
1534
1535 const param_list = brk: {
1536 const scratch = t.scratch.mark();
1537 defer scratch.deinit();
1538
1539 for (data.params) |param| {
1540 try t.scratch.add(t.arena, Inst.Ref, try t.transformParamDecl(ctx, param));
1541 }
1542
1543 break :brk try t.scratchToOptionalRefList(scratch);
1544 };
1545
1546 const return_type = if (data.return_type) |return_type_node|
1547 try t.transformToBlock(
1548 ctx.withReturn(null),
1549 return_type_node,
1550 .untracked_result,
1551 .{ .ref = .type_type },
1552 .run_time,
1553 )
1554 else
1555 null;
1556
1557 const body = try t.transformToBlock(
1558 ctx,
1559 data.body,
1560 .function_body,
1561 if (return_type) |ref|
1562 .{ .ref = ref }
1563 else
1564 .none,
1565 .run_time,
1566 );
1567
1568 const inst_count = @as(u32, @intCast(t.insts.len)) - @intFromEnum(body);
1569
1570 const capture_scope = t.captureScope();
1571 const captures_len = capture_scope.symbol_uses.count();
1572 const captures_data = try t.extra.alloc(t.gpa, captures_len);
1573 @memcpy(
1574 t.extra.list.items[captures_data..][0..captures_len],
1575 @as([]u32, @ptrCast(capture_scope.symbol_uses.entries.items(.key))),
1576 );
1577
1578 return try t.addRef(.func, .{
1579 .src_node = ctx.nodeOffset(node),
1580 .extra_index = try t.extra.append(t.gpa, Teg.Func, .{
1581 .params = param_list,
1582 .return_type = if (return_type) |ref| ref.toOptional() else .none,
1583 .body = body,
1584 .instruction_count = inst_count,
1585 .captures = .{ .start = captures_data, .len = @intCast(captures_len) },
1586 }),
1587 });
1588 },
1589
1444 else => |tag| std.debug.panic("TODO: expr {s}", .{@tagName(tag)}),1590 else => |tag| std.debug.panic("TODO: expr {s}", .{@tagName(tag)}),
1445 }1591 }
1446}1592}
...@@ -1467,9 +1613,9 @@ fn transformExpressionList(t: *Transform, decl: BlockContext, result: ResultType...@@ -1467,9 +1613,9 @@ fn transformExpressionList(t: *Transform, decl: BlockContext, result: ResultType
1467/// This returns the assignment target's Ref, as well as a result type to visit1613/// This returns the assignment target's Ref, as well as a result type to visit
1468/// the right-hand side with. Note that only a few expressions are valid here:1614/// the right-hand side with. Note that only a few expressions are valid here:
1469///1615///
1470/// - A plain identifier, which is resolved directly to a `memory` instruction1616/// - A plain identifier with a `memory` instruction -> `.store_memory`
1471/// - A `field` node: TODO1617/// - A plain identifier to a .variable_declaration -> `.store_decl`
1472/// - An `index` node: TODO1618/// - A `field` or `index` node -> `.store_field`
1473///1619///
1474/// Note that these three are the root nodes, it is legal to have:1620/// Note that these three are the root nodes, it is legal to have:
1475///1621///
...@@ -1479,9 +1625,9 @@ fn transformExpressionList(t: *Transform, decl: BlockContext, result: ResultType...@@ -1479,9 +1625,9 @@ fn transformExpressionList(t: *Transform, decl: BlockContext, result: ResultType
1479/// or a value with a reference type; and optionally a sub property.1625/// or a value with a reference type; and optionally a sub property.
1480fn transformAssignmentTarget(1626fn transformAssignmentTarget(
1481 t: *Transform,1627 t: *Transform,
1482 decl: BlockContext,1628 ctx: BlockContext,
1483 node: NodeIndex,1629 node: NodeIndex,
1484) Allocator.Error!?struct { Inst.Index, ResultType } {1630) Allocator.Error!?struct { AssignmentTarget, ResultType } {
1485 const node_tag = t.ast.nodes.items(.tag)[node];1631 const node_tag = t.ast.nodes.items(.tag)[node];
14861632
1487 const trace = tracer.traceFmt(@src(), "{s}", .{@tagName(node_tag)});1633 const trace = tracer.traceFmt(@src(), "{s}", .{@tagName(node_tag)});
...@@ -1502,32 +1648,44 @@ fn transformAssignmentTarget(...@@ -1502,32 +1648,44 @@ fn transformAssignmentTarget(
1502 return null;1648 return null;
1503 }1649 }
15041650
1505 if (BuiltInIds.has(id)) {1651 if (built_in_ids.has(id)) {
1506 try t.report.err(.assign_builtin, .{ .node = node });1652 try t.report.err(.assign_builtin, .{ .node = node });
1507 return null;1653 return null;
1508 }1654 }
15091655
1510 const lookup: *Scope.Symbol = t.lookupId(id) orelse {1656 const lookup: *Symbol = t.lookupId(id) orelse {
1511 try t.report.err(.unknown_identifier, .{ .node = node });1657 try t.report.err(.unknown_identifier, .{ .node = node });
1512 return null;1658 return null;
1513 };1659 };
15141660
1515 return switch (lookup.data) {1661 return switch (lookup.kind) {
1516 .mutable => |inst| {1662 .mutable => {
1517 lookup.mutated = true;1663 lookup.flags.mutated = true;
1518 return .{ inst, lookup.type.resolve() };1664 return .{
1665 .{ .memory = lookup.value.mutable },
1666 lookup.resultType(),
1667 };
1668 },
1669 .variable_declaration => {
1670 return .{
1671 .{ .variable_declaration = .{
1672 .src_node = ctx.nodeOffset(node),
1673 .string = try t.addNullTerminatedString(id),
1674 } },
1675 lookup.resultType(),
1676 };
1519 },1677 },
1520 else => {1678 else => {
1521 lookup.used = true;1679 lookup.flags.used = true;
15221680
1523 try t.report.err(.invalid_assignment_symbol, .{1681 try t.report.err(.invalid_assignment_symbol, .{
1524 .node = node,1682 .node = node,
1525 .decl = lookup.node,1683 .decl = lookup.src_node,
1526 .kind = switch (lookup.data) {1684 .kind = switch (lookup.kind) {
1527 .mutable => unreachable,1685 .mutable, .variable_declaration => unreachable,
1528 .declaration => .declaration,1686 .declaration => .declaration,
1529 .param => .param,1687 .param => .param,
1530 .immutable => switch (t.ast.nodeTag(lookup.node)) {1688 .immutable => switch (t.ast.nodeTag(lookup.src_node)) {
1531 .param_decl => .param,1689 .param_decl => .param,
1532 else => .constant,1690 else => .constant,
1533 },1691 },
...@@ -1539,11 +1697,50 @@ fn transformAssignmentTarget(...@@ -1539,11 +1697,50 @@ fn transformAssignmentTarget(
1539 };1697 };
1540 },1698 },
15411699
1542 // TODO: allow field expressions, but these need to emit a different store node1700 .field => {
1701 const field = t.ast.nodeData(node).field;
1702
1703 var no_result: ResultType = .none;
1704 const base = try t.transformExpression(ctx, &no_result, field.base) orelse return null;
1705
1706 const field_str = try t.decodeIdentifier(t.ast.tokenSlice(field.field_token));
1707 const field_sliced = try t.addSlicedString(field_str);
1708
1709 const field_ref = try t.addRef(.string, .{
1710 .src_node = ctx.nodeOffset(node),
1711 .extra_index = try t.extra.append(t.gpa, SlicedString, field_sliced),
1712 });
1713
1714 return .{
1715 .{ .field = .{
1716 .src_node = ctx.nodeOffset(node),
1717 .base = base,
1718 .field = field_ref,
1719 } },
1720 .none, // TODO:
1721 };
1722 },
1723
1724 .index => {
1725 const bin = t.ast.nodeData(node).bin;
1726
1727 var no_result: ResultType = .none;
1728 const base = try t.transformExpression(ctx, &no_result, bin.lhs) orelse return null;
1729 const field = try t.transformExpression(ctx, &no_result, bin.rhs) orelse return null;
1730
1731 return .{
1732 .{ .field = .{
1733 .src_node = ctx.nodeOffset(node),
1734 .base = base,
1735 .field = field,
1736 } },
1737 .none, // TODO:
1738 };
1739 },
15431740
1544 else => {1741 else => {
1545 var result_type: ResultType = .none;1742 var result_type: ResultType = .none;
1546 _ = try t.transformExpression(decl, &result_type, node);1743 _ = try t.transformExpression(ctx, &result_type, node);
15471744
1548 try t.report.err(.invalid_assignment_target, .{1745 try t.report.err(.invalid_assignment_target, .{
1549 .node = node,1746 .node = node,
...@@ -1554,6 +1751,63 @@ fn transformAssignmentTarget(...@@ -1554,6 +1751,63 @@ fn transformAssignmentTarget(
1554 }1751 }
1555}1752}
15561753
1754const AssignmentTarget = union(enum) {
1755 memory: Inst.Index,
1756 variable_declaration: struct {
1757 src_node: i32,
1758 string: NullTerminatedString,
1759 },
1760 field: struct {
1761 src_node: i32,
1762 base: Inst.Ref,
1763 field: Inst.Ref,
1764 },
1765
1766 fn toRef(target: AssignmentTarget, t: *Transform) !Inst.Ref {
1767 return switch (target) {
1768 .memory => |memory| memory.toRef(),
1769 .variable_declaration => |var_decl| try t.addRef(.load_decl, .{
1770 .src_node = var_decl.src_node,
1771 .string = var_decl.string,
1772 }),
1773 .field => |field| try t.addRef(.field, .{
1774 .src_node = field.src_node,
1775 .extra_index = try t.extra.append(t.gpa, Teg.Bin, .{
1776 .lhs = field.base,
1777 .rhs = field.field,
1778 }),
1779 }),
1780 };
1781 }
1782
1783 fn emit(target: AssignmentTarget, t: *Transform, rhs: Inst.Ref, ctx: BlockContext, node: NodeIndex) !Inst.Ref {
1784 return switch (target) {
1785 .memory => |memory| try t.addRef(.store, .{
1786 .src_node = ctx.nodeOffset(node),
1787 .extra_index = try t.extra.append(t.gpa, Teg.Store, .{
1788 .memory = memory,
1789 .operand = rhs,
1790 }),
1791 }),
1792 .variable_declaration => |var_decl| try t.addRef(.store_decl, .{
1793 .src_node = ctx.nodeOffset(node),
1794 .extra_index = try t.extra.append(t.gpa, Teg.StoreVarDecl, .{
1795 .var_decl = var_decl.string,
1796 .operand = rhs,
1797 }),
1798 }),
1799 .field => |field| try t.addRef(.store_field, .{
1800 .src_node = ctx.nodeOffset(node),
1801 .extra_index = try t.extra.append(t.gpa, Teg.StoreField, .{
1802 .base = field.base,
1803 .field = field.field,
1804 .operand = rhs,
1805 }),
1806 }),
1807 };
1808 }
1809};
1810
1557fn transformBlockStatement(1811fn transformBlockStatement(
1558 t: *Transform,1812 t: *Transform,
1559 ctx: BlockContext,1813 ctx: BlockContext,
...@@ -1592,7 +1846,7 @@ fn transformBlockStatement(...@@ -1592,7 +1846,7 @@ fn transformBlockStatement(
1592 return .void;1846 return .void;
1593 }1847 }
15941848
1595 if (BuiltInIds.has(inner_id)) {1849 if (built_in_ids.has(inner_id)) {
1596 try t.report.warn(.discard_builtin, .{ .node = var_decl.init });1850 try t.report.warn(.discard_builtin, .{ .node = var_decl.init });
1597 return .void;1851 return .void;
1598 }1852 }
...@@ -1602,7 +1856,7 @@ fn transformBlockStatement(...@@ -1602,7 +1856,7 @@ fn transformBlockStatement(
1602 return .void;1856 return .void;
1603 };1857 };
16041858
1605 lookup.discarded = node;1859 lookup.discarded = .init(node);
16061860
1607 return .void;1861 return .void;
1608 },1862 },
...@@ -1628,7 +1882,7 @@ fn transformBlockStatement(...@@ -1628,7 +1882,7 @@ fn transformBlockStatement(
1628 assert(isSliceInBuffer(u8, t.ast.utf8_source, id));1882 assert(isSliceInBuffer(u8, t.ast.utf8_source, id));
1629 }1883 }
16301884
1631 if (BuiltInIds.has(id)) {1885 if (built_in_ids.has(id)) {
1632 try t.report.err(.shadow_builtin_variable, .{ .node = node });1886 try t.report.err(.shadow_builtin_variable, .{ .node = node });
1633 return .void;1887 return .void;
1634 }1888 }
...@@ -1674,20 +1928,22 @@ fn transformBlockStatement(...@@ -1674,20 +1928,22 @@ fn transformBlockStatement(
1674 }1928 }
16751929
1676 gop.value_ptr.* = .{1930 gop.value_ptr.* = .{
1677 .node = node,1931 .src_node = node,
1678 .data = if (var_decl.is_const)1932 .kind = if (var_decl.is_const) .immutable else .mutable,
1933 .value = if (var_decl.is_const)
1679 .{ .immutable = data_coerced_ref }1934 .{ .immutable = data_coerced_ref }
1680 else1935 else
1681 .{1936 .{ .mutable = try t.addIndex(.memory, .{
1682 .mutable = try t.addIndex(.memory, .{1937 .src_node = ctx.nodeOffset(node),
1683 .src_node = ctx.nodeOffset(node),1938 .operand = data_coerced_ref,
1684 .operand = data_coerced_ref,1939 }) },
1685 }),1940 .discarded = .none,
1686 },1941 .flags = .{
1687 .type = if (type_ref) |ty|1942 .result_type_kind = if (type_ref != null) .present else .missing,
1688 .{ .present = ty }1943 .used = false,
1689 else1944 .mutated = false,
1690 .{ .missing = data_ref },1945 },
1946 .result_type_ref = if (type_ref) |ty| ty else data_ref,
1691 };1947 };
16921948
1693 return .void;1949 return .void;
...@@ -1767,7 +2023,7 @@ fn transformBlockStatement(...@@ -1767,7 +2023,7 @@ fn transformBlockStatement(
1767fn addUntracked(t: *Transform, comptime tag: Teg.Tag, data: tag.Data()) !Inst.Index {2023fn addUntracked(t: *Transform, comptime tag: Teg.Tag, data: tag.Data()) !Inst.Index {
1768 try t.insts.append(t.gpa, .{2024 try t.insts.append(t.gpa, .{
1769 .tag = tag,2025 .tag = tag,
1770 .data = EncodedData.encode(tag, data),2026 .data = .encode(tag, data),
1771 });2027 });
1772 return @enumFromInt(t.insts.len - 1);2028 return @enumFromInt(t.insts.len - 1);
1773}2029}
...@@ -1796,6 +2052,38 @@ fn scope(t: *Transform) *Scope {...@@ -1796,6 +2052,38 @@ fn scope(t: *Transform) *Scope {
1796 return &t.scopes.items[t.scopes.items.len - 1];2052 return &t.scopes.items[t.scopes.items.len - 1];
1797}2053}
17982054
2055fn pushCaptureScope(t: *Transform, start_index: Inst.Index) !void {
2056 try t.capture_scopes.append(t.arena, .{ .start_index = start_index });
2057}
2058
2059fn popCaptureScope(t: *Transform) void {
2060 t.captureScope().deinit(t);
2061 t.capture_scopes.items[t.capture_scopes.items.len - 1] = undefined;
2062 t.capture_scopes.items.len -= 1;
2063}
2064
2065fn captureScope(t: *Transform) *CaptureScope {
2066 return &t.capture_scopes.items[t.capture_scopes.items.len - 1];
2067}
2068
2069fn markCapturedRef(t: *Transform, ref: Inst.Ref) !void {
2070 switch (ref.decode()) {
2071 .value => {},
2072 .index => |idx| {
2073 var i = t.capture_scopes.items.len;
2074 while (i > 0) {
2075 i -= 1;
2076 const s = &t.capture_scopes.items[i];
2077
2078 if (@intFromEnum(idx) > @intFromEnum(s.start_index))
2079 break;
2080
2081 try s.symbol_uses.put(t.arena, idx, {});
2082 }
2083 },
2084 }
2085}
2086
1799fn getInstData(t: *Transform, index: Inst.Index) *EncodedData {2087fn getInstData(t: *Transform, index: Inst.Index) *EncodedData {
1800 return &t.insts.items(.data)[@intFromEnum(index)];2088 return &t.insts.items(.data)[@intFromEnum(index)];
1801}2089}
...@@ -1820,7 +2108,17 @@ fn scratchToIndexList(t: *Transform, scratch: core.U32Stack.Mark) !IndexList {...@@ -1820,7 +2108,17 @@ fn scratchToIndexList(t: *Transform, scratch: core.U32Stack.Mark) !IndexList {
1820 };2108 };
1821}2109}
18222110
1823fn lookupId(t: *Transform, id: []const u8) ?*Scope.Symbol {2111fn scratchToOptionalRefList(t: *Transform, scratch_top: core.U32Stack.Mark) !OptionalRefList {
2112 const items = scratch_top.slice(u32);
2113 const extra_top = t.extra.list.items.len;
2114 try t.extra.list.appendSlice(t.gpa, items);
2115 return .{
2116 .start = @intCast(extra_top),
2117 .len = @intCast(items.len),
2118 };
2119}
2120
2121fn lookupId(t: *Transform, id: []const u8) ?*Symbol {
1824 var i = t.scopes.items.len;2122 var i = t.scopes.items.len;
1825 while (i > 0) {2123 while (i > 0) {
1826 i -= 1;2124 i -= 1;
...@@ -1835,11 +2133,12 @@ fn addNullTerminatedString(t: *Transform, str: []const u8) !NullTerminatedString...@@ -1835,11 +2133,12 @@ fn addNullTerminatedString(t: *Transform, str: []const u8) !NullTerminatedString
1835 if (str.len == 0) {2133 if (str.len == 0) {
1836 return .empty;2134 return .empty;
1837 }2135 }
2136
1838 // loop through strings trying to find an existing match, and dedupe memory2137 // loop through strings trying to find an existing match, and dedupe memory
1839 var pos: usize = 0;2138 var pos: usize = 0;
1840 while (mem.indexOfScalarPos(u8, str, pos, 0)) |i| : (pos = i + 1) {2139 while (mem.indexOfScalarPos(u8, str, pos, 0)) |i| : (pos = i + 1) {
1841 const start_index = i - str.len;2140 const start_index = i - str.len;
1842 if (mem.eql(u8, t.string_bytes.items[start_index..][0..str.len], str)) {2141 if (mem.eql(u8, t.string_bytes.items[start_index..][0..str.len :0], str)) {
1843 const string: NullTerminatedString = @enumFromInt(pos);2142 const string: NullTerminatedString = @enumFromInt(pos);
1844 assert(mem.eql(u8, string.slice(t.string_bytes.items), str));2143 assert(mem.eql(u8, string.slice(t.string_bytes.items), str));
1845 return string;2144 return string;
...@@ -1862,7 +2161,7 @@ fn addSlicedString(t: *Transform, str: []const u8) !SlicedString {...@@ -1862,7 +2161,7 @@ fn addSlicedString(t: *Transform, str: []const u8) !SlicedString {
1862 // loop through strings trying to find an existing match, and dedupe memory2161 // loop through strings trying to find an existing match, and dedupe memory
1863 var pos: usize = 0;2162 var pos: usize = 0;
1864 while (mem.indexOfScalarPos(u8, str, pos, 0)) |i| : (pos = i + 1) {2163 while (mem.indexOfScalarPos(u8, str, pos, 0)) |i| : (pos = i + 1) {
1865 const start_index = i - str.len;2164 const start_index = i - str.len - 1;
1866 if (mem.eql(u8, t.string_bytes.items[start_index..][0..str.len], str)) {2165 if (mem.eql(u8, t.string_bytes.items[start_index..][0..str.len], str)) {
1867 const string: SlicedString = .{2166 const string: SlicedString = .{
1868 .start = @intCast(start_index),2167 .start = @intCast(start_index),
...@@ -1950,12 +2249,13 @@ const NullTerminatedString = Teg.NullTerminatedString;...@@ -1950,12 +2249,13 @@ const NullTerminatedString = Teg.NullTerminatedString;
1950const SlicedString = Teg.SlicedString;2249const SlicedString = Teg.SlicedString;
1951const RefList = Teg.RefList;2250const RefList = Teg.RefList;
1952const IndexList = Teg.IndexList;2251const IndexList = Teg.IndexList;
2252const OptionalRefList = Teg.OptionalRefList;
1953const EncodedData = Teg.EncodedData;2253const EncodedData = Teg.EncodedData;
1954const ExtraIndex = Teg.ExtraIndex;2254const ExtraIndex = Teg.ExtraIndex;
19552255
1956const extra = core.array;2256const array = core.array;
1957const ExtraStore = extra.ExtraStore(.teg, .@"var");2257const ExtraStore = array.ExtraStore(.teg, .@"var");
1958const Float = extra.Float;2258const Float = array.Float;
19592259
1960const Tokenizer = core.Tokenizer;2260const Tokenizer = core.Tokenizer;
1961const Token = Tokenizer.Token;2261const Token = Tokenizer.Token;
src/core/array.zig+38-19
...@@ -21,12 +21,12 @@ fn setAsU32(items: []u32, comptime T: type, index: usize, payload: T) void {...@@ -21,12 +21,12 @@ fn setAsU32(items: []u32, comptime T: type, index: usize, payload: T) void {
2121
22 else => brk: {22 else => brk: {
23 const info = @typeInfo(field.type);23 const info = @typeInfo(field.type);
24 if (info == .Enum) {24 if (info == .@"enum") {
25 items[i] = @intFromEnum(value);25 items[i] = @intFromEnum(value);
26 break :brk;26 break :brk;
27 }27 }
2828
29 const struct_data = info.Struct;29 const struct_data = info.@"struct";
30 if (struct_data.layout == .@"packed") {30 if (struct_data.layout == .@"packed") {
31 items[i] = @bitCast(value);31 items[i] = @bitCast(value);
32 break :brk;32 break :brk;
...@@ -48,11 +48,11 @@ fn getAsU32(items: []const u32, comptime T: type, index: usize) T {...@@ -48,11 +48,11 @@ fn getAsU32(items: []const u32, comptime T: type, index: usize) T {
4848
49 else => brk: {49 else => brk: {
50 const info = @typeInfo(T);50 const info = @typeInfo(T);
51 if (info == .Enum) {51 if (info == .@"enum") {
52 break :brk @enumFromInt(items[index]);52 break :brk @enumFromInt(items[index]);
53 }53 }
5454
55 const struct_data = info.Struct;55 const struct_data = info.@"struct";
56 if (struct_data.layout == .@"packed") {56 if (struct_data.layout == .@"packed") {
57 break :brk @bitCast(items[index]);57 break :brk @bitCast(items[index]);
58 }58 }
...@@ -99,9 +99,9 @@ pub const StoreKind = enum {...@@ -99,9 +99,9 @@ pub const StoreKind = enum {
99 comptime kind: StoreKind,99 comptime kind: StoreKind,
100 comptime Owner: type,100 comptime Owner: type,
101 ) type {101 ) type {
102 const ptr = @typeInfo(Owner).Pointer;102 const ptr = @typeInfo(Owner).pointer;
103 assert(ptr.size == .One);103 assert(ptr.size == .One);
104 const mutability = brk: for (@typeInfo(ptr.child).Struct.fields) |field| {104 const mutability = brk: for (@typeInfo(ptr.child).@"struct".fields) |field| {
105 if (std.mem.eql(u8, field.name, "extra")) {105 if (std.mem.eql(u8, field.name, "extra")) {
106 break :brk field.type.mutability;106 break :brk field.type.mutability;
107 }107 }
...@@ -148,15 +148,31 @@ pub fn EncodedData(comptime Tag: type, backing_type: type) type {...@@ -148,15 +148,31 @@ pub fn EncodedData(comptime Tag: type, backing_type: type) type {
148148
149 pub fn decode(data: @This(), comptime tag: Tag, proof: Tag) tag.Data() {149 pub fn decode(data: @This(), comptime tag: Tag, proof: Tag) tag.Data() {
150 assert(proof == tag); // incorrect memory access150 assert(proof == tag); // incorrect memory access
151 const Data = tag.Data();151 return data.decodeUnsafe(tag.Data(), proof);
152 const info = @typeInfo(Data);152 }
153 if (info == .Enum) {153
154 pub fn decodeUnsafe(data: @This(), comptime T: type, proof: Tag) T {
155 if (core.runtime_safety) {
156 const values = comptime brk: {
157 const values = std.enums.values(Tag);
158 var allowed = std.enums.EnumArray(Tag, bool).initFill(false);
159 for (values) |val| {
160 if (Tag == InternPool.Tag and val == .removed) continue;
161
162 allowed.set(val, val.Data() == T);
163 }
164 break :brk allowed;
165 };
166 assert(values.get(proof)); // incorrect memory access
167 }
168 const info = @typeInfo(T);
169 if (info == .@"enum") {
154 return @enumFromInt(@intFromEnum(data));170 return @enumFromInt(@intFromEnum(data));
155 }171 }
156 if (info == .Struct) {172 if (info == .@"struct") {
157 assert(backing_type == u64);173 assert(backing_type == u64);
158 var fields: [2]u32 = @bitCast(@intFromEnum(data));174 var fields: [2]u32 = @bitCast(@intFromEnum(data));
159 return getAsU32(&fields, Data, 0);175 return getAsU32(&fields, T, 0);
160 }176 }
161 return @bitCast(@intFromEnum(data));177 return @bitCast(@intFromEnum(data));
162 }178 }
...@@ -164,10 +180,10 @@ pub fn EncodedData(comptime Tag: type, backing_type: type) type {...@@ -164,10 +180,10 @@ pub fn EncodedData(comptime Tag: type, backing_type: type) type {
164 pub fn encode(comptime tag: Tag, data: tag.Data()) @This() {180 pub fn encode(comptime tag: Tag, data: tag.Data()) @This() {
165 const Data = tag.Data();181 const Data = tag.Data();
166 const info = @typeInfo(Data);182 const info = @typeInfo(Data);
167 if (info == .Enum) {183 if (info == .@"enum") {
168 return @enumFromInt(@intFromEnum(data));184 return @enumFromInt(@intFromEnum(data));
169 }185 }
170 if (info == .Struct) {186 if (info == .@"struct") {
171 comptime assert(backing_type == u64);187 comptime assert(backing_type == u64);
172 comptime assert(@sizeOf(Data) == 8);188 comptime assert(@sizeOf(Data) == 8);
173 var fields: [2]u32 = undefined;189 var fields: [2]u32 = undefined;
...@@ -190,12 +206,12 @@ pub fn extraSizeOf(comptime T: type) usize {...@@ -190,12 +206,12 @@ pub fn extraSizeOf(comptime T: type) usize {
190206
191 else => {207 else => {
192 const info = @typeInfo(T);208 const info = @typeInfo(T);
193 if (info == .Enum) {209 if (info == .@"enum") {
194 comptime assert(@sizeOf(info.Enum.tag_type) <= 32);210 comptime assert(@sizeOf(info.@"enum".tag_type) <= 32);
195 return 1;211 return 1;
196 }212 }
197213
198 const struct_data = info.Struct;214 const struct_data = info.@"struct";
199 if (struct_data.layout == .@"packed") {215 if (struct_data.layout == .@"packed") {
200 comptime assert(struct_data.backing_integer == u32);216 comptime assert(struct_data.backing_integer == u32);
201 return 1;217 return 1;
...@@ -216,13 +232,14 @@ pub fn ExtraStore(comptime set_kind: StoreKind, comptime set_mutability: Mutabil...@@ -216,13 +232,14 @@ pub fn ExtraStore(comptime set_kind: StoreKind, comptime set_mutability: Mutabil
216 return struct {232 return struct {
217 list: List = .{},233 list: List = .{},
218234
235 pub const empty: @This() = .{};
236
219 pub const kind = set_kind;237 pub const kind = set_kind;
220 pub const mutability = set_mutability;238 pub const mutability = set_mutability;
221239
222 const Store = @This();240 const Store = @This();
223241
224 const index_holder = ExtraWrap(kind);242 pub const Index = ExtraWrap(kind).Index;
225 pub const Index = index_holder.Index;
226243
227 pub const List = switch (mutability) {244 pub const List = switch (mutability) {
228 .@"const" => struct {245 .@"const" => struct {
...@@ -301,7 +318,7 @@ pub fn ExtraStore(comptime set_kind: StoreKind, comptime set_mutability: Mutabil...@@ -301,7 +318,7 @@ pub fn ExtraStore(comptime set_kind: StoreKind, comptime set_mutability: Mutabil
301 pub fn writeSlice(w: *TrailWriter, slice: anytype) void {318 pub fn writeSlice(w: *TrailWriter, slice: anytype) void {
302 const Child = std.meta.Child(@TypeOf(slice));319 const Child = std.meta.Child(@TypeOf(slice));
303 const child = @typeInfo(Child);320 const child = @typeInfo(Child);
304 comptime assert(Child == u32 or (child == .Enum and child.Enum.tag_type == u32));321 comptime assert(Child == u32 or (child == .@"enum" and child.@"enum".tag_type == u32));
305 assert(w.leftover >= slice.len);322 assert(w.leftover >= slice.len);
306 w.store.list.appendSliceAssumeCapacity(@ptrCast(slice));323 w.store.list.appendSliceAssumeCapacity(@ptrCast(slice));
307 w.leftover -= slice.len;324 w.leftover -= slice.len;
...@@ -433,6 +450,8 @@ pub const Float = struct {...@@ -433,6 +450,8 @@ pub const Float = struct {
433 }450 }
434};451};
435452
453const core = @import("mod.zig");
454
436const std = @import("std");455const std = @import("std");
437const assert = std.debug.assert;456const assert = std.debug.assert;
438const Allocator = std.mem.Allocator;457const Allocator = std.mem.Allocator;
src/core/js/Js.zig+1-2
...@@ -24,7 +24,7 @@ pub fn handleFunctionAir(...@@ -24,7 +24,7 @@ pub fn handleFunctionAir(
24 backend: *Js,24 backend: *Js,
25 comp: *Comp,25 comp: *Comp,
26 value: Value,26 value: Value,
27 air: *const Air,27 air: Air,
28 arena: Allocator,28 arena: Allocator,
29) !u32 {29) !u32 {
30 var ng = try NodeGen.init(comp.gpa, arena, &comp.intern_pool, air);30 var ng = try NodeGen.init(comp.gpa, arena, &comp.intern_pool, air);
...@@ -55,7 +55,6 @@ const Js = @This();...@@ -55,7 +55,6 @@ const Js = @This();
5555
56const std = @import("std");56const std = @import("std");
57const assert = std.debug.assert;57const assert = std.debug.assert;
58const log = std.log.scoped(.js);
59const Allocator = std.mem.Allocator;58const Allocator = std.mem.Allocator;
6059
61const core = @import("../mod.zig");60const core = @import("../mod.zig");
src/core/js/Link.zig+32-16
...@@ -10,7 +10,7 @@ backend: *Js,...@@ -10,7 +10,7 @@ backend: *Js,
1010
11/// Value is an index into `link_strings`11/// Value is an index into `link_strings`
12decls: std.AutoHashMapUnmanaged(InternPool.Value, LinkString.Index) = .{},12decls: std.AutoHashMapUnmanaged(InternPool.Value, LinkString.Index) = .{},
13link_strings: std.ArrayHashMapUnmanaged(13decl_strings: std.ArrayHashMapUnmanaged(
14 LinkString,14 LinkString,
15 GlobalOwner,15 GlobalOwner,
16 LinkString.Context,16 LinkString.Context,
...@@ -26,7 +26,6 @@ pub fn linkJsCode(link: *Link, entry_decl: Decl.Index, writer: Writer) !void {...@@ -26,7 +26,6 @@ pub fn linkJsCode(link: *Link, entry_decl: Decl.Index, writer: Writer) !void {
26 // names. We also need this list prepared so that local identifier names do not26 // names. We also need this list prepared so that local identifier names do not
27 // shadow functions or externals.27 // shadow functions or externals.
28 const root_decl = link.comp.intern_pool.declPtrConst(entry_decl);28 const root_decl = link.comp.intern_pool.declPtrConst(entry_decl);
29 assert(root_decl.name == .empty);
3029
31 const root_function = link.treeForFunction(root_decl.value());30 const root_function = link.treeForFunction(root_decl.value());
3231
...@@ -39,6 +38,12 @@ pub fn linkJsCode(link: *Link, entry_decl: Decl.Index, writer: Writer) !void {...@@ -39,6 +38,12 @@ pub fn linkJsCode(link: *Link, entry_decl: Decl.Index, writer: Writer) !void {
39 try dependency_queue.list.appendSlice(link.comp.gpa, root_function.top_level_value_deps);38 try dependency_queue.list.appendSlice(link.comp.gpa, root_function.top_level_value_deps);
4039
41 while (dependency_queue.next()) |value| {40 while (dependency_queue.next()) |value| {
41 const gop_decl = try link.decls.getOrPut(link.comp.gpa, value);
42 if (gop_decl.found_existing) {
43 dependency_queue.swapRemovePrevious();
44 continue;
45 }
46
42 const tree = link.treeForFunction(value);47 const tree = link.treeForFunction(value);
4348
44 for (tree.extern_symbols) |extern_symbol| {49 for (tree.extern_symbols) |extern_symbol| {
...@@ -55,24 +60,24 @@ pub fn linkJsCode(link: *Link, entry_decl: Decl.Index, writer: Writer) !void {...@@ -55,24 +60,24 @@ pub fn linkJsCode(link: *Link, entry_decl: Decl.Index, writer: Writer) !void {
55 const name = decl.name.toSlice(&link.comp.intern_pool);60 const name = decl.name.toSlice(&link.comp.intern_pool);
56 assert(name.len > 0);61 assert(name.len > 0);
5762
58 const gop = try link.link_strings.getOrPutContextAdapted(63 const gop_string = try link.decl_strings.getOrPutContextAdapted(
59 link.comp.gpa,64 link.comp.gpa,
60 name,65 name,
61 LinkString.SliceContext{ .link = link },66 LinkString.SliceContext{ .link = link },
62 .{ .link = link },67 .{ .link = link },
63 );68 );
64 if (gop.found_existing) {69 if (gop_string.found_existing) {
65 @panic("TODO: handle this name collision case");70 std.debug.panic("TODO: handle this name collision case: {s}", .{name});
66 }71 }
67 const name_str = try link.appendLinkString(name);72 const name_str = try link.appendLinkString(name);
68 gop.key_ptr.* = name_str;73 gop_string.key_ptr.* = name_str;
69 gop.value_ptr.* = .decl;74 gop_string.value_ptr.* = .decl;
70 try link.decls.putNoClobber(link.comp.gpa, value, @enumFromInt(gop.index));75 gop_decl.value_ptr.* = @enumFromInt(gop_string.index);
71 }76 }
7277
73 try writer.writeAll(std.fmt.comptimePrint(78 try writer.writeAll(std.fmt.comptimePrint(
74 "// Language Compiler v{}. Alpha quality software.\n",79 "// Language Compiler v{}. Alpha quality software.\n",
75 .{core.version},80 .{@import("config").version},
76 ));81 ));
7782
78 if (root_function.nodes.len > 1) {83 if (root_function.nodes.len > 1) {
...@@ -91,7 +96,8 @@ pub fn linkJsCode(link: *Link, entry_decl: Decl.Index, writer: Writer) !void {...@@ -91,7 +96,8 @@ pub fn linkJsCode(link: *Link, entry_decl: Decl.Index, writer: Writer) !void {
9196
92fn treeForFunction(link: *const Link, value: InternPool.Value) *Tree {97fn treeForFunction(link: *const Link, value: InternPool.Value) *Tree {
93 assert(value.valueTag(&link.comp.intern_pool) == .func); // must be a function98 assert(value.valueTag(&link.comp.intern_pool) == .func); // must be a function
94 return link.backend.codegen_map.getPtr(value) orelse unreachable;99 return link.backend.codegen_map.getPtr(value) orelse
100 unreachable; // did not codegen function
95}101}
96102
97const DependencyQueue = struct {103const DependencyQueue = struct {
...@@ -106,6 +112,10 @@ const DependencyQueue = struct {...@@ -106,6 +112,10 @@ const DependencyQueue = struct {
106 defer queue.i += 1;112 defer queue.i += 1;
107 return queue.list.items[queue.i];113 return queue.list.items[queue.i];
108 }114 }
115
116 pub fn swapRemovePrevious(queue: *DependencyQueue) void {
117 _ = queue.list.swapRemove(queue.i - 1);
118 }
109};119};
110120
111pub fn deinit(link: *Link) void {121pub fn deinit(link: *Link) void {
...@@ -114,7 +124,7 @@ pub fn deinit(link: *Link) void {...@@ -114,7 +124,7 @@ pub fn deinit(link: *Link) void {
114124
115 defer link.* = undefined;125 defer link.* = undefined;
116 link.decls.deinit(link.comp.gpa);126 link.decls.deinit(link.comp.gpa);
117 link.link_strings.deinit(link.comp.gpa);127 link.decl_strings.deinit(link.comp.gpa);
118 link.string_bytes.deinit(link.comp.gpa);128 link.string_bytes.deinit(link.comp.gpa);
119}129}
120130
...@@ -123,7 +133,7 @@ fn reserveExternSymbol(link: *Link, extern_symbol: String.WithLen) !void {...@@ -123,7 +133,7 @@ fn reserveExternSymbol(link: *Link, extern_symbol: String.WithLen) !void {
123 defer t.end();133 defer t.end();
124134
125 const name = extern_symbol.toSlice(&link.comp.intern_pool);135 const name = extern_symbol.toSlice(&link.comp.intern_pool);
126 const gop = try link.link_strings.getOrPutContextAdapted(136 const gop = try link.decl_strings.getOrPutContextAdapted(
127 link.comp.gpa,137 link.comp.gpa,
128 name,138 name,
129 LinkString.SliceContext{ .link = link },139 LinkString.SliceContext{ .link = link },
...@@ -152,7 +162,7 @@ fn valueRefIdentifier(link: *const Link, value: InternPool.Value) []const u8 {...@@ -152,7 +162,7 @@ fn valueRefIdentifier(link: *const Link, value: InternPool.Value) []const u8 {
152 var t = tracer.trace(@src());162 var t = tracer.trace(@src());
153 defer t.end();163 defer t.end();
154164
155 return (link.decls.get(value) orelse if (std.debug.runtime_safety) {165 return (link.decls.get(value) orelse if (core.runtime_safety) {
156 std.debug.panic("Value \"{}\" is not present in top level value map", .{166 std.debug.panic("Value \"{}\" is not present in top level value map", .{
157 value.dump(&link.comp.intern_pool),167 value.dump(&link.comp.intern_pool),
158 });168 });
...@@ -305,6 +315,13 @@ fn printValue(link: *Link, value: InternPool.Value, level: Level, writer: anytyp...@@ -305,6 +315,13 @@ fn printValue(link: *Link, value: InternPool.Value, level: Level, writer: anytyp
305 try writer.writeAll("]");315 try writer.writeAll("]");
306 },316 },
307317
318 .external_value => |external_value| {
319 for (external_value.key, 0..) |part, i| {
320 if (i != 0) try writer.writeAll(".");
321 try writer.writeAll(part.toSlice(&link.comp.intern_pool));
322 }
323 },
324
308 else => unreachable, // this value is not a plain_value325 else => unreachable, // this value is not a plain_value
309 }326 }
310}327}
...@@ -470,7 +487,7 @@ fn printExprLevel(...@@ -470,7 +487,7 @@ fn printExprLevel(
470487
471 for (args.slice(tree), 0..) |arg, i| {488 for (args.slice(tree), 0..) |arg, i| {
472 if (i != 0) try writer.writeAll(", ");489 if (i != 0) try writer.writeAll(", ");
473 try link.printExpr(tree, arg, writer);490 try link.printExprLevel(tree, arg, .assignment, writer);
474 }491 }
475492
476 try writer.writeAll(")");493 try writer.writeAll(")");
...@@ -806,7 +823,7 @@ const LinkString = struct {...@@ -806,7 +823,7 @@ const LinkString = struct {
806 _,823 _,
807824
808 pub fn toString(index: Index, link: *const Link) LinkString {825 pub fn toString(index: Index, link: *const Link) LinkString {
809 return link.link_strings.keys()[@intFromEnum(index)];826 return link.decl_strings.keys()[@intFromEnum(index)];
810 }827 }
811 };828 };
812829
...@@ -843,7 +860,6 @@ const Link = @This();...@@ -843,7 +860,6 @@ const Link = @This();
843const std = @import("std");860const std = @import("std");
844const assert = std.debug.assert;861const assert = std.debug.assert;
845const Wyhash = std.hash.Wyhash;862const Wyhash = std.hash.Wyhash;
846const log = std.log.scoped(.js_link);
847const Writer = std.io.AnyWriter;863const Writer = std.io.AnyWriter;
848864
849const core = @import("../mod.zig");865const core = @import("../mod.zig");
src/core/js/NodeGen.zig+81-52
...@@ -1,7 +1,7 @@...@@ -1,7 +1,7 @@
1//! Air -> Js.Tree1//! Air -> Js.Tree
2gpa: Allocator,2gpa: Allocator,
3arena: Allocator,3arena: Allocator,
4air: *const Air,4air: Air,
5ip: *InternPool,5ip: *InternPool,
66
7nodes: Node.List,7nodes: Node.List,
...@@ -28,12 +28,12 @@ const MapEntry = union {...@@ -28,12 +28,12 @@ const MapEntry = union {
28 },28 },
29};29};
3030
31pub fn init(gpa: Allocator, arena: Allocator, ip: *InternPool, air: *const Air) !NodeGen {31pub fn init(gpa: Allocator, arena: Allocator, ip: *InternPool, air: Air) !NodeGen {
32 const t = tracer.trace(@src());32 const t = tracer.trace(@src());
33 defer t.end();33 defer t.end();
3434
35 const air_node_map = try arena.alloc(MapEntry, air.insts.len);35 const air_node_map = try arena.alloc(MapEntry, air.insts.len);
36 if (std.debug.runtime_safety) {36 if (core.runtime_safety) {
37 @memset(air_node_map, .{ .none = {} });37 @memset(air_node_map, .{ .none = {} });
38 }38 }
39 const param_count = air.paramCount();39 const param_count = air.paramCount();
...@@ -99,8 +99,8 @@ pub fn build(c: *NodeGen) !void {...@@ -99,8 +99,8 @@ pub fn build(c: *NodeGen) !void {
99 const t = tracer.trace(@src());99 const t = tracer.trace(@src());
100 defer t.end();100 defer t.end();
101101
102 assert(c.air.instTag(.main_block) == .block);102 assert(c.air.instTag(.main_block) == .block); // expected Air to start with block
103 const statements = c.air.instData(.block, .main_block).slice(c.air);103 const statements = c.air.instData(.block, .main_block).slice(&c.air);
104104
105 const scratch_top = c.scratch.items.len;105 const scratch_top = c.scratch.items.len;
106 defer c.scratch.shrinkRetainingCapacity(scratch_top);106 defer c.scratch.shrinkRetainingCapacity(scratch_top);
...@@ -109,11 +109,9 @@ pub fn build(c: *NodeGen) !void {...@@ -109,11 +109,9 @@ pub fn build(c: *NodeGen) !void {
109109
110 if (statements.len > 0) {110 if (statements.len > 0) {
111 for (statements[0..c.param_count], 1..) |stmt, expected_id| {111 for (statements[0..c.param_count], 1..) |stmt, expected_id| {
112 assert(c.air.instTag(stmt) == .arg); // expected Air to start with param nodes
112 assert(@intFromEnum(stmt) == expected_id);113 assert(@intFromEnum(stmt) == expected_id);
113 assert(c.air.instTag(stmt) == .arg);114 c.air_node_map[@intFromEnum(stmt)] = .{ .node = try c.add(.local, .{ .id = @intFromEnum(stmt) - 1 }) };
114 c.air_node_map[@intFromEnum(stmt)] = .{
115 .node = try c.add(.local, .{ .id = @intFromEnum(stmt) - 1 }),
116 };
117 }115 }
118116
119 for (statements[c.param_count..]) |stmt| {117 for (statements[c.param_count..]) |stmt| {
...@@ -124,7 +122,7 @@ pub fn build(c: *NodeGen) !void {...@@ -124,7 +122,7 @@ pub fn build(c: *NodeGen) !void {
124 const items = c.scratch.items[scratch_top..];122 const items = c.scratch.items[scratch_top..];
125 const top = c.extra.list.items.len;123 const top = c.extra.list.items.len;
126 try c.extra.list.appendSlice(c.gpa, items);124 try c.extra.list.appendSlice(c.gpa, items);
127 c.nodes.items(.data)[0] = EncodedData.encode(.root, .{125 c.nodes.items(.data)[0] = .encode(.root, .{
128 .start = @intCast(top),126 .start = @intCast(top),
129 .len = @intCast(items.len),127 .len = @intCast(items.len),
130 });128 });
...@@ -139,13 +137,13 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void {...@@ -139,13 +137,13 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void {
139137
140 .block => try c.codeGenBlock(inst),138 .block => try c.codeGenBlock(inst),
141139
142 .js_global_ref => {140 // .js_global_ref => {
143 const data: InternPool.String.WithLen = c.air.instData(.js_global_ref, inst);141 // const data: InternPool.String.WithLen = c.air.instData(.js_global_ref, inst);
144 try c.extern_symbols.put(c.gpa, data, {});142 // try c.extern_symbols.put(c.gpa, data, {});
145 c.air_node_map[@intFromEnum(inst)] = .{143 // c.air_node_map[@intFromEnum(inst)] = .{
146 .node = try c.add(.global, .{ .name = data }),144 // .node = try c.add(.global, .{ .name = data }),
147 };145 // };
148 },146 // },
149147
150 .memory => {148 .memory => {
151 const i = @intFromEnum(inst);149 const i = @intFromEnum(inst);
...@@ -171,7 +169,6 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void {...@@ -171,7 +169,6 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void {
171 .sub,169 .sub,
172 .mul,170 .mul,
173 .div,171 .div,
174 .index,
175 => |tag| {172 => |tag| {
176 const data: Air.Bin = c.air.instData(tag, inst);173 const data: Air.Bin = c.air.instData(tag, inst);
177 const node = try c.add(@field(Tree.Tag, @tagName(tag)), .{174 const node = try c.add(@field(Tree.Tag, @tagName(tag)), .{
...@@ -181,6 +178,15 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void {...@@ -181,6 +178,15 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void {
181 try c.markExpression(inst, node);178 try c.markExpression(inst, node);
182 },179 },
183180
181 .field_array => {
182 const data: Air.Bin = c.air.instData(.field_array, inst);
183 const node = try c.add(.index, .{
184 .lhs = try c.fetchExpression(data.lhs),
185 .rhs = try c.fetchExpression(data.rhs),
186 });
187 try c.markExpression(inst, node);
188 },
189
184 .store => {190 .store => {
185 const data: Air.Store = c.air.instData(.store, inst);191 const data: Air.Store = c.air.instData(.store, inst);
186192
...@@ -253,10 +259,21 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void {...@@ -253,10 +259,21 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void {
253 },259 },
254260
255 .field_opaque => {261 .field_opaque => {
256 const data: Air.FieldOpaque = c.air.instData(.field_opaque, inst);262 const data: Air.Bin = c.air.instData(.field_opaque, inst);
257 const node = try c.add(.field, .{263 if (data.rhs.value()) |field_value| {
258 .base = try c.fetchExpression(data.base),264 if (field_value.typeOf(c.ip) == .string) {
259 .field = data.field,265 const node = try c.add(.field, .{
266 .base = try c.fetchExpression(data.lhs),
267 .field = field_value.asString(c.ip).tryToNullTerminatedString(c.ip, c.gpa) catch @panic("TODO"),
268 });
269 try c.markExpression(inst, node);
270 return;
271 }
272 }
273
274 const node = try c.add(.index, .{
275 .lhs = try c.fetchExpression(data.lhs),
276 .rhs = try c.fetchExpression(data.rhs),
260 });277 });
261 try c.markExpression(inst, node);278 try c.markExpression(inst, node);
262 },279 },
...@@ -288,7 +305,7 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void {...@@ -288,7 +305,7 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void {
288305
289 inline .call, .js_new => |tag| {306 inline .call, .js_new => |tag| {
290 const data: Air.Call = c.air.instData(tag, inst);307 const data: Air.Call = c.air.instData(tag, inst);
291 const call_data = data.call_data.get(c.air);308 const call_data = data.call_data.get(&c.air);
292309
293 try c.extra.list.ensureUnusedCapacity(c.gpa, call_data.args.len);310 try c.extra.list.ensureUnusedCapacity(c.gpa, call_data.args.len);
294 const start = c.extra.list.items.len;311 const start = c.extra.list.items.len;
...@@ -296,7 +313,7 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void {...@@ -296,7 +313,7 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void {
296313
297 const items = c.extra.list.items[start..][0..call_data.args.len];314 const items = c.extra.list.items[start..][0..call_data.args.len];
298315
299 for (call_data.args.slice(c.air), items) |arg, *dest| {316 for (call_data.args.slice(&c.air), items) |arg, *dest| {
300 dest.* = @intFromEnum(try c.fetchExpression(arg));317 dest.* = @intFromEnum(try c.fetchExpression(arg));
301 }318 }
302319
...@@ -312,7 +329,7 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void {...@@ -312,7 +329,7 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void {
312329
313 .@"if" => {330 .@"if" => {
314 const data: Air.If = c.air.instData(.@"if", inst);331 const data: Air.If = c.air.instData(.@"if", inst);
315 const extra = data.data.get(c.air);332 const extra = data.data.get(&c.air);
316 var cond = try c.fetchExpression(data.cond);333 var cond = try c.fetchExpression(data.cond);
317334
318 if (c.canLowerToExpression(extra.yes) and335 if (c.canLowerToExpression(extra.yes) and
...@@ -380,17 +397,31 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void {...@@ -380,17 +397,31 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void {
380 .@"break" => {397 .@"break" => {
381 const brk: Air.Break = c.air.instData(.@"break", inst);398 const brk: Air.Break = c.air.instData(.@"break", inst);
382399
383 const operand = try c.fetchExpression(brk.value);
384
385 // break #0 -> return400 // break #0 -> return
386 if (brk.block_ref == .main_block) {401 if (brk.block == .main_block) {
387 try c.addStmt(try c.add(.stmt_return, .{402 if (brk.value.isVoid()) {
388 .operand = operand.toOptional(),403 // If this is a void break at the end of the main block, nothing
389 }));404 // needs to be emitted as that is already what happens at the end
405 // of a block.
406 //
407 // This is also required for the main function, which cannot return
408 // anything. We don't want to emit return instructions here.
409 const main_insts = c.air.instData(.block, .main_block).slice(&c.air);
410 if (main_insts[main_insts.len - 1] == inst) {
411 return;
412 }
413
414 try c.addStmt(try c.add(.stmt_return, .{ .operand = .none }));
415 } else {
416 try c.addStmt(try c.add(.stmt_return, .{
417 .operand = (try c.fetchExpression(brk.value)).toOptional(),
418 }));
419 }
390 } else {420 } else {
391 const block = &c.air_node_map[@intFromEnum(brk.block_ref)].block;421 const operand = try c.fetchExpression(brk.value);
422 const block = &c.air_node_map[@intFromEnum(brk.block)].block;
392423
393 const block_insts = c.air.instData(.block, brk.block_ref).slice(c.air);424 const block_insts = c.air.instData(.block, brk.block).slice(&c.air);
394 if (block_insts[block_insts.len - 1] != inst) {425 if (block_insts[block_insts.len - 1] != inst) {
395 block.used = true;426 block.used = true;
396 }427 }
...@@ -418,7 +449,7 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void {...@@ -418,7 +449,7 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void {
418449
419 for (450 for (
420 struct_type.field_keys,451 struct_type.field_keys,
421 struct_init.valuesPtr(c.air),452 struct_init.valuesPtr(&c.air),
422 store_offset..,453 store_offset..,
423 store_offset + property_count..,454 store_offset + property_count..,
424 ) |key, value, key_i, value_i| {455 ) |key, value, key_i, value_i| {
...@@ -438,7 +469,7 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void {...@@ -438,7 +469,7 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void {
438 .array_init => {469 .array_init => {
439 const struct_init: Air.ArrayInit = c.air.instData(.array_init, inst);470 const struct_init: Air.ArrayInit = c.air.instData(.array_init, inst);
440471
441 const items = struct_init.list.get(c.air).slice(c.air);472 const items = struct_init.list.get(&c.air).slice(&c.air);
442 const store_offset = try c.extra.alloc(c.gpa, items.len);473 const store_offset = try c.extra.alloc(c.gpa, items.len);
443474
444 for (475 for (
...@@ -500,15 +531,15 @@ fn fetchInstExpression(c: *NodeGen, index: Air.Inst.Index) !Node.Index {...@@ -500,15 +531,15 @@ fn fetchInstExpression(c: *NodeGen, index: Air.Inst.Index) !Node.Index {
500 defer t.end();531 defer t.end();
501532
502 const data = c.air_node_map[@intFromEnum(index)];533 const data = c.air_node_map[@intFromEnum(index)];
503 if (std.debug.runtime_safety) {534 if (core.runtime_safety) {
504 const flags: Air.Flags = c.air.instFlags(index);535 const flags: Air.Flags = c.air.instFlags(index);
505 switch (flags.uses) {536 switch (flags.uses) {
506 .none => if (std.debug.runtime_safety) {537 .none => {
507 // incorrectly marked instruction as unused538 // incorrectly marked instruction as unused
508 std.debug.panic("called fetchInstExpression(#{d}), but no expression was generated", .{539 std.debug.panic("called fetchInstExpression(#{d}), but no expression was generated", .{
509 @intFromEnum(index),540 @intFromEnum(index),
510 });541 });
511 } else unreachable,542 },
512 .single => {},543 .single => {},
513 .multi => {544 .multi => {
514 // multi-references can only duplicate identifier nodes.545 // multi-references can only duplicate identifier nodes.
...@@ -543,6 +574,7 @@ fn valueToExpression(c: *NodeGen, v: Value) !Node.Index {...@@ -543,6 +574,7 @@ fn valueToExpression(c: *NodeGen, v: Value) !Node.Index {
543 .simple_value,574 .simple_value,
544 .struct_value,575 .struct_value,
545 .array_value,576 .array_value,
577 .external_value,
546 => return try c.add(.plain_value, .{ .value = v }),578 => return try c.add(.plain_value, .{ .value = v }),
547579
548 // Hoist functions as top level values580 // Hoist functions as top level values
...@@ -609,7 +641,7 @@ fn codeGenBranch(c: *NodeGen, inst: Air.Inst.Ref, temporary: ?Node.Index) !?Bran...@@ -609,7 +641,7 @@ fn codeGenBranch(c: *NodeGen, inst: Air.Inst.Ref, temporary: ?Node.Index) !?Bran
609 });641 });
610642
611 const block: Air.IndexList = c.air.instData(.block, i);643 const block: Air.IndexList = c.air.instData(.block, i);
612 for (block.slice(c.air)) |item| {644 for (block.slice(&c.air)) |item| {
613 try c.codeGenInst(item);645 try c.codeGenInst(item);
614 }646 }
615647
...@@ -661,7 +693,7 @@ fn codeGenBlock(c: *NodeGen, inst: Air.Inst.Index) !void {...@@ -661,7 +693,7 @@ fn codeGenBlock(c: *NodeGen, inst: Air.Inst.Index) !void {
661 };693 };
662 const block: Air.IndexList = c.air.instData(.block, inst);694 const block: Air.IndexList = c.air.instData(.block, inst);
663695
664 for (block.slice(c.air)) |item| {696 for (block.slice(&c.air)) |item| {
665 try c.codeGenInst(item);697 try c.codeGenInst(item);
666 }698 }
667699
...@@ -689,12 +721,12 @@ fn canLowerToExpression(c: *const NodeGen, inst: Air.Inst.Ref) bool {...@@ -689,12 +721,12 @@ fn canLowerToExpression(c: *const NodeGen, inst: Air.Inst.Ref) bool {
689 return switch (inst.decode()) {721 return switch (inst.decode()) {
690 .value => true,722 .value => true,
691 .index => |index| switch (c.air.instTag(index)) {723 .index => |index| switch (c.air.instTag(index)) {
692 .js_global_ref,
693 .call,724 .call,
694 .js_new,725 .js_new,
695 .arg,726 .arg,
696 .field_opaque,727 .field_opaque,
697 .field_struct,728 .field_struct,
729 .field_array,
698 .add,730 .add,
699 .sub,731 .sub,
700 .mul,732 .mul,
...@@ -707,7 +739,6 @@ fn canLowerToExpression(c: *const NodeGen, inst: Air.Inst.Ref) bool {...@@ -707,7 +739,6 @@ fn canLowerToExpression(c: *const NodeGen, inst: Air.Inst.Ref) bool {
707 .store,739 .store,
708 .struct_init,740 .struct_init,
709 .array_init,741 .array_init,
710 .index,
711 .array_len,742 .array_len,
712 => true,743 => true,
713744
...@@ -728,7 +759,7 @@ fn canLowerToExpression(c: *const NodeGen, inst: Air.Inst.Ref) bool {...@@ -728,7 +759,7 @@ fn canLowerToExpression(c: *const NodeGen, inst: Air.Inst.Ref) bool {
728759
729 .@"if" => {760 .@"if" => {
730 const call = c.air.instData(.@"if", index);761 const call = c.air.instData(.@"if", index);
731 const data = call.data.get(c.air);762 const data = call.data.get(&c.air);
732763
733 return c.canLowerToExpression(call.cond) and764 return c.canLowerToExpression(call.cond) and
734 c.canLowerToExpression(data.yes) and765 c.canLowerToExpression(data.yes) and
...@@ -758,7 +789,7 @@ fn add(c: *NodeGen, comptime tag: Tree.Tag, data: tag.Data()) !Node.Index {...@@ -758,7 +789,7 @@ fn add(c: *NodeGen, comptime tag: Tree.Tag, data: tag.Data()) !Node.Index {
758789
759 try c.nodes.append(c.gpa, .{790 try c.nodes.append(c.gpa, .{
760 .tag = tag,791 .tag = tag,
761 .data = EncodedData.encode(tag, data),792 .data = .encode(tag, data),
762 });793 });
763 return @enumFromInt(c.nodes.len - 1);794 return @enumFromInt(c.nodes.len - 1);
764}795}
...@@ -776,19 +807,17 @@ const std = @import("std");...@@ -776,19 +807,17 @@ const std = @import("std");
776const assert = std.debug.assert;807const assert = std.debug.assert;
777const Allocator = std.mem.Allocator;808const Allocator = std.mem.Allocator;
778809
779const InternPool = @import("../InternPool.zig");810const core = @import("../mod.zig");
811const InternPool = core.InternPool;
780const Value = InternPool.Value;812const Value = InternPool.Value;
781const NullTerminatedString = InternPool.NullTerminatedString;813const NullTerminatedString = InternPool.NullTerminatedString;
782814const Comp = core.Comp;
783const Comp = @import("../Comp.zig");
784const Decl = Comp.Decl;815const Decl = Comp.Decl;
785816const Tree = core.Js.Tree;
786const Tree = @import("Tree.zig");
787const EncodedData = Tree.EncodedData;817const EncodedData = Tree.EncodedData;
788const Node = Tree.Node;818const Node = Tree.Node;
789const LocalIndex = Tree.LocalIndex;819const LocalIndex = Tree.LocalIndex;
820const Air = core.Air;
821const dod = core.array;
790822
791const Air = @import("../Air.zig");
792
793const dod = @import("../array.zig");
794const tracer = @import("tracer");823const tracer = @import("tracer");
src/core/js/Tree.zig+3-3
...@@ -10,8 +10,8 @@ top_level_value_deps: []Value,...@@ -10,8 +10,8 @@ top_level_value_deps: []Value,
10extra: ExtraStore,10extra: ExtraStore,
1111
12pub const LocalIndex = u32;12pub const LocalIndex = u32;
13pub const ExtraStore = extra.ExtraStore(.js_tree, .@"const");13pub const ExtraStore = array.ExtraStore(.js_tree, .@"const");
14pub const EncodedData = extra.EncodedData(Tag, u64);14pub const EncodedData = array.EncodedData(Tag, u64);
15pub const ExtraIndex = ExtraStore.Index;15pub const ExtraIndex = ExtraStore.Index;
1616
17pub const Tag = enum {17pub const Tag = enum {
...@@ -299,6 +299,6 @@ const String = InternPool.String;...@@ -299,6 +299,6 @@ const String = InternPool.String;
299const Comp = @import("../Comp.zig");299const Comp = @import("../Comp.zig");
300const Decl = Comp.Decl;300const Decl = Comp.Decl;
301301
302const extra = @import("../array.zig");302const array = @import("../array.zig");
303303
304const Unused = @import("../Ast.zig").Unused;304const Unused = @import("../Ast.zig").Unused;
src/core/mod.zig+61-34
...@@ -2,57 +2,35 @@...@@ -2,57 +2,35 @@
22
3// sort-lines: start3// sort-lines: start
44
5/// Analyzed Intermediate Representation.
6pub const Air = @import("Air.zig");5pub const Air = @import("Air.zig");
7
8pub const array = @import("array.zig");6pub const array = @import("array.zig");
9
10/// Abstract Syntax Tree - Preserves all syntax forms.
11pub const Ast = @import("Ast.zig");7pub const Ast = @import("Ast.zig");
12
13pub const Cache = @import("Cache.zig");8pub const Cache = @import("Cache.zig");
14
15/// Language Compilation Unit
16pub const Comp = @import("Comp.zig");9pub const Comp = @import("Comp.zig");
17
18/// Storage for shared objects (`Value`/`Decl`/`Namespace`) in a compilation
19pub const InternPool = @import("InternPool.zig");10pub const InternPool = @import("InternPool.zig");
20
21/// JavaScript backend and related utilities
22pub const Js = @import("js/Js.zig");11pub const Js = @import("js/Js.zig");
23
24/// Recursive descent parser. Produces `Ast`
25pub const Parse = @import("Parse.zig");12pub const Parse = @import("Parse.zig");
26
27pub const Report = @import("Report.zig");13pub const Report = @import("Report.zig");
28
29/// Semantic Analysis visits a `Teg` declaration and produces `Air`
30pub const Sema = @import("Sema.zig");14pub const Sema = @import("Sema.zig");
31
32pub const Stack = @import("stack.zig").Stack;15pub const Stack = @import("stack.zig").Stack;
33pub const U32Stack = Stack(u32);16pub const U32Stack = Stack(u32);
34
35pub const StoredTrace = @import("StoredTrace.zig");17pub const StoredTrace = @import("StoredTrace.zig");
36pub const DebugStoredTrace = StoredTrace.DebugStoredTrace;18pub const DebugStoredTrace = StoredTrace.DebugStoredTrace;
3719pub const Queue = @import("queue.zig").Queue;
38/// Untyped Intermediate Representation. Flat instruction list
39pub const Teg = @import("Teg.zig");20pub const Teg = @import("Teg.zig");
40
41/// Iterate tokens from a `[:0]const u8`. Includes code to decode/encode string escapes.
42pub const Tokenizer = @import("Tokenizer.zig");21pub const Tokenizer = @import("Tokenizer.zig");
43
44/// Transform pass converts `Ast` into `Teg`
45pub const Transform = @import("Transform.zig");22pub const Transform = @import("Transform.zig");
46
47pub const unicode_props = @import("unicode_props.zig");23pub const unicode_props = @import("unicode_props.zig");
4824
49// sort-lines: end25// sort-lines: end
5026
51// The rest of this file is utility functions, which are not specific to any of27// The rest of this file is small utility functions, which are not specific to
52// the above namespaces. Remember Zig discourages namespaces like `utils`28// any of the above namespaces. Remember Zig discourages namespaces like `utils`.
5329
54/// Language SDK version according to the latest entry in `CHANGELOG.md`30pub const runtime_safety = switch (@import("builtin").mode) {
55pub const version = @import("build_options").version;31 .Debug, .ReleaseSafe => true,
32 .ReleaseFast, .ReleaseSmall => false,
33};
5634
57/// This is an alternative to `std.debug.assert`, where you want to print some35/// This is an alternative to `std.debug.assert`, where you want to print some
58/// debug info instead of "reached unreachable code". Usage is as follows:36/// debug info instead of "reached unreachable code". Usage is as follows:
...@@ -64,7 +42,7 @@ pub const version = @import("build_options").version;...@@ -64,7 +42,7 @@ pub const version = @import("build_options").version;
64/// This allows you to perform any number of side effects in the failure case42/// This allows you to perform any number of side effects in the failure case
65/// with certainty that they are not analyzed in unsafe modes.43/// with certainty that they are not analyzed in unsafe modes.
66pub inline fn assertFailed(cond: bool) bool {44pub inline fn assertFailed(cond: bool) bool {
67 if (std.debug.runtime_safety) {45 if (runtime_safety) {
68 return !cond;46 return !cond;
69 } else if (!cond) {47 } else if (!cond) {
70 unreachable; // assertion failure48 unreachable; // assertion failure
...@@ -74,13 +52,13 @@ pub inline fn assertFailed(cond: bool) bool {...@@ -74,13 +52,13 @@ pub inline fn assertFailed(cond: bool) bool {
74/// If `value` can be cast to `i32`52/// If `value` can be cast to `i32`
75pub fn inInt32Range(value: anytype) bool {53pub fn inInt32Range(value: anytype) bool {
76 return switch (@typeInfo(@TypeOf(value))) {54 return switch (@typeInfo(@TypeOf(value))) {
77 .Int,55 .int,
78 .ComptimeInt,56 .comptime_int,
79 => value >= std.math.minInt(i32) and57 => value >= std.math.minInt(i32) and
80 value <= std.math.maxInt(i32),58 value <= std.math.maxInt(i32),
8159
82 .Float,60 .float,
83 .ComptimeFloat,61 .comptime_float,
84 => std.math.isFinite(value) and62 => std.math.isFinite(value) and
85 value >= std.math.minInt(i32) and63 value >= std.math.minInt(i32) and
86 value <= std.math.maxInt(i32) and64 value <= std.math.maxInt(i32) and
...@@ -134,6 +112,16 @@ pub const OutStream = struct {...@@ -134,6 +112,16 @@ pub const OutStream = struct {
134 };112 };
135 }113 }
136114
115 var stderr_writer: std.fs.File.Writer = undefined;
116 pub fn getStderr() OutStream {
117 const stderr = std.io.getStdErr();
118 stderr_writer = stderr.writer();
119 return .{
120 .writer = stderr_writer.any(),
121 .tty = std.io.tty.detectConfig(stderr),
122 };
123 }
124
137 pub fn print(out: OutStream, comptime fmt: []const u8, args: anytype) Error!void {125 pub fn print(out: OutStream, comptime fmt: []const u8, args: anytype) Error!void {
138 out.writer.print(fmt, args) catch |e| switch (e) {126 out.writer.print(fmt, args) catch |e| switch (e) {
139 error.OutOfMemory => return Error.OutOfMemory,127 error.OutOfMemory => return Error.OutOfMemory,
...@@ -149,9 +137,48 @@ pub const OutStream = struct {...@@ -149,9 +137,48 @@ pub const OutStream = struct {
149 }137 }
150};138};
151139
140pub const SerializeTarget = union(enum) {
141 file: std.fs.File,
142 writer: std.io.AnyWriter,
143
144 pub fn writev(target: SerializeTarget, vecs: []std.posix.iovec_const) !void {
145 return switch (target) {
146 .file => |file| try file.writevAll(vecs),
147 .writer => |w| for (vecs) |iov| {
148 try w.writeAll(iov.base[0..iov.len]);
149 },
150 };
151 }
152};
153
154pub fn iovec(slice: anytype) if (@typeInfo(@TypeOf(slice)).pointer.is_const)
155 std.posix.iovec_const
156else
157 std.posix.iovec {
158 return .{ .base = slice.ptr, .len = slice.len };
159}
160pub fn iovecConst(slice: []const u8) std.posix.iovec_const {
161 return .{ .base = slice.ptr, .len = slice.len };
162}
163
152const std = @import("std");164const std = @import("std");
153const assert = std.debug.assert;165const assert = std.debug.assert;
154166
155comptime {167comptime {
156 std.testing.refAllDecls(@This());168 std.testing.refAllDecls(@This());
157}169}
170
171pub fn FieldType(comptime T: type, comptime field: FieldEnum(T)) type {
172 if (@typeInfo(T) != .@"struct" and @typeInfo(T) != .@"union") {
173 @compileError("Expected struct or union, found '" ++ @typeName(T) ++ "'");
174 }
175
176 @setEvalBranchQuota(std.math.maxInt(u32)); // i'm so sorry mlugg
177 return std.meta.fieldInfo(T, field).type;
178}
179
180/// Returns an enum with a variant named after each field of `T`.
181pub fn FieldEnum(comptime T: type) type {
182 @setEvalBranchQuota(std.math.maxInt(u32)); // i'm so sorry mlugg
183 return std.meta.FieldEnum(T);
184}
src/core/pool.zig created+49
...@@ -0,0 +1,49 @@
1/// A memory pool lets you reuse shared objects. When objects are released,
2/// their state is not reset. This allows you to use it with objects such
3/// as ArenaAllocator.
4fn ObjectPool(Item: type, static_count: usize, dynamic_limit: usize, deinitFunction: anytype) type {
5 return struct {
6 static_data: [static_count]Item,
7 static_usage: std.StaticBitSet(static_count),
8 dynamic_unused: if (have_dynamic_pool) LinkedList else void,
9 dynamic_count: if (have_dynamic_limit) std.math.IntFittingRange(0, dynamic_limit) else void,
10
11 const have_dynamic_pool = dynamic_limit > 0;
12 const have_dynamic_limit = have_dynamic_pool and dynamic_limit < std.math.maxInt(usize);
13
14 const LinkedList = std.SinglyLinkedList(Item);
15
16 const empty: @This() = .{
17 .static_data = undefined,
18 .static_usage = .initFull(),
19 .dynamic_unused = .{},
20 };
21
22 pub fn get(pool: *@This(), allocator: std.mem.Allocator) !*Item {
23 if (pool.static_usage.toggleFirstSet()) |index| {
24 return &pool.static_data[index];
25 }
26 if (have_dynamic_pool) {
27 if (pool.dynamic_unused.popFirst()) |first| {
28 if (have_dynamic_limit)
29 pool.dynamic_count -= 1;
30 return &first.data;
31 }
32 }
33 return &(try allocator.create(LinkedList.Node)).data;
34 }
35
36 pub fn release(pool: *@This(), item: *Item, allocator: std.mem.Allocator) void {
37 deinitFunction(item);
38 _ = pool; // autofix
39 _ = allocator; // autofix
40 //
41 }
42 };
43}
44
45fn AutoObjectPool(Item: type) type {
46 return ObjectPool(Item, 32, 32, Item.deinit);
47}
48
49const std = @import("std");
src/core/queue.zig created+47
...@@ -0,0 +1,47 @@
1const std = @import("std");
2const assert = std.debug.assert;
3const Allocator = std.mem.Allocator;
4
5/// A thread-safe queue where any number of threads may consume items,
6/// and any of those same threads may concurrently append new items.
7///
8/// This relevant for multi-threaded compilation, where new tasks should
9/// be picked up by new threads
10pub fn Queue(comptime Item: type) type {
11 return struct {
12 const Node = std.SinglyLinkedList(Item).Node;
13
14 head: ?*Node = null,
15 tail: ?*Node = null,
16
17 object_pool: std.SinglyLinkedList(Item) = .{},
18
19 pub fn init(thread_count: usize) @This() {
20 assert(thread_count != 0);
21 if (thread_count != 1)
22 @panic("Multi-threaded queue is not yet implemented");
23 return .{};
24 }
25
26 pub fn push(queue: *@This(), allocator: Allocator, item: Item) !void {
27 const new_node = queue.object_pool.popFirst() orelse
28 try allocator.create(Node);
29
30 new_node.* = .{ .data = item };
31 if (queue.tail) |tail| {
32 tail.next = new_node;
33 } else {
34 queue.head = new_node;
35 }
36 queue.tail = new_node;
37 }
38
39 pub fn next(queue: *@This()) ?Item {
40 const head = queue.head orelse return null;
41 queue.head = head.next;
42 if (queue.head == null) queue.tail = null;
43 queue.object_pool.prepend(head);
44 return head.data;
45 }
46 };
47}
src/core/stack.zig+7-3
...@@ -9,14 +9,16 @@ pub fn Stack(comptime Base: type) type {...@@ -9,14 +9,16 @@ pub fn Stack(comptime Base: type) type {
9 return struct {9 return struct {
10 const Self = @This();10 const Self = @This();
1111
12 list: std.ArrayListUnmanaged(Base) = .{},12 list: std.ArrayListUnmanaged(Base),
13
14 pub const empty: Self = .{ .list = .empty };
1315
14 pub const Mark = struct {16 pub const Mark = struct {
15 stack: *Self,17 stack: *Self,
16 index: usize,18 index: usize,
1719
18 pub fn deinit(m: Mark) void {20 pub fn deinit(m: Mark) void {
19 if (std.debug.runtime_safety)21 if (core.runtime_safety)
20 @memset(m.slice(Base), undefined);22 @memset(m.slice(Base), undefined);
21 m.stack.list.shrinkRetainingCapacity(m.index);23 m.stack.list.shrinkRetainingCapacity(m.index);
22 }24 }
...@@ -106,7 +108,7 @@ pub fn Stack(comptime Base: type) type {...@@ -106,7 +108,7 @@ pub fn Stack(comptime Base: type) type {
106 return @field(types, @tagName(field));108 return @field(types, @tagName(field));
107 }109 }
108110
109 pub fn slice(r: @This(), comptime field: FieldEnum) []Type(field) {111 pub fn items(r: @This(), comptime field: FieldEnum) []Type(field) {
110 const f = comptime std.meta.fieldIndex(Types, @tagName(field)).?;112 const f = comptime std.meta.fieldIndex(Types, @tagName(field)).?;
111 return @ptrCast(r.stack.list.items[r.starts[f]..][0..r.lengths[f]]);113 return @ptrCast(r.stack.list.items[r.starts[f]..][0..r.lengths[f]]);
112 }114 }
...@@ -154,3 +156,5 @@ pub fn Stack(comptime Base: type) type {...@@ -154,3 +156,5 @@ pub fn Stack(comptime Base: type) type {
154const std = @import("std");156const std = @import("std");
155const assert = std.debug.assert;157const assert = std.debug.assert;
156const Allocator = std.mem.Allocator;158const Allocator = std.mem.Allocator;
159
160const core = @import("./mod.zig");
src/core/transform_cli.zig created+85
...@@ -0,0 +1,85 @@
1//! Minimal CLI via `zig build transform -- filepath`
2//! Use this only for working on Parse+Transform
3
4pub fn main() !u8 {
5 try core.unicode_props.init();
6
7 const use_gpa = @import("builtin").mode == .Debug;
8 var gpa = if (use_gpa)
9 std.heap.GeneralPurposeAllocator(.{
10 .stack_trace_frames = 100,
11 }){}
12 else
13 std.heap.ArenaAllocator.init(std.heap.page_allocator);
14 defer _ = if (use_gpa) gpa.deinit();
15 const alloc = gpa.allocator();
16
17 var args = try std.process.argsWithAllocator(alloc);
18 defer args.deinit();
19
20 _ = args.skip();
21 const file_path = args.next() orelse {
22 std.debug.print("Missing filepath argument\n", .{});
23 return 1;
24 };
25
26 const out = core.OutStream.getStderr();
27
28 const code = try std.fs.cwd()
29 .readFileAllocOptions(alloc, file_path, std.math.maxInt(u32), null, @alignOf(u8), 0);
30 defer alloc.free(code);
31
32 var err_data: core.Parse.AstError.WithTrace = undefined;
33 var ast = core.Parse.parse(
34 alloc,
35 code,
36 &err_data,
37 ) catch |err| switch (err) {
38 error.OutOfMemory => return err,
39 error.ParseFail => {
40 var report = try err_data.toReport(alloc);
41 defer report.deinit(alloc);
42
43 report.dump(
44 .{ .single_file = .{
45 .allocator = alloc,
46 .url = file_path,
47 .file = .{ .utf8_source = code },
48 } },
49 out,
50 ) catch {};
51
52 return 1;
53 },
54 };
55 defer ast.deinit(alloc);
56
57 var teg_report: core.Report = undefined;
58 var teg = core.Transform.transform(ast, alloc, &teg_report) catch |err| switch (err) {
59 error.OutOfMemory => return err,
60 error.TransformFail => {
61 defer teg_report.deinit(alloc);
62 teg_report.dump(
63 .{ .single_file = .{
64 .allocator = alloc,
65 .url = file_path,
66 .file = .{ .ast = &ast },
67 } },
68 out,
69 ) catch {};
70 return 1;
71 },
72 };
73 defer teg.deinit(alloc);
74
75 // const file = try std.fs.cwd().createFile("serialized.teg", .{});
76 // defer file.close();
77 // try teg.serialize(.{ .file = file });
78
79 try teg.dumpToWriter(out.writer, out.tty);
80
81 return 0;
82}
83
84const std = @import("std");
85const core = @import("core");
src/core/unicode_props.zig+1-1
...@@ -23,7 +23,7 @@ pub fn init() !void {...@@ -23,7 +23,7 @@ pub fn init() !void {
23 if (allocation_size == 0) {23 if (allocation_size == 0) {
24 var logging_alloc = std.heap.loggingAllocator(std.heap.page_allocator);24 var logging_alloc = std.heap.loggingAllocator(std.heap.page_allocator);
25 props = try PropsData.init(logging_alloc.allocator());25 props = try PropsData.init(logging_alloc.allocator());
26 std.log.info("please write the above numbers into unicode.zig to prealloc this", .{});26 std.log.err("please write the above numbers into unicode.zig to prealloc this", .{});
27 std.process.exit(1);27 std.process.exit(1);
28 }28 }
2929
src/deps/quickjs/build.zig+1-1
...@@ -146,7 +146,7 @@ pub fn build(b: *Build) !void {...@@ -146,7 +146,7 @@ pub fn build(b: *Build) !void {
146 .optimize = optimize,146 .optimize = optimize,
147 .link_libc = true,147 .link_libc = true,
148 });148 });
149 const module2 = translate_libc.addModule("quickjs_libc");149 const module2 = translate_libc.addModule("quickjs-libc");
150 if (pthread.pthreads) |pt| translate_libc.addIncludeDir(pt.include.getPath(b));150 if (pthread.pthreads) |pt| translate_libc.addIncludeDir(pt.include.getPath(b));
151 module2.linkLibrary(libquickjs);151 module2.linkLibrary(libquickjs);
152 module2.linkLibrary(libquickjs_libc);152 module2.linkLibrary(libquickjs_libc);
src/format/Format.zig created+84
...@@ -0,0 +1,84 @@
1//! `Format` converts a parsed Ast back into source code. This uses
2//! the fact that `Ast` contains the original source code slice in
3//! order to preserve comments despite not representing them as nodes.
4ast: Ast,
5writer: AnyWriter,
6ident: usize = 0,
7
8// Formatting options such as tab size or character are intentionally
9// not exposed as configuration options. This makes all source files
10// follow the same style guide.
11const spaces_per_level = 3;
12
13pub fn format(ast: Ast, writer: AnyWriter) !void {
14 var state: Format = .{ .ast = ast, .writer = writer };
15
16 // Print the top-level documentation comment
17 {
18 var i: u32 = 0;
19 while (state.ast.tokenTag(i) == .module_doc_comment) {
20 i += 1;
21 }
22 if (i > 0) {
23 const start = state.ast.tokenOffset(0);
24 const end = (mem.lastIndexOf(
25 u8,
26 state.ast.utf8_source[0..state.ast.tokenOffset(i)],
27 "\n",
28 ) orelse unreachable) + 1;
29 try state.writer.writeAll(state.ast.utf8_source[start..end]);
30 }
31 }
32
33 try state.formatNode(0);
34
35 assert(state.ident == 0);
36}
37
38fn formatNode(state: Format, node: NodeIndex) !void {
39 switch (state.ast.nodeTag(node)) {
40 .root => {
41 for (state.ast.nodeData(node).list.slice(&state.ast)) |child| {
42 try state.formatNode(child);
43 }
44 },
45 .fn_decl => {
46 const decl = state.ast.fullFnDecl(node);
47
48 if (decl.public) {
49 try state.writer.writeAll("pub ");
50 }
51 try state.writer.writeAll("fn ");
52 try state.rewriteIdentifier(decl.id);
53 try state.writer.writeAll("(");
54 try state.writer.writeAll(")");
55
56 if (decl.return_type) |return_type| {
57 try state.writer.writeAll(" -> ");
58 try state.formatNode(return_type);
59 }
60
61 try state.writer.writeAll(" ");
62
63 try state.formatNode(decl.body);
64
65 try state.writer.writeAll("\n");
66 },
67 else => |unhandled_tag| std.debug.panic("TODO: unhandled tag .{s}", .{@tagName(unhandled_tag)}),
68 }
69}
70
71fn rewriteIdentifier(state: Format, id: []const u8) !void {
72 try state.writer.writeAll(id);
73}
74
75const Format = @This();
76
77const std = @import("std");
78const mem = std.mem;
79const assert = std.debug.assert;
80const AnyWriter = std.io.AnyWriter;
81
82const core = @import("core");
83const Ast = core.Ast;
84const NodeIndex = Ast.NodeIndex;
src/format/format_cli.zig created+63
...@@ -0,0 +1,63 @@
1//! Minimal formatter CLI via `zig build format -- filepath`
2//! Use this only for working on the formatter.
3
4pub fn main() !u8 {
5 try core.unicode_props.init();
6
7 const use_gpa = @import("builtin").mode == .Debug;
8 var gpa = if (use_gpa)
9 std.heap.GeneralPurposeAllocator(.{
10 .stack_trace_frames = 100,
11 }){}
12 else
13 std.heap.ArenaAllocator.init(std.heap.page_allocator);
14 defer _ = if (use_gpa) gpa.deinit();
15 const alloc = gpa.allocator();
16
17 var args = try std.process.argsWithAllocator(alloc);
18 defer args.deinit();
19
20 _ = args.skip();
21 const file_path = args.next() orelse {
22 std.debug.print("Missing filepath argument\n", .{});
23 return 1;
24 };
25
26 const code = try std.fs.cwd()
27 .readFileAllocOptions(alloc, file_path, std.math.maxInt(u32), null, @alignOf(u8), 0);
28 defer alloc.free(code);
29
30 var err_data: core.Parse.AstError.WithTrace = undefined;
31 var ast = core.Parse.parse(
32 alloc,
33 code,
34 &err_data,
35 ) catch |err| switch (err) {
36 error.OutOfMemory => return err,
37 error.ParseFail => {
38 var report = try err_data.toReport(alloc);
39 defer report.deinit(alloc);
40
41 report.dump(
42 .{ .single_file = .{
43 .allocator = alloc,
44 .url = file_path,
45 .file = .{ .utf8_source = code },
46 } },
47 .getStderr(),
48 ) catch {};
49
50 return err;
51 },
52 };
53 defer ast.deinit(alloc);
54
55 const stdout = std.io.getStdOut();
56 try Format.format(ast, stdout.writer().any());
57
58 return 0;
59}
60
61const std = @import("std");
62const core = @import("core");
63const Format = @import("format");
src/loader/Loader.zig+2-4
...@@ -252,7 +252,7 @@ pub const Failure = struct {...@@ -252,7 +252,7 @@ pub const Failure = struct {
252 std.posix.PollError ||252 std.posix.PollError ||
253 error{253 error{
254 SchemeNotSupported,254 SchemeNotSupported,
255 /// `stat` lied255 /// The system `stat` has lied or otherwise file corruption
256 UnexpectedEndOfFile,256 UnexpectedEndOfFile,
257 /// non-200 status codes are errors257 /// non-200 status codes are errors
258 HttpStatus,258 HttpStatus,
...@@ -1032,9 +1032,7 @@ fn tickDownload(...@@ -1032,9 +1032,7 @@ fn tickDownload(
10321032
1033fn writeToCache(cache: std.fs.Dir, data: Module.Data) !void {1033fn writeToCache(cache: std.fs.Dir, data: Module.Data) !void {
1034 var rng: [16]u8 = undefined;1034 var rng: [16]u8 = undefined;
1035 std.posix.getrandom(&rng) catch {1035 std.crypto.random.bytes(&rng);
1036 rng = std.mem.asBytes(&std.time.nanoTimestamp()).*;
1037 };
10381036
1039 // TODO: i do not trust the atomicy of this on windows1037 // TODO: i do not trust the atomicy of this on windows
1040 // i think renameat will not use directory handles, when that is totally possible1038 // i think renameat will not use directory handles, when that is totally possible
src/manual/generate-man.zig+1-1
...@@ -12,7 +12,7 @@ const ArrayListUnmanaged = std.ArrayListUnmanaged;...@@ -12,7 +12,7 @@ const ArrayListUnmanaged = std.ArrayListUnmanaged;
12const assert = std.debug.assert;12const assert = std.debug.assert;
1313
14comptime {14comptime {
15 assert(std.debug.runtime_safety);15 assert(core.runtime_safety);
16}16}
1717
18pub fn main() !void {18pub fn main() !void {
src/wasm/enum_codegen.zig+10-11
...@@ -1,9 +1,3 @@...@@ -1,9 +1,3 @@
1const std = @import("std");
2const Tokenizer = @import("Tokenizer.zig");
3const Ast = @import("Ast.zig");
4const Teg = @import("Teg.zig");
5// const InternPool = @import("InternPool.zig");
6
7pub fn main() !void {1pub fn main() !void {
8 var buf = std.io.bufferedWriter(std.io.getStdOut().writer());2 var buf = std.io.bufferedWriter(std.io.getStdOut().writer());
9 defer buf.flush() catch {};3 defer buf.flush() catch {};
...@@ -12,17 +6,22 @@ pub fn main() !void {...@@ -12,17 +6,22 @@ pub fn main() !void {
12 inline for (.{6 inline for (.{
13 .{ "TokenTag", Tokenizer.Token.Tag },7 .{ "TokenTag", Tokenizer.Token.Tag },
14 .{ "NodeTag", Ast.Node.Tag },8 .{ "NodeTag", Ast.Node.Tag },
15 .{ "TegInstTag", Teg.Inst.Tag },9 .{ "TegInstTag", Teg.Tag },
16 .{ "TegInstRef", Teg.Inst.Ref },10 .{ "TegInstRef", Teg.Inst.Ref },
17 // .{ "InternPoolIndex", InternPool.Index },
18 }) |decl| {11 }) |decl| {
19 const name, const enum_type = decl;12 const name, const enum_type = decl;
2013
21 try w.print("export const {s} = {{\n", .{name});14 try w.print("pub enum {s} {{\n", .{name});
22 for (std.enums.values(enum_type)) |tag| {15 for (std.enums.values(enum_type)) |tag| {
23 try w.print(" {d}: \"{s}\",\n", .{ @intFromEnum(tag), @tagName(tag) });16 try w.print(" @\"{s}\" = {d},\n", .{ @tagName(tag), @intFromEnum(tag) });
24 }17 }
25 try w.writeAll("};\n");18 try w.writeAll("}\n");
26 try w.writeAll("\n");19 try w.writeAll("\n");
27 }20 }
28}21}
22
23const std = @import("std");
24const core = @import("core");
25const Tokenizer = core.Tokenizer;
26const Ast = core.Ast;
27const Teg = core.Teg;
src/wasm/wasm_api.zig+7-8
...@@ -10,13 +10,11 @@ export fn iterateTokens(...@@ -10,13 +10,11 @@ export fn iterateTokens(
10 source_bytes: [*]const u8,10 source_bytes: [*]const u8,
11 source_len: u32,11 source_len: u32,
12) callconv(.C) i32 {12) callconv(.C) i32 {
13 unicode_props.init() catch @panic("unicode_props.init failed");13 core.unicode_props.init() catch @panic("unicode_props.init failed");
1414
15 var tokenizer: Tokenizer = .{ .code = source_bytes[0..source_len], .pos = 0 };15 var tokenizer: Tokenizer = .{ .code = source_bytes[0..source_len :0], .pos = 0 };
16 tokenizer.skipWhitespace();16 tokenizer.skipWhitespace();
17 while (tokenizer.next() catch {17 while (tokenizer.next()) |token| {
18 return @intCast(tokenizer.pos);
19 }) |token| {
20 iterateTokenCallback(@intFromEnum(token.tag), token.offset);18 iterateTokenCallback(@intFromEnum(token.tag), token.offset);
21 tokenizer.skipWhitespace();19 tokenizer.skipWhitespace();
22 }20 }
...@@ -25,8 +23,9 @@ export fn iterateTokens(...@@ -25,8 +23,9 @@ export fn iterateTokens(
25}23}
2624
27const std = @import("std");25const std = @import("std");
28const wasm_allocator = std.heap.wasm_allocator;26const core = @import("core");
29const Tokenizer = @import("Tokenizer.zig");27const Tokenizer = core.Tokenizer;
30const Token = Tokenizer.Token;28const Token = Tokenizer.Token;
31const ByteOffset = Tokenizer.ByteOffset;29const ByteOffset = Tokenizer.ByteOffset;
32const unicode_props = @import("unicode_props.zig");30
31const wasm_allocator = std.heap.wasm_allocator;
test/behavior.zig deleted-393
...@@ -1,393 +0,0 @@
1//! This program executes behavior tests found in ./test/behavior. Additionally, it can be run
2//! as a standalone program to compile and execute a single behavior test (for debugging)
3
4/// This file is included for all behavior tests for the JavaScript backend.
5const helpers =
6 \\(function (self) {
7 \\ function assert(condition) {
8 \\ if (!condition) {
9 \\ throw new Error("Assertion failure");
10 \\ }
11 \\ }
12 \\
13 \\ function assertEqual(actual, expected) {
14 \\ if (actual !== expected) {
15 \\ var err;
16 \\ try {
17 \\ err = new Error("Expected " + expected + ", but got " + actual);
18 \\ } catch (e) {
19 \\ err = new Error("Assertion failure");
20 \\ }
21 \\ throw err;
22 \\ }
23 \\ }
24 \\
25 \\ function print(str) {
26 \\ console.log(str);
27 \\ }
28 \\
29 \\ self.assert = assert;
30 \\ self.assertEqual = assertEqual;
31 \\ self.print = print;
32 \\})(typeof globalThis !== "undefined" ? globalThis : this);
33 \\
34;
35
36const CaseOptions = struct {
37 dump_ir: bool = false,
38 sync: bool = false,
39};
40
41const Case = struct {
42 path: []const u8,
43 display: []const u8,
44 success: bool = false,
45 logs: []const u8 = "",
46 failure: ?struct {
47 err: anyerror,
48 stack: core.StoredTrace,
49 } = null,
50};
51
52fn runTestCase(
53 backing_allocator: std.mem.Allocator,
54 file_path: []const u8,
55 case_options: CaseOptions,
56 out: std.io.AnyWriter,
57) !void {
58 var gpa = std.heap.GeneralPurposeAllocator(.{
59 .stack_trace_frames = 100,
60 }){ .backing_allocator = backing_allocator };
61 const alloc = gpa.allocator();
62
63 var backend = core.Js{};
64 var comp: core.Comp = .{
65 .gpa = tracer.tracingAllocator(&alloc),
66 .options = .{ .dump_ir = case_options.dump_ir },
67 .backend = .{ .js = &backend },
68 .out = .{ .writer = out, .tty = .escape_codes },
69 };
70 try comp.init();
71
72 const main_file = try comp.getFile(file_path);
73
74 var fail_report: core.Report = undefined;
75 const teg = main_file.getTeg(&comp, &fail_report) catch |err| switch (err) {
76 error.TransformFail => {
77 try out.print("Transform failed:\n", .{});
78 try fail_report.dumpToWriter(&comp, out, .escape_codes);
79 return error.TransformFail;
80 },
81 error.ParseFail => {
82 // Already printed.
83 return error.ParseFail;
84 },
85 else => return err,
86 };
87
88 if (case_options.dump_ir) {
89 try teg.dumpToWriter(out, .escape_codes);
90 }
91
92 const entry_point = try comp.semaEntryPoint(main_file);
93
94 if (comp.temp_flag_had_errors)
95 return error.SemaErrors;
96
97 if (teg.report.warnings.len > 0)
98 return error.TegWarnings;
99
100 var js_out: std.ArrayListUnmanaged(u8) = .{};
101 defer js_out.deinit(alloc);
102
103 try js_out.appendSlice(alloc, helpers);
104 try js_out.appendSlice(alloc, "\n\n(function() {\n\n");
105
106 const start_i = js_out.items.len;
107 try backend.link(&comp, entry_point, js_out.writer(alloc).any());
108
109 try out.writeAll("--- JS CODE ---\n");
110 try out.writeAll(js_out.items[start_i..]);
111 try out.writeAll("--- JS CODE ---\n");
112
113 try js_out.appendSlice(alloc, "\n\n})();");
114 try js_out.append(alloc, 0);
115
116 const runtime = qjs.JS_NewRuntime() orelse
117 return error.UnexpectedError;
118 defer qjs.JS_FreeRuntime(runtime);
119 const js_ctx = qjs.JS_NewContext(runtime) orelse
120 return error.UnexpectedError;
121 defer qjs.JS_FreeContext(js_ctx);
122
123 const result = qjs.JS_Eval(
124 js_ctx,
125 js_out.items.ptr,
126 js_out.items.len - 1,
127 "test.js",
128 qjs.JS_EVAL_TYPE_GLOBAL,
129 );
130 defer qjs.JS_FreeValue(js_ctx, result);
131
132 if (qjs.JS_IsException(result) != 0) {
133 const exception = qjs.JS_GetException(js_ctx);
134 defer qjs.JS_FreeValue(js_ctx, exception);
135
136 const exception_str = qjs.JS_ToCString(js_ctx, exception);
137 defer qjs.JS_FreeCString(js_ctx, exception_str);
138
139 const slice = std.mem.span(exception_str);
140
141 try out.print("Exception: {s}\n", .{slice});
142
143 return error.RuntimeFailed;
144 }
145}
146
147const threadlocal_state = struct {
148 threadlocal var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
149};
150
151pub fn runTestCaseWrap(
152 gpa: std.mem.Allocator,
153 case: *Case,
154 case_options: CaseOptions,
155 progress: std.Progress.Node,
156) void {
157 const node = progress.start(case.display, 0);
158 defer node.end();
159
160 const alloc = threadlocal_state.arena.allocator();
161 defer _ = threadlocal_state.arena.reset(.{ .retain_with_limit = std.mem.page_size * 8 });
162
163 var logs: std.ArrayListUnmanaged(u8) = .{};
164 defer logs.deinit(alloc);
165
166 defer case.logs = gpa.dupe(u8, logs.items) catch
167 "[logs failed to allocate]";
168
169 if (case_options.sync) std.debug.lockStdErr();
170 defer if (case_options.sync) std.debug.unlockStdErr();
171
172 runTestCase(
173 alloc,
174 case.path,
175 case_options,
176 if (case_options.sync)
177 std.io.getStdErr().writer().any()
178 else
179 logs.writer(alloc).any(),
180 ) catch |err| {
181 const stack = switch (err) {
182 error.TransformFail,
183 error.ParseFail,
184 error.TegWarnings,
185 error.SemaErrors,
186 error.AnalysisFail,
187 => core.StoredTrace.empty,
188
189 else => core.StoredTrace.from(@errorReturnTrace()),
190 };
191
192 case.failure = .{
193 .err = err,
194 .stack = stack,
195 };
196
197 return;
198 };
199
200 case.success = true;
201}
202
203pub fn main() !void {
204 // unicode props is asserted to be initialized before core.Tokenizer is used
205 // this will result in a static page allocation, which is never cleaned up.
206 try core.unicode_props.init();
207 var gpa = std.heap.GeneralPurposeAllocator(.{
208 .stack_trace_frames = 100,
209 .thread_safe = true,
210 }){};
211
212 defer _ = gpa.deinit();
213 const alloc = gpa.allocator();
214
215 const args = try std.process.argsAlloc(alloc);
216 defer std.process.argsFree(alloc, args);
217
218 var test_root: []const u8 = "test/behavior";
219 var case_options: CaseOptions = .{};
220 var all_logs = false;
221 var filter: ?[]const u8 = null;
222 defer if (filter) |f| alloc.free(f);
223
224 var argument_error = false;
225
226 for (args[1..]) |full_arg| {
227 if (std.mem.startsWith(u8, full_arg, "--")) {
228 if (getFlag("--test-root", full_arg)) |flag| {
229 test_root = flag;
230 } else if (std.mem.eql(u8, "--dump-ir", full_arg)) {
231 case_options.dump_ir = true;
232 } else if (std.mem.eql(u8, "--all-logs", full_arg)) {
233 all_logs = true;
234 } else if (std.mem.eql(u8, "--sync", full_arg)) {
235 case_options.sync = true;
236 } else {
237 std.debug.print("Unknown argument: {s}\n", .{full_arg});
238 argument_error = true;
239 }
240 } else {
241 if (filter != null) {
242 std.debug.print("Unknown positional argument: {s}\n", .{full_arg});
243 } else {
244 filter = try std.mem.replaceOwned(
245 u8,
246 alloc,
247 full_arg,
248 if (std.fs.path.sep == '/') "\\" else '/',
249 std.fs.path.sep_str,
250 );
251 }
252 }
253 }
254
255 if (argument_error) std.process.exit(1);
256
257 const root = try std.fs.cwd().openDir(test_root, .{ .iterate = true });
258
259 var test_cases: std.ArrayListUnmanaged(Case) = .{};
260 defer test_cases.deinit(alloc);
261 defer for (test_cases.items) |test_case| {
262 alloc.free(test_case.path);
263 };
264
265 var path_buf: std.BoundedArray(u8, std.fs.max_path_bytes) = .{};
266 path_buf.len = @intCast((try std.os.getFdPath(root.fd, &path_buf.buffer)).len);
267 try iterateTestCases(alloc, root, path_buf.len, &path_buf, &test_cases, filter);
268
269 if (test_cases.items.len == 0) {
270 if (filter) |f| {
271 std.debug.print("No test cases match filter \"{s}\"\n", .{f});
272 } else {
273 std.debug.print("No test cases\n", .{});
274 }
275 std.process.exit(1);
276 }
277
278 const progress = std.Progress.start(.{ .root_name = "Behavior Tests" });
279 defer progress.end();
280 progress.setEstimatedTotalItems(test_cases.items.len);
281
282 var wg: std.Thread.WaitGroup = .{};
283
284 var pool: std.Thread.Pool = undefined;
285 if (!case_options.sync) try pool.init(.{ .allocator = alloc });
286 defer if (!case_options.sync) pool.deinit();
287
288 for (test_cases.items) |*test_case| {
289 const call_args = .{
290 alloc,
291 test_case,
292 case_options,
293 progress,
294 };
295
296 if (case_options.sync) {
297 @call(.auto, runTestCaseWrap, call_args);
298 } else {
299 pool.spawnWg(&wg, runTestCaseWrap, call_args);
300 }
301 }
302
303 wg.wait();
304
305 for (test_cases.items) |*case| {
306 if (case.success) {
307 if (all_logs) {
308 std.debug.print("PASS: {s}\n", .{case.display});
309 std.debug.print("{s}", .{case.logs});
310 }
311 } else if (case.failure) |*fail| {
312 std.debug.print("FAIL: {s}\n", .{case.display});
313 std.debug.print("{s}", .{case.logs});
314 if (fail.stack.index > 0) {
315 std.debug.print("Error return trace:\n", .{});
316 std.debug.dumpStackTrace(fail.stack.trace());
317 }
318 } else {
319 // success was never set true but failure was left null
320 // this means runTestCaseWrap was never called
321 std.debug.print("INVALID STATE: {s}\n", .{case.path});
322 }
323 if (case.logs.len > 0)
324 alloc.free(case.logs);
325 }
326}
327
328fn iterateTestCases(
329 alloc: std.mem.Allocator,
330 root: Dir,
331 root_path_len: usize,
332 path_buf: *std.BoundedArray(u8, std.fs.max_path_bytes),
333 test_cases: *std.ArrayListUnmanaged(Case),
334 filter: ?[]const u8,
335) !void {
336 var it = root.iterate();
337 while (try it.next()) |entry| {
338 switch (entry.kind) {
339 .file => {
340 if (entry.name.len > 0 and entry.name[0] == '.')
341 continue;
342
343 const path = try std.fs.path.join(alloc, &.{
344 path_buf.slice(),
345 entry.name,
346 });
347
348 if (filter) |f| {
349 if (std.mem.indexOf(u8, path, f) == null) {
350 alloc.free(path);
351 continue;
352 }
353 }
354
355 try test_cases.append(alloc, .{
356 .path = path,
357 .display = path[root_path_len + 1 ..],
358 });
359 },
360 .directory => {
361 const top = path_buf.len;
362 defer path_buf.len = top;
363
364 try path_buf.append(std.fs.path.sep);
365 try path_buf.appendSlice(entry.name);
366
367 const sub_dir = try root.openDir(entry.name, .{ .iterate = true });
368 try iterateTestCases(alloc, sub_dir, root_path_len, path_buf, test_cases, filter);
369 },
370 else => return error.UnsupportedFileType,
371 }
372 }
373}
374
375fn getFlag(name: []const u8, full_arg: []const u8) ?[]const u8 {
376 if (full_arg.len > name.len + 1 and
377 full_arg[name.len] == '=' and
378 std.mem.startsWith(u8, name, full_arg))
379 {
380 return full_arg[name.len + 1 ..];
381 }
382
383 return null;
384}
385
386const std = @import("std");
387const Dir = std.fs.Dir;
388
389const core = @import("core");
390const qjs = @import("quickjs");
391
392const tracer = @import("tracer");
393pub const tracer_impl = @field(tracer, @import("build_options").tracing);
test/behavior/arithmetic+18-15
...@@ -1,3 +1,21 @@...@@ -1,3 +1,21 @@
1let assertEqual = @extern("assertEqual");
2
3fn main {
4 assertEqual(addFloat(1, 2), f(3));
5 assertEqual(addFloat(1.5, 2.2), f(3.7));
6 assertEqual(subFloat(1, 2), f(-1));
7 assertEqual(subFloat(1.5, 2.2), f(-0.7000000000000002));
8 assertEqual(mulFloat(1, 2), f(2));
9 assertEqual(mulFloat(1.5, 2.2), f(3.3000000000000003));
10 assertEqual(divFloat(1, 2), f(0.5));
11 assertEqual(divFloat(1.5, 2.2), f(0.6818181818181818));
12
13 assertEqual(addInt(1, 2), i(3));
14 assertEqual(addInt(-421, 592), i(171));
15 assertEqual(subInt(1, 2), i(-1));
16 assertEqual(subInt(528, -43), i(571));
17}
18
1fn f(a: float) -> float { a }19fn f(a: float) -> float { a }
220
3fn addFloat(a: float, b: float) -> float {21fn addFloat(a: float, b: float) -> float {
...@@ -50,18 +68,3 @@ fn subInt(a: int, b: int) -> int {...@@ -50,18 +68,3 @@ fn subInt(a: int, b: int) -> int {
50// a % b68// a % b
51// }69// }
5270
53let assertEqual = @js("assertEqual");
54
55assertEqual(addFloat(1, 2), f(3));
56assertEqual(addFloat(1.5, 2.2), f(3.7));
57assertEqual(subFloat(1, 2), f(-1));
58assertEqual(subFloat(1.5, 2.2), f(-0.7000000000000002));
59assertEqual(mulFloat(1, 2), f(2));
60assertEqual(mulFloat(1.5, 2.2), f(3.3000000000000003));
61assertEqual(divFloat(1, 2), f(0.5));
62assertEqual(divFloat(1.5, 2.2), f(0.6818181818181818));
63
64assertEqual(addInt(1, 2), i(3));
65assertEqual(addInt(-421, 592), i(171));
66assertEqual(subInt(1, 2), i(-1));
67assertEqual(subInt(528, -43), i(571));
test/behavior/array+11-9
...@@ -1,12 +1,14 @@...@@ -1,12 +1,14 @@
1fn i(a: int) -> int { a }1let assertEqual = @extern("assertEqual");
22
3let array = [i(9), i(3), i(8), i(3), i(-1)];3fn main {
4 let array = [i(9), i(3), i(8), i(3), i(-1)];
45
5let assertEqual = @js("assertEqual");6 assertEqual(array.len, i(5));
7 assertEqual(array[0], i(9));
8 assertEqual(array[1], i(3));
9 assertEqual(array.2, i(8));
10 assertEqual(array.3, i(3));
11 assertEqual(array[i(1) + 2] + array[i(2) + 2], i(2));
12}
613
7assertEqual(array.len, i(5));14fn i(a: int) -> int { a }
8assertEqual(array[0], i(9));
9assertEqual(array[1], i(3));
10assertEqual(array.2, i(8));
11assertEqual(array.3, i(3));
12assertEqual(array[i(1) + 2] + array[i(2) + 2], i(2));
test/behavior/empty_function created+2
...@@ -0,0 +1,2 @@
1// Ensure that an empty file is indeed executable without failure.
2fn main {}
test/behavior/function created+17
...@@ -0,0 +1,17 @@
1let assertEqual = @extern("assertEqual");
2
3fn i(x: int) -> int { x }
4
5fn hello(n: int) -> int {
6 if n > 1 {
7 hello(n - 1) + 1
8 } else {
9 // TODO: fix a missing coercion
10 let i: int = 1;
11 i
12 }
13}
14
15fn main {
16 assertEqual(hello(5), i(5));
17}
test/compiler.zig created+431
...@@ -0,0 +1,431 @@
1//! This program executes compiler tests found in this directory. Additionally,
2//! it can be run as a standalone program to compile and execute a single test.
3//!
4//! See the "Testing" section in `architecture.md` for more information on the
5//! structure of these tests, and `zig build test-compiler -- --help` for more
6//! details on the arguments this supports.
7
8/// This file is included for all behavior tests for the JavaScript backend.
9const helpers =
10 \\(function (self) {
11 \\ function assert(condition) {
12 \\ if (!condition) {
13 \\ throw new Error("Assertion failure");
14 \\ }
15 \\ }
16 \\
17 \\ function assertEqual(actual, expected) {
18 \\ if (actual !== expected) {
19 \\ var err;
20 \\ try {
21 \\ err = new Error("Expected " + expected + ", but got " + actual);
22 \\ } catch (e) {
23 \\ err = new Error("Assertion failure");
24 \\ }
25 \\ throw err;
26 \\ }
27 \\ }
28 \\
29 \\ function print(str) {
30 \\ console.log(str);
31 \\ }
32 \\
33 \\ self.assert = assert;
34 \\ self.assertEqual = assertEqual;
35 \\ self.print = print;
36 \\})(typeof globalThis !== "undefined" ? globalThis : this);
37 \\
38;
39
40const CaseOptions = struct {
41 dump_ir: bool = false,
42 sync: bool = false,
43};
44
45const Case = struct {
46 path: []const u8,
47 display: []const u8,
48 success: bool = false,
49 logs: []const u8 = "",
50 failure: ?struct {
51 err: anyerror,
52 stack: core.StoredTrace,
53 } = null,
54
55 const Kind = enum {
56 behavior,
57 analysis,
58 transform_error,
59 };
60};
61
62fn runTestCase(
63 backing_allocator: std.mem.Allocator,
64 file_path: []const u8,
65 case_options: CaseOptions,
66 out: std.io.AnyWriter,
67) !void {
68 var gpa = std.heap.GeneralPurposeAllocator(.{
69 .stack_trace_frames = 100,
70 }){ .backing_allocator = backing_allocator };
71 const alloc = gpa.allocator();
72
73 var backend = core.Js{};
74 var comp = try core.Comp.init(.{
75 .gpa = tracer.tracingAllocator(&alloc),
76 .options = .{ .dump_ir = case_options.dump_ir },
77 .backend = .{ .js = &backend },
78 .out = .{ .writer = out, .tty = .escape_codes },
79 });
80
81 const main_file = try comp.getFile(file_path);
82
83 var fail_report: core.Report = undefined;
84 const teg = main_file.getTeg(&comp, &fail_report) catch |err| switch (err) {
85 error.TransformFail => {
86 try out.print("Transform failed:\n", .{});
87 try fail_report.dump(.{ .comp = .{ .comp = &comp, .file = main_file } }, comp.out);
88 return error.TransformFail;
89 },
90 error.ParseFail => {
91 // Already printed.
92 return error.ParseFail;
93 },
94 else => return err,
95 };
96
97 if (case_options.dump_ir) {
98 try teg.dumpToWriter(out, .escape_codes);
99 }
100
101 const entry_point = try comp.enqueueEntryModule(main_file);
102 try comp.runAllJobs();
103
104 if (comp.temp_flag_had_errors)
105 return error.SemaErrors;
106
107 if (teg.report.warnings.len > 0)
108 return error.TegWarnings;
109
110 var js_out: std.ArrayListUnmanaged(u8) = .{};
111 defer js_out.deinit(alloc);
112
113 try js_out.appendSlice(alloc, helpers);
114 try js_out.appendSlice(alloc, "\n\n(function() {\n\n");
115
116 const start_i = js_out.items.len;
117 try backend.link(&comp, entry_point, js_out.writer(alloc).any());
118
119 try out.writeAll("--- JS CODE ---\n");
120 try out.writeAll(js_out.items[start_i..]);
121 try out.writeAll("--- JS CODE ---\n");
122
123 if (isOnlyComments(js_out.items[start_i..])) {
124 try out.writeAll("Behavior tests should output code!");
125 return error.EmptyFile;
126 }
127
128 try js_out.appendSlice(alloc, "\n\n})();");
129 try js_out.append(alloc, 0);
130
131 const runtime = qjs.JS_NewRuntime() orelse
132 return error.UnexpectedError;
133 defer qjs.JS_FreeRuntime(runtime);
134 const js_ctx = qjs.JS_NewContext(runtime) orelse
135 return error.UnexpectedError;
136 defer qjs.JS_FreeContext(js_ctx);
137
138 const result = qjs.JS_Eval(
139 js_ctx,
140 js_out.items.ptr,
141 js_out.items.len - 1,
142 "test.js",
143 qjs.JS_EVAL_TYPE_GLOBAL,
144 );
145 defer qjs.JS_FreeValue(js_ctx, result);
146
147 if (qjs.JS_IsException(result) != 0) {
148 const exception = qjs.JS_GetException(js_ctx);
149 defer qjs.JS_FreeValue(js_ctx, exception);
150
151 const exception_str = qjs.JS_ToCString(js_ctx, exception);
152 defer qjs.JS_FreeCString(js_ctx, exception_str);
153
154 const slice = std.mem.span(exception_str);
155
156 try out.print("Exception: {s}\n", .{slice});
157
158 return error.RuntimeFailed;
159 }
160}
161
162fn isOnlyComments(input: []const u8) bool {
163 var rest = input;
164 while (rest.len > 0) {
165 if (rest.len == 1) return false;
166 if (rest[0] == '/' and rest[1] == '/') {
167 rest = rest[std.mem.indexOfScalar(u8, rest, '\n') orelse return true ..];
168 } else {
169 return false;
170 }
171 }
172 return true;
173}
174
175const threadlocal_state = struct {
176 threadlocal var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
177};
178
179pub fn runTestCaseWrap(
180 gpa: std.mem.Allocator,
181 case: *Case,
182 case_options: CaseOptions,
183 progress: std.Progress.Node,
184) void {
185 const node = progress.start(case.display, 0);
186 defer node.end();
187
188 const alloc = threadlocal_state.arena.allocator();
189 defer _ = threadlocal_state.arena.reset(.{ .retain_with_limit = std.mem.page_size * 8 });
190
191 var logs: std.ArrayListUnmanaged(u8) = .{};
192 defer logs.deinit(alloc);
193
194 defer case.logs = gpa.dupe(u8, logs.items) catch
195 "[logs failed to allocate]";
196
197 runTestCase(
198 alloc,
199 case.path,
200 case_options,
201 if (case_options.sync)
202 std.io.getStdErr().writer().any()
203 else
204 logs.writer(alloc).any(),
205 ) catch |err| {
206 const stack = switch (err) {
207 error.TransformFail,
208 error.ParseFail,
209 error.TegWarnings,
210 error.SemaErrors,
211 error.AnalysisFail,
212 => core.StoredTrace.empty,
213
214 else => core.StoredTrace.from(@errorReturnTrace()),
215 };
216
217 case.failure = .{
218 .err = err,
219 .stack = stack,
220 };
221
222 return;
223 };
224
225 case.success = true;
226}
227
228pub fn main() !void {
229 // unicode props is asserted to be initialized before core.Tokenizer is used
230 // this will result in a static page allocation, which is never cleaned up.
231 try core.unicode_props.init();
232 var gpa = std.heap.GeneralPurposeAllocator(.{
233 .stack_trace_frames = 100,
234 .thread_safe = true,
235 }){};
236
237 defer _ = gpa.deinit();
238 const alloc = gpa.allocator();
239
240 const args = try std.process.argsAlloc(alloc);
241 defer std.process.argsFree(alloc, args);
242
243 var test_root: []const u8 = "test/behavior";
244 var case_options: CaseOptions = .{};
245 var all_logs = false;
246 var filter: ?[]const u8 = null;
247 defer if (filter) |f| alloc.free(f);
248
249 var argument_error = false;
250
251 for (args[1..]) |full_arg| {
252 if (std.mem.startsWith(u8, full_arg, "--")) {
253 if (getFlag("--test-root", full_arg)) |flag| {
254 test_root = flag;
255 } else if (std.mem.eql(u8, "--dump-ir", full_arg)) {
256 case_options.dump_ir = true;
257 } else if (std.mem.eql(u8, "--all-logs", full_arg)) {
258 all_logs = true;
259 } else if (std.mem.eql(u8, "--sync", full_arg)) {
260 case_options.sync = true;
261 } else {
262 std.debug.print("Unknown argument: {s}\n", .{full_arg});
263 argument_error = true;
264 }
265 } else {
266 if (filter != null) {
267 std.debug.print("Unknown positional argument: {s}\n", .{full_arg});
268 } else {
269 filter = try std.mem.replaceOwned(
270 u8,
271 alloc,
272 full_arg,
273 if (std.fs.path.sep == '/') "\\" else '/',
274 std.fs.path.sep_str,
275 );
276 }
277 }
278 }
279
280 if (argument_error) {
281 // TODO: print a nice help menu
282 std.process.exit(1);
283 }
284
285 const some_logs = all_logs or args.len > 1;
286
287 const root = try std.fs.cwd().openDir(test_root, .{ .iterate = true });
288
289 var test_cases: std.ArrayListUnmanaged(Case) = .{};
290 defer test_cases.deinit(alloc);
291 defer for (test_cases.items) |test_case| {
292 alloc.free(test_case.path);
293 };
294
295 var path_buf: std.BoundedArray(u8, std.fs.max_path_bytes) = .{};
296 path_buf.len = @intCast((try std.os.getFdPath(root.fd, &path_buf.buffer)).len);
297 try iterateTestCases(alloc, root, path_buf.len, &path_buf, &test_cases, filter);
298
299 if (test_cases.items.len == 0) {
300 if (filter) |f| {
301 std.debug.print("No test cases match filter \"{s}\"\n", .{f});
302 } else {
303 std.debug.print("No test cases\n", .{});
304 }
305 std.process.exit(1);
306 }
307
308 const progress = std.Progress.start(.{ .root_name = "Behavior Tests" });
309 defer progress.end();
310 progress.setEstimatedTotalItems(test_cases.items.len);
311
312 var wg: std.Thread.WaitGroup = .{};
313
314 var pool: std.Thread.Pool = undefined;
315 if (!case_options.sync) try pool.init(.{ .allocator = alloc });
316 defer if (!case_options.sync) pool.deinit();
317
318 for (test_cases.items) |*test_case| {
319 const call_args = .{
320 alloc,
321 test_case,
322 case_options,
323 progress,
324 };
325
326 if (case_options.sync) {
327 @call(.auto, runTestCaseWrap, call_args);
328 } else {
329 pool.spawnWg(&wg, runTestCaseWrap, call_args);
330 }
331 }
332
333 wg.wait();
334
335 var num_pass: usize = 0;
336 for (test_cases.items) |*case| {
337 if (case.success) {
338 num_pass += 1;
339 if (some_logs) {
340 std.debug.print("PASS: {s}\n", .{case.display});
341 }
342 if (all_logs) {
343 std.debug.print("{s}", .{case.logs});
344 }
345 } else if (case.failure) |*fail| {
346 std.debug.print("FAIL: {s}\n", .{case.display});
347 std.debug.print("{s}", .{case.logs});
348 if (fail.stack.index > 0) {
349 std.debug.print("Error return trace:\n", .{});
350 std.debug.dumpStackTrace(fail.stack.trace());
351 }
352 } else {
353 // success was never set true but failure was left null
354 // this means runTestCaseWrap was never called
355 std.debug.print("INVALID STATE: {s}\n", .{case.path});
356 }
357 if (case.logs.len > 0)
358 alloc.free(case.logs);
359 }
360
361 if (some_logs) {
362 std.debug.print("Complete: {d} PASS / {d} TOTAL\n", .{ num_pass, test_cases.items.len });
363 }
364}
365
366fn iterateTestCases(
367 alloc: std.mem.Allocator,
368 root: Dir,
369 root_path_len: usize,
370 path_buf: *std.BoundedArray(u8, std.fs.max_path_bytes),
371 test_cases: *std.ArrayListUnmanaged(Case),
372 filter: ?[]const u8,
373) !void {
374 var it = root.iterate();
375 while (try it.next()) |entry| {
376 switch (entry.kind) {
377 .file => {
378 if (entry.name.len > 0 and entry.name[0] == '.')
379 continue;
380
381 const path = try std.fs.path.join(alloc, &.{
382 path_buf.slice(),
383 entry.name,
384 });
385
386 if (filter) |f| {
387 if (std.mem.indexOf(u8, path, f) == null) {
388 alloc.free(path);
389 continue;
390 }
391 }
392
393 try test_cases.append(alloc, .{
394 .path = path,
395 .display = path[root_path_len + 1 ..],
396 });
397 },
398 .directory => {
399 const top = path_buf.len;
400 defer path_buf.len = top;
401
402 try path_buf.append(std.fs.path.sep);
403 try path_buf.appendSlice(entry.name);
404
405 const sub_dir = try root.openDir(entry.name, .{ .iterate = true });
406 try iterateTestCases(alloc, sub_dir, root_path_len, path_buf, test_cases, filter);
407 },
408 else => return error.UnsupportedFileType,
409 }
410 }
411}
412
413fn getFlag(name: []const u8, full_arg: []const u8) ?[]const u8 {
414 if (full_arg.len > name.len + 1 and
415 full_arg[name.len] == '=' and
416 std.mem.startsWith(u8, name, full_arg))
417 {
418 return full_arg[name.len + 1 ..];
419 }
420
421 return null;
422}
423
424const std = @import("std");
425const Dir = std.fs.Dir;
426
427const core = @import("core");
428const qjs = @import("quickjs");
429
430const tracer = @import("tracer");
431pub const tracer_impl = @field(tracer, @tagName(@import("config").tracing_mode));
test/parse_error/string_invalid_escape created+3
...@@ -0,0 +1,3 @@
1let abc = "hello \j";
2
3// error:1:11: expected an expression, but found unterminated string literal
test/parse_error/string_unterminated created+5
...@@ -0,0 +1,5 @@
1let abc = "hello
2let def = 123;
3let hij = "wtf
4
5// error:1:11: expected an expression, but found unterminated string literal
test/transform_error/break_across_function created+8
...@@ -0,0 +1,8 @@
1let x = hello: {
2 fn() {
3 break :hello 123;
4 }
5};
6_ = x;
7
8// error:3:15: unknown block label "hello"
test/transform_error/top_level_statement created+6
...@@ -0,0 +1,6 @@
1let func = @extern("func");
2
3func(1);
4func(2);
5
6// error:3:1: top-level statements are not allowed
test/transform_error/top_level_statement_err created+3
...@@ -0,0 +1,3 @@
1func(1);
2
3// error:3:1: top-level statements are not allowed