From 83328b1abfbacdb0e756bfa55250d5702aa145f6 Mon Sep 17 00:00:00 2001 From: fubark Date: Sun, 24 Sep 2023 09:22:09 -0400 Subject: [PATCH] Support `@host` vars. --- .github/workflows/latest-build.yml | 26 +-- build.zig | 29 +++- docs/hugo/content/docs/toc/control-flow.md | 7 +- docs/hugo/content/docs/toc/data-types.md | 4 +- docs/hugo/content/docs/toc/functions.md | 2 + docs/hugo/content/docs/toc/modules.md | 3 +- examples/c-embedded/bind_module.c | 131 ++++++++++------ src/api.zig | 4 - src/builtins/builtins.cy | 1 - src/builtins/builtins.zig | 8 - src/builtins/math.cy | 33 ++++ src/builtins/math.zig | 71 +++------ src/chunk.zig | 11 +- src/cli.zig | 7 +- src/codegen.zig | 33 ++-- src/cyber.zig | 34 ++-- src/include/cyber.h | 79 ++++++++-- src/lib.zig | 61 ++++++-- src/log_wasm.zig | 19 +-- src/module.zig | 73 +++++++-- src/parser.zig | 174 ++++++++++++--------- src/sema.zig | 130 ++++++++++++--- src/std/os.cy | 8 + src/std/os.zig | 83 +++++----- src/value.zig | 2 + src/vm_compiler.zig | 38 ++++- 26 files changed, 721 insertions(+), 350 deletions(-) diff --git a/.github/workflows/latest-build.yml b/.github/workflows/latest-build.yml index 05a663c42..5b04adb08 100644 --- a/.github/workflows/latest-build.yml +++ b/.github/workflows/latest-build.yml @@ -238,22 +238,22 @@ jobs: - name: Move. (windows lib) if: env.BUILD_TARGET == 'x86_64-windows-gnu' && env.BUILD_CMD == 'lib' run: | - mv zig-out/lib/cyber.dll cyber.dll + mv zig-out/lib/cyber.lib cyber-windows-x64.lib - name: Move. (linux lib) if: env.BUILD_TARGET == 'x86_64-linux-gnu' && env.BUILD_CMD == 'lib' run: | - mv zig-out/lib/libcyber.so libcyber.so + mv zig-out/lib/libcyber.a libcyber-linux-x64.a - name: Move. (macos lib) if: env.BUILD_TARGET == 'x86_64-macos-none' && env.BUILD_CMD == 'lib' run: | - mv zig-out/lib/libcyber.dylib libcyber.dylib + mv zig-out/lib/libcyber.a libcyber-macos-x64.a - name: Move. (macos lib) if: env.BUILD_TARGET == 'aarch64-macos-none' && env.BUILD_CMD == 'lib' run: | - mv zig-out/lib/libcyber.dylib libcyber-arm64.dylib + mv zig-out/lib/libcyber.a libcyber-macos-arm64.a - name: Move. (wasm) if: env.BUILD_TARGET == 'wasm32-freestanding' @@ -293,6 +293,7 @@ jobs: with: name: bin + # Manual dispatch. - name: Github Latest Release. uses: marvinpinto/action-automatic-releases@latest if: ${{ !startsWith(github.ref, 'refs/tags/') }} @@ -307,12 +308,13 @@ jobs: cyber-macos-x64.tar.gz cyber-macos-arm64.tar.gz cyber-windows-x64.zip - cyber.dll - libcyber.so - libcyber.dylib - libcyber-arm64.dylib + cyber-windows-x64.lib + libcyber-linux-x64.a + libcyber-macos-x64.a + libcyber-macos-arm64.a cyber.wasm + # Auto dispatch. - name: Github Release. uses: softprops/action-gh-release@v1 # Releases for tags only. @@ -324,8 +326,8 @@ jobs: cyber-macos-x64.tar.gz cyber-macos-arm64.tar.gz cyber-windows-x64.zip - cyber.dll - libcyber.so - libcyber.dylib - libcyber-arm64.dylib + cyber-windows-x64.lib + libcyber-linux-x64.a + libcyber-macos-x64.a + libcyber-macos-arm64.a cyber.wasm diff --git a/build.zig b/build.zig index ec5255c17..8260b6e7a 100644 --- a/build.zig +++ b/build.zig @@ -13,6 +13,7 @@ var vmEngine: config.Engine = undefined; var testFilter: ?[]const u8 = undefined; var trace: bool = undefined; var optFFI: ?bool = undefined; +var optStatic: ?bool = undefined; var stdx: *std.build.Module = undefined; var tcc: *std.build.Module = undefined; @@ -27,6 +28,7 @@ pub fn build(b: *std.build.Builder) !void { vmEngine = b.option(config.Engine, "vm", "Build with `zig` or `c` VM.") orelse .c; optMalloc = b.option(config.Allocator, "malloc", "Override default allocator: `malloc`, `mimalloc`, `zig`"); optFFI = b.option(bool, "ffi", "Override default FFI: true, false"); + optStatic = b.option(bool, "static", "Override default lib build type: true=static, false=dynamic"); trace = b.option(bool, "trace", "Enable tracing features.") orelse (optimize == .Debug); stdx = b.createModule(.{ @@ -86,12 +88,22 @@ pub fn build(b: *std.build.Builder) !void { opts.malloc = .malloc; opts.applyOverrides(); - const lib = b.addSharedLibrary(.{ - .name = "cyber", - .root_source_file = .{ .path = "src/lib.zig" }, - .target = target, - .optimize = optimize, - }); + var lib: *std.build.Step.Compile = undefined; + if (opts.static) { + lib = b.addStaticLibrary(.{ + .name = "cyber", + .root_source_file = .{ .path = "src/lib.zig" }, + .target = target, + .optimize = optimize, + }); + } else { + lib = b.addSharedLibrary(.{ + .name = "cyber", + .root_source_file = .{ .path = "src/lib.zig" }, + .target = target, + .optimize = optimize, + }); + } if (lib.optimize != .Debug) { lib.strip = true; } @@ -294,6 +306,7 @@ pub const Options = struct { target: std.zig.CrossTarget, optimize: std.builtin.OptimizeMode, malloc: config.Allocator, + static: bool, gc: bool, ffi: bool, @@ -304,6 +317,9 @@ pub const Options = struct { if (optFFI) |ffi| { self.ffi = ffi; } + if (optStatic) |static| { + self.static = static; + } } }; @@ -329,6 +345,7 @@ fn getDefaultOptions(target: std.zig.CrossTarget, optimize: std.builtin.Optimize .optimize = optimize, .gc = true, .malloc = malloc, + .static = !target.getCpuArch().isWasm(), .ffi = !target.getCpuArch().isWasm(), }; } diff --git a/docs/hugo/content/docs/toc/control-flow.md b/docs/hugo/content/docs/toc/control-flow.md index ebc744de6..65c62f29c 100644 --- a/docs/hugo/content/docs/toc/control-flow.md +++ b/docs/hugo/content/docs/toc/control-flow.md @@ -52,7 +52,8 @@ while iter.next() some entry: print entry.name ``` -`for` loops can iterate over a range that starts at a `int` (inclusive) to a target `int` (exclusive). When the range operator `..` is replaced with `..=`, the target `int` is inclusive. The range can be given a custom step. +`for` loops can iterate over a range that starts at a `int` (inclusive) to a target `int` (exclusive). +The range can be given a custom step. ```cy for 0..100 each i: print i -- 0, 1, 2, ... , 99 @@ -63,6 +64,10 @@ for 0..100, 10 each i: for 100..0, 1 each i: print i -- 100, 99, 98, ... , 1 +``` +When the range operator `..` is replaced with `..=`, the target `int` is inclusive. +> _Planned Feature_ +```cy for 100..=0, 1 each i: print i -- 100, 99, 98, ... , 0 ``` diff --git a/docs/hugo/content/docs/toc/data-types.md b/docs/hugo/content/docs/toc/data-types.md index 60e33988d..bf99e4571 100644 --- a/docs/hugo/content/docs/toc/data-types.md +++ b/docs/hugo/content/docs/toc/data-types.md @@ -73,7 +73,9 @@ var b = float(a) ## Strings. The `string` type represents a sequence of UTF-8 codepoints, also known as `runes`. Each rune is stored internally as 1-4 bytes and can be represented as an `int`. Under the hood, Cyber implements 6 different internal string types to optimize string operations, but the user just sees them as one type and doesn't need to care about this detail under normal usage. -Strings are **immutable**, so operations that do string manipulation return a new string. By default, small strings are interned to reduce memory footprint. To mutate an existing string, use the [StringBuffer](#string-buffer). +Strings are **immutable**, so operations that do string manipulation return a new string. By default, small strings are interned to reduce memory footprint. + +To mutate an existing string, use the [StringBuffer](#string-buffer). > _Planned Feature_ A string is always UTF-8 validated. [rawstrings](#rawstring) outperform strings but you'll have to validate them and take care of indexing yourself. diff --git a/docs/hugo/content/docs/toc/functions.md b/docs/hugo/content/docs/toc/functions.md index ca159e02d..c51950e76 100644 --- a/docs/hugo/content/docs/toc/functions.md +++ b/docs/hugo/content/docs/toc/functions.md @@ -144,6 +144,7 @@ You can call functions with named parameters. var d = dist(x0: 10, x1: 20, y0: 30, y1: 40) ``` +### Shorthand syntax. The shorthand method for calling functions omits parentheses and commas. This only works for functions that accept parameters: > _Incomplete: Only the most trivial cases work with the shorthand method. The case with operators being separated by spaces might not end up being implemented._ ```cy @@ -177,6 +178,7 @@ var a = myFunc 'hello' (1 + 2 * 3) a = myFunc 'hello' (otherFunc 1+2 'world') ``` +### Call block syntax. The call expression block continues to add arguments from the block's body. If arguments are omitted from the initial call expression they can be added inside using the `..` syntax. Arguments mapped to named parameters have a key value syntax separated by a `:`. All other arguments are added into a list and passed as the last argument. > _Planned Feature_ ```cy diff --git a/docs/hugo/content/docs/toc/modules.md b/docs/hugo/content/docs/toc/modules.md index d74a22199..cdf94675e 100644 --- a/docs/hugo/content/docs/toc/modules.md +++ b/docs/hugo/content/docs/toc/modules.md @@ -112,8 +112,7 @@ print id | `parseCyon(src any) any` | Parses a CYON string into a value. | | `performGC() map` | Runs the garbage collector once to detect reference cycles and abandoned objects. Returns the statistics of the run in a map value. | | `pointer(val any) pointer` | Converts a `int` to a `pointer` value, or casts to a `pointer`. This is usually used with FFI. | -| `print(s string) none` | Prints a value as a string to stdout. The new line is also printed. | -| `prints(s string) none` | Prints a value as a string to stdout. | +| `print(s string) none` | Prints a value. The host determines how it is printed. | | `rawstring(str string) rawstring` | Converts a string to a `rawstring`. | | `runestr(val int) string` | Converts a rune to a string. | | `string(val any) string` | Converts a value to a string. | diff --git a/examples/c-embedded/bind_module.c b/examples/c-embedded/bind_module.c index afe71a788..917933b2d 100644 --- a/examples/c-embedded/bind_module.c +++ b/examples/c-embedded/bind_module.c @@ -2,70 +2,103 @@ #include #include "cyber.h" +// This example shows how to setup a module loader for custom modules. +// Declarations from a module's source code invokes callbacks to bind with the host's functions and types. +// To see how to inject symbols programmatically, see `inject_module.c`. + // Compile this program with a C compiler. `zig cc` is used here as an example. -// zig cc bind_module.c -I src -lcyber -L -o main.exe +// zig cc bind_module.c -I ../../src/include ../../zig-out/lib/libcyber.a -o main.exe -bool loadCore(CyVM* vm, CyModule* mod); -bool loadMyMod(CyVM* vm, CyModule* mod); -CyValue print(CyVM* vm, CyValue* args, uint8_t nargs); -CyValue add(CyVM* vm, CyValue* args, uint8_t nargs); +// Convenience macros to deal with Cyber string slices. +#define STR(s) ((CsStr){ s, strlen(s) }) +#define PRINTS(s) (printf("%.*s\n", (int)s.len, s.buf)) -int main() { - CyVM* vm = cyVmCreate(); +CsValue add(CsVM* vm, const CsValue* args, uint8_t nargs) { + double res = csAsFloat(args[0]) + csAsFloat(args[1]); + return csFloat(res); +} +struct { char* n; CsHostFuncFn fn; } funcs[] = { + {"add", add}, +}; - // Add syms to the default module (Loaded into each script's namespace). - cyVmAddModuleLoader(vm, cstr("core"), loadCore); +CsHostFuncFn funcLoader(CsVM* vm, CsHostFuncInfo funcInfo) { + // Check that the name matches before setting the function pointer. + if (strncmp(funcs[funcInfo.idx].n, funcInfo.name.buf, funcInfo.name.len) == 0) { + return funcs[funcInfo.idx].fn; + } else { + return NULL; + } +} - // Add syms to a custom builtin named "mymod". - cyVmAddModuleLoader(vm, cstr("mymod"), loadMyMod); +// C has limited static initializers (and objects need a vm instance) so initialize them in `main`. +typedef struct { char* n; CsValue v; } NameValue; +NameValue vars[2]; - CStr src = cstr( - "import mod 'mymod'\n" - "\n" - "a = 2\n" - "print add(a, 100)\n" - "print mod.add(a, a)\n" - "print mod.hello" - ); - CyValue val; - int res = cyVmEval(vm, src, &val); - if (res == CY_Success) { - printf("Success!\n"); - cyVmRelease(vm, val); +bool varLoader(CsVM* vm, CsHostVarInfo varInfo, CsValue* out) { + // Check that the name matches before setting the value. + if (strncmp(vars[varInfo.idx].n, varInfo.name.buf, varInfo.name.len) == 0) { + // Objects are consumed by the module. + *out = vars[varInfo.idx].v; + return true; } else { - CStr err = cyVmGetLastErrorReport(vm); - printf("%s\n", err.charz); + return NULL; } - cyVmDestroy(vm); - return 0; } -bool loadMyMod(CyVM* vm, CyModule* mod) { - cyVmSetModuleFunc(vm, mod, cstr("add"), 2, add); +bool modLoader(CsVM* vm, CsStr spec, CsModuleLoaderResult* out) { + if (strncmp("my_engine", spec.buf, spec.len) == 0) { + out->src = STR( + "@host func add(a float, b float) float\n" + "@host var MyConstant\n" + "@host var MyList\n" + ); + out->srcIsStatic = true; + out->funcLoader = funcLoader; + out->varLoader = varLoader; + return true; + } else { + // Fallback to the default module loader to load `builtins`. + return csDefaultModuleLoader(vm, spec, out); + } +} - CyValue str = cyValueGetOrAllocStringInfer(vm, cstr("hello world")); - cyVmSetModuleVar(vm, mod, cstr("hello"), str); - return true; +void print(CsVM* vm, CsStr str) { + printf("MY_VM: %.*s\n", (int)str.len, str.buf); } -bool loadCore(CyVM* vm, CyModule* mod) { - cyVmSetModuleFunc(vm, mod, cstr("add"), 2, add); +int main() { + // csVerbose = true; + CsVM* vm = csCreate(); + csSetModuleLoader(vm, modLoader); + csSetPrint(vm, print); - // Override core.print. - cyVmSetModuleFunc(vm, mod, cstr("print"), 1, print); + // Initialize var array for loader. + vars[0] = (NameValue){"MyConstant", csFloat(1.23)}; + vars[1] = (NameValue){"MyList", csNewList(vm)}; - return true; -} + CsStr main = STR( + "import mod 'my_engine'\n" + "\n" + "var a = 1.0\n" + "print mod.add(a, 2)\n" + "print(-mod.MyConstant)\n" + "mod.MyList.append(3)\n" + "print mod.MyList.len()\n" + ); + CsValue resv; + CsResultCode res = csEval(vm, main, &resv); + if (res == CS_SUCCESS) { + printf("Success!\n"); + } else { + CsStr err = csAllocLastErrorReport(vm); + PRINTS(err); + csFreeStr(vm, err); + } -CyValue add(CyVM* vm, CyValue* args, uint8_t nargs) { - double left = cyValueAsNumber(args[0]); - double right = cyValueAsNumber(args[1]); - return cyValueNumber(left + right); -} + // Check that all references were accounted for. (Should be 0) + csDeinit(vm); + printf("Global RC %zu\n", csGetGlobalRC(vm)); + csDestroy(vm); -CyValue print(CyVM* vm, CyValue* args, uint8_t nargs) { - CStr str = cyValueToTempRawString(vm, args[0]); - printf("Overrided print: %s\n", str.charz); - cyVmRelease(vm, args[0]); - return cyValueNone(); + return 0; } \ No newline at end of file diff --git a/src/api.zig b/src/api.zig index 959b43ddc..5feefb99c 100644 --- a/src/api.zig +++ b/src/api.zig @@ -153,10 +153,6 @@ pub const UserVM = struct { return cy.arc.getGlobalRC(@ptrCast(self)); } - pub inline fn checkMemory(self: *UserVM) !bool { - return cy.arc.checkMemory(self.internal()); - } - pub inline fn validate(self: *UserVM, srcUri: []const u8, src: []const u8) !cy.ValidateResult { return self.internal().validate(srcUri, src, .{ .enableFileModules = true }); } diff --git a/src/builtins/builtins.cy b/src/builtins/builtins.cy index 1edc94da1..b9f69452b 100644 --- a/src/builtins/builtins.cy +++ b/src/builtins/builtins.cy @@ -13,7 +13,6 @@ @host func parseCyon(src any) any @host func performGC() Map @host func print(str any) none -@host func prints(str any) none @host func runestr(val int) string @host func toCyon(val any) string @host func typeid(val any) int diff --git a/src/builtins/builtins.zig b/src/builtins/builtins.zig index 93e942a16..fbf53f0ef 100644 --- a/src/builtins/builtins.zig +++ b/src/builtins/builtins.zig @@ -46,7 +46,6 @@ const funcs = [_]NameHostFunc{ .{"parseCyon", parseCyon}, .{"performGC", performGC}, .{"print", print}, - .{"prints", prints}, .{"runestr", runestr}, .{"toCyon", toCyon}, .{"typeid", typeid}, @@ -432,13 +431,6 @@ pub fn performGC(vm: *cy.UserVM, _: [*]const Value, _: u8) linksection(cy.StdSec } pub fn print(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { - const str = vm.valueToTempRawString(args[0]); - vm.internal().print(vm, cy.Str.initSlice(str)); - vm.internal().print(vm, cy.Str.initSlice("\n")); - return Value.None; -} - -pub fn prints(vm: *cy.UserVM, args: [*]const Value, _: u8) linksection(cy.StdSection) Value { const str = vm.valueToTempRawString(args[0]); vm.internal().print(vm, cy.Str.initSlice(str)); return Value.None; diff --git a/src/builtins/math.cy b/src/builtins/math.cy index 419d28c9b..f9da12733 100644 --- a/src/builtins/math.cy +++ b/src/builtins/math.cy @@ -1,3 +1,36 @@ +-- Euler's number and the base of natural logarithms; approximately 2.718. +@host var e float + +-- Infinity. +@host var inf float + +-- Base-10 logarithm of E; approximately 0.434. +@host var log10e float + +-- Base-2 logarithm of E; approximately 1.443. +@host var log2e float + +-- Natural logarithm of 10; approximately 2.303. +@host var ln10 float + +-- Natural logarithm of 2; approximately 0.693. +@host var ln2 float + +-- Not a number. +@host var nan float + +-- Neg infinity. +@host var neginf float + +-- Ratio of a circle's circumference to its diameter; approximately 3.14159. +@host var pi float + +-- Square root of ½; approximately 0.707. +@host var sqrt1_2 float + +-- Square root of 2; approximately 1.414. +@host var sqrt2 float + @host func abs(a float) float @host func acos(a float) float @host func acosh(a float) float diff --git a/src/builtins/math.zig b/src/builtins/math.zig index a8024fc8d..ade07ea5a 100644 --- a/src/builtins/math.zig +++ b/src/builtins/math.zig @@ -5,15 +5,37 @@ const Value = cy.Value; const bt = cy.types.BuiltinTypeSymIds; pub const Src = @embedFile("math.cy"); -pub fn defaultFuncLoader(_: *cy.UserVM, func: cy.HostFuncInfo) callconv(.C) vmc.HostFuncFn { +pub fn funcLoader(_: *cy.UserVM, func: cy.HostFuncInfo) callconv(.C) vmc.HostFuncFn { if (std.mem.eql(u8, funcs[func.idx].@"0", func.name.slice())) { return @ptrCast(funcs[func.idx].@"1"); } return null; } +pub fn varLoader(_: *cy.UserVM, v: cy.HostVarInfo, out: *cy.Value) callconv(.C) bool { + if (std.mem.eql(u8, vars[v.idx].@"0", v.name.slice())) { + out.* = vars[v.idx].@"1"; + return true; + } + return false; +} + +const NameVar = struct { []const u8, cy.Value }; +const vars = [_]NameVar{ + .{"e", Value.initF64(std.math.e)}, + .{"inf", Value.initF64(std.math.inf(f64))}, + .{"log10e", Value.initF64(std.math.log10e)}, + .{"log2e", Value.initF64(std.math.log2e)}, + .{"ln10", Value.initF64(std.math.ln10)}, + .{"ln2", Value.initF64(std.math.ln2)}, + .{"nan", Value.initF64(std.math.nan_f64)}, + .{"neginf", Value.initF64(-std.math.inf(f64))}, + .{"pi", Value.initF64(std.math.pi)}, + .{"sqrt1_2", Value.initF64(std.math.sqrt1_2)}, + .{"sqrt2", Value.initF64(std.math.sqrt2)}, +}; -const NameHostFunc = struct { []const u8, cy.ZHostFuncFn }; -const funcs = [_]NameHostFunc{ +const NameFunc = struct { []const u8, cy.ZHostFuncFn }; +const funcs = [_]NameFunc{ .{"abs", abs}, .{"acos", acos}, .{"acosh", acosh}, @@ -52,49 +74,6 @@ const funcs = [_]NameHostFunc{ .{"trunc", trunc}, }; -pub fn postLoad(vm: *cy.UserVM, modId: cy.ModuleId) callconv(.C) void { - zPostLoad(vm.internal().compiler, modId) catch |err| { - cy.panicFmt("math module: {}", .{err}); - }; -} - -fn zPostLoad(c: *cy.VMcompiler, modId: cy.ModuleId) linksection(cy.InitSection) anyerror!void { - const b = cy.bindings.ModuleBuilder.init(c, modId); - - // Euler's number and the base of natural logarithms; approximately 2.718. - try b.setVar("e", bt.Float, Value.initF64(std.math.e)); - - // Infinity. - try b.setVar("inf", bt.Float, Value.initF64(std.math.inf(f64))); - - // Base-10 logarithm of E; approximately 0.434. - try b.setVar("log10e", bt.Float, Value.initF64(std.math.log10e)); - - // Base-2 logarithm of E; approximately 1.443. - try b.setVar("log2e", bt.Float, Value.initF64(std.math.log2e)); - - // Natural logarithm of 10; approximately 2.303. - try b.setVar("ln10", bt.Float, Value.initF64(std.math.ln10)); - - // Natural logarithm of 2; approximately 0.693. - try b.setVar("ln2", bt.Float, Value.initF64(std.math.ln2)); - - // Not a number. - try b.setVar("nan", bt.Float, Value.initF64(-std.math.nan_f64)); - - // Neg infinity. - try b.setVar("neginf", bt.Float, Value.initF64(-std.math.inf(f64))); - - // Ratio of a circle's circumference to its diameter; approximately 3.14159. - try b.setVar("pi", bt.Float, Value.initF64(std.math.pi)); - - // Square root of ½; approximately 0.707. - try b.setVar("sqrt1_2", bt.Float, Value.initF64(std.math.sqrt1_2)); - - // Square root of 2; approximately 1.414. - try b.setVar("sqrt2", bt.Float, Value.initF64(std.math.sqrt2)); -} - /// Returns the absolute value of x. pub fn abs(_: *cy.UserVM, args: [*]const Value, _: u8) Value { return Value.initF64(@fabs(args[0].asF64())); diff --git a/src/chunk.zig b/src/chunk.zig index ffaddeb3b..a940f4289 100644 --- a/src/chunk.zig +++ b/src/chunk.zig @@ -117,14 +117,20 @@ pub const Chunk = struct { /// Its exported members will be populated in the Module as sema encounters them. modId: cy.ModuleId, - /// For binding hostfunc declarations. + /// For binding @host func declarations. funcLoader: ?cy.HostFuncLoaderFn = null, + /// For binding @host var declarations. + varLoader: ?cy.HostVarLoaderFn = null, + /// Run before declarations are loaded. + preLoad: ?cy.PreLoadModuleFn = null, /// Run after declarations have been loaded. postLoad: ?cy.PostLoadModuleFn = null, /// Run before chunk is destroyed. destroy: ?cy.ModuleDestroyFn = null, - /// Counter for loading hostfuncs. + /// Counter for loading @host funcs. curHostFuncIdx: u32, + /// Counter for loading @host vars. + curHostVarIdx: u32, pub fn init(c: *cy.VMcompiler, id: ChunkId, srcUri: []const u8, src: []const u8) !Chunk { var new = Chunk{ @@ -172,6 +178,7 @@ pub const Chunk = struct { .localSyms = .{}, .rega = cy.register.Allocator.init(c, id), .curHostFuncIdx = 0, + .curHostVarIdx = 0, .usingModules = .{}, // .funcCandidateStack = .{}, }; diff --git a/src/cli.zig b/src/cli.zig index 69bc3d8a4..883bc07d9 100644 --- a/src/cli.zig +++ b/src/cli.zig @@ -17,9 +17,10 @@ const builtins = std.ComptimeStringMap(void, .{ const stdMods = std.ComptimeStringMap(cy.ModuleLoaderResult, .{ .{"os", cy.ModuleLoaderResult{ .src = cy.Str.initSlice(os_mod.Src), .srcIsStatic = true, - .funcLoader = os_mod.defaultFuncLoader, + .varLoader = os_mod.varLoader, + .funcLoader = os_mod.funcLoader, + .preLoad = os_mod.preLoad, .postLoad = os_mod.postLoad, - .destroy = os_mod.destroy, }}, .{"test", cy.ModuleLoaderResult{ .src = cy.Str.initSlice(test_mod.Src), .srcIsStatic = true, @@ -39,9 +40,11 @@ pub fn setupVMForCLI(vm: *cy.UserVM) void { fn print(_: *cy.UserVM, str: cy.Str) callconv(.C) void { if (cy.isWasmFreestanding) { os_mod.hostFileWrite(1, str.buf, str.len); + os_mod.hostFileWrite(1, "\n", 1); } else { const w = std.io.getStdOut().writer(); w.writeAll(str.slice()) catch cy.fatal(); + w.writeByte('\n') catch cy.fatal(); } } diff --git a/src/codegen.zig b/src/codegen.zig index 3b23af2ee..0c9457ee5 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -2767,24 +2767,27 @@ pub fn genStaticInitializerDFS(self: *Chunk, csymId: sema.CompactSymbolId) anyer const chunk = &self.compiler.chunks.items[rSym.inner.variable.chunkId]; const decl = chunk.nodes[declId]; - // Generate deps first. - try genStaticInitializerDeps(chunk, csymId); + // Only generate for user vars. + if (decl.node_t == .staticDecl) { + // Generate deps first. + try genStaticInitializerDeps(chunk, csymId); - log.debug("gen static var init: {s}", .{sema.getName(self.compiler, rSym.key.resolvedSymKey.nameId)}); + log.debug("gen static var init: {s}", .{sema.getName(self.compiler, rSym.key.resolvedSymKey.nameId)}); - // Clear register state. - chunk.rega.resetNextTemp(); + // Clear register state. + chunk.rega.resetNextTemp(); - try self.unwindTempIndexStack.append(self.alloc, @bitCast(@as(u32, cy.NullId))); - defer self.popUnwindTempIndex(); + try self.unwindTempIndexStack.append(self.alloc, @bitCast(@as(u32, cy.NullId))); + defer self.popUnwindTempIndex(); - const exprv = try expression(chunk, decl.head.staticDecl.right, RegisterCstr.tempMustRetain); + const exprv = try expression(chunk, decl.head.staticDecl.right, RegisterCstr.tempMustRetain); - const rtSymId = try self.compiler.vm.ensureVarSym(rSym.key.resolvedSymKey.parentSymId, rSym.key.resolvedSymKey.nameId); + const rtSymId = try self.compiler.vm.ensureVarSym(rSym.key.resolvedSymKey.parentSymId, rSym.key.resolvedSymKey.nameId); - const pc = self.buf.len(); - try self.buf.pushOp3(.setStaticVar, 0, 0, exprv.local); - self.buf.setOpArgU16(pc + 1, @intCast(rtSymId)); + const pc = self.buf.len(); + try self.buf.pushOp3(.setStaticVar, 0, 0, exprv.local); + self.buf.setOpArgU16(pc + 1, @intCast(rtSymId)); + } } else { cy.panicFmt("Unsupported sym {}", .{rSym.symT}); } @@ -2920,7 +2923,11 @@ fn expression(c: *Chunk, nodeId: cy.NodeId, cstr: RegisterCstr) anyerror!GenValu return c.initGenValue(dst, childT, false); }, .generic => { - return callObjSymUnaryExpr(c, c.compiler.vm.@"prefix~MGID", node.head.unary.child, cstr, nodeId); + if (op == .minus) { + return callObjSymUnaryExpr(c, c.compiler.vm.@"prefix-MGID", node.head.unary.child, cstr, nodeId); + } else { + return callObjSymUnaryExpr(c, c.compiler.vm.@"prefix~MGID", node.head.unary.child, cstr, nodeId); + } } } }, diff --git a/src/cyber.zig b/src/cyber.zig index d00b87e66..14777f8c2 100644 --- a/src/cyber.zig +++ b/src/cyber.zig @@ -142,6 +142,10 @@ pub const InitSection = if (builtin.os.tag == .macos) "__TEXT,.cyInit" else ".cy /// Whether to print verbose logs. pub export var verbose = false; +comptime { + @export(verbose, .{ .name = "csVerbose", .linkage = .Strong }); +} + /// Compile errors are not printed. pub var silentError = false; @@ -165,6 +169,7 @@ pub const Malloc = build_options.malloc; const std = @import("std"); pub const NullId = std.math.maxInt(u32); pub const NullU8 = std.math.maxInt(u8); +pub const NullU16 = std.math.maxInt(u16); /// Document that a id type can contain NullId. pub fn Nullable(comptime T: type) type { @@ -176,7 +181,7 @@ pub const ZHostFuncFn = *const fn (*UserVM, [*]const Value, u8) Value; pub const ZHostFuncCFn = *const fn (*UserVM, [*]const Value, u8) callconv(.C) Value; pub const ZHostFuncPairFn = *const fn (*UserVM, [*]const Value, u8) ValuePair; -/// Overlap with `include/cyber.h`. +/// Overlap with `include/cyber.h` for Cyber types. pub const Str = extern struct { buf: [*]const u8, len: usize, @@ -192,24 +197,33 @@ pub const Str = extern struct { return self.buf[0..self.len]; } }; -pub const HostFuncInfo = extern struct { - modId: vmc.ModuleId, - name: Str, - funcSigId: vmc.FuncSigId, - idx: u32, -}; +pub const PreLoadModuleFn = *const fn (*UserVM, modId: vmc.ModuleId) callconv(.C) void; +pub const PostLoadModuleFn = *const fn (*UserVM, modId: vmc.ModuleId) callconv(.C) void; +pub const ModuleDestroyFn = *const fn (*UserVM, modId: vmc.ModuleId) callconv(.C) void; +pub const ModuleResolverFn = *const fn (*UserVM, ChunkId, curUri: Str, spec: Str, outUri: *Str) callconv(.C) bool; pub const ModuleLoaderResult = extern struct { src: Str, srcIsStatic: bool, funcLoader: ?HostFuncLoaderFn = null, + varLoader: ?HostVarLoaderFn = null, + preLoad: ?PreLoadModuleFn = null, postLoad: ?PostLoadModuleFn = null, destroy: ?ModuleDestroyFn = null, }; -pub const ModuleResolverFn = *const fn (*UserVM, ChunkId, curUri: Str, spec: Str, outUri: *Str) callconv(.C) bool; pub const ModuleLoaderFn = *const fn (*UserVM, Str, out: *ModuleLoaderResult) callconv(.C) bool; +pub const HostFuncInfo = extern struct { + modId: vmc.ModuleId, + name: Str, + funcSigId: vmc.FuncSigId, + idx: u32, +}; pub const HostFuncLoaderFn = *const fn (*UserVM, HostFuncInfo) callconv(.C) vmc.HostFuncFn; -pub const PostLoadModuleFn = *const fn (*UserVM, modId: vmc.ModuleId) callconv(.C) void; -pub const ModuleDestroyFn = *const fn (*UserVM, modId: vmc.ModuleId) callconv(.C) void; +pub const HostVarInfo = extern struct { + modId: vmc.ModuleId, + name: Str, + idx: u32, +}; +pub const HostVarLoaderFn = *const fn (*UserVM, HostVarInfo, *Value) callconv(.C) bool; pub const PrintFn = *const fn (*UserVM, str: Str) callconv(.C) void; pub const cli = @import("cli.zig"); diff --git a/src/include/cyber.h b/src/include/cyber.h index b8d460dca..617f633de 100644 --- a/src/include/cyber.h +++ b/src/include/cyber.h @@ -72,7 +72,7 @@ CsStr csGetBuild(); CsStr csGetCommit(); // @host func is binded to this function pointer signature. -typedef CsValue (*CsHostFuncFn)(CsVM* vm, CsValue* args, uint8_t nargs); +typedef CsValue (*CsHostFuncFn)(CsVM* vm, const CsValue* args, uint8_t nargs); // Given the current module's resolved URI and the "to be" imported module specifier, // write the resolved specifier in `outUri` and return true, otherwise return false. @@ -80,13 +80,16 @@ typedef CsValue (*CsHostFuncFn)(CsVM* vm, CsValue* args, uint8_t nargs); // simply returns `spec` without any adjustments. typedef bool (*CsModuleResolverFn)(CsVM* vm, uint32_t chunkId, CsStr curUri, CsStr spec, CsStr* outUri); -// Callback invoked after all symbols in module's src are loaded. -// This would be a convenient time to inject symbols not declared in the module's src. +// Callback invoked before all symbols in the module's src are loaded. +// This could be used to set up an array or hashmap for binding @host vars. +typedef void (*CsPreLoadModuleFn)(CsVM* vm, uint32_t modId); + +// Callback invoked after all symbols in the module's src are loaded. +// This could be used to inject symbols not declared in the module's src. typedef void (*CsPostLoadModuleFn)(CsVM* vm, uint32_t modId); // Callback invoked just before the module is destroyed. -// When you have explicity injected symbols into a module from `CsPostLoadModuleFn`, -// this can be a convenient time to do cleanup logic (eg. release objects). +// This could be used to cleanup (eg. release) injected symbols from `CsPostLoadModuleFn`, typedef void (*CsModuleDestroyFn)(CsVM* vm, uint32_t modId); // Info about a @host func. @@ -105,6 +108,22 @@ typedef struct CsHostFuncInfo { // Given info about a @host func, return it's function pointer or null. typedef CsHostFuncFn (*CsHostFuncLoaderFn)(CsVM* vm, CsHostFuncInfo funcInfo); +// Info about a @host var. +typedef struct CsHostVarInfo { + // The module it belongs to. + uint32_t modId; + // The name of the var. + CsStr name; + // A counter that tracks it's current position among all @host vars in the module. + // This is useful if you want to bind an array of `CsValue`s to @host vars. + uint32_t idx; +} CsHostVarInfo; + +// Given info about a @host var, write a value to `out` and return true, otherwise return false. +// The value is consumed by the module. If the value should outlive the module, +// call `csRetain` before handing it over. +typedef bool (*CsHostVarLoaderFn)(CsVM* vm, CsHostVarInfo funcInfo, CsValue* out); + // Module loader config. typedef struct CsModuleLoaderResult { // The Cyber source code for the module. @@ -114,6 +133,10 @@ typedef struct CsModuleLoaderResult { // Pointer to callback or null. CsHostFuncLoaderFn funcLoader; // Pointer to callback or null. + CsHostVarLoaderFn varLoader; + // Pointer to callback or null. + CsPreLoadModuleFn preLoad; + // Pointer to callback or null. CsPostLoadModuleFn postLoad; // Pointer to callback or null. CsModuleDestroyFn destroy; @@ -138,35 +161,63 @@ typedef struct CsGCResult { // // [ VM ] // + CsVM* csCreate(); +// Deinitialize the VM. Afterwards, call `csDestroy` or perform a check on `csGetGlobalRC`. +void csDeinit(CsVM* vm); +// Deinitializes the VM and frees it. Any operation on `vm` afterwards is undefined. void csDestroy(CsVM* vm); + CsModuleResolverFn csGetModuleResolver(CsVM* vm); void csSetModuleResolver(CsVM* vm, CsModuleResolverFn resolver); + +// The default module resolver. It returns `spec`. +bool csDefaultModuleResolver(CsVM* vm, uint32_t chunkId, CsStr curUri, CsStr spec, CsStr* outUri); + CsModuleLoaderFn csGetModuleLoader(CsVM* vm); void csSetModuleLoader(CsVM* vm, CsModuleLoaderFn loader); + +// The default module loader. It knows how to load the `builtins` module. +bool csDefaultModuleLoader(CsVM* vm, CsStr resolvedSpec, CsModuleLoaderResult* out); + CsPrintFn csGetPrint(CsVM* vm); void csSetPrint(CsVM* vm, CsPrintFn print); + +// Evalutes the source code and returns the result code. +// If the last statement of the script is an expression, `outVal` will contain the value. CsResultCode csEval(CsVM* vm, CsStr src, CsValue* outVal); CsResultCode csValidate(CsVM* vm, CsStr src); -CsStr csGetLastErrorReport(CsVM* vm); + +/// After receiving an error CsResultCode, this returns the error report. Call `csFreeStr` afterwards. +CsStr csAllocLastErrorReport(CsVM* vm); + +// Attach a userdata pointer inside the VM. void* csGetUserData(CsVM* vm); void csSetUserData(CsVM* vm, void* userData); -// Memory. -void csRelease(CsVM* vm, CsValue val); -void csRetain(CsVM* vm, CsValue val); -CsGCResult csPerformGC(CsVM* vm); +// Verbose flag. In a debug build, this would print more logs. +extern bool csVerbose; // Modules. void csSetModuleFunc(CsVM* vm, CsModuleId modId, CsStr name, uint32_t numParams, CsHostFuncFn func); void csSetModuleVar(CsVM* vm, CsModuleId modId, CsStr name, CsValue val); +// Memory. +void csRelease(CsVM* vm, CsValue val); +void csRetain(CsVM* vm, CsValue val); +// Deinitialize the VM. Afterwards, you may do `csDestroy` or perform some checks on `csGetGlobalRC`. +CsGCResult csPerformGC(CsVM* vm); +// Get's the current global reference count. This will panic if the lib was not built with `TrackGlobalRC`. +// Use this to see if all objects were cleaned up after `csDeinit`. +size_t csGetGlobalRC(CsVM* vm); + // For embedded, Cyber by default uses malloc (it can be configured to use the high-perf mimalloc). // If the host uses a different allocator than Cyber, use `csAlloc` to allocate memory // that is handed over to Cyber so it knows how to free it. // This is also used to manage accessible buffers when embedding WASM. void* csAlloc(CsVM* vm, size_t size); void csFree(CsVM* vm, void* ptr, size_t len); +void csFreeStr(CsVM* vm, CsStr str); // // [ Values ] @@ -176,8 +227,12 @@ void csFree(CsVM* vm, void* ptr, size_t len); CsValue csNone(); CsValue csTrue(); CsValue csFalse(); -CsValue csFloat(double n); -CsValue csInteger(int n); +CsValue csBool(bool b); + +// int64_t is downcasted to a 48-bit int. +CsValue csInteger(int64_t n); +CsValue csInteger32(int32_t n); +CsValue csFloat(double f); CsValue csTagLiteral(CsVM* vm, CsStr str); CsValue csNewString(CsVM* vm, CsStr str); CsValue csNewAstring(CsVM* vm, CsStr str); diff --git a/src/lib.zig b/src/lib.zig index c742f7b23..540e72aae 100644 --- a/src/lib.zig +++ b/src/lib.zig @@ -19,17 +19,22 @@ export fn csCreate() *cy.UserVM { const alloc = cy.heap.getAllocator(); const vm = alloc.create(cy.VM) catch fatal(); vm.init(alloc) catch fatal(); - if (cy.isWasm) { - cy.log.wasm.init(alloc); - } return @ptrCast(vm); } -export fn csDestroy(vm: *cy.UserVM) void { - vm.deinit(); - if (cy.isWasm) { - cy.log.wasm.deinit(); - } +export fn csDeinit(uvm: *cy.UserVM) void { + uvm.deinit(); +} + +export fn csDestroy(uvm: *cy.UserVM) void { + uvm.deinit(); + const alloc = cy.heap.getAllocator(); + const vm = uvm.internal(); + alloc.destroy(vm); +} + +export fn csGetGlobalRC(vm: *cy.UserVM) usize { + return cy.arc.getGlobalRC(vm.internal()); } /// This is useful when calling into wasm to allocate some memory. @@ -42,6 +47,10 @@ export fn csFree(vm: *cy.UserVM, ptr: [*]const align(8) u8, size: usize) void { vm.allocator().free(ptr[0..size]); } +export fn csFreeStr(vm: *cy.UserVM, str: cy.Str) void { + vm.allocator().free(str.slice()); +} + export fn csEval(vm: *cy.UserVM, src: cy.Str, outVal: *cy.Value) c.CsResultCode { outVal.* = vm.eval("main", src.slice(), .{ .singleRun = false, @@ -104,11 +113,19 @@ test "csValidate()" { var tempBuf: [1024]u8 align(8) = undefined; -export fn csGetLastErrorReport(vm: *cy.UserVM) cy.Str { +export fn csAllocLastErrorReport(vm: *cy.UserVM) cy.Str { const report = vm.allocLastErrorReport() catch fatal(); return cy.Str.initSlice(report); } +export fn csGetModuleResolver(vm: *cy.UserVM) c.CsModuleResolverFn { + return @ptrCast(vm.getModuleResolver()); +} + +export fn csSetModuleResolver(vm: *cy.UserVM, resolver: c.CsModuleResolverFn) void { + vm.setModuleResolver(@ptrCast(resolver)); +} + export fn csGetModuleLoader(vm: *cy.UserVM) c.CsModuleLoaderFn { return @ptrCast(vm.getModuleLoader()); } @@ -117,6 +134,22 @@ export fn csSetModuleLoader(vm: *cy.UserVM, loader: c.CsModuleLoaderFn) void { vm.setModuleLoader(@ptrCast(loader)); } +export fn csGetPrint(vm: *cy.UserVM) c.CsPrintFn { + return @ptrCast(vm.getPrint()); +} + +export fn csSetPrint(vm: *cy.UserVM, print: c.CsPrintFn) void { + vm.setPrint(@ptrCast(print)); +} + +export fn csPerformGC(vm: *cy.UserVM) c.CsGCResult { + const res = cy.arc.performGC(vm.internal()) catch cy.fatal(); + return .{ + .numCycFreed = res.numCycFreed, + .numObjFreed = res.numObjFreed, + }; +} + export fn csSetModuleFunc(vm: *cy.UserVM, modId: cy.ModuleId, cname: cy.Str, numParams: u32, func: c.CsHostFuncFn) void { const symName = cname.slice(); const mod = vm.internal().compiler.sema.getModulePtr(modId); @@ -157,11 +190,19 @@ export fn csFalse() Value { return Value.False; } +export fn csBool(b: bool) Value { + return Value.initBool(b); +} + export fn csFloat(n: f64) Value { return Value.initF64(n); } -export fn csInteger(n: i32) Value { +export fn csInteger(n: i64) Value { + return Value.initInt(@intCast(n)); +} + +export fn csInteger32(n: i32) Value { return Value.initInt(n); } diff --git a/src/log_wasm.zig b/src/log_wasm.zig index b748a39e7..6ec1e60e6 100644 --- a/src/log_wasm.zig +++ b/src/log_wasm.zig @@ -13,19 +13,6 @@ const DebugLog = builtin.mode == .Debug and true; /// This allows small logs to still work without an allocator. var small_buf: [1024]u8 = undefined; -/// Larger logs will use a growing heap allocation. -var large_buf_inner: std.ArrayList(u8) = undefined; -var large_buf: *std.ArrayList(u8) = undefined; - -pub fn init(alloc: std.mem.Allocator) void { - large_buf_inner = std.ArrayList(u8).init(alloc); - large_buf = &large_buf_inner; -} - -pub fn deinit() void { - large_buf.deinit(); -} - pub fn scoped(comptime Scope: @Type(.EnumLiteral)) type { return struct { pub fn debug( @@ -71,10 +58,8 @@ pub fn scoped(comptime Scope: @Type(.EnumLiteral)) type { if (len <= small_buf.len) { return std.fmt.bufPrint(&small_buf, prefix ++ format, args) catch @panic("error"); } else { - large_buf.clearRetainingCapacity(); - const writer = large_buf.writer(); - std.fmt.format(writer, prefix ++ format, args) catch @panic("error"); - return large_buf.items[0..len]; + // TODO: Instead of building buffer, write to wasm. + @panic("log too big"); } } }; diff --git a/src/module.zig b/src/module.zig index e19651813..74a698c53 100644 --- a/src/module.zig +++ b/src/module.zig @@ -18,6 +18,9 @@ pub const ModuleId = u32; pub const Module = struct { syms: std.HashMapUnmanaged(ModuleSymKey, ModuleSym, cy.hash.KeyU64Context, 80), + /// Tracks which binded vars are retained. + retainedVars: std.ArrayListUnmanaged(sema.NameSymId), + id: ModuleId, /// Chunk that contains the module declaration. `NullId` if this module is a builtin. @@ -221,14 +224,37 @@ pub const Module = struct { } } - pub fn setUserVar(self: *Module, c: *cy.VMcompiler, name: []const u8, declId: cy.NodeId) !void { + pub fn setHostVar(self: *Module, c: *cy.VMcompiler, name: []const u8, declId: cy.NodeId, value: cy.Value) !void { const nameId = try sema.ensureNameSym(c, name); - const key = ModuleSymKey{ - .moduleSymKey = .{ - .nameId = nameId, - .funcSigId = cy.NullId, + const key = ModuleSymKey.initModuleSymKey(nameId, null); + var retainIdx: u16 = cy.NullU16; + if (value.isPointer()) { + retainIdx = @intCast(self.retainedVars.items.len); + try self.retainedVars.append(c.alloc, nameId); + } + try self.syms.put(c.alloc, key, .{ + .symT = .hostVar, + .extraSmall = .{ + .hostVar = .{ + .retainedIdx = retainIdx, + } }, - }; + .extra = .{ + .hostVar = .{ + .declId = declId, + } + }, + .inner = .{ + .hostVar = .{ + .val = value, + }, + }, + }); + } + + pub fn setUserVar(self: *Module, c: *cy.VMcompiler, name: []const u8, declId: cy.NodeId) !void { + const nameId = try sema.ensureNameSym(c, name); + const key = ModuleSymKey.initModuleSymKey(nameId, null); try self.syms.put(c.alloc, key, .{ .symT = .userVar, .inner = .{ @@ -239,7 +265,19 @@ pub const Module = struct { }); } - pub fn deinit(self: *Module, alloc: std.mem.Allocator) void { + pub fn deinit(self: *Module, vm: *cy.VM, alloc: std.mem.Allocator) void { + for (self.retainedVars.items) |nameId| { + const key = ModuleSymKey.initModuleSymKey(nameId, null); + const sym = self.syms.get(key).?; + switch (sym.symT) { + .hostVar => { + cy.arc.release(vm, sym.inner.hostVar.val); + }, + else => {} + } + } + self.retainedVars.deinit(alloc); + var iter = self.syms.iterator(); while (iter.next()) |e| { const sym = e.value_ptr.*; @@ -279,6 +317,7 @@ const ModuleSymType = enum { symToManyFuncs, userVar, + hostVar, userFunc, object, enumType, @@ -289,10 +328,19 @@ const ModuleSymType = enum { const ModuleSym = struct { symT: ModuleSymType, - extra: union { - variable: struct { + extraSmall: extern union { + hostVar: extern struct { + // Index into `retainedVars`. + retainedIdx: u16, + }, + } = undefined, + extra: extern union { + variable: extern struct { rTypeSymId: sema.SymbolId, - } + }, + hostVar: extern struct { + declId: cy.NodeId, + }, } = undefined, inner: union { hostFunc: struct { @@ -310,6 +358,9 @@ const ModuleSym = struct { userVar: struct { declId: cy.NodeId, }, + hostVar: struct { + val: cy.Value, + }, typeAlias: extern struct { /// Type aliases are lazily loaded. declId: cy.NodeId, @@ -493,6 +544,7 @@ pub fn findDistinctModuleSym(chunk: *cy.Chunk, modId: ModuleId, nameId: sema.Nam if (mod.syms.get(relKey)) |modSym| { switch (modSym.symT) { .userVar, + .hostVar, .userFunc, .variable, .object, @@ -643,6 +695,7 @@ pub fn appendModule(c: *cy.VMcompiler, name: []const u8) !ModuleId { try c.sema.modules.append(c.alloc, .{ .id = id, .syms = .{}, + .retainedVars = .{}, .chunkId = cy.NullId, // Updated afterwards. .resolvedRootSymId = cy.NullId, diff --git a/src/parser.zig b/src/parser.zig index 27ba34ed2..d1c898e2a 100644 --- a/src/parser.zig +++ b/src/parser.zig @@ -1621,11 +1621,14 @@ pub const Parser = struct { }; self.advanceToken(); - if (self.peekToken().tag() != .func_k) { + if (self.peekToken().tag() == .func_k) { + return try self.parseFuncDecl(modifier); + } else if (self.peekToken().tag() == .var_k) { + return try self.parseVarDecl(modifier); + } else { return self.reportParseError("Expected func declaration.", &.{}); } - return try self.parseFuncDecl(modifier); } else { return self.reportParseError("Expected ident after @.", &.{}); } @@ -1706,77 +1709,7 @@ pub const Parser = struct { return try self.parseReturnStatement(); }, .var_k => { - const start = self.next_pos; - self.advanceToken(); - - // Static var name. - token = self.peekToken(); - var name: NodeId = undefined; - if (token.tag() == .ident) { - name = try self.pushIdentNode(self.next_pos); - self.advanceToken(); - } else return self.reportParseError("Expected local name identifier.", &.{}); - - const typeSpecHead = (try self.parseOptTypeSpec()) orelse cy.NullId; - - token = self.peekToken(); - var isStatic: bool = undefined; - if (token.tag() == .colon) { - isStatic = true; - } else if (token.tag() == .equal) { - isStatic = false; - } else { - return self.reportParseError("Expected `:` after local variable name.", &.{}); - } - self.advanceToken(); - - var right: NodeId = undefined; - switch (self.peekToken().tag()) { - .func_k => { - right = try self.parseMultilineLambdaFunction(); - }, - .match_k => { - right = try self.parseStatement(); - }, - else => { - right = (try self.parseExpr(.{})) orelse { - return self.reportParseError("Expected right expression for assignment statement.", &.{}); - }; - }, - } - const varSpec = try self.pushNode(.varSpec, start); - self.nodes.items[varSpec].head = .{ - .varSpec = .{ - .name = name, - .typeSpecHead = typeSpecHead, - }, - }; - - if (isStatic) { - const decl = try self.pushNode(.staticDecl, start); - self.nodes.items[decl].head = .{ - .staticDecl = .{ - .varSpec = varSpec, - .right = right, - }, - }; - try self.staticDecls.append(self.alloc, .{ - .declT = .variable, - .inner = .{ - .variable = decl, - } - }); - return decl; - } else { - const decl = try self.pushNode(.localDecl, start); - self.nodes.items[decl].head = .{ - .localDecl = .{ - .varSpec = varSpec, - .right = right, - }, - }; - return decl; - } + return try self.parseVarDecl(cy.NullId); }, else => {}, } @@ -2941,6 +2874,98 @@ pub const Parser = struct { return left_id; } + fn parseVarDecl(self: *Parser, modifierHead: cy.NodeId) !cy.NodeId { + const start = self.next_pos; + self.advanceToken(); + + // Var name. + var token = self.peekToken(); + var name: NodeId = undefined; + if (token.tag() == .ident) { + name = try self.pushIdentNode(self.next_pos); + self.advanceToken(); + } else return self.reportParseError("Expected local name identifier.", &.{}); + + const typeSpecHead = (try self.parseOptTypeSpec()) orelse cy.NullId; + const varSpec = try self.pushNode(.varSpec, start); + self.nodes.items[varSpec].head = .{ + .varSpec = .{ + .name = name, + .typeSpecHead = typeSpecHead, + .modifierHead = modifierHead, + }, + }; + + token = self.peekToken(); + var isStatic: bool = undefined; + if (token.tag() == .colon) { + isStatic = true; + } else if (token.tag() == .equal) { + isStatic = false; + } else if (token.tag() == .new_line or token.tag() == .none) { + const decl = try self.pushNode(.staticDecl, start); + self.nodes.items[decl].head = .{ + .staticDecl = .{ + .varSpec = varSpec, + .right = cy.NullId, + }, + }; + try self.staticDecls.append(self.alloc, .{ + .declT = .variable, + .inner = .{ + .variable = decl, + } + }); + return decl; + } else { + return self.reportParseError("Expected `:` after local variable name.", &.{}); + } + self.advanceToken(); + + // Continue parsing right expr. + + var right: NodeId = undefined; + switch (self.peekToken().tag()) { + .func_k => { + right = try self.parseMultilineLambdaFunction(); + }, + .match_k => { + right = try self.parseStatement(); + }, + else => { + right = (try self.parseExpr(.{})) orelse { + return self.reportParseError("Expected right expression for assignment statement.", &.{}); + }; + }, + } + + if (isStatic) { + const decl = try self.pushNode(.staticDecl, start); + self.nodes.items[decl].head = .{ + .staticDecl = .{ + .varSpec = varSpec, + .right = right, + }, + }; + try self.staticDecls.append(self.alloc, .{ + .declT = .variable, + .inner = .{ + .variable = decl, + } + }); + return decl; + } else { + const decl = try self.pushNode(.localDecl, start); + self.nodes.items[decl].head = .{ + .localDecl = .{ + .varSpec = varSpec, + .right = right, + }, + }; + return decl; + } + } + /// Assumes next token is the return token. fn parseReturnStatement(self: *Parser) !NodeId { const start = self.next_pos; @@ -3343,6 +3368,7 @@ pub const NodeType = enum { range_clause, eachClause, label_decl, + hostVarDecl, hostFuncDecl, funcDecl, funcDeclInit, @@ -3563,6 +3589,8 @@ pub const Node = struct { varSpec: struct { name: NodeId, typeSpecHead: Nullable(NodeId), + modifierHead: Nullable(NodeId), + // `next` contains TypeId for @host var }, staticDecl: struct { varSpec: NodeId, diff --git a/src/sema.zig b/src/sema.zig index 667ca14be..7ea9f245f 100644 --- a/src/sema.zig +++ b/src/sema.zig @@ -536,6 +536,7 @@ pub fn semaStmt(c: *cy.Chunk, nodeId: cy.NodeId) !void { .funcDecl => { try funcDecl(c, nodeId); }, + .hostVarDecl, .hostFuncDecl => { return; }, @@ -1040,18 +1041,22 @@ pub fn declareHostFunc(c: *cy.Chunk, nodeId: cy.NodeId) !void { .idx = c.curHostFuncIdx, }; c.curHostFuncIdx += 1; - if (c.funcLoader.?(@ptrCast(c.compiler.vm), info)) |funcPtr| { - cy.module.declareHostFunc(c.compiler, c.modId, name, func.funcSigId, declId, funcPtr) catch |err| { - if (err == error.DuplicateSymName) { - return c.reportErrorAt("The symbol `{}` already exists.", &.{v(name)}, nodeId); - } else if (err == error.DuplicateFuncSig) { - return c.reportErrorAt("The function `{}` with the same signature already exists.", &.{v(name)}, nodeId); - } else return err; - }; - const key = ResolvedSymKey.initResolvedSymKey(mod.resolvedRootSymId, nameId); - _ = try resolveHostFunc(c, key, func.funcSigId, funcPtr); + if (c.funcLoader) |funcLoader| { + if (funcLoader(@ptrCast(c.compiler.vm), info)) |funcPtr| { + cy.module.declareHostFunc(c.compiler, c.modId, name, func.funcSigId, declId, funcPtr) catch |err| { + if (err == error.DuplicateSymName) { + return c.reportErrorAt("The symbol `{}` already exists.", &.{v(name)}, nodeId); + } else if (err == error.DuplicateFuncSig) { + return c.reportErrorAt("The function `{}` with the same signature already exists.", &.{v(name)}, nodeId); + } else return err; + }; + const key = ResolvedSymKey.initResolvedSymKey(mod.resolvedRootSymId, nameId); + _ = try resolveHostFunc(c, key, func.funcSigId, funcPtr); + } else { + return c.reportErrorAt("Host func `{}` failed to load.", &.{v(name)}, nodeId); + } } else { - return c.reportErrorAt("Host func `{}` failed to load.", &.{v(name)}, nodeId); + return c.reportErrorAt("No function loader set for `{}`.", &.{v(name)}, nodeId); } } @@ -1091,23 +1096,79 @@ fn funcDecl(c: *cy.Chunk, nodeId: cy.NodeId) !void { } pub fn declareVar(c: *cy.Chunk, nodeId: cy.NodeId) !void { + const node = c.nodes[nodeId]; + if (node.head.staticDecl.right == cy.NullId) { + // No initializer. Check if @host var. + const modifierId = c.nodes[node.head.staticDecl.varSpec].head.varSpec.modifierHead; + if (modifierId != cy.NullId) { + const modifier = c.nodes[modifierId]; + if (modifier.head.annotation.type == .host) { + try declareHostVar(c, nodeId); + return; + } + } + const name = c.getNodeTokenString(c.nodes[c.nodes[node.head.staticDecl.varSpec].head.varSpec.name]); + return c.reportErrorAt("`{}` does not have an initializer.", &.{v(name)}, nodeId); + } else { + const varSpec = c.nodes[node.head.staticDecl.varSpec]; + const nameN = c.nodes[varSpec.head.varSpec.name]; + const name = c.getNodeTokenString(nameN); + const nameId = try ensureNameSym(c.compiler, name); + try c.compiler.sema.modules.items[c.modId].setUserVar(c.compiler, name, nodeId); + + // var type. + var typeSymId: SymbolId = undefined; + if (varSpec.head.varSpec.typeSpecHead != cy.NullId) { + typeSymId = try getOrResolveTypeSymFromSpecNode(c, varSpec.head.varSpec.typeSpecHead); + } else { + typeSymId = bt.Any; + } + + const symId = try resolveLocalVarSym(c, c.semaRootSymId, nameId, typeSymId, nodeId, true); + c.nodes[nodeId].head.staticDecl.sema_symId = symId; + } +} + +fn declareHostVar(c: *cy.Chunk, nodeId: cy.NodeId) !void { + c.nodes[nodeId].node_t = .hostVarDecl; const node = c.nodes[nodeId]; const varSpec = c.nodes[node.head.staticDecl.varSpec]; const nameN = c.nodes[varSpec.head.varSpec.name]; const name = c.getNodeTokenString(nameN); - const nameId = try ensureNameSym(c.compiler, name); - try c.compiler.sema.modules.items[c.modId].setUserVar(c.compiler, name, nodeId); + // const nameId = try ensureNameSym(c.compiler, name); - // var type. - var typeSymId: SymbolId = undefined; - if (varSpec.head.varSpec.typeSpecHead != cy.NullId) { - typeSymId = try getOrResolveTypeSymFromSpecNode(c, varSpec.head.varSpec.typeSpecHead); + const info = cy.HostVarInfo{ + .modId = c.modId, + .name = cy.Str.initSlice(name), + .idx = c.curHostVarIdx, + }; + c.curHostVarIdx += 1; + if (c.varLoader) |varLoader| { + var out: cy.Value = cy.Value.None; + if (varLoader(@ptrCast(c.compiler.vm), info, &out)) { + // var type. + var typeId: TypeId = undefined; + if (varSpec.head.varSpec.typeSpecHead != cy.NullId) { + typeId = try getOrResolveTypeSymFromSpecNode(c, varSpec.head.varSpec.typeSpecHead); + } else { + typeId = bt.Any; + } + c.nodes[node.head.staticDecl.varSpec].next = typeId; + + const outTypeId = c.compiler.vm.types.buf[out.getTypeId()].semaTypeId; + if (!types.isTypeSymCompat(c.compiler, outTypeId, typeId)) { + const expTypeName = getSymName(c.compiler, typeId); + const actTypeName = getSymName(c.compiler, outTypeId); + return c.reportErrorAt("Host var `{}` expects type {}, got: {}.", &.{v(name), v(expTypeName), v(actTypeName)}, nodeId); + } + + try c.compiler.sema.modules.items[c.modId].setHostVar(c.compiler, name, nodeId, out); + } else { + return c.reportErrorAt("Host var `{}` failed to load.", &.{v(name)}, nodeId); + } } else { - typeSymId = bt.Any; + return c.reportErrorAt("No var loader set for `{}`.", &.{v(name)}, nodeId); } - - const symId = try resolveLocalVarSym(c, c.semaRootSymId, nameId, typeSymId, nodeId, true); - c.nodes[nodeId].head.staticDecl.sema_symId = symId; } fn localDecl(self: *cy.Chunk, nodeId: cy.NodeId) !void { @@ -3185,6 +3246,23 @@ fn resolveSymFromModule(chunk: *cy.Chunk, modId: cy.ModuleId, nameId: NameSymId, const res = try resolveHostFunc(chunk, key, funcSigId, modSym.inner.hostFunc.func); return CompactSymbolId.initFuncSymId(res.funcSymId); }, + .hostVar => { + const rtSymId = try self.vm.ensureVarSym(mod.resolvedRootSymId, nameId); + const rtSym = rt.VarSym.init(modSym.inner.hostVar.val); + cy.arc.retain(self.vm, rtSym.value); + self.vm.setVarSym(rtSymId, rtSym); + + const srcChunk = &chunk.compiler.chunks.items[mod.chunkId]; + const typeId = srcChunk.nodes[srcChunk.nodes[modSym.extra.hostVar.declId].head.staticDecl.varSpec].next; + const id = try self.sema.addSymbol(key, .variable, .{ + .variable = .{ + .chunkId = self.sema.modules.items[modId].chunkId, + .declId = modSym.extra.hostVar.declId, + .rTypeSymId = typeId, + }, + }); + return CompactSymbolId.initSymId(id); + }, .variable => { const rtSymId = try self.vm.ensureVarSym(mod.resolvedRootSymId, nameId); const rtSym = rt.VarSym.init(modSym.inner.variable.val); @@ -3613,6 +3691,7 @@ pub fn declareUsingModule(chunk: *cy.Chunk, modId: cy.ModuleId) !void { try chunk.usingModules.append(chunk.alloc, modId); } +// Use `declareUsingModule` instead. Kept for reference. pub fn importAllFromModule(self: *cy.Chunk, modId: cy.ModuleId) !void { const mod = self.compiler.sema.modules.items[modId]; var iter = mod.syms.iterator(); @@ -3718,7 +3797,10 @@ pub fn resolveRootModuleSym(self: *cy.VMcompiler, name: []const u8, modId: cy.Mo /// Given the local sym path, add a resolved var sym entry. /// Fail if there is already a symbol in this path with the same name. -fn resolveLocalVarSym(self: *cy.Chunk, parentSymId: SymbolId, nameId: NameSymId, typeSymId: SymbolId, declId: cy.NodeId, exported: bool) !SymbolId { +fn resolveLocalVarSym( + self: *cy.Chunk, parentSymId: SymbolId, nameId: NameSymId, typeSymId: SymbolId, + declId: cy.NodeId, exported: bool, +) !SymbolId { if (parentSymId == self.semaRootSymId) { // Check for local sym. const key = LocalSymKey.initLocalSymKey(nameId, null); @@ -4197,7 +4279,7 @@ pub const Model = struct { }; } - pub fn deinit(self: *Model, alloc: std.mem.Allocator, comptime reset: bool) void { + pub fn deinit(self: *Model, vm: *cy.VM, alloc: std.mem.Allocator, comptime reset: bool) void { if (reset) { self.resolvedSyms.clearRetainingCapacity(); self.resolvedSymMap.clearRetainingCapacity(); @@ -4213,7 +4295,7 @@ pub const Model = struct { } for (self.modules.items) |*mod| { - mod.deinit(alloc); + mod.deinit(vm, self.alloc); } if (reset) { self.modules.clearRetainingCapacity(); diff --git a/src/std/os.cy b/src/std/os.cy index 837b03944..34bf08d59 100644 --- a/src/std/os.cy +++ b/src/std/os.cy @@ -1,3 +1,11 @@ +@host var cpu string +@host var endian symbol +@host var stdin any +@host var stdout any +@host var stderr any +@host var system string +@host var vecBitSize int + @host func access(path any, mode symbol) any @host func args() List @host func bindLib(path any, decls List) any diff --git a/src/std/os.zig b/src/std/os.zig index 9d564b20d..6968c6574 100644 --- a/src/std/os.zig +++ b/src/std/os.zig @@ -24,15 +24,15 @@ pub var CStructT: rt.TypeId = undefined; pub var CArrayT: rt.TypeId = undefined; pub const Src = @embedFile("os.cy"); -pub fn defaultFuncLoader(_: *cy.UserVM, func: cy.HostFuncInfo) callconv(.C) vmc.HostFuncFn { +pub fn funcLoader(_: *cy.UserVM, func: cy.HostFuncInfo) callconv(.C) vmc.HostFuncFn { if (std.mem.eql(u8, funcs[func.idx].@"0", func.name.slice())) { return @ptrCast(funcs[func.idx].@"1"); } return null; } -const NameHostFunc = struct { []const u8, cy.ZHostFuncFn }; -const funcs = [_]NameHostFunc{ +const NameFunc = struct { []const u8, cy.ZHostFuncFn }; +const funcs = [_]NameFunc{ .{"access", access}, .{"args", osArgs}, .{"bindLib", bindLib}, @@ -71,64 +71,67 @@ const funcs = [_]NameHostFunc{ .{"writeFile", writeFile}, }; -pub fn postLoad(vm: *cy.UserVM, modId: cy.ModuleId) callconv(.C) void { - zPostLoad(vm.internal().compiler, modId) catch |err| { +const NameValue = struct { []const u8, cy.Value }; +var vars: [7]NameValue = undefined; +pub fn varLoader(_: *cy.UserVM, v: cy.HostVarInfo, out: *cy.Value) callconv(.C) bool { + if (std.mem.eql(u8, vars[v.idx].@"0", v.name.slice())) { + out.* = vars[v.idx].@"1"; + return true; + } + return false; +} + +pub fn preLoad(vm: *cy.UserVM, modId: cy.ModuleId) callconv(.C) void { + zPreLoad(vm.internal().compiler, modId) catch |err| { cy.panicFmt("os module: {}", .{err}); }; } -fn zPostLoad(self: *cy.VMcompiler, modId: cy.ModuleId) linksection(cy.InitSection) anyerror!void { - const b = bindings.ModuleBuilder.init(self, modId); - - // Object Types. - CFuncT = try b.createAndSetTypeObject("CFunc", &.{"sym", "args", "ret"}); - CStructT = try b.createAndSetTypeObject("CStruct", &.{"fields", "type"}); - CArrayT = try b.createAndSetTypeObject("CArray", &.{"n", "elem"}); - - // Variables. - try b.setVar("cpu", bt.String, try self.buf.getOrPushStringValue(@tagName(builtin.cpu.arch))); +pub fn zPreLoad(c: *cy.VMcompiler, modId: cy.ModuleId) !void { + _ = modId; + vars[0] = .{ "cpu", try c.buf.getOrPushStringValue(@tagName(builtin.cpu.arch)) }; if (builtin.cpu.arch.endian() == .Little) { - try b.setVar("endian", bt.Symbol, cy.Value.initSymbol(@intFromEnum(Symbol.little))); + vars[1] = .{ "endian", cy.Value.initSymbol(@intFromEnum(Symbol.little)) }; } else { - try b.setVar("endian", bt.Symbol, cy.Value.initSymbol(@intFromEnum(Symbol.big))); + vars[1] = .{ "endian", cy.Value.initSymbol(@intFromEnum(Symbol.big)) }; } if (cy.hasStdFiles) { - const stdin = try cy.heap.allocFile(self.vm, std.io.getStdIn().handle); + const stdin = try cy.heap.allocFile(c.vm, std.io.getStdIn().handle); stdin.asHeapObject().file.closeOnFree = false; - try b.setVar("stdin", bt.Any, stdin); - const stdout = try cy.heap.allocFile(self.vm, std.io.getStdOut().handle); + vars[2] = .{ "stdin", stdin }; + const stdout = try cy.heap.allocFile(c.vm, std.io.getStdOut().handle); stdout.asHeapObject().file.closeOnFree = false; - try b.setVar("stdout", bt.Any, stdout); - const stderr = try cy.heap.allocFile(self.vm, std.io.getStdErr().handle); + vars[3] = .{ "stdout", stdout }; + const stderr = try cy.heap.allocFile(c.vm, std.io.getStdErr().handle); stderr.asHeapObject().file.closeOnFree = false; - try b.setVar("stderr", bt.Any, stderr); + vars[4] = .{ "stderr", stderr }; } else { - try b.setVar("stdin", bt.Any, Value.None); - try b.setVar("stdout", bt.Any, Value.None); - try b.setVar("stderr", bt.Any, Value.None); + vars[2] = .{ "stdin", Value.None }; + vars[3] = .{ "stdout", Value.None }; + vars[4] = .{ "stderr", Value.None }; } - try b.setVar("system", bt.String, try self.buf.getOrPushStringValue(@tagName(builtin.os.tag))); + vars[5] = .{ "system", try c.buf.getOrPushStringValue(@tagName(builtin.os.tag)) }; if (comptime std.simd.suggestVectorSize(u8)) |VecSize| { - try b.setVar("vecBitSize", bt.Integer, cy.Value.initI32(VecSize * 8)); + vars[6] = .{ "vecBitSize", cy.Value.initI32(VecSize * 8) }; } else { - try b.setVar("vecBitSize", bt.Integer, cy.Value.initI32(0)); + vars[6] = .{ "vecBitSize", cy.Value.initI32(0) }; } } -pub fn destroy(vm: *cy.UserVM, modId: cy.ModuleId) callconv(.C) void { - const c = vm.internal().compiler; - const mod = c.sema.getModulePtr(modId); - if (cy.hasStdFiles) { - const stdin = (mod.getVarVal(c, "stdin") catch cy.fatal()).?; - cy.arc.release(c.vm, stdin); +pub fn postLoad(vm: *cy.UserVM, modId: cy.ModuleId) callconv(.C) void { + zPostLoad(vm.internal().compiler, modId) catch |err| { + cy.panicFmt("os module: {}", .{err}); + }; +} - const stdout = (mod.getVarVal(c, "stdout") catch cy.fatal()).?; - cy.arc.release(c.vm, stdout); +fn zPostLoad(self: *cy.VMcompiler, modId: cy.ModuleId) linksection(cy.InitSection) anyerror!void { + const b = bindings.ModuleBuilder.init(self, modId); - const stderr = (mod.getVarVal(c, "stderr") catch cy.fatal()).?; - cy.arc.release(c.vm, stderr); - } + // Object Types. + CFuncT = try b.createAndSetTypeObject("CFunc", &.{"sym", "args", "ret"}); + CStructT = try b.createAndSetTypeObject("CStruct", &.{"fields", "type"}); + CArrayT = try b.createAndSetTypeObject("CArray", &.{"n", "elem"}); } fn openDir(vm: *cy.UserVM, args: [*]const Value, nargs: u8) linksection(cy.StdSection) Value { diff --git a/src/value.zig b/src/value.zig index 2bb65b86a..cb9e30bd6 100644 --- a/src/value.zig +++ b/src/value.zig @@ -705,7 +705,9 @@ test "value internals." { try t.eq(StaticUstringMask, 0x7FFC000400000000); try t.eq(NoneMask, 0x7FFC000000000000); try t.eq(TrueMask, 0x7FFC000100000001); + try t.eq(FalseMask, 0x7FFC000100000000); try t.eq(vmc.POINTER_MASK, 0xFFFE000000000000); + try t.eq(Value.initInt(0).val, 0x7ffd000000000000); // Check Zig/C struct compat. try t.eq(@sizeOf(Value), @sizeOf(vmc.Value)); diff --git a/src/vm_compiler.zig b/src/vm_compiler.zig index 72e949b0f..8006b4262 100644 --- a/src/vm_compiler.zig +++ b/src/vm_compiler.zig @@ -114,7 +114,7 @@ pub const VMcompiler = struct { } // Chunks depends on modules. - self.sema.deinit(self.alloc, reset); + self.sema.deinit(self.vm, self.alloc, reset); if (reset) { self.typeStack.clearRetainingCapacity(); @@ -279,6 +279,7 @@ pub const VMcompiler = struct { } // All modules and data types are loaded first. + log.tracev("Load imports and types.", .{}); var id: u32 = 0; while (true) { while (id < self.chunks.items.len) : (id += 1) { @@ -327,10 +328,15 @@ pub const VMcompiler = struct { } // Declare static vars and funcs after types have been resolved. + log.tracev("Load module symbols.", .{}); id = 0; while (id < self.chunks.items.len) : (id += 1) { var chunk = &self.chunks.items[id]; + if (chunk.preLoad) |preLoad| { + preLoad(@ptrCast(self.vm), chunk.modId); + } + // Process static declarations. for (chunk.parser.staticDecls.items) |decl| { switch (decl.declT) { @@ -359,6 +365,7 @@ pub const VMcompiler = struct { // Perform sema on all chunks. // TODO: Single pass sema/codegen. + log.tracev("Perform sema.", .{}); id = 0; while (id < self.chunks.items.len) : (id += 1) { try self.performChunkSema(id); @@ -374,6 +381,7 @@ pub const VMcompiler = struct { } if (!config.skipCodegen) { + log.tracev("Perform codegen.", .{}); // Once all symbols have been resolved, the static initializers are generated in DFS order. for (self.chunks.items) |*chunk| { log.debug("gen static initializer for chunk: {}", .{chunk.id}); @@ -381,9 +389,12 @@ pub const VMcompiler = struct { for (chunk.parser.staticDecls.items) |decl| { if (decl.declT == .variable) { const node = chunk.nodes[decl.inner.variable]; - const symId = node.head.staticDecl.sema_symId; - const csymId = sema.CompactSymbolId.initSymId(symId); - try gen.genStaticInitializerDFS(chunk, csymId); + if (node.node_t == .staticDecl) { + // Nodetype could have changed after declaration. eg. hostVarDecl. + const symId = node.head.staticDecl.sema_symId; + const csymId = sema.CompactSymbolId.initSymId(symId); + try gen.genStaticInitializerDFS(chunk, csymId); + } } else if (decl.declT == .funcInit) { const node = chunk.nodes[decl.inner.funcInit]; if (node.node_t == .funcDeclInit) { @@ -509,8 +520,14 @@ pub const VMcompiler = struct { } fn performImportTask(self: *VMcompiler, task: ImportTask) !void { - var res: cy.ModuleLoaderResult = undefined; + // Initialize defaults. + var res: cy.ModuleLoaderResult = .{ + .src = cy.Str.initSlice(""), + .srcIsStatic = true, + }; + self.hasApiError = false; + log.tracev("Invoke module loader: {s}", .{task.absSpec}); if (self.moduleLoader(@ptrCast(self.vm), cy.Str.initSlice(task.absSpec), &res)) { // Push another chunk. const newChunkId: u32 = @intCast(self.chunks.items.len); @@ -518,6 +535,8 @@ pub const VMcompiler = struct { const src = res.src.slice(); var newChunk = try cy.Chunk.init(self, newChunkId, task.absSpec, src); newChunk.funcLoader = res.funcLoader; + newChunk.varLoader = res.varLoader; + newChunk.preLoad = res.preLoad; newChunk.postLoad = res.postLoad; newChunk.srcOwned = !res.srcIsStatic; newChunk.destroy = res.destroy; @@ -637,14 +656,19 @@ pub fn defaultModuleLoader(_: *cy.UserVM, spec: cy.Str, out: *cy.ModuleLoaderRes out.* = .{ .src = cy.Str.initSlice(math_mod.Src), .srcIsStatic = true, - .funcLoader = math_mod.defaultFuncLoader, - .postLoad = math_mod.postLoad, + .funcLoader = math_mod.funcLoader, + .varLoader = math_mod.varLoader, }; return true; } return false; } +comptime { + @export(defaultModuleResolver, .{ .name = "csDefaultModuleResolver", .linkage = .Strong }); + @export(defaultModuleLoader, .{ .name = "csDefaultModuleLoader", .linkage = .Strong }); +} + test "vm compiler internals." { try t.eq(@offsetOf(VMcompiler, "buf"), @offsetOf(vmc.Compiler, "buf")); try t.eq(@offsetOf(VMcompiler, "sema"), @offsetOf(vmc.Compiler, "sema"));