From cfec9641882ffc5ceda9ea14ac49c523832b38fe Mon Sep 17 00:00:00 2001 From: Benoit Giannangeli Date: Wed, 5 Jun 2024 10:30:31 +0200 Subject: [PATCH] feat(obj): Store properties and methods in a slice rather than a hashmap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit closes #90 We never needed to known the property name at runtime, this is an artifact of the 'Crafting Interpreters' heritage. This results in a performance boost of about 40% on some benches: Before: → hyperfine --warmup 10 'buzz tests/bench/001-btree.buzz 15 Benchmark 1: buzz tests/bench/001-btree.buzz 15 Time (mean ± σ): 1.221 s ± 0.022 s [User: 1.028 s, System: 0.174 s] Range (min … max): 1.195 s … 1.271 s 10 runs After: → hyperfine --warmup 10 'buzz tests/bench/001-btree.buzz 15 Benchmark 1: buzz tests/bench/001-btree.buzz 15 Time (mean ± σ): 795.5 ms ± 7.7 ms [User: 725.1 ms, System: 62.9 ms] Range (min … max): 785.3 ms … 811.7 ms 10 runs --- CHANGELOG.md | 3 + src/Ast.zig | 4 +- src/Chunk.zig | 9 +- src/Codegen.zig | 250 +++++---- src/Jit.zig | 212 ++++++-- src/Parser.zig | 70 ++- src/builtin/list.zig | 29 +- src/builtin/map.zig | 55 +- src/builtin/pattern.zig | 4 +- src/builtin/range.zig | 25 +- src/builtin/str.zig | 10 +- src/buzz_api.zig | 262 +++++---- src/disassembler.zig | 80 ++- src/jit_extern_api.zig | 112 ++-- src/lib/buzz_api.zig | 26 +- src/lib/buzz_http.zig | 39 +- src/lib/http.buzz | 1 + src/main.zig | 2 +- src/memory.zig | 179 +++--- src/obj.zig | 887 ++++++++++++++++++------------ src/repl.zig | 2 +- src/vm.zig | 1150 +++++++++++++++++++++++---------------- src/wasm_repl.zig | 2 +- tests/053-range.buzz | 8 +- 24 files changed, 2082 insertions(+), 1339 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9efe577e..ed6f76bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,9 @@ tuples.@"0" == "john"; - Type checking was not done on object instance property assignments - Http client could not be collected because it kept connection opened to previous requests' domains +## Internal +- Properties are now retrieve with an index rather than by a hashmap lookup (https://github.com/buzz-language/buzz/issues/90) which gives a nice performance boost of about 40% on some benches + # 0.4.0 (05-16-2024) ## Added diff --git a/src/Ast.zig b/src/Ast.zig index 7ca5e856..948a9d4d 100644 --- a/src/Ast.zig +++ b/src/Ast.zig @@ -1139,7 +1139,7 @@ pub fn toValue(self: Self, node: Node.Index, gc: *GarbageCollector) Error!Value var list = try gc.allocateObject( obj.ObjList, - obj.ObjList.init(gc.allocator, type_def.?), + try obj.ObjList.init(gc.allocator, type_def.?), ); for (components.items) |item| { @@ -1156,7 +1156,7 @@ pub fn toValue(self: Self, node: Node.Index, gc: *GarbageCollector) Error!Value var map = try gc.allocateObject( obj.ObjMap, - obj.ObjMap.init(gc.allocator, type_def.?), + try obj.ObjMap.init(gc.allocator, type_def.?), ); for (components.entries) |entry| { diff --git a/src/Chunk.zig b/src/Chunk.zig index b4830152..48314d8e 100644 --- a/src/Chunk.zig +++ b/src/Chunk.zig @@ -67,8 +67,12 @@ pub const OpCode = enum(u8) { OP_CALL, OP_TAIL_CALL, + OP_CALL_INSTANCE_PROPERTY, + OP_TAIL_CALL_INSTANCE_PROPERTY, OP_INSTANCE_INVOKE, OP_INSTANCE_TAIL_INVOKE, + OP_PROTOCOL_INVOKE, + OP_PROTOCOL_TAIL_INVOKE, OP_STRING_INVOKE, OP_PATTERN_INVOKE, OP_FIBER_INVOKE, @@ -80,7 +84,6 @@ pub const OpCode = enum(u8) { OP_CLOSE_UPVALUE, OP_FIBER, - OP_INVOKE_FIBER, OP_RESUME, OP_RESOLVE, OP_YIELD, @@ -94,10 +97,12 @@ pub const OpCode = enum(u8) { OP_OBJECT, OP_INSTANCE, OP_FCONTAINER_INSTANCE, - OP_METHOD, OP_PROPERTY, + OP_OBJECT_DEFAULT, OP_GET_OBJECT_PROPERTY, OP_GET_INSTANCE_PROPERTY, + OP_GET_INSTANCE_METHOD, + OP_GET_PROTOCOL_METHOD, OP_GET_FCONTAINER_INSTANCE_PROPERTY, OP_GET_LIST_PROPERTY, OP_GET_MAP_PROPERTY, diff --git a/src/Codegen.zig b/src/Codegen.zig index ee86de9c..326d8124 100644 --- a/src/Codegen.zig +++ b/src/Codegen.zig @@ -301,7 +301,7 @@ pub fn makeConstant(self: *Self, value: Value) !u24 { pub fn identifierConstant(self: *Self, name: []const u8) !u24 { return try self.makeConstant( - Value.fromObj((try self.gc.copyString(name)).toObj()), + (try self.gc.copyString(name)).toValue(), ); } @@ -1298,78 +1298,75 @@ fn generateCall(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*obj // This is an async call, create a fiber if (components.is_async) { - if (!invoked) { - const call_arg_count: u8 = if (!invoked) - @as(u8, @intCast(arg_count)) - else if (invoked_on != null and invoked_on.? != .ObjectInstance and invoked_on.? != .ProtocolInstance) - @as(u8, @intCast(arg_count)) + 1 - else - @as(u8, @intCast(arg_count)); + // We emit OP_FIBER, the vm will then read the next instruction to get the info about the call + // and create the fiber + try self.emitOpCode(locations[node], .OP_FIBER); + } - try self.emitCodeArgs( - locations[node], - .OP_FIBER, - call_arg_count, - if (components.catch_default != null) 1 else 0, - ); + // Normal call/invoke + if (invoked) { + const callee_def_type = type_defs[node_components[components.callee].Dot.callee].?.def_type; + const member_lexeme = lexemes[node_components[components.callee].Dot.identifier]; - try self.patchOptJumps(node); - try self.endScope(node); + if (callee_def_type == .ObjectInstance) { + const fields = type_defs[node_components[components.callee].Dot.callee].? + .resolved_type.?.ObjectInstance + .resolved_type.?.Object.fields; - return null; - } else { - if (invoked) { - try self.emitCodeArg( - locations[node], - .OP_INVOKE_FIBER, - try self.identifierConstant( - lexemes[node_components[components.callee].Dot.identifier], - ), - ); - } + const field = fields.get(member_lexeme).?; - try self.emitTwo( + try self.emitCodeArg( locations[node], - if (invoked_on != null and invoked_on.? != .ObjectInstance and invoked_on.? != .ProtocolInstance) - @as(u8, @intCast(arg_count)) + 1 + if (components.tail_call and field.method) + .OP_INSTANCE_TAIL_INVOKE + else if (field.method) + .OP_INSTANCE_INVOKE + else if (components.tail_call) + .OP_TAIL_CALL_INSTANCE_PROPERTY else - @as(u8, @intCast(components.arguments.len)), - if (components.catch_default != null) 1 else 0, + .OP_CALL_INSTANCE_PROPERTY, + @intCast(field.index), ); - - try self.patchOptJumps(node); - try self.endScope(node); - - return null; - } - } - - // Normal call/invoke - if (invoked) { - // TODO: can it be invoked without callee being a DotNode? - try self.emitCodeArg( - locations[node], - switch (type_defs[node_components[components.callee].Dot.callee].?.def_type) { - .ObjectInstance, .ProtocolInstance => if (components.tail_call) - .OP_INSTANCE_TAIL_INVOKE + } else if (callee_def_type == .ProtocolInstance) { + try self.emitCodeArg( + locations[node], + if (components.tail_call) + .OP_PROTOCOL_TAIL_INVOKE else - .OP_INSTANCE_INVOKE, - .String => .OP_STRING_INVOKE, - .Pattern => .OP_PATTERN_INVOKE, - .Fiber => .OP_FIBER_INVOKE, - .List => .OP_LIST_INVOKE, - .Map => .OP_MAP_INVOKE, - .Range => .OP_RANGE_INVOKE, - else => unexpected: { - std.debug.assert(self.reporter.had_error); - break :unexpected if (components.tail_call) - .OP_INSTANCE_TAIL_INVOKE - else - .OP_INSTANCE_INVOKE; + .OP_PROTOCOL_INVOKE, + try self.identifierConstant(member_lexeme), + ); + } else { + try self.emitCodeArg( + locations[node], + switch (callee_def_type) { + .String => .OP_STRING_INVOKE, + .Pattern => .OP_PATTERN_INVOKE, + .Fiber => .OP_FIBER_INVOKE, + .List => .OP_LIST_INVOKE, + .Map => .OP_MAP_INVOKE, + .Range => .OP_RANGE_INVOKE, + else => unexpected: { + std.debug.assert(self.reporter.had_error); + break :unexpected .OP_INSTANCE_INVOKE; + }, }, - }, - try self.identifierConstant(lexemes[node_components[components.callee].Dot.identifier]), - ); + @intCast( + switch (callee_def_type) { + .String => obj.ObjString.members_name.get(member_lexeme).?, + .Pattern => obj.ObjPattern.members_name.get(member_lexeme).?, + .Fiber => obj.ObjFiber.members_name.get(member_lexeme).?, + .List => obj.ObjList.members_name.get(member_lexeme).?, + .Map => obj.ObjMap.members_name.get(member_lexeme).?, + .Range => obj.ObjRange.members_name.get(member_lexeme).?, + else => unexpected: { + std.debug.assert(self.reporter.had_error); + break :unexpected 0; + }, + }, + ), + ); + } } if (!invoked) { @@ -1396,6 +1393,7 @@ fn generateCall(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*obj return null; } +// FIXME: this is become a unreadable mess fn generateDot(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*obj.ObjFunction { const node_components = self.ast.nodes.items(.components); const type_defs = self.ast.nodes.items(.type_def); @@ -1443,7 +1441,8 @@ fn generateDot(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*obj. const get_code: ?Chunk.OpCode = switch (callee_type.def_type) { .Object => .OP_GET_OBJECT_PROPERTY, - .ObjectInstance, .ProtocolInstance => .OP_GET_INSTANCE_PROPERTY, + .ObjectInstance => .OP_GET_INSTANCE_PROPERTY, + .ProtocolInstance => .OP_GET_PROTOCOL_METHOD, .ForeignContainer => .OP_GET_FCONTAINER_INSTANCE_PROPERTY, .List => .OP_GET_LIST_PROPERTY, .Map => .OP_GET_MAP_PROPERTY, @@ -1464,11 +1463,32 @@ fn generateDot(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*obj. try self.emitCodeArg( locations[node], get_code.?, - try self.identifierConstant(identifier_lexeme), + switch (callee_type.def_type) { + .Fiber => @intCast(obj.ObjFiber.members_name.get(identifier_lexeme).?), + .Pattern => @intCast(obj.ObjPattern.members_name.get(identifier_lexeme).?), + .String => @intCast(obj.ObjString.members_name.get(identifier_lexeme).?), + else => unreachable, + }, ); } }, .ForeignContainer, .ObjectInstance, .Object => { + const field_name = self.ast.tokens.items(.lexeme)[components.identifier]; + const field = switch (callee_type.def_type) { + .ObjectInstance => callee_type.resolved_type.?.ObjectInstance + .resolved_type.?.Object.fields + .get(field_name), + .Object => callee_type.resolved_type.?.Object.fields + .get(field_name), + else => null, + }; + const field_index = if (field) |f| + f.index + else fcontainer: { + std.debug.assert(callee_type.def_type == .ForeignContainer); + break :fcontainer callee_type.resolved_type.?.ForeignContainer.fields.getIndex(field_name).?; + }; + switch (components.member_kind) { .Value => { const value = components.value_or_call_or_enum.Value; @@ -1478,7 +1498,6 @@ fn generateDot(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*obj. } // Type check value - const field_name = self.ast.tokens.items(.lexeme)[components.identifier]; switch (callee_type.def_type) { .ForeignContainer => { const field_type = callee_type.resolved_type.?.ForeignContainer.buzz_type.get(field_name).?; @@ -1495,15 +1514,9 @@ fn generateDot(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*obj. } }, .ObjectInstance, .Object => { - const field = if (callee_type.def_type == .ObjectInstance) - callee_type.resolved_type.?.ObjectInstance - .resolved_type.?.Object.fields.get(field_name).? - else - callee_type.resolved_type.?.Object.fields.get(field_name).?; - - if (field.method or - (callee_type.def_type == .ObjectInstance and field.static) or - (callee_type.def_type == .Object and !field.static)) + if (field.?.method or + (callee_type.def_type == .ObjectInstance and field.?.static) or + (callee_type.def_type == .Object and !field.?.static)) { self.reporter.reportErrorFmt( .assignable, @@ -1513,7 +1526,7 @@ fn generateDot(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*obj. field_name, }, ); - } else if (field.constant) { + } else if (field.?.constant) { self.reporter.reportErrorFmt( .constant_property, self.ast.tokens.get(locations[value]), @@ -1524,11 +1537,11 @@ fn generateDot(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*obj. ); } - if (!field.type_def.eql(value_type_def)) { + if (!field.?.type_def.eql(value_type_def)) { self.reporter.reportTypeCheck( .assignment_value_type, - field.location, - field.type_def, + field.?.location, + field.?.type_def, self.ast.tokens.get(locations[value]), value_type_def, "Bad property type", @@ -1545,9 +1558,10 @@ fn generateDot(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*obj. switch (callee_type.def_type) { .ObjectInstance => .OP_SET_INSTANCE_PROPERTY, .ForeignContainer => .OP_SET_FCONTAINER_INSTANCE_PROPERTY, - else => .OP_SET_OBJECT_PROPERTY, + .Object => .OP_SET_OBJECT_PROPERTY, + else => unreachable, }, - try self.identifierConstant(identifier_lexeme), + @intCast(field_index), ); }, .Call => { @@ -1564,7 +1578,7 @@ fn generateDot(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*obj. try self.emitCodeArg( locations[node], get_code.?, - try self.identifierConstant(identifier_lexeme), + @intCast(field.?.index), ); } @@ -1572,8 +1586,11 @@ fn generateDot(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*obj. }, .Ref => try self.emitCodeArg( locations[node], - get_code.?, - try self.identifierConstant(identifier_lexeme), + if (callee_type.def_type == .ObjectInstance and field.?.method) + .OP_GET_INSTANCE_METHOD + else + get_code.?, + @intCast(field_index), ), else => unreachable, } @@ -1582,7 +1599,7 @@ fn generateDot(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*obj. if (components.member_kind == .Call) { _ = try self.generateNode(components.value_or_call_or_enum.Call, breaks); } else { - std.debug.assert(components.member_kind == .Value); + std.debug.assert(components.member_kind == .Ref); try self.emitCodeArg( locations[node], get_code.?, @@ -1597,27 +1614,12 @@ fn generateDot(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*obj. @intCast(components.value_or_call_or_enum.EnumCase), ); }, - .Range => { - if (components.member_kind == .Call) { - try self.emitOpCode(locations[node], .OP_COPY); - - _ = try self.generateNode(components.value_or_call_or_enum.Call, breaks); - } else { - std.debug.assert(components.member_kind != .Value); - - try self.emitCodeArg( - locations[node], - get_code.?, - try self.identifierConstant(identifier_lexeme), - ); - } - }, .EnumInstance => { std.debug.assert(std.mem.eql(u8, identifier_lexeme, "value")); try self.emitOpCode(locations[node], .OP_GET_ENUM_CASE_VALUE); }, - .List, .Map => { + .List, .Map, .Range => { if (components.member_kind == .Call) { try self.emitOpCode(locations[node], .OP_COPY); @@ -1627,10 +1629,16 @@ fn generateDot(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error!?*obj. try self.emitCodeArg( locations[node], get_code.?, - try self.identifierConstant(identifier_lexeme), + switch (callee_type.def_type) { + .List => @intCast(obj.ObjList.members_name.get(identifier_lexeme).?), + .Map => @intCast(obj.ObjMap.members_name.get(identifier_lexeme).?), + .Range => @intCast(obj.ObjRange.members_name.get(identifier_lexeme).?), + else => unreachable, + }, ); } }, + else => std.debug.assert(self.reporter.had_error), } @@ -2953,11 +2961,12 @@ fn generateObjectDeclaration(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks for (components.members) |member| { const member_name = lexemes[member.name]; - const member_name_constant = try self.identifierConstant(member_name); if (member.method) { + // Method const member_field = object_def.fields.get(member_name).?; const member_type_def = member_field.type_def; + const member_idx = member_field.index; if (member_type_def.def_type == .Placeholder) { self.reporter.reportPlaceholder(self.ast, member_type_def.resolved_type.?.Placeholder); @@ -3008,13 +3017,14 @@ fn generateObjectDeclaration(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks _ = try self.generateNode(member.method_or_default_value.?, breaks); try self.emitCodeArg( location, - if (member_field.static) .OP_PROPERTY else .OP_METHOD, - member_name_constant, + .OP_PROPERTY, + @intCast(member_idx), ); } else { - // Properties + // Property const property_field = object_def.fields.get(member_name).?; const property_type = property_field.type_def; + const property_idx = property_field.index; // Create property default value if (member.method_or_default_value) |default| { @@ -3040,10 +3050,18 @@ fn generateObjectDeclaration(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks // Create property default value if (property_field.static) { - try self.emitCodeArg(location, .OP_SET_OBJECT_PROPERTY, member_name_constant); + try self.emitCodeArg( + location, + .OP_SET_OBJECT_PROPERTY, + @intCast(property_idx), + ); try self.emitOpCode(location, .OP_POP); } else { - try self.emitCodeArg(location, .OP_PROPERTY, member_name_constant); + try self.emitCodeArg( + location, + .OP_OBJECT_DEFAULT, + @intCast(property_idx), + ); } } } @@ -3124,7 +3142,17 @@ fn generateObjectInit(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error for (components.properties) |property| { const property_name = lexemes[property.name]; - const property_name_constant = try self.identifierConstant(property_name); + const property_idx = if (node_type_def.def_type == .ObjectInstance) + if (node_type_def.resolved_type.?.ObjectInstance + .resolved_type.?.Object + .fields.get(property_name)) |field| + field.index + else + null + else + node_type_def.resolved_type.?.ForeignContainer + .fields.getIndex(property_name); + const value_type_def = type_defs[property.value].?; if (fields.get(property_name)) |prop| { @@ -3167,7 +3195,7 @@ fn generateObjectInit(self: *Self, node: Ast.Node.Index, breaks: ?*Breaks) Error .OP_SET_INSTANCE_PROPERTY else .OP_SET_FCONTAINER_INSTANCE_PROPERTY, - property_name_constant, + @intCast(property_idx.?), ); try self.emitOpCode(location, .OP_POP); // Pop property value } else { diff --git a/src/Jit.zig b/src/Jit.zig index 68826a2f..e9a22d65 100644 --- a/src/Jit.zig +++ b/src/Jit.zig @@ -1807,6 +1807,8 @@ fn generateCall(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { const callee_reg = try self.REG("callee", m.MIR_T_I64); const callee = m.MIR_new_reg_op(self.ctx, callee_reg); if (invoked_on != null) { + const member_lexeme = lexemes[node_components[components.callee].Dot.identifier]; + switch (invoked_on.?) { .Object => try self.buildExternApiCall( .bz_getObjectField, @@ -1815,13 +1817,45 @@ fn generateCall(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { subject.?, m.MIR_new_uint_op( self.ctx, - (try self.vm.gc.copyString( - self.state.?.ast.tokens.items(.lexeme)[node_components[dot.?].Dot.identifier], - )).toValue().val, + type_defs[node_components[components.callee].Dot.callee].? + .resolved_type.?.Object + .fields.get(member_lexeme).? + .index, ), }, ), - .ObjectInstance, + .ObjectInstance => instance: { + const field = type_defs[node_components[components.callee].Dot.callee].? + .resolved_type.?.ObjectInstance + .resolved_type.?.Object + .fields.get(member_lexeme).?; + + break :instance try self.buildExternApiCall( + if (field.method) + .bz_getInstanceMethod + else + .bz_getInstanceProperty, + callee, + if (field.method) + &[_]m.MIR_op_t{ + // vm + m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), + // subject + subject.?, + // member + m.MIR_new_uint_op(self.ctx, field.index), + // bound + m.MIR_new_uint_op(self.ctx, 0), + } + else + &[_]m.MIR_op_t{ + // subject + subject.?, + // member + m.MIR_new_uint_op(self.ctx, field.index), + }, + ); + }, .ProtocolInstance, .String, .Pattern, @@ -1831,13 +1865,13 @@ fn generateCall(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { .Range, => try self.buildExternApiCall( switch (invoked_on.?) { - .ObjectInstance, .ProtocolInstance => .bz_getInstanceField, - .String => .bz_getStringField, - .Pattern => .bz_getPatternField, - .Fiber => .bz_getFiberField, - .List => .bz_getListField, - .Map => .bz_getMapField, - .Range => .bz_getRangeField, + .ProtocolInstance => .bz_getProtocolMethod, + .String => .bz_getStringProperty, + .Pattern => .bz_getPatternProperty, + .Fiber => .bz_getFiberProperty, + .List => .bz_getListProperty, + .Map => .bz_getMapProperty, + .Range => .bz_getRangeProperty, else => unreachable, }, callee, @@ -1849,9 +1883,18 @@ fn generateCall(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { // member m.MIR_new_uint_op( self.ctx, - (try self.vm.gc.copyString( - self.state.?.ast.tokens.items(.lexeme)[node_components[dot.?].Dot.identifier], - )).toValue().val, + switch (invoked_on.?) { + .String => o.ObjString.members_name.get(member_lexeme).?, + .Pattern => o.ObjPattern.members_name.get(member_lexeme).?, + .Fiber => o.ObjFiber.members_name.get(member_lexeme).?, + .Range => o.ObjRange.members_name.get(member_lexeme).?, + .List => o.ObjList.members_name.get(member_lexeme).?, + .Map => o.ObjMap.members_name.get(member_lexeme).?, + .ProtocolInstance => (try self.vm.gc.copyString( + self.state.?.ast.tokens.items(.lexeme)[node_components[dot.?].Dot.identifier], + )).toValue().val, + else => unreachable, + }, ), // bound m.MIR_new_uint_op(self.ctx, 0), @@ -3121,14 +3164,14 @@ fn generateDot(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { try self.REG("res", m.MIR_T_I64), ); try self.buildExternApiCall( - .bz_getFiberField, + .bz_getFiberProperty, res, &[_]m.MIR_op_t{ m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), (try self.generateNode(components.callee)).?, m.MIR_new_uint_op( self.ctx, - member_identifier, + o.ObjFiber.members_name.get(member_lexeme).?, ), m.MIR_new_uint_op(self.ctx, 1), }, @@ -3148,12 +3191,15 @@ fn generateDot(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { try self.REG("res", m.MIR_T_I64), ); try self.buildExternApiCall( - .bz_getPatternField, + .bz_getPatternProperty, res, &[_]m.MIR_op_t{ m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), (try self.generateNode(components.callee)).?, - m.MIR_new_uint_op(self.ctx, member_identifier), + m.MIR_new_uint_op( + self.ctx, + o.ObjPattern.members_name.get(member_lexeme).?, + ), m.MIR_new_uint_op(self.ctx, 1), }, ); @@ -3172,12 +3218,15 @@ fn generateDot(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { try self.REG("res", m.MIR_T_I64), ); try self.buildExternApiCall( - .bz_getStringField, + .bz_getStringProperty, res, &[_]m.MIR_op_t{ m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), (try self.generateNode(components.callee)).?, - m.MIR_new_uint_op(self.ctx, member_identifier), + m.MIR_new_uint_op( + self.ctx, + o.ObjString.members_name.get(member_lexeme).?, + ), m.MIR_new_uint_op(self.ctx, 1), }, ); @@ -3196,12 +3245,15 @@ fn generateDot(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { try self.REG("res", m.MIR_T_I64), ); try self.buildExternApiCall( - .bz_getRangeField, + .bz_getRangeProperty, res, &[_]m.MIR_op_t{ m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), (try self.generateNode(components.callee)).?, - m.MIR_new_uint_op(self.ctx, member_identifier), + m.MIR_new_uint_op( + self.ctx, + o.ObjRange.members_name.get(member_lexeme).?, + ), m.MIR_new_uint_op(self.ctx, 1), }, ); @@ -3215,6 +3267,9 @@ fn generateDot(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { switch (components.member_kind) { .Call => return try self.generateCall(components.value_or_call_or_enum.Call), .Value => { + const field = callee_type.resolved_type.?.Object.fields + .get(member_lexeme).?; + const gen_value = (try self.generateNode(components.value_or_call_or_enum.Value)).?; try self.buildExternApiCall( @@ -3223,7 +3278,7 @@ fn generateDot(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { &[_]m.MIR_op_t{ m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), (try self.generateNode(components.callee)).?, - m.MIR_new_uint_op(self.ctx, member_identifier), + m.MIR_new_uint_op(self.ctx, field.index), gen_value, }, ); @@ -3231,6 +3286,9 @@ fn generateDot(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { return gen_value; }, else => { + const field = callee_type.resolved_type.?.Object.fields + .get(member_lexeme).?; + const res = m.MIR_new_reg_op( self.ctx, try self.REG("res", m.MIR_T_I64), @@ -3240,7 +3298,7 @@ fn generateDot(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { res, &[_]m.MIR_op_t{ (try self.generateNode(components.callee)).?, - m.MIR_new_uint_op(self.ctx, member_identifier), + m.MIR_new_uint_op(self.ctx, field.index), }, ); @@ -3253,15 +3311,22 @@ fn generateDot(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { switch (components.member_kind) { .Call => return try self.generateCall(components.value_or_call_or_enum.Call), .Value => { + std.debug.assert(callee_type.def_type == .ObjectInstance); + const gen_value = (try self.generateNode(components.value_or_call_or_enum.Value)).?; try self.buildExternApiCall( - .bz_setInstanceField, + .bz_setInstanceProperty, null, &[_]m.MIR_op_t{ m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), (try self.generateNode(components.callee)).?, - m.MIR_new_uint_op(self.ctx, member_identifier), + m.MIR_new_uint_op( + self.ctx, + callee_type.resolved_type.?.ObjectInstance + .resolved_type.?.Object.fields + .get(member_lexeme).?.index, + ), gen_value, }, ); @@ -3269,20 +3334,52 @@ fn generateDot(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { return gen_value; }, else => { + const field = if (callee_type.def_type == .ObjectInstance) + callee_type.resolved_type.?.ObjectInstance + .resolved_type.?.Object.fields + .get(member_lexeme) + else + null; + const res = m.MIR_new_reg_op( self.ctx, try self.REG("res", m.MIR_T_I64), ); - try self.buildExternApiCall( - .bz_getInstanceField, - res, - &[_]m.MIR_op_t{ - m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), - (try self.generateNode(components.callee)).?, - m.MIR_new_uint_op(self.ctx, member_identifier), - m.MIR_new_uint_op(self.ctx, 1), - }, - ); + + if (field) |f| { + if (f.method) { + try self.buildExternApiCall( + .bz_getInstanceMethod, + res, + &[_]m.MIR_op_t{ + m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), + (try self.generateNode(components.callee)).?, + m.MIR_new_uint_op(self.ctx, f.index), + m.MIR_new_uint_op(self.ctx, 1), + }, + ); + } else { + try self.buildExternApiCall( + .bz_getInstanceProperty, + res, + &[_]m.MIR_op_t{ + (try self.generateNode(components.callee)).?, + m.MIR_new_uint_op(self.ctx, f.index), + }, + ); + } + } else { + try self.buildExternApiCall( + .bz_getProtocolMethod, + res, + &[_]m.MIR_op_t{ + m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), + (try self.generateNode(components.callee)).?, + m.MIR_new_uint_op(self.ctx, member_identifier), + m.MIR_new_uint_op(self.ctx, 1), + }, + ); + } return res; }, @@ -3301,8 +3398,12 @@ fn generateDot(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { &[_]m.MIR_op_t{ m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), (try self.generateNode(components.callee)).?, - m.MIR_new_uint_op(self.ctx, @as(u64, @intFromPtr(member_lexeme.ptr))), - m.MIR_new_uint_op(self.ctx, member_lexeme.len), + m.MIR_new_uint_op( + self.ctx, + callee_type.resolved_type.?.ForeignContainer + .fields + .getIndex(member_lexeme).?, + ), gen_value, }, ); @@ -3321,8 +3422,12 @@ fn generateDot(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { &[_]m.MIR_op_t{ m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), (try self.generateNode(components.callee)).?, - m.MIR_new_uint_op(self.ctx, @as(u64, @intFromPtr(member_lexeme.ptr))), - m.MIR_new_uint_op(self.ctx, member_lexeme.len), + m.MIR_new_uint_op( + self.ctx, + callee_type.resolved_type.?.ForeignContainer + .fields + .getIndex(member_lexeme).?, + ), }, ); @@ -3374,12 +3479,12 @@ fn generateDot(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { try self.REG("res", m.MIR_T_I64), ); try self.buildExternApiCall( - .bz_getListField, + .bz_getListProperty, res, &[_]m.MIR_op_t{ m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), (try self.generateNode(components.callee)).?, - m.MIR_new_uint_op(self.ctx, member_identifier), + m.MIR_new_uint_op(self.ctx, o.ObjList.members_name.get(member_lexeme).?), m.MIR_new_uint_op(self.ctx, 1), }, ); @@ -3398,12 +3503,12 @@ fn generateDot(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { try self.REG("res", m.MIR_T_I64), ); try self.buildExternApiCall( - .bz_getMapField, + .bz_getMapProperty, res, &[_]m.MIR_op_t{ m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), (try self.generateNode(components.callee)).?, - m.MIR_new_uint_op(self.ctx, member_identifier), + m.MIR_new_uint_op(self.ctx, o.ObjMap.members_name.get(member_lexeme).?), m.MIR_new_uint_op(self.ctx, 1), }, ); @@ -3881,10 +3986,10 @@ fn generateUnwrap(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { } fn generateObjectInit(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { + const lexemes = self.state.?.ast.tokens.items(.lexeme); const components = self.state.?.ast.nodes.items(.components)[node].ObjectInit; const type_defs = self.state.?.ast.nodes.items(.type_def); const type_def = type_defs[node]; - const lexemes = self.state.?.ast.tokens.items(.lexeme); if (type_def.?.def_type == .ForeignContainer) { return self.generateForeignContainerInit(node); @@ -3919,12 +4024,17 @@ fn generateObjectInit(self: *Self, node: Ast.Node.Index) Error!?m.MIR_op_t { for (components.properties) |property| { try self.buildExternApiCall( - .bz_setInstanceField, + .bz_setInstanceProperty, null, &[_]m.MIR_op_t{ m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), instance, - m.MIR_new_uint_op(self.ctx, (try self.vm.gc.copyString(lexemes[property.name])).toValue().val), + m.MIR_new_uint_op( + self.ctx, + type_def.?.resolved_type.?.ObjectInstance + .resolved_type.?.Object.fields + .get(lexemes[property.name]).?.index, + ), (try self.generateNode(property.value)).?, }, ); @@ -3964,8 +4074,12 @@ fn generateForeignContainerInit(self: *Self, node: Ast.Node.Index) Error!?m.MIR_ &[_]m.MIR_op_t{ m.MIR_new_reg_op(self.ctx, self.state.?.vm_reg.?), instance, - m.MIR_new_uint_op(self.ctx, @as(u64, @intFromPtr(lexemes[property.name].ptr))), - m.MIR_new_uint_op(self.ctx, lexemes[property.name].len), + m.MIR_new_uint_op( + self.ctx, + type_def.?.resolved_type.?.ForeignContainer + .fields + .getIndex(lexemes[property.name]).?, + ), (try self.generateNode(property.value)).?, }, ); diff --git a/src/Parser.zig b/src/Parser.zig index 725aacf1..7c465111 100644 --- a/src/Parser.zig +++ b/src/Parser.zig @@ -1825,35 +1825,52 @@ fn resolvePlaceholderWithRelation( .List => { std.debug.assert(child_placeholder.name != null); - if (try obj.ObjList.ListDef.member(resolved_type, self, child_placeholder.name.?.string)) |member| { + if (try obj.ObjList.ListDef.member( + resolved_type, + self, + child_placeholder.name.?.string, + )) |member| { try self.resolvePlaceholder(child, member, false); } }, .Map => { std.debug.assert(child_placeholder.name != null); - if (try obj.ObjMap.MapDef.member(resolved_type, self, child_placeholder.name.?.string)) |member| { + if (try obj.ObjMap.MapDef.member( + resolved_type, + self, + child_placeholder.name.?.string, + )) |member| { try self.resolvePlaceholder(child, member, false); } }, .String => { std.debug.assert(child_placeholder.name != null); - if (try obj.ObjString.memberDef(self, child_placeholder.name.?.string)) |member| { + if (try obj.ObjString.memberDefByName( + self, + child_placeholder.name.?.string, + )) |member| { try self.resolvePlaceholder(child, member, false); } }, .Pattern => { std.debug.assert(child_placeholder.name != null); - if (try obj.ObjPattern.memberDef(self, child_placeholder.name.?.string)) |member| { + if (try obj.ObjPattern.memberDefByName( + self, + child_placeholder.name.?.string, + )) |member| { try self.resolvePlaceholder(child, member, false); } }, .Fiber => { std.debug.assert(child_placeholder.name != null); - if (try obj.ObjFiber.memberDef(self, child_placeholder.name.?.string)) |member| { + if (try obj.ObjFiber.memberDefByName( + self, + child_placeholder.name.?.string, + )) |member| { try self.resolvePlaceholder(child, member, false); } }, @@ -3007,7 +3024,8 @@ fn parseObjType(self: *Self, generic_types: ?std.AutoArrayHashMap(*obj.ObjString var tuple_index: u8 = 0; var obj_is_tuple = false; var obj_is_not_tuple = false; - while (!self.check(.RightBrace) and !self.check(.Eof)) { + var property_idx: usize = 0; + while (!self.check(.RightBrace) and !self.check(.Eof)) : (property_idx += 1) { const constant = try self.match(.Const); const property_type = try self.parseTypeDef(generic_types, true); @@ -3075,6 +3093,7 @@ fn parseObjType(self: *Self, generic_types: ?std.AutoArrayHashMap(*obj.ObjString .static = false, .method = false, .has_default = false, + .index = property_idx, }, ); try field_names.put(property_name_lexeme, {}); @@ -3920,7 +3939,8 @@ fn anonymousObjectInit(self: *Self, _: bool) Error!Ast.Node.Index { var tuple_index: u8 = 0; var obj_is_tuple = false; var obj_is_not_tuple = false; - while (!self.check(.RightBrace) and !self.check(.Eof)) { + var property_idx: usize = 0; + while (!self.check(.RightBrace) and !self.check(.Eof)) : (property_idx += 1) { // Unnamed: this expression is a little bit tricky: // - either an identifier followed by something other than = // - or not an identifier @@ -3992,6 +4012,7 @@ fn anonymousObjectInit(self: *Self, _: bool) Error!Ast.Node.Index { .method = false, .constant = false, .has_default = false, + .index = property_idx, }, ); } else { @@ -4042,6 +4063,7 @@ fn anonymousObjectInit(self: *Self, _: bool) Error!Ast.Node.Index { .method = false, .constant = false, .has_default = false, + .index = property_idx, }, ); } @@ -4103,7 +4125,7 @@ fn dot(self: *Self, can_assign: bool, callee: Ast.Node.Index) Error!Ast.Node.Ind const callee_def_type = callee_type_def.?.def_type; switch (callee_def_type) { .String => { - if (try obj.ObjString.memberDef(self, member_name)) |member_type_def| { + if (try obj.ObjString.memberDefByName(self, member_name)) |member_type_def| { const generic_resolve = if (try self.match(.DoubleColon)) try self.parseGenericResolve(member_type_def, null) else @@ -4143,7 +4165,7 @@ fn dot(self: *Self, can_assign: bool, callee: Ast.Node.Index) Error!Ast.Node.Ind } }, .Range => { - if (try obj.ObjRange.memberDef(self, member_name)) |member_type_def| { + if (try obj.ObjRange.memberDefByName(self, member_name)) |member_type_def| { const generic_resolve = if (try self.match(.DoubleColon)) try self.parseGenericResolve(member_type_def, null) else @@ -4178,15 +4200,12 @@ fn dot(self: *Self, can_assign: bool, callee: Ast.Node.Index) Error!Ast.Node.Ind self.ast.nodes.items(.components)[dot_node].Dot.member_kind = .Ref; self.ast.nodes.items(.type_def)[dot_node] = member; } - } else if (std.mem.eql(u8, member_name, "high") or std.mem.eql(u8, member_name, "low")) { - self.ast.nodes.items(.components)[dot_node].Dot.member_kind = .Ref; - self.ast.nodes.items(.type_def)[dot_node] = self.gc.type_registry.int_type; } else { self.reportError(.property_does_not_exists, "Range property doesn't exist."); } }, .Pattern => { - if (try obj.ObjPattern.memberDef(self, member_name)) |member_type_def| { + if (try obj.ObjPattern.memberDefByName(self, member_name)) |member_type_def| { const generic_resolve = if (try self.match(.DoubleColon)) try self.parseGenericResolve(member_type_def, null) else @@ -4226,7 +4245,7 @@ fn dot(self: *Self, can_assign: bool, callee: Ast.Node.Index) Error!Ast.Node.Ind } }, .Fiber => { - if (try obj.ObjFiber.memberDef(self, member_name)) |member_type_def| { + if (try obj.ObjFiber.memberDefByName(self, member_name)) |member_type_def| { const generic_resolve = if (try self.match(.DoubleColon)) try self.parseGenericResolve(member_type_def, null) else @@ -6335,6 +6354,8 @@ fn objectDeclaration(self: *Self) Error!Ast.Node.Index { var properties_type = std.StringHashMap(*obj.ObjTypeDef).init(self.gc.allocator); // Docblocks + var property_idx: usize = 0; + var static_property_idx: usize = 0; while (!self.check(.RightBrace) and !self.check(.Eof)) { const docblock = if (try self.match(.Docblock)) self.current_token.? - 1 @@ -6403,9 +6424,19 @@ fn objectDeclaration(self: *Self) Error!Ast.Node.Index { .location = self.ast.tokens.get(method_token), .method = true, .has_default = false, + .index = if (static) + static_property_idx + else + property_idx, }, ); + if (static) { + static_property_idx += 1; + } else { + property_idx += 1; + } + try members.append( .{ .name = method_token, @@ -6507,9 +6538,19 @@ fn objectDeclaration(self: *Self) Error!Ast.Node.Index { .location = property_name, .method = false, .has_default = default != null, + .index = if (static) + static_property_idx + else + property_idx, }, ); + if (static) { + static_property_idx += 1; + } else { + property_idx += 1; + } + try members.append( .{ .name = property_token, @@ -8444,7 +8485,6 @@ fn returnStatement(self: *Self) Error!Ast.Node.Index { if (value) |uvalue| { try self.consume(.Semicolon, "Expected `;` after statement."); - // Tail call (TODO: do it for dot call) if (self.ast.nodes.items(.tag)[uvalue] == .Call) { self.ast.nodes.items(.components)[uvalue].Call.tail_call = true; } else if (self.ast.nodes.items(.tag)[uvalue] == .Dot and self.ast.nodes.items(.components)[uvalue].Dot.member_kind == .Call) { diff --git a/src/builtin/list.zig b/src/builtin/list.zig index 0bb21c64..c988094d 100644 --- a/src/builtin/list.zig +++ b/src/builtin/list.zig @@ -66,7 +66,10 @@ pub fn reverse(ctx: *NativeCtx) c_int { var new_list = ctx.vm.gc.allocateObject( ObjList, - ObjList.init(ctx.vm.gc.allocator, list.type_def), + ObjList.init(ctx.vm.gc.allocator, list.type_def) catch { + ctx.vm.panic("Out of memory"); + unreachable; + }, ) catch { ctx.vm.panic("Out of memory"); unreachable; @@ -181,7 +184,10 @@ pub fn clone(ctx: *NativeCtx) c_int { var new_list = ctx.vm.gc.allocateObject( ObjList, - ObjList.init(ctx.vm.gc.allocator, self.type_def), + ObjList.init(ctx.vm.gc.allocator, self.type_def) catch { + ctx.vm.panic("Out of memory"); + unreachable; + }, ) catch { ctx.vm.panic("Out of memory"); unreachable; @@ -247,11 +253,18 @@ pub fn sub(ctx: *NativeCtx) c_int { self.items.items.len; const substr = self.items.items[@intCast(start)..limit]; + var methods = std.ArrayList(?*_obj.ObjNative) + .fromOwnedSlice(ctx.vm.gc.allocator, self.methods) + .clone() catch { + ctx.vm.panic("Out of memory"); + unreachable; + }; + var list = ctx.vm.gc.allocateObject( ObjList, .{ .type_def = self.type_def, - .methods = self.methods.clone() catch { + .methods = methods.toOwnedSlice() catch { ctx.vm.panic("Out of memory"); unreachable; }, @@ -355,7 +368,10 @@ pub fn filter(ctx: *NativeCtx) c_int { ObjList.init( ctx.vm.gc.allocator, list.type_def, - ), + ) catch { + ctx.vm.panic("Out of memory"); + unreachable; + }, ) catch { ctx.vm.panic("Out of memory"); unreachable; @@ -396,7 +412,10 @@ pub fn map(ctx: *NativeCtx) c_int { ObjList.init( ctx.vm.gc.allocator, mapped_type, - ), + ) catch { + ctx.vm.panic("Out of memory"); + unreachable; + }, ) catch { ctx.vm.panic("Out of memory"); unreachable; diff --git a/src/builtin/map.zig b/src/builtin/map.zig index fd396333..6056bed5 100644 --- a/src/builtin/map.zig +++ b/src/builtin/map.zig @@ -20,7 +20,10 @@ pub fn clone(ctx: *NativeCtx) c_int { var new_map = ctx.vm.gc.allocateObject( ObjMap, - ObjMap.init(ctx.vm.gc.allocator, self.type_def), + ObjMap.init(ctx.vm.gc.allocator, self.type_def) catch { + ctx.vm.panic("Out of memory"); + unreachable; + }, ) catch { ctx.vm.panic("Out of memory"); unreachable; @@ -70,7 +73,10 @@ pub fn filter(ctx: *NativeCtx) c_int { ObjMap.init( ctx.vm.gc.allocator, self.type_def, - ), + ) catch { + ctx.vm.panic("Out of memory"); + unreachable; + }, ) catch { ctx.vm.panic("Out of memory"); unreachable; @@ -147,20 +153,15 @@ pub fn map(ctx: *NativeCtx) c_int { ctx.vm.panic("Out of memory"); unreachable; }, - ), + ) catch { + ctx.vm.panic("Out of memory"); + unreachable; + }, ) catch { ctx.vm.panic("Out of memory"); unreachable; }; - const key_str = ctx.vm.gc.copyString("key") catch { - ctx.vm.panic("Out of memory"); - unreachable; - }; - const value_str = ctx.vm.gc.copyString("value") catch { - ctx.vm.panic("Out of memory"); - unreachable; - }; var it = self.map.iterator(); while (it.next()) |kv| { var args = [_]*const Value{ kv.key_ptr, kv.value_ptr }; @@ -173,11 +174,15 @@ pub fn map(ctx: *NativeCtx) c_int { null, ); - const entry = ObjObjectInstance.cast(ctx.vm.pop().obj()).?; - const key = entry.fields.get(key_str).?; - const value = entry.fields.get(value_str).?; + const instance = ObjObjectInstance.cast(ctx.vm.pop().obj()).?; + const object_def = instance.type_def.resolved_type.?.ObjectInstance + .resolved_type.?.Object; - new_map.set(ctx.vm.gc, key, value) catch { + new_map.set( + ctx.vm.gc, + instance.fields[object_def.fields.get("key").?.index], + instance.fields[object_def.fields.get("value").?.index], + ) catch { ctx.vm.panic("Out of memory"); unreachable; }; @@ -238,7 +243,10 @@ pub fn diff(ctx: *NativeCtx) c_int { ObjMap.init( ctx.vm.gc.allocator, lhs.type_def, - ), + ) catch { + ctx.vm.panic("Out of memory"); + unreachable; + }, ) catch { ctx.vm.panic("Out of memory"); unreachable; @@ -273,7 +281,10 @@ pub fn intersect(ctx: *NativeCtx) c_int { ObjMap.init( ctx.vm.gc.allocator, lhs.type_def, - ), + ) catch { + ctx.vm.panic("Out of memory"); + unreachable; + }, ) catch { ctx.vm.panic("Out of memory"); unreachable; @@ -353,7 +364,10 @@ pub fn keys(ctx: *NativeCtx) c_int { var list = ctx.vm.gc.allocateObject( ObjList, - ObjList.init(ctx.vm.gc.allocator, list_def_type), + ObjList.init(ctx.vm.gc.allocator, list_def_type) catch { + ctx.vm.panic("Out of memory"); + unreachable; + }, ) catch { ctx.vm.panic("Out of memory"); unreachable; @@ -396,7 +410,10 @@ pub fn values(ctx: *NativeCtx) c_int { var list = ctx.vm.gc.allocateObject( ObjList, - ObjList.init(ctx.vm.gc.allocator, list_def_type), + ObjList.init(ctx.vm.gc.allocator, list_def_type) catch { + ctx.vm.panic("Out of memory"); + unreachable; + }, ) catch { ctx.vm.panic("Out of memory"); unreachable; diff --git a/src/builtin/pattern.zig b/src/builtin/pattern.zig index ac3ba0b8..150c2c2a 100644 --- a/src/builtin/pattern.zig +++ b/src/builtin/pattern.zig @@ -89,7 +89,7 @@ fn rawMatch(self: *ObjPattern, vm: *VM, subject: *ObjString, offset: *usize) !?* results = try vm.gc.allocateObject( ObjList, - ObjList.init( + try ObjList.init( vm.gc.allocator, vm.gc.type_registry.str_type, ), @@ -126,7 +126,7 @@ fn rawMatchAll(self: *ObjPattern, vm: *VM, subject: *ObjString) !?*ObjList { const was_null = results == null; results = results orelse try vm.gc.allocateObject( ObjList, - ObjList.init(vm.gc.allocator, matches.type_def), + try ObjList.init(vm.gc.allocator, matches.type_def), ); if (was_null) { diff --git a/src/builtin/range.zig b/src/builtin/range.zig index ec680f90..91f122b0 100644 --- a/src/builtin/range.zig +++ b/src/builtin/range.zig @@ -9,7 +9,10 @@ pub fn toList(ctx: *obj.NativeCtx) c_int { obj.ObjList.init( ctx.vm.gc.allocator, ctx.vm.gc.type_registry.int_type, - ), + ) catch { + ctx.vm.panic("Out of memory"); + unreachable; + }, ) catch { ctx.vm.panic("Out of memory"); unreachable; @@ -137,3 +140,23 @@ pub fn @"union"(ctx: *obj.NativeCtx) c_int { return 1; } + +pub fn high(ctx: *obj.NativeCtx) c_int { + ctx.vm.push( + Value.fromInteger( + ctx.vm.peek(0).obj().access(obj.ObjRange, .Range, ctx.vm.gc).?.high, + ), + ); + + return 1; +} + +pub fn low(ctx: *obj.NativeCtx) c_int { + ctx.vm.push( + Value.fromInteger( + ctx.vm.peek(0).obj().access(obj.ObjRange, .Range, ctx.vm.gc).?.low, + ), + ); + + return 1; +} diff --git a/src/builtin/str.zig b/src/builtin/str.zig index 45b6c7fe..c3886a58 100644 --- a/src/builtin/str.zig +++ b/src/builtin/str.zig @@ -78,7 +78,10 @@ pub fn utf8Codepoints(ctx: *NativeCtx) c_int { var list = (ctx.vm.gc.allocateObject( ObjList, - ObjList.init(ctx.vm.gc.allocator, list_def_type), + ObjList.init(ctx.vm.gc.allocator, list_def_type) catch { + ctx.vm.panic("Out of memory"); + unreachable; + }, ) catch { ctx.vm.panic("Out of memory"); unreachable; @@ -267,7 +270,10 @@ pub fn split(ctx: *NativeCtx) c_int { var list = ctx.vm.gc.allocateObject( ObjList, - ObjList.init(ctx.vm.gc.allocator, list_def_type), + ObjList.init(ctx.vm.gc.allocator, list_def_type) catch { + ctx.vm.panic("Out of memory"); + unreachable; + }, ) catch { ctx.vm.panic("Out of memory"); unreachable; diff --git a/src/buzz_api.zig b/src/buzz_api.zig index 0fe280aa..59187a15 100644 --- a/src/buzz_api.zig +++ b/src/buzz_api.zig @@ -274,7 +274,7 @@ fn valueDump(value: Value, vm: *VM, seen: *std.AutoHashMap(*_obj.Obj, void), dep }, ); - if (object.fields.get(vm.gc.copyString(kv.key_ptr.*) catch unreachable)) |v| { + if (if (field.static) object.fields[field.index] else object.defaults[field.index]) |v| { io.print(" = ", .{}); valueDump(v, vm, seen, depth + 1); } @@ -294,6 +294,8 @@ fn valueDump(value: Value, vm: *VM, seen: *std.AutoHashMap(*_obj.Obj, void), dep .ObjectInstance => { const object_instance = ObjObjectInstance.cast(value.obj()).?; + const object_def = object_instance.type_def.resolved_type.?.ObjectInstance + .resolved_type.?.Object; io.print( "{s}{{ ", @@ -304,10 +306,21 @@ fn valueDump(value: Value, vm: *VM, seen: *std.AutoHashMap(*_obj.Obj, void), dep ".", }, ); - var it = object_instance.fields.iterator(); - while (it.next()) |kv| { - io.print("{s} = ", .{kv.key_ptr.*.string}); - valueDump(kv.value_ptr.*, vm, seen, depth + 1); + for (object_instance.fields, 0..) |field, i| { + var it = object_def.fields.iterator(); + const field_def: ObjObject.ObjectDef.Field = field_def: { + while (it.next()) |kv| { + const f = kv.value_ptr.*; + if (f.index == i and !f.static and !f.method) { + break :field_def f; + } + } + + unreachable; + }; + + io.print("{s} = ", .{field_def.name}); + valueDump(field, vm, seen, depth + 1); io.print(", ", .{}); } io.print("}}", .{}); @@ -533,7 +546,7 @@ export fn bz_newList(vm: *VM, of_type: Value) Value { return (vm.gc.allocateObject( ObjList, - ObjList.init(vm.gc.allocator, list_def_type), + ObjList.init(vm.gc.allocator, list_def_type) catch @panic("Out of memory"), ) catch @panic("Could not create list")).toValue(); } @@ -621,7 +634,10 @@ export fn bz_newVM(self: *VM) ?*VM { return null; }; // FIXME: should share strings between gc - gc.* = GarbageCollector.init(self.gc.allocator); + gc.* = GarbageCollector.init(self.gc.allocator) catch { + vm.panic("Out of memory"); + unreachable; + }; gc.type_registry = TypeRegistry.init(gc) catch { vm.panic("Out of memory"); unreachable; @@ -760,7 +776,7 @@ fn calleeIsCompiled(value: Value) bool { pub export fn bz_invoke( self: *VM, instance: Value, - method: *ObjString, + method_idx: usize, arguments: ?[*]const *const Value, len: u8, catch_value: ?*Value, @@ -776,11 +792,11 @@ pub export fn bz_invoke( // TODO: catch properly const callee = self.invoke( - method, + false, + method_idx, len, if (catch_value) |v| v.* else null, false, - false, ) catch unreachable; // If not compiled, run it with the VM loop @@ -809,7 +825,6 @@ pub export fn bz_call( closure.toValue(), len, if (catch_value) |v| v.* else null, - false, ) catch unreachable; // If not compiled, run it with the VM loop @@ -830,19 +845,21 @@ export fn bz_instanceQualified(self: *VM, qualified_name: [*]const u8, len: usiz self.panic("Out of memory"); unreachable; }, - ), + self.gc, + ) catch @panic("Out of memory"), ) catch { @panic("Could not create error"); }; // Set instance fields with default values - var it = object.fields.iterator(); - while (it.next()) |kv| { - instance.setField( - self.gc, - kv.key_ptr.*, - self.cloneValue(kv.value_ptr.*) catch @panic("Could not set object property"), - ) catch @panic("Could not set object property"); + for (object.defaults, 0..) |default, idx| { + if (default) |udefault| { + instance.setField( + self.gc, + idx, + self.cloneValue(udefault) catch @panic("Could not set object property"), + ) catch @panic("Could not set object property"); + } } return instance.toValue(); @@ -859,19 +876,15 @@ fn instanciateError( if (message) |msg| { const obj_instance = ObjObjectInstance.cast(instance.obj()).?; - const message_key = vm.gc.strings.get("message").?; + const object_def = obj_instance.type_def.resolved_type.?.ObjectInstance + .resolved_type.?.Object + .fields; - if (obj_instance.fields.get(message_key) != null) { - obj_instance.fields.put( - message_key, - (vm.gc.copyString(msg[0..mlen]) catch { - vm.panic("Out of memory"); - unreachable; - }).toValue(), - ) catch { + if (object_def.get("message")) |field| { + obj_instance.fields[field.index] = (vm.gc.copyString(msg[0..mlen]) catch { vm.panic("Out of memory"); unreachable; - }; + }).toValue(); } } @@ -946,57 +959,60 @@ export fn bz_instance(vm: *VM, object_value: Value, typedef_value: Value) Value vm, object, typedef, - ), + vm.gc, + ) catch @panic("Out of memory"), ) catch @panic("Could not instanciate object"); // If not anonymous, set default fields if (object) |uobject| { - var it = uobject.fields.iterator(); - while (it.next()) |kv| { - instance.setField( - vm.gc, - kv.key_ptr.*, - vm.cloneValue(kv.value_ptr.*) catch @panic("Could not set object field"), - ) catch @panic("Could not set object field"); + for (uobject.defaults, 0..) |default, idx| { + if (default) |udefault| { + instance.setField( + vm.gc, + idx, + vm.cloneValue(udefault) catch @panic("Could not set object field"), + ) catch @panic("Could not set object field"); + } } } return instance.toValue(); } -export fn bz_getObjectField(object_value: Value, field_name_value: Value) Value { - const object = ObjObject.cast(object_value.obj()).?; - - return object.static_fields.get(ObjString.cast(field_name_value.obj()).?).?; +export fn bz_getObjectField(object_value: Value, field_idx: usize) Value { + return ObjObject.cast(object_value.obj()).?.fields[field_idx]; } -export fn bz_setObjectField(vm: *VM, object_value: Value, field_name_value: Value, value: Value) void { - const object = ObjObject.cast(object_value.obj()).?; - const field = ObjString.cast(field_name_value.obj()).?; - - object.setStaticField(vm.gc, field, value) catch @panic("Could not set static field"); +export fn bz_setObjectField(vm: *VM, object_value: Value, field_idx: usize, value: Value) void { + ObjObject.cast(object_value.obj()).?.setField( + vm.gc, + field_idx, + value, + ) catch @panic("Could not set static field"); } -export fn bz_setInstanceField(vm: *VM, instance_value: Value, field_name_value: Value, value: Value) void { +export fn bz_setInstanceProperty(vm: *VM, instance_value: Value, field_idx: usize, value: Value) void { ObjObjectInstance.cast(instance_value.obj()).?.setField( vm.gc, - ObjString.cast(field_name_value.obj()).?, + field_idx, value, ) catch @panic("Could not set instance field"); } -export fn bz_getInstanceField(vm: *VM, instance_value: Value, field_name_value: Value, bind: bool) Value { - const instance = ObjObjectInstance.cast(instance_value.obj()).?; - if (instance.fields.get(ObjString.cast(field_name_value.obj()).?)) |field| { - return field; - } +export fn bz_getInstanceProperty(instance_value: Value, property_idx: usize) Value { + return ObjObjectInstance.cast(instance_value.obj()).? + .fields[property_idx]; +} - const method = instance.object.?.methods.get(ObjString.cast(field_name_value.obj()).?).?.toValue(); +export fn bz_getInstanceMethod(vm: *VM, instance_value: Value, method_idx: usize, bind: bool) Value { + const method = ObjObjectInstance.cast(instance_value.obj()).? + .object.? + .fields[method_idx]; return if (bind) bz_bindMethod( vm, - instance.toValue(), + method, method, Value.Null, ) @@ -1004,6 +1020,29 @@ export fn bz_getInstanceField(vm: *VM, instance_value: Value, field_name_value: method; } +export fn bz_getProtocolMethod(vm: *VM, instance_value: Value, method_name: Value) Value { + const instance = instance_value.obj().access( + ObjObjectInstance, + .ObjectInstance, + vm.gc, + ).?; + + const name = method_name.obj() + .access(ObjString, .String, vm.gc).? + .string; + + const method_idx = instance.type_def.resolved_type.?.ObjectInstance + .resolved_type.?.Object + .fields.get(name).?.index; + + return bz_bindMethod( + vm, + instance_value, + instance.object.?.fields[method_idx], + Value.Null, + ); +} + export fn bz_bindMethod(vm: *VM, receiver: Value, method_value: Value, native_value: Value) Value { return (vm.gc.allocateObject( ObjBoundMethod, @@ -1115,10 +1154,13 @@ export fn bz_valueTypeOf(self: Value, vm: *VM) Value { } export fn bz_newMap(vm: *VM, map_type: Value) Value { - var map: *ObjMap = vm.gc.allocateObject(ObjMap, ObjMap.init( - vm.gc.allocator, - ObjTypeDef.cast(map_type.obj()).?, - )) catch @panic("Could not create map"); + var map: *ObjMap = vm.gc.allocateObject( + ObjMap, + ObjMap.init( + vm.gc.allocator, + ObjTypeDef.cast(map_type.obj()).?, + ) catch @panic("Could not create map"), + ) catch @panic("Could not create map"); return Value.fromObj(map.toObj()); } @@ -1325,101 +1367,90 @@ export fn bz_dumpStack(ctx: *NativeCtx, off: usize) void { dumpStack(ctx.vm); } -export fn bz_getStringField(vm: *VM, string_value: Value, field_name_value: Value, bind: bool) Value { - const string = ObjString.cast(string_value.obj()).?; - const method = (ObjString.member(vm, ObjString.cast(field_name_value.obj()).?) catch @panic("Could not get string method")).?; +export fn bz_getStringProperty(vm: *VM, string_value: Value, property_idx: usize, bind: bool) Value { + const method = ObjString.member(vm, property_idx) catch @panic("Out of memory").?; return if (bind) bz_bindMethod( vm, - string.toValue(), + string_value, Value.Null, - method.toValue(), + method, ) else - method.toValue(); + method; } -export fn bz_getPatternField(vm: *VM, pattern_value: Value, field_name_value: Value, bind: bool) Value { - const pattern = ObjPattern.cast(pattern_value.obj()).?; - const method = (ObjPattern.member(vm, ObjString.cast(field_name_value.obj()).?) catch @panic("Could not get pattern method")).?; +export fn bz_getPatternProperty(vm: *VM, pattern_value: Value, property_idx: usize, bind: bool) Value { + const method = ObjPattern.member(vm, property_idx) catch @panic("Could not get pattern method"); return if (bind) bz_bindMethod( vm, - pattern.toValue(), + pattern_value, Value.Null, - method.toValue(), + method, ) else - method.toValue(); + method; } -export fn bz_getFiberField(vm: *VM, fiber_value: Value, field_name_value: Value, bind: bool) Value { - const fiber = ObjFiber.cast(fiber_value.obj()).?; - const method = (ObjFiber.member(vm, ObjString.cast(field_name_value.obj()).?) catch @panic("Could not get fiber method")).?; +export fn bz_getFiberProperty(vm: *VM, fiber_value: Value, property_idx: usize, bind: bool) Value { + const method = ObjFiber.member(vm, property_idx) catch @panic("Could not get fiber method"); return if (bind) bz_bindMethod( vm, - fiber.toValue(), + fiber_value, Value.Null, - method.toValue(), + method, ) else - method.toValue(); + method; } -export fn bz_getListField(vm: *VM, list_value: Value, field_name_value: Value, bind: bool) Value { - const field_name = ObjString.cast(field_name_value.obj()).?; - const list = ObjList.cast(list_value.obj()).?; - const method = (list.member(vm, field_name) catch @panic("Could not get list method")).?; +export fn bz_getListProperty(vm: *VM, list_value: Value, property_idx: usize, bind: bool) Value { + const method = ObjList.cast(list_value.obj()).? + .member(vm, property_idx) catch @panic("Could not get list method"); return if (bind) bz_bindMethod( vm, - list.toValue(), + list_value, Value.Null, - method.toValue(), + method, ) else - method.toValue(); + method; } -export fn bz_getRangeField(vm: *VM, range_value: Value, field_name_value: Value, bind: bool) Value { - const range = ObjRange.cast(range_value.obj()).?; - const member_name = ObjString.cast(field_name_value.obj()).?; +export fn bz_getRangeProperty(vm: *VM, range_value: Value, property_idx: usize, bind: bool) Value { + const method = ObjRange.member(vm, property_idx) catch @panic("Out of memory"); - if (ObjRange.member(vm, member_name) catch @panic("Could not get range method")) |method| { - return if (bind) - bz_bindMethod( - vm, - range.toValue(), - Value.Null, - method.toValue(), - ) - else - method.toValue(); - } else if (std.mem.eql(u8, member_name.string, "high")) { - return Value.fromInteger(range.high); - } - - return Value.fromInteger(range.low); + return if (bind) + bz_bindMethod( + vm, + range_value, + Value.Null, + method, + ) + else + method; } -export fn bz_getMapField(vm: *VM, map_value: Value, field_name_value: Value, bind: bool) Value { - const map = ObjMap.cast(map_value.obj()).?; - const method = (map.member(vm, ObjString.cast(field_name_value.obj()).?) catch @panic("Could not get map method")).?; +export fn bz_getMapProperty(vm: *VM, map_value: Value, property_idx: usize, bind: bool) Value { + const method = ObjMap.cast(map_value.obj()).? + .member(vm, property_idx) catch @panic("Could not get map method"); return if (bind) bz_bindMethod( vm, - map.toValue(), + map_value, Value.Null, - method.toValue(), + method, ) else - method.toValue(); + method; } export fn bz_stringNext(vm: *VM, string_value: Value, index: *Value) Value { @@ -1817,19 +1848,24 @@ export fn bz_writeZigValueToBuffer( } } -export fn bz_containerGet(vm: *VM, value: Value, field: [*]const u8, len: usize) Value { +export fn bz_containerGet(vm: *VM, value: Value, field_idx: usize) Value { const container = ObjForeignContainer.cast(value.obj()).?; - // Oh right that's beautiful enough... - return container.type_def.resolved_type.?.ForeignContainer.fields.get(field[0..len]).?.getter( + + return container.type_def.resolved_type.? + .ForeignContainer + .fields.values()[field_idx] + .getter( vm, container.data.ptr, ); } -export fn bz_containerSet(vm: *VM, value: Value, field: [*]const u8, len: usize, new_value: Value) void { +export fn bz_containerSet(vm: *VM, value: Value, field_idx: usize, new_value: Value) void { const container = ObjForeignContainer.cast(value.obj()).?; // Oh right that's beautiful enough... - return container.type_def.resolved_type.?.ForeignContainer.fields.get(field[0..len]).?.setter( + return container.type_def.resolved_type.?.ForeignContainer + .fields.values()[field_idx] + .setter( vm, container.data.ptr, new_value, diff --git a/src/disassembler.zig b/src/disassembler.zig index e4d50dfc..bcedb223 100644 --- a/src/disassembler.zig +++ b/src/disassembler.zig @@ -21,7 +21,7 @@ pub fn disassembleChunk(chunk: *Chunk, name: []const u8) void { print("\u{001b}[0m", .{}); } -fn invokeInstruction(code: OpCode, chunk: *Chunk, offset: usize) usize { +fn namedInvokeInstruction(code: OpCode, chunk: *Chunk, offset: usize) usize { const constant: u24 = @intCast(0x00ffffff & chunk.code.items[offset]); const arg_count: u8 = @intCast(chunk.code.items[offset + 1] >> 24); const catch_count: u24 = @intCast(0x00ffffff & chunk.code.items[offset + 1]); @@ -29,16 +29,37 @@ fn invokeInstruction(code: OpCode, chunk: *Chunk, offset: usize) usize { var value_str = chunk.constants.items[constant].toStringAlloc(global_allocator) catch @panic("Out of memory"); defer value_str.deinit(); - print("{s}\t{s}({} args, {} catches)", .{ - @tagName(code), - value_str.items[0..@min(value_str.items.len, 100)], - arg_count, - catch_count, - }); + print( + "{s}\t{s}({} args, {} catches)", + .{ + @tagName(code), + value_str.items[0..@min(value_str.items.len, 100)], + arg_count, + catch_count, + }, + ); return offset + 2; } +fn invokeInstruction(code: OpCode, chunk: *Chunk, offset: usize) usize { + const method_idx: u24 = @intCast(0x00ffffff & chunk.code.items[offset]); + const arg_count: u8 = @intCast(chunk.code.items[offset + 1] >> 24); + const catch_count: u24 = @intCast(0x00ffffff & chunk.code.items[offset + 1]); + + print( + "{s}\t{}({} args, {} catches)", + .{ + @tagName(code), + method_idx, + arg_count, + catch_count, + }, + ); + + return offset + 1; +} + fn simpleInstruction(code: OpCode, offset: usize) usize { print("{s}\t", .{@tagName(code)}); @@ -272,25 +293,27 @@ pub fn disassembleInstruction(chunk: *Chunk, offset: usize) usize { .OP_RETURN, .OP_LIST_APPEND, .OP_SET_MAP, + .OP_PROPERTY, + .OP_OBJECT_DEFAULT, + .OP_SET_OBJECT_PROPERTY, + .OP_SET_INSTANCE_PROPERTY, + .OP_GET_INSTANCE_PROPERTY, + .OP_GET_INSTANCE_METHOD, + .OP_GET_LIST_PROPERTY, + .OP_GET_MAP_PROPERTY, + .OP_GET_FIBER_PROPERTY, + .OP_GET_RANGE_PROPERTY, + .OP_GET_STRING_PROPERTY, + .OP_GET_PATTERN_PROPERTY, + .OP_GET_OBJECT_PROPERTY, => byteInstruction(instruction, chunk, offset), .OP_OBJECT, .OP_LIST, .OP_MAP, .OP_RANGE, - .OP_METHOD, - .OP_PROPERTY, - .OP_GET_OBJECT_PROPERTY, - .OP_GET_INSTANCE_PROPERTY, + .OP_GET_PROTOCOL_METHOD, .OP_GET_FCONTAINER_INSTANCE_PROPERTY, - .OP_GET_LIST_PROPERTY, - .OP_GET_MAP_PROPERTY, - .OP_GET_STRING_PROPERTY, - .OP_GET_PATTERN_PROPERTY, - .OP_GET_FIBER_PROPERTY, - .OP_GET_RANGE_PROPERTY, - .OP_SET_OBJECT_PROPERTY, - .OP_SET_INSTANCE_PROPERTY, .OP_SET_FCONTAINER_INSTANCE_PROPERTY, .OP_CONSTANT, => constantInstruction(instruction, chunk, offset), @@ -304,6 +327,8 @@ pub fn disassembleInstruction(chunk: *Chunk, offset: usize) usize { .OP_LOOP => jumpInstruction(instruction, chunk, false, offset), + .OP_CALL_INSTANCE_PROPERTY, + .OP_TAIL_CALL_INSTANCE_PROPERTY, .OP_INSTANCE_INVOKE, .OP_INSTANCE_TAIL_INVOKE, .OP_STRING_INVOKE, @@ -314,10 +339,13 @@ pub fn disassembleInstruction(chunk: *Chunk, offset: usize) usize { .OP_RANGE_INVOKE, => invokeInstruction(instruction, chunk, offset), + .OP_PROTOCOL_INVOKE, + .OP_PROTOCOL_TAIL_INVOKE, + => namedInvokeInstruction(instruction, chunk, offset), + .OP_CALL, .OP_TAIL_CALL, .OP_FIBER, - .OP_INVOKE_FIBER, => triInstruction(instruction, chunk, offset), .OP_CLOSURE => closure: { @@ -568,7 +596,7 @@ pub const DumpState = struct { }, ) catch unreachable; - if (object.fields.get(state.vm.gc.copyString(kv.key_ptr.*) catch unreachable)) |v| { + if (object.defaults[field.index]) |v| { out.print(" = ", .{}) catch unreachable; state.valueDump( v, @@ -595,6 +623,9 @@ pub const DumpState = struct { .ObjectInstance => { const object_instance = obj.ObjObjectInstance.cast(value.obj()).?; + const fields = object_instance.type_def.resolved_type.?.ObjectInstance + .resolved_type.?.Object + .fields; out.print( "{s}{{\n", @@ -606,16 +637,15 @@ pub const DumpState = struct { }, ) catch unreachable; state.tab += 1; - var it = object_instance.fields.iterator(); - while (it.next()) |kv| { + for (object_instance.fields, 0..) |val, idx| { out.print( " {s} = ", .{ - kv.key_ptr.*.string, + fields.keys()[idx], }, ) catch unreachable; state.valueDump( - kv.value_ptr.*, + val, out, true, ); diff --git a/src/jit_extern_api.zig b/src/jit_extern_api.zig index 691a0c36..fbe7d3bd 100644 --- a/src/jit_extern_api.zig +++ b/src/jit_extern_api.zig @@ -39,18 +39,20 @@ pub const ExternApi = enum { bz_closure, bz_context, bz_instance, - bz_setInstanceField, - bz_getInstanceField, + bz_setInstanceProperty, + bz_getInstanceProperty, + bz_getInstanceMethod, + bz_getProtocolMethod, bz_getObjectField, bz_setObjectField, - bz_getStringField, - bz_getPatternField, - bz_getFiberField, + bz_getStringProperty, + bz_getPatternProperty, + bz_getFiberProperty, bz_getEnumCase, bz_getEnumCaseValue, - bz_getListField, - bz_getMapField, - bz_getRangeField, + bz_getListProperty, + bz_getMapProperty, + bz_getRangeProperty, bz_getEnumCaseFromValue, bz_bindMethod, bz_stringNext, @@ -349,7 +351,10 @@ pub const ExternApi = enum { }, }, ), - .bz_getObjectField, .bz_valueIs => m.MIR_new_proto_arr( + .bz_getObjectField, + .bz_valueIs, + .bz_getInstanceProperty, + => m.MIR_new_proto_arr( ctx, self.pname(), 1, @@ -368,13 +373,14 @@ pub const ExternApi = enum { }, }, ), - .bz_getListField, - .bz_getMapField, - .bz_getRangeField, - .bz_getStringField, - .bz_getPatternField, - .bz_getFiberField, - .bz_getInstanceField, + .bz_getListProperty, + .bz_getMapProperty, + .bz_getRangeProperty, + .bz_getStringProperty, + .bz_getPatternProperty, + .bz_getFiberProperty, + .bz_getInstanceMethod, + .bz_getProtocolMethod, => m.MIR_new_proto_arr( ctx, self.pname(), @@ -404,7 +410,7 @@ pub const ExternApi = enum { }, }, ), - .bz_setInstanceField, + .bz_setInstanceProperty, .bz_setObjectField, .bz_bindMethod, => m.MIR_new_proto_arr( @@ -829,7 +835,7 @@ pub const ExternApi = enum { self.pname(), 1, &[_]m.MIR_type_t{m.MIR_T_I64}, - 4, + 3, &[_]m.MIR_var_t{ .{ .type = m.MIR_T_P, @@ -841,14 +847,9 @@ pub const ExternApi = enum { .name = "value", .size = undefined, }, - .{ - .type = m.MIR_T_P, - .name = "field", - .size = undefined, - }, .{ .type = m.MIR_T_U64, - .name = "len", + .name = "field_idx", .size = undefined, }, }, @@ -858,7 +859,7 @@ pub const ExternApi = enum { self.pname(), 0, null, - 5, + 4, &[_]m.MIR_var_t{ .{ .type = m.MIR_T_P, @@ -870,14 +871,9 @@ pub const ExternApi = enum { .name = "value", .size = undefined, }, - .{ - .type = m.MIR_T_P, - .name = "field", - .size = undefined, - }, .{ .type = m.MIR_T_U64, - .name = "len", + .name = "field_idx", .size = undefined, }, .{ @@ -1029,8 +1025,10 @@ pub const ExternApi = enum { .bz_closure => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.VM.bz_closure))), .bz_context => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.VM.bz_context))), .bz_instance => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjObject.bz_instance))), - .bz_setInstanceField => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjObject.bz_setInstanceField))), - .bz_getInstanceField => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjObject.bz_getInstanceField))), + .bz_setInstanceProperty => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjObject.bz_setInstanceProperty))), + .bz_getInstanceProperty => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjObject.bz_getInstanceProperty))), + .bz_getInstanceMethod => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjObject.bz_getInstanceMethod))), + .bz_getProtocolMethod => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjObject.bz_getProtocolMethod))), .bz_rethrow => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.VM.bz_rethrow))), .bz_throw => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.VM.bz_throw))), .bz_bindMethod => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.VM.bz_bindMethod))), @@ -1045,12 +1043,12 @@ pub const ExternApi = enum { .bz_getEnumCaseValue => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjEnumInstance.bz_getEnumCaseValue))), .bz_setObjectField => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjObject.bz_setObjectField))), .bz_getObjectField => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjObject.bz_getObjectField))), - .bz_getListField => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjList.bz_getListField))), - .bz_getMapField => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjMap.bz_getMapField))), - .bz_getRangeField => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjRange.bz_getRangeField))), - .bz_getStringField => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjString.bz_getStringField))), - .bz_getPatternField => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjPattern.bz_getPatternField))), - .bz_getFiberField => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjFiber.bz_getFiberField))), + .bz_getListProperty => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjList.bz_getListProperty))), + .bz_getMapProperty => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjMap.bz_getMapProperty))), + .bz_getRangeProperty => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjRange.bz_getRangeProperty))), + .bz_getStringProperty => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjString.bz_getStringProperty))), + .bz_getPatternProperty => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjPattern.bz_getPatternProperty))), + .bz_getFiberProperty => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.ObjFiber.bz_getFiberProperty))), .bz_setTryCtx => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.VM.bz_setTryCtx))), .bz_popTryCtx => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.VM.bz_popTryCtx))), .bz_valueToCString => @as(*anyopaque, @ptrFromInt(@intFromPtr(&api.Value.bz_valueToCString))), @@ -1114,18 +1112,20 @@ pub const ExternApi = enum { .bz_closure => "bz_closure", .bz_context => "bz_context", .bz_instance => "bz_instance", - .bz_setInstanceField => "bz_setInstanceField", - .bz_getInstanceField => "bz_getInstanceField", + .bz_setInstanceProperty => "bz_setInstanceProperty", + .bz_getInstanceProperty => "bz_getInstanceProperty", + .bz_getInstanceMethod => "bz_getInstanceMethod", + .bz_getProtocolMethod => "bz_getProtocolMethod", .bz_setObjectField => "bz_setObjectField", .bz_getObjectField => "bz_getObjectField", - .bz_getStringField => "bz_getStringField", - .bz_getPatternField => "bz_getPatternField", - .bz_getFiberField => "bz_getFiberField", - .bz_getRangeField => "bz_getRangeField", + .bz_getStringProperty => "bz_getStringProperty", + .bz_getPatternProperty => "bz_getPatternProperty", + .bz_getFiberProperty => "bz_getFiberProperty", + .bz_getRangeProperty => "bz_getRangeProperty", .bz_getEnumCase => "bz_getEnumCase", .bz_getEnumCaseValue => "bz_getEnumCaseValue", - .bz_getListField => "bz_getListField", - .bz_getMapField => "bz_getMapField", + .bz_getListProperty => "bz_getListProperty", + .bz_getMapProperty => "bz_getMapProperty", .bz_getEnumCaseFromValue => "bz_getEnumCaseFromValue", .bz_bindMethod => "bz_bindMethod", .bz_stringNext => "bz_stringNext", @@ -1187,18 +1187,20 @@ pub const ExternApi = enum { .bz_closure => "p_bz_closure", .bz_context => "p_bz_context", .bz_instance => "p_bz_instance", - .bz_setInstanceField => "p_bz_setInstanceField", - .bz_getInstanceField => "p_bz_getInstanceField", + .bz_setInstanceProperty => "p_bz_setInstanceProperty", + .bz_getInstanceProperty => "p_bz_getInstanceProperty", + .bz_getInstanceMethod => "p_bz_getInstanceMethod", + .bz_getProtocolMethod => "p_bz_getProtocolMethod", .bz_setObjectField => "p_bz_setObjectField", .bz_getObjectField => "p_bz_getObjectField", - .bz_getStringField => "p_bz_getStringField", - .bz_getPatternField => "p_bz_getPatternField", - .bz_getFiberField => "p_bz_getFiberField", - .bz_getRangeField => "p_bz_getRangeField", + .bz_getStringProperty => "p_bz_getStringProperty", + .bz_getPatternProperty => "p_bz_getPatternProperty", + .bz_getFiberProperty => "p_bz_getFiberProperty", + .bz_getRangeProperty => "p_bz_getRangeProperty", .bz_getEnumCase => "p_bz_getEnumCase", .bz_getEnumCaseValue => "p_bz_getEnumCaseValue", - .bz_getListField => "p_bz_getListField", - .bz_getMapField => "p_bz_getMapField", + .bz_getListProperty => "p_bz_getListProperty", + .bz_getMapProperty => "p_bz_getMapProperty", .bz_getEnumCaseFromValue => "p_bz_getEnumCaseFromValue", .bz_bindMethod => "p_bz_bindMethod", .bz_stringNext => "p_bz_stringNext", diff --git a/src/lib/buzz_api.zig b/src/lib/buzz_api.zig index 6fcf7a7d..69fd3366 100644 --- a/src/lib/buzz_api.zig +++ b/src/lib/buzz_api.zig @@ -326,14 +326,14 @@ pub const ObjString = opaque { pub extern fn bz_objStringConcat(vm: *VM, obj_string: Value, other: Value) Value; pub extern fn bz_objStringSubscript(vm: *VM, obj_string: Value, index_value: Value) Value; pub extern fn bz_toString(vm: *VM, value: Value) Value; - pub extern fn bz_getStringField(vm: *VM, field_name_value: Value) Value; + pub extern fn bz_getStringProperty(vm: *VM, method_idx: usize) Value; pub extern fn bz_stringNext(vm: *VM, string_value: Value, index: *Value) Value; }; pub const ObjRange = opaque { pub extern fn bz_newRange(vm: *VM, low: i32, high: i32) Value; pub extern fn bz_rangeNext(range_value: Value, index_slot: Value) Value; - pub extern fn bz_getRangeField(vm: *VM, range_value: Value, field_name_value: Value, bind: bool) Value; + pub extern fn bz_getRangeProperty(vm: *VM, range_value: Value, property_idx: usize, bind: bool) Value; }; pub const ObjList = opaque { @@ -344,7 +344,7 @@ pub const ObjList = opaque { pub extern fn bz_listSet(vm: *VM, self: Value, index: usize, value: Value) void; pub extern fn bz_listLen(self: *ObjList) usize; pub extern fn bz_listConcat(vm: *VM, list: Value, other_list: Value) Value; - pub extern fn bz_getListField(vm: *VM, list_value: Value, field_name_value: Value, bind: bool) Value; + pub extern fn bz_getListProperty(vm: *VM, list_value: Value, property_idx: usize, bind: bool) Value; pub extern fn bz_listNext(vm: *VM, list_value: Value, index: *Value) Value; }; @@ -353,7 +353,7 @@ pub const ObjMap = opaque { pub extern fn bz_mapSet(vm: *VM, map: Value, key: Value, value: Value) void; pub extern fn bz_mapGet(map: Value, key: Value) Value; pub extern fn bz_mapConcat(vm: *VM, map: Value, other_map: Value) Value; - pub extern fn bz_getMapField(vm: *VM, map_value: Value, field_name_value: Value, bind: bool) Value; + pub extern fn bz_getMapProperty(vm: *VM, map_value: Value, property_idx: usize, bind: bool) Value; pub extern fn bz_mapNext(vm: *VM, map_value: Value, index: *Value) Value; }; @@ -368,10 +368,12 @@ pub const ObjObjectInstance = opaque {}; pub const ObjObject = opaque { pub extern fn bz_instanceQualified(self: *VM, qualified_name: [*]const u8, len: usize) Value; pub extern fn bz_instance(vm: *VM, object_value: Value, typedef_value: Value) Value; - pub extern fn bz_setInstanceField(vm: *VM, instance_value: Value, field_name_value: Value, value: Value) void; - pub extern fn bz_getInstanceField(vm: *VM, instance_value: Value, field_name_value: Value) Value; - pub extern fn bz_getObjectField(object_value: Value, field_name_value: Value) Value; - pub extern fn bz_setObjectField(vm: *VM, object_value: Value, field_name_value: Value, value: Value) void; + pub extern fn bz_setInstanceProperty(vm: *VM, instance_value: Value, property_idx: usize, value: Value) void; + pub extern fn bz_getInstanceProperty(vm: *VM, instance_value: Value, property_idx: usize) Value; + pub extern fn bz_getInstanceMethod(vm: *VM, instance_value: Value, method_idx: usize, bind: bool) Value; + pub extern fn bz_getProtocolMethod(vm: *VM, instance_value: Value, method_name: Value) Value; + pub extern fn bz_getObjectField(object_value: Value, field_idx: usize) Value; + pub extern fn bz_setObjectField(vm: *VM, object_value: Value, field_idx: usize, value: Value) void; }; pub const ObjEnumInstance = opaque { @@ -385,17 +387,17 @@ pub const ObjEnum = opaque { }; pub const ObjPattern = opaque { - pub extern fn bz_getPatternField(vm: *VM, field_name_value: Value) Value; + pub extern fn bz_getPatternProperty(vm: *VM, property_idx: usize) Value; }; pub const ObjFiber = opaque { - pub extern fn bz_getFiberField(vm: *VM, field_name_value: Value) Value; + pub extern fn bz_getFiberProperty(vm: *VM, property_idx: usize) Value; pub extern fn bz_isMainFiber(self: *ObjFiber, vm: *VM) Value; }; pub const ObjForeignContainer = opaque { - pub extern fn bz_containerGet(vm: *VM, value: Value, field: [*]const u8, len: usize) Value; - pub extern fn bz_containerSet(vm: *VM, value: Value, field: [*]const u8, len: usize, new_value: Value) void; + pub extern fn bz_containerGet(vm: *VM, value: Value, field_idx: usize) Value; + pub extern fn bz_containerSet(vm: *VM, value: Value, field_idx: usize, new_value: Value) void; pub extern fn bz_containerInstance(vm: *VM, typedef_value: Value) Value; pub extern fn bz_containerSlice(container_value: Value, len: *usize) [*]u8; pub extern fn bz_containerFromSlice(vm: *VM, type_def: *ObjTypeDef, ptr: [*]u8, len: usize) Value; diff --git a/src/lib/buzz_http.zig b/src/lib/buzz_http.zig index 66cf75c8..8134cb51 100644 --- a/src/lib/buzz_http.zig +++ b/src/lib/buzz_http.zig @@ -198,19 +198,10 @@ pub export fn HttpRequestRead(ctx: *api.NativeCtx) c_int { ); // Set body - api.ObjObject.bz_setInstanceField( + api.ObjObject.bz_setInstanceProperty( ctx.vm, response, - api.ObjString.bz_objStringToValue( - api.ObjString.bz_string( - ctx.vm, - "body".ptr, - "body".len, - ) orelse { - ctx.vm.bz_panic("Out of memory", "Out of memory".len); - unreachable; - }, - ), + 2, if (body_raw.items.len == 0) api.Value.Null else @@ -227,19 +218,10 @@ pub export fn HttpRequestRead(ctx: *api.NativeCtx) c_int { ); // Set status - api.ObjObject.bz_setInstanceField( + api.ObjObject.bz_setInstanceProperty( ctx.vm, response, - api.ObjString.bz_objStringToValue( - api.ObjString.bz_string( - ctx.vm, - "status".ptr, - "status".len, - ) orelse { - ctx.vm.bz_panic("Out of memory", "Out of memory".len); - unreachable; - }, - ), + 0, api.Value.fromInteger(@intFromEnum(request.response.status)), ); @@ -254,19 +236,10 @@ pub export fn HttpRequestRead(ctx: *api.NativeCtx) c_int { ), ); - api.ObjObject.bz_setInstanceField( + api.ObjObject.bz_setInstanceProperty( ctx.vm, response, - api.ObjString.bz_objStringToValue( - api.ObjString.bz_string( - ctx.vm, - "headers".ptr, - "headers".len, - ) orelse { - ctx.vm.bz_panic("Out of memory", "Out of memory".len); - unreachable; - }, - ), + 1, headers, ); diff --git a/src/lib/http.buzz b/src/lib/http.buzz index c9ffa1e8..f6e7b44b 100644 --- a/src/lib/http.buzz +++ b/src/lib/http.buzz @@ -260,6 +260,7 @@ export object Request { } } +| Don't change proeprties order export object Response { int status = 200, {str: str} headers = {}, diff --git a/src/main.zig b/src/main.zig index bdc44f71..4637e80a 100644 --- a/src/main.zig +++ b/src/main.zig @@ -74,7 +74,7 @@ fn printBanner(out: anytype, full: bool) void { fn runFile(allocator: Allocator, file_name: []const u8, args: [][:0]u8, flavor: RunFlavor) !void { var total_timer = if (!is_wasm) std.time.Timer.start() catch unreachable else {}; var import_registry = ImportRegistry.init(allocator); - var gc = GarbageCollector.init(allocator); + var gc = try GarbageCollector.init(allocator); gc.type_registry = try TypeRegistry.init(&gc); var imports = std.StringHashMap(Parser.ScriptImport).init(allocator); var vm = try VM.init(&gc, &import_registry, flavor); diff --git a/src/memory.zig b/src/memory.zig index ecf5d75e..103cf376 100644 --- a/src/memory.zig +++ b/src/memory.zig @@ -196,14 +196,14 @@ pub const GarbageCollector = struct { where: ?Token = null, // Types we generaly don't wan't to ever be collected - objfiber_members: std.AutoHashMap(*ObjString, *ObjNative), - objfiber_memberDefs: std.StringHashMap(*ObjTypeDef), - objpattern_members: std.AutoHashMap(*ObjString, *ObjNative), - objpattern_memberDefs: std.StringHashMap(*ObjTypeDef), - objstring_members: std.AutoHashMap(*ObjString, *ObjNative), - objstring_memberDefs: std.StringHashMap(*ObjTypeDef), - objrange_memberDefs: std.StringHashMap(*ObjTypeDef), - objrange_members: std.AutoHashMap(*ObjString, *ObjNative), + objfiber_members: []?*ObjNative, + objfiber_memberDefs: []?*ObjTypeDef, + objpattern_members: []?*ObjNative, + objpattern_memberDefs: []?*ObjTypeDef, + objstring_members: []?*ObjNative, + objstring_memberDefs: []?*ObjTypeDef, + objrange_memberDefs: []?*ObjTypeDef, + objrange_members: []?*ObjNative, full_collection_count: usize = 0, light_collection_count: usize = 0, @@ -211,8 +211,8 @@ pub const GarbageCollector = struct { gc_time: usize = 0, - pub fn init(allocator: std.mem.Allocator) Self { - return Self{ + pub fn init(allocator: std.mem.Allocator) !Self { + const self = Self{ .allocator = allocator, .strings = std.StringHashMap(*ObjString).init(allocator), .type_registry = undefined, @@ -220,15 +220,37 @@ pub const GarbageCollector = struct { .active_vms = std.AutoHashMap(*VM, void).init(allocator), .debugger = if (BuildOptions.gc_debug_access) GarbageCollectorDebugger.init(allocator) else null, - .objfiber_members = std.AutoHashMap(*ObjString, *ObjNative).init(allocator), - .objfiber_memberDefs = std.StringHashMap(*ObjTypeDef).init(allocator), - .objpattern_members = std.AutoHashMap(*ObjString, *ObjNative).init(allocator), - .objpattern_memberDefs = std.StringHashMap(*ObjTypeDef).init(allocator), - .objstring_members = std.AutoHashMap(*ObjString, *ObjNative).init(allocator), - .objstring_memberDefs = std.StringHashMap(*ObjTypeDef).init(allocator), - .objrange_members = std.AutoHashMap(*ObjString, *ObjNative).init(allocator), - .objrange_memberDefs = std.StringHashMap(*ObjTypeDef).init(allocator), + .objfiber_members = try allocator.alloc(?*ObjNative, ObjFiber.members.len), + .objfiber_memberDefs = try allocator.alloc(?*ObjTypeDef, ObjFiber.members.len), + .objpattern_members = try allocator.alloc(?*ObjNative, ObjPattern.members.len), + .objpattern_memberDefs = try allocator.alloc(?*ObjTypeDef, ObjPattern.members.len), + .objstring_members = try allocator.alloc(?*ObjNative, ObjString.members.len), + .objstring_memberDefs = try allocator.alloc(?*ObjTypeDef, ObjString.members.len), + .objrange_members = try allocator.alloc(?*ObjNative, ObjRange.members.len), + .objrange_memberDefs = try allocator.alloc(?*ObjTypeDef, ObjRange.members.len), }; + + for (0..ObjFiber.members.len) |i| { + self.objfiber_members[i] = null; + self.objfiber_memberDefs[i] = null; + } + + for (0..ObjPattern.members.len) |i| { + self.objpattern_members[i] = null; + self.objpattern_memberDefs[i] = null; + } + + for (0..ObjString.members.len) |i| { + self.objstring_members[i] = null; + self.objstring_memberDefs[i] = null; + } + + for (0..ObjRange.members.len) |i| { + self.objrange_members[i] = null; + self.objrange_memberDefs[i] = null; + } + + return self; } pub fn registerVM(self: *Self, vm: *VM) !void { @@ -247,14 +269,14 @@ pub const GarbageCollector = struct { debugger.deinit(); } - self.objfiber_members.deinit(); - self.objfiber_memberDefs.deinit(); - self.objpattern_members.deinit(); - self.objpattern_memberDefs.deinit(); - self.objstring_members.deinit(); - self.objstring_memberDefs.deinit(); - self.objrange_members.deinit(); - self.objrange_memberDefs.deinit(); + self.allocator.free(self.objfiber_members); + self.allocator.free(self.objfiber_memberDefs); + self.allocator.free(self.objpattern_members); + self.allocator.free(self.objpattern_memberDefs); + self.allocator.free(self.objstring_members); + self.allocator.free(self.objstring_memberDefs); + self.allocator.free(self.objrange_members); + self.allocator.free(self.objrange_memberDefs); } pub fn allocate(self: *Self, comptime T: type) !*T { @@ -663,47 +685,48 @@ pub const GarbageCollector = struct { // Calling eventual destructor method if (obj_objectinstance.object) |object| { - const collect_key = self.strings.get("collect"); - if (collect_key != null and object.methods.get(collect_key.?) != null) { - if (self.debugger != null) { - self.debugger.?.invoking_collector = true; - } - buzz_api.bz_invoke( - obj_objectinstance.vm, - obj_objectinstance.toValue(), - collect_key.?, - null, - 0, - null, - ); - if (self.debugger != null) { - self.debugger.?.invoking_collector = false; + if (object.type_def.resolved_type.?.Object.fields.get("collect")) |field| { + if (field.method and !field.static) { + if (self.debugger != null) { + self.debugger.?.invoking_collector = true; + } + buzz_api.bz_invoke( + obj_objectinstance.vm, + obj_objectinstance.toValue(), + field.index, + null, + 0, + null, + ); + if (self.debugger != null) { + self.debugger.?.invoking_collector = false; + } + + // Remove void result of the collect call + _ = obj_objectinstance.vm.pop(); } - - // Remove void result of the collect call - _ = obj_objectinstance.vm.pop(); } } - obj_objectinstance.deinit(); + obj_objectinstance.deinit(self.allocator); free(self, ObjObjectInstance, obj_objectinstance); }, .Object => { var obj_object = ObjObject.cast(obj).?; - obj_object.deinit(); + obj_object.deinit(self.allocator); free(self, ObjObject, obj_object); }, .List => { var obj_list = ObjList.cast(obj).?; - obj_list.deinit(); + obj_list.deinit(self.allocator); free(self, ObjList, obj_list); }, .Map => { var obj_map = ObjMap.cast(obj).?; - obj_map.deinit(); + obj_map.deinit(self.allocator); free(self, ObjMap, obj_map); }, @@ -802,63 +825,51 @@ pub const GarbageCollector = struct { io.print("MARKING BASIC TYPES METHOD\n", .{}); } // Mark basic types methods - { - var it = self.objfiber_members.iterator(); - while (it.next()) |umember| { - try self.markObj(umember.key_ptr.*.toObj()); - try self.markObj(umember.value_ptr.*.toObj()); + for (self.objfiber_members) |member| { + if (member) |umember| { + try self.markObj(umember.toObj()); } } - { - var it = self.objfiber_memberDefs.iterator(); - while (it.next()) |umember| { - try self.markObj(@constCast(umember.value_ptr.*.toObj())); + for (self.objfiber_memberDefs) |def| { + if (def) |udef| { + try self.markObj(udef.toObj()); } } - { - var it = self.objpattern_members.iterator(); - while (it.next()) |kv| { - try self.markObj(kv.key_ptr.*.toObj()); - try self.markObj(kv.value_ptr.*.toObj()); + for (self.objrange_members) |member| { + if (member) |umember| { + try self.markObj(umember.toObj()); } } - { - var it = self.objpattern_memberDefs.iterator(); - while (it.next()) |kv| { - try self.markObj(@constCast(kv.value_ptr.*.toObj())); + for (self.objrange_memberDefs) |def| { + if (def) |udef| { + try self.markObj(udef.toObj()); } } - { - var it = self.objstring_members.iterator(); - while (it.next()) |kv| { - try self.markObj(kv.key_ptr.*.toObj()); - try self.markObj(kv.value_ptr.*.toObj()); + for (self.objstring_members) |member| { + if (member) |umember| { + try self.markObj(umember.toObj()); } } - { - var it = self.objstring_memberDefs.iterator(); - while (it.next()) |kv| { - try self.markObj(@constCast(kv.value_ptr.*.toObj())); + for (self.objstring_memberDefs) |def| { + if (def) |udef| { + try self.markObj(udef.toObj()); } } - { - var it = self.objrange_members.iterator(); - while (it.next()) |kv| { - try self.markObj(kv.key_ptr.*.toObj()); - try self.markObj(kv.value_ptr.*.toObj()); + for (self.objpattern_members) |member| { + if (member) |umember| { + try self.markObj(umember.toObj()); } } - { - var it = self.objrange_memberDefs.iterator(); - while (it.next()) |kv| { - try self.markObj(@constCast(kv.value_ptr.*.toObj())); + for (self.objpattern_memberDefs) |def| { + if (def) |udef| { + try self.markObj(udef.toObj()); } } diff --git a/src/obj.zig b/src/obj.zig index b0e0ed83..0f053626 100644 --- a/src/obj.zig +++ b/src/obj.zig @@ -149,7 +149,7 @@ pub const Obj = struct { ObjMap.init( vm.gc.allocator, map_type, - ), + ) catch return error.OutOfMemory, ) catch return error.OutOfMemory; serialized_range.map.put( @@ -187,7 +187,7 @@ pub const Obj = struct { ObjList.init( vm.gc.allocator, list_type, - ), + ) catch return error.OutOfMemory, ) catch return error.OutOfMemory; for (list.items.items) |item| { @@ -224,7 +224,7 @@ pub const Obj = struct { ObjMap.init( vm.gc.allocator, map_type, - ), + ) catch return error.OutOfMemory, ) catch return error.OutOfMemory; for (map.map.keys()) |key| { @@ -263,16 +263,20 @@ pub const Obj = struct { ObjMap.init( vm.gc.allocator, map_type, - ), + ) catch return error.OutOfMemory, ) catch return error.OutOfMemory; - for (object_def.fields.keys()) |property| { - const property_str = (vm.gc.copyString(property) catch return error.OutOfMemory); - serialized_instance.set( - vm.gc, - property_str.toValue(), - try instance.fields.get(property_str).?.serialize(vm, seen), - ) catch return error.OutOfMemory; + var it = object_def.fields.iterator(); + while (it.next()) |kv| { + const field = kv.value_ptr.*; + if (!field.static and !field.method) { + const property_str = (vm.gc.copyString(field.name) catch return error.OutOfMemory); + serialized_instance.set( + vm.gc, + property_str.toValue(), + try instance.fields[field.index].serialize(vm, seen), + ) catch return error.OutOfMemory; + } } return serialized_instance.toValue(); @@ -303,7 +307,7 @@ pub const Obj = struct { ObjMap.init( vm.gc.allocator, map_type, - ), + ) catch return error.OutOfMemory, ) catch return error.OutOfMemory; var it = container_def.fields.iterator(); @@ -561,20 +565,23 @@ pub const Obj = struct { @intFromPtr(instance), }, ); - var it = instance.fields.iterator(); - while (it.next()) |kv| { - // This line is awesome - try instance + + for (0..instance.fields.len) |i| { + const object_def = instance .type_def .resolved_type.? .ObjectInstance .resolved_type.? - .Object - .fields - .get(kv.key_ptr.*.string).? + .Object; + + const field_name = object_def.fields.keys()[i]; + + try object_def + .fields.get(field_name).? .type_def .toString(writer); - try writer.print(" {s}, ", .{kv.key_ptr.*.string}); + + try writer.print(" {s}, ", .{field_name}); } try writer.writeAll("}"); } @@ -696,55 +703,69 @@ pub const ObjFiber = struct { return obj.cast(Self, .Fiber); } - const members = std.StaticStringMap(NativeFn).initComptime( + pub const members = [_]NativeFn{ + buzz_builtin.fiber.cancel, + buzz_builtin.fiber.isMain, + buzz_builtin.fiber.over, + }; + + pub const members_typedef = [_][]const u8{ + "extern Function cancel() > void", + "extern Function isMain() > bool", + "extern Function over() > bool", + }; + + pub const members_name = std.StaticStringMap(usize).initComptime( .{ - .{ "over", buzz_builtin.fiber.over }, - .{ "cancel", buzz_builtin.fiber.cancel }, - .{ "isMain", buzz_builtin.fiber.isMain }, + .{ "cancel", 0 }, + .{ "isMain", 1 }, + .{ "over", 2 }, }, ); - const members_typedef = std.StaticStringMap( - []const u8, - ).initComptime(.{ - .{ "over", "extern Function over() > bool" }, - .{ "cancel", "extern Function cancel() > void" }, - .{ "isMain", "extern Function isMain() > bool" }, - }); + pub fn memberByName(vm: *VM, name: []const u8) !?Value { + return if (members_name.get(name)) |idx| + try member(vm, idx) + else + null; + } - pub fn member(vm: *VM, method: *ObjString) !?*ObjNative { - if (vm.gc.objfiber_members.get(method)) |umethod| { - return umethod; + pub fn member(vm: *VM, method_idx: usize) !Value { + if (vm.gc.objfiber_members[method_idx]) |umethod| { + return umethod.toValue(); } - if (members.get(method.string)) |unativeFn| { - var native: *ObjNative = try vm.gc.allocateObject( - ObjNative, - .{ - // Complains about const qualifier discard otherwise - .native = @as(*anyopaque, @ptrFromInt(@intFromPtr(unativeFn))), - }, - ); + var native: *ObjNative = try vm.gc.allocateObject( + ObjNative, + .{ + // Complains about const qualifier discard otherwise + .native = @as(*anyopaque, @ptrFromInt(@intFromPtr(members[method_idx]))), + }, + ); - try vm.gc.objfiber_members.put(method, native); + vm.gc.objfiber_members[method_idx] = native; - // We need to mark it otherwise it could be collected by a Young gc and then badly accessed by a Full gc - vm.gc.markObj(native.toObj()) catch @panic("Could not mark obj"); + // We need to mark it otherwise it could be collected by a Young gc and then badly accessed by a Full gc + vm.gc.markObj(native.toObj()) catch @panic("Could not mark obj"); - return native; - } + return native.toValue(); + } - return null; + pub fn memberDefByName(parser: *Parser, name: []const u8) !?*ObjTypeDef { + return if (members_name.get(name)) |idx| + try memberDef(parser, idx) + else + null; } - pub fn memberDef(parser: *Parser, method: []const u8) !?*ObjTypeDef { - if (parser.gc.objfiber_memberDefs.get(method)) |umethod| { + pub fn memberDef(parser: *Parser, method_idx: usize) !*ObjTypeDef { + if (parser.gc.objfiber_memberDefs[method_idx]) |umethod| { return umethod; } - const native_type = try parser.parseTypeDefFrom(members_typedef.get(method).?); + const native_type = try parser.parseTypeDefFrom(members_typedef[method_idx]); - try parser.gc.objfiber_memberDefs.put(method, native_type); + parser.gc.objfiber_memberDefs[method_idx] = native_type; return native_type; } @@ -794,72 +815,93 @@ pub const ObjPattern = struct { return obj.cast(Self, .Pattern); } - const members = std.StaticStringMap(NativeFn).initComptime( - if (!is_wasm) - .{ - .{ "match", buzz_builtin.pattern.match }, - .{ "matchAll", buzz_builtin.pattern.matchAll }, - .{ "replace", buzz_builtin.pattern.replace }, - .{ "replaceAll", buzz_builtin.pattern.replaceAll }, - } - else - .{ - .{ "replace", buzz_builtin.pattern.replace }, - .{ "replaceAll", buzz_builtin.pattern.replaceAll }, - }, - ); + pub const members = if (!is_wasm) + [_]NativeFn{ + buzz_builtin.pattern.match, + buzz_builtin.pattern.matchAll, + buzz_builtin.pattern.replace, + buzz_builtin.pattern.replaceAll, + } + else + [_]NativeFn{ + buzz_builtin.pattern.replace, + buzz_builtin.pattern.replaceAll, + }; - const members_typedef = std.StaticStringMap([]const u8).initComptime( + const members_typedef = if (!is_wasm) + [_][]const u8{ + "extern Function match(str subject) > [str]?", + "extern Function matchAll(str subject) > [[str]]?", + "extern Function replace(str subject, str with) > str", + "extern Function replaceAll(str subject, str with) > str", + } + else + [_][]const u8{ + "extern Function replace(str subject, str with) > str", + "extern Function replaceAll(str subject, str with) > str", + }; + + pub const members_name = std.StaticStringMap(usize).initComptime( if (!is_wasm) .{ - .{ "match", "extern Function match(str subject) > [str]?" }, - .{ "matchAll", "extern Function matchAll(str subject) > [[str]]?" }, - .{ "replace", "extern Function replace(str subject, str with) > str" }, - .{ "replaceAll", "extern Function replaceAll(str subject, str with) > str" }, + .{ "match", 0 }, + .{ "matchAll", 1 }, + .{ "replace", 2 }, + .{ "replaceAll", 3 }, } else .{ - .{ "replace", "extern Function replace(str subject, str with) > str" }, - .{ "replaceAll", "extern Function replaceAll(str subject, str with) > str" }, + .{ "replace", 0 }, + .{ "replaceAll", 1 }, }, ); - pub fn member(vm: *VM, method: *ObjString) !?*ObjNative { - if (vm.gc.objpattern_members.get(method)) |umethod| { - return umethod; + pub fn member(vm: *VM, method_idx: usize) !Value { + if (vm.gc.objpattern_members[method_idx]) |umethod| { + return umethod.toValue(); } - if (members.get(method.string)) |nativeFn| { - var native: *ObjNative = try vm.gc.allocateObject( - ObjNative, - .{ - // Complains about const qualifier discard otherwise - .native = @as(*anyopaque, @ptrFromInt(@intFromPtr(nativeFn))), - }, - ); + var native: *ObjNative = try vm.gc.allocateObject( + ObjNative, + .{ + // Complains about const qualifier discard otherwise + .native = @as(*anyopaque, @ptrFromInt(@intFromPtr(members[method_idx]))), + }, + ); - try vm.gc.objpattern_members.put(method, native); + vm.gc.objpattern_members[method_idx] = native; - // We need to mark it otherwise it could be collected by a Young gc and then badly accessed by a Full gc - vm.gc.markObj(native.toObj()) catch @panic("Could not mark obj"); + // We need to mark it otherwise it could be collected by a Young gc and then badly accessed by a Full gc + vm.gc.markObj(native.toObj()) catch @panic("Could not mark obj"); - return native; - } + return native.toValue(); + } - return null; + pub fn memberByName(vm: *VM, name: []const u8) !?Value { + return if (members_name.get(name)) |idx| + try member(vm, idx) + else + null; } - pub fn memberDef(parser: *Parser, method: []const u8) !?*ObjTypeDef { - if (parser.gc.objpattern_memberDefs.get(method)) |umethod| { + pub fn memberDef(parser: *Parser, method_idx: usize) !*ObjTypeDef { + if (parser.gc.objpattern_memberDefs[method_idx]) |umethod| { return umethod; } - const native_type = try parser.parseTypeDefFrom(members_typedef.get(method).?); + const native_type = try parser.parseTypeDefFrom(members_typedef[method_idx]); - try parser.gc.objpattern_memberDefs.put(method, native_type); + parser.gc.objpattern_memberDefs[method_idx] = native_type; return native_type; } + + pub fn memberDefByName(parser: *Parser, name: []const u8) !?*ObjTypeDef { + return if (members_name.get(name)) |idx| + try memberDef(parser, idx) + else + null; + } }; /// User data, type around an opaque pointer @@ -936,90 +978,117 @@ pub const ObjString = struct { } } - pub const members = std.StaticStringMap(NativeFn).initComptime( - .{ - .{ "len", buzz_builtin.str.len }, - .{ "utf8Len", buzz_builtin.str.utf8Len }, - .{ "utf8Valid", buzz_builtin.str.utf8Valid }, - .{ "utf8Codepoints", buzz_builtin.str.utf8Codepoints }, - .{ "trim", buzz_builtin.str.trim }, - .{ "byte", buzz_builtin.str.byte }, - .{ "indexOf", buzz_builtin.str.indexOf }, - .{ "split", buzz_builtin.str.split }, - .{ "sub", buzz_builtin.str.sub }, - .{ "startsWith", buzz_builtin.str.startsWith }, - .{ "endsWith", buzz_builtin.str.endsWith }, - .{ "replace", buzz_builtin.str.replace }, - .{ "repeat", buzz_builtin.str.repeat }, - .{ "encodeBase64", buzz_builtin.str.encodeBase64 }, - .{ "decodeBase64", buzz_builtin.str.decodeBase64 }, - .{ "upper", buzz_builtin.str.upper }, - .{ "lower", buzz_builtin.str.lower }, - .{ "hex", buzz_builtin.str.hex }, - .{ "bin", buzz_builtin.str.bin }, - }, - ); + pub const members = [_]NativeFn{ + buzz_builtin.str.bin, + buzz_builtin.str.byte, + buzz_builtin.str.decodeBase64, + buzz_builtin.str.encodeBase64, + buzz_builtin.str.endsWith, + buzz_builtin.str.hex, + buzz_builtin.str.indexOf, + buzz_builtin.str.len, + buzz_builtin.str.lower, + buzz_builtin.str.repeat, + buzz_builtin.str.replace, + buzz_builtin.str.split, + buzz_builtin.str.startsWith, + buzz_builtin.str.sub, + buzz_builtin.str.trim, + buzz_builtin.str.upper, + buzz_builtin.str.utf8Codepoints, + buzz_builtin.str.utf8Len, + buzz_builtin.str.utf8Valid, + }; + + pub const members_typedef = [_][]const u8{ + "extern Function bin() > str", + "extern Function byte(int at = 0) > int", + "extern Function decodeBase64() > str", + "extern Function encodeBase64() > str", + "extern Function endsWith(str needle) > bool", + "extern Function hex() > str", + "extern Function indexOf(str needle) > int?", + "extern Function len() > int", + "extern Function lower() > str", + "extern Function repeat(int n) > str", + "extern Function replace(str needle, str with) > str", + "extern Function split(str separator) > [str]", + "extern Function startsWith(str needle) > bool", + "extern Function sub(int start, int? len) > str", + "extern Function trim() > str", + "extern Function upper() > str", + "extern Function utf8Codepoints() > [str]", + "extern Function utf8Len() > int", + "extern Function utf8Valid() > bool", + }; - pub const members_typedef = std.StaticStringMap( - []const u8, - ).initComptime( + pub const members_name = std.StaticStringMap(usize).initComptime( .{ - .{ "len", "extern Function len() > int" }, - .{ "utf8Len", "extern Function utf8Len() > int" }, - .{ "utf8Valid", "extern Function utf8Valid() > bool" }, - .{ "utf8Codepoints", "extern Function utf8Codepoints() > [str]" }, - .{ "trim", "extern Function trim() > str" }, - .{ "byte", "extern Function byte(int at = 0) > int" }, - .{ "indexOf", "extern Function indexOf(str needle) > int?" }, - .{ "startsWith", "extern Function startsWith(str needle) > bool" }, - .{ "endsWith", "extern Function endsWith(str needle) > bool" }, - .{ "replace", "extern Function replace(str needle, str with) > str" }, - .{ "split", "extern Function split(str separator) > [str]" }, - .{ "sub", "extern Function sub(int start, int? len) > str" }, - .{ "repeat", "extern Function repeat(int n) > str" }, - .{ "encodeBase64", "extern Function encodeBase64() > str" }, - .{ "decodeBase64", "extern Function decodeBase64() > str" }, - .{ "upper", "extern Function upper() > str" }, - .{ "lower", "extern Function lower() > str" }, - .{ "hex", "extern Function hex() > str" }, - .{ "bin", "extern Function bin() > str" }, + .{ "bin", 0 }, + .{ "byte", 1 }, + .{ "decodeBase64", 2 }, + .{ "encodeBase64", 3 }, + .{ "endsWith", 4 }, + .{ "hex", 5 }, + .{ "indexOf", 6 }, + .{ "len", 7 }, + .{ "lower", 8 }, + .{ "repeat", 9 }, + .{ "replace", 10 }, + .{ "split", 11 }, + .{ "startsWith", 12 }, + .{ "sub", 13 }, + .{ "trim", 14 }, + .{ "upper", 15 }, + .{ "utf8Codepoints", 16 }, + .{ "utf8Len", 17 }, + .{ "utf8Valid", 18 }, }, ); - // TODO: find a way to return the same ObjNative pointer for the same type of Lists - pub fn member(vm: *VM, method: *ObjString) !?*ObjNative { - if (vm.gc.objstring_members.get(method)) |umethod| { - return umethod; + pub fn memberByName(vm: *VM, name: []const u8) !?Value { + return if (members_name.get(name)) |idx| + try member(vm, idx) + else + null; + } + + pub fn member(vm: *VM, method_idx: usize) !Value { + if (vm.gc.objstring_members[method_idx]) |umethod| { + return umethod.toValue(); } - if (members.get(method.string)) |nativeFn| { - var native: *ObjNative = try vm.gc.allocateObject( - ObjNative, - .{ - // Complains about const qualifier discard otherwise - .native = @as(*anyopaque, @ptrFromInt(@intFromPtr(nativeFn))), - }, - ); + var native: *ObjNative = try vm.gc.allocateObject( + ObjNative, + .{ + // Complains about const qualifier discard otherwise + .native = @as(*anyopaque, @ptrFromInt(@intFromPtr(members[method_idx]))), + }, + ); - try vm.gc.objstring_members.put(method, native); + vm.gc.objstring_members[method_idx] = native; - // We need to mark it otherwise it could be collected by a Young gc and then badly accessed by a Full gc - vm.gc.markObj(native.toObj()) catch @panic("Could not mark obj"); + // We need to mark it otherwise it could be collected by a Young gc and then badly accessed by a Full gc + vm.gc.markObj(native.toObj()) catch @panic("Could not mark obj"); - return native; - } + return native.toValue(); + } - return null; + pub fn memberDefByName(parser: *Parser, name: []const u8) !?*ObjTypeDef { + return if (members_name.get(name)) |idx| + try memberDef(parser, idx) + else + null; } - pub fn memberDef(parser: *Parser, method: []const u8) !?*ObjTypeDef { - if (parser.gc.objstring_memberDefs.get(method)) |umethod| { + pub fn memberDef(parser: *Parser, method_idx: usize) !*ObjTypeDef { + if (parser.gc.objstring_memberDefs[method_idx]) |umethod| { return umethod; } - const native_type = try parser.parseTypeDefFrom(members_typedef.get(method).?); + const native_type = try parser.parseTypeDefFrom(members_typedef[method_idx]); - try parser.gc.objstring_memberDefs.put(method, native_type); + parser.gc.objstring_memberDefs[method_idx] = native_type; return native_type; } @@ -1317,21 +1386,47 @@ pub const ObjObjectInstance = struct { /// Populated object type type_def: *ObjTypeDef, /// Fields value - fields: std.AutoHashMap(*ObjString, Value), + fields: []Value, /// VM in which the instance was created, we need this so the instance destructor can be called in the appropriate vm vm: *VM, - pub fn setField(self: *Self, gc: *GarbageCollector, key: *ObjString, value: Value) !void { - try self.fields.put(key, value); + pub fn setField(self: *Self, gc: *GarbageCollector, key: usize, value: Value) !void { + self.fields[key] = value; try gc.markObjDirty(&self.obj); } - pub fn init(vm: *VM, object: ?*ObjObject, type_def: *ObjTypeDef) Self { - return Self{ + /// Should not be called by runtime when possible + pub fn setFieldByName(self: *Self, gc: *GarbageCollector, key: *ObjString, value: Value) !void { + const object_def = self.type_def.resolved_type.?.ObjectInstance.resolved_type.?.Object; + const index = std.mem.indexOf( + *ObjString, + object_def.fields.keys(), + key, + ); + + self.setField( + gc, + index, + value, + ); + } + + pub fn init( + vm: *VM, + object: ?*ObjObject, + type_def: *ObjTypeDef, + gc: *GarbageCollector, + ) !Self { + return .{ .vm = vm, .object = object, .type_def = type_def, - .fields = std.AutoHashMap(*ObjString, Value).init(vm.gc.allocator), + .fields = try gc.allocateMany( + Value, + type_def.resolved_type.?.ObjectInstance + .resolved_type.?.Object + .propertiesCount(), + ), }; } @@ -1340,15 +1435,13 @@ pub const ObjObjectInstance = struct { try gc.markObj(object.toObj()); } try gc.markObj(@constCast(self.type_def.toObj())); - var it = self.fields.iterator(); - while (it.next()) |kv| { - try gc.markObj(kv.key_ptr.*.toObj()); - try gc.markValue(kv.value_ptr.*); + for (self.fields) |field| { + try gc.markValue(field); } } - pub fn deinit(self: *Self) void { - self.fields.deinit(); + pub fn deinit(self: *Self, allocator: Allocator) void { + allocator.free(self.fields); } pub inline fn toObj(self: *Self) *Obj { @@ -1396,8 +1489,8 @@ pub const ObjForeignContainer = struct { }; } - pub fn setField(self: *Self, vm: *VM, field: []const u8, value: Value) !void { - self.type_def.resolved_type.?.ForeignContainer.fields.get(field).?.setter( + pub fn setField(self: *Self, vm: *VM, field_idx: usize, value: Value) !void { + self.type_def.resolved_type.?.ForeignContainer.fields.values()[field_idx].setter( vm, self.data.ptr, value, @@ -1405,8 +1498,8 @@ pub const ObjForeignContainer = struct { try vm.gc.markObjDirty(&self.obj); } - pub fn getField(self: *Self, vm: *VM, field: []const u8) Value { - return self.type_def.resolved_type.?.ForeignContainer.fields.get(field).?.getter( + pub fn getField(self: *Self, vm: *VM, field_idx: usize) Value { + return self.type_def.resolved_type.?.ForeignContainer.fields.values()[field_idx].getter( vm, self.data.ptr, ); @@ -1473,62 +1566,83 @@ pub const ObjObject = struct { /// Object name name: *ObjString, - /// Object methods - methods: std.AutoHashMap(*ObjString, *ObjClosure), - /// Object fields default values - fields: std.AutoHashMap(*ObjString, Value), - /// Object static fields - static_fields: std.AutoHashMap(*ObjString, Value), - - pub fn init(allocator: Allocator, name: *ObjString, type_def: *ObjTypeDef) Self { - return Self{ + /// Static fields and methods + fields: []Value, + /// Properties default values (null if none) + defaults: []?Value, + + /// To avoid counting object fields that are instance properties + property_count: ?usize = 0, + + pub fn init(allocator: Allocator, name: *ObjString, type_def: *ObjTypeDef) !Self { + const self = Self{ .name = name, - .methods = std.AutoHashMap(*ObjString, *ObjClosure).init(allocator), - .fields = std.AutoHashMap(*ObjString, Value).init(allocator), - .static_fields = std.AutoHashMap(*ObjString, Value).init(allocator), + .fields = try allocator.alloc( + Value, + type_def.resolved_type.?.Object.fields.count(), + ), + .defaults = try allocator.alloc( + ?Value, + type_def.resolved_type.?.Object.propertiesCount(), + ), .type_def = type_def, }; + + return self; } - pub fn setField(self: *Self, gc: *GarbageCollector, key: *ObjString, value: Value) !void { - try self.fields.put(key, value); + pub fn propertyCount(self: *Self) usize { + if (self.property_count) |pc| { + return pc; + } + + var property_count: usize = 0; + var it = self.fields.iterator(); + while (it.next()) |kv| { + property_count += if (!kv.value_ptr.*.static and !kv.value_ptr.*.method) + 1 + else + 0; + } + + self.property_count = property_count; + + return property_count; + } + + pub fn setField(self: *Self, gc: *GarbageCollector, key: usize, value: Value) !void { + self.fields[key] = value; try gc.markObjDirty(&self.obj); } - pub fn setStaticField(self: *Self, gc: *GarbageCollector, key: *ObjString, value: Value) !void { - try self.static_fields.put(key, value); + pub fn setDefault(self: *Self, gc: *GarbageCollector, key: usize, value: Value) !void { + self.defaults[key] = value; try gc.markObjDirty(&self.obj); } - pub fn setMethod(self: *Self, gc: *GarbageCollector, key: *ObjString, closure: *ObjClosure) !void { - try self.methods.put(key, closure); + pub fn setPropertyDefaultValue(self: *Self, gc: *GarbageCollector, key: usize, value: Value) !void { + self.defaults[key] = value; try gc.markObjDirty(&self.obj); } pub fn mark(self: *Self, gc: *GarbageCollector) !void { try gc.markObj(@constCast(self.type_def.toObj())); try gc.markObj(self.name.toObj()); - var it = self.methods.iterator(); - while (it.next()) |kv| { - try gc.markObj(kv.key_ptr.*.toObj()); - try gc.markObj(kv.value_ptr.*.toObj()); - } - var it2 = self.fields.iterator(); - while (it2.next()) |kv| { - try gc.markObj(kv.key_ptr.*.toObj()); - try gc.markValue(kv.value_ptr.*); + + for (self.fields) |field| { + try gc.markValue(field); } - var it3 = self.static_fields.iterator(); - while (it3.next()) |kv| { - try gc.markObj(kv.key_ptr.*.toObj()); - try gc.markValue(kv.value_ptr.*); + + for (self.defaults) |field_opt| { + if (field_opt) |field| { + try gc.markValue(field); + } } } - pub fn deinit(self: *Self) void { - self.methods.deinit(); - self.fields.deinit(); - self.static_fields.deinit(); + pub fn deinit(self: *Self, allocator: Allocator) void { + allocator.free(self.fields); + allocator.free(self.defaults); } pub inline fn toObj(self: *Self) *Obj { @@ -1589,6 +1703,8 @@ pub const ObjObject = struct { static: bool, location: Token, has_default: bool, + // If the field is a static property or a method or an instance property, the index is not the same + index: usize, }; id: usize, @@ -1641,6 +1757,20 @@ pub const ObjObject = struct { self.generic_types.deinit(); } + pub fn propertiesCount(self: ObjectDef) usize { + var count: usize = 0; + var it = self.fields.iterator(); + while (it.next()) |kv| { + const field = kv.value_ptr.*; + + if (!field.method and !field.static) { + count += 1; + } + } + + return count; + } + // Do they both conform to a common protocol? pub fn both_conforms(self: ObjectDef, other: ObjectDef) ?*ObjTypeDef { var it = self.conforms_to.iterator(); @@ -1697,14 +1827,23 @@ pub const ObjList = struct { /// List items items: std.ArrayList(Value), - methods: std.AutoHashMap(*ObjString, *ObjNative), + methods: []?*ObjNative, - pub fn init(allocator: Allocator, type_def: *ObjTypeDef) Self { - return Self{ + pub fn init(allocator: Allocator, type_def: *ObjTypeDef) !Self { + const self = Self{ .items = std.ArrayList(Value).init(allocator), .type_def = type_def, - .methods = std.AutoHashMap(*ObjString, *ObjNative).init(allocator), + .methods = try allocator.alloc( + ?*ObjNative, + Self.members.len, + ), }; + + for (0..Self.members.len) |i| { + self.methods[i] = null; + } + + return self; } pub fn mark(self: *Self, gc: *GarbageCollector) !void { @@ -1712,16 +1851,17 @@ pub const ObjList = struct { try gc.markValue(value); } try gc.markObj(@constCast(self.type_def.toObj())); - var it = self.methods.iterator(); - while (it.next()) |kv| { - try gc.markObj(kv.key_ptr.*.toObj()); - try gc.markObj(kv.value_ptr.*.toObj()); + + for (self.methods) |method_opt| { + if (method_opt) |method| { + try gc.markObj(method.toObj()); + } } } - pub fn deinit(self: *Self) void { + pub fn deinit(self: *Self, allocator: Allocator) void { self.items.deinit(); - self.methods.deinit(); + allocator.free(self.methods); } pub inline fn toObj(self: *Self) *Obj { @@ -1736,54 +1876,68 @@ pub const ObjList = struct { return obj.cast(Self, .List); } - const members = std.StaticStringMap( - NativeFn, - ).initComptime( + pub const members = [_]NativeFn{ + buzz_builtin.list.append, + buzz_builtin.list.clone, + buzz_builtin.list.fill, + buzz_builtin.list.filter, + buzz_builtin.list.forEach, + buzz_builtin.list.indexOf, + buzz_builtin.list.insert, + buzz_builtin.list.join, + buzz_builtin.list.len, + buzz_builtin.list.map, + buzz_builtin.list.next, + buzz_builtin.list.pop, + buzz_builtin.list.reduce, + buzz_builtin.list.remove, + buzz_builtin.list.reverse, + buzz_builtin.list.sort, + buzz_builtin.list.sub, + }; + + // TODO: could probably build this in a comptime block? + pub const members_name = std.StaticStringMap(usize).initComptime( .{ - .{ "append", buzz_builtin.list.append }, - .{ "clone", buzz_builtin.list.clone }, - .{ "filter", buzz_builtin.list.filter }, - .{ "forEach", buzz_builtin.list.forEach }, - .{ "indexOf", buzz_builtin.list.indexOf }, - .{ "insert", buzz_builtin.list.insert }, - .{ "join", buzz_builtin.list.join }, - .{ "len", buzz_builtin.list.len }, - .{ "map", buzz_builtin.list.map }, - .{ "next", buzz_builtin.list.next }, - .{ "pop", buzz_builtin.list.pop }, - .{ "reduce", buzz_builtin.list.reduce }, - .{ "remove", buzz_builtin.list.remove }, - .{ "reverse", buzz_builtin.list.reverse }, - .{ "sort", buzz_builtin.list.sort }, - .{ "sub", buzz_builtin.list.sub }, - .{ "fill", buzz_builtin.list.fill }, + .{ "append", 0 }, + .{ "clone", 1 }, + .{ "fill", 2 }, + .{ "filter", 3 }, + .{ "forEach", 4 }, + .{ "indexOf", 5 }, + .{ "insert", 6 }, + .{ "join", 7 }, + .{ "len", 8 }, + .{ "map", 9 }, + .{ "next", 10 }, + .{ "pop", 11 }, + .{ "reduce", 12 }, + .{ "remove", 13 }, + .{ "reverse", 14 }, + .{ "sort", 15 }, + .{ "sub", 16 }, }, ); - // TODO: find a way to return the same ObjNative pointer for the same type of Lists - pub fn member(self: *Self, vm: *VM, method: *ObjString) !?*ObjNative { - if (self.methods.get(method)) |native| { - return native; + pub fn member(self: *Self, vm: *VM, method_idx: usize) !Value { + if (self.methods[method_idx]) |native| { + return native.toValue(); } - if (members.get(method.string)) |nativeFn| { - var native: *ObjNative = try vm.gc.allocateObject( - ObjNative, - .{ - // Complains about const qualifier discard otherwise - .native = @as(*anyopaque, @ptrFromInt(@intFromPtr(nativeFn))), - }, - ); - - try self.methods.put(method, native); + var native: *ObjNative = try vm.gc.allocateObject( + ObjNative, + .{ + // Complains about const qualifier discard otherwise + .native = @as(*anyopaque, @ptrFromInt(@intFromPtr(members[method_idx]))), + }, + ); - // We need to mark it otherwise it could be collected by a Young gc and then badly accessed by a Full gc - vm.gc.markObj(native.toObj()) catch @panic("Could not mark obj"); + self.methods[method_idx] = native; - return native; - } + // We need to mark it otherwise it could be collected by a Young gc and then badly accessed by a Full gc + vm.gc.markObj(native.toObj()) catch @panic("Could not mark obj"); - return null; + return native.toValue(); } pub fn rawAppend(self: *Self, gc: *GarbageCollector, value: Value) !void { @@ -2664,66 +2818,86 @@ pub const ObjRange = struct { return obj.cast(Self, .Range); } - const members = std.StaticStringMap(NativeFn).initComptime( - .{ - .{ "toList", buzz_builtin.range.toList }, - .{ "len", buzz_builtin.range.len }, - .{ "invert", buzz_builtin.range.invert }, - .{ "subsetOf", buzz_builtin.range.subsetOf }, - .{ "intersect", buzz_builtin.range.intersect }, - .{ "union", buzz_builtin.range.@"union" }, - }, - ); + pub const members = [_]NativeFn{ + buzz_builtin.range.high, + buzz_builtin.range.intersect, + buzz_builtin.range.invert, + buzz_builtin.range.len, + buzz_builtin.range.low, + buzz_builtin.range.subsetOf, + buzz_builtin.range.toList, + buzz_builtin.range.@"union", + }; - const members_typedef = std.StaticStringMap([]const u8).initComptime( + const members_typedef = [_][]const u8{ + "extern Function high() > int", + "extern Function intersect(rg other) > rg", + "extern Function invert() > rg", + "extern Function len() > int", + "extern Function low() > int", + "extern Function subsetOf(rg other) > bool", + "extern Function toList() > [int]", + "extern Function union(rg other) > rg", + }; + + pub const members_name = std.StaticStringMap(usize).initComptime( .{ - .{ "toList", "extern Function toList() > [int]" }, - .{ "len", "extern Function len() > int" }, - .{ "invert", "extern Function invert() > rg" }, - .{ "subsetOf", "extern Function subsetOf(rg other) > bool" }, - .{ "intersect", "extern Function intersect(rg other) > rg" }, - .{ "union", "extern Function union(rg other) > rg" }, + .{ "high", 0 }, + .{ "intersect", 1 }, + .{ "invert", 2 }, + .{ "len", 3 }, + .{ "low", 4 }, + .{ "subsetOf", 5 }, + .{ "toList", 6 }, + .{ "union", 7 }, }, ); - pub fn member(vm: *VM, method: *ObjString) !?*ObjNative { - if (vm.gc.objrange_members.get(method)) |native| { - return native; + pub fn memberByName(vm: *VM, name: []const u8) !?Value { + return if (members_name.get(name)) |idx| + try member(vm, idx) + else + null; + } + + pub fn member(vm: *VM, method_idx: usize) !Value { + if (vm.gc.objrange_members[method_idx]) |native| { + return native.toValue(); } - if (members.get(method.string)) |nativeFn| { - var native: *ObjNative = try vm.gc.allocateObject( - ObjNative, - .{ - // Complains about const qualifier discard otherwise - .native = @as(*anyopaque, @ptrFromInt(@intFromPtr(nativeFn))), - }, - ); + var native: *ObjNative = try vm.gc.allocateObject( + ObjNative, + .{ + // Complains about const qualifier discard otherwise + .native = @as(*anyopaque, @ptrFromInt(@intFromPtr(members[method_idx]))), + }, + ); - try vm.gc.objrange_members.put(method, native); + vm.gc.objrange_members[method_idx] = native; - // We need to mark it otherwise it could be collected by a Young gc and then badly accessed by a Full gc - vm.gc.markObj(native.toObj()) catch @panic("Could not mark obj"); + // We need to mark it otherwise it could be collected by a Young gc and then badly accessed by a Full gc + vm.gc.markObj(native.toObj()) catch @panic("Could not mark obj"); - return native; - } + return native.toValue(); + } - return null; + pub fn memberDefByName(parser: *Parser, name: []const u8) !?*ObjTypeDef { + return if (members_name.get(name)) |idx| + try memberDef(parser, idx) + else + null; } - pub fn memberDef(parser: *Parser, method: []const u8) !?*ObjTypeDef { - if (parser.gc.objrange_memberDefs.get(method)) |umethod| { + + pub fn memberDef(parser: *Parser, method_idx: usize) !*ObjTypeDef { + if (parser.gc.objrange_memberDefs[method_idx]) |umethod| { return umethod; } - if (members_typedef.get(method)) |member_typedef| { - const native_type = try parser.parseTypeDefFrom(member_typedef); + const native_type = try parser.parseTypeDefFrom(members_typedef[method_idx]); - try parser.gc.objrange_memberDefs.put(method, native_type); + parser.gc.objrange_memberDefs[method_idx] = native_type; - return native_type; - } - - return null; + return native_type; } }; @@ -2738,14 +2912,23 @@ pub const ObjMap = struct { // We need an ArrayHashMap for `next` map: std.AutoArrayHashMap(Value, Value), - methods: std.AutoHashMap(*ObjString, *ObjNative), + methods: []?*ObjNative, - pub fn init(allocator: Allocator, type_def: *ObjTypeDef) Self { - return .{ + pub fn init(allocator: Allocator, type_def: *ObjTypeDef) !Self { + const self = Self{ .type_def = type_def, .map = std.AutoArrayHashMap(Value, Value).init(allocator), - .methods = std.AutoHashMap(*ObjString, *ObjNative).init(allocator), + .methods = try allocator.alloc( + ?*ObjNative, + Self.members.len, + ), }; + + for (0..Self.members.len) |i| { + self.methods[i] = null; + } + + return self; } pub fn set(self: *Self, gc: *GarbageCollector, key: Value, value: Value) !void { @@ -2753,46 +2936,57 @@ pub const ObjMap = struct { try gc.markObjDirty(&self.obj); } - const members = std.StaticStringMap(NativeFn).initComptime( + pub const members = [_]NativeFn{ + buzz_builtin.map.clone, + buzz_builtin.map.diff, + buzz_builtin.map.filter, + buzz_builtin.map.forEach, + buzz_builtin.map.intersect, + buzz_builtin.map.keys, + buzz_builtin.map.map, + buzz_builtin.map.reduce, + buzz_builtin.map.remove, + buzz_builtin.map.size, + buzz_builtin.map.sort, + buzz_builtin.map.values, + }; + + pub const members_name = std.StaticStringMap(usize).initComptime( .{ - .{ "clone", buzz_builtin.map.clone }, - .{ "diff", buzz_builtin.map.diff }, - .{ "filter", buzz_builtin.map.filter }, - .{ "forEach", buzz_builtin.map.forEach }, - .{ "intersect", buzz_builtin.map.intersect }, - .{ "keys", buzz_builtin.map.keys }, - .{ "map", buzz_builtin.map.map }, - .{ "reduce", buzz_builtin.map.reduce }, - .{ "remove", buzz_builtin.map.remove }, - .{ "size", buzz_builtin.map.size }, - .{ "sort", buzz_builtin.map.sort }, - .{ "values", buzz_builtin.map.values }, + .{ "clone", 0 }, + .{ "diff", 1 }, + .{ "filter", 2 }, + .{ "forEach", 3 }, + .{ "intersect", 4 }, + .{ "keys", 5 }, + .{ "map", 6 }, + .{ "reduce", 7 }, + .{ "remove", 8 }, + .{ "size", 9 }, + .{ "sort", 10 }, + .{ "values", 11 }, }, ); - pub fn member(self: *Self, vm: *VM, method: *ObjString) !?*ObjNative { - if (self.methods.get(method)) |native| { - return native; + pub fn member(self: *Self, vm: *VM, method_idx: usize) !Value { + if (self.methods[method_idx]) |native| { + return native.toValue(); } - if (members.get(method.string)) |nativeFn| { - const native: *ObjNative = try vm.gc.allocateObject( - ObjNative, - .{ - // Complains about const qualifier discard otherwise - .native = @constCast(nativeFn), - }, - ); - - try self.methods.put(method, native); + const native: *ObjNative = try vm.gc.allocateObject( + ObjNative, + .{ + // Complains about const qualifier discard otherwise + .native = @constCast(members[method_idx]), + }, + ); - // We need to mark it otherwise it could be collected by a Young gc and then badly accessed by a Full gc - vm.gc.markObj(native.toObj()) catch @panic("Could not mark obj"); + self.methods[method_idx] = native; - return native; - } + // We need to mark it otherwise it could be collected by a Young gc and then badly accessed by a Full gc + vm.gc.markObj(native.toObj()) catch @panic("Could not mark obj"); - return null; + return native.toValue(); } pub fn mark(self: *Self, gc: *GarbageCollector) !void { @@ -2802,10 +2996,10 @@ pub const ObjMap = struct { try gc.markValue(kv.value_ptr.*); } - var it2 = self.methods.iterator(); - while (it2.next()) |kv| { - try gc.markObj(kv.key_ptr.*.toObj()); - try gc.markObj(kv.value_ptr.*.toObj()); + for (self.methods) |method_opt| { + if (method_opt) |method| { + try gc.markObj(method.toObj()); + } } try gc.markObj(@constCast(self.type_def.toObj())); @@ -2827,9 +3021,9 @@ pub const ObjMap = struct { } } - pub fn deinit(self: *Self) void { + pub fn deinit(self: *Self, allocator: Allocator) void { self.map.deinit(); - self.methods.deinit(); + allocator.free(self.methods); } pub inline fn toObj(self: *Self) *Obj { @@ -3262,6 +3456,7 @@ pub const ObjMap = struct { .static = false, .has_default = false, .location = Token.identifier("key"), + .index = 0, }, ); try entry_def.fields.put( @@ -3274,6 +3469,7 @@ pub const ObjMap = struct { .static = false, .has_default = false, .location = Token.identifier("value"), + .index = 1, }, ); @@ -3938,6 +4134,7 @@ pub const ObjTypeDef = struct { visited_ptr, ), .has_default = kv.value_ptr.*.has_default, + .index = kv.value_ptr.*.index, }, ); } diff --git a/src/repl.zig b/src/repl.zig index 209584f0..a5dfbff9 100644 --- a/src/repl.zig +++ b/src/repl.zig @@ -93,7 +93,7 @@ pub fn repl(allocator: std.mem.Allocator) !void { false; var import_registry = ImportRegistry.init(allocator); - var gc = GarbageCollector.init(allocator); + var gc = try GarbageCollector.init(allocator); gc.type_registry = try TypeRegistry.init(&gc); var imports = std.StringHashMap(Parser.ScriptImport).init(allocator); var vm = try VM.init(&gc, &import_registry, .Repl); diff --git a/src/vm.zig b/src/vm.zig index 77b7c6d7..9a5d2223 100644 --- a/src/vm.zig +++ b/src/vm.zig @@ -66,24 +66,24 @@ pub const CallFrame = struct { const Self = @This(); closure: *ObjClosure, - // Index into closure's chunk + /// Index into closure's chunk ip: usize, // Frame slots: [*]Value, - // Default value in case of error + /// Default value in case of error error_value: ?Value = null, - // Line in source code where the call occured + /// Line in source code where the call occured call_site: ?Ast.TokenIndex, - // Offset at which error can be handled (means we're in a try block) + /// Offset at which error can be handled (means we're in a try block) try_ip: ?usize = null, - // Top when try block started + /// Top when try block started try_top: ?[*]Value = null, - // True if a native function is being called, we need this because a native function can also - // call buzz code and we need to know how to stop interpreting once we get back to native code + /// True if a native function is being called, we need this because a native function can also + /// call buzz code and we need to know how to stop interpreting once we get back to native code in_native_call: bool = false, native_call_error_value: ?Value = null, }; @@ -92,7 +92,6 @@ pub const TryCtx = if (!is_wasm) extern struct { previous: ?*TryCtx, env: jmp.jmp_buf = undefined, - // FIXME: remember top here } else void; @@ -101,13 +100,13 @@ pub const Fiber = struct { const Self = @This(); pub const Status = enum { - // Just created, never started + /// Just created, never started Instanciated, - // Currently running + /// Currently running Running, - // Yielded an expected value + /// Yielded an expected value Yielded, - // Reached return statement + /// Reached return statement Over, }; @@ -115,10 +114,9 @@ pub const Fiber = struct { parent_fiber: ?*Fiber, - call_type: OpCode, - arg_count: u8, - has_catch_value: bool, - method: ?*ObjString, + // Instruction(s) that triggered the fiber + instruction: u32, + extra_instruction: ?u32, frames: std.ArrayList(CallFrame), // FIXME: this is useless since we actually pop items from the frames list @@ -144,10 +142,8 @@ pub const Fiber = struct { type_def: *ObjTypeDef, parent_fiber: ?*Fiber, stack_slice: ?[]Value, - call_type: OpCode, - arg_count: u8, - has_catch_value: bool, - method: ?*ObjString, + instruction: u32, + extra_instruction: ?u32, ) !Self { var self: Self = .{ .allocator = allocator, @@ -157,10 +153,8 @@ pub const Fiber = struct { .stack_top = undefined, .frames = std.ArrayList(CallFrame).init(allocator), .open_upvalues = null, - .call_type = call_type, - .arg_count = arg_count, - .has_catch_value = has_catch_value, - .method = method, + .instruction = instruction, + .extra_instruction = extra_instruction, }; if (stack_slice != null) { @@ -180,29 +174,128 @@ pub const Fiber = struct { self.frames.deinit(); } + // FIXME: we replicate here what opcodes do, would be easier to call the opcodes themselves but they assume + // there's a current frame with an active chunk pub fn start(self: *Self, vm: *VM) !void { assert(self.status == .Instanciated); vm.current_fiber = self; - switch (self.call_type) { - .OP_FIBER => { // | closure | ...args | ?catch | - try vm.callValue( - vm.peek(self.arg_count), - self.arg_count, - if (self.has_catch_value) vm.pop() else null, - true, + const arg_count: u8 = if (self.extra_instruction) |extra| + @intCast(extra >> 24) + else + @intCast((0x00ffffff & self.instruction) >> 16); + + const catch_count: u16 = if (self.extra_instruction) |extra| + @intCast(0x00ffffff & extra) + else + @intCast(0x0000ffff & self.instruction); + + const catch_value = if (catch_count > 1) + vm.pop() + else + null; + + switch (VM.getCode(self.instruction)) { + .OP_TAIL_CALL, .OP_CALL => try vm.callValue( + vm.peek(arg_count), + arg_count, + catch_value, + ), + .OP_CALL_INSTANCE_PROPERTY, .OP_TAIL_CALL_INSTANCE_PROPERTY => vm.callInstanceProperty( + self.extra_instruction.?, + VM.getArg(self.instruction), + false, + ), + .OP_INSTANCE_INVOKE, .OP_INSTANCE_TAIL_INVOKE => { + const instance: *ObjObjectInstance = vm.peek(arg_count).obj() + .access(ObjObjectInstance, .ObjectInstance, vm.gc).?; + + assert(instance.object != null); + + _ = try vm.invokeFromObject( + instance.object.?, + VM.getArg(self.instruction), + arg_count, + catch_value, + false, ); }, - .OP_INVOKE_FIBER => { // | receiver | ...args | ?catch | - _ = try vm.invoke( - self.method.?, - self.arg_count, - if (self.has_catch_value) vm.pop() else null, - true, + .OP_PROTOCOL_INVOKE, .OP_PROTOCOL_TAIL_INVOKE => { + const name = vm.readConstant(VM.getArg(self.instruction)) + .obj().access(ObjString, .String, vm.gc).? + .string; + + const instance: *ObjObjectInstance = vm.peek(arg_count).obj() + .access(ObjObjectInstance, .ObjectInstance, vm.gc).?; + + assert(instance.object != null); + + // Find the actual field + const property_idx = instance.type_def.resolved_type.?.ObjectInstance + .resolved_type.?.Object + .fields.get(name).?.index; + + _ = try vm.invokeFromObject( + instance.object.?, + property_idx, + arg_count, + catch_value, false, ); }, + .OP_MAP_INVOKE => { + const map = vm.peek(arg_count).obj().access(ObjMap, .Map, vm.gc).?; + const member = try map.member(vm, VM.getArg(self.instruction)); + + (self.stack_top - arg_count - 1)[0] = member; + try vm.callValue( + member, + arg_count, + catch_value, + ); + }, + .OP_LIST_INVOKE => { + const list = vm.peek(arg_count).obj().access(ObjList, .List, vm.gc).?; + const member = try list.member(vm, VM.getArg(self.instruction)); + + (self.stack_top - arg_count - 1)[0] = member; + try vm.callValue( + member, + arg_count, + catch_value, + ); + }, + .OP_RANGE_INVOKE => { + const member = try ObjRange.member(vm, VM.getArg(self.instruction)); + + (self.stack_top - arg_count - 1)[0] = member; + try vm.callValue( + member, + arg_count, + catch_value, + ); + }, + .OP_STRING_INVOKE => { + const member = try ObjString.member(vm, VM.getArg(self.instruction)); + + (self.stack_top - arg_count - 1)[0] = member; + try vm.callValue( + member, + arg_count, + catch_value, + ); + }, + .OP_PATTERN_INVOKE => { + const member = try ObjPattern.member(vm, VM.getArg(self.instruction)); + + (self.stack_top - arg_count - 1)[0] = member; + try vm.callValue( + member, + arg_count, + catch_value, + ); + }, else => unreachable, } @@ -390,7 +483,7 @@ pub const VM = struct { pub fn cliArgs(self: *Self, args: ?[][:0]u8) !*ObjList { var arg_list = try self.gc.allocateObject( ObjList, - ObjList.init( + try ObjList.init( self.gc.allocator, // TODO: get instance that already exists try self.gc.allocateObject( @@ -509,12 +602,11 @@ pub const VM = struct { }, }, ), - null, // parent fiber - null, // stack_slice - .OP_CALL, // call_type - 1, // arg_count - false, // catch_count - null, // method/member + null, + null, + // Those don't matter for the main fiber + undefined, + null, ); self.push((try self.gc.allocateObject( @@ -527,7 +619,11 @@ pub const VM = struct { try self.gc.registerVM(self); defer self.gc.unregisterVM(self); - try self.callValue(self.peek(1), 0, null, false); + try self.callValue( + self.peek(1), + 0, + null, + ); self.current_fiber.status = .Running; @@ -557,6 +653,10 @@ pub const VM = struct { return @enumFromInt(@as(u8, @intCast(instruction >> 24))); } + inline fn replaceCode(instruction: u32, new_code: OpCode) u32 { + return (@as(u32, @intCast(@intFromEnum(new_code))) << 24) | @as(u32, @intCast(getArg(instruction))); + } + inline fn getArg(instruction: u32) u24 { return @as(u24, @intCast(0x00ffffff & instruction)); } @@ -565,7 +665,7 @@ pub const VM = struct { return @as(u8, @intCast(self.readInstruction())); } - inline fn readConstant(self: *Self, arg: u24) Value { + pub inline fn readConstant(self: *Self, arg: u24) Value { return self.currentFrame().?.closure.function.chunk.constants.items[arg]; } @@ -642,8 +742,12 @@ pub const VM = struct { OP_CALL, OP_TAIL_CALL, + OP_CALL_INSTANCE_PROPERTY, + OP_TAIL_CALL_INSTANCE_PROPERTY, OP_INSTANCE_INVOKE, OP_INSTANCE_TAIL_INVOKE, + OP_PROTOCOL_INVOKE, + OP_PROTOCOL_TAIL_INVOKE, OP_STRING_INVOKE, OP_PATTERN_INVOKE, OP_FIBER_INVOKE, @@ -655,7 +759,6 @@ pub const VM = struct { OP_CLOSE_UPVALUE, OP_FIBER, - OP_INVOKE_FIBER, OP_RESUME, OP_RESOLVE, OP_YIELD, @@ -669,10 +772,12 @@ pub const VM = struct { OP_OBJECT, OP_INSTANCE, OP_FCONTAINER_INSTANCE, - OP_METHOD, OP_PROPERTY, + OP_OBJECT_DEFAULT, OP_GET_OBJECT_PROPERTY, OP_GET_INSTANCE_PROPERTY, + OP_GET_INSTANCE_METHOD, + OP_GET_PROTOCOL_METHOD, OP_GET_FCONTAINER_INSTANCE_PROPERTY, OP_GET_LIST_PROPERTY, OP_GET_MAP_PROPERTY, @@ -737,7 +842,7 @@ pub const VM = struct { const err = self.pop(); // Close scope - self.closeUpValues(@as(*Value, @ptrCast(current_frame.try_top.?))); + self.closeUpValues(@ptrCast(current_frame.try_top.?)); self.current_fiber.stack_top = current_frame.try_top.?; // Put error back on stack @@ -1191,66 +1296,37 @@ pub const VM = struct { ); } - fn OP_FIBER(self: *Self, _: *CallFrame, full_instruction: u32, instruction: OpCode, _: u24) void { - const arg_count: u8 = @intCast((0x00ffffff & full_instruction) >> 16); - const catch_count: u16 = @intCast(0x0000ffff & full_instruction); - - const stack_ptr = self.current_fiber.stack_top - arg_count - catch_count - 1; - const stack_len = arg_count + catch_count + 1; - const stack_slice = stack_ptr[0..stack_len]; - - var fiber = self.gc.allocator.create(Fiber) catch { - self.panic("Out of memory"); - unreachable; - }; - fiber.* = Fiber.init( - self.gc.allocator, - undefined, - self.current_fiber, - stack_slice, - instruction, - arg_count, - catch_count > 0, - null, - ) catch { - self.panic("Out of memory"); - unreachable; + fn OP_FIBER(self: *Self, _: *CallFrame, _: u32, _: OpCode, _: u24) void { + // We read the next instruction to know about the call we want to wrap in the fiber + const instruction = self.readInstruction(); + // Some opcodes need an extra instruction + const extra_instruction = switch (getCode(instruction)) { + .OP_CALL_INSTANCE_PROPERTY, + .OP_TAIL_CALL_INSTANCE_PROPERTY, + .OP_MAP_INVOKE, + .OP_LIST_INVOKE, + .OP_STRING_INVOKE, + .OP_PATTERN_INVOKE, + .OP_FIBER_INVOKE, + .OP_PROTOCOL_INVOKE, + .OP_PROTOCOL_TAIL_INVOKE, + .OP_RANGE_INVOKE, + .OP_INSTANCE_INVOKE, + => self.readInstruction(), + else => null, }; - // Pop arguments and catch clauses - self.current_fiber.stack_top = self.current_fiber.stack_top - stack_len; - - fiber.type_def = self.pop().obj().access(ObjTypeDef, .Type, self.gc).?; - - // Put new fiber on the stack - var obj_fiber = self.gc.allocateObject(ObjFiber, ObjFiber{ - .fiber = fiber, - }) catch { - self.panic("Out of memory"); - unreachable; - }; - - self.push(obj_fiber.toValue()); + // FIXME: in the case of PROTOCOL_INVOKE, it will try to read the constant from the other fiber's chunk and fail? - const next_full_instruction: u32 = self.readInstruction(); - @call( - dispatch_call_modifier, - dispatch, - .{ - self, - self.currentFrame().?, - next_full_instruction, - getCode(next_full_instruction), - getArg(next_full_instruction), - }, - ); - } + const arg_count: u8 = if (extra_instruction) |extra| + @intCast(extra >> 24) + else + @intCast((0x00ffffff & instruction) >> 16); - fn OP_INVOKE_FIBER(self: *Self, _: *CallFrame, _: u32, instruction: OpCode, arg: u24) void { - const method: *ObjString = self.readString(arg); - const arg_instruction: u32 = self.readInstruction(); - const arg_count: u8 = @intCast(arg_instruction >> 24); - const catch_count: u24 = @intCast(0x00ffffff & arg_instruction); + const catch_count: u24 = if (extra_instruction) |extra| + @intCast(0x00ffffff & extra) + else + @intCast(0x0000ffff & instruction); const stack_ptr = self.current_fiber.stack_top - arg_count - catch_count - 1; const stack_len = arg_count + catch_count + 1; @@ -1266,9 +1342,7 @@ pub const VM = struct { self.current_fiber, stack_slice, instruction, - arg_count, - catch_count > 0, - method, + extra_instruction, ) catch { self.panic("Out of memory"); unreachable; @@ -1279,7 +1353,7 @@ pub const VM = struct { fiber.type_def = self.pop().obj().access(ObjTypeDef, .Type, self.gc).?; - // Push new fiber on the stack + // Put new fiber on the stack var obj_fiber = self.gc.allocateObject(ObjFiber, ObjFiber{ .fiber = fiber, }) catch { @@ -1289,7 +1363,7 @@ pub const VM = struct { self.push(obj_fiber.toValue()); - const next_full_instruction: u32 = self.readInstruction(); + const next_full_instruction = self.readInstruction(); @call( dispatch_call_modifier, dispatch, @@ -1383,7 +1457,6 @@ pub const VM = struct { self.peek(arg_count), arg_count, catch_value, - false, ) catch |err| { switch (err) { Error.RuntimeError => return, @@ -1419,7 +1492,6 @@ pub const VM = struct { self.peek(arg_count), arg_count, catch_value, - false, ) catch |err| { switch (err) { Error.RuntimeError => return, @@ -1444,25 +1516,25 @@ pub const VM = struct { ); } - fn OP_INSTANCE_INVOKE(self: *Self, _: *CallFrame, _: u32, _: OpCode, arg: u24) void { - const method: *ObjString = self.readString(arg); - const arg_instruction: u32 = self.readInstruction(); + fn callInstanceProperty(self: *Self, arg_instruction: u32, property_idx: u24, tail_call: bool) void { const arg_count: u8 = @intCast(arg_instruction >> 24); const catch_count: u24 = @intCast(0x00ffffff & arg_instruction); const catch_value = if (catch_count > 0) self.pop() else null; - const instance: *ObjObjectInstance = self.peek(arg_count).obj().access(ObjObjectInstance, .ObjectInstance, self.gc).?; + const instance: *ObjObjectInstance = self.peek(arg_count).obj() + .access(ObjObjectInstance, .ObjectInstance, self.gc).?; assert(instance.object != null); - if (instance.fields.get(method)) |field| { - (self.current_fiber.stack_top - arg_count - 1)[0] = field; + const property = instance.fields[property_idx]; - self.callValue( - field, + (self.current_fiber.stack_top - arg_count - 1)[0] = property; + + if (tail_call) { + self.tailCall( + property, arg_count, catch_value, - false, ) catch |err| { switch (err) { Error.RuntimeError => return, @@ -1473,13 +1545,10 @@ pub const VM = struct { } }; } else { - _ = self.invokeFromObject( - instance.object.?, - method, + self.callValue( + property, arg_count, catch_value, - false, - false, ) catch |err| { switch (err) { Error.RuntimeError => return, @@ -1490,6 +1559,35 @@ pub const VM = struct { } }; } + } + + fn OP_CALL_INSTANCE_PROPERTY(self: *Self, _: *CallFrame, _: u32, _: OpCode, property_idx: u24) void { + self.callInstanceProperty( + self.readInstruction(), + property_idx, + false, + ); + + const next_full_instruction: u32 = self.readInstruction(); + @call( + dispatch_call_modifier, + dispatch, + .{ + self, + self.currentFrame().?, + next_full_instruction, + getCode(next_full_instruction), + getArg(next_full_instruction), + }, + ); + } + + fn OP_TAIL_CALL_INSTANCE_PROPERTY(self: *Self, _: *CallFrame, _: u32, _: OpCode, property_idx: u24) void { + self.callInstanceProperty( + self.readInstruction(), + property_idx, + true, + ); const next_full_instruction: u32 = self.readInstruction(); @call( @@ -1505,52 +1603,173 @@ pub const VM = struct { ); } - fn OP_INSTANCE_TAIL_INVOKE(self: *Self, _: *CallFrame, _: u32, _: OpCode, arg: u24) void { - const method: *ObjString = self.readString(arg); + fn OP_INSTANCE_INVOKE(self: *Self, _: *CallFrame, _: u32, _: OpCode, property_idx: u24) void { const arg_instruction: u32 = self.readInstruction(); const arg_count: u8 = @intCast(arg_instruction >> 24); const catch_count: u24 = @intCast(0x00ffffff & arg_instruction); const catch_value = if (catch_count > 0) self.pop() else null; - const instance: *ObjObjectInstance = self.peek(arg_count).obj().access(ObjObjectInstance, .ObjectInstance, self.gc).?; + const instance: *ObjObjectInstance = self.peek(arg_count).obj() + .access(ObjObjectInstance, .ObjectInstance, self.gc).?; assert(instance.object != null); - if (instance.fields.get(method)) |field| { - (self.current_fiber.stack_top - arg_count - 1)[0] = field; + _ = self.invokeFromObject( + instance.object.?, + property_idx, + arg_count, + catch_value, + false, + ) catch |err| { + switch (err) { + Error.RuntimeError => return, + else => { + self.panic("Out of memory"); + unreachable; + }, + } + }; - self.tailCall( - field, - arg_count, - catch_value, - false, - ) catch |err| { - switch (err) { - Error.RuntimeError => return, - else => { - self.panic("Out of memory"); - unreachable; - }, - } - }; - } else { - _ = self.invokeFromObject( - instance.object.?, - method, - arg_count, - catch_value, - false, - true, - ) catch |err| { - switch (err) { - Error.RuntimeError => return, - else => { - self.panic("Out of memory"); - unreachable; - }, - } - }; - } + const next_full_instruction: u32 = self.readInstruction(); + @call( + dispatch_call_modifier, + dispatch, + .{ + self, + self.currentFrame().?, + next_full_instruction, + getCode(next_full_instruction), + getArg(next_full_instruction), + }, + ); + } + + fn OP_PROTOCOL_INVOKE(self: *Self, _: *CallFrame, _: u32, _: OpCode, name_constant: u24) void { + const name = self.readConstant(name_constant) + .obj().access(ObjString, .String, self.gc).? + .string; + + const arg_instruction: u32 = self.readInstruction(); + const arg_count: u8 = @intCast(arg_instruction >> 24); + const catch_count: u24 = @intCast(0x00ffffff & arg_instruction); + const catch_value = if (catch_count > 0) self.pop() else null; + + const instance: *ObjObjectInstance = self.peek(arg_count).obj() + .access(ObjObjectInstance, .ObjectInstance, self.gc).?; + + assert(instance.object != null); + + // Find the actual field + const property_idx = instance.type_def.resolved_type.?.ObjectInstance + .resolved_type.?.Object + .fields.get(name).?.index; + + _ = self.invokeFromObject( + instance.object.?, + property_idx, + arg_count, + catch_value, + false, + ) catch |err| { + switch (err) { + Error.RuntimeError => return, + else => { + self.panic("Out of memory"); + unreachable; + }, + } + }; + + const next_full_instruction: u32 = self.readInstruction(); + @call( + dispatch_call_modifier, + dispatch, + .{ + self, + self.currentFrame().?, + next_full_instruction, + getCode(next_full_instruction), + getArg(next_full_instruction), + }, + ); + } + + fn OP_INSTANCE_TAIL_INVOKE(self: *Self, _: *CallFrame, _: u32, _: OpCode, property_idx: u24) void { + const arg_instruction: u32 = self.readInstruction(); + const arg_count: u8 = @intCast(arg_instruction >> 24); + const catch_count: u24 = @intCast(0x00ffffff & arg_instruction); + const catch_value = if (catch_count > 0) self.pop() else null; + + const instance: *ObjObjectInstance = self.peek(arg_count).obj() + .access(ObjObjectInstance, .ObjectInstance, self.gc).?; + + assert(instance.object != null); + + _ = self.invokeFromObject( + instance.object.?, + property_idx, + arg_count, + catch_value, + false, + ) catch |err| { + switch (err) { + Error.RuntimeError => return, + else => { + self.panic("Out of memory"); + unreachable; + }, + } + }; + + const next_full_instruction: u32 = self.readInstruction(); + @call( + dispatch_call_modifier, + dispatch, + .{ + self, + self.currentFrame().?, + next_full_instruction, + getCode(next_full_instruction), + getArg(next_full_instruction), + }, + ); + } + + fn OP_PROTOCOL_TAIL_INVOKE(self: *Self, _: *CallFrame, _: u32, _: OpCode, name_constant: u24) void { + const name = self.readConstant(name_constant) + .obj().access(ObjString, .String, self.gc).? + .string; + + const arg_instruction: u32 = self.readInstruction(); + const arg_count: u8 = @intCast(arg_instruction >> 24); + const catch_count: u24 = @intCast(0x00ffffff & arg_instruction); + const catch_value = if (catch_count > 0) self.pop() else null; + + const instance: *ObjObjectInstance = self.peek(arg_count).obj() + .access(ObjObjectInstance, .ObjectInstance, self.gc).?; + + assert(instance.object != null); + + // Find the actual field + const property_idx = instance.type_def.resolved_type.?.ObjectInstance + .resolved_type.?.Object + .fields.get(name).?.index; + + _ = self.invokeFromObject( + instance.object.?, + property_idx, + arg_count, + catch_value, + false, + ) catch |err| { + switch (err) { + Error.RuntimeError => return, + else => { + self.panic("Out of memory"); + unreachable; + }, + } + }; const next_full_instruction: u32 = self.readInstruction(); @call( @@ -1566,25 +1785,23 @@ pub const VM = struct { ); } - fn OP_STRING_INVOKE(self: *Self, _: *CallFrame, _: u32, _: OpCode, arg: u24) void { - const method: *ObjString = self.readString(arg); + fn OP_STRING_INVOKE(self: *Self, _: *CallFrame, _: u32, _: OpCode, method_idx: u24) void { const arg_instruction: u32 = self.readInstruction(); const arg_count: u8 = @intCast(arg_instruction >> 24); const catch_count: u24 = @intCast(0x00ffffff & arg_instruction); const catch_value = if (catch_count > 0) self.pop() else null; - const member = (ObjString.member(self, method) catch { + const member = ObjString.member(self, method_idx) catch { self.panic("Out of memory"); unreachable; - }).?; - const member_value: Value = member.toValue(); - (self.current_fiber.stack_top - arg_count - 1)[0] = member_value; + }; + + (self.current_fiber.stack_top - arg_count - 1)[0] = member; self.callValue( - member_value, + member, arg_count, catch_value, - false, ) catch |err| { switch (err) { Error.RuntimeError => return, @@ -1609,25 +1826,23 @@ pub const VM = struct { ); } - fn OP_RANGE_INVOKE(self: *Self, _: *CallFrame, _: u32, _: OpCode, arg: u24) void { - const method: *ObjString = self.readString(arg); + fn OP_RANGE_INVOKE(self: *Self, _: *CallFrame, _: u32, _: OpCode, method_idx: u24) void { const arg_instruction: u32 = self.readInstruction(); const arg_count: u8 = @intCast(arg_instruction >> 24); const catch_count: u24 = @intCast(0x00ffffff & arg_instruction); const catch_value = if (catch_count > 0) self.pop() else null; - const member = (ObjRange.member(self, method) catch { + const member = ObjRange.member(self, method_idx) catch { self.panic("Out of memory"); unreachable; - }).?; - const member_value: Value = member.toValue(); - (self.current_fiber.stack_top - arg_count - 1)[0] = member_value; + }; + + (self.current_fiber.stack_top - arg_count - 1)[0] = member; self.callValue( - member_value, + member, arg_count, catch_value, - false, ) catch |err| { switch (err) { Error.RuntimeError => return, @@ -1652,25 +1867,23 @@ pub const VM = struct { ); } - fn OP_PATTERN_INVOKE(self: *Self, _: *CallFrame, _: u32, _: OpCode, arg: u24) void { - const method: *ObjString = self.readString(arg); + fn OP_PATTERN_INVOKE(self: *Self, _: *CallFrame, _: u32, _: OpCode, method_idx: u24) void { const arg_instruction: u32 = self.readInstruction(); const arg_count: u8 = @intCast(arg_instruction >> 24); const catch_count: u24 = @intCast(0x00ffffff & arg_instruction); const catch_value = if (catch_count > 0) self.pop() else null; - const member = (ObjPattern.member(self, method) catch { + const member = ObjPattern.member(self, method_idx) catch { self.panic("Out of memory"); unreachable; - }).?; - const member_value: Value = member.toValue(); - (self.current_fiber.stack_top - arg_count - 1)[0] = member_value; + }; + + (self.current_fiber.stack_top - arg_count - 1)[0] = member; self.callValue( - member_value, + member, arg_count, catch_value, - false, ) catch |err| { switch (err) { Error.RuntimeError => return, @@ -1695,24 +1908,22 @@ pub const VM = struct { ); } - fn OP_FIBER_INVOKE(self: *Self, _: *CallFrame, _: u32, _: OpCode, arg: u24) void { - const method: *ObjString = self.readString(arg); - const arg_instruction: u32 = self.readInstruction(); + fn OP_FIBER_INVOKE(self: *Self, _: *CallFrame, _: u32, _: OpCode, method_idx: u24) void { + const arg_instruction = self.readInstruction(); const arg_count: u8 = @intCast(arg_instruction >> 24); const catch_count: u24 = @intCast(0x00ffffff & arg_instruction); const catch_value = if (catch_count > 0) self.pop() else null; - const member = (ObjFiber.member(self, method) catch { + const member = ObjFiber.member(self, method_idx) catch { self.panic("Out of memory"); unreachable; - }).?; - const member_value: Value = member.toValue(); - (self.current_fiber.stack_top - arg_count - 1)[0] = member_value; + }; + + (self.current_fiber.stack_top - arg_count - 1)[0] = member; self.callValue( - member_value, + member, arg_count, catch_value, - false, ) catch |err| { switch (err) { Error.RuntimeError => return, @@ -1737,26 +1948,23 @@ pub const VM = struct { ); } - fn OP_LIST_INVOKE(self: *Self, _: *CallFrame, _: u32, _: OpCode, arg: u24) void { - const method: *ObjString = self.readString(arg); + fn OP_LIST_INVOKE(self: *Self, _: *CallFrame, _: u32, _: OpCode, method_idx: u24) void { const arg_instruction: u32 = self.readInstruction(); const arg_count: u8 = @intCast(arg_instruction >> 24); const catch_count: u24 = @intCast(0x00ffffff & arg_instruction); const catch_value = if (catch_count > 0) self.pop() else null; const list = self.peek(arg_count).obj().access(ObjList, .List, self.gc).?; - const member = (list.member(self, method) catch { + const member = list.member(self, method_idx) catch { self.panic("Out of memory"); unreachable; - }).?; + }; - const member_value: Value = member.toValue(); - (self.current_fiber.stack_top - arg_count - 1)[0] = member_value; + (self.current_fiber.stack_top - arg_count - 1)[0] = member; self.callValue( - member_value, + member, arg_count, catch_value, - false, ) catch |err| { switch (err) { Error.RuntimeError => return, @@ -1781,26 +1989,23 @@ pub const VM = struct { ); } - fn OP_MAP_INVOKE(self: *Self, _: *CallFrame, _: u32, _: OpCode, arg: u24) void { - const method: *ObjString = self.readString(arg); + fn OP_MAP_INVOKE(self: *Self, _: *CallFrame, _: u32, _: OpCode, method_idx: u24) void { const arg_instruction: u32 = self.readInstruction(); const arg_count: u8 = @intCast(arg_instruction >> 24); const catch_count: u24 = @intCast(0x00ffffff & arg_instruction); const catch_value = if (catch_count > 0) self.pop() else null; const map = self.peek(arg_count).obj().access(ObjMap, .Map, self.gc).?; - const member = (map.member(self, method) catch { + const member = map.member(self, method_idx) catch { self.panic("Out of memory"); unreachable; - }).?; + }; - const member_value: Value = member.toValue(); - (self.current_fiber.stack_top - arg_count - 1)[0] = member_value; + (self.current_fiber.stack_top - arg_count - 1)[0] = member; self.callValue( - member_value, + member, arg_count, catch_value, - false, ) catch |err| { switch (err) { Error.RuntimeError => return, @@ -1861,9 +2066,9 @@ pub const VM = struct { return false; } - inline fn repurposeFrame(self: *Self, closure: *ObjClosure, arg_count: u8, catch_value: ?Value, in_fiber: bool) Error!void { + inline fn repurposeFrame(self: *Self, closure: *ObjClosure, arg_count: u8, catch_value: ?Value) Error!void { // Is or will be JIT compiled, call and stop there - if (!is_wasm and !in_fiber and try self.compileAndCall(closure, arg_count, catch_value)) { + if (!is_wasm and self.current_fiber.parent_fiber == null and try self.compileAndCall(closure, arg_count, catch_value)) { return; } @@ -2073,7 +2278,10 @@ pub const VM = struct { .Type, self.gc, ).?, - ), + ) catch { + self.panic("Out of memory"); + unreachable; + }, ) catch { self.panic("Out of memory"); unreachable; @@ -2156,10 +2364,16 @@ pub const VM = struct { } fn OP_MAP(self: *Self, _: *CallFrame, _: u32, _: OpCode, arg: u24) void { - var map: *ObjMap = self.gc.allocateObject(ObjMap, ObjMap.init( - self.gc.allocator, - self.readConstant(arg).obj().access(ObjTypeDef, .Type, self.gc).?, - )) catch { + var map: *ObjMap = self.gc.allocateObject( + ObjMap, + ObjMap.init( + self.gc.allocator, + self.readConstant(arg).obj().access(ObjTypeDef, .Type, self.gc).?, + ) catch { + self.panic("Out of memory"); + unreachable; + }, + ) catch { self.panic("Out of memory"); unreachable; }; @@ -2587,8 +2801,14 @@ pub const VM = struct { ObjObject.init( self.gc.allocator, self.readConstant(arg).obj().access(ObjString, .String, self.gc).?, - self.readConstant(@as(u24, @intCast(self.readInstruction()))).obj().access(ObjTypeDef, .Type, self.gc).?, - ), + self.readConstant( + @intCast(self.readInstruction()), + ) + .obj().access(ObjTypeDef, .Type, self.gc).?, + ) catch { + self.panic("Out of memory"); + unreachable; + }, ) catch { self.panic("Out of memory"); unreachable; @@ -2656,7 +2876,11 @@ pub const VM = struct { self, object, typedef, - ), + self.gc, + ) catch { + self.panic("Out of memory"); + unreachable; + }, ) catch { self.panic("Out of memory"); unreachable; @@ -2665,19 +2889,20 @@ pub const VM = struct { // If not anonymous, set default fields if (object) |obj| { // Set instance fields with default values - var it = obj.fields.iterator(); - while (it.next()) |kv| { - obj_instance.setField( - self.gc, - kv.key_ptr.*, - self.cloneValue(kv.value_ptr.*) catch { + for (obj.defaults, 0..) |default_opt, i| { + if (default_opt) |default| { + obj_instance.setField( + self.gc, + i, + self.cloneValue(default) catch { + self.panic("Out of memory"); + unreachable; + }, + ) catch { self.panic("Out of memory"); unreachable; - }, - ) catch { - self.panic("Out of memory"); - unreachable; - }; + }; + } } } @@ -2697,14 +2922,13 @@ pub const VM = struct { ); } - fn OP_METHOD(self: *Self, _: *CallFrame, _: u32, _: OpCode, arg: u24) void { - const name = self.readString(arg); - var method: Value = self.peek(0); - var object: *ObjObject = self.peek(1).obj().access(ObjObject, .Object, self.gc).?; - - object.methods.put( - name, - method.obj().access(ObjClosure, .Closure, self.gc).?, + fn OP_OBJECT_DEFAULT(self: *Self, _: *CallFrame, _: u32, _: OpCode, property_idx: u24) void { + self.peek(1).obj() + .access(ObjObject, .Object, self.gc).? + .setPropertyDefaultValue( + self.gc, + property_idx, + self.peek(0), ) catch { self.panic("Out of memory"); unreachable; @@ -2726,27 +2950,17 @@ pub const VM = struct { ); } - fn OP_PROPERTY(self: *Self, _: *CallFrame, _: u32, _: OpCode, arg: u24) void { - const name = self.readString(arg); - const property = self.peek(0); - var object = self.peek(1).obj().access(ObjObject, .Object, self.gc).?; - const field = object.type_def.resolved_type.?.Object.fields.get(name.string).?; - - if (!field.static) { - object.setField(self.gc, name, property) catch { - self.panic("Out of memory"); - unreachable; - }; - } else { - assert( - object.type_def.resolved_type.?.Object.fields.contains(name.string) and - object.type_def.resolved_type.?.Object.fields.get(name.string).?.static, - ); - object.setStaticField(self.gc, name, property) catch { - self.panic("Out of memory"); - unreachable; - }; - } + fn OP_PROPERTY(self: *Self, _: *CallFrame, _: u32, _: OpCode, property_idx: u24) void { + self.peek(1).obj() + .access(ObjObject, .Object, self.gc).? + .setField( + self.gc, + property_idx, + self.peek(0), + ) catch { + self.panic("Out of memory"); + unreachable; + }; _ = self.pop(); @@ -2764,12 +2978,11 @@ pub const VM = struct { ); } - fn OP_GET_OBJECT_PROPERTY(self: *Self, _: *CallFrame, _: u32, _: OpCode, arg: u24) void { - const object: *ObjObject = self.peek(0).obj().access(ObjObject, .Object, self.gc).?; - const name: *ObjString = self.readString(arg); + fn OP_GET_OBJECT_PROPERTY(self: *Self, _: *CallFrame, _: u32, _: OpCode, property_idx: u24) void { + const object = self.peek(0).obj().access(ObjObject, .Object, self.gc).?; _ = self.pop(); // Pop instance - self.push(object.static_fields.get(name).?); + self.push(object.fields[property_idx]); const next_full_instruction: u32 = self.readInstruction(); @call( @@ -2785,9 +2998,8 @@ pub const VM = struct { ); } - fn OP_GET_FCONTAINER_INSTANCE_PROPERTY(self: *Self, _: *CallFrame, _: u32, _: OpCode, arg: u24) void { + fn OP_GET_FCONTAINER_INSTANCE_PROPERTY(self: *Self, _: *CallFrame, _: u32, _: OpCode, field_idx: u24) void { const instance_value = self.peek(0); - const name: *ObjString = self.readString(arg); const struct_instance = instance_value.obj().access( ObjForeignContainer, @@ -2796,7 +3008,7 @@ pub const VM = struct { ).?; _ = self.pop(); // Pop instance - self.push(struct_instance.getField(self, name.string)); + self.push(struct_instance.getField(self, field_idx)); const next_full_instruction: u32 = self.readInstruction(); @call( @@ -2812,24 +3024,15 @@ pub const VM = struct { ); } - fn OP_GET_INSTANCE_PROPERTY(self: *Self, _: *CallFrame, _: u32, _: OpCode, arg: u24) void { + fn OP_GET_INSTANCE_PROPERTY(self: *Self, _: *CallFrame, _: u32, _: OpCode, property_idx: u24) void { const instance_value = self.peek(0); - const name: *ObjString = self.readString(arg); - const obj_instance = instance_value.obj().access(ObjObjectInstance, .ObjectInstance, self.gc).?; - - if (obj_instance.fields.get(name)) |field| { - _ = self.pop(); // Pop instance - self.push(field); - } else if (obj_instance.object) |object| { - if (object.methods.get(name)) |method| { - self.bindMethod(method, null) catch { - self.panic("Out of memory"); - unreachable; - }; - } else { - unreachable; - } - } + + _ = self.pop(); // Pop instance + self.push( + instance_value.obj() + .access(ObjObjectInstance, .ObjectInstance, self.gc).? + .fields[property_idx], + ); const next_full_instruction: u32 = self.readInstruction(); @call( @@ -2845,21 +3048,22 @@ pub const VM = struct { ); } - fn OP_GET_LIST_PROPERTY(self: *Self, _: *CallFrame, _: u32, _: OpCode, arg: u24) void { - const list = self.peek(0).obj().access(ObjList, .List, self.gc).?; - const name: *ObjString = self.readString(arg); + fn OP_GET_INSTANCE_METHOD(self: *Self, _: *CallFrame, _: u32, _: OpCode, method_idx: u24) void { + const instance_value = self.peek(0); - if (list.member(self, name) catch { + self.bindMethod( + instance_value + .obj() + .access(ObjObjectInstance, .ObjectInstance, self.gc).? + .object.? + .fields[method_idx] + .obj() + .access(ObjClosure, .Closure, self.gc), + null, + ) catch { self.panic("Out of memory"); unreachable; - }) |member| { - self.bindMethod(null, member) catch { - self.panic("Out of memory"); - unreachable; - }; - } else { - unreachable; - } + }; const next_full_instruction: u32 = self.readInstruction(); @call( @@ -2874,21 +3078,31 @@ pub const VM = struct { }, ); } - fn OP_GET_MAP_PROPERTY(self: *Self, _: *CallFrame, _: u32, _: OpCode, arg: u24) void { - const map = self.peek(0).obj().access(ObjMap, .Map, self.gc).?; - const name: *ObjString = self.readString(arg); - if (map.member(self, name) catch { + fn OP_GET_PROTOCOL_METHOD(self: *Self, _: *CallFrame, _: u32, _: OpCode, name_constant: u24) void { + const instance: *ObjObjectInstance = self.peek(0).obj() + .access(ObjObjectInstance, .ObjectInstance, self.gc).?; + + const name = self.readConstant(name_constant).obj() + .access(ObjString, .String, self.gc).? + .string; + + // Find the actual field + const method_idx = instance.type_def.resolved_type.?.ObjectInstance + .resolved_type.?.Object + .fields.get(name).?.index; + + self.bindMethod( + instance + .object.? + .fields[method_idx] + .obj() + .access(ObjClosure, .Closure, self.gc), + null, + ) catch { self.panic("Out of memory"); unreachable; - }) |member| { - self.bindMethod(null, member) catch { - self.panic("Out of memory"); - unreachable; - }; - } else { - unreachable; - } + }; const next_full_instruction: u32 = self.readInstruction(); @call( @@ -2904,20 +3118,44 @@ pub const VM = struct { ); } - fn OP_GET_STRING_PROPERTY(self: *Self, _: *CallFrame, _: u32, _: OpCode, arg: u24) void { - const name: *ObjString = self.readString(arg); - - if (ObjString.member(self, name) catch { + fn OP_GET_LIST_PROPERTY(self: *Self, _: *CallFrame, _: u32, _: OpCode, method_idx: u24) void { + self.bindMethod( + null, + (self.peek(0).obj().access(ObjList, .List, self.gc).? + .member(self, method_idx) catch { + self.panic("Out of memory"); + unreachable; + }).obj().access(ObjNative, .Native, self.gc).?, + ) catch { self.panic("Out of memory"); unreachable; - }) |member| { - self.bindMethod(null, member) catch { + }; + + const next_full_instruction: u32 = self.readInstruction(); + @call( + dispatch_call_modifier, + dispatch, + .{ + self, + self.currentFrame().?, + next_full_instruction, + getCode(next_full_instruction), + getArg(next_full_instruction), + }, + ); + } + fn OP_GET_MAP_PROPERTY(self: *Self, _: *CallFrame, _: u32, _: OpCode, method_idx: u24) void { + self.bindMethod( + null, + (self.peek(0).obj().access(ObjMap, .Map, self.gc).? + .member(self, method_idx) catch { self.panic("Out of memory"); unreachable; - }; - } else { + }).obj().access(ObjNative, .Native, self.gc), + ) catch { + self.panic("Out of memory"); unreachable; - } + }; const next_full_instruction: u32 = self.readInstruction(); @call( @@ -2933,20 +3171,18 @@ pub const VM = struct { ); } - fn OP_GET_PATTERN_PROPERTY(self: *Self, _: *CallFrame, _: u32, _: OpCode, arg: u24) void { - const name: *ObjString = self.readString(arg); - - if (ObjPattern.member(self, name) catch { - self.panic("Out of memory"); - unreachable; - }) |member| { - self.bindMethod(null, member) catch { + fn OP_GET_STRING_PROPERTY(self: *Self, _: *CallFrame, _: u32, _: OpCode, method_idx: u24) void { + self.bindMethod( + null, + (ObjString + .member(self, method_idx) catch { self.panic("Out of memory"); unreachable; - }; - } else { + }).obj().access(ObjNative, .Native, self.gc), + ) catch { + self.panic("Out of memory"); unreachable; - } + }; const next_full_instruction: u32 = self.readInstruction(); @call( @@ -2962,20 +3198,18 @@ pub const VM = struct { ); } - fn OP_GET_FIBER_PROPERTY(self: *Self, _: *CallFrame, _: u32, _: OpCode, arg: u24) void { - const name: *ObjString = self.readString(arg); - - if (ObjFiber.member(self, name) catch { - self.panic("Out of memory"); - unreachable; - }) |member| { - self.bindMethod(null, member) catch { + fn OP_GET_PATTERN_PROPERTY(self: *Self, _: *CallFrame, _: u32, _: OpCode, method_idx: u24) void { + self.bindMethod( + null, + (ObjPattern + .member(self, method_idx) catch { self.panic("Out of memory"); unreachable; - }; - } else { + }).obj().access(ObjNative, .Native, self.gc), + ) catch { + self.panic("Out of memory"); unreachable; - } + }; const next_full_instruction: u32 = self.readInstruction(); @call( @@ -2991,25 +3225,44 @@ pub const VM = struct { ); } - fn OP_GET_RANGE_PROPERTY(self: *Self, _: *CallFrame, _: u32, _: OpCode, arg: u24) void { - const range = self.peek(0).obj().access(ObjRange, .Range, self.gc).?; - const name: *ObjString = self.readString(arg); - - if (ObjRange.member(self, name) catch { + fn OP_GET_FIBER_PROPERTY(self: *Self, _: *CallFrame, _: u32, _: OpCode, method_idx: u24) void { + self.bindMethod( + null, + (ObjFiber + .member(self, method_idx) catch { + self.panic("Out of memory"); + unreachable; + }).obj().access(ObjNative, .Native, self.gc), + ) catch { self.panic("Out of memory"); unreachable; - }) |member| { - self.bindMethod(null, member) catch { + }; + + const next_full_instruction: u32 = self.readInstruction(); + @call( + dispatch_call_modifier, + dispatch, + .{ + self, + self.currentFrame().?, + next_full_instruction, + getCode(next_full_instruction), + getArg(next_full_instruction), + }, + ); + } + + fn OP_GET_RANGE_PROPERTY(self: *Self, _: *CallFrame, _: u32, _: OpCode, method_idx: u24) void { + self.bindMethod( + null, + (ObjRange.member(self, method_idx) catch { self.panic("Out of memory"); unreachable; - }; - } else if (std.mem.eql(u8, "high", name.string)) { - _ = self.pop(); // Pop range - self.push(Value.fromInteger(range.high)); - } else { - _ = self.pop(); // Pop range - self.push(Value.fromInteger(range.low)); - } + }).obj().access(ObjNative, .Native, self.gc), + ) catch { + self.panic("Out of memory"); + unreachable; + }; const next_full_instruction: u32 = self.readInstruction(); @call( @@ -3025,12 +3278,15 @@ pub const VM = struct { ); } - fn OP_SET_OBJECT_PROPERTY(self: *Self, _: *CallFrame, _: u32, _: OpCode, arg: u24) void { + fn OP_SET_OBJECT_PROPERTY(self: *Self, _: *CallFrame, _: u32, _: OpCode, property_idx: u24) void { const object: *ObjObject = self.peek(1).obj().access(ObjObject, .Object, self.gc).?; - const name: *ObjString = self.readString(arg); // Set new value - object.setStaticField(self.gc, name, self.peek(0)) catch { + object.setField( + self.gc, + property_idx, + self.peek(0), + ) catch { self.panic("Out of memory"); unreachable; }; @@ -3054,9 +3310,8 @@ pub const VM = struct { ); } - fn OP_SET_FCONTAINER_INSTANCE_PROPERTY(self: *Self, _: *CallFrame, _: u32, _: OpCode, arg: u24) void { + fn OP_SET_FCONTAINER_INSTANCE_PROPERTY(self: *Self, _: *CallFrame, _: u32, _: OpCode, field_idx: u24) void { const instance_value = self.peek(1); - const name: *ObjString = self.readString(arg); const struct_instance = instance_value.obj().access( ObjForeignContainer, @@ -3066,7 +3321,7 @@ pub const VM = struct { struct_instance.setField( self, - name.string, + field_idx, self.peek(0), ) catch { self.panic("Out of memory"); @@ -3092,9 +3347,8 @@ pub const VM = struct { ); } - fn OP_SET_INSTANCE_PROPERTY(self: *Self, _: *CallFrame, _: u32, _: OpCode, arg: u24) void { + fn OP_SET_INSTANCE_PROPERTY(self: *Self, _: *CallFrame, _: u32, _: OpCode, property_idx: u24) void { const instance_value = self.peek(1); - const name: *ObjString = self.readString(arg); // Set new value instance_value.obj().access( @@ -3103,7 +3357,7 @@ pub const VM = struct { self.gc, ).?.setField( self.gc, - name, + property_idx, self.peek(0), ) catch { self.panic("Out of memory"); @@ -4217,12 +4471,22 @@ pub const VM = struct { // Raise the runtime error // If object instance, does it have a str `message` field ? - var processed_payload = payload; - if (payload.isObj()) { + const processed_payload = + if (payload.isObj()) + payload: { if (payload.obj().access(ObjObjectInstance, .ObjectInstance, self.gc)) |instance| { - processed_payload = instance.fields.get(try self.gc.copyString("message")) orelse payload; + const object_def = instance.type_def.resolved_type.?.ObjectInstance + .resolved_type.?.Object; + + if (object_def.fields.get("message")) |field| { + if (!field.method and !field.static) { + break :payload instance.fields[field.index]; + } + } } - } + + break :payload payload; + } else payload; const value_str = try processed_payload.toStringAlloc(self.gc.allocator); defer value_str.deinit(); @@ -4418,7 +4682,7 @@ pub const VM = struct { return false; } - fn call(self: *Self, closure: *ObjClosure, arg_count: u8, catch_value: ?Value, in_fiber: bool) Error!void { + fn call(self: *Self, closure: *ObjClosure, arg_count: u8, catch_value: ?Value) Error!void { closure.function.call_count += 1; if (BuildOptions.recursive_call_limit) |recursive_call_limit| { @@ -4441,7 +4705,13 @@ pub const VM = struct { } // Is or will be JIT compiled, call and stop there - if (!is_wasm and !in_fiber and try self.compileAndCall(closure, arg_count, catch_value)) { + if (!is_wasm and + self.current_fiber.parent_fiber == null and + try self.compileAndCall( + closure, + arg_count, + catch_value, + )) { return; } @@ -4619,7 +4889,7 @@ pub const VM = struct { self.push(Value.fromObj(bound.toObj())); } - pub fn callValue(self: *Self, callee: Value, arg_count: u8, catch_value: ?Value, in_fiber: bool) Error!void { + pub fn callValue(self: *Self, callee: Value, arg_count: u8, catch_value: ?Value) Error!void { var obj: *Obj = callee.obj(); switch (obj.obj_type) { .Bound => { @@ -4631,7 +4901,6 @@ pub const VM = struct { closure, arg_count, catch_value, - in_fiber, ); } else { assert(bound.native != null); @@ -4647,7 +4916,6 @@ pub const VM = struct { obj.access(ObjClosure, .Closure, self.gc).?, arg_count, catch_value, - in_fiber, ); }, .Native => { @@ -4663,7 +4931,7 @@ pub const VM = struct { } } - fn tailCall(self: *Self, callee: Value, arg_count: u8, catch_value: ?Value, in_fiber: bool) Error!void { + fn tailCall(self: *Self, callee: Value, arg_count: u8, catch_value: ?Value) Error!void { var obj: *Obj = callee.obj(); switch (obj.obj_type) { .Bound => { @@ -4675,7 +4943,6 @@ pub const VM = struct { closure, arg_count, catch_value, - in_fiber, ); } else { assert(bound.native != null); @@ -4691,7 +4958,6 @@ pub const VM = struct { obj.access(ObjClosure, .Closure, self.gc).?, arg_count, catch_value, - in_fiber, ); }, .Native => { @@ -4710,31 +4976,36 @@ pub const VM = struct { fn invokeFromObject( self: *Self, object: *ObjObject, - name: *ObjString, + method_idx: usize, arg_count: u8, catch_value: ?Value, - in_fiber: bool, tail_call: bool, ) !Value { - if (object.methods.get(name)) |method| { - if (tail_call) - try self.tailCall(method.toValue(), arg_count, catch_value, in_fiber) - else - try self.call(method, arg_count, catch_value, in_fiber); + const method = object.fields[method_idx]; - return method.toValue(); - } + if (tail_call) + try self.tailCall( + method, + arg_count, + catch_value, + ) + else + try self.call( + method.obj().access(ObjClosure, .Closure, self.gc).?, + arg_count, + catch_value, + ); - unreachable; + return method; } - // FIXME: find way to remove + // Used by bz_invoke pub fn invoke( self: *Self, - name: *ObjString, + is_property_call: bool, + member_idx: usize, arg_count: u8, catch_value: ?Value, - in_fiber: bool, tail_call: bool, ) !Value { var receiver: Value = self.peek(arg_count); @@ -4746,89 +5017,54 @@ pub const VM = struct { assert(instance.object != null); - if (instance.fields.get(name)) |field| { - (self.current_fiber.stack_top - arg_count - 1)[0] = field; + if (is_property_call) { + const property = instance.fields[member_idx]; + + (self.current_fiber.stack_top - arg_count - 1)[0] = property; if (tail_call) - try self.tailCall(field, arg_count, catch_value, in_fiber) + try self.tailCall( + property, + arg_count, + catch_value, + ) else - try self.callValue(field, arg_count, catch_value, in_fiber); + try self.callValue( + property, + arg_count, + catch_value, + ); - return field; + return property; } return try self.invokeFromObject( instance.object.?, - name, + member_idx, arg_count, catch_value, - in_fiber, tail_call, ); }, - .String => { - if (try ObjString.member(self, name)) |member| { - const member_value = member.toValue(); - (self.current_fiber.stack_top - arg_count - 1)[0] = member_value; - - try self.callValue(member_value, arg_count, catch_value, in_fiber); - - return member_value; - } - - unreachable; - }, - .Pattern => { - if (try ObjPattern.member(self, name)) |member| { - const member_value = member.toValue(); - (self.current_fiber.stack_top - arg_count - 1)[0] = member_value; - - try self.callValue(member_value, arg_count, catch_value, in_fiber); - - return member_value; - } - - unreachable; - }, - .Fiber => { - if (try ObjFiber.member(self, name)) |member| { - const member_value = member.toValue(); - (self.current_fiber.stack_top - arg_count - 1)[0] = member_value; - - try self.callValue(member_value, arg_count, catch_value, in_fiber); - - return member_value; - } - - unreachable; - }, - .List => { - const list = obj.access(ObjList, .List, self.gc).?; - - if (try list.member(self, name)) |member| { - const member_value = member.toValue(); - (self.current_fiber.stack_top - arg_count - 1)[0] = member_value; - - try self.callValue(member_value, arg_count, catch_value, in_fiber); - - return member_value; - } - - unreachable; - }, - .Map => { - const map = obj.access(ObjMap, .Map, self.gc).?; - - if (try map.member(self, name)) |member| { - const member_value = member.toValue(); - (self.current_fiber.stack_top - arg_count - 1)[0] = member_value; + .String, .Pattern, .Fiber, .List, .Map => { + const member = switch (obj.obj_type) { + .String => try ObjString.member(self, member_idx), + .Pattern => try ObjPattern.member(self, member_idx), + .Fiber => try ObjFiber.member(self, member_idx), + .List => try obj.access(ObjList, .List, self.gc).?.member(self, member_idx), + .Map => try obj.access(ObjMap, .Map, self.gc).?.member(self, member_idx), + else => unreachable, + }; - try self.callValue(member_value, arg_count, catch_value, in_fiber); + (self.current_fiber.stack_top - arg_count - 1)[0] = member; - return member_value; - } + try self.callValue( + member, + arg_count, + catch_value, + ); - unreachable; + return member; }, else => unreachable, } diff --git a/src/wasm_repl.zig b/src/wasm_repl.zig index b2d87ef5..6c854505 100644 --- a/src/wasm_repl.zig +++ b/src/wasm_repl.zig @@ -36,7 +36,7 @@ pub export fn initRepl() *ReplCtx { import_registry.* = ImportRegistry.init(allocator); const gc = allocator.create(GarbageCollector) catch unreachable; - gc.* = GarbageCollector.init(allocator); + gc.* = GarbageCollector.init(allocator) catch unreachable; gc.type_registry = TypeRegistry.init(gc) catch unreachable; const imports = allocator.create(std.StringHashMap(Parser.ScriptImport)) catch unreachable; diff --git a/tests/053-range.buzz b/tests/053-range.buzz index 21d0ffef..5439718f 100644 --- a/tests/053-range.buzz +++ b/tests/053-range.buzz @@ -5,8 +5,8 @@ test "Range" { rg range = 0..limit; std.assert(range == 0..10, message: "Could compare ranges"); - std.assert(range.low == 0, message: "Could get low limit of range"); - std.assert(range.high == 10, message: "Could get high limit of range"); + std.assert(range.low() == 0, message: "Could get low limit of range"); + std.assert(range.high() == 10, message: "Could get high limit of range"); [int] list = range.toList(); std.assert(list.len() == 10, message: "Could create list from range"); @@ -25,8 +25,8 @@ test "Inverted range" { rg range = 10..limit; std.assert((0..10).invert() == range, message: "Could invert range"); - std.assert(range.low == 10, message: "Could get low limit of range"); - std.assert(range.high == 0, message: "Could get high limit of range"); + std.assert(range.low() == 10, message: "Could get low limit of range"); + std.assert(range.high() == 0, message: "Could get high limit of range"); [int] list = range.toList(); std.assert(list.len() == 10, message: "Could create list from inverted range");