authorgravatar for me@paperdave.netdave caruso <me@paperdave.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
log9ee226fafa7d8e90b97a395fbb16a370a83daa31
tree29bf2fda9311ae600235b8b7349c10ae57905985
parent4a2887aab0628d3b5b834049ea398d98f2d5549c
signature Signed by PGP key B5690EEEBB952194

turn semantic analysis into a state machine (#79)


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

CHANGELOG.md+8
......@@ -1,8 +1,16 @@
11## 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
39- feat: add `--run` to execute the file in QuickJS instead of outputting source
410- feat: add array index syntax via `x[1]` and `x.1`
511- feat: add `len` property to arrays
12- feat: rename `noreturn` to `never`
13- feat: allow instantiating `@extern`
614- fix: crash when printing unused struct declaration error
715- fix: provide array result type to sema
816- fix: crash when analyzing an empty array without a result type
README.md+10-9
......@@ -1,20 +1,21 @@
11# New Language Project
22
3This is a high-level, general purpose programming language. It is a mix of Rust, Zig, and
4JavaScript's great points.
3This is a high-level, general purpose programming language. It is a mix of Rust,
4Zig, and JavaScript's great points.
55
6For a guide on how to use the language, please see the Language Manual included in the binary
7distribution. (TODO: host a rendered version)
6For a guide on how to use the language, please see the Language Manual included
7in the binary distribution. (TODO: host a rendered version)
88
99## Building
1010
11The primary dependency of the language SDK is a Zig compiler. If one is not available, the
12`./build` and `.\build.cmd` scripts can substitute for running `zig build` on POSIX and Windows
13systems, respectively. With a Zig compiler, a release build can be produced in a single command:
11The primary dependency of the language SDK is a Zig compiler. If one is not
12available, the `./build` and `.\build.cmd` scripts can substitute for running
13`zig build` on POSIX and Windows systems, respectively. With a Zig compiler, a
14release build can be produced in a single command:
1415
1516 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
1921 zig build package
20
build.zig+429-189
......@@ -1,8 +1,207 @@
1//! To learn more about the Zig build system:
2//! https://ziglang.org/learn/build-system/
3//!
14//! See `architecture.md` for a codebase overview, and `zig-out/doc/README.html`
25//! 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
4203pub fn build(b: *Build) !void {
5 b.reference_trace = 20;
204 b.reference_trace = 32;
6205
7206 const target = b.standardTargetOptions(.{});
8207
......@@ -27,7 +226,7 @@ pub fn build(b: *Build) !void {
27226 };
28227
29228 const tracing = b.option(
30 TracingMode,
229 BuildConfig.TracingMode,
31230 "trace",
32231 "Enable tracing",
33232 ) orelse .none;
......@@ -38,17 +237,11 @@ pub fn build(b: *Build) !void {
38237 "Experiment with the Zig self-hosted backends",
39238 ) orelse false;
40239
41 const version = version: {
42 const text = try b.build_root.handle.readFileAlloc(
43 b.graph.arena,
44 "CHANGELOG.md",
45 std.math.maxInt(usize),
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 };
240 const have_embedded_quickjs = b.option(
241 bool,
242 "quickjs",
243 "Build with QuickJS features",
244 ) orelse true;
52245
53246 const js_runtime: PathOrString = if (b.option(
54247 []const u8,
......@@ -63,14 +256,126 @@ pub fn build(b: *Build) !void {
63256 .optimize = .ReleaseSafe,
64257 }).artifact("qjs").getEmittedBin() };
65258
66 const native_debug = buildWithTarget(b, b.graph.host, .Debug, .none, version, no_llvm);
67 const main = buildWithTarget(b, target, optimize, tracing, version, no_llvm);
259 // This code is taken from Zig
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
71374 inline for (.{
72 .{ main.cli(b, .minimal), "compiler" },
73 .{ main.loader_bin, "loader" },
375 .{ minimal.cli, "compiler" },
376 .{ minimal.format_cli, "format" },
377 .{ minimal.loader_cli, "loader" },
378 .{ minimal.transform_cli, "transform" },
74379 }) |entry| {
75380 const artifact, const name = entry;
76381
......@@ -85,8 +390,9 @@ pub fn build(b: *Build) !void {
85390
86391 const test_all = b.step("test", "Run all tests");
87392 inline for (.{
88 .{ main.core_tests, "core", "core module" },
89 .{ main.behavior_tests, "behavior", "compiler behavior" },
393 .{ main.core_mod_tests, "core", "core module" },
394 .{ main.loader_mod_tests, "loader", "loader module" },
395 .{ main.compiler_tests, "compiler", "compiler behavior" },
90396 }) |entry| {
91397 const artifact, const name, const description = entry;
92398 const run_cmd = b.addRunArtifact(artifact);
......@@ -107,10 +413,11 @@ pub fn build(b: *Build) !void {
107413 .optimize = .Debug,
108414 .link_libc = true,
109415 });
110 manual_generator.root_module.addImport("build_options", native_debug.opts_mod);
111 manual_generator.root_module.addImport("core", native_debug.core);
416 manual_generator.root_module.addImport("config", native_debug.config_mod);
417 manual_generator.root_module.addImport("core", native_debug.core_mod);
418
112419 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());
114421 switch (js_runtime) {
115422 .path => |path| manual_run.addFileArg(path),
116423 .string => |str| manual_run.addArg(str),
......@@ -119,53 +426,79 @@ pub fn build(b: *Build) !void {
119426 manual_run.addFileArg(b.path("src/manual/template.html"));
120427 const manual_out = manual_run.addOutputFileArg("language-reference.html");
121428 const install_manual = b.addInstallFile(manual_out, "doc/README.html");
429
122430 b.step("manual", "build language reference").dependOn(&install_manual.step);
123431
124 // const wasm_api = b.addExecutable(.{
125 // .name = "compiler",
126 // .root_source_file = b.path("src/wasm_api.zig"),
127 // .target = b.resolveTargetQuery(.{
128 // .cpu_arch = .wasm32,
129 // .os_tag = .freestanding,
130 // }),
131 // .optimize = optimize,
132 // });
133 // wasm_api.root_module.export_symbol_names = &[_][]const u8{
134 // "iterateTokens",
135 // };
136 // wasm_api.entry = .disabled;
137 // const wasm_step = b.step("wasm", "Build wasm api");
138 // b.installArtifact(wasm_api);
139 // wasm_step.dependOn(&wasm_api.step);
140 // wasm_api.root_module.addImport("zg:PropsData", zg.module("PropsData"));
432 {
433 const wasm_target = b.resolveTargetQuery(.{
434 .cpu_arch = .wasm32,
435 .os_tag = .freestanding,
436 });
437 const wasm_artifacts = createArtifactGroup(b, .{
438 .config = .{
439 .version = version,
440 .have_embedded_quickjs = false,
441 },
442 .target = wasm_target,
443 .optimize = optimize,
444 .no_llvm = no_llvm,
445 });
446 const wasm_api = b.addExecutable(.{
447 .name = "sdk",
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
142474 // Packaging
143475 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 (.{
149 b.resolveTargetQuery(.{ .cpu_arch = .x86_64, .os_tag = .linux, .abi = .musl, .os_version_min = linux_version }),
150 b.resolveTargetQuery(.{ .cpu_arch = .aarch64, .os_tag = .linux, .abi = .musl, .os_version_min = linux_version }),
151 b.resolveTargetQuery(.{ .cpu_arch = .x86_64, .os_tag = .macos, .os_version_min = mac_version }),
152 b.resolveTargetQuery(.{ .cpu_arch = .aarch64, .os_tag = .macos, .os_version_min = mac_version }),
153 b.resolveTargetQuery(.{ .cpu_arch = .x86_64, .os_tag = .windows, .os_version_min = win_version, .abi = .gnu }),
154 b.resolveTargetQuery(.{ .cpu_arch = .aarch64, .os_tag = .windows, .os_version_min = win_version, .abi = .gnu }),
477 for ([_]Build.ResolvedTarget{
478 crossTarget(.x86_64, .linux),
479 crossTarget(.aarch64, .linux),
480 crossTarget(.x86_64, .macos),
481 crossTarget(.aarch64, .macos),
482 crossTarget(.x86_64, .windows),
483 crossTarget(.aarch64, .windows),
155484 }) |cross_target| {
156 const name = std.fmt.allocPrint(
157 b.graph.arena,
158 "{s}-{s}",
159 .{
160 @tagName(cross_target.result.cpu.arch),
161 @tagName(cross_target.result.os.tag),
162 },
163 ) catch @panic("OOM");
485 const name = b.fmt("{s}-{s}", .{
486 @tagName(cross_target.result.cpu.arch),
487 @tagName(cross_target.result.os.tag),
488 });
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
167500 inline for (.{
168 .{ artifacts.cli(b, BuildConfig.all), "compiler" },
501 .{ artifacts.cli, "compiler" },
169502 }) |entry| {
170503 const artifact, const bin_name = entry;
171504 package.dependOn(&b.addInstallFile(
......@@ -173,10 +506,10 @@ pub fn build(b: *Build) !void {
173506 b.pathJoin(&.{
174507 "package",
175508 name,
176 std.mem.concat(b.graph.arena, u8, &.{
509 b.fmt("{s}{s}", .{
177510 bin_name,
178511 cross_target.result.exeFileExt(),
179 }) catch @panic("OOM"),
512 }),
180513 }),
181514 ).step);
182515 }
......@@ -184,140 +517,46 @@ pub fn build(b: *Build) !void {
184517 package.dependOn(&b.addInstallFile(b.path("CHANGELOG.md"), "package/CHANGELOG.md").step);
185518 package.dependOn(&b.addInstallFile(manual_out, "package/README.html").step);
186519 }
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,
197 core_tests: *Build.Step.Compile,
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 }
521 {
522 _ = b.top_level_steps.swapRemove("uninstall"); // this step does not work
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);
237527 }
238};
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"));
528}
273529
274 // Core Unit Tests
275 const core_tests = b.addTest(.{
276 .root_source_file = b.path("src/core/mod.zig"),
277 .target = target,
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"));
530fn crossTarget(cpu_arch: std.Target.Cpu.Arch, os_tag: std.Target.Os.Tag) Build.ResolvedTarget {
531 const query: std.Target.Query = .{
532 .cpu_arch = cpu_arch,
533 .os_tag = os_tag,
286534
287 // Loader
288 const loader = b.addExecutable(.{
289 .name = "loader",
290 .root_source_file = b.path("src/loader/main.zig"),
291 .target = target,
292 .optimize = optimize,
293 .use_lld = use_lld,
294 .use_llvm = use_llvm,
295 .strip = false,
296 });
535 .cpu_model = switch (cpu_arch) {
536 .aarch64 => switch (os_tag) {
537 .macos => .{ .explicit = &std.Target.aarch64.cpu.apple_m1 },
538 else => .baseline,
539 },
540 else => .baseline,
541 },
297542
298 const behavior_tests = b.addExecutable(.{
299 .name = "run_behavior_tests",
300 .root_source_file = b.path("test/behavior.zig"),
301 .target = target,
302 .optimize = optimize,
303 .use_lld = use_lld,
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"));
543 .os_version_min = switch (os_tag) {
544 .linux => .{ .semver = .{ .major = 4, .minor = 19, .patch = 0 } },
545 .macos => .{ .semver = .{ .major = 11, .minor = 0, .patch = 0 } },
546 .windows => .{ .windows = .win7 },
547 else => @panic("unhandled"),
548 },
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 };
311557 return .{
312 .no_llvm = no_llvm,
313 .target = target,
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,
558 .query = query,
559 .result = std.zig.system.resolveTargetQuery(query) catch unreachable,
321560 };
322561}
323562
......@@ -328,7 +567,8 @@ pub const PathOrString = union(enum) {
328567
329568const std = @import("std");
330569const Build = std.Build;
570const Step = Build.Step;
571const Module = Build.Module;
331572const LazyPath = Build.LazyPath;
332573const Target = std.Target;
333574const OsVersion = Target.Query.OsVersion;
334const BuildConfig = @import("src/BuildConfig.zig");
build.zig.zon+4-1
......@@ -2,17 +2,20 @@
22 .name = "",
33 .version = "0.0.0",
44
5 .minimum_zig_version = "0.13.0",
5 .minimum_zig_version = "0.14.0-dev.1472+3929cac15",
66
77 .dependencies = .{
8 // Used to determine unicode properties to tokenize identifiers
89 .zg = .{
910 .url = "https://codeberg.org/dude_the_builder/zg/archive/c425c9c8511bf92e14b8b612d1d16e774b186f2e.tar.gz",
1011 .hash = "1220c79009f1e443b015db4de6e5319432538ead2fea10d271f3f35d3ae83c996789",
1112 },
13 // Optional: Used for tracing
1214 .tracer = .{
1315 .url = "https://github.com/paperdave/zig-tracer/archive/d4c2eff040e9e69b9f08d2175b315f3055ad7ba3.tar.gz",
1416 .hash = "12202d60766a46ceb4074ce5651a58a11f2f6b32927476a55c6889805073e8e3d261",
1517 },
18 // Optional: Used for JavaScript behavior tests
1619 .quickjs = .{
1720 .path = "src/deps/quickjs",
1821 },
doc/architecture.md+55-6
......@@ -1,13 +1,62 @@
11# 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- [**SDK Manual Generator**][manual] (`src/manual`)
6- [**Core**][#core] (`src/core`) includes the parser, analysis passes, codegen
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.
44
55Key goals of this project are:
66
7- **Ease of Use**: Use of type inference keeps code concise and easy to read and write.
8- **Fast Iteration**: The compiler is designed for quick incremental compilation and hot module reloading. (note: incremental is not yet available)
7- **Ease of Use**: Use of type inference keeps code concise and easy to read and
8 write.
9- **Fast Iteration**: The compiler is designed for quick incremental compilation
10 and hot module reloading. (note: incremental is not yet available)
911- 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
1316- **Rust** - Expressive control flow that is easy to write.
1417- **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.
2124## Hello World
2225
2326```lang:run
24let print = @js("console").log;
25print("Hello, world!");
27let print = @extern("console").log;
28
29fn main {
30 print("Hello, world!");
31}
2632```
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
3037## Primitive Data Types
3138
32| name | ts equivalent | description |
33| ---------- | --------------------- | ---------------------------------------------- |
34| `int` | `number` | a number in signed 32-bit range |
35| `float` | `number` | a 64-bit floating point number |
36| `bool` | `boolean` | either `true` or `false` |
37| `string` | `string` | a sequence of text characters |
38| `void` | `undefined` or `null` | detectable lack of a value |
39| `opaque` | `unknown` or `any` | could be any value |
40| `noreturn` | `never` | control flow prevents this value from existing |
39| name | ts equivalent | description |
40| -------- | --------------------- | ---------------------------------------------- |
41| `int` | `number` | a number in signed 32-bit range |
42| `float` | `number` | a 64-bit floating point number |
43| `bool` | `boolean` | either `true` or `false` |
44| `string` | `string` | a sequence of text characters |
45| `void` | `undefined` or `null` | detectable lack of a value |
46| `opaque` | `unknown` or `any` | could be any value |
47| `never` | `never` | control flow prevents this value from existing |
4148
4249> **note**: `bigint` is planned but not added
4350
4451## 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
4856| name | description |
4957| ------- | ------------------------------------- |
......@@ -61,41 +69,49 @@ let true = 1 == 0;
6169
6270There 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
6676```lang:run
6777let hello: int = 1;
6878let world = hello + 2;
6979let hello = hello * world;
7080
71@js("console").log(hello);
81@extern("console").log(hello);
7282```
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
7687```lang:run
7788var message = "first message";
7889
79if @js("Math").random() > 0.5 {
90if @extern("Math").random() > 0.5 {
8091 message = "second message";
8192}
8293
83@js("console").log(message);
94@extern("console").log(message);
8495```
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
88101```lang:run
89let message = if @js("Math").random() > 0.5 {
102let message = if @extern("Math").random() > 0.5 {
90103 "second message"
91104} else {
92105 "first message"
93106};
94107
95@js("console").log(message);
108@extern("console").log(message);
96109```
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
100116```lang
101117var number = 0; // `number` is a `float`, but not explicit
......@@ -105,18 +121,21 @@ var number: float = 0; // `number` is `float`
105121
106122## 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
110127```lang:run
111128fn add(a: int, b: int) -> int {
112129 a + b
113130}
114131
115let print = @js("console").log;
132let print = @extern("console").log;
116133print("Sum of 2 and 7:", add(2, 7));
117134```
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
121140The `return` expression can be used within functions to return early.
122141
......@@ -129,7 +148,7 @@ fn something(a: int) -> bool {
129148 a > 100
130149}
131150
132let print = @js("console").log;
151let print = @extern("console").log;
133152print(something(0xFF), something(0));
134153```
135154
......@@ -143,28 +162,35 @@ TODO
143162
144163## 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
148169> **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
152174```lang:run
153let print = @js("console").log;
175let print = @extern("console").log;
154176
155177fn currentYear() -> int {
156 @jsNew(@js("Date")).getFullYear()
178 @jsNew(@extern("Date")).getFullYear()
157179}
158180
159181print(currentYear());
160182```
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
162187## 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
166192```lang:run
167let print = @js("console").log;
193let print = @extern("console").log;
168194
169195struct Position {
170196 x: int,
......@@ -186,7 +212,9 @@ let added = addPositions(
186212print("Add result:", added.x, added.y);
187213```
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
191219```lang:build
192220struct Person {
......@@ -194,6 +222,6 @@ struct Person {
194222 age: int,
195223}
196224
197let print = @js("console").log;
225let print = @extern("console").log;
198226print(Person{ name: "hello", age: 42 });
199227```
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 @@
1version: std.SemanticVersion,
2tracing_mode: TracingMode = .none,
13have_embedded_quickjs: bool = false,
24
3pub const minimal: BuildConfig = .{};
4
5pub const all: BuildConfig = .{
6 .have_embedded_quickjs = true,
5pub const TracingMode = enum {
6 none,
7 spall,
8 chrome,
9 log,
710};
811
912const BuildConfig = @This();
13const std = @import("std");
src/cli.zig+24-13
......@@ -5,7 +5,7 @@
55//! needing to compile the formatter.
66
77const have_debug_features = builtin.mode == .Debug;
8const config = @import("build_config").config;
8const config = @import("config");
99
1010pub fn main() !u8 {
1111 try tracer.init();
......@@ -24,7 +24,8 @@ pub fn main() !u8 {
2424 }){}
2525 else
2626 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();
2829 const alloc = gpa.allocator();
2930
3031 // Current working directory is required for CLI resolution.
......@@ -48,9 +49,15 @@ pub fn main() !u8 {
4849
4950 while (args.next()) |arg| {
5051 if (std.mem.startsWith(u8, arg, "-")) {
51 if (have_debug_features and std.mem.eql(u8, arg, "--report-trace")) {
52 core.Report.debug_show_report_trace = true;
53 continue;
52 if (have_debug_features) {
53 if (std.mem.eql(u8, arg, "--report-trace")) {
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 }
5461 }
5562
5663 if (std.mem.eql(u8, arg, "--dump-ir")) {
......@@ -107,9 +114,11 @@ pub fn main() !u8 {
107114 \\
108115 \\Debug Mode Flags:
109116 \\ --report-trace Print stack traces per error
117 \\ --no-leak-checks
110118 \\
111119 , .{});
112120 }
121 std.debug.print("\nLanguage SDK v" ++ config.version_string ++ "\n", .{});
113122 return 1;
114123 }
115124 const file_path = maybe_file_path.?;
......@@ -118,15 +127,13 @@ pub fn main() !u8 {
118127 {
119128 var js_backend = core.Js{};
120129
121 var comp: core.Comp = .{
130 var comp = try core.Comp.init(.{
122131 .gpa = tracer.tracingAllocator(&alloc),
123132 .options = .{
124133 .dump_ir = dump_ir,
125134 },
126135 .backend = .{ .js = &js_backend },
127 .out = core.OutStream.init(std.io.getStdOut().writer().any()),
128 };
129 try comp.init();
136 });
130137 defer comp.deinit();
131138
132139 defer if (dump_state) |path| {
......@@ -141,7 +148,7 @@ pub fn main() !u8 {
141148 const teg = main_file.getTeg(&comp, &fail_report) catch |err| switch (err) {
142149 error.TransformFail => {
143150 std.debug.print("Transform failed:\n", .{});
144 try fail_report.dumpToStdErr(&comp);
151 try fail_report.dump(.initComp(&comp, main_file), comp.out);
145152 fail_report.deinit(comp.gpa);
146153 return 1;
147154 },
......@@ -157,8 +164,12 @@ pub fn main() !u8 {
157164 try teg.dumpToWriter(stderr, null);
158165 }
159166
160 const entry_point = comp.semaEntryPoint(main_file) catch |err| switch (err) {
161 error.AnalysisFail => std.process.exit(1),
167 const entry_point = try comp.enqueueEntryModule(main_file);
168 comp.runAllJobs() catch |err| switch (err) {
169 error.AnalysisFail => {
170 // error is already printed
171 return 1;
172 },
162173 else => return err,
163174 };
164175
......@@ -229,7 +240,7 @@ const Comp = core.Comp;
229240const Report = core.Report;
230241
231242const 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
234245const std = @import("std");
235246const builtin = @import("builtin");
src/core/Air.Dump.zig+11-18
......@@ -55,12 +55,6 @@ pub const State = struct {
5555 const insts = index_list.slice(air);
5656 try s.dumpInstList(w, insts, '{', '}');
5757 },
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 },
6458 .arg => {
6559 const arg: Air.Arg = air.instData(.arg, inst);
6660 try w.writeAll(" ");
......@@ -73,7 +67,7 @@ pub const State = struct {
7367 .@"break" => {
7468 const brk: Air.Break = air.instData(.@"break", inst);
7569 try w.writeAll("(");
76 try s.dumpInstRef(w, brk.block_ref);
70 try s.dumpInstRef(w, brk.block);
7771 try w.writeAll(", ");
7872 try s.dumpRef(w, brk.value);
7973 try w.writeAll(")");
......@@ -93,7 +87,8 @@ pub const State = struct {
9387 .lt,
9488 .gt,
9589 .eq,
96 .index,
90 .field_array,
91 .field_opaque,
9792 => |comptime_tag| {
9893 const bin: Air.Bin = air.instData(comptime_tag, inst);
9994 try w.writeAll("(");
......@@ -119,16 +114,6 @@ pub const State = struct {
119114 try s.dumpValue(w, memory.ty.value());
120115 try w.writeAll(")");
121116 },
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 },
132117 .field_struct => {
133118 const field: Air.FieldStruct = air.instData(.field_struct, inst);
134119 try w.writeAll("(");
......@@ -374,6 +359,14 @@ pub const State = struct {
374359 try w.writeAll(">");
375360 try tty.setColor(w, .reset);
376361 },
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 },
377370 }
378371 }
379372
src/core/Air.zig+14-15
......@@ -14,7 +14,6 @@ pub const Inst = struct {
1414
1515 pub const Tag = enum {
1616 block,
17 js_global_ref,
1817 call,
1918 js_new,
2019 @"break",
......@@ -29,11 +28,10 @@ pub const Inst = struct {
2928
3029 field_opaque,
3130 field_struct,
31 field_array,
3232
3333 array_len,
3434
35 index,
36
3735 // bin
3836 add,
3937 sub,
......@@ -52,7 +50,6 @@ pub const Inst = struct {
5250 pub fn Data(comptime tag: Tag) type {
5351 return switch (tag) {
5452 .block => IndexList,
55 .js_global_ref => InternPool.String.WithLen,
5653 .@"break" => Break,
5754 .@"if" => If,
5855 .call, .js_new => Call,
......@@ -61,9 +58,9 @@ pub const Inst = struct {
6158 .store => Store,
6259 .struct_init => StructInit,
6360 .array_init => ArrayInit,
64 .field_opaque => FieldOpaque,
6561 .field_struct => FieldStruct,
66 .index,
62 .field_opaque,
63 .field_array,
6764 .add,
6865 .sub,
6966 .mul,
......@@ -216,7 +213,8 @@ pub const RefList = struct {
216213};
217214
218215pub const Break = struct {
219 block_ref: Inst.Index,
216 block: Inst.Index,
217 // TODO: rename to operand
220218 value: Inst.Ref,
221219};
222220
......@@ -261,6 +259,7 @@ pub const If = struct {
261259 pub const Data = struct {
262260 yes: Inst.Ref,
263261 no: Inst.Ref,
262 // TODO: delete this. both refs should be coerced to the common type
264263 union_ty: Type,
265264 };
266265};
......@@ -328,11 +327,11 @@ pub fn typeOf(air: *const Air, ref: Inst.Ref, ip: *const InternPool) InternPool.
328327pub fn typeOfIndex(air: *const Air, index: Inst.Index, ip: *const InternPool) InternPool.Type {
329328 return switch (air.instTag(index)) {
330329 // These values are by definition, opaque
331 .js_global_ref, .js_new => .@"opaque",
330 .js_new => .@"opaque",
332331
333332 // These operations always produce known types
334333 .lt, .gt, .eq, .bool_not => .bool,
335 .@"break" => .noreturn,
334 .@"break" => .never,
336335 .field_opaque => .@"opaque",
337336 .array_len => .int32,
338337
......@@ -353,7 +352,7 @@ pub fn typeOfIndex(air: *const Air, index: Inst.Index, ip: *const InternPool) In
353352 .div,
354353 => |t| {
355354 const left = air.typeOf(air.instData(t, index).lhs, ip);
356 if (std.debug.runtime_safety) {
355 if (core.runtime_safety) {
357356 assert(left == air.typeOf(air.instData(t, index).rhs, ip));
358357 }
359358 return left;
......@@ -362,7 +361,7 @@ pub fn typeOfIndex(air: *const Air, index: Inst.Index, ip: *const InternPool) In
362361 .negate => air.typeOf(air.instData(.negate, index).operand, ip),
363362
364363 // TODO: taking the last instruction is likely wrong.
365 // The last instruction is going to be a .break and be of type .noreturn
364 // The last instruction is going to be a .break and be of type .never
366365 .block => {
367366 const items = air.instData(.block, index).slice(air);
368367 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
386385 },
387386
388387 // Array accesses use the array they are accessing into.
389 .index => {
390 const inst = air.instData(.index, index);
388 .field_array => {
389 const inst = air.instData(.field_array, index);
391390 const base_type = air.typeOf(inst.lhs, ip)
392 .toArrayType(ip) orelse unreachable; // index demands array type
391 .toArrayType(ip) orelse unreachable; // field_array demands array type
393392 return base_type.item_type;
394393 },
395394 };
......@@ -399,7 +398,7 @@ pub fn paramCount(air: Air) u32 {
399398 for (air.insts.items(.tag)[1..], 0..) |tag, i| {
400399 if (tag != .arg) return @intCast(i);
401400 }
402 return @intCast(air.insts.len);
401 return 0;
403402}
404403
405404pub 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
188188 try state.newline(writer);
189189 try writer.writeAll(")");
190190 },
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 },
191204 .un => {
192205 try writer.writeAll(" (");
193206 {
src/core/Ast.zig+17-4
......@@ -51,7 +51,6 @@ pub const Node = struct {
5151 pub const Tag = enum {
5252 /// Read with `ast.rootNodes()`
5353 /// data is .list (containing stmt slice)
54 /// TODO: rename to `module`
5554 root,
5655 /// main_token is the {
5756 /// data is .list (containing stmt slice)
......@@ -82,6 +81,8 @@ pub const Node = struct {
8281 /// 'name: type' declaration
8382 /// Main token is the identifier
8483 param_decl,
84 ///
85 test_decl,
8586 /// An identifier. main_token is the identifier.
8687 id,
8788 /// Main token is the data. May not fit in i32 range,
......@@ -302,6 +303,7 @@ pub const Node = struct {
302303 .bool_not = .un,
303304 .builtin_call = .list,
304305 .call = .call,
306 .test_decl = .test_decl,
305307 .div = .bin,
306308 .div_assign = .bin,
307309 .eq = .bin,
......@@ -361,6 +363,10 @@ pub const Node = struct {
361363 type: OptionalNodeIndex,
362364 init: NodeIndex,
363365 },
366 test_decl: struct {
367 doc_test_name: OptionalNodeIndex,
368 block: NodeIndex,
369 },
364370 param_decl: struct {
365371 type: OptionalNodeIndex,
366372 unused: Unused = .zero,
......@@ -588,7 +594,6 @@ pub fn fullFnDecl(ast: Ast, node: NodeIndex) full.FnDecl {
588594 const public = tok_tags[main_token -| 1] == .kw_pub;
589595 assert(tok_tags[main_token] == .kw_fn);
590596 assert(tok_tags[main_token + 1] == .id);
591 assert(tok_tags[main_token + 2] == .start_paren);
592597 const id = ast.tokenSlice(main_token + 1);
593598
594599 const data = ast.nodes.items(.data)[node].function;
......@@ -653,7 +658,7 @@ pub fn fullVarDecl(ast: Ast, node: NodeIndex) full.VarDecl {
653658 .kw_let => true,
654659 .kw_var => false,
655660 else => |tag| {
656 if (std.debug.runtime_safety) {
661 if (core.runtime_safety) {
657662 std.debug.panic("Expected var decl, got {s}", .{@tagName(tag)});
658663 }
659664 unreachable;
......@@ -733,6 +738,14 @@ pub fn tokenLocation(self: Ast, start_offset: ByteOffset, token_index: TokenInde
733738 return Tokenizer.tokenLocation(self.utf8_source, start_offset, token_start);
734739}
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
736749pub fn tokenSlice(ast: Ast, token_index: TokenIndex) []const u8 {
737750 return ast.tokens.get(token_index).slice(ast.utf8_source);
738751}
......@@ -805,7 +818,7 @@ const Report = core.Report;
805818/// based offsets, use this type to reduce mistakes. A common usage of this
806819/// would be line and column numbers for `Report.Item`s
807820pub fn Ordinal(Int: type) type {
808 comptime assert(@typeInfo(Int).Int.signedness == .unsigned);
821 comptime assert(@typeInfo(Int).int.signedness == .unsigned);
809822 return enum(Int) {
810823 start = 0,
811824 _,
src/core/Cache.zig+17
......@@ -7,3 +7,20 @@ pub const BinDigest = [bin_digest_len]u8;
77pub const HexDigest = [hex_digest_len]u8;
88
99pub 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 @@
33//! This is all of the state for a compilation, which starts at one or more URLs
44//! (entry points), and will eventually emit one or more object files.
55
6/// General purpose allocator
6/// General purpose and thread-safe allocator.
77gpa: Allocator,
88/// Stores all Type and Value objects.
9intern_pool: InternPool = .{},
10/// All files in the compilation
11/// TODO: turn this into a HashMap(*File, void) as File owns it's key
12files: std.StringHashMapUnmanaged(*File) = .{},
13
9intern_pool: InternPool,
10/// Frontend options.
11options: Options,
12/// All text output is printed here. A typical compiler binary will set this to
13/// standard error, but a testing or web interface may want stronger control
14/// over the output.
1415out: 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
2027// 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 {
2455 try comp.intern_pool.init(comp.gpa);
56
57 return comp;
2558}
2659
2760pub fn deinit(comp: *Comp) void {
28 comp.intern_pool.deinit(comp.gpa);
29 var it = comp.files.valueIterator();
30 while (it.next()) |file| {
31 file.*.deinit(comp.gpa);
61 {
62 var it = comp.ongoing_analysis.valueIterator();
63 while (it.next()) |sema| {
64 _ = sema.*.deinit(.free_all);
65 comp.gpa.destroy(sema.*);
66 }
67 comp.ongoing_analysis.deinit(comp.gpa);
3268 }
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);
3477 comp.backend.deinit(comp);
78 comp.jobs.deinit();
3579}
3680
37/// This is the entry point for compilation.
38/// Returns the Decl of the top-level code.
39pub fn semaEntryPoint(comp: *Comp, file: *File) !Decl.Index {
81const Job = union(enum) {
82 analysis: struct {
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 {
40112 var report: Report = undefined;
41113 const teg = try file.getTeg(comp, &report);
42114
43115 // TODO, move this elsewhere. we should print all errors and warnings at once
44116 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);
46118 }
47119
48120 const ns = try comp.indexTegModule(file, .main_module_inst);
49 const decl_index = comp.intern_pool.namespacePtr(ns).lookupDirect(comp, .empty) orelse
50 unreachable; // Transform should always emit a top-level function
51 const decl = comp.intern_pool.declPtr(decl_index);
52
53 var arena = std.heap.ArenaAllocator.init(comp.gpa);
54 defer arena.deinit();
55
56 var sema: Sema = .{
57 .comp = comp,
58 .gpa = comp.gpa,
59 .code = teg,
60 .arena = arena.allocator(),
61 .ip = &comp.intern_pool,
62 .decl = decl,
63 .decl_index = decl_index,
64 .namespace_index = ns,
121 const main_str = try comp.intern_pool.getOrPutString(comp.gpa, "main", .no_embedded_nulls);
122 const decl_index = comp.intern_pool.namespacePtr(ns).lookupDirect(comp, main_str) orelse
123 @panic("missing 'fn main' in root module");
124
125 try comp.jobs.writeItem(.{ .analysis = .{ .au = .wrap(.{ .decl = decl_index }) } });
126
127 return decl_index; // root decl is needed for linking
128}
129
130pub fn runAllJobs(comp: *Comp) !void {
131 while (comp.jobs.readItem()) |job| {
132 // log.info("processing {}", .{job.dump(&comp.intern_pool)});
133 switch (job) {
134 .analysis => |detail| try comp.runAnalysisJob(detail.au, detail.first_blocked_sema),
135 .codegen_func => |detail| try comp.runCodegenJob(detail.value, detail.air),
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 },
65195 };
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);
72223}
73224
74225/// 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 {
83234 .file_path = absolute_file_path,
84235 .path_digest = null,
85236 .root_namespace = .none,
86 .status = .never_loaded,
87237 .source = null,
88238 .ast = null,
89239 .teg = null,
......@@ -104,17 +254,6 @@ pub const File = struct {
104254 file_path: []const u8,
105255
106256 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
119258 source: ?Source,
120259 ast: ?Ast,
......@@ -170,8 +309,8 @@ pub const File = struct {
170309
171310 pub const GetAstError = GetSourceError || Parse.Error;
172311
173 pub fn getAst(file: *File, comp: *Comp) GetAstError!Ast {
174 if (file.ast) |ast| {
312 pub fn getAst(file: *File, comp: *Comp) GetAstError!*const Ast {
313 if (file.ast) |*ast| {
175314 return ast;
176315 }
177316
......@@ -184,9 +323,9 @@ pub const File = struct {
184323 error.OutOfMemory => return err,
185324 error.ParseFail => {
186325 // TODO: let this merge with other report
187 var report = try err_data.toReport(comp, file);
188 report.dumpToWriter(comp, comp.out.writer, comp.out.tty) catch {};
189 report.deinit(comp.gpa);
326 var report = try err_data.toReport(comp.gpa);
327 defer report.deinit(comp.gpa);
328 report.dump(.{ .comp = .{ .comp = comp, .file = file } }, comp.out) catch {};
190329 return err;
191330 },
192331 };
......@@ -198,7 +337,7 @@ pub const File = struct {
198337 }
199338
200339 file.ast = ast;
201 return ast;
340 return &(file.ast.?);
202341 }
203342
204343 pub const GetTegError = GetAstError || Transform.Error;
......@@ -210,7 +349,7 @@ pub const File = struct {
210349
211350 const bin_digest = hash: {
212351 var path_hash: Cache.HashHelper = .{};
213 path_hash.addBytes("0.1.0");
352 path_hash.addBytes(@import("config").version_string);
214353 path_hash.addBytes(file.file_path);
215354 var bin: Cache.BinDigest = undefined;
216355 path_hash.hasher.final(&bin);
......@@ -218,12 +357,7 @@ pub const File = struct {
218357 };
219358 file.path_digest = bin_digest;
220359
221 const teg = try Transform.transform(
222 try file.getAst(comp),
223 comp.gpa,
224 file,
225 fail_report,
226 );
360 const teg = try Transform.transform((try file.getAst(comp)).*, comp.gpa, fail_report);
227361
228362 file.teg = teg;
229363 return teg;
......@@ -274,8 +408,6 @@ pub const File = struct {
274408 jw.write(std.fmt.bytesToHex(digest, .lower))
275409 else
276410 jw.write(null);
277 try jw.objectField("status");
278 try jw.write(ctx.file.status);
279411 try jw.objectField("root_namespace");
280412 if (ctx.file.root_namespace.unwrap()) |ns_index| {
281413 const ns = ctx.comp.intern_pool.namespacePtrConst(ns_index);
......@@ -325,29 +457,8 @@ pub const Decl = struct {
325457 /// This Decl corresponds to an AST Node that has not been referenced yet, and therefore
326458 /// because of Zig's lazy declaration analysis, it will remain unanalyzed until referenced.
327459 unreferenced,
328 /// Semantic analysis for this Decl is running right now.
329 /// This state detects dependency loops.
330460 in_progress,
331 /// Semantic analysis for this Decl is running right now, and has
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.
461 waiting,
351462 complete,
352463 },
353464
......@@ -513,38 +624,37 @@ pub fn indexTegModule(comp: *Comp, file: *File, module_index: Teg.Inst.Index) !N
513624 errdefer comp.intern_pool.destroyNamespace(comp.gpa, ns);
514625
515626 for (teg.moduleDeclarations(module_index)) |decl_index| {
516 const name = try comp.intern_pool.getOrPutString(
517 comp.gpa,
518 teg.declName(decl_index),
519 .no_embedded_nulls,
520 );
521 const tracked_inst = try comp.intern_pool.trackInst(comp.gpa, file, decl_index);
522 const decl = try comp.intern_pool.createDecl(comp.gpa, .{
523 .name = name,
524 .raw_value = null,
525 .src_namespace = ns,
526 .teg_decl_index = tracked_inst,
527 .analysis = .unreferenced,
528 });
529 try ns_ptr.decls.putNoClobberContext(comp.gpa, decl, void{}, .{ .comp = comp });
627 switch (teg.instTag(decl_index)) {
628 .declaration => {
629 const name = try comp.intern_pool.getOrPutString(
630 comp.gpa,
631 teg.declName(decl_index),
632 .no_embedded_nulls,
633 );
634 const tracked_inst = try comp.intern_pool.trackInst(comp.gpa, file, decl_index);
635 const decl = try comp.intern_pool.createDecl(comp.gpa, .{
636 .name = name,
637 .raw_value = null,
638 .src_namespace = ns,
639 .teg_decl_index = tracked_inst,
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 }
530650 }
531651
532652 return ns;
533653}
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
546655const Options = struct {
547656 dump_ir: bool = false,
657 verbose_sema: bool = false,
548658};
549659
550660/// The compiler backend is stored separately from the compilation, and
......@@ -565,7 +675,7 @@ pub const Backend = union(enum) {
565675 backend: Backend,
566676 comp: *Comp,
567677 value: InternPool.Value,
568 air: *const Air,
678 air: Air,
569679 arena: Allocator,
570680 ) HandleFunctionAirError!u32 {
571681 return backend.dispatch("handleFunctionAir", .{ comp, value, air, arena });
......@@ -575,7 +685,7 @@ pub const Backend = union(enum) {
575685 backend: Backend,
576686 comptime name: []const u8,
577687 args: anytype,
578 ) @typeInfo(@TypeOf(@field(Backend, name))).Fn.return_type.? {
688 ) @typeInfo(@TypeOf(@field(Backend, name))).@"fn".return_type.? {
579689 return switch (backend) {
580690 inline else => |ptr| @call(.auto, @field(@TypeOf(ptr.*), name), .{ptr} ++ args),
581691 };
......@@ -607,7 +717,7 @@ const std = @import("std");
607717const Allocator = std.mem.Allocator;
608718const assert = std.debug.assert;
609719const Stat = std.Build.Cache.File.Stat;
610const log = std.log.scoped(.comp);
720const LinearFifo = std.fifo.LinearFifo;
611721
612722const core = @import("mod.zig");
613723const InternPool = core.InternPool;
src/core/InternPool.zig+254-84
......@@ -8,6 +8,11 @@
88//!
99//! This data structure is self-contained, with the following exceptions:
1010//! * 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
1217/// Maps `Key` to `Value`. `Key` objects are not stored anywhere; they are
1318/// constructed lazily. The `Value` is also stored out of line in `.items`. The
......@@ -25,7 +30,7 @@ extra: ExtraStore = .{},
2530/// is referencing the data here whether they want to store both index and length,
2631/// thus allowing null bytes, or store only index, and use null-termination. The
2732/// `string_bytes` array is agnostic to either usage.
28string_bytes: std.ArrayListUnmanaged(u8) = .{},
33string_bytes: std.ArrayListUnmanaged(u8) = .empty,
2934
3035/// Used for finding the index inside `string_bytes`.
3136string_table: std.HashMapUnmanaged(
......@@ -33,11 +38,11 @@ string_table: std.HashMapUnmanaged(
3338 void,
3439 std.hash_map.StringIndexContext,
3540 std.hash_map.default_max_load_percentage,
36) = .{},
41) = .empty,
3742
3843/// An index into `tracked_insts` gives a reference to a single ZIR instruction which
3944/// persists across incremental updates.
40tracked_insts: std.AutoArrayHashMapUnmanaged(TrackedInst, void) = .{},
45tracked_insts: std.AutoArrayHashMapUnmanaged(TrackedInst, void) = .empty,
4146
4247/// Rather than allocating Decl objects with an Allocator, we instead allocate
4348/// them with this SegmentedList. This provides four advantages:
......@@ -58,6 +63,31 @@ allocated_namespaces: std.SegmentedList(Comp.Namespace, 0) = .{},
5863/// Same pattern as with `decls_free_list`.
5964namespaces_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
6191pub const Tag = enum(u8) {
6292 /// This special tag represents a value that was removed from this pool via
6393 /// `InternPool.remove`. The item remains allocated to preserve indices, but
......@@ -91,15 +121,16 @@ pub const Tag = enum(u8) {
91121 func,
92122 /// A string
93123 string,
94
124 /// An instance of a struct
95125 struct_value,
96
126 /// An instance of an array
97127 array_value,
128 /// An extern reference. For JS, this can be a property access
129 external_value,
98130
99131 pub fn Data(comptime tag: Tag) type {
100132 return switch (tag) {
101 .removed,
102 => unreachable, // does not have a data
133 .removed => unreachable, // does not have a data
103134
104135 .simple_type => SimpleType,
105136 .simple_value => SimpleValue,
......@@ -125,6 +156,7 @@ pub const Tag = enum(u8) {
125156 .struct_type => StructType,
126157 .array_type => ArrayType,
127158 .array_value => ArrayValue,
159 .external_value => ExternalValue,
128160 };
129161 }
130162
......@@ -137,15 +169,15 @@ pub const Tag = enum(u8) {
137169 };
138170
139171 /// Trailing:
140 /// 0. param_type: Index for each params_len
172 /// 0. param_type: Index to param type. Repeated .params_len
141173 pub const TypeFunction = struct {
142174 params_len: u32,
143175 return_type: Type,
144176 };
145177
146178 /// Trailing:
147 /// 0. field_key: NullTerminatedString. Repeated per field_len
148 /// 1. field_type: Type. Repeated per field_len
179 /// 0. field_key: NullTerminatedString. Repeated .field_len
180 /// 1. field_type: Type. Repeated .field_len
149181 pub const StructType = struct {
150182 fields_len: u32,
151183 };
......@@ -156,7 +188,7 @@ pub const Tag = enum(u8) {
156188 };
157189
158190 /// Trailing:
159 /// 1. item: Type. Repeated per items_len
191 /// 1. item: Type. Repeated per .items_len
160192 pub const ArrayValue = struct {
161193 /// This is the array type, not the item type
162194 type: Type,
......@@ -164,11 +196,17 @@ pub const Tag = enum(u8) {
164196 };
165197
166198 /// Trailing:
167 /// 0. value: Value. Repeated ty.structInfo().field_len
199 /// 0. value: Value. Repeated .ty.structInfo().field_len
168200 pub const StructValue = struct {
169201 /// The type of this field
170202 ty: Type,
171203 };
204
205 /// Trailing:
206 /// 0. value: Value. Repeated .key_count
207 pub const ExternalValue = struct {
208 key_count: u32,
209 };
172210};
173211
174212/// This is an index into .items
......@@ -183,11 +221,11 @@ pub const Value = enum(u32) {
183221 int32_type,
184222 float_type,
185223 string_type,
186 noreturn_type,
224 never_type,
187225 type_type,
188226 numeric_literal_type,
189227 numeric_literal_array_type,
190 noreturn_array_type,
228 never_array_type,
191229
192230 void,
193231 bool_true,
......@@ -289,6 +327,7 @@ pub const Value = enum(u32) {
289327 .func => value.toKey(ip).func.ty,
290328 .struct_value => value.toKey(ip).struct_value.ty,
291329 .array_value => value.toKey(ip).array_value.type,
330 .external_value => .@"opaque",
292331 };
293332 }
294333
......@@ -312,7 +351,7 @@ pub const Value = enum(u32) {
312351 }
313352
314353 pub fn toTypeUnsafe(value: Value, proof_ip: *const InternPool) Type {
315 if (std.debug.runtime_safety) {
354 if (core.runtime_safety) {
316355 return value.toType(proof_ip) orelse
317356 std.debug.panic("{} is not a type", .{value.dump(proof_ip)});
318357 }
......@@ -347,11 +386,11 @@ pub const Type = enum(u32) {
347386 int32 = @intFromEnum(Value.int32_type),
348387 float = @intFromEnum(Value.float_type),
349388 string = @intFromEnum(Value.string_type),
350 noreturn = @intFromEnum(Value.noreturn_type),
389 never = @intFromEnum(Value.never_type),
351390 type = @intFromEnum(Value.type_type),
352391 numeric_literal = @intFromEnum(Value.numeric_literal_type),
353392 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),
355394 void = @intFromEnum(Value.void),
356395 _,
357396
......@@ -416,7 +455,7 @@ pub const Type = enum(u32) {
416455 => true,
417456 .numeric_literal,
418457 .type,
419 .noreturn,
458 .never,
420459 => false,
421460 else => switch (ty.tag(ip)) {
422461 .simple_type => unreachable, // all simple types are already printed
......@@ -441,7 +480,7 @@ pub const Type = enum(u32) {
441480 .void,
442481 .numeric_literal,
443482 .type,
444 .noreturn,
483 .never,
445484 => false,
446485 else => switch (ty.tag(ip)) {
447486 .simple_type => unreachable, // all simple types are already printed
......@@ -476,7 +515,7 @@ pub const Type = enum(u32) {
476515 .int32 => return try writer.writeAll("int"),
477516 .float => return try writer.writeAll("float"),
478517 .string => return try writer.writeAll("string"),
479 .noreturn => return try writer.writeAll("noreturn"),
518 .never => return try writer.writeAll("never"),
480519 .type => return try writer.writeAll("type"),
481520 .numeric_literal => return try writer.writeAll("numeric literal"),
482521 .void => return try writer.writeAll("void"),
......@@ -512,7 +551,7 @@ pub const Item = struct {
512551 data: EncodedData,
513552
514553 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) };
516555 }
517556};
518557
......@@ -536,11 +575,11 @@ pub const static_keys = [_]Key{
536575 SimpleType.int32.key(),
537576 SimpleType.float.key(),
538577 SimpleType.string.key(),
539 SimpleType.noreturn.key(),
578 SimpleType.never.key(),
540579 SimpleType.type.key(),
541580 SimpleType.numeric_literal.key(),
542581 .{ .array_type = .{ .item_type = Type.numeric_literal } },
543 .{ .array_type = .{ .item_type = Type.noreturn } },
582 .{ .array_type = .{ .item_type = Type.never } },
544583
545584 SimpleValue.void.key(),
546585 SimpleValue.true.key(),
......@@ -549,7 +588,7 @@ pub const static_keys = [_]Key{
549588 .{ .float = 0.0 },
550589 .{ .float = Float.pure_nan.decode() },
551590 .{ .array_value = .{
552 .type = Type.noreturn_array,
591 .type = Type.never_array,
553592 .items = &.{},
554593 } },
555594 .{ .string = "" },
......@@ -646,10 +685,6 @@ pub const NullTerminatedString = enum(u32) {
646685 return @enumFromInt(@intFromEnum(self));
647686 }
648687
649 // pub fn toOptional(self: NullTerminatedString) Optional{
650 // return @enumFromInt(@intFromEnum(self));
651 // }
652
653688 pub fn toSlice(string: NullTerminatedString, ip: *const InternPool) [:0]const u8 {
654689 const slice = ip.string_bytes.items[@intFromEnum(string)..];
655690 return slice[0..std.mem.indexOfScalar(u8, slice, 0).? :0];
......@@ -801,51 +836,6 @@ pub const NamespaceIndex = enum(u32) {
801836 };
802837};
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
849839const SimpleValue = enum {
850840 true,
851841 false,
......@@ -862,7 +852,7 @@ const SimpleType = enum {
862852 float,
863853 string,
864854 bool,
865 noreturn,
855 never,
866856 type,
867857 numeric_literal,
868858
......@@ -886,6 +876,7 @@ pub const Key = union(enum) {
886876 function_type: FunctionType,
887877 struct_type: StructType,
888878 array_type: ArrayType,
879 external_value: ExternalValue,
889880
890881 const Adapter = struct {
891882 intern_pool: *const InternPool,
......@@ -950,6 +941,16 @@ pub const Key = union(enum) {
950941 }
951942 };
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
953954 pub const ArrayValue = struct {
954955 /// This is the array type, not the item type
955956 type: Type,
......@@ -983,7 +984,7 @@ pub const Key = union(enum) {
983984 defer t.end();
984985
985986 const asBytes = std.mem.asBytes;
986 const KeyTag = @typeInfo(Key).Union.tag_type.?;
987 const KeyTag = @typeInfo(Key).@"union".tag_type.?;
987988 const seed = @intFromEnum(@as(KeyTag, key));
988989
989990 return switch (key) {
......@@ -1000,6 +1001,7 @@ pub const Key = union(enum) {
10001001 .struct_value,
10011002 .array_value,
10021003 .array_type,
1004 .external_value,
10031005 => |x| {
10041006 var hasher = Hash.init(seed);
10051007 x.hash(&hasher);
......@@ -1013,7 +1015,7 @@ pub const Key = union(enum) {
10131015 const t = tracer.trace(@src());
10141016 defer t.end();
10151017
1016 const KeyTag = @typeInfo(Key).Union.tag_type.?;
1018 const KeyTag = @typeInfo(Key).@"union".tag_type.?;
10171019 const a_tag: KeyTag = a;
10181020 const b_tag: KeyTag = b;
10191021 if (a_tag != b_tag) return false;
......@@ -1053,6 +1055,10 @@ pub const Key = union(enum) {
10531055 return a_info.type == b_info.type and
10541056 std.mem.eql(Value, a_info.items, b_info.items);
10551057 },
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 },
10561062 };
10571063 }
10581064};
......@@ -1079,9 +1085,113 @@ const EmbeddedNulls = enum {
10791085 }
10801086};
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
10821192// implementations:
10831193
1084/// To construct, instantiate `.{ .gpa = gpa }` then call this.
1194/// To construct, instantiate `.{}` then call this.
10851195pub fn init(ip: *InternPool, gpa: Allocator) !void {
10861196 const t = tracer.trace(@src());
10871197 defer t.end();
......@@ -1102,7 +1212,7 @@ pub fn init(ip: *InternPool, gpa: Allocator) !void {
11021212 _ = ip.get(gpa, key) catch unreachable;
11031213 }
11041214
1105 if (std.debug.runtime_safety) {
1215 if (core.runtime_safety) {
11061216 // Sanity check.
11071217 assert(ip.valueToKey(.bool_true).simple_value == .true);
11081218 assert(ip.valueToKey(.bool_false).simple_value == .false);
......@@ -1139,16 +1249,16 @@ pub fn deinit(ip: *InternPool, gpa: Allocator) void {
11391249
11401250 ip.tracked_insts.deinit(gpa);
11411251
1142 // ip.src_hash_deps.deinit(gpa);
1143 // ip.decl_val_deps.deinit(gpa);
1252 ip.src_hash_deps.deinit(gpa);
1253 ip.decl_val_deps.deinit(gpa);
11441254 // ip.func_ies_deps.deinit(gpa);
11451255 // ip.namespace_deps.deinit(gpa);
11461256 // 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);
1151 // ip.free_dep_entries.deinit(gpa);
1260 ip.dep_entries.deinit(gpa);
1261 ip.free_dep_entries.deinit(gpa);
11521262
11531263 ip.* = undefined;
11541264}
......@@ -1212,6 +1322,12 @@ pub fn valueToKey(ip: *const InternPool, index: Value) Key {
12121322 .items = trail.readValueSlice(payload.items_len).get(ip),
12131323 } };
12141324 },
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 },
12151331 };
12161332}
12171333
......@@ -1307,10 +1423,17 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) !Value {
13071423 trail.writeSlice(s.items);
13081424 break :brk Item.encode(.array_value, extra);
13091425 },
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 },
13101433 };
13111434 ip.items.appendAssumeCapacity(item);
13121435 const index: Value = @enumFromInt(ip.items.len - 1);
1313 if (std.debug.runtime_safety) {
1436 if (core.runtime_safety) {
13141437 const key_added = ip.valueToKey(index);
13151438 assert(key == .struct_type or key_added.eql(key, ip));
13161439 }
......@@ -1398,6 +1521,53 @@ pub fn trackInst(ip: *InternPool, gpa: Allocator, file: *Comp.File, inst: Teg.In
13981521 return @enumFromInt(gop.index);
13991522}
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
14011571pub fn declPtr(ip: *InternPool, index: DeclIndex) *Comp.Decl {
14021572 return ip.allocated_decls.at(@intFromEnum(index));
14031573}
src/core/Parse.zig+103-71
......@@ -18,24 +18,7 @@ depth: u32 = 0,
1818const depth_limit = 4096;
1919
2020pub fn parse(gpa: std.mem.Allocator, source: [:0]const u8, ast_error: *AstError.WithTrace) Parse.Error!Ast {
21 var err_byte: ByteOffset = 0;
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 };
21 var tokens = try Tokenizer.tokenize(source, gpa);
3922 errdefer tokens.deinit(gpa);
4023
4124 var parser = Parse.init(gpa, source, tokens);
......@@ -125,11 +108,11 @@ pub const ExpectedContext = enum {
125108/// When printing these, token list and Ast is not actually available, so full tokens
126109/// are stored in the errors.
127110pub const AstError = union(enum) {
128 /// Failure during tokenization
129 tokenization: struct {
130 err: Tokenizer.TokenizeError,
131 byte: ByteOffset,
132 },
111 // /// Failure during tokenization
112 // tokenization: struct {
113 // err: Tokenizer.TokenizeError,
114 // byte: ByteOffset,
115 // },
133116 /// Unexpected data, expected a token.
134117 unexpected_token: struct {
135118 expected: Token.Tag,
......@@ -145,29 +128,29 @@ pub const AstError = union(enum) {
145128
146129 array_type_flipped_hint: Token,
147130
148 pub fn toReport(err: AstError, comp: *Comp, file: *Comp.File, trace: DebugStoredTrace) !Report {
149 return (Report.Err{ .ast = err }).toReport(comp, file, trace);
131 pub fn toReport(err: AstError, allocator: Allocator, trace: DebugStoredTrace) !Report {
132 return (Report.Err{ .ast = err }).toReport(allocator, trace);
150133 }
151134
152135 pub fn build(err: AstError, ctx: *Report.MsgContext) !void {
153136 switch (err) {
154 .tokenization => |data| {
155 // TODO: consider making tokenizer emit AstError more directly,
156 // so that it can just annotate start and ends
157 try ctx.msg(switch (data.err) {
158 error.UnterminatedString => .{ .codepoint_eol = data.byte },
159 else => .{ .codepoint = data.byte },
160 }, "{s}", .{switch (data.err) {
161 error.OutOfMemory => unreachable,
162 error.InvalidSyntax => "unexpected token",
163 error.TrailingNumericSeparator => "trailing numeric separator",
164 error.UnterminatedString => "unterminated string literal",
165 error.InvalidEscapeSequence => "invalid escape sequence",
166 error.InvalidEscapeCodepoint => "invalid escape codepoint",
167 error.InvalidUtf8 => "file is not valid utf8",
168 error.GitConflict => "git merge conflict encountered",
169 }});
170 },
137 // .tokenization => |data| {
138 // // TODO: consider making tokenizer emit AstError more directly,
139 // // so that it can just annotate start and ends
140 // try ctx.msg(switch (data.err) {
141 // error.UnterminatedString => .{ .codepoint_eol = data.byte },
142 // else => .{ .codepoint = data.byte },
143 // }, "{s}", .{switch (data.err) {
144 // error.OutOfMemory => unreachable,
145 // error.InvalidSyntax => "unexpected token",
146 // error.TrailingNumericSeparator => "trailing numeric separator",
147 // error.UnterminatedString => "unterminated string literal",
148 // error.InvalidEscapeSequence => "invalid escape sequence",
149 // error.InvalidEscapeCodepoint => "invalid escape codepoint",
150 // error.InvalidUtf8 => "file is not valid utf8",
151 // error.GitConflict => "git merge conflict encountered",
152 // }});
153 // },
171154 .unexpected_token => |data| {
172155 try ctx.msg(.{ .token_stored = data.got }, "expected {s}, but found {s}", .{
173156 data.expected.description(),
......@@ -197,8 +180,8 @@ pub const AstError = union(enum) {
197180 err: AstError,
198181 trace: DebugStoredTrace,
199182
200 pub fn toReport(err: WithTrace, comp: *Comp, file: *Comp.File) !Report {
201 return err.err.toReport(comp, file, err.trace);
183 pub fn toReport(wt: WithTrace, allocator: Allocator) !Report {
184 return wt.err.toReport(allocator, wt.trace);
202185 }
203186 };
204187};
......@@ -247,8 +230,13 @@ fn parseModuleMembers(p: *Parse) Error!SubList {
247230 .kw_pub,
248231 => try p.scratch.append(p.gpa, try p.parseModuleDecl()),
249232
250 .kw_struct,
251 => try p.scratch.append(p.gpa, try p.parseStructDecl()),
233 .kw_struct => 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
253241 .kw_enum,
254242 .kw_trait,
......@@ -261,25 +249,32 @@ fn parseModuleMembers(p: *Parse) Error!SubList {
261249 if (!p.isExpressionStart()) {
262250 return p.failUnexpectedToken(.statement);
263251 }
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) {
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 => {},
264 const first_tag = p.currentTag();
275265
276 // TODO: make semicolons optional, but emit a warning
277 else => _ = try p.expect(.semi),
278 }
279 },
280 };
266 try p.scratch.append(p.gpa, try p.parseExpression(.statement));
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 }
283278}
284279
285280fn parseModuleDecl(p: *Parse) Error!NodeIndex {
......@@ -313,9 +308,10 @@ fn parseFnDecl(p: *Parse) Error!NodeIndex {
313308
314309 _ = try p.expect(.id);
315310
316 _ = try p.expect(.start_paren);
317
318 const params = try p.parseParamList(.types_required);
311 const params = if (p.eatToken(.start_paren) != null)
312 try p.parseParamList(.types_required)
313 else
314 null;
319315
320316 const return_type = if (p.eatToken(.minus_lt)) |_|
321317 if (!p.isExpressionStart())
......@@ -329,7 +325,7 @@ fn parseFnDecl(p: *Parse) Error!NodeIndex {
329325 const body = try p.parseBlock();
330326
331327 const extra = try p.addExtra(Ast.Fn, .{
332 .params = params,
328 .params = params orelse .empty,
333329 .return_type = optionalNodeIndex(return_type),
334330 });
335331
......@@ -413,6 +409,28 @@ fn parseStructDecl(p: *Parse) Error!NodeIndex {
413409 });
414410}
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
416434fn parseStructFieldList(p: *Parse) Error!SubList {
417435 const trace = t.trace(@src());
418436 defer trace.end();
......@@ -1255,7 +1273,7 @@ fn parseArrayValueOrType(p: *Parse, mode: ParseExprMode) Error!NodeIndex {
12551273}
12561274
12571275fn parseTupleType(p: *Parse) Error!NodeIndex {
1258 const main_token = p.assertToken(.start_bracket);
1276 const main_token = p.assertToken(.start_paren);
12591277 const list = try p.parseExpressionList(.end_paren, .expression);
12601278 return p.addNode(.{
12611279 .tag = .tuple_type,
......@@ -1334,12 +1352,7 @@ fn eatToken(p: *Parse, tag: Token.Tag) ?TokenIndex {
13341352}
13351353
13361354fn expect(p: *Parse, tag: Token.Tag) !TokenIndex {
1337 if (p.currentTag() != tag) {
1338 return p.fail(.{ .unexpected_token = .{
1339 .got = p.fullToken(p.pos),
1340 .expected = tag,
1341 } });
1342 }
1355 try p.expectWithoutAdvance(tag);
13431356 return p.nextToken();
13441357}
13451358
......@@ -1364,6 +1377,11 @@ fn nextToken(p: *Parse) TokenIndex {
13641377 return result;
13651378}
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
13671385fn fullToken(p: *Parse, token: TokenIndex) Token {
13681386 return Token{
13691387 .tag = p.token_tags[token],
......@@ -1417,3 +1435,17 @@ const t = @import("tracer");
14171435
14181436const Report = core.Report;
14191437const 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 @@
55//!
66//! Across the compiler, `Report.Builder`s' `.err` and `.warn` methods
77//! can add messages by ID.
8errors: []Item = &.{},
9warnings: []Item = &.{},
10string_bytes: []u8 = &.{},
8errors: []Item,
9warnings: []Item,
10string_bytes: []u8,
11
12pub const empty: Report = .{
13 .errors = &.{},
14 .warnings = &.{},
15 .string_bytes = &.{},
16};
1117
1218pub var debug_show_report_trace: if (builtin.mode == .Debug) bool else void =
1319 if (builtin.mode == .Debug) false;
......@@ -15,31 +21,23 @@ pub var debug_show_report_trace: if (builtin.mode == .Debug) bool else void =
1521pub const Item = struct {
1622 /// What is this diagnostic
1723 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
2327 /// Type is never stored, inferred from which list it is in.
2428 pub const Type = enum { @"error", warn };
2529};
2630
2731pub const Err = union(enum) {
28 pub const FieldEnum = std.meta.FieldEnum(Err);
32 pub const FieldEnum = core.FieldEnum(Err);
2933
3034 /// Constructs a `Report` with just a single error.
31 pub fn toReport(err: Err, comp: *Comp, file: *File, trace: DebugStoredTrace) !Report {
32 const string_bytes = try comp.gpa.dupe(u8, file.file_path);
33 errdefer comp.gpa.free(string_bytes);
34
35 const errors = try comp.gpa.dupe(Item, &.{.{
35 pub fn toReport(err: Err, alloc: Allocator, trace: DebugStoredTrace) !Report {
36 const errors = try alloc.dupe(Item, &.{.{
3637 .data = .{ .@"error" = err },
37 .url = .{ .start = 0, .len = @intCast(string_bytes.len) },
3838 .debug_stack_trace = trace,
3939 }});
40 errdefer comp.gpa.free(errors);
41
42 return .{ .errors = errors, .string_bytes = string_bytes };
40 return .{ .errors = errors, .warnings = &.{}, .string_bytes = &.{} };
4341 }
4442
4543 ast: @import("Parse.zig").AstError,
......@@ -54,6 +52,20 @@ pub const Err = union(enum) {
5452 });
5553 }
5654 },
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 },
5769 unknown_identifier: struct {
5870 node: NodeIndex,
5971
......@@ -82,8 +94,8 @@ pub const Err = union(enum) {
8294 fn build(data: @This(), ctx: *MsgContext) !void {
8395 try ctx.msgToken(
8496 ctx.ast.nodeToken(data.node),
85 "unknown built-in function {}",
86 .{formatId(ctx.ast.basicNodeSlice(data.node))},
97 "unknown built-in function {s}",
98 .{ctx.ast.basicNodeSlice(data.node)},
8799 );
88100 }
89101 },
......@@ -103,10 +115,11 @@ pub const Err = union(enum) {
103115 prev: NodeIndex,
104116
105117 fn build(data: @This(), ctx: *MsgContext) !void {
106 try ctx.msgToken(ctx.ast.fnDeclIdToken(data.node), "re-declaration of {}", .{
107 formatId(ctx.ast.fullFnDecl(data.node).id),
118 const id_token = ctx.ast.idToken(data.node);
119 try ctx.msgToken(id_token, "re-declaration of {}", .{
120 formatId(ctx.ast.tokenSlice(id_token)),
108121 });
109 try ctx.msgToken(ctx.ast.fnDeclIdToken(data.prev), "also declared here", .{});
122 try ctx.msgToken(ctx.ast.idToken(data.prev), "also declared here", .{});
110123 // TODO: be able to store a full list of other declarations
111124 // and add them here as "and here" and so on
112125 }
......@@ -177,6 +190,14 @@ pub const Err = union(enum) {
177190 try ctx.msgNode(node, "numeric literal \"{d}\" cannot be coerced to \"int\"", .{data.val});
178191 }
179192 },
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 },
180201 coercion: struct {
181202 from: Type,
182203 to: Type,
......@@ -535,6 +556,29 @@ pub const Warn = union(enum) {
535556 },
536557};
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
538582pub const UnusedKind = enum {
539583 param,
540584 constant,
......@@ -579,50 +623,96 @@ pub fn deinit(report: Report, gpa: Allocator) void {
579623 gpa.free(report.string_bytes);
580624}
581625
582pub fn dumpToWriter(report: Report, comp: *Comp, writer: anytype, tty: std.io.tty.Config) !void {
583 // try writer.print("{d} errors, {d} warnings\n", .{ report.errors.len, report.warnings.len });
626pub fn dump(report: Report, ctx: DumpContext, out: core.OutStream) !void {
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
584691 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);
586694 }
587695 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);
589698 }
590699}
591700
592pub fn formatItem(
593 report: *const Report,
701fn formatItem(
594702 item: Item,
595703 item_type: Item.Type,
596 comp: *Comp,
597 writer: anytype,
598 tty: std.io.tty.Config,
704 arena: Allocator,
705 url: []const u8,
706 ctx_init: MsgContext,
707 out: core.OutStream,
599708) !void {
600 var arena = std.heap.ArenaAllocator.init(comp.gpa);
601 defer arena.deinit();
602 var sfa = std.heap.stackFallback(16384, arena.allocator());
709 var sfa = std.heap.stackFallback(16384, arena);
603710 const alloc = sfa.get();
604711
605 const file = try comp.getFile(item.url.slice(report));
606
607 const need_load_ast = !(item_type == .@"error" and item.data.@"error" == .ast);
608 const ast_data = if (need_load_ast)
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 };
712 var ctx: MsgContext = ctx_init;
713 ctx.arena = alloc;
714 ctx.components = ArrayListUnmanaged(Component).initCapacity(alloc, 16) catch
715 unreachable; // stack-fallback allocator has enough space.
626716
627717 switch (item_type) {
628718 inline else => |tag| switch (@field(item.data, @tagName(tag))) {
......@@ -632,6 +722,9 @@ pub fn formatItem(
632722
633723 assert(ctx.components.items.len > 0);
634724
725 const writer = out.writer;
726 const tty = out.tty;
727
635728 const width = brk: {
636729 var max: usize = 0;
637730 for (ctx.components.items) |component| {
......@@ -704,7 +797,7 @@ pub fn formatItem(
704797 try writer.writeByteNTimes(' ', width);
705798 try writer.print("{s}{s}:{d}:{d}\n", .{
706799 if (i == 1) "┬ " else "├ ",
707 file.file_path,
800 url,
708801 at.loc.line.oneBased(),
709802 at.loc.column.oneBased(),
710803 });
......@@ -740,52 +833,58 @@ pub fn formatItem(
740833 }
741834}
742835
743pub fn dumpToStdErr(report: Report, comp: *Comp) !void {
744 const stderr = std.io.getStdErr();
745 const tty = std.io.tty.detectConfig(stderr);
746
747 if (false or
748 // Windows API uses its own API calls, so a buffered writer
749 // cannot be used :/
750 (builtin.os.tag == .windows and tty == .windows_api) or
751 // Debug stack trace printer writes directly to standard err,
752 // so we need to be unbuffered for that.
753 (builtin.mode == .Debug and debug_show_report_trace))
754 {
755 try dumpToWriter(report, comp, stderr.writer(), tty);
756 } else {
757 var buf = std.io.bufferedWriter(stderr.writer());
758 const writer = buf.writer();
759 try dumpToWriter(report, comp, writer, tty);
760 try buf.flush();
761 }
762}
836// pub fn dumpToStdErr(report: Report, comp: *Comp) !void {
837// const stderr = std.io.getStdErr();
838// const tty = std.io.tty.detectConfig(stderr);
839
840// if (false or
841// // Windows API uses its own API calls, so a buffered writer
842// // cannot be used :/
843// (builtin.os.tag == .windows and tty == .windows_api) or
844// // Debug stack trace printer writes directly to standard err,
845// // so we need to be unbuffered for that.
846// (builtin.mode == .Debug and debug_show_report_trace))
847// {
848// try dumpToWriter(report, comp, stderr.writer(), tty);
849// } else {
850// var buf = std.io.bufferedWriter(stderr.writer());
851// const writer = buf.writer();
852// try dumpToWriter(report, comp, writer, tty);
853// try buf.flush();
854// }
855// }
763856
764857pub const Builder = struct {
765858 gpa: Allocator,
766 current_file: *Comp.File,
767 errors: ArrayListUnmanaged(Item) = .{},
768 warnings: ArrayListUnmanaged(Item) = .{},
769 string_bytes: ArrayListUnmanaged(u8) = .{},
770 current_file_str: ?String = null,
859 errors: ArrayListUnmanaged(Item),
860 warnings: ArrayListUnmanaged(Item),
861 string_bytes: ArrayListUnmanaged(u8),
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 {
773873 try errAt(b, kind, data, @returnAddress());
774874 }
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 {
777877 try warnAt(b, kind, data, @returnAddress());
778878 }
779879
780880 pub fn errAt(
781881 b: *Builder,
782882 comptime kind: Err.FieldEnum,
783 data: std.meta.FieldType(Err, kind),
883 data: core.FieldType(Err, kind),
784884 return_address: usize,
785885 ) !void {
786886 try b.errors.append(b.gpa, .{
787887 .data = .{ .@"error" = @unionInit(Err, @tagName(kind), data) },
788 .url = try b.currentFileString(),
789888 .debug_stack_trace = DebugStoredTrace.capture(return_address),
790889 });
791890 }
......@@ -793,12 +892,11 @@ pub const Builder = struct {
793892 pub fn warnAt(
794893 b: *Builder,
795894 comptime kind: Warn.FieldEnum,
796 data: std.meta.FieldType(Warn, kind),
895 data: core.FieldType(Warn, kind),
797896 return_address: usize,
798897 ) !void {
799898 try b.warnings.append(b.gpa, .{
800899 .data = .{ .warn = @unionInit(Warn, @tagName(kind), data) },
801 .url = try b.currentFileString(),
802900 .debug_stack_trace = DebugStoredTrace.capture(return_address),
803901 });
804902 }
......@@ -823,17 +921,14 @@ pub const Builder = struct {
823921 }
824922
825923 pub fn finalize(b: *Builder) !Report {
826 defer b.* = undefined;
827 errdefer b.deinit();
828
829924 const errors = try b.errors.toOwnedSlice(b.gpa);
830 errdefer b.gpa.free(errors);
925 errdefer b.errors = .fromOwnedSlice(errors);
831926
832927 const warnings = try b.warnings.toOwnedSlice(b.gpa);
833 errdefer b.gpa.free(warnings);
928 errdefer b.warnings = .fromOwnedSlice(warnings);
834929
835930 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
838933 return .{
839934 .errors = errors,
......@@ -943,8 +1038,8 @@ pub const Highlight = union(enum) {
9431038
9441039/// This serves as a small API to make creating messages easier.
9451040pub const MsgContext = struct {
946 file: *const File,
947 comp: *const Comp,
1041 file: ?*const File,
1042 comp: ?*const Comp,
9481043 ast: *const Ast,
9491044 report: *const Report,
9501045 utf8_source: [:0]const u8,
......@@ -981,18 +1076,19 @@ pub const MsgContext = struct {
9811076 }
9821077
9831078 pub fn ip(ctx: *const MsgContext) *const InternPool {
984 return &ctx.comp.intern_pool;
1079 return &ctx.comp.?.intern_pool;
9851080 }
9861081};
9871082
9881083pub const LazyNode = struct {
1084 // TODO: remove this, it should be inferable off of the analysis unit that belongs to the report
9891085 decl: Comp.Decl.Index,
9901086 inst: Teg.Inst.Index,
9911087 operand: Operand,
9921088
9931089 pub fn get(lazy: LazyNode, ctx: *MsgContext) !NodeIndex {
994 const decl = ctx.comp.intern_pool.declPtrConst(lazy.decl);
995 const ns = ctx.comp.intern_pool.namespacePtrConst(decl.src_namespace);
1090 const decl = ctx.comp.?.intern_pool.declPtrConst(lazy.decl);
1091 const ns = ctx.comp.?.intern_pool.namespacePtrConst(decl.src_namespace);
9961092
9971093 // TODO: This might change if we have errors like
9981094 // - function arguments not matching at <callsite>
......@@ -1005,7 +1101,7 @@ pub const LazyNode = struct {
10051101 @panic("TODO: LazyNode.get when the decl is in a nested namespace");
10061102 }
10071103
1008 const teg = &(ctx.file.teg orelse
1104 const teg = &(ctx.file.?.teg orelse
10091105 // this will be required once incremental compilation hits
10101106 @panic("TODO: teg was unloaded"));
10111107
......@@ -1072,7 +1168,7 @@ pub const LazyNode = struct {
10721168 else => fn_decl.params.slice(ctx.ast)[lazy.operand.getArgId()],
10731169 };
10741170 },
1075 .js => {
1171 .@"extern" => {
10761172 return base_node;
10771173 },
10781174 .struct_init => {
......@@ -1090,6 +1186,11 @@ pub const LazyNode = struct {
10901186 },
10911187 }
10921188 },
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 },
10931194 .array_init => {
10941195 assert(node_tag == .array_init);
10951196 const items = data.list.slice(ctx.ast);
......@@ -1101,11 +1202,21 @@ pub const LazyNode = struct {
11011202 else => {},
11021203 }
11031204
1104 std.debug.panic("TODO: operand lookup: .{s} + .{s} + {}", .{
1105 @tagName(teg.instTag(lazy.inst)),
1106 @tagName(ctx.ast.nodeTag(base_node)),
1107 lazy.operand,
1108 });
1205 if (builtin.mode == .Debug) {
1206 std.debug.panic("TODO: operand lookup: .{s} + .{s} + {}", .{
1207 @tagName(teg.instTag(lazy.inst)),
1208 @tagName(ctx.ast.nodeTag(base_node)),
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;
11091220 }
11101221};
11111222
......@@ -1125,18 +1236,29 @@ pub const Operand = enum(u32) {
11251236 /// One of:
11261237 /// - An argument of a .call node
11271238 /// - A parameter of a .fn_decl node
1239 /// - A value of a .struct_decl node
11281240 pub fn argument(id: u32) Operand {
1129 return @enumFromInt(id + 5);
1241 return @enumFromInt(id + 6);
11301242 }
11311243
11321244 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 }
11341256 }
11351257};
11361258
11371259/// Non-allocating formatter for taking an id token slice (raw), normalizing it
11381260/// into it's canonical form.
1139fn formatId(id: []const u8) std.fmt.Formatter(formatIdImpl) {
1261pub fn formatId(id: []const u8) std.fmt.Formatter(formatIdImpl) {
11401262 return .{ .data = id };
11411263}
11421264
......@@ -1148,7 +1270,7 @@ fn formatIdImpl(
11481270) !void {
11491271 comptime assert(format.len == 0);
11501272
1151 if (id[0] != '@') {
1273 if (id[0] != '@' and !Tokenizer.Token.keywords.has(id)) {
11521274 // no work needed
11531275 try writer.writeByte('"');
11541276 try writer.writeAll(id);
......@@ -1303,14 +1425,14 @@ fn formatIdImpl(
13031425 try writer.writeByte('"');
13041426}
13051427
1306fn formatIdCode(id: []const u8) std.fmt.Formatter(formatIdCodeImpl) {
1428pub fn formatIdCode(id: []const u8) std.fmt.Formatter(formatIdCodeImpl) {
13071429 return .{ .data = id };
13081430}
13091431
13101432fn formatIdCodeImpl(
13111433 id: []const u8,
13121434 comptime format: []const u8,
1313 _: std.fmt.FormatOptions,
1435 opts: std.fmt.FormatOptions,
13141436 writer: anytype,
13151437) !void {
13161438 comptime assert(format.len == 0);
......@@ -1319,10 +1441,7 @@ fn formatIdCodeImpl(
13191441 try writer.writeAll(id);
13201442 return;
13211443 }
1322
1323 try writer.writeAll("@\"");
1324
1325 try writer.writeByte('"');
1444 try formatIdImpl(id, format, opts, writer);
13261445}
13271446
13281447const Report = @This();
src/core/Sema.zig+1341-1088
......@@ -1,85 +1,287 @@
1/// absolutely stealing zig on this one haha
2/// This struct is created per declaration and chills
3///
4/// TODO: explain this in more depth. or maybe take this to architecture.md
5///
6/// All things in semantic analysis are either Air.Inst.Index (runtime known
7/// value pointing to the instruction that computed it), or Value (comptime known)
8///
9/// There is a union of these two called Ref [Air.Inst.Ref]. Generally,
10/// functions referring to Ref refer to runtime known values.
11///
12/// Many things have two versions, one for Ref (runtime) + one for Value (comptime)
13///
14/// Example is going from Teg.Inst.Index to the result of it's analysis:
15/// - s.resolveRef()
16/// - s.resolveValue()
17/// The latter will ensure the result is a Value (comptime) and return that.
18///
19/// Many things in Semantic Analysis don't have a way to represent constants
20/// using Air.Inst.Index, and we also want to avoid emitting things like `1 + 2`.
21///
22/// We do that by using Air.Inst.Ref.value() or .decode(), which allows
23/// observing if the value is known at compile time. This makes analysis
24/// lazy and forces constant folding whenever possible. (see .bool_not for a
25/// simple example)
26
27// inputs:
1//! Semantic Analysis
2//!
3//! Designed as a nonrecursive pause/resumable state machine, Sema converts
4//! declarations to Intern Pool `Value`s and function bodies to `Air`.
5//!
6//! - Compile time resolution of values
7//! - Type checking
8
289comp: *Comp,
29gpa: Allocator,
30arena: Allocator,
31code: Teg,
3210ip: *InternPool,
33decl: *Decl,
34decl_index: Decl.Index,
35namespace_index: Namespace.Index,
11gpa: Allocator,
12arena_state: std.heap.ArenaAllocator,
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,
3939/// Scratch memory for temporary allocations.
4040/// Block contents are appended for marks.
41scratch: core.U32Stack = .{},
42
43/// This is only set for function declarations
44params: []Type = undefined,
41scratch: core.U32Stack,
4542
46// outputs
47report: Report.Builder = undefined,
48air_instructions: std.MultiArrayList(Air.Inst) = .{},
49air_extra: store.ExtraStore(.air, .@"var") = .{},
43/// To validate that a pointer to `Sema` is not corrupted.
44magic: Magic align(1),
5045
51temp_result_map: std.AutoArrayHashMapUnmanaged(Teg.Inst.Index, Type) = .{},
46instruction_step_lock: std.debug.SafetyLock,
5247
5348pub const Error = error{
54 /// Analysis has failed, see error list
49 /// The analysis of this branch or declaration has failed.
50 /// At least one error will be placed in the `Sema`'s report
5551 AnalysisFail,
56 // This analysis is waiting on other dependencies to finish
57 // Interrupt,
58
59 // TODO: add handling of this
52 /// There is a dependency on another declaration that we are waiting on.
53 Interrupt,
54 /// Must invoke garbage collection?
6055 TooManyValues,
61 // TODO: add handling of this
62 HasNulls,
63 // TODO: remove this from the primary error set
64 HitNoReturn,
65
66 OutStream,
6756 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.
71/// This is only possible because all instructions are adjacent in Teg.
201 fn currentIndex(frame: *const Frame) Teg.Inst.Index {
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.
72270const InstMap = struct {
73271 items: []Air.Inst.Ref.Optional,
74272 start: Teg.Inst.Index,
75 debug_traces: if (std.debug.runtime_safety) []StoredTrace else void,
76
77 pub fn init(allocator: Allocator, code: *const Teg, inst_start: Teg.Inst.Index) !InstMap {
78 const decl = code.declData(inst_start);
79 const insts = try allocator.alloc(Air.Inst.Ref.Optional, decl.instruction_count);
80 const debug_traces = if (std.debug.runtime_safety)
81 try allocator.alloc(StoredTrace, decl.instruction_count);
82 @memset(insts, .none);
273 debug_traces: if (runtime_safety) []StoredTrace else void,
274
275 pub fn init(allocator: Allocator, inst_start: Teg.Inst.Index, inst_len: u32) !InstMap {
276 const insts = try allocator.alloc(Air.Inst.Ref.Optional, inst_len);
277 errdefer allocator.free(insts);
278
279 const debug_traces = if (runtime_safety)
280 try allocator.alloc(StoredTrace, inst_len);
281 errdefer if (runtime_safety)
282 allocator.free(debug_traces);
283
284 @memset(insts[0..], .none);
83285 return .{
84286 .items = insts,
85287 .start = inst_start,
......@@ -89,435 +291,262 @@ const InstMap = struct {
89291
90292 pub fn deinit(map: *InstMap, allocator: Allocator) void {
91293 allocator.free(map.items);
92 if (std.debug.runtime_safety)
294 if (runtime_safety)
93295 allocator.free(map.debug_traces);
94296 map.* = undefined;
95297 }
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 {
98300 const source_i = @intFromEnum(teg_code);
99301 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 }
101310 return map.items[source_i - start_i].unwrap();
102311 }
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 {
105323 const source_i = @intFromEnum(from);
106324 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 }
107332 const index = source_i - start_i;
108 if (assertFailed(map.get(from) == null)) {
333 if (assertFailed(map.getOrNull(from) == null)) {
109334 const s: *const Sema = @fieldParentPtr("inst_map", map);
110335 std.debug.print("Original was added here:\n", .{});
111336 std.debug.dumpStackTrace(map.debug_traces[index].trace());
112337 std.debug.panic("%{d} is already mapped (to {})", .{
113338 @intFromEnum(from),
114 map.get(from).?.dump(s.ip),
339 map.get(from).dump(s.ip),
115340 });
116341 }
117342 map.items[index] = to.toOptional();
118 if (std.debug.runtime_safety)
343 if (runtime_safety)
119344 map.debug_traces[index] = StoredTrace.capture(@returnAddress());
120345 }
121346};
122347
123pub fn init(s: *Sema) !void {
124 const t = tracer.trace(@src());
125 defer t.end();
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);
348fn markInst(s: *Sema, inst: Air.Inst.Index) void {
349 const flags: *Air.Flags = &s.func.?.instructions.items(.flags)[@intFromEnum(inst)];
350 flags.uses = flags.uses.increment();
159351}
160352
161pub fn finalize(s: *Sema) !void {
162 s.decl.analysis = .complete;
353// TODO: name when awaake pls
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) {
167 if (s.comp.options.dump_ir) {
168 try s.comp.out.print("=" ** 20, .{});
169 try s.comp.out.print("\nAir for {}\n", .{s.decl.value().dump(s.ip)});
170 try air.dumpToWriter(s.ip, s.comp.out);
171 }
368 if (mark == .marked) {
369 switch (air.decode()) {
370 .index => |i| s.markInst(i),
371 else => {},
372 }
373 }
172374
173 try s.comp.codeGenDecl(s.decl.value(), &air);
375 return air;
376 },
174377 }
175378}
176379
177// --- analyze functions ---
178
179/// This is the entrypoint to semantic analysis: It all starts at a declaration.
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 },
380fn getValue(s: *Sema, src: LazyNode, teg_ref: Teg.Inst.Ref) !Value {
381 switch (teg_ref.decode()) {
382 .value => |value| return value,
193383 .index => |index| {
194 const result = try s.analyzeBlock(index);
195 switch (result.decode()) {
196 .value => |val| {
197 s.decl.raw_value = val;
198 s.decl.analysis = .complete;
199 },
200 .index => @panic("TODO"),
201 }
384 return s.inst_map.get(index).value() orelse
385 return s.fail(.not_comptime_known, .{ .src = src });
202386 },
203387 }
204
205 return s.decl.value();
206388}
207389
208/// TODO: Delete this legacy code. This will be replaced by analyzeDeclaration and a lambda
209/// function (See #45)
210pub fn analyzeFunctionDeclaration(s: *Sema) Error!Value {
211 const t = tracer.trace(@src());
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);
390fn getCoercedValue(s: *Sema, src: LazyNode, teg_ref: Teg.Inst.Ref, ty: Type) !Value {
391 const any = try s.getValue(src, teg_ref);
392 return try s.coerceValue(src, any, ty);
393}
221394
222 s.params = try s.gpa.alloc(Type, fn_decl.params.len);
223 defer s.gpa.free(s.params);
395fn getCoercedRef(s: *Sema, src: LazyNode, teg_ref: Teg.Inst.Ref, ty: Type, mark: DoMark) !Air.Inst.Ref {
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| {
226 const src = s.inst_map.start.op(operandArgument(@intCast(i)));
227 const ty = try s.resolveType(src, param);
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 }
400fn getType(s: *Sema, src: LazyNode, teg_ref: Teg.Inst.Ref) !Type {
401 return (try s.getCoercedValue(src, teg_ref, .type)).toTypeUnsafe(s.ip);
402}
236403
237 var return_type, const early_value = if (fn_decl.return_type.unwrap()) |ty| brk: {
238 const return_type = try s.resolveType(
239 s.inst_map.start.op(.return_type),
240 ty,
241 );
242 const value = try s.makeValue(.{
243 .func = .{
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,
404pub fn run(s: *Sema) Error!void {
405 while (true) if (try s.step()) |result| {
406 switch (s.owner.unwrap()) {
407 .decl => |decl_index| {
408 const decl = s.ip.declPtr(decl_index);
409 decl.raw_value = result;
410 decl.analysis = .complete;
252411 },
253 });
254
255 s.decl.raw_value = value.typeOf(s.ip).value();
256 s.decl.analysis = .in_progress_type_available;
257
258 break :brk .{ return_type, value };
259 } else .{ null, null };
260
261 const root_block = decl.body;
262 switch (root_block.decode()) {
263 .value => |val| {
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 };
412 .func => |value| {
413 const air = try s.func.?.finalize(s.gpa);
414 try s.comp.jobs.writeItem(.{ .codegen_func = .{
415 .value = value,
416 .air = air,
417 } });
418 },
419 }
420 return;
421 };
422}
274423
275 const block = try s.addBlock();
276 _ = try s.addInstRef(.@"break", .{
277 .block_ref = block.block_index,
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 }
424pub fn step(s: *Sema) Error!?Value {
425 s.instruction_step_lock.lock(); // Semantic analysis must not be recursive
426 defer s.instruction_step_lock.unlock();
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);
289 },
290 }
430 switch (s.frame.currentTag()) {
431 inline else => |tag| {
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(.{
293 .func = .{
294 .ty = try s.makeType(.{
295 .function_type = .{
296 .param_types = s.params,
297 .return_type = return_type orelse
298 unreachable, // type is reassigned above
438 s.inst_map.add(s.frame.ip[0], result);
439 s.frame.advance();
440 },
441 .void => {
442 s.frame.advance();
443 },
444 .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 },
299457 },
300 }),
301 .owner_decl = s.decl_index,
302 .teg_body_inst = s.decl.teg_decl_index,
458 }
303459 },
304 });
305
306 s.decl.analysis = .complete;
307 s.decl.raw_value = value;
460 }
308461
309 return value;
462 return null;
310463}
311464
312pub fn analyzeBlock(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {
313 const t = tracer.trace(@src());
314 defer t.end();
315
316 const scratch = s.scratch.mark();
317 defer scratch.deinit();
318
319 const block = try s.addBlock();
320 try s.inst_map.add(in, block.block_index.toRef());
321
322 var last: Air.Inst.Ref = Value.void.toAir();
323 const instructions = s.code.blockInstructions(in);
324 assert(instructions.len != 0);
325 for (instructions) |inst| {
326 last = s.analyzeCode(inst) catch |err| switch (err) {
327 error.HitNoReturn => break,
328 else => return err,
465const InstructionKind = enum {
466 result,
467 void,
468 control_flow,
469 pop_control_flow,
470
471 pub inline fn fromTag(tag: Teg.Tag) InstructionKind {
472 return switch (tag) {
473 // these push new frames, and frame end will assign its value
474 .func,
475 .block,
476 .@"comptime",
477 .@"if",
478 => .control_flow,
479 // these pop frames, which may end the compilation
480 .@"break",
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,
329488 };
330489 }
331490
332 if (scratch.len() == 1) {
333 const air_instructions = s.air_instructions.slice();
334 const single_inst_id = @intFromEnum(block.block_index) + 1;
335 assert(@intFromEnum(scratch.slice(Air.Inst.Index)[0]) == single_inst_id);
336
337 const single_instruction = air_instructions.items(.tag)[single_inst_id];
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);
491 pub fn Result(kind: InstructionKind) type {
492 return switch (kind) {
493 .result => Air.Inst.Ref,
494 .void, .control_flow => void,
495 .pop_control_flow => PopControlFlowState,
496 };
369497 }
498};
370499
371 return out;
372}
500const PopControlFlowState = enum { stop, @"continue" };
373501
374fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {
375 switch (s.code.instTag(in)) {
502fn stepInner(s: *Sema, comptime tag: Teg.Tag) Error!InstructionKind.fromTag(tag).Result() {
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 },
376516 .string => {
377 const string: Teg.SlicedString = s.code.instPayload(.string, in);
378 return s.makeValueAir(.{ .string = string.slice(s.code.string_bytes) });
517 const string: Teg.SlicedString = s.frame.currentPayload(.string);
518 return s.makeValueAir(.{ .string = string.slice(s.teg.string_bytes) });
379519 },
380520 .number => {
381 const number: store.Float = s.code.instPayload(.number, in);
521 const number: store.Float = s.frame.currentPayload(.number);
382522 return s.makeValueAir(.{ .comptime_float = number.decode() });
383523 },
384 .js => {
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,
524 .add,
501525 .sub,
502526 .mul,
503527 .div,
504528 .lt,
505529 .gt,
506530 .eq,
507 => |tag| {
508 const bin: Teg.Bin = s.code.instPayload(tag, in);
531 => {
532 const bin: Teg.Bin = s.frame.currentPayload(tag);
509533 const op = @field(BinaryOp, @tagName(tag));
510534
511 const lhs = try s.resolveRef(bin.lhs);
512 const rhs = try s.resolveRef(bin.rhs);
513
535 const lhs = s.getRef(bin.lhs, .marked);
514536 const left_ty = s.typeOfRef(lhs);
537
538 const rhs = s.getRef(bin.rhs, .marked);
515539 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);
520 const right = try s.coerceRef(in.op(.rhs), rhs, ty);
548 const left = try s.coerceRef(s.lazyNode(.lhs), lhs, ty);
549 const right = try s.coerceRef(s.lazyNode(.rhs), rhs, ty);
521550
522551 if (left.kind == .value and right.kind == .value) {
523552 return (try s.comptimeBin(
......@@ -527,37 +556,23 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {
527556 )).toAir();
528557 }
529558
530 return s.addInstRef(tag.airTag(), .{
559 try s.ensureAtRuntime(s.lazyNode(.main_node));
560
561 return s.addRef(tag.airTag(), .{
531562 .lhs = left,
532563 .rhs = right,
533564 });
534565 },
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 },
551566 .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
556571 const ty = s.typeOfRef(operand);
557572 switch (ty) {
558573 .numeric_literal, .int32, .float => {},
559574 else => return s.fail(.un_op_mismatch, .{
560 .src = in.op(.operand).lazyNode(s.decl_index),
575 .src = s.lazyNode(.operand),
561576 .ty = ty,
562577 }),
563578 }
......@@ -568,34 +583,128 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {
568583 });
569584 }
570585
571 return s.addInstRef(.negate, .{ .operand = operand });
586 return s.addRef(.negate, .{ .operand = operand });
572587 },
573 .@"break" => {
574 const brk: Teg.Break = s.code.instPayload(.@"break", in);
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
588 .bool_not => {
589 const un: Teg.RefNode = s.frame.currentData(.bool_not);
577590
578 const coerced = if (s.temp_result_map.get(brk.block)) |ty|
579 try s.coerceRef(in.op(.operand), operand, ty)
580 else
581 operand;
591 const operand = try s.getCoercedRef(s.lazyNode(.operand), un.operand, .bool, .marked);
592
593 if (operand.value()) |value| {
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", .{
584 .block_ref = block_ref,
585 .value = coerced,
608 const scratch = try s.scratch.reserveMulti(
609 s.arena(),
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),
586693 });
587694 },
588695 .call => {
589 const call: Teg.Call = s.code.instPayload(.call, in);
590 const base = try s.resolveRef(call.base);
696 const call: Teg.Call = s.frame.currentPayload(.call);
697 const base = s.getRef(call.base, .marked);
591698 const base_type = s.typeOfRef(base);
592699
593 const scratch = try s.arena.alloc(Air.Inst.Ref, call.args.len);
594 defer s.arena.free(scratch);
700 try s.ensureAtRuntime(s.lazyNode(.main_node)); // todo: comptime calls
701
702 const scratch = try s.arena().alloc(Air.Inst.Ref, call.args.len);
703 defer s.arena().free(scratch);
595704 if (base_type != .@"opaque") {
596705 const proto = base_type.toFunctionPrototype(s.ip) orelse {
597706 return s.fail(.call_non_function, .{
598 .call = s.nodeLoc(in, .operand),
707 .call = s.lazyNode(.operand),
599708 .ty = base_type,
600709 });
601710 };
......@@ -603,38 +712,38 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {
603712 if (proto.param_types.len != call.args.len) {
604713 // TODO: also attempt to coerce some of the params to reveal more errors.
605714 return s.fail(.call_mismatch_args, .{
606 .call = s.nodeLoc(in, .operand),
715 .call = s.lazyNode(.operand),
607716 .actual = @intCast(call.args.len),
608717 .expected = @intCast(proto.param_types.len),
609718 });
610719 }
611720
612721 // validate function args
613 for (proto.param_types, call.args.slice(&s.code), 0..) |param, arg, i| {
614 const ref = try s.resolveRef(arg);
722 for (proto.param_types, call.args.slice(&s.teg), 0..) |param, arg, i| {
723 const ref = s.getRef(arg, .marked);
615724 const coerced = try s.coerceRef(
616 in.op(operandArgument(@intCast(i))),
725 s.lazyNode(.argument(@intCast(i))),
617726 ref,
618727 param,
619728 );
620729 scratch[i] = coerced;
621730 }
622731 } else {
623 for (call.args.slice(&s.code), 0..) |arg, i| {
732 for (call.args.slice(&s.teg), 0..) |arg, i| {
624733 const ref = try s.ensureValueDefinedRepresentation(
625 in.op(operandArgument(@intCast(i))),
626 try s.resolveRef(arg),
734 s.lazyNode(.argument(@intCast(i))),
735 s.getRef(arg, .marked),
627736 );
628737 scratch[i] = ref;
629738 }
630739 }
631740
632 const start = s.air_extra.list.items.len;
633 try s.air_extra.list.appendSlice(s.gpa, @ptrCast(scratch));
741 const start = s.func.?.extra.list.items.len;
742 try s.func.?.extra.list.appendSlice(s.gpa, @ptrCast(scratch));
634743
635 return s.addInstRef(.call, .{
744 return s.addRef(.call, .{
636745 .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, .{
638747 .args = .{
639748 .start = @intCast(start),
640749 .len = @intCast(scratch.len),
......@@ -642,178 +751,389 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {
642751 }),
643752 });
644753 },
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
645847 .js_new => {
646 const call: Teg.JsNew = s.code.instPayload(.js_new, in);
647 const base = try s.resolveRef(call.base);
848 const call: Teg.JsNew = s.frame.currentPayload(.js_new);
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);
650 defer s.arena.free(scratch);
651 for (call.args.slice(&s.code), 0..) |arg, i| {
854 const scratch = try s.arena().alloc(Air.Inst.Ref, call.args.len);
855 defer s.arena().free(scratch);
856 for (call.args.slice(&s.teg), 0..) |arg, i| {
652857 const ref = try s.ensureValueDefinedRepresentation(
653 in.op(operandArgument(@intCast(i))),
654 try s.resolveRef(arg),
858 s.lazyNode(.argument(@intCast(i))),
859 s.getRef(arg, .marked),
655860 );
656861 scratch[i] = ref;
657862 }
658863
659 const start = s.air_extra.list.items.len;
660 try s.air_extra.list.appendSlice(s.gpa, @ptrCast(scratch));
864 const start = s.func.?.extra.list.items.len;
865 try s.func.?.extra.list.appendSlice(s.gpa, @ptrCast(scratch));
661866
662 return s.addInstRef(.js_new, .{
867 return s.addRef(.js_new, .{
663868 .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 = .{
665870 .start = @intCast(start),
666871 .len = @intCast(scratch.len),
667872 } }),
668873 });
669874 },
670 .ensure_void => {
671 const operand = try s.resolveRefExtra(s.code.instData(.ensure_void, in).operand, false);
672 const t = s.typeOfRef(operand);
673 _ = t;
674 // if (!t.isEnsureVoid(s.ip)) {
675 // std.debug.print("oh no, we are discarding {} on accident\n", .{
676 // t.fmt(s.ip),
677 // });
678 // }
679 return InternPool.Value.void.toAir();
875
876 // .assert => {
877 // return Value.void.toAir();
878 // },
879
880 // control flow
881 .block => unreachable, // frame should already be pushed
882 .@"comptime" => {
883 const index = s.frame.currentIndex();
884 try s.pushFrame();
885 s.frame = Frame.initFromBlock(s, index, .compile_time, .none);
680886 },
681 .discard => {
682 const operand = try s.resolveRefExtra(s.code.instData(.discard, in).operand, false);
683 const t = s.typeOfRef(operand);
684 if (t == .void) {
685 std.debug.print("oh no, we have a useless _ = void;\n", .{});
887
888 .func => {
889 const data: Teg.Func = s.frame.currentPayload(.func);
890 const reserve = try s.scratch.reserveMulti(
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;
686908 }
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 }) } });
688938 },
689 .@"if" => {
690 const data: Teg.If = s.code.instData(.@"if", in).extra_index.get(&s.code);
939 .@"break" => {
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);
693 const cond = try s.coerceRef(in.op(.operand), cond_raw, .bool);
1008 assert(s.frame.mode == .run_time);
6941009
695 // If known at comptime, only analyze the taken path
696 if (cond.kind == .value) {
697 @panic("TODO: comptime IF");
698 }
1010 const yes = s.getRef(if_data.yes, .no_mark);
1011 const no = s.getRef(if_data.no, .no_mark);
6991012
700 // Emit a runtime branch
701 const yes_block = try s.analyzeRefToBlock(data.yes);
702 const no_block = try s.analyzeRefToBlock(data.no);
1013 const union_type = s.unionType(s.typeOfRef(yes), s.typeOfRef(no));
7031014
704 return try s.addInstRef(.@"if", .{
705 .cond = cond,
706 .data = try s.air_extra.append(s.gpa, Air.If.Data, .{
707 .yes = yes_block,
708 .no = no_block,
709 .union_ty = s.unionType(s.typeOfRef(yes_block), s.typeOfRef(no_block)),
710 }),
711 });
712 },
713 .block => {
714 const block = try s.analyzeBlock(in);
715 switch (block.decode()) {
716 .index => |t| if (s.tempAir().instTag(t) == .block) {
717 try s.scratch.add(s.gpa, Air.Inst.Index, t);
1015 // const yes_coerced = try s.coerceValue(s.lazyNode(.lhs), yes, union_type);
1016 // const no_coerced = try s.coerceValue(s.lazyNode(.rhs), no, union_type);
1017
1018 s.inst_map.add(s.frame.currentIndex(), try s.addRef(.@"if", .{
1019 .cond = s.getRef(if_data.cond, .no_mark),
1020 .data = try s.func.?.extra.append(s.gpa, Air.If.Data, .{
1021 .yes = yes,
1022 .no = no,
1023 .union_ty = union_type,
1024 }),
1025 }));
1026 s.frame.advance();
7181027 },
719 else => {},
1028 else => |t| std.debug.panic("TODO: post-visit .{s}", .{@tagName(t)}),
7201029 }
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(
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 });
1031 return .@"continue";
7371032 },
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;
743 s.markInst(location);
744 const operand = try s.resolveRef(data.operand);
745 const operand_coerced = try s.coerceRef(
746 in.op(.rhs),
747 operand,
748 s.typeOfRef(location.toRef()),
1034 .load_decl => {
1035 const request: Teg.StrNode = s.frame.currentData(.load_decl);
1036 const decl_name = try s.ip.getOrPutString(
1037 s.gpa,
1038 request.string.slice(s.teg.string_bytes),
1039 .no_embedded_nulls,
7491040 );
750 return try s.addInstRef(.store, .{
751 .memory = location,
752 .operand = operand_coerced,
753 });
1041 const decl_index = s.lookupDecl(decl_name);
1042 return (try s.getDecl(decl_index)).toAir();
7541043 },
1044
7551045 .coercion => {
756 const as: Teg.Coercion = s.code.instPayload(.coercion, in);
757 const coerce_to = try s.resolveCoerceValue(in.op(.lhs), as.type, .type);
758 const operand = try s.resolveRef(as.operand);
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();
1046 const as: Teg.Coercion = s.frame.currentPayload(.coercion);
1047 const coerce_to = try s.getType(s.lazyNode(.operand), as.type);
1048 return try s.getCoercedRef(s.lazyNode(.operand), as.operand, coerce_to, .marked);
7701049 },
1050
7711051 .compile_error => {
772 const node: Teg.RefNode = s.code.instData(.compile_error, in);
773 const value = try s.resolveCoerceValue(
774 in.op(operandArgument(0)),
775 node.operand,
776 .string,
777 );
1052 const node: Teg.RefNode = s.frame.currentData(.compile_error);
1053 const value = try s.getCoercedValue(s.lazyNode(.operand), node.operand, .string);
7781054 const str = value.asString(s.ip).toSlice(s.ip);
7791055 return s.fail(.user_compile_error, .{
780 .src = s.lazyNode(in.op(.main_node)),
1056 .src = s.lazyNode(.main_node),
7811057 .msg = try s.report.addString(str),
7821058 });
7831059 },
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 keys
795 for (scratch.slice(.keys), node.keys(&s.code)) |*dest, src| {
796 dest.* = try s.ip.getOrPutString(s.gpa, src.slice(s.code.string_bytes), .no_embedded_nulls);
797 }
1061 .@"if" => {
1062 const data: Teg.If = s.frame.currentPayload(.@"if");
1063
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.
800 for (node.types(&s.code), 0..) |ty, i| {
801 scratch.at(.types, i).* = try s.resolveType(in.op(operandArgument(@intCast(i))), ty);
1109 // When both branches are values, Sema has nowhere
1110 // to jump to, so only one Air instruction is emitted
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 },
8021126 }
803
804 return try s.makeValueAir(.{ .struct_type = .{
805 .field_keys = scratch.slice(.keys),
806 .field_types = scratch.slice(.types),
807 } });
8081127 },
1128
8091129 .struct_init => {
810 const node: Teg.StructInit = s.code.instPayload(.struct_init, in);
1130 const node: Teg.StructInit = s.frame.currentPayload(.struct_init);
8111131
8121132 // 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);
8141134 const struct_type = ty.toStructType(s.ip) orelse {
8151135 return s.fail(.struct_init_invalid_type, .{
816 .src = in.op(.operand).lazyNode(s.decl_index),
1136 .src = s.lazyNode(.operand),
8171137 .ty = ty,
8181138 });
8191139 };
......@@ -823,26 +1143,26 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {
8231143
8241144 // Allocate scratch space to store the values as they are being prepared.
8251145 const scratch = try s.scratch.reserve(
826 s.arena,
1146 s.arena(),
8271147 Air.Inst.Ref.Optional,
8281148 struct_type.field_types.len,
8291149 );
8301150 defer scratch.deinit();
8311151 @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| {
8341154 // The order of `storage` is as ordered by the type, but this
8351155 // can be in a different ordering than the initialization. We
8361156 // guarantee the ordering the initializers are visited is in the
8371157 // order they are declared.
8381158 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))
8401160 break :index j;
8411161 } else {
8421162 has_errors = true;
8431163 try s.addError(.struct_init_unknown_field, .{
8441164 .ty = ty,
845 .src = in.op(operandArgument(@intCast(i))).lazyNode(s.decl_index),
1165 .src = s.lazyNode(.argument(@intCast(i))),
8461166 });
8471167 continue;
8481168 };
......@@ -850,14 +1170,15 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {
8501170 has_errors = true;
8511171 try s.addError(.struct_init_duplicate_field, .{
8521172 .ty = ty,
853 .src = in.op(operandArgument(@intCast(i))).lazyNode(s.decl_index),
1173 .src = s.lazyNode(.argument(@intCast(i))),
8541174 });
8551175 continue;
8561176 }
857 const coerced = s.resolveCoerceRef(
858 in.op(operandArgument(@intCast(i))),
1177 const coerced = s.getCoercedRef(
1178 s.lazyNode(.argument(@intCast(i))),
8591179 init_value,
8601180 struct_type.field_types[found_index],
1181 .marked,
8611182 ) catch |e| switch (e) {
8621183 error.AnalysisFail => {
8631184 // 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 {
8801201 try s.addError(.struct_init_missing_field, .{
8811202 .ty = ty,
8821203 .field = @intCast(index),
883 .src = in.op(.main_node).lazyNode(s.decl_index),
1204 .src = s.lazyNode(.main_node),
8841205 });
8851206 i = index + 1;
8861207 }
......@@ -901,82 +1222,27 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {
9011222 .field_values = result_storage,
9021223 } });
9031224 } else {
1225 try s.ensureAtRuntime(s.lazyNode(.main_node));
1226
9041227 // Create a runtime struct instantiation
905 const value_index = try s.air_extra.appendSlice(s.gpa, scratch.slice());
906 return try s.addInstRef(.struct_init, .{
1228 const value_index = try s.func.?.extra.appendSlice(s.gpa, scratch.slice());
1229 return try s.addRef(.struct_init, .{
9071230 .ty = ty,
9081231 .field_values = value_index,
9091232 });
9101233 }
9111234 },
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 },
9701236 .array_type => {
971 const un: Teg.RefNode = s.code.instData(.array_type, in);
972 const operand = try s.resolveType(in.op(.operand), un.operand);
1237 const un: Teg.RefNode = s.frame.currentData(.array_type);
1238 const operand = try s.getType(s.lazyNode(.operand), un.operand);
9731239 return s.makeValueAir(.{ .array_type = .{ .item_type = operand } });
9741240 },
9751241
9761242 .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);
9801246 const ty = s.typeOfRef(operand);
9811247
9821248 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 {
9891255 },
9901256
9911257 .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);
9951261 const ty = operand.toType(s.ip) orelse
9961262 return Value.opaque_type.toAir();
9971263
......@@ -1002,9 +1268,9 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {
10021268 },
10031269
10041270 .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);
10081274 const ty = operand.toType(s.ip) orelse
10091275 return Value.opaque_type.toAir();
10101276
......@@ -1014,16 +1280,16 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {
10141280 },
10151281
10161282 .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);
10201286 const ty = operand.toType(s.ip) orelse
10211287 return Value.opaque_type.toAir();
10221288
10231289 const struct_type = ty.toStructType(s.ip) orelse
10241290 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);
10271293 for (struct_type.field_keys, struct_type.field_types) |k, v| {
10281294 if (k.eqlSlice(to_match, s.ip)) {
10291295 return v.value().toAir();
......@@ -1033,148 +1299,193 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref {
10331299 return Value.opaque_type.toAir();
10341300 },
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)}),
10371303 }
10381304}
10391305
1040fn markInst(s: *Sema, inst: Air.Inst.Index) void {
1041 const flags: *Air.Flags = &s.air_instructions.items(.flags)[@intFromEnum(inst)];
1042 flags.uses = flags.uses.increment();
1306fn emitRuntimeBlock(
1307 s: *Sema,
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();
10431337}
10441338
1045fn analyzeRefToBlock(s: *Sema, in: Teg.Inst.Ref) Error!Air.Inst.Ref {
1046 const t = tracer.trace(@src());
1047 defer t.end();
1048
1049 const scratch = s.scratch.mark();
1050 defer scratch.deinit();
1051
1052 const visited = s.resolveRef(in) catch |err| switch (err) {
1053 error.HitNoReturn => Value.void.toAir(),
1054 else => return err,
1055 };
1339const BinaryOp = enum {
1340 add,
1341 sub,
1342 mul,
1343 div,
1344 lt,
1345 gt,
1346 eq,
1347};
10561348
1057 if (scratch.len() == 0 and visited.kind == .value) {
1058 return visited;
1349fn binTypeResolution(s: *Sema, src: Teg.Inst.Index, op: BinaryOp, lhs: Type, rhs: Type) !Type {
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;
10591355 }
10601356
1061 if (visited.kind == .index and
1062 s.air_instructions.items(.tag)[@intFromEnum(visited.decode().index)] == .block)
1063 {
1064 return visited;
1357 switch (lhs) {
1358 .string => if (op == .add) switch (rhs) {
1359 .string => return .string,
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 => {},
10651381 }
10661382
1067 const block = try s.addBlock();
1068 _ = try s.addInstRef(.@"break", .{
1069 .block_ref = block.block_index,
1070 .value = visited,
1383 return s.fail(.bin_op_mismatch, .{
1384 .src = src.op(.main_node).lazyNode(s.techDebtDeclIndex()),
1385 .lhs = lhs,
1386 .rhs = rhs,
10711387 });
1072 return (try block.finish(s, scratch)).toRef();
10731388}
10741389
1075fn resolveRefExtra(s: *Sema, ref: Teg.Inst.Ref, mark: bool) Error!Air.Inst.Ref {
1076 return switch (ref.decode()) {
1077 .value => |value| value.toAir(),
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 }
1390fn comptimeBin(s: *Sema, op: BinaryOp, lhs: Value, rhs: Value) !Value {
1391 const left_ty = lhs.typeOf(s.ip);
1392 assert(rhs.typeOf(s.ip) == left_ty);
10891393
1090 return s.analyzeCode(index);
1091 },
1092 };
1093}
1394 switch (op) {
1395 .add => switch (left_ty) {
1396 .numeric_literal, .float => {
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 {
1096 return resolveRefExtra(s, ref, true);
1097}
1402 const result = l + r;
10981403
1099fn resolveValue(s: *Sema, ref: Teg.Inst.Ref) !Value {
1100 const index = switch (ref.decode()) {
1101 .value => |value| return value,
1102 .index => |index| index,
1103 };
1404 return try s.makeValue(if (left_ty == .numeric_literal)
1405 .{ .comptime_float = result }
1406 else
1407 .{ .float = result });
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| {
1106 switch (air.decode()) {
1107 .value => |value| return value,
1108 else => {},
1109 }
1110 }
1415 const result = std.math.add(i32, l, r) catch
1416 @panic("TODO: out of range");
11111417
1112 switch (s.code.instTag(index)) {
1113 .string => {
1114 const string: Teg.SlicedString = s.code.instPayload(.string, index);
1115 return s.makeValue(.{ .string = string.slice(s.code.string_bytes) });
1418 return try s.makeValue(.{ .int32 = result });
1419 },
1420 .string => {
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,
11161430 },
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 => {
1119 // Currently only one pattern is supported:
1120 //
1121 // %2 = block [
1122 // %1 = load_decl X
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;
1438 const result = switch (tag) {
1439 .sub => l - r,
1440 .mul => l * r,
1441 .div => l / r,
1442 else => @compileError(unreachable),
11511443 };
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) }),
1159 }
1160}
1445 return try s.makeValue(if (left_ty == .numeric_literal)
1446 .{ .comptime_float = result }
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 {
1163 const any = try s.resolveValue(ref);
1164 return try s.coerceValue(src, any, ty);
1165}
1456 const result = switch (tag) {
1457 .sub => std.math.sub(i32, l, r),
1458 .mul => std.math.mul(i32, l, r),
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 {
1168 const any = try s.resolveRef(ref);
1169 return try s.coerceRef(src, any, ty);
1170}
1463 return try s.makeValue(.{ .int32 = result });
1464 },
1465 else => unreachable,
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 {
1173 return (try s.resolveCoerceValue(src, ref, .type)).toTypeUnsafe(s.ip);
1473 const result = switch (tag) {
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 }
11741485}
11751486
11761487/// 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 {
11781489 const t = tracer.traceFmt(@src(), "{} to {}", .{ ref.dump(s.ip), to.dump(s.ip) });
11791490 defer t.end();
11801491
......@@ -1182,9 +1493,6 @@ fn coerceRef(s: *Sema, src: InstOp, ref: Air.Inst.Ref, to: Type) !Air.Inst.Ref {
11821493 if (from == to) {
11831494 return ref;
11841495 }
1185 if (from == .noreturn) {
1186 return error.HitNoReturn;
1187 }
11881496 if (from == .@"opaque") {
11891497 return ref;
11901498 }
......@@ -1192,6 +1500,9 @@ fn coerceRef(s: *Sema, src: InstOp, ref: Air.Inst.Ref, to: Type) !Air.Inst.Ref {
11921500 .value => |v| return (try s.coerceValue(src, v, to)).toAir(),
11931501 .index => |index| index,
11941502 };
1503 if (s.frame.mode == .compile_time) {
1504 return s.fail(.not_comptime_known, .{ .src = src });
1505 }
11951506 switch (to) {
11961507 .@"opaque" => {
11971508 //
......@@ -1212,7 +1523,7 @@ fn coerceRef(s: *Sema, src: InstOp, ref: Air.Inst.Ref, to: Type) !Air.Inst.Ref {
12121523 .string => {
12131524 //
12141525 },
1215 .noreturn => {
1526 .never => {
12161527 //
12171528 },
12181529 .type => {
......@@ -1230,7 +1541,7 @@ fn coerceRef(s: *Sema, src: InstOp, ref: Air.Inst.Ref, to: Type) !Air.Inst.Ref {
12301541}
12311542
12321543/// 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 {
12341545 const from = value.typeOf(s.ip);
12351546 if (from == to) {
12361547 return value;
......@@ -1249,7 +1560,7 @@ fn coerceValue(s: *Sema, src: InstOp, value: Value, to: Type) Error!Value {
12491560 if (!core.inInt32Range(val)) {
12501561 return s.fail(.comptime_float_to_int, .{
12511562 .val = val,
1252 .src = s.lazyNode(src),
1563 .src = src,
12531564 });
12541565 }
12551566
......@@ -1272,8 +1583,8 @@ fn coerceValue(s: *Sema, src: InstOp, value: Value, to: Type) Error!Value {
12721583 .string => {
12731584 // TODO:
12741585 },
1275 .noreturn => {
1276 // nothing coerces to `noreturn`
1586 .never => {
1587 // nothing coerces to `never`
12771588 },
12781589 .numeric_literal => {
12791590 if (value.toFloat(s.ip)) |float| {
......@@ -1299,7 +1610,7 @@ fn coerceValue(s: *Sema, src: InstOp, value: Value, to: Type) Error!Value {
12991610 return s.coercionError(src, from, to);
13001611}
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 {
13031614 switch (ref.decode()) {
13041615 .value => |v| {
13051616 const ty = v.typeOf(s.ip);
......@@ -1307,9 +1618,9 @@ fn ensureValueRuntime(s: *Sema, src: InstOp, ref: Air.Inst.Ref) Error!Air.Inst.R
13071618 .numeric_literal => {
13081619 const float = v.toFloat(s.ip) orelse unreachable;
13091620
1310 if (core.inInt32Range(float)) {
1621 if (!core.inInt32Range(float)) {
13111622 try s.report.warn(.numeric_int_var_ambiguity, .{
1312 .src = s.lazyNode(src),
1623 .src = src,
13131624 });
13141625 }
13151626
......@@ -1319,22 +1630,22 @@ fn ensureValueRuntime(s: *Sema, src: InstOp, ref: Air.Inst.Ref) Error!Air.Inst.R
13191630 },
13201631 .numeric_literal_array => {
13211632 try s.report.warn(.numeric_int_array_var_ambiguity, .{
1322 .src = s.lazyNode(src),
1633 .src = src,
13231634 });
13241635 return (try s.coerceArrayValue(src, v, .float)).toAir();
13251636 },
1326 .noreturn_array => {
1637 .never_array => {
13271638 if (v.valueData(s.ip, .array_value).get(s.ip).items_len != 0) {
13281639 return s.fail(.runtime_incompatible_type, .{
1329 .src = s.lazyNode(src),
1640 .src = src,
13301641 .type = ty,
13311642 });
13321643 }
13331644 },
13341645 .type,
1335 .noreturn,
1646 .never,
13361647 => return s.fail(.runtime_incompatible_type, .{
1337 .src = s.lazyNode(src),
1648 .src = src,
13381649 .type = ty,
13391650 }),
13401651 .@"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
13471658 => {},
13481659 _ => if (!ty.hasPossibleRepresentation(s.ip)) {
13491660 return s.fail(.runtime_incompatible_type, .{
1350 .src = s.lazyNode(src),
1661 .src = src,
13511662 .type = ty,
13521663 });
13531664 },
......@@ -1357,7 +1668,7 @@ fn ensureValueRuntime(s: *Sema, src: InstOp, ref: Air.Inst.Ref) Error!Air.Inst.R
13571668 const ty = s.typeOfRef(ref);
13581669 if (!ty.hasPossibleRepresentation(s.ip)) {
13591670 return s.fail(.runtime_incompatible_type, .{
1360 .src = s.lazyNode(src),
1671 .src = src,
13611672 .type = ty,
13621673 });
13631674 }
......@@ -1366,89 +1677,27 @@ fn ensureValueRuntime(s: *Sema, src: InstOp, ref: Air.Inst.Ref) Error!Air.Inst.R
13661677 return ref;
13671678}
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 {
13701681 const runtime = try s.ensureValueRuntime(src, ref);
13711682 const ty = s.typeOfRef(runtime);
13721683 if (!ty.hasDefinedRepresentation(s.ip)) {
13731684 return s.fail(.type_no_defined_representation, .{
1374 .src = s.lazyNode(src),
1685 .src = src,
13751686 .type = ty,
13761687 });
13771688 }
13781689 return runtime;
13791690}
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
14431692fn unionType(s: *Sema, a: Type, b: Type) Type {
14441693 if (a == b) {
14451694 return a;
14461695 }
14471696
1448 if (a == .noreturn)
1697 if (a == .never)
14491698 return b;
14501699
1451 if (b == .noreturn)
1700 if (b == .never)
14521701 return a;
14531702
14541703 std.debug.panic(
......@@ -1461,198 +1710,208 @@ fn unionType(s: *Sema, a: Type, b: Type) Type {
14611710 // return s.ip.get(s.gpa, .{ .@"union" = &.{ a, b } });
14621711}
14631712
1464const BinaryOp = enum {
1465 add,
1466 sub,
1467 mul,
1468 div,
1469 lt,
1470 gt,
1471 eq,
1472};
1713fn coerceArrayValue(s: *Sema, src: LazyNode, value: Value, item_type: Type) !Value {
1714 const array: InternPool.Tag.ArrayValue, var trail = value.valueData(s.ip, .array_value).getTrail(s.ip);
14731715
1474fn binTypeResolution(s: *Sema, src: Teg.Inst.Index, op: BinaryOp, lhs: Type, rhs: Type) !Type {
1475 if (lhs == .@"opaque") {
1476 return if (rhs == .numeric_literal) .float else rhs;
1716 const scratch = try s.scratch.reserve(s.arena(), Value, array.items_len);
1717 defer scratch.deinit();
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);
14771723 }
1478 if (rhs == .@"opaque") {
1479 return if (lhs == .numeric_literal) .float else lhs;
1724
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
14801740 }
1741}
14811742
1482 switch (lhs) {
1483 .string => if (op == .add) switch (rhs) {
1484 .string => return .string,
1485 else => {},
1486 },
1487 .numeric_literal => switch (rhs) {
1488 .int32,
1489 .float,
1490 .numeric_literal,
1491 => return rhs,
1492 else => {},
1743fn getDecl(sema: *Sema, decl_index: Decl.Index) !Value {
1744 if (sema.owner.kind == .decl) {
1745 if (decl_index == sema.owner.unwrap().decl) {
1746 return sema.ip.declPtr(decl_index).value();
1747 }
1748 }
1749 const decl_data = sema.ip.declPtr(decl_index);
1750 try sema.declareDependency(.{ .decl_val = decl_index });
1751 const au: AnalysisUnit = .wrap(.{ .decl = decl_index });
1752 switch (decl_data.analysis) {
1753 .unreferenced => {
1754 try sema.comp.jobs.writeItem(.{ .analysis = .{ .au = au, .first_blocked_sema = sema } });
1755 return error.Interrupt;
14931756 },
1494 .int32 => switch (rhs) {
1495 .int32 => return .int32,
1496 .numeric_literal => return .int32,
1497 else => {},
1757 .waiting, .in_progress => {
1758 const other_sema = sema.comp.ongoing_analysis.get(au) orelse unreachable;
1759 try other_sema.blocked_semas.append(sema.gpa, sema);
1760 return error.Interrupt;
14981761 },
1499 .float => switch (rhs) {
1500 .int32 => return .float,
1501 .numeric_literal => return .float,
1502 .float => return .float,
1503 else => {},
1762 .complete => {
1763 return decl_data.value();
15041764 },
1505 else => {},
15061765 }
1766}
15071767
1508 return s.fail(.bin_op_mismatch, .{
1509 .src = s.nodeLoc(src, .main_node),
1510 .lhs = lhs,
1511 .rhs = rhs,
1512 });
1768fn declareDependency(sema: *Sema, dependee: InternPool.Dependee) !void {
1769 try sema.ip.addDependency(sema.gpa, sema.owner, dependee);
15131770}
15141771
1515fn comptimeBin(s: *Sema, op: BinaryOp, lhs: Value, rhs: Value) !Value {
1516 const left_ty = lhs.typeOf(s.ip);
1517 assert(rhs.typeOf(s.ip) == left_ty);
1772fn fieldAccessStruct(
1773 s: *Sema,
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) {
1520 .add => switch (left_ty) {
1521 .numeric_literal, .float => {
1522 const l = lhs.toFloat(s.ip) orelse
1523 unreachable; // must be coerced
1524 const r = rhs.toFloat(s.ip) orelse
1525 unreachable; // must be coerced
1791 break :found_index for (struct_type.field_keys, 0..) |key, i| {
1792 if (key == lookup)
1793 break @as(u32, @intCast(i));
1794 } else null;
1795 } orelse {
1796 return s.fail(.field_lookup_failed, .{
1797 .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)
1530 .{ .comptime_float = result }
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
1818fn fieldAccessArray(s: *Sema, lhs: Air.Inst.Ref, lhs_type: Type, rhs_teg: Teg.Inst.Ref) !Air.Inst.Ref {
1819 const rhs = s.getRef(rhs_teg, .marked);
15391820
1540 const result = std.math.add(i32, l, r) catch
1541 @panic("TODO: out of range");
1821 switch (s.typeOfRef(rhs)) {
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 });
1544 },
1545 .string => {
1546 const l = lhs.asString(s.ip).toSlice(s.ip);
1547 const r = rhs.asString(s.ip).toSlice(s.ip);
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,
1841 return s.fail(.field_lookup_failed, .{
1842 .ty = lhs_type,
1843 .src = s.lazyNode(.lhs),
1844 .field = value,
1845 });
15551846 },
1556 inline .sub, .mul, .div => |tag| switch (left_ty) {
1557 .numeric_literal, .float => {
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
1847 else => {
1848 const index = try s.getCoercedRef(s.lazyNode(.rhs), rhs_teg, .int32, .marked);
15801849
1581 const result = switch (tag) {
1582 .sub => std.math.sub(i32, l, r),
1583 .mul => std.math.mul(i32, l, r),
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");
1850 if (lhs.kind == .value and index.kind == .value) {
1851 // TODO:
1852 }
15871853
1588 return try s.makeValue(.{ .int32 = result });
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
1854 try s.ensureAtRuntime(s.lazyNode(.main_node));
15971855
1598 const result = switch (tag) {
1599 .lt => l < r,
1600 .gt => l > r,
1601 else => @compileError(unreachable),
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 );
1856 return s.addRef(.field_array, .{
1857 .lhs = lhs,
1858 .rhs = index,
1859 });
16141860 },
16151861 }
16161862}
16171863
1618const IncompleteBlock = struct {
1619 block_index: Air.Inst.Index,
1864//
16201865
1621 pub fn finish(block: IncompleteBlock, s: *Sema, scratch_mark: core.U32Stack.Mark) !Air.Inst.Index {
1622 s.setInstData(.block, block.block_index, try s.scratchMarkToIndexList(scratch_mark));
1623 return block.block_index;
1624 }
1625};
1866fn tempAir(s: *const Sema) Air {
1867 return .{
1868 .insts = s.func.?.instructions.slice(),
1869 .extra = s.func.?.extra.constant(),
1870 };
1871}
16261872
1627fn addBlock(s: *Sema) !IncompleteBlock {
1628 try s.air_instructions.append(s.gpa, .{
1629 .tag = .block,
1630 .data = undefined,
1631 .flags = .{},
1632 });
1633 return .{ .block_index = @enumFromInt(s.air_instructions.len - 1) };
1873fn typeOfRef(s: *Sema, ref: Air.Inst.Ref) Type {
1874 return switch (ref.decode()) {
1875 .value => |v| v.typeOf(s.ip),
1876 .index => |i| s.tempAir().typeOfIndex(i, s.ip),
1877 };
16341878}
16351879
1636fn addInstRef(s: *Sema, comptime tag: Air.Inst.Tag, data: tag.Data()) !Air.Inst.Ref {
1637 try s.air_instructions.append(s.gpa, .{
1880fn pushFrame(s: *Sema) !void {
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, .{
16381892 .tag = tag,
1639 .data = Air.EncodedData.encode(tag, data),
1893 .data = .encode(tag, data),
16401894 .flags = .{},
16411895 });
1642 const index: Air.Inst.Index = @enumFromInt(s.air_instructions.len - 1);
1643 try s.scratch.add(s.arena, Air.Inst.Index, index);
1644 return index.toRef();
1896 return @enumFromInt(s.func.?.instructions.len - 1);
16451897}
16461898
1647fn setInstData(
1648 s: *Sema,
1649 comptime tag: Air.Inst.Tag,
1650 index: Air.Inst.Index,
1651 data: tag.Data(),
1652) void {
1653 const i = @intFromEnum(index);
1654 assert(s.air_instructions.items(.tag)[i] == tag);
1655 s.air_instructions.items(.data)[i] = Air.EncodedData.encode(tag, data);
1899fn addIndex(s: *Sema, comptime tag: Air.Inst.Tag, data: tag.Data()) !Air.Inst.Index {
1900 const index = try s.addIndexUntracked(tag, data);
1901 try s.scratch.add(s.arena(), Air.Inst.Index, index);
1902 return index;
1903}
1904
1905fn addRef(s: *Sema, comptime tag: Air.Inst.Tag, data: tag.Data()) !Air.Inst.Ref {
1906 return (try s.addIndex(tag, data)).toRef();
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);
16561915}
16571916
16581917fn makeValue(s: *Sema, key: InternPool.Key) !Value {
......@@ -1667,91 +1926,85 @@ fn makeValueAir(s: *Sema, key: InternPool.Key) !Air.Inst.Ref {
16671926 return (try s.makeValue(key)).toAir();
16681927}
16691928
1670fn mapTeg(s: *Sema, inst: Teg.Inst.Ref) Air.Inst.Ref {
1671 return switch (inst.decode()) {
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 };
1929fn lazyNode(s: *const Sema, op: Report.Operand) LazyNode {
1930 return s.frame.currentIndex().op(op).lazyNode(s.techDebtDeclIndex());
16801931}
16811932
1682fn scratchMarkToIndexList(s: *Sema, mark: core.U32Stack.Mark) !Air.IndexList {
1683 const items = mark.slice(u32);
1684 const extra_top = s.air_extra.list.items.len;
1685 try s.air_extra.list.appendSlice(s.gpa, items);
1686 return .{
1687 .start = @intCast(extra_top),
1688 .len = @intCast(items.len),
1933/// Using this function is incorrect because as of "Sema v5" rewrite,
1934/// it is incorrect to use a Decl.Index as the source of truth.
1935fn techDebtDeclIndex(s: *const Sema) Decl.Index {
1936 return switch (s.owner.unwrap()) {
1937 .decl => |decl| decl,
1938 .func => |func| s.ip.valueToKey(func).func.owner_decl,
16891939 };
16901940}
16911941
1692fn tempAir(s: *const Sema) Air {
1693 return .{
1694 .insts = s.air_instructions.slice(),
1695 .extra = s.air_extra.constant(),
1696 };
1942fn addWarn(s: *Sema, comptime kind: Report.Err.FieldEnum, data: core.FieldType(Report.Err, kind)) !void {
1943 try s.report.warn(kind, data);
16971944}
16981945
1699fn typeOfRef(s: *Sema, ref: Air.Inst.Ref) InternPool.Type {
1700 return s.tempAir().typeOf(ref, s.ip);
1946fn addError(s: *Sema, comptime kind: Report.Err.FieldEnum, data: core.FieldType(Report.Err, kind)) !void {
1947 try s.report.err(kind, data);
17011948}
17021949
1703fn lookupDecl(s: *Sema, name: NullTerminatedString) ?InternPool.DeclIndex {
1704 var nsi = s.namespace_index;
1705 while (true) {
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 }
1950fn fail(s: *Sema, comptime kind: Report.Err.FieldEnum, data: core.FieldType(Report.Err, kind)) Error {
1951 try s.report.err(kind, data);
1952 return Error.AnalysisFail;
17131953}
17141954
1715fn nodeLoc(s: *const Sema, in: Teg.Inst.Index, op: Report.Operand) Report.LazyNode {
1716 return s.lazyNode(in.op(op));
1955fn coercionError(s: *Sema, src: LazyNode, from: Type, to: Type) Error {
1956 return s.fail(.coercion, .{
1957 .from = from,
1958 .to = to,
1959 .src = src,
1960 });
17171961}
17181962
1719fn addWarn(s: *Sema, comptime kind: Report.Err.FieldEnum, data: std.meta.FieldType(Report.Err, kind)) !void {
1720 try s.report.warnAt(kind, data, @returnAddress());
1963fn assertHasFunction(s: *const Sema) void {
1964 assert(s.func_stack_len > 0);
17211965}
17221966
1723fn addError(s: *Sema, comptime kind: Report.Err.FieldEnum, data: std.meta.FieldType(Report.Err, kind)) !void {
1724 try s.report.errAt(kind, data, @returnAddress());
1967/// TODO: This function is designed incorrectly. it should be
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 }
17251974}
17261975
1727fn fail(s: *Sema, comptime kind: Report.Err.FieldEnum, data: std.meta.FieldType(Report.Err, kind)) Error {
1728 try s.report.errAt(kind, data, @returnAddress());
1729 return Error.AnalysisFail;
1976fn arena(s: *Sema) Allocator {
1977 return s.arena_state.allocator();
17301978}
17311979
1732fn coercionError(s: *Sema, src: InstOp, from: Type, to: Type) Error {
1733 return s.fail(.coercion, .{
1734 .from = from,
1735 .to = to,
1736 .src = s.lazyNode(src),
1737 });
1980fn debug(s: *Sema, comptime fmt: []const u8, args: anytype) void {
1981 if (s.comp.options.verbose_sema)
1982 s.comp.out.print(
1983 "sema {}: " ++ fmt ++ "\n",
1984 .{s.owner.dump(s.ip)} ++ args,
1985 ) catch {};
17381986}
17391987
1740fn lazyNode(s: *const Sema, src: InstOp) Report.LazyNode {
1741 return src.lazyNode(s.decl_index);
1742}
1988/// Frame uses @fieldParentPtr to do some pointer arithmetic. To ensure every
1989/// pointer to `Sema` is valid, it has a field set to a specific 128-bit number.
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
17441996const Sema = @This();
17451997
17461998const std = @import("std");
17471999const mem = std.mem;
17482000const math = std.math;
1749const Allocator = std.mem.Allocator;
17502001const assert = std.debug.assert;
1751const log = std.log.scoped(.sema);
2002const Allocator = std.mem.Allocator;
17522003const DynamicBitSetUnmanaged = std.DynamicBitSetUnmanaged;
2004const DynamicArray = std.ArrayListUnmanaged;
17532005
17542006const core = @import("mod.zig");
2007const runtime_safety = core.runtime_safety;
17552008const assertFailed = core.assertFailed;
17562009const StoredTrace = core.StoredTrace;
17572010
......@@ -1761,18 +2014,18 @@ const Decl = Comp.Decl;
17612014const Namespace = Comp.Namespace;
17622015
17632016const Teg = core.Teg;
1764const InstOp = Teg.InstOp;
17652017
17662018const Air = core.Air;
17672019const Ast = core.Ast;
17682020const store = core.array;
17692021
17702022const Report = core.Report;
1771const operandArgument = Report.Operand.argument;
2023const LazyNode = Report.LazyNode;
17722024
17732025const InternPool = core.InternPool;
17742026const Value = InternPool.Value;
17752027const Type = InternPool.Type;
2028const AnalysisUnit = InternPool.AnalysisUnit;
17762029const NullTerminatedString = InternPool.NullTerminatedString;
17772030
17782031const tracer = @import("tracer");
src/core/StoredTrace.zig+2
......@@ -41,6 +41,8 @@ pub fn from(stack_trace: ?*std.builtin.StackTrace) StoredTrace {
4141}
4242
4343pub const BlankImpl = struct {
44 pub const empty: BlankImpl = .{};
45
4446 pub fn trace(_: *BlankImpl) std.builtin.StackTrace {
4547 @compileError("trace is not available with StoredTrace.BlankImpl");
4648 }
src/core/Teg.Dump.zig+100-28
......@@ -46,39 +46,57 @@ const State = struct {
4646 .module => {
4747 try s.dumpInstList(writer, teg.moduleDeclarations(inst), '{', '}');
4848 },
49 .declaration => {
49 .declaration, .variable_declaration, .test_declaration => {
5050 const decl = teg.fullDecl(inst);
51 try writer.print(" ({s}) ", .{@tagName(decl.data)});
5251
52 try writer.writeAll(" ");
5353 const name = decl.name.slice(teg.string_bytes);
5454 if (name.len == 0) {
5555 try tty.setColor(writer, .bold);
56 try writer.writeAll("<top level code>");
56 try writer.writeAll("[no name]");
5757 } else {
5858 try tty.setColor(writer, .magenta);
5959 try writer.writeAll(name);
6060 }
6161 try tty.setColor(writer, .reset);
62
62 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);
6380 try writer.writeAll(" {");
6481 s.indent += 1;
6582 try s.newline(writer);
66
67 switch (decl.data) {
68 .function => |fn_data| {
69 try writer.writeAll("params: ");
70 try s.dumpRefList(writer, fn_data.params.slice(teg), '[', ']');
71 try s.newline(writer);
72 if (fn_data.return_type.unwrap()) |return_type| {
73 try writer.writeAll("return_type: ");
74 try s.dumpRef(writer, return_type);
75 try s.newline(writer);
76 }
77 },
78 .immutable => {},
83 try writer.writeAll("params: ");
84 try s.dumpOptionalRefList(writer, func.params.slice(teg), '[', ']');
85 try s.newline(writer);
86 try writer.writeAll("return_type: ");
87 if (func.return_type.unwrap()) |ret|
88 try s.dumpRef(writer, ret)
89 else {
90 try tty.setColor(writer, .bright_black);
91 try writer.writeAll("<inferred>");
92 try tty.setColor(writer, .reset);
7993 }
94 try s.newline(writer);
95 try writer.writeAll("captures: ");
96 try s.dumpInstList2(writer, func.captures.slice(teg), '[', ']');
97 try s.newline(writer);
8098 try writer.writeAll("body: ");
81 try s.dumpRef(writer, decl.body);
99 try s.dumpInst(writer, func.body);
82100 s.indent -= 1;
83101 try s.newline(writer);
84102 try writer.writeAll("}");
......@@ -91,8 +109,8 @@ const State = struct {
91109 try tty.setColor(writer, .reset);
92110 try writer.writeAll(")");
93111 },
94 .block => {
95 const block = teg.instData(.block, inst);
112 inline .block, .@"comptime" => |t| {
113 const block = teg.instData(t, inst);
96114 const index_list = block.extra_index.get(teg);
97115 try s.dumpInstList(writer, index_list.slice(teg), '[', ']');
98116 },
......@@ -156,6 +174,26 @@ const State = struct {
156174 try s.dumpRefMarker(writer, data.operand);
157175 try writer.writeAll(")");
158176 },
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 },
159197 .string => {
160198 const data: Teg.SlicedString = teg.instData(.string, inst).extra_index.get(teg);
161199 try tty.setColor(writer, .green);
......@@ -170,12 +208,13 @@ const State = struct {
170208 .negate,
171209 .bool_not,
172210 .bit_not,
173 .js,
211 .@"extern",
174212 .import,
175213 .fetch,
176214 .compile_error,
177215 .type_of,
178216 .array_type,
217 .assert,
179218 => |comptime_tag| {
180219 const data = teg.instData(comptime_tag, inst);
181220 try writer.writeAll("(");
......@@ -221,12 +260,6 @@ const State = struct {
221260 try s.newline(writer);
222261 try writer.writeAll("}");
223262 },
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 },
230263 .struct_decl => {
231264 const data: Teg.StructDecl = teg.instPayload(.struct_decl, inst);
232265 try writer.writeAll(" {");
......@@ -274,7 +307,7 @@ const State = struct {
274307
275308 for (data.list.slice(teg)) |item| {
276309 try s.newline(writer);
277 try s.dumpRef(writer, item);
310 try s.dumpRefMarker(writer, item);
278311 try writer.writeAll(",");
279312 }
280313
......@@ -362,6 +395,22 @@ const State = struct {
362395 try writer.print("{c}", .{r});
363396 }
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
365414 fn dumpRefList(state: *State, writer: anytype, list: []const Teg.Inst.Ref, l: u8, r: u8) !void {
366415 try writer.print(" {c}", .{l});
367416 if (list.len > 0) {
......@@ -379,6 +428,29 @@ const State = struct {
379428 try writer.print("{c}", .{r});
380429 }
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
382454 fn dumpRefMarkerList(state: *State, writer: anytype, list: []const Teg.Inst.Ref, l: u8, r: u8) !void {
383455 try writer.print(" {c}", .{l});
384456 if (list.len > 0) {
src/core/Teg.zig+381-68
......@@ -10,12 +10,16 @@ pub const EncodedData = dod.EncodedData(Tag, u64);
1010pub const ExtraIndex = ExtraStore.Index;
1111pub const Dump = @import("Teg.Dump.zig");
1212
13pub const Tag = enum {
13pub const Tag = enum(u8) {
1414 /// Either the top level module or a `module` keyword. Contains a list of `.declarations`
1515 /// Every analyzed module gets a `Comp.Namespace`
1616 module,
1717 /// Smallest unit of re-compilation. Every analyzed declaration gets a `Comp.Decl`
1818 declaration,
19 /// Top-level memory location
20 variable_declaration,
21 /// Test declaration
22 test_declaration,
1923 /// Retrieves a declaration value via it's name. Since declarations are
2024 /// analyzed in isolation of each other, all references to instructions
2125 /// outside of the current declaration must be in the form of this.
......@@ -30,6 +34,10 @@ pub const Tag = enum {
3034 memory,
3135 /// Re-assign a memory cell with a new ref.
3236 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,
3341 /// Performs a type coercion, such as in variable declarations.
3442 /// The resulting Air Ref will be guaranteed to be the coerced type.
3543 coercion,
......@@ -39,12 +47,14 @@ pub const Tag = enum {
3947 discard,
4048 /// Asserts the operand is `void`. Used for undiscarded expressions.
4149 ensure_void,
42 /// Numeric constant.
43 number,
44 /// String literal constant.
45 string,
46 /// Branch takes a boolean condition, and yes and no paths.
50
51 /// Branch takes a boolean condition and takes either a yes or no path.
4752 @"if",
53 /// Given a block instruction
54 @"comptime",
55
56 /// A function expression, with or without captures
57 func,
4858
4959 // The following instructions are used to implement `Transform.ResultType`
5060 result_type_argument,
......@@ -52,10 +62,17 @@ pub const Tag = enum {
5262 result_type_tuple_field,
5363 result_type_array_value,
5464
55 struct_decl,
56 struct_init,
65 /// Numeric constant.
66 number,
67 /// String literal constant.
68 string,
5769
70 /// Constructs a unique struct type.
71 struct_decl,
72 /// Constructs an array type
5873 array_type,
74
75 struct_init,
5976 array_init,
6077
6178 add,
......@@ -81,13 +98,14 @@ pub const Tag = enum {
8198 negate,
8299 bool_not,
83100 bit_not,
101 assert,
84102 field,
85103 index,
86104 call,
87 @"comptime",
105 type_of,
106 /// Implements the `@extern` builtin
107 @"extern",
88108
89 /// Implements the `@js` builtin
90 js,
91109 /// Implements the `@jsNew` builtin
92110 js_new,
93111 /// Implements the `@import` builtin
......@@ -96,27 +114,32 @@ pub const Tag = enum {
96114 fetch,
97115 /// Implements the `@compileError` builtin
98116 compile_error,
99 /// Implements the `@typeOf` builtin
100 type_of,
101117
102118 pub fn Data(comptime tag: Tag) type {
103119 return switch (tag) {
104 .module => PlNode(IndexList),
105 .declaration => PlNode(Decl),
106 .block => PlNode(IndexList),
120 .module, .block, .@"comptime" => PlNode(IndexList),
121
122 .declaration,
123 .variable_declaration,
124 .test_declaration,
125 => PlNode(Decl),
107126 .coercion => PlNode(Coercion),
108127 .@"break" => PlNode(Break),
109128 .number => PlNode(Float),
110129 .string => PlNode(SlicedString),
111130 .call => PlNode(Call),
112131 .@"if" => PlNode(If),
113 .load_decl => StrNode,
114 .param => DataNode,
115132 .store => PlNode(Store),
133 .store_decl => PlNode(StoreVarDecl),
134 .store_field => PlNode(StoreField),
116135 .js_new => PlNode(JsNew),
117136 .struct_decl => PlNode(StructDecl),
118137 .struct_init => PlNode(StructInit),
119138 .array_init => PlNode(ArrayInit),
139 .func => PlNode(Func),
140
141 .load_decl => StrNode,
142 .param => DataNode,
120143
121144 .result_type_argument => ResultTypeArgument,
122145 .result_type_struct_field => ResultTypeStructField,
......@@ -130,13 +153,13 @@ pub const Tag = enum {
130153 .negate,
131154 .bool_not,
132155 .bit_not,
133 .js,
156 .@"extern",
134157 .import,
135158 .fetch,
136159 .compile_error,
137 .@"comptime",
138160 .type_of,
139161 .array_type,
162 .assert,
140163 => RefNode,
141164
142165 // Two instruction operands
......@@ -241,11 +264,11 @@ pub const Inst = struct {
241264 int32_type,
242265 float_type,
243266 string_type,
244 noreturn_type,
267 never_type,
245268 type_type,
246269 numeric_literal_type,
247270 numeric_literal_array_type,
248 noreturn_array_type,
271 never_array_type,
249272
250273 void,
251274 bool_true,
......@@ -315,6 +338,17 @@ pub const Store = struct {
315338 operand: Inst.Ref,
316339};
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
318352pub const Coercion = struct {
319353 type: Inst.Ref,
320354 operand: Inst.Ref,
......@@ -389,25 +423,19 @@ pub const ArrayInit = struct {
389423 list: RefList,
390424};
391425
392/// This will contain trailing data depending on the .kind
393426pub const Decl = struct {
394427 name: NullTerminatedString,
395428 hash: Cache.BinDigest,
396 kind: Kind,
397429 instruction_count: u32,
398430 body: Inst.Ref,
399
400 pub const Kind = enum {
401 // TODO: remove function, see #45
402 function,
403 immutable,
404 };
405431};
406432
407/// Trailing data for function declarations
408pub const DeclFn = struct {
409 params: RefList,
433pub const Func = struct {
434 params: OptionalRefList,
410435 return_type: Inst.Ref.Optional,
436 body: Inst.Index,
437 captures: IndexList,
438 instruction_count: u32,
411439};
412440
413441pub const ResultTypeArgument = struct {
......@@ -440,6 +468,7 @@ pub const ResultTypeArrayValue = struct {
440468// TODO: make this an enum
441469const NodeOffset = i32;
442470
471// TODO: remove InstOp. LazyNode should not have a decl
443472pub const InstOp = struct {
444473 index: Inst.Index,
445474 op: Report.Operand,
......@@ -486,6 +515,15 @@ pub const RefList = struct {
486515 }
487516};
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
489527pub const IndexList = struct {
490528 start: u32,
491529 len: u32,
......@@ -524,6 +562,10 @@ pub fn instData(teg: *const Teg, comptime tag: Teg.Tag, index: Inst.Index) tag.D
524562 return teg.instEncodedData(index).decode(tag, teg.instTag(index));
525563}
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
527569pub fn instPayload(teg: *const Teg, comptime tag: Teg.Tag, index: Inst.Index) tag.Data().T {
528570 return teg.instData(tag, index).extra_index.get(teg);
529571}
......@@ -554,52 +596,31 @@ pub fn fullFuncDecl(teg: *const Teg, index: Inst.Index) struct { Teg.Decl, Teg.D
554596 return .{ decl, decl_fn };
555597}
556598
557pub const FullDecl = struct {
558 name: NullTerminatedString,
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);
599pub fn fullDecl(teg: *const Teg, index: Inst.Index) Decl {
600 const decl = teg.instEncodedData(index).decodeUnsafe(PlNode(Decl), teg.instTag(index)).extra_index.get(teg);
572601 return .{
573602 .name = decl.name,
574603 .hash = decl.hash,
575604 .instruction_count = decl.instruction_count,
576605 .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 },
587606 };
588607}
589608
590609pub 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);
592611}
593612
594613pub fn moduleDeclarations(teg: *const Teg, module_index: Inst.Index) []const Inst.Index {
595614 const module = teg.instData(.module, module_index);
596615 const module_data: IndexList = module.extra_index.get(teg);
597616 const slice = module_data.slice(teg);
598 if (std.debug.runtime_safety) {
617 if (core.runtime_safety) {
599618 for (slice) |item| {
600 if (teg.instTag(item) != .declaration) {
601 std.debug.print("\ngot unknown tag {s}\n", .{@tagName(teg.instTag(item))});
602 unreachable;
619 switch (teg.instTag(item)) {
620 .declaration, .variable_declaration, .test_declaration => {},
621 else => {
622 std.debug.panic("\ngot unknown tag {s}\n", .{@tagName(teg.instTag(item))});
623 },
603624 }
604625 }
605626 }
......@@ -614,8 +635,8 @@ pub fn rootFunction(teg: *const Teg) Inst.Index {
614635}
615636
616637pub fn declName(teg: *const Teg, decl_index: Inst.Index) []const u8 {
617 const decl = teg.instData(.declaration, decl_index);
618 const decl_data: Teg.Decl = decl.extra_index.get(teg);
638 const decl = teg.instDataUnsafe(PlNode(Decl), decl_index);
639 const decl_data: Decl = decl.extra_index.get(teg);
619640 return decl_data.name.slice(teg.string_bytes);
620641}
621642
......@@ -623,11 +644,306 @@ pub fn dumpToWriter(teg: Teg, writer: anytype, tty: ?std.io.tty.Config) !void {
623644 try writer.print("{}", .{Dump{ .teg = teg, .tty = tty }});
624645}
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
626941const Teg = @This();
627942
628943const std = @import("std");
629944const mem = std.mem;
630945const assert = std.debug.assert;
946const HashHelper = std.Build.Cache.HashHelper;
631947
632948const core = @import("mod.zig");
633949const Cache = core.Cache;
......@@ -639,9 +955,6 @@ const TokenIndex = Ast.TokenIndex;
639955const OptionalNodeIndex = Ast.OptionalNodeIndex;
640956const OptionalTokenIndex = Ast.OptionalTokenIndex;
641957const OptionalExtraIndex = Ast.OptionalExtraIndex;
642const optionalTokenIndex = OptionalTokenIndex.init;
643const optionalNodeIndex = OptionalNodeIndex.init;
644const optionalExtraIndex = OptionalExtraIndex.init;
645958
646959const dod = core.array;
647960const Float = dod.Float;
src/core/Tokenizer.zig+93-52
......@@ -9,13 +9,8 @@ code: [:0]const u8,
99pos: ByteOffset = 0,
1010
1111/// Tokenize source code into a list of tokens.
12/// When an error occurs, the position of the error is returned in `error_pos`.
1312/// Caller owns returned slice.
14pub fn tokenize(
15 source_code: [:0]const u8,
16 gpa: mem.Allocator,
17 error_pos: *ByteOffset,
18) TokenizeError!Token.List.Slice {
13pub fn tokenize(source_code: [:0]const u8, gpa: mem.Allocator) mem.Allocator.Error!Token.List.Slice {
1914 const trace = T.trace(@src());
2015 defer trace.end();
2116
......@@ -25,11 +20,10 @@ pub fn tokenize(
2520 // pre-allocate some space based on a very rough guess
2621 try tokens.ensureTotalCapacity(gpa, @divFloor(source_code.len, 8));
2722
28 var tokenizer: Tokenizer = .{ .code = source_code, .pos = 0 };
29 errdefer error_pos.* = tokenizer.pos;
23 var tokenizer: Tokenizer = .{ .code = source_code };
3024
3125 tokenizer.skipWhitespace();
32 while (try tokenizer.next()) |token| {
26 while (tokenizer.next()) |token| {
3327 try tokens.append(gpa, token);
3428 tokenizer.skipWhitespace();
3529 }
......@@ -142,6 +136,11 @@ pub const Token = struct {
142136 /// tokenization, parsing will begin, and an error will be reported when
143137 /// this token is hit. That way, the error can tell you what it expected.
144138 invalid,
139 invalid_number,
140 invalid_string,
141 invalid_unterminated_string,
142 invalid_git_conflict,
143 invalid_utf8,
145144 /// End of file
146145 /// Used so that parser does not need to do out of bounds checks
147146 eof,
......@@ -149,7 +148,16 @@ pub const Token = struct {
149148 /// Most tokens have statically known lexemes, such as "struct" or "+="
150149 pub fn lexeme(tag: Tag) ?[]const u8 {
151150 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
154162 // do not have a static lexeme
155163 .id,
......@@ -162,8 +170,6 @@ pub const Token = struct {
162170 .module_doc_comment,
163171 => null,
164172
165 .eof => unreachable, // always must be special cased
166
167173 .@"and" => "&",
168174 .and_and => "&&",
169175 .bang => "!",
......@@ -226,8 +232,15 @@ pub const Token = struct {
226232
227233 pub fn description(tag: Tag) []const u8 {
228234 return switch (tag) {
229 .invalid => unreachable,
230235 .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
231244 .id => "an identifier",
232245 .builtin_id => "a builtin identifier",
233246 .number_int => "a numeric literal",
......@@ -242,7 +255,7 @@ pub const Token = struct {
242255
243256 pub fn isKeyword(tag: Tag) bool {
244257 return switch (tag) {
245 inline else => |t| mem.startsWith(u8, @tagName(t), "kw_"),
258 inline else => |t| comptime mem.startsWith(u8, @tagName(t), "kw_"),
246259 };
247260 }
248261
......@@ -250,6 +263,13 @@ pub const Token = struct {
250263 pub fn staticLen(tag: Tag) ?u32 {
251264 return if (tag.lexeme()) |str| @intCast(str.len) else null;
252265 }
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 }
253273 };
254274
255275 /// Instead of storing the length of a token, we can recompute this
......@@ -258,15 +278,27 @@ pub const Token = struct {
258278 /// If you are computing the length of something that is knowable
259279 /// without the source code, use token.tag.staticLen() instead.
260280 pub fn len(token: Token, utf8_source: [:0]const u8) u32 {
261 return token.tag.staticLen() orelse {
262 var tokenizer = Tokenizer{ .code = utf8_source, .pos = token.offset };
263 assert(
264 (tokenizer.next() catch
265 unreachable // data corruption
266 orelse unreachable // data corruption
267 ).tag == token.tag,
268 );
269 return tokenizer.pos - token.offset;
281 return token.tag.staticLen() orelse switch (token.tag) {
282
283 // TODO: calling len on these tokens is incorrect, as
284 // error handling code should special case these all
285 // to highlight exactly what went wrong.
286 .invalid => std.unicode.utf8ByteSequenceLength(utf8_source[token.offset]) catch 1,
287 .invalid_git_conflict => 7,
288 .invalid_utf8 => 1,
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 },
270302 };
271303 }
272304
......@@ -275,7 +307,7 @@ pub const Token = struct {
275307 utf8_source[token.offset..][0..token.len(utf8_source)];
276308 }
277309
278 const keywords = std.StaticStringMap(Tag).initComptime(keywords: {
310 pub const keywords = std.StaticStringMap(Tag).initComptime(keywords: {
279311 const fields = std.enums.values(Tag);
280312 const KV = struct { []const u8, Tag };
281313 var kws: [fields.len]KV = undefined;
......@@ -290,8 +322,8 @@ pub const Token = struct {
290322 });
291323};
292324
293/// When any of these errors occurs, the tokenizer's position is set to
294/// the start of the offending token.
325/// When any of these errors occurs, the tokenizer's
326/// position is set to the start of the offending bytes.
295327pub const NextTokenError = error{
296328 /// Generic token syntax error
297329 InvalidSyntax,
......@@ -310,11 +342,34 @@ pub const NextTokenError = error{
310342 InvalidUtf8,
311343};
312344
313pub const TokenizeError = mem.Allocator.Error || NextTokenError;
314
315345/// Next assumes the position is already at the start of a token.
316346/// 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 {
318373 while (true) {
319374 const trace = T.trace(@src());
320375 defer trace.end();
......@@ -935,7 +990,7 @@ fn simpleToken(t: *Tokenizer, comptime tag: Token.Tag) Token {
935990/// However, the character we ran into might actually be invalid UTF-8.
936991/// This utility just checks for that to make sure we don't mask error.InvalidUtf8
937992fn maybeUTF8Error(t: *Tokenizer, comptime real_err: anytype) @Type(.{
938 .ErrorSet = &[_]std.builtin.Type.Error{
993 .error_set = &[_]std.builtin.Type.Error{
939994 .{ .name = "InvalidUtf8" },
940995 .{ .name = @errorName(real_err) },
941996 },
......@@ -950,7 +1005,7 @@ fn maybeUTF8Error(t: *Tokenizer, comptime real_err: anytype) @Type(.{
9501005}
9511006
9521007fn maybeUTF8ErrorAt(t: *Tokenizer, comptime real_err: anytype, at: u32) @Type(.{
953 .ErrorSet = &[_]std.builtin.Type.Error{
1008 .error_set = &[_]std.builtin.Type.Error{
9541009 .{ .name = "InvalidUtf8" },
9551010 .{ .name = @errorName(real_err) },
9561011 },
......@@ -1240,10 +1295,10 @@ test "basic tokenizer example" {
12401295 var tokenizer = Tokenizer{ .code = source };
12411296 for (expected_tokens) |tok| {
12421297 tokenizer.skipWhitespace();
1243 try testing.expectEqual(tok, (try tokenizer.next()).?);
1298 try testing.expectEqual(tok, tokenizer.next().?);
12441299 }
12451300 tokenizer.skipWhitespace();
1246 try testing.expectEqual(null, (try tokenizer.next()));
1301 try testing.expectEqual(null, tokenizer.next());
12471302}
12481303
12491304test "field access" {
......@@ -1254,25 +1309,11 @@ test "field access" {
12541309 \\.0 hello.1.2
12551310 ;
12561311 const expected_tokens = [_]Token{
1257 .{ .tag = .kw_var, .offset = 0 },
1258 .{ .tag = .id, .offset = 4 },
1259 .{ .tag = .equal, .offset = 10 },
1260 .{ .tag = .number_float, .offset = 12 },
1261 .{ .tag = .number_int, .offset = 20 },
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 },
1312 .{ .tag = .dot, .offset = 0 },
1313 .{ .tag = .number_int, .offset = 1 },
1314 .{ .tag = .id, .offset = 3 },
1315 .{ .tag = .dot, .offset = 8 },
1316 .{ .tag = .number_float, .offset = 9 },
12761317 };
12771318 var tokenizer = Tokenizer{ .code = source };
12781319 for (expected_tokens) |tok| {
src/core/Transform.zig+681-381
......@@ -18,41 +18,46 @@ ast: Ast,
1818// these values are allocated through `gpa`, so that resulting IR can be
1919// trivially shrunk before returning.
2020
21insts: Inst.List = .{},
22extra: ExtraStore = .{},
23string_bytes: std.ArrayListUnmanaged(u8) = .{},
21insts: Inst.List,
22extra: ExtraStore,
23string_bytes: std.ArrayListUnmanaged(u8),
2424report: Report.Builder,
2525
2626// however, these are allocated through `arena`, since they are always freed at
2727// the end of the pass.
2828
29scratch: core.U32Stack = .{},
29scratch: core.U32Stack,
3030id_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
3336pub const Error = error{
3437 /// A `Report` will contain at least one error.
3538 TransformFail,
3639} || Allocator.Error;
3740
38pub fn transform(
39 ast: Ast,
40 gpa: Allocator,
41 file: *Comp.File,
42 fail_report: *Report,
43) Error!Teg {
41pub fn transform(ast: Ast, gpa: Allocator, fail_report: *Report) Error!Teg {
4442 const trace = tracer.trace(@src());
4543 defer trace.end();
4644
4745 var sfa = std.heap.stackFallback(4096, gpa);
4846
4947 var t: Transform = .{
50 .ast = ast,
5148 .gpa = gpa,
52 // TODO: Currently this is illusion of free will
5349 .arena = gpa,
54 .report = .{ .gpa = gpa, .current_file = file },
55 .id_decode_buf = std.ArrayList(u8).initCapacity(sfa.get(), 4096) catch unreachable,
50 .ast = ast,
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,
5661 };
5762 defer t.deinit();
5863
......@@ -83,7 +88,7 @@ fn deinit(t: *Transform) void {
8388 t.id_decode_buf.deinit();
8489 t.scratch.list.deinit(t.arena);
8590 t.scopes.deinit(t.arena);
86 t.id_decode_buf.deinit();
91 t.capture_scopes.deinit(t.arena);
8792 t.string_bytes.deinit(t.gpa);
8893 t.insts.deinit(t.gpa);
8994 t.extra.deinit(t.gpa);
......@@ -120,6 +125,9 @@ const ResultType = union(enum) {
120125 /// The expression is a field in an array initialization
121126 array_value: Inst.Ref,
122127
128 // /// The expression is unknown field
129 // field: Teg.ResultTypeField,
130
123131 /// This will return an instruction pointing to the the type
124132 pub fn consume(result: *ResultType, t: *Transform) !?Inst.Ref {
125133 if (result.* == .none) return null;
......@@ -150,7 +158,7 @@ const BlockContext = struct {
150158 break_target: ?*BreakTarget = null,
151159
152160 const ReturnTarget = struct {
153 generated_block: ?Inst.Index = null,
161 block_index: Inst.Index,
154162 result_type: ResultType,
155163 };
156164
......@@ -166,14 +174,6 @@ const BlockContext = struct {
166174 parent: ?*BreakTarget = null,
167175 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
177177 fn getInst(target: *BreakTarget, t: *Transform) !Inst.Index {
178178 return target.block_index orelse {
179179 const inst = try t.addUntracked(.block, undefined);
......@@ -203,10 +203,11 @@ const BlockContext = struct {
203203 return .{
204204 .decl_node_offset = self.decl_node_offset,
205205 .return_target = return_target,
206 .break_target = null,
206207 };
207208 }
208209
209 fn withBreak(self: BlockContext, break_target: *BreakTarget) BlockContext {
210 fn withBreak(self: BlockContext, break_target: ?*BreakTarget) BlockContext {
210211 return .{
211212 .decl_node_offset = self.decl_node_offset,
212213 .return_target = self.return_target,
......@@ -215,78 +216,29 @@ const BlockContext = struct {
215216 }
216217
217218 fn lookupNamedBreak(self: BlockContext, label: []const u8) ?*BreakTarget {
218 var break_target = self.break_target;
219 while (break_target) |target| : (break_target = target.parent) {
219 var target = self.break_target orelse return null;
220 while (true) {
220221 if (target.label) |target_label| {
221222 if (mem.eql(u8, label, target_label)) {
222223 return target;
223224 }
224225 }
226
227 target = target.parent orelse return null;
225228 }
226 return null;
227229 }
228230};
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.
230240const Scope = struct {
231 symbols: std.StringArrayHashMapUnmanaged(Symbol) = .{},
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 };
241 symbols: std.StringArrayHashMapUnmanaged(Symbol) = .empty,
290242
291243 fn addWarnings(s: Scope, t: *Transform) !void {
292244 for (s.symbols.values()) |value| {
......@@ -305,9 +257,109 @@ const Scope = struct {
305257 }
306258};
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(.{
311363 .{ "void", .void },
312364 .{ "true", .bool_true },
313365 .{ "false", .bool_false },
......@@ -316,7 +368,7 @@ const BuiltInIds = std.StaticStringMap(Inst.Ref).initComptime(.{
316368 .{ "string", .string_type },
317369 // .{ "bigint", .bigint_type },
318370 .{ "bool", .bool_type },
319 .{ "noreturn", .noreturn_type },
371 .{ "never", .never_type },
320372 .{ "type", .type_type },
321373 // .{ "std", .standard_library },
322374});
......@@ -327,261 +379,235 @@ fn transformModule(t: *Transform, module_node: NodeIndex) Allocator.Error!Inst.I
327379
328380 const module = try t.addUntracked(.module, undefined);
329381
330 const decl = BlockContext.init(module_node);
382 const decl: BlockContext = .init(module_node);
331383
332384 try t.pushScope();
333385 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);
335392 const statements = t.ast.nodeStatements(module_node);
336393 for (statements) |statement| {
337 try t.previsitModuleStatement(statement);
394 try t.previsitModuleStatement(&previsit_state, statement);
338395 }
339396
397 // Pass 2 processes all declarations in order, recursively
398 // visiting every Ast node.
340399 const s = t.scope();
341
342 const temp = try t.gpa.alloc(Inst.Index, s.symbols.count() + 1);
343 defer t.gpa.free(temp);
400 const decl_len = s.symbols.count() + previsit_state.test_decls.items.len;
401 const decls = try t.gpa.alloc(Inst.Index, decl_len);
402 defer t.gpa.free(decls);
344403
345404 {
346 // prevent concurrent access
405 // `transformDecl` is not allowed to mutate symbols as we rely on the stable value.
347406 s.symbols.lockPointers();
348407 defer s.symbols.unlockPointers();
408
349409 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);
351411 }
352412 }
353413
354 const top_level_fn = try t.addUntracked(.declaration, undefined);
355
356 temp[temp.len - 1] = top_level_fn;
414 for (previsit_state.test_decls.items, s.symbols.count()..) |test_decl_node, i| {
415 decls[i] = try t.transformTestDecl(decl, test_decl_node);
416 }
357417
358418 const declarations: IndexList = .{
359419 .start = @intCast(t.extra.list.items.len),
360 .len = @intCast(temp.len),
420 .len = @intCast(decls.len),
361421 };
362 try t.extra.list.appendSlice(t.gpa, @ptrCast(temp));
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);
422 try t.extra.list.appendSlice(t.gpa, @ptrCast(decls));
378423
379 t.getInstData(block).* = EncodedData.encode(.block, .{
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, .{
424 t.getInstData(module).* = .encode(.module, .{
406425 .src_node = 0,
407426 .extra_index = try t.extra.append(t.gpa, IndexList, declarations),
408427 });
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
410437 try t.scope().addWarnings(t);
411438
412439 return module;
413440}
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 {
416447 const trace = tracer.trace(@src());
417448 defer trace.end();
418449
419 const tag = t.ast.nodes.items(.tag)[node];
420 switch (tag) {
421 .fn_decl => {
422 const decl = t.ast.fullFnDecl(node);
423 var id, const id_allocated = try t.decodeIdentifierExtra(decl.id);
450 switch (t.ast.nodes.items(.tag)[node]) {
451 // The paths of these three are all very similar and
452 // it is worth grouping them together
453 inline .fn_decl,
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);
424470 if (id_allocated)
425471 id = try t.gpa.dupe(u8, id);
472
426473 const gop = try t.scope().symbols.getOrPut(t.gpa, id);
427474 if (gop.found_existing) {
428475 if (id_allocated) t.gpa.free(id);
476
429477 try t.report.err(.duplicate_function, .{
430478 .node = node,
431 .prev = gop.value_ptr.node,
479 .prev = gop.value_ptr.src_node,
432480 });
433481 return;
434482 }
483
435484 gop.value_ptr.* = .{
436 .node = node,
437 .data = .declaration,
438 .type = .{ .present = .void },
485 .kind = new_kind,
486 .flags = .{
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,
439495 };
440496 },
441 .struct_decl => {
442 const decl = t.ast.fullStructDecl(node);
443 var id, const id_allocated = try t.decodeIdentifierExtra(decl.id);
444 if (id_allocated)
445 id = try t.gpa.dupe(u8, id);
446 const gop = try t.scope().symbols.getOrPut(t.gpa, decl.id);
447 if (gop.found_existing) {
448 try t.report.err(.duplicate_function, .{
449 .node = node,
450 .prev = gop.value_ptr.node,
451 // TODO: put type of both declarations
452 });
453 return;
454 }
455 gop.value_ptr.* = .{
456 .node = node,
457 .data = .declaration,
458 .type = .{ .present = .void },
497 .test_decl => {
498 try state.test_decls.append(t.arena, node);
499 },
500 else => if (!t.has_warned_top_level_statement) {
501 t.has_warned_top_level_statement = true;
502 try t.report.err(.top_level_statement, .{ .src = node });
503
504 // Visit the statement so that any identifiers get tracked, so that mistakes in the code get caught
505 const insts_top = t.insts.len;
506 defer t.insts.len = insts_top;
507 const extra_top = t.extra.list.items.len;
508 defer t.extra.list.items.len = extra_top;
509 const string_top = t.string_bytes.items.len;
510 defer t.string_bytes.items.len = string_top;
511
512 _ = transformBlockStatement(t, .init(node), node, null) catch |err| switch (err) {
513 error.OutOfMemory => return err,
459514 };
460515 },
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),
480516 }
481517}
482518
483519fn 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 {
492520 const trace = tracer.trace(@src());
493521 defer trace.end();
494522
495 const decl = outer_decl.declOffset(node);
496 const fn_decl = t.ast.fullFnDecl(node);
523 const decl_index = try t.addUntracked(.declaration, undefined);
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();
501 defer t.popScope();
528 const inst_count = @as(u32, @intCast(t.insts.len)) - @intFromEnum(decl_index);
502529
503 const param_list = brk: {
504 const scratch = t.scratch.mark();
505 defer scratch.deinit();
530 const id_encoded = t.ast.tokenSlice(t.ast.idToken(node));
531 const id = try t.decodeIdentifier(id_encoded);
532 const name = try t.addNullTerminatedString(id);
506533
507 for (fn_decl.params) |param| {
508 try t.scratch.add(t.arena, Inst.Ref, try t.transformParamDecl(decl, param));
509 }
534 var hasher: std.Build.Cache.HashHelper = .{ .hasher = core.Cache.hasher_init };
535 hasher.addBytes(id);
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,
512542 };
513543
514 const return_type = if (fn_decl.return_type) |return_type_node|
515 try t.transformToBlock(
516 decl,
517 return_type_node,
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 );
544 const tag_list = temp_teg.insts.items(.tag)[@intFromEnum(decl_index) + 1 ..];
545 const data_list = temp_teg.insts.items(.data)[@intFromEnum(decl_index) + 1 ..];
546
547 temp_teg.writeToHasher(&hasher, tag_list, data_list, decl_index);
530548
531549 const decl_extra = try t.extra.append(t.gpa, Teg.Decl, .{
532 .kind = .function,
533 .name = try t.addNullTerminatedString(fn_decl.id),
534 .hash = .{0} ** 16,
535 .instruction_count = @as(u32, @intCast(t.insts.len)) - @intFromEnum(decl_inst),
550 .name = name,
551 .hash = hasher.hasher.finalResult(),
552 .instruction_count = inst_count,
536553 .body = body,
537554 });
538555
539 _ = try t.extra.append(t.gpa, Teg.DeclFn, .{
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, .{
556 t.getInstData(decl_index).* = .encode(.declaration, .{
545557 .src_node = outer_decl.nodeOffset(node),
546558 .extra_index = decl_extra,
547559 });
548560
549 try t.scope().addWarnings(t);
550
551 return decl_inst;
561 return decl_index;
552562}
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 {
555565 const trace = tracer.trace(@src());
556566 defer trace.end();
557567
558 const decl = outer_decl.declOffset(node);
559 const decl_data = t.ast.fullStructDecl(node);
568 const decl_index = try t.addUntracked(.test_declaration, undefined);
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
565593 const decl_extra = try t.extra.append(t.gpa, Teg.Decl, .{
566 .kind = .immutable,
567 .name = try t.addNullTerminatedString(decl_data.id),
568 .hash = .{0} ** 16,
569 .instruction_count = @as(u32, @intCast(t.insts.len)) - @intFromEnum(decl_inst),
594 .name = .empty,
595 .hash = hasher.hasher.finalResult(),
596 .instruction_count = inst_count,
570597 .body = body,
571598 });
572599
573 t.getInstData(decl_inst).* = EncodedData.encode(.declaration, .{
600 t.getInstData(decl_index).* = .encode(.test_declaration, .{
574601 .src_node = outer_decl.nodeOffset(node),
575602 .extra_index = decl_extra,
576603 });
577604
578 return decl_inst;
605 return decl_index;
579606}
580607
581608const TransformToBlockMode = enum {
582609 tracked_result,
583 result_without_coercion,
584 coerced_result,
610 untracked_result,
585611 function_body,
586612
587613 fn ResultType(mode: TransformToBlockMode) type {
......@@ -590,42 +616,63 @@ const TransformToBlockMode = enum {
590616 else => Transform.ResultType,
591617 };
592618 }
619
620 fn InstType(mode: TransformToBlockMode) type {
621 return switch (mode) {
622 .function_body => Inst.Index,
623 else => Inst.Ref,
624 };
625 }
593626};
594627
595/// guh
628const 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.
596639fn transformToBlock(
597640 t: *Transform,
598641 ctx: BlockContext,
599642 node: NodeIndex,
600643 comptime mode: TransformToBlockMode,
601644 result_type: mode.ResultType(),
602) Allocator.Error!Inst.Ref {
645 block_type: BlockType,
646) Allocator.Error!mode.InstType() {
603647 const trace = tracer.trace(@src());
604648 defer trace.end();
605649
606650 const scratch = t.scratch.mark();
607651 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
609663 // For function blocks, add .param instructions for all of the parameters
610664 if (mode == .function_body) {
611665 const s = t.scopes.getLast();
612666 for (s.symbols.values(), 0..) |*param, i| {
613 assert(param.data == .param);
667 assert(param.kind == .param);
614668 const ref = try t.addRef(.param, .{
615669 .src_node = ctx.nodeOffset(node),
616670 .data = @intCast(i),
617671 });
618 param.data = .{ .immutable = ref };
672 param.value = .{ .immutable = ref };
619673 }
620674 }
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
629676 const expr = try t.transformExpression(
630677 // Function blocks are allowed to return, in which
631678 // it will return to this block.
......@@ -641,15 +688,15 @@ fn transformToBlock(
641688 node,
642689 );
643690
644 // if there are no new instructions, we dont have to create a block context
645 if (scratch.len() == 0) {
646 const result = expr orelse
647 unreachable; // null means we *added* break or return
648
649 if (result.value() != null) {
650 if (mode == .function_body)
651 assert(ret.generated_block == null); // return was called without an instruction
691 // if we were given a value without any new instructions, we can just
692 // forward that value.
693 if (mode != .function_body and scratch.len() == 0) {
694 const result = expr orelse {
695 assert(t.report.errors.items.len > 0);
696 return .void; // null means control flow ended, and with no instructions guarantees a compile error
697 };
652698
699 if (result.decode() == .value) {
653700 return result;
654701 }
655702 }
......@@ -658,14 +705,20 @@ fn transformToBlock(
658705 if (expr) |result| {
659706 if (result.index()) |index| {
660707 if (t.insts.items(.tag)[@intFromEnum(index)] == .block) {
708 if (mode == .function_body)
709 return index;
661710 return result;
662711 }
663712 }
664713 }
665714
666715 // 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;
668 const ret_block = maybe_generated_block orelse try t.addUntracked(.block, undefined);
716 const ret_block = if (mode == .function_body)
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
670723 // The block must end with a break
671724 if (expr) |result| {
......@@ -681,12 +734,12 @@ fn transformToBlock(
681734 const content = try t.scratchToIndexList(scratch);
682735 assert(content.len > 0);
683736
684 t.getInstData(ret_block).* = EncodedData.encode(.block, .{
737 t.getInstData(ret_block).* = .encode(.block, .{
685738 .src_node = ctx.nodeOffset(node),
686739 .extra_index = try t.extra.append(t.gpa, IndexList, content),
687740 });
688741
689 return ret_block.toRef();
742 return if (mode == .function_body) ret_block else ret_block.toRef();
690743}
691744
692745fn transformParamDecl(t: *Transform, decl: BlockContext, param: NodeIndex) Allocator.Error!Inst.Ref {
......@@ -698,8 +751,9 @@ fn transformParamDecl(t: *Transform, decl: BlockContext, param: NodeIndex) Alloc
698751 decl,
699752 param_decl.type orelse
700753 @panic("TODO: inferred param types"),
701 .result_without_coercion,
754 .untracked_result,
702755 .{ .ref = .type_type },
756 .compile_time,
703757 );
704758
705759 var id, const id_allocated = try t.decodeIdentifierExtra(param_decl.id);
......@@ -713,22 +767,29 @@ fn transformParamDecl(t: *Transform, decl: BlockContext, param: NodeIndex) Alloc
713767 if (id_allocated) t.gpa.free(id);
714768 try t.report.err(.duplicate_param, .{
715769 .node = param,
716 .prev = sym.value_ptr.node,
770 .prev = sym.value_ptr.src_node,
717771 });
718772 return .void;
719773 }
720774
721775 sym.value_ptr.* = .{
722 .node = param,
723 .data = .param,
724 .type = .{ .present = ty },
776 .src_node = param,
777 .kind = .param,
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,
725786 };
726787
727788 return ty;
728789}
729790
730791pub const Builtin = enum {
731 js,
792 @"extern",
732793 js_new,
733794 import,
734795 fetch,
......@@ -740,13 +801,13 @@ pub const Builtin = enum {
740801 .{ "compileError", .compile_error },
741802 .{ "fetch", .fetch },
742803 .{ "import", .import },
743 .{ "js", .js },
804 .{ "extern", .@"extern" },
744805 .{ "jsNew", .js_new },
745806 .{ "typeOf", .type_of },
746807 // sort-lines: end
747808 });
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 {
750811 const trace = tracer.trace(@src());
751812 defer trace.end();
752813
......@@ -758,7 +819,7 @@ pub const Builtin = enum {
758819 var result: ResultType = .{ .ref = .string_type };
759820 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", .{
762823 .src_node = decl.nodeOffset(args[0]),
763824 .operand = source,
764825 });
......@@ -841,7 +902,6 @@ fn transformExpression(
841902 switch (node_tag) {
842903 .root,
843904 .param_decl,
844 .var_decl,
845905 => unreachable, // not an expression
846906
847907 .id => {
......@@ -851,7 +911,7 @@ fn transformExpression(
851911 return .void;
852912 }
853913
854 if (BuiltInIds.get(id)) |ref| {
914 if (built_in_ids.get(id)) |ref| {
855915 return ref;
856916 }
857917
......@@ -860,17 +920,53 @@ fn transformExpression(
860920 return .void;
861921 };
862922
863 lookup.used = true;
923 lookup.flags.used = true;
864924
865 return switch (lookup.data) {
866 .declaration => try t.addRef(.load_decl, .{
925 switch (lookup.kind) {
926 .declaration,
927 .variable_declaration,
928 => return try t.addRef(.load_decl, .{
867929 .src_node = ctx.nodeOffset(node),
868930 .string = try t.addNullTerminatedString(id),
869931 }),
870 .mutable => |idx| idx.toRef(),
871 .immutable => |ref| ref,
872 .param => unreachable, // unresolved param
873 };
932 .mutable => {
933 const ref = lookup.value.mutable.toRef();
934 try t.markCapturedRef(ref);
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 });
874970 },
875971
876972 inline .number_int, .number_float => |tag| {
......@@ -910,18 +1006,11 @@ fn transformExpression(
9101006
9111007 .assign => {
9121008 const ast_bin = t.ast.nodeData(node).bin;
913 const lhs, var result_type = try t.transformAssignmentTarget(ctx, ast_bin.lhs) orelse
1009 const target, var result_type = try t.transformAssignmentTarget(ctx, ast_bin.lhs) orelse
9141010 return .void;
9151011 const rhs = try t.transformExpression(ctx, &result_type, ast_bin.rhs) orelse
9161012 return null;
917 const assign_inst = try t.addRef(.store, .{
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;
1013 return try target.emit(t, rhs, ctx, node);
9251014 },
9261015
9271016 inline .add,
......@@ -983,20 +1072,13 @@ fn transformExpression(
9831072 const compute_inst = try t.addRef(teg_tag, .{
9841073 .src_node = ctx.nodeOffset(node),
9851074 .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,
9871076 .rhs = rhs,
9881077 }),
9891078 });
9901079
9911080 if (is_assign) {
992 const assign_inst = try t.addRef(.store, .{
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;
1081 return try lhs.emit(t, compute_inst, ctx, node);
10001082 }
10011083
10021084 return compute_inst;
......@@ -1006,6 +1088,7 @@ fn transformExpression(
10061088 .bool_not,
10071089 .bit_not,
10081090 .array_type,
1091 .assert,
10091092 => |tag| {
10101093 const ast_un = t.ast.nodeData(node).un;
10111094 const teg_tag = @field(Teg.Tag, @tagName(tag));
......@@ -1015,6 +1098,7 @@ fn transformExpression(
10151098 .bool_not => .{ .ref = .bool_type },
10161099 .bit_not => .{ .ref = .int32_type },
10171100 .array_type => .{ .ref = .type_type },
1101 .assert => .{ .ref = .bool_type },
10181102 else => @compileError(unreachable),
10191103 };
10201104
......@@ -1029,11 +1113,20 @@ fn transformExpression(
10291113
10301114 .@"comptime" => {
10311115 const ast_un = t.ast.nodeData(node).un;
1032 const operand = try t.transformToBlock(ctx, ast_un.expr, .tracked_result, result);
1033 return try t.addRef(.@"comptime", .{
1034 .src_node = ctx.nodeOffset(node),
1035 .operand = operand,
1036 });
1116 const operand = try t.transformToBlock(ctx, ast_un.expr, .tracked_result, result, .compile_time);
1117 // Move the source index to the `comptime` node
1118 if (operand.index()) |index| {
1119 const slice = t.insts.slice();
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;
10371130 },
10381131
10391132 .label => {
......@@ -1054,8 +1147,7 @@ fn transformExpression(
10541147
10551148 // if there are no new instructions, we dont have to create a block context
10561149 if (scratch.len() == 0)
1057 return inner_expr orelse
1058 unreachable; // null means we added break or return
1150 return inner_expr;
10591151
10601152 if (target.block_index) |block| {
10611153 if (inner_expr) |out| {
......@@ -1071,7 +1163,7 @@ fn transformExpression(
10711163 const content = try t.scratchToIndexList(scratch);
10721164 scratch.deinit();
10731165
1074 t.getInstData(block).* = EncodedData.encode(.block, .{
1166 t.getInstData(block).* = .encode(.block, .{
10751167 .src_node = ctx.nodeOffset(node),
10761168 .extra_index = try t.extra.append(t.gpa, IndexList, content),
10771169 });
......@@ -1080,7 +1172,7 @@ fn transformExpression(
10801172
10811173 return block.toRef();
10821174 } else {
1083 std.debug.print("Warn, unused label {s}", .{label});
1175 std.debug.print("Warn, unused label {s}\n", .{label});
10841176 }
10851177
10861178 return inner_expr;
......@@ -1119,7 +1211,7 @@ fn transformExpression(
11191211 const args = base.slice(&t.ast);
11201212
11211213 return switch (builtin) {
1122 .js => Builtin.builtin_js(t, ctx, node, args),
1214 .@"extern" => Builtin.builtin_extern(t, ctx, node, args),
11231215 .js_new => Builtin.builtin_jsNew(t, ctx, node, args),
11241216 // .import => Builtin.import(t, decl, node, args),
11251217 // .fetch => Builtin.fetch(t, decl, node, args),
......@@ -1238,17 +1330,11 @@ fn transformExpression(
12381330 return null;
12391331
12401332 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
12441335 var no_result: ResultType = if (if_data.no == .none) .used else result.*;
12451336 const no = if (if_data.no.unwrap()) |no|
1246 try t.transformToBlock(
1247 ctx,
1248 no,
1249 .tracked_result,
1250 &no_result,
1251 )
1337 try t.transformToBlock(ctx, no, .tracked_result, &no_result, .run_time)
12521338 else
12531339 Inst.Ref.void;
12541340
......@@ -1275,14 +1361,6 @@ fn transformExpression(
12751361 return .void;
12761362 };
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
12861364 var return_result = target.result_type;
12871365
12881366 const operand: Inst.Ref = if (ret_node.expr.unwrap()) |expr|
......@@ -1297,7 +1375,7 @@ fn transformExpression(
12971375 _ = try t.addIndex(.@"break", .{
12981376 .src_node = ctx.nodeOffset(node),
12991377 .extra_index = try t.extra.append(t.gpa, Teg.Break, .{
1300 .block = ret_block,
1378 .block = target.block_index,
13011379 .operand = operand,
13021380 }),
13031381 });
......@@ -1319,7 +1397,7 @@ fn transformExpression(
13191397
13201398 var break_result = block.result_type.*;
13211399 const operand: Inst.Ref = if (brk_node.value.unwrap()) |expr|
1322 try t.transformExpression(ctx.withReturn(null), &break_result, expr) orelse
1400 try t.transformExpression(ctx, &break_result, expr) orelse
13231401 return null
13241402 else
13251403 .void;
......@@ -1441,6 +1519,74 @@ fn transformExpression(
14411519 });
14421520 },
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
14441590 else => |tag| std.debug.panic("TODO: expr {s}", .{@tagName(tag)}),
14451591 }
14461592}
......@@ -1467,9 +1613,9 @@ fn transformExpressionList(t: *Transform, decl: BlockContext, result: ResultType
14671613/// This returns the assignment target's Ref, as well as a result type to visit
14681614/// the right-hand side with. Note that only a few expressions are valid here:
14691615///
1470/// - A plain identifier, which is resolved directly to a `memory` instruction
1471/// - A `field` node: TODO
1472/// - An `index` node: TODO
1616/// - A plain identifier with a `memory` instruction -> `.store_memory`
1617/// - A plain identifier to a .variable_declaration -> `.store_decl`
1618/// - A `field` or `index` node -> `.store_field`
14731619///
14741620/// Note that these three are the root nodes, it is legal to have:
14751621///
......@@ -1479,9 +1625,9 @@ fn transformExpressionList(t: *Transform, decl: BlockContext, result: ResultType
14791625/// or a value with a reference type; and optionally a sub property.
14801626fn transformAssignmentTarget(
14811627 t: *Transform,
1482 decl: BlockContext,
1628 ctx: BlockContext,
14831629 node: NodeIndex,
1484) Allocator.Error!?struct { Inst.Index, ResultType } {
1630) Allocator.Error!?struct { AssignmentTarget, ResultType } {
14851631 const node_tag = t.ast.nodes.items(.tag)[node];
14861632
14871633 const trace = tracer.traceFmt(@src(), "{s}", .{@tagName(node_tag)});
......@@ -1502,32 +1648,44 @@ fn transformAssignmentTarget(
15021648 return null;
15031649 }
15041650
1505 if (BuiltInIds.has(id)) {
1651 if (built_in_ids.has(id)) {
15061652 try t.report.err(.assign_builtin, .{ .node = node });
15071653 return null;
15081654 }
15091655
1510 const lookup: *Scope.Symbol = t.lookupId(id) orelse {
1656 const lookup: *Symbol = t.lookupId(id) orelse {
15111657 try t.report.err(.unknown_identifier, .{ .node = node });
15121658 return null;
15131659 };
15141660
1515 return switch (lookup.data) {
1516 .mutable => |inst| {
1517 lookup.mutated = true;
1518 return .{ inst, lookup.type.resolve() };
1661 return switch (lookup.kind) {
1662 .mutable => {
1663 lookup.flags.mutated = true;
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 };
15191677 },
15201678 else => {
1521 lookup.used = true;
1679 lookup.flags.used = true;
15221680
15231681 try t.report.err(.invalid_assignment_symbol, .{
15241682 .node = node,
1525 .decl = lookup.node,
1526 .kind = switch (lookup.data) {
1527 .mutable => unreachable,
1683 .decl = lookup.src_node,
1684 .kind = switch (lookup.kind) {
1685 .mutable, .variable_declaration => unreachable,
15281686 .declaration => .declaration,
15291687 .param => .param,
1530 .immutable => switch (t.ast.nodeTag(lookup.node)) {
1688 .immutable => switch (t.ast.nodeTag(lookup.src_node)) {
15311689 .param_decl => .param,
15321690 else => .constant,
15331691 },
......@@ -1539,11 +1697,50 @@ fn transformAssignmentTarget(
15391697 };
15401698 },
15411699
1542 // TODO: allow field expressions, but these need to emit a different store node
1700 .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
15441741 else => {
15451742 var result_type: ResultType = .none;
1546 _ = try t.transformExpression(decl, &result_type, node);
1743 _ = try t.transformExpression(ctx, &result_type, node);
15471744
15481745 try t.report.err(.invalid_assignment_target, .{
15491746 .node = node,
......@@ -1554,6 +1751,63 @@ fn transformAssignmentTarget(
15541751 }
15551752}
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
15571811fn transformBlockStatement(
15581812 t: *Transform,
15591813 ctx: BlockContext,
......@@ -1592,7 +1846,7 @@ fn transformBlockStatement(
15921846 return .void;
15931847 }
15941848
1595 if (BuiltInIds.has(inner_id)) {
1849 if (built_in_ids.has(inner_id)) {
15961850 try t.report.warn(.discard_builtin, .{ .node = var_decl.init });
15971851 return .void;
15981852 }
......@@ -1602,7 +1856,7 @@ fn transformBlockStatement(
16021856 return .void;
16031857 };
16041858
1605 lookup.discarded = node;
1859 lookup.discarded = .init(node);
16061860
16071861 return .void;
16081862 },
......@@ -1628,7 +1882,7 @@ fn transformBlockStatement(
16281882 assert(isSliceInBuffer(u8, t.ast.utf8_source, id));
16291883 }
16301884
1631 if (BuiltInIds.has(id)) {
1885 if (built_in_ids.has(id)) {
16321886 try t.report.err(.shadow_builtin_variable, .{ .node = node });
16331887 return .void;
16341888 }
......@@ -1674,20 +1928,22 @@ fn transformBlockStatement(
16741928 }
16751929
16761930 gop.value_ptr.* = .{
1677 .node = node,
1678 .data = if (var_decl.is_const)
1931 .src_node = node,
1932 .kind = if (var_decl.is_const) .immutable else .mutable,
1933 .value = if (var_decl.is_const)
16791934 .{ .immutable = data_coerced_ref }
16801935 else
1681 .{
1682 .mutable = try t.addIndex(.memory, .{
1683 .src_node = ctx.nodeOffset(node),
1684 .operand = data_coerced_ref,
1685 }),
1686 },
1687 .type = if (type_ref) |ty|
1688 .{ .present = ty }
1689 else
1690 .{ .missing = data_ref },
1936 .{ .mutable = try t.addIndex(.memory, .{
1937 .src_node = ctx.nodeOffset(node),
1938 .operand = data_coerced_ref,
1939 }) },
1940 .discarded = .none,
1941 .flags = .{
1942 .result_type_kind = if (type_ref != null) .present else .missing,
1943 .used = false,
1944 .mutated = false,
1945 },
1946 .result_type_ref = if (type_ref) |ty| ty else data_ref,
16911947 };
16921948
16931949 return .void;
......@@ -1767,7 +2023,7 @@ fn transformBlockStatement(
17672023fn addUntracked(t: *Transform, comptime tag: Teg.Tag, data: tag.Data()) !Inst.Index {
17682024 try t.insts.append(t.gpa, .{
17692025 .tag = tag,
1770 .data = EncodedData.encode(tag, data),
2026 .data = .encode(tag, data),
17712027 });
17722028 return @enumFromInt(t.insts.len - 1);
17732029}
......@@ -1796,6 +2052,38 @@ fn scope(t: *Transform) *Scope {
17962052 return &t.scopes.items[t.scopes.items.len - 1];
17972053}
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
17992087fn getInstData(t: *Transform, index: Inst.Index) *EncodedData {
18002088 return &t.insts.items(.data)[@intFromEnum(index)];
18012089}
......@@ -1820,7 +2108,17 @@ fn scratchToIndexList(t: *Transform, scratch: core.U32Stack.Mark) !IndexList {
18202108 };
18212109}
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 {
18242122 var i = t.scopes.items.len;
18252123 while (i > 0) {
18262124 i -= 1;
......@@ -1835,11 +2133,12 @@ fn addNullTerminatedString(t: *Transform, str: []const u8) !NullTerminatedString
18352133 if (str.len == 0) {
18362134 return .empty;
18372135 }
2136
18382137 // loop through strings trying to find an existing match, and dedupe memory
18392138 var pos: usize = 0;
18402139 while (mem.indexOfScalarPos(u8, str, pos, 0)) |i| : (pos = i + 1) {
18412140 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)) {
18432142 const string: NullTerminatedString = @enumFromInt(pos);
18442143 assert(mem.eql(u8, string.slice(t.string_bytes.items), str));
18452144 return string;
......@@ -1862,7 +2161,7 @@ fn addSlicedString(t: *Transform, str: []const u8) !SlicedString {
18622161 // loop through strings trying to find an existing match, and dedupe memory
18632162 var pos: usize = 0;
18642163 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;
18662165 if (mem.eql(u8, t.string_bytes.items[start_index..][0..str.len], str)) {
18672166 const string: SlicedString = .{
18682167 .start = @intCast(start_index),
......@@ -1950,12 +2249,13 @@ const NullTerminatedString = Teg.NullTerminatedString;
19502249const SlicedString = Teg.SlicedString;
19512250const RefList = Teg.RefList;
19522251const IndexList = Teg.IndexList;
2252const OptionalRefList = Teg.OptionalRefList;
19532253const EncodedData = Teg.EncodedData;
19542254const ExtraIndex = Teg.ExtraIndex;
19552255
1956const extra = core.array;
1957const ExtraStore = extra.ExtraStore(.teg, .@"var");
1958const Float = extra.Float;
2256const array = core.array;
2257const ExtraStore = array.ExtraStore(.teg, .@"var");
2258const Float = array.Float;
19592259
19602260const Tokenizer = core.Tokenizer;
19612261const 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 {
2121
2222 else => brk: {
2323 const info = @typeInfo(field.type);
24 if (info == .Enum) {
24 if (info == .@"enum") {
2525 items[i] = @intFromEnum(value);
2626 break :brk;
2727 }
2828
29 const struct_data = info.Struct;
29 const struct_data = info.@"struct";
3030 if (struct_data.layout == .@"packed") {
3131 items[i] = @bitCast(value);
3232 break :brk;
......@@ -48,11 +48,11 @@ fn getAsU32(items: []const u32, comptime T: type, index: usize) T {
4848
4949 else => brk: {
5050 const info = @typeInfo(T);
51 if (info == .Enum) {
51 if (info == .@"enum") {
5252 break :brk @enumFromInt(items[index]);
5353 }
5454
55 const struct_data = info.Struct;
55 const struct_data = info.@"struct";
5656 if (struct_data.layout == .@"packed") {
5757 break :brk @bitCast(items[index]);
5858 }
......@@ -99,9 +99,9 @@ pub const StoreKind = enum {
9999 comptime kind: StoreKind,
100100 comptime Owner: type,
101101 ) type {
102 const ptr = @typeInfo(Owner).Pointer;
102 const ptr = @typeInfo(Owner).pointer;
103103 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| {
105105 if (std.mem.eql(u8, field.name, "extra")) {
106106 break :brk field.type.mutability;
107107 }
......@@ -148,15 +148,31 @@ pub fn EncodedData(comptime Tag: type, backing_type: type) type {
148148
149149 pub fn decode(data: @This(), comptime tag: Tag, proof: Tag) tag.Data() {
150150 assert(proof == tag); // incorrect memory access
151 const Data = tag.Data();
152 const info = @typeInfo(Data);
153 if (info == .Enum) {
151 return data.decodeUnsafe(tag.Data(), proof);
152 }
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") {
154170 return @enumFromInt(@intFromEnum(data));
155171 }
156 if (info == .Struct) {
172 if (info == .@"struct") {
157173 assert(backing_type == u64);
158174 var fields: [2]u32 = @bitCast(@intFromEnum(data));
159 return getAsU32(&fields, Data, 0);
175 return getAsU32(&fields, T, 0);
160176 }
161177 return @bitCast(@intFromEnum(data));
162178 }
......@@ -164,10 +180,10 @@ pub fn EncodedData(comptime Tag: type, backing_type: type) type {
164180 pub fn encode(comptime tag: Tag, data: tag.Data()) @This() {
165181 const Data = tag.Data();
166182 const info = @typeInfo(Data);
167 if (info == .Enum) {
183 if (info == .@"enum") {
168184 return @enumFromInt(@intFromEnum(data));
169185 }
170 if (info == .Struct) {
186 if (info == .@"struct") {
171187 comptime assert(backing_type == u64);
172188 comptime assert(@sizeOf(Data) == 8);
173189 var fields: [2]u32 = undefined;
......@@ -190,12 +206,12 @@ pub fn extraSizeOf(comptime T: type) usize {
190206
191207 else => {
192208 const info = @typeInfo(T);
193 if (info == .Enum) {
194 comptime assert(@sizeOf(info.Enum.tag_type) <= 32);
209 if (info == .@"enum") {
210 comptime assert(@sizeOf(info.@"enum".tag_type) <= 32);
195211 return 1;
196212 }
197213
198 const struct_data = info.Struct;
214 const struct_data = info.@"struct";
199215 if (struct_data.layout == .@"packed") {
200216 comptime assert(struct_data.backing_integer == u32);
201217 return 1;
......@@ -216,13 +232,14 @@ pub fn ExtraStore(comptime set_kind: StoreKind, comptime set_mutability: Mutabil
216232 return struct {
217233 list: List = .{},
218234
235 pub const empty: @This() = .{};
236
219237 pub const kind = set_kind;
220238 pub const mutability = set_mutability;
221239
222240 const Store = @This();
223241
224 const index_holder = ExtraWrap(kind);
225 pub const Index = index_holder.Index;
242 pub const Index = ExtraWrap(kind).Index;
226243
227244 pub const List = switch (mutability) {
228245 .@"const" => struct {
......@@ -301,7 +318,7 @@ pub fn ExtraStore(comptime set_kind: StoreKind, comptime set_mutability: Mutabil
301318 pub fn writeSlice(w: *TrailWriter, slice: anytype) void {
302319 const Child = std.meta.Child(@TypeOf(slice));
303320 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));
305322 assert(w.leftover >= slice.len);
306323 w.store.list.appendSliceAssumeCapacity(@ptrCast(slice));
307324 w.leftover -= slice.len;
......@@ -433,6 +450,8 @@ pub const Float = struct {
433450 }
434451};
435452
453const core = @import("mod.zig");
454
436455const std = @import("std");
437456const assert = std.debug.assert;
438457const Allocator = std.mem.Allocator;
src/core/js/Js.zig+1-2
......@@ -24,7 +24,7 @@ pub fn handleFunctionAir(
2424 backend: *Js,
2525 comp: *Comp,
2626 value: Value,
27 air: *const Air,
27 air: Air,
2828 arena: Allocator,
2929) !u32 {
3030 var ng = try NodeGen.init(comp.gpa, arena, &comp.intern_pool, air);
......@@ -55,7 +55,6 @@ const Js = @This();
5555
5656const std = @import("std");
5757const assert = std.debug.assert;
58const log = std.log.scoped(.js);
5958const Allocator = std.mem.Allocator;
6059
6160const core = @import("../mod.zig");
src/core/js/Link.zig+32-16
......@@ -10,7 +10,7 @@ backend: *Js,
1010
1111/// Value is an index into `link_strings`
1212decls: std.AutoHashMapUnmanaged(InternPool.Value, LinkString.Index) = .{},
13link_strings: std.ArrayHashMapUnmanaged(
13decl_strings: std.ArrayHashMapUnmanaged(
1414 LinkString,
1515 GlobalOwner,
1616 LinkString.Context,
......@@ -26,7 +26,6 @@ pub fn linkJsCode(link: *Link, entry_decl: Decl.Index, writer: Writer) !void {
2626 // names. We also need this list prepared so that local identifier names do not
2727 // shadow functions or externals.
2828 const root_decl = link.comp.intern_pool.declPtrConst(entry_decl);
29 assert(root_decl.name == .empty);
3029
3130 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 {
3938 try dependency_queue.list.appendSlice(link.comp.gpa, root_function.top_level_value_deps);
4039
4140 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
4247 const tree = link.treeForFunction(value);
4348
4449 for (tree.extern_symbols) |extern_symbol| {
......@@ -55,24 +60,24 @@ pub fn linkJsCode(link: *Link, entry_decl: Decl.Index, writer: Writer) !void {
5560 const name = decl.name.toSlice(&link.comp.intern_pool);
5661 assert(name.len > 0);
5762
58 const gop = try link.link_strings.getOrPutContextAdapted(
63 const gop_string = try link.decl_strings.getOrPutContextAdapted(
5964 link.comp.gpa,
6065 name,
6166 LinkString.SliceContext{ .link = link },
6267 .{ .link = link },
6368 );
64 if (gop.found_existing) {
65 @panic("TODO: handle this name collision case");
69 if (gop_string.found_existing) {
70 std.debug.panic("TODO: handle this name collision case: {s}", .{name});
6671 }
6772 const name_str = try link.appendLinkString(name);
68 gop.key_ptr.* = name_str;
69 gop.value_ptr.* = .decl;
70 try link.decls.putNoClobber(link.comp.gpa, value, @enumFromInt(gop.index));
73 gop_string.key_ptr.* = name_str;
74 gop_string.value_ptr.* = .decl;
75 gop_decl.value_ptr.* = @enumFromInt(gop_string.index);
7176 }
7277
7378 try writer.writeAll(std.fmt.comptimePrint(
7479 "// Language Compiler v{}. Alpha quality software.\n",
75 .{core.version},
80 .{@import("config").version},
7681 ));
7782
7883 if (root_function.nodes.len > 1) {
......@@ -91,7 +96,8 @@ pub fn linkJsCode(link: *Link, entry_decl: Decl.Index, writer: Writer) !void {
9196
9297fn treeForFunction(link: *const Link, value: InternPool.Value) *Tree {
9398 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
95101}
96102
97103const DependencyQueue = struct {
......@@ -106,6 +112,10 @@ const DependencyQueue = struct {
106112 defer queue.i += 1;
107113 return queue.list.items[queue.i];
108114 }
115
116 pub fn swapRemovePrevious(queue: *DependencyQueue) void {
117 _ = queue.list.swapRemove(queue.i - 1);
118 }
109119};
110120
111121pub fn deinit(link: *Link) void {
......@@ -114,7 +124,7 @@ pub fn deinit(link: *Link) void {
114124
115125 defer link.* = undefined;
116126 link.decls.deinit(link.comp.gpa);
117 link.link_strings.deinit(link.comp.gpa);
127 link.decl_strings.deinit(link.comp.gpa);
118128 link.string_bytes.deinit(link.comp.gpa);
119129}
120130
......@@ -123,7 +133,7 @@ fn reserveExternSymbol(link: *Link, extern_symbol: String.WithLen) !void {
123133 defer t.end();
124134
125135 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(
127137 link.comp.gpa,
128138 name,
129139 LinkString.SliceContext{ .link = link },
......@@ -152,7 +162,7 @@ fn valueRefIdentifier(link: *const Link, value: InternPool.Value) []const u8 {
152162 var t = tracer.trace(@src());
153163 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) {
156166 std.debug.panic("Value \"{}\" is not present in top level value map", .{
157167 value.dump(&link.comp.intern_pool),
158168 });
......@@ -305,6 +315,13 @@ fn printValue(link: *Link, value: InternPool.Value, level: Level, writer: anytyp
305315 try writer.writeAll("]");
306316 },
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
308325 else => unreachable, // this value is not a plain_value
309326 }
310327}
......@@ -470,7 +487,7 @@ fn printExprLevel(
470487
471488 for (args.slice(tree), 0..) |arg, i| {
472489 if (i != 0) try writer.writeAll(", ");
473 try link.printExpr(tree, arg, writer);
490 try link.printExprLevel(tree, arg, .assignment, writer);
474491 }
475492
476493 try writer.writeAll(")");
......@@ -806,7 +823,7 @@ const LinkString = struct {
806823 _,
807824
808825 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)];
810827 }
811828 };
812829
......@@ -843,7 +860,6 @@ const Link = @This();
843860const std = @import("std");
844861const assert = std.debug.assert;
845862const Wyhash = std.hash.Wyhash;
846const log = std.log.scoped(.js_link);
847863const Writer = std.io.AnyWriter;
848864
849865const core = @import("../mod.zig");
src/core/js/NodeGen.zig+81-52
......@@ -1,7 +1,7 @@
11//! Air -> Js.Tree
22gpa: Allocator,
33arena: Allocator,
4air: *const Air,
4air: Air,
55ip: *InternPool,
66
77nodes: Node.List,
......@@ -28,12 +28,12 @@ const MapEntry = union {
2828 },
2929};
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 {
3232 const t = tracer.trace(@src());
3333 defer t.end();
3434
3535 const air_node_map = try arena.alloc(MapEntry, air.insts.len);
36 if (std.debug.runtime_safety) {
36 if (core.runtime_safety) {
3737 @memset(air_node_map, .{ .none = {} });
3838 }
3939 const param_count = air.paramCount();
......@@ -99,8 +99,8 @@ pub fn build(c: *NodeGen) !void {
9999 const t = tracer.trace(@src());
100100 defer t.end();
101101
102 assert(c.air.instTag(.main_block) == .block);
103 const statements = c.air.instData(.block, .main_block).slice(c.air);
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);
104104
105105 const scratch_top = c.scratch.items.len;
106106 defer c.scratch.shrinkRetainingCapacity(scratch_top);
......@@ -109,11 +109,9 @@ pub fn build(c: *NodeGen) !void {
109109
110110 if (statements.len > 0) {
111111 for (statements[0..c.param_count], 1..) |stmt, expected_id| {
112 assert(c.air.instTag(stmt) == .arg); // expected Air to start with param nodes
112113 assert(@intFromEnum(stmt) == expected_id);
113 assert(c.air.instTag(stmt) == .arg);
114 c.air_node_map[@intFromEnum(stmt)] = .{
115 .node = try c.add(.local, .{ .id = @intFromEnum(stmt) - 1 }),
116 };
114 c.air_node_map[@intFromEnum(stmt)] = .{ .node = try c.add(.local, .{ .id = @intFromEnum(stmt) - 1 }) };
117115 }
118116
119117 for (statements[c.param_count..]) |stmt| {
......@@ -124,7 +122,7 @@ pub fn build(c: *NodeGen) !void {
124122 const items = c.scratch.items[scratch_top..];
125123 const top = c.extra.list.items.len;
126124 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, .{
128126 .start = @intCast(top),
129127 .len = @intCast(items.len),
130128 });
......@@ -139,13 +137,13 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void {
139137
140138 .block => try c.codeGenBlock(inst),
141139
142 .js_global_ref => {
143 const data: InternPool.String.WithLen = c.air.instData(.js_global_ref, inst);
144 try c.extern_symbols.put(c.gpa, data, {});
145 c.air_node_map[@intFromEnum(inst)] = .{
146 .node = try c.add(.global, .{ .name = data }),
147 };
148 },
140 // .js_global_ref => {
141 // const data: InternPool.String.WithLen = c.air.instData(.js_global_ref, inst);
142 // try c.extern_symbols.put(c.gpa, data, {});
143 // c.air_node_map[@intFromEnum(inst)] = .{
144 // .node = try c.add(.global, .{ .name = data }),
145 // };
146 // },
149147
150148 .memory => {
151149 const i = @intFromEnum(inst);
......@@ -171,7 +169,6 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void {
171169 .sub,
172170 .mul,
173171 .div,
174 .index,
175172 => |tag| {
176173 const data: Air.Bin = c.air.instData(tag, inst);
177174 const node = try c.add(@field(Tree.Tag, @tagName(tag)), .{
......@@ -181,6 +178,15 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void {
181178 try c.markExpression(inst, node);
182179 },
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
184190 .store => {
185191 const data: Air.Store = c.air.instData(.store, inst);
186192
......@@ -253,10 +259,21 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void {
253259 },
254260
255261 .field_opaque => {
256 const data: Air.FieldOpaque = c.air.instData(.field_opaque, inst);
257 const node = try c.add(.field, .{
258 .base = try c.fetchExpression(data.base),
259 .field = data.field,
262 const data: Air.Bin = c.air.instData(.field_opaque, inst);
263 if (data.rhs.value()) |field_value| {
264 if (field_value.typeOf(c.ip) == .string) {
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),
260277 });
261278 try c.markExpression(inst, node);
262279 },
......@@ -288,7 +305,7 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void {
288305
289306 inline .call, .js_new => |tag| {
290307 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
293310 try c.extra.list.ensureUnusedCapacity(c.gpa, call_data.args.len);
294311 const start = c.extra.list.items.len;
......@@ -296,7 +313,7 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void {
296313
297314 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| {
300317 dest.* = @intFromEnum(try c.fetchExpression(arg));
301318 }
302319
......@@ -312,7 +329,7 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void {
312329
313330 .@"if" => {
314331 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);
316333 var cond = try c.fetchExpression(data.cond);
317334
318335 if (c.canLowerToExpression(extra.yes) and
......@@ -380,17 +397,31 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void {
380397 .@"break" => {
381398 const brk: Air.Break = c.air.instData(.@"break", inst);
382399
383 const operand = try c.fetchExpression(brk.value);
384
385400 // break #0 -> return
386 if (brk.block_ref == .main_block) {
387 try c.addStmt(try c.add(.stmt_return, .{
388 .operand = operand.toOptional(),
389 }));
401 if (brk.block == .main_block) {
402 if (brk.value.isVoid()) {
403 // If this is a void break at the end of the main block, nothing
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 }
390420 } 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);
394425 if (block_insts[block_insts.len - 1] != inst) {
395426 block.used = true;
396427 }
......@@ -418,7 +449,7 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void {
418449
419450 for (
420451 struct_type.field_keys,
421 struct_init.valuesPtr(c.air),
452 struct_init.valuesPtr(&c.air),
422453 store_offset..,
423454 store_offset + property_count..,
424455 ) |key, value, key_i, value_i| {
......@@ -438,7 +469,7 @@ fn codeGenInst(c: *NodeGen, inst: Air.Inst.Index) Error!void {
438469 .array_init => {
439470 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);
442473 const store_offset = try c.extra.alloc(c.gpa, items.len);
443474
444475 for (
......@@ -500,15 +531,15 @@ fn fetchInstExpression(c: *NodeGen, index: Air.Inst.Index) !Node.Index {
500531 defer t.end();
501532
502533 const data = c.air_node_map[@intFromEnum(index)];
503 if (std.debug.runtime_safety) {
534 if (core.runtime_safety) {
504535 const flags: Air.Flags = c.air.instFlags(index);
505536 switch (flags.uses) {
506 .none => if (std.debug.runtime_safety) {
537 .none => {
507538 // incorrectly marked instruction as unused
508539 std.debug.panic("called fetchInstExpression(#{d}), but no expression was generated", .{
509540 @intFromEnum(index),
510541 });
511 } else unreachable,
542 },
512543 .single => {},
513544 .multi => {
514545 // multi-references can only duplicate identifier nodes.
......@@ -543,6 +574,7 @@ fn valueToExpression(c: *NodeGen, v: Value) !Node.Index {
543574 .simple_value,
544575 .struct_value,
545576 .array_value,
577 .external_value,
546578 => return try c.add(.plain_value, .{ .value = v }),
547579
548580 // Hoist functions as top level values
......@@ -609,7 +641,7 @@ fn codeGenBranch(c: *NodeGen, inst: Air.Inst.Ref, temporary: ?Node.Index) !?Bran
609641 });
610642
611643 const block: Air.IndexList = c.air.instData(.block, i);
612 for (block.slice(c.air)) |item| {
644 for (block.slice(&c.air)) |item| {
613645 try c.codeGenInst(item);
614646 }
615647
......@@ -661,7 +693,7 @@ fn codeGenBlock(c: *NodeGen, inst: Air.Inst.Index) !void {
661693 };
662694 const block: Air.IndexList = c.air.instData(.block, inst);
663695
664 for (block.slice(c.air)) |item| {
696 for (block.slice(&c.air)) |item| {
665697 try c.codeGenInst(item);
666698 }
667699
......@@ -689,12 +721,12 @@ fn canLowerToExpression(c: *const NodeGen, inst: Air.Inst.Ref) bool {
689721 return switch (inst.decode()) {
690722 .value => true,
691723 .index => |index| switch (c.air.instTag(index)) {
692 .js_global_ref,
693724 .call,
694725 .js_new,
695726 .arg,
696727 .field_opaque,
697728 .field_struct,
729 .field_array,
698730 .add,
699731 .sub,
700732 .mul,
......@@ -707,7 +739,6 @@ fn canLowerToExpression(c: *const NodeGen, inst: Air.Inst.Ref) bool {
707739 .store,
708740 .struct_init,
709741 .array_init,
710 .index,
711742 .array_len,
712743 => true,
713744
......@@ -728,7 +759,7 @@ fn canLowerToExpression(c: *const NodeGen, inst: Air.Inst.Ref) bool {
728759
729760 .@"if" => {
730761 const call = c.air.instData(.@"if", index);
731 const data = call.data.get(c.air);
762 const data = call.data.get(&c.air);
732763
733764 return c.canLowerToExpression(call.cond) and
734765 c.canLowerToExpression(data.yes) and
......@@ -758,7 +789,7 @@ fn add(c: *NodeGen, comptime tag: Tree.Tag, data: tag.Data()) !Node.Index {
758789
759790 try c.nodes.append(c.gpa, .{
760791 .tag = tag,
761 .data = EncodedData.encode(tag, data),
792 .data = .encode(tag, data),
762793 });
763794 return @enumFromInt(c.nodes.len - 1);
764795}
......@@ -776,19 +807,17 @@ const std = @import("std");
776807const assert = std.debug.assert;
777808const Allocator = std.mem.Allocator;
778809
779const InternPool = @import("../InternPool.zig");
810const core = @import("../mod.zig");
811const InternPool = core.InternPool;
780812const Value = InternPool.Value;
781813const NullTerminatedString = InternPool.NullTerminatedString;
782
783const Comp = @import("../Comp.zig");
814const Comp = core.Comp;
784815const Decl = Comp.Decl;
785
786const Tree = @import("Tree.zig");
816const Tree = core.Js.Tree;
787817const EncodedData = Tree.EncodedData;
788818const Node = Tree.Node;
789819const LocalIndex = Tree.LocalIndex;
820const Air = core.Air;
821const dod = core.array;
790822
791const Air = @import("../Air.zig");
792
793const dod = @import("../array.zig");
794823const tracer = @import("tracer");
src/core/js/Tree.zig+3-3
......@@ -10,8 +10,8 @@ top_level_value_deps: []Value,
1010extra: ExtraStore,
1111
1212pub const LocalIndex = u32;
13pub const ExtraStore = extra.ExtraStore(.js_tree, .@"const");
14pub const EncodedData = extra.EncodedData(Tag, u64);
13pub const ExtraStore = array.ExtraStore(.js_tree, .@"const");
14pub const EncodedData = array.EncodedData(Tag, u64);
1515pub const ExtraIndex = ExtraStore.Index;
1616
1717pub const Tag = enum {
......@@ -299,6 +299,6 @@ const String = InternPool.String;
299299const Comp = @import("../Comp.zig");
300300const Decl = Comp.Decl;
301301
302const extra = @import("../array.zig");
302const array = @import("../array.zig");
303303
304304const Unused = @import("../Ast.zig").Unused;
src/core/mod.zig+61-34
......@@ -2,57 +2,35 @@
22
33// sort-lines: start
44
5/// Analyzed Intermediate Representation.
65pub const Air = @import("Air.zig");
7
86pub const array = @import("array.zig");
9
10/// Abstract Syntax Tree - Preserves all syntax forms.
117pub const Ast = @import("Ast.zig");
12
138pub const Cache = @import("Cache.zig");
14
15/// Language Compilation Unit
169pub const Comp = @import("Comp.zig");
17
18/// Storage for shared objects (`Value`/`Decl`/`Namespace`) in a compilation
1910pub const InternPool = @import("InternPool.zig");
20
21/// JavaScript backend and related utilities
2211pub const Js = @import("js/Js.zig");
23
24/// Recursive descent parser. Produces `Ast`
2512pub const Parse = @import("Parse.zig");
26
2713pub const Report = @import("Report.zig");
28
29/// Semantic Analysis visits a `Teg` declaration and produces `Air`
3014pub const Sema = @import("Sema.zig");
31
3215pub const Stack = @import("stack.zig").Stack;
3316pub const U32Stack = Stack(u32);
34
3517pub const StoredTrace = @import("StoredTrace.zig");
3618pub const DebugStoredTrace = StoredTrace.DebugStoredTrace;
37
38/// Untyped Intermediate Representation. Flat instruction list
19pub const Queue = @import("queue.zig").Queue;
3920pub const Teg = @import("Teg.zig");
40
41/// Iterate tokens from a `[:0]const u8`. Includes code to decode/encode string escapes.
4221pub const Tokenizer = @import("Tokenizer.zig");
43
44/// Transform pass converts `Ast` into `Teg`
4522pub const Transform = @import("Transform.zig");
46
4723pub const unicode_props = @import("unicode_props.zig");
4824
4925// sort-lines: end
5026
51// The rest of this file is utility functions, which are not specific to any of
52// the above namespaces. Remember Zig discourages namespaces like `utils`
27// The rest of this file is small utility functions, which are not specific to
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`
55pub const version = @import("build_options").version;
30pub const runtime_safety = switch (@import("builtin").mode) {
31 .Debug, .ReleaseSafe => true,
32 .ReleaseFast, .ReleaseSmall => false,
33};
5634
5735/// This is an alternative to `std.debug.assert`, where you want to print some
5836/// debug info instead of "reached unreachable code". Usage is as follows:
......@@ -64,7 +42,7 @@ pub const version = @import("build_options").version;
6442/// This allows you to perform any number of side effects in the failure case
6543/// with certainty that they are not analyzed in unsafe modes.
6644pub inline fn assertFailed(cond: bool) bool {
67 if (std.debug.runtime_safety) {
45 if (runtime_safety) {
6846 return !cond;
6947 } else if (!cond) {
7048 unreachable; // assertion failure
......@@ -74,13 +52,13 @@ pub inline fn assertFailed(cond: bool) bool {
7452/// If `value` can be cast to `i32`
7553pub fn inInt32Range(value: anytype) bool {
7654 return switch (@typeInfo(@TypeOf(value))) {
77 .Int,
78 .ComptimeInt,
55 .int,
56 .comptime_int,
7957 => value >= std.math.minInt(i32) and
8058 value <= std.math.maxInt(i32),
8159
82 .Float,
83 .ComptimeFloat,
60 .float,
61 .comptime_float,
8462 => std.math.isFinite(value) and
8563 value >= std.math.minInt(i32) and
8664 value <= std.math.maxInt(i32) and
......@@ -134,6 +112,16 @@ pub const OutStream = struct {
134112 };
135113 }
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
137125 pub fn print(out: OutStream, comptime fmt: []const u8, args: anytype) Error!void {
138126 out.writer.print(fmt, args) catch |e| switch (e) {
139127 error.OutOfMemory => return Error.OutOfMemory,
......@@ -149,9 +137,48 @@ pub const OutStream = struct {
149137 }
150138};
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
152164const std = @import("std");
153165const assert = std.debug.assert;
154166
155167comptime {
156168 std.testing.refAllDecls(@This());
157169}
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 {
99 return struct {
1010 const Self = @This();
1111
12 list: std.ArrayListUnmanaged(Base) = .{},
12 list: std.ArrayListUnmanaged(Base),
13
14 pub const empty: Self = .{ .list = .empty };
1315
1416 pub const Mark = struct {
1517 stack: *Self,
1618 index: usize,
1719
1820 pub fn deinit(m: Mark) void {
19 if (std.debug.runtime_safety)
21 if (core.runtime_safety)
2022 @memset(m.slice(Base), undefined);
2123 m.stack.list.shrinkRetainingCapacity(m.index);
2224 }
......@@ -106,7 +108,7 @@ pub fn Stack(comptime Base: type) type {
106108 return @field(types, @tagName(field));
107109 }
108110
109 pub fn slice(r: @This(), comptime field: FieldEnum) []Type(field) {
111 pub fn items(r: @This(), comptime field: FieldEnum) []Type(field) {
110112 const f = comptime std.meta.fieldIndex(Types, @tagName(field)).?;
111113 return @ptrCast(r.stack.list.items[r.starts[f]..][0..r.lengths[f]]);
112114 }
......@@ -154,3 +156,5 @@ pub fn Stack(comptime Base: type) type {
154156const std = @import("std");
155157const assert = std.debug.assert;
156158const 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 {
2323 if (allocation_size == 0) {
2424 var logging_alloc = std.heap.loggingAllocator(std.heap.page_allocator);
2525 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", .{});
2727 std.process.exit(1);
2828 }
2929
src/deps/quickjs/build.zig+1-1
......@@ -146,7 +146,7 @@ pub fn build(b: *Build) !void {
146146 .optimize = optimize,
147147 .link_libc = true,
148148 });
149 const module2 = translate_libc.addModule("quickjs_libc");
149 const module2 = translate_libc.addModule("quickjs-libc");
150150 if (pthread.pthreads) |pt| translate_libc.addIncludeDir(pt.include.getPath(b));
151151 module2.linkLibrary(libquickjs);
152152 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 {
252252 std.posix.PollError ||
253253 error{
254254 SchemeNotSupported,
255 /// `stat` lied
255 /// The system `stat` has lied or otherwise file corruption
256256 UnexpectedEndOfFile,
257257 /// non-200 status codes are errors
258258 HttpStatus,
......@@ -1032,9 +1032,7 @@ fn tickDownload(
10321032
10331033fn writeToCache(cache: std.fs.Dir, data: Module.Data) !void {
10341034 var rng: [16]u8 = undefined;
1035 std.posix.getrandom(&rng) catch {
1036 rng = std.mem.asBytes(&std.time.nanoTimestamp()).*;
1037 };
1035 std.crypto.random.bytes(&rng);
10381036
10391037 // TODO: i do not trust the atomicy of this on windows
10401038 // 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;
1212const assert = std.debug.assert;
1313
1414comptime {
15 assert(std.debug.runtime_safety);
15 assert(core.runtime_safety);
1616}
1717
1818pub fn main() !void {
src/wasm/enum_codegen.zig+10-11
......@@ -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
71pub fn main() !void {
82 var buf = std.io.bufferedWriter(std.io.getStdOut().writer());
93 defer buf.flush() catch {};
......@@ -12,17 +6,22 @@ pub fn main() !void {
126 inline for (.{
137 .{ "TokenTag", Tokenizer.Token.Tag },
148 .{ "NodeTag", Ast.Node.Tag },
15 .{ "TegInstTag", Teg.Inst.Tag },
9 .{ "TegInstTag", Teg.Tag },
1610 .{ "TegInstRef", Teg.Inst.Ref },
17 // .{ "InternPoolIndex", InternPool.Index },
1811 }) |decl| {
1912 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});
2215 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) });
2417 }
25 try w.writeAll("};\n");
18 try w.writeAll("}\n");
2619 try w.writeAll("\n");
2720 }
2821}
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(
1010 source_bytes: [*]const u8,
1111 source_len: u32,
1212) 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 };
1616 tokenizer.skipWhitespace();
17 while (tokenizer.next() catch {
18 return @intCast(tokenizer.pos);
19 }) |token| {
17 while (tokenizer.next()) |token| {
2018 iterateTokenCallback(@intFromEnum(token.tag), token.offset);
2119 tokenizer.skipWhitespace();
2220 }
......@@ -25,8 +23,9 @@ export fn iterateTokens(
2523}
2624
2725const std = @import("std");
28const wasm_allocator = std.heap.wasm_allocator;
29const Tokenizer = @import("Tokenizer.zig");
26const core = @import("core");
27const Tokenizer = core.Tokenizer;
3028const Token = Tokenizer.Token;
3129const 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 @@
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
119fn f(a: float) -> float { a }
220
321fn addFloat(a: float, b: float) -> float {
......@@ -50,18 +68,3 @@ fn subInt(a: int, b: int) -> int {
5068// a % b
5169// }
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 @@
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));
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));
14fn i(a: int) -> int { a }
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