diff --git a/Lumi.Bytecode/Constants/ConstantPool.cs b/Lumi.Bytecode/Constants/ConstantPool.cs index d1c24e9..fb9012a 100644 --- a/Lumi.Bytecode/Constants/ConstantPool.cs +++ b/Lumi.Bytecode/Constants/ConstantPool.cs @@ -7,6 +7,7 @@ /// internal sealed class ConstantPool { + // TODO: need a constantpool pass to remove dead constants after optimizations like constant folding and inlining. private readonly List _values = new(capacity: 16); private readonly Dictionary _numberIndex = []; private readonly Dictionary _stringIndex = new(StringComparer.Ordinal); diff --git a/Lumi.StdLib.Tests/MSTestSettings.cs b/Lumi.StdLib.Tests/MSTestSettings.cs new file mode 100644 index 0000000..300f5b1 --- /dev/null +++ b/Lumi.StdLib.Tests/MSTestSettings.cs @@ -0,0 +1 @@ +[assembly: Parallelize(Scope = ExecutionScope.MethodLevel)] diff --git a/Lumi.VM/NativeMemberDispatcher.cs b/Lumi.VM/NativeMemberDispatcher.cs index aa4e0a3..dd6bf24 100644 --- a/Lumi.VM/NativeMemberDispatcher.cs +++ b/Lumi.VM/NativeMemberDispatcher.cs @@ -61,13 +61,13 @@ private static Value InvokeFilePreludeMethod(HeapManager heap, string methodName { return methodName switch { - StdLibConstants.FilePreludeMethods.ReadText => Value.FromString(ReadAllText(methodName, args)), - StdLibConstants.FilePreludeMethods.WriteText => WriteText(args), - StdLibConstants.FilePreludeMethods.AppendText => AppendText(args), + StdLibConstants.FilePreludeMethods.ReadText => ReadText(heap, args), + StdLibConstants.FilePreludeMethods.WriteText => WriteText(heap, args), + StdLibConstants.FilePreludeMethods.AppendText => AppendText(heap, args), StdLibConstants.FilePreludeMethods.ReadLines => ReadLines(heap, args), StdLibConstants.FilePreludeMethods.WriteLines => WriteLines(heap, args), - StdLibConstants.FilePreludeMethods.Delete => Delete(args), - StdLibConstants.FilePreludeMethods.Create => Create(args), + StdLibConstants.FilePreludeMethods.Delete => Delete(heap, args), + StdLibConstants.FilePreludeMethods.Create => Create(heap, args), _ => throw VirtualMachineError.UnknownPreludeMethod(StandardLibraryRegistry.FilePreludeName, methodName) }; } @@ -77,29 +77,32 @@ private static Value InvokeFilePreludeMethod(HeapManager heap, string methodName } } - private static string ReadAllText(string methodName, IReadOnlyList args) - => File.ReadAllText(GetRequiredStringArgument(methodName, 0, args[0])); + private static string ReadAllText(HeapManager heap, string methodName, IReadOnlyList args) + => File.ReadAllText(GetRequiredStringArgument(methodName, 0, args[0], heap)); - private static Value Delete(IReadOnlyList args) + private static Value ReadText(HeapManager heap, IReadOnlyList args) + => Value.FromHeapObject(heap.InternString(ReadAllText(heap, StdLibConstants.FilePreludeMethods.ReadText, args))); + + private static Value Delete(HeapManager heap, IReadOnlyList args) { - var path = GetRequiredStringArgument(StdLibConstants.FilePreludeMethods.Delete, 0, args[0]); + var path = GetRequiredStringArgument(StdLibConstants.FilePreludeMethods.Delete, 0, args[0], heap); File.Delete(path); return Value.Undefined(); } - private static Value Create(IReadOnlyList args) + private static Value Create(HeapManager heap, IReadOnlyList args) { - var path = GetRequiredStringArgument(StdLibConstants.FilePreludeMethods.Create, 0, args[0]); + var path = GetRequiredStringArgument(StdLibConstants.FilePreludeMethods.Create, 0, args[0], heap); using var fileStream = File.Create(path); return Value.Undefined(); } - private static Value AppendText(IReadOnlyList args) + private static Value AppendText(HeapManager heap, IReadOnlyList args) { - var path = GetRequiredStringArgument(StdLibConstants.FilePreludeMethods.AppendText, 0, args[0]); - var contents = GetRequiredStringArgument(StdLibConstants.FilePreludeMethods.AppendText, 1, args[1]); + var path = GetRequiredStringArgument(StdLibConstants.FilePreludeMethods.AppendText, 0, args[0], heap); + var contents = GetRequiredStringArgument(StdLibConstants.FilePreludeMethods.AppendText, 1, args[1], heap); File.AppendAllText(path, contents); return Value.Undefined(); @@ -107,17 +110,14 @@ private static Value AppendText(IReadOnlyList args) private static Value WriteLines(HeapManager heap, IReadOnlyList args) { - var path = GetRequiredStringArgument(StdLibConstants.FilePreludeMethods.WriteLines, 0, args[0]); + var path = GetRequiredStringArgument(StdLibConstants.FilePreludeMethods.WriteLines, 0, args[0], heap); var linesArray = GetRequiredArrayArgument(StdLibConstants.FilePreludeMethods.WriteLines, 1, args[1], heap); var lines = new string[linesArray.Elements.Count]; for (var i = 0; i < linesArray.Elements.Count; i++) { var element = linesArray.Elements[i]; - if (element.Kind != ValueKind.String || element.String is null) - throw VirtualMachineError.MethodArgumentTypeMismatch(StdLibConstants.FilePreludeMethods.WriteLines, 1, ValueKind.String, element.Kind); - - lines[i] = element.String; + lines[i] = GetRequiredStringArgument(StdLibConstants.FilePreludeMethods.WriteLines, 1, element, heap); } File.WriteAllLines(path, lines); @@ -139,21 +139,23 @@ private static Value RemoveArrayItem(HeapArrayObject target, Value item) return Value.FromBoolean(removed); } - private static Value WriteText(IReadOnlyList args) + private static Value WriteText(HeapManager heap, IReadOnlyList args) { - var path = GetRequiredStringArgument(StdLibConstants.FilePreludeMethods.WriteText, 0, args[0]); - var contents = GetRequiredStringArgument(StdLibConstants.FilePreludeMethods.WriteText, 1, args[1]); + var path = GetRequiredStringArgument(StdLibConstants.FilePreludeMethods.WriteText, 0, args[0], heap); + var contents = GetRequiredStringArgument(StdLibConstants.FilePreludeMethods.WriteText, 1, args[1], heap); File.WriteAllText(path, contents); return Value.Undefined(); } - private static string GetRequiredStringArgument(string methodName, int parameterIndex, Value value) + private static string GetRequiredStringArgument(string methodName, int parameterIndex, Value value, HeapManager? heap) { - if (value.Kind != ValueKind.String || value.String is null) - throw VirtualMachineError.MethodArgumentTypeMismatch(methodName, parameterIndex, ValueKind.String, value.Kind); + if (heap is not null && value.IsHeapAllocated()) + { + return heap.GetStringValue(value.GetRequiredHeapHandle()); + } - return value.String; + throw VirtualMachineError.MethodArgumentTypeMismatch(methodName, parameterIndex, ValueKind.String, value.Kind); } private static HeapArrayObject GetRequiredArrayArgument(string methodName, int parameterIndex, Value value, HeapManager heap) @@ -161,11 +163,7 @@ private static HeapArrayObject GetRequiredArrayArgument(string methodName, int p if (!value.IsHeapAllocated()) throw VirtualMachineError.MethodArgumentTypeMismatch(methodName, parameterIndex, ValueKind.Array, value.Kind); - var heapObject = heap.Get(value.GetRequiredHeapHandle()); - if (heapObject is not HeapArrayObject arrayObject) - throw VirtualMachineError.MethodArgumentTypeMismatch(methodName, parameterIndex, ValueKind.Array, heapObject.Kind); - - return arrayObject; + return heap.Get(value.GetRequiredHeapHandle()); } private static void ValidateArgumentCount(string methodName, int expected, int actual) @@ -176,16 +174,15 @@ private static void ValidateArgumentCount(string methodName, int expected, int a private static Value ReadLines(HeapManager heap, IReadOnlyList args) { - var lines = File.ReadAllLines(GetRequiredStringArgument(StdLibConstants.FilePreludeMethods.ReadLines, 0, args[0])); + var lines = File.ReadAllLines(GetRequiredStringArgument(StdLibConstants.FilePreludeMethods.ReadLines, 0, args[0], heap)); var values = new List(lines.Length); foreach (var line in lines) { - values.Add(Value.FromString(line)); + var handle = heap.InternString(line); + values.Add(Value.FromHeapObject(handle)); } - var heapArray = new HeapArrayObject(values); - - return Value.FromHeapObject(heap.Allocate(heapArray)); + return Value.FromHeapObject(heap.Allocate(new HeapArrayObject(values))); } -} +} \ No newline at end of file diff --git a/Lumi.VM/Value.cs b/Lumi.VM/Value.cs index 81b55a7..d225dff 100644 --- a/Lumi.VM/Value.cs +++ b/Lumi.VM/Value.cs @@ -14,26 +14,22 @@ internal readonly struct Value // Payload fields — only the one matching Kind is populated. public double Number { get; } - public string? String { get; } public bool Bool { get; } public HeapHandle? HeapHandle { get; } private Value( ValueKind kind, double number = 0, - string? str = null, bool b = false, HeapHandle? heapHandle = null) { Kind = kind; Number = number; - String = str; Bool = b; HeapHandle = heapHandle; } public static Value FromNumber(double n) => new(ValueKind.Number, number: n); - public static Value FromString(string s) => new(ValueKind.String, str: s); // REMOVE after full implementation to heap-allocated strings public static Value FromBoolean(bool b) => new(ValueKind.Boolean, b: b); public static Value Undefined() => new(ValueKind.Undefined); public static Value FromHeapObject(HeapHandle heapHandle) => new(ValueKind.HeapObject, heapHandle: heapHandle); @@ -53,7 +49,6 @@ private Value( public static Value ConstantToValue(Constant constant) => constant.Kind switch { ConstantKind.Number => FromNumber(constant.Number), - ConstantKind.String => FromString(constant.String!), // TODO: we leave strings on the stack for now. Move to heap later. ConstantKind.Boolean => FromBoolean(constant.Boolean), ConstantKind.Null => new(ValueKind.Null), ConstantKind.Undefined => new(ValueKind.Undefined), @@ -63,7 +58,6 @@ private Value( public string PrintValue() => Kind switch { ValueKind.Number => Number.ToString(), - ValueKind.String => "\"" + String + "\"" ?? string.Empty, // TODO: will be obsolete once we move string to the heap ValueKind.Boolean => Bool.ToString(), ValueKind.Null => "null", ValueKind.Undefined => "undefined", diff --git a/Lumi.VM/VirtualMachine.cs b/Lumi.VM/VirtualMachine.cs index 592899f..c41d05a 100644 --- a/Lumi.VM/VirtualMachine.cs +++ b/Lumi.VM/VirtualMachine.cs @@ -52,7 +52,7 @@ public void Execute(BytecodeResult bytecode) switch (instruction.Kind) { case InstructionKind.PushConst: - _stack[_stackTop++] = Value.ConstantToValue(constants[instruction.IntOperand.GetValueOrDefault()]); + _stack[_stackTop++] = ConstantToValue(constants[instruction.IntOperand.GetValueOrDefault()]); break; case InstructionKind.Add: @@ -60,10 +60,11 @@ public void Execute(BytecodeResult bytecode) var b = _stack[--_stackTop]; var a = _stack[--_stackTop]; - // NOTE: clean this up. - if (a.Kind == ValueKind.String || b.Kind == ValueKind.String) + if (TryGetStringValue(a, out _) || TryGetStringValue(b, out _)) { - _stack[_stackTop++] = Value.FromString(a.ToString() + b.ToString()); + _heap.MaybeCollect(Roots([a, b])); + var handle = _heap.InternString(StringifyForConcatenation(a) + StringifyForConcatenation(b)); + _stack[_stackTop++] = Value.FromHeapObject(handle); break; } _stack[_stackTop++] = Value.FromNumber(a.Number + b.Number); @@ -115,9 +116,7 @@ public void Execute(BytecodeResult bytecode) case InstructionKind.LoadPreludeGlobal: { - var heapObject = new HeapNativeObject(instruction.GetSafeStringOperand(), []); // TODO: fields? - _heap.MaybeCollect(EnumerateRoots()); - _stack[_stackTop++] = Value.FromHeapObject(_heap.Allocate(heapObject)); + AllocateHeapObject(new HeapNativeObject(instruction.GetSafeStringOperand(), [])); break; } @@ -144,9 +143,7 @@ public void Execute(BytecodeResult bytecode) values[fields[i]] = i < args.Length ? args[i] : Value.Undefined(); } - var heapObject = new HeapStructObject(structName, values); - _heap.MaybeCollect(EnumerateRoots()); - _stack[_stackTop++] = Value.FromHeapObject(_heap.Allocate(heapObject)); + AllocateHeapObject(new HeapStructObject(structName, values), values.Values); break; } @@ -203,9 +200,7 @@ public void Execute(BytecodeResult bytecode) values[i] = _stack[--_stackTop]; } - var heapObject = new HeapArrayObject([.. values]); - _heap.MaybeCollect(EnumerateRoots()); - _stack[_stackTop++] = Value.FromHeapObject(_heap.Allocate(heapObject)); + AllocateHeapObject(new HeapArrayObject([.. values]), values); break; } @@ -429,4 +424,54 @@ private IEnumerable EnumerateRoots() yield return _variables[i]; } } -} + + private IEnumerable Roots(IEnumerable? additionalRoots = null) + { + foreach (var root in EnumerateRoots()) yield return root; + if (additionalRoots is not null) + { + foreach (var root in additionalRoots) yield return root; + } + } + + private void AllocateHeapObject(HeapObject heapObject, IEnumerable? additionalRoots = null) + { + _heap.MaybeCollect(Roots()); + _stack[_stackTop++] = Value.FromHeapObject(_heap.Allocate(heapObject)); + } + + private Value ConstantToValue(Constant constant) + { + if (constant.Kind != ConstantKind.String) + { + return Value.ConstantToValue(constant); + } + + _heap.MaybeCollect(EnumerateRoots()); + return Value.FromHeapObject(_heap.InternString(constant.String!)); + } + + private bool TryGetStringValue(Value value, out string text) + { + text = string.Empty; + + if (!value.IsHeapAllocated()) + { + return false; + } + + var obj = _heap.Get(value.GetRequiredHeapHandle()); + if (obj is not HeapStringObject stringObj) + { + return false; + } + + text = stringObj.Value; + return true; + } + + private string StringifyForConcatenation(Value value) + => TryGetStringValue(value, out var text) + ? text + : _heap.FormatValue(value); +} \ No newline at end of file diff --git a/Lumi.slnx b/Lumi.slnx index bfbdee6..8968df1 100644 --- a/Lumi.slnx +++ b/Lumi.slnx @@ -35,7 +35,7 @@ - +