From 3454ae0465ed667b63d004c1143aed665dda0aed Mon Sep 17 00:00:00 2001 From: ruby0x1 Date: Mon, 6 Jun 2022 14:56:07 -0700 Subject: [PATCH] initial debugger commit - most work contributed by @KeyMaster- as part of luxe engine! --- doc/notes/debugger.md | 30 + projects/make.bsd/wren.make | 4 + projects/make.bsd/wren_shared.make | 4 + projects/make.mac/wren.make | 4 + projects/make.mac/wren_shared.make | 4 + projects/make/wren.make | 4 + projects/make/wren_shared.make | 4 + projects/vs2017/wren.vcxproj | 6 +- projects/vs2017/wren.vcxproj.filters | 12 + projects/vs2017/wren_shared.vcxproj | 6 +- projects/vs2017/wren_shared.vcxproj.filters | 12 + projects/vs2017/wren_test.vcxproj | 2 +- projects/vs2019/wren.vcxproj | 4 + projects/vs2019/wren.vcxproj.filters | 12 + projects/vs2019/wren_shared.vcxproj | 4 + projects/vs2019/wren_shared.vcxproj.filters | 12 + projects/xcode/wren.xcodeproj/project.pbxproj | 10 + .../wren_shared.xcodeproj/project.pbxproj | 10 + src/include/wren.h | 15 + src/vm/wren_common.h | 1 + src/vm/wren_compiler.c | 99 +- src/vm/wren_core.c | 2 +- src/vm/wren_debugger.c | 913 ++++++++++++++++++ src/vm/wren_debugger.h | 138 +++ src/vm/wren_debugger_types.h | 37 + src/vm/wren_value.c | 63 +- src/vm/wren_value.h | 21 +- src/vm/wren_vm.c | 44 +- src/vm/wren_vm.h | 8 + 29 files changed, 1469 insertions(+), 16 deletions(-) create mode 100644 doc/notes/debugger.md create mode 100644 src/vm/wren_debugger.c create mode 100644 src/vm/wren_debugger.h create mode 100644 src/vm/wren_debugger_types.h diff --git a/doc/notes/debugger.md b/doc/notes/debugger.md new file mode 100644 index 00000000..5f51d96f --- /dev/null +++ b/doc/notes/debugger.md @@ -0,0 +1,30 @@ + +# The debugger is work in progress + +This branch is very experimental and not finished. +There's a lot of exploratory code that isn't well defined or cleaned up. +This is expected. + +We welcome contribution to make it more complete. +Please open a discussion to discuss major changes before doing them. + +https://github.com/wren-lang/wren/issues/425 + +# Testing the debugger +- enable `#define WREN_DEBUGGER 1` in include/wren.h +- enable `#define WREN_DEBUGGER 1` in wren_common.h +- call `wrenDebuggerPollConfigCmds(vm)` in e.g a main loop + +# Accessing the debugger +The debugger operates a simple protocol over a network socket. +You can connect to it with telnet for example and control it. + +The vscode extension below integrates with it for a bigger example. + +# The (very wip) vscode extension + +Debugging shown in this video in vscode was made possible using this extension: +https://github.com/wren-lang/wren-vscode + +The extension is also very rough and experimental. + diff --git a/projects/make.bsd/wren.make b/projects/make.bsd/wren.make index 9ef71928..da553972 100644 --- a/projects/make.bsd/wren.make +++ b/projects/make.bsd/wren.make @@ -103,6 +103,7 @@ OBJECTS := OBJECTS += $(OBJDIR)/wren_compiler.o OBJECTS += $(OBJDIR)/wren_core.o OBJECTS += $(OBJDIR)/wren_debug.o +OBJECTS += $(OBJDIR)/wren_debugger.o OBJECTS += $(OBJDIR)/wren_opt_meta.o OBJECTS += $(OBJDIR)/wren_opt_random.o OBJECTS += $(OBJDIR)/wren_primitive.o @@ -185,6 +186,9 @@ $(OBJDIR)/wren_core.o: ../../src/vm/wren_core.c $(OBJDIR)/wren_debug.o: ../../src/vm/wren_debug.c @echo $(notdir $<) $(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<" +$(OBJDIR)/wren_debugger.o: ../../src/vm/wren_debugger.c + @echo $(notdir $<) + $(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<" $(OBJDIR)/wren_primitive.o: ../../src/vm/wren_primitive.c @echo $(notdir $<) $(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<" diff --git a/projects/make.bsd/wren_shared.make b/projects/make.bsd/wren_shared.make index 18459980..24aabb6d 100644 --- a/projects/make.bsd/wren_shared.make +++ b/projects/make.bsd/wren_shared.make @@ -103,6 +103,7 @@ OBJECTS := OBJECTS += $(OBJDIR)/wren_compiler.o OBJECTS += $(OBJDIR)/wren_core.o OBJECTS += $(OBJDIR)/wren_debug.o +OBJECTS += $(OBJDIR)/wren_debugger.o OBJECTS += $(OBJDIR)/wren_opt_meta.o OBJECTS += $(OBJDIR)/wren_opt_random.o OBJECTS += $(OBJDIR)/wren_primitive.o @@ -185,6 +186,9 @@ $(OBJDIR)/wren_core.o: ../../src/vm/wren_core.c $(OBJDIR)/wren_debug.o: ../../src/vm/wren_debug.c @echo $(notdir $<) $(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<" +$(OBJDIR)/wren_debugger.o: ../../src/vm/wren_debugger.c + @echo $(notdir $<) + $(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<" $(OBJDIR)/wren_primitive.o: ../../src/vm/wren_primitive.c @echo $(notdir $<) $(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<" diff --git a/projects/make.mac/wren.make b/projects/make.mac/wren.make index 43198d10..d3e36647 100644 --- a/projects/make.mac/wren.make +++ b/projects/make.mac/wren.make @@ -111,6 +111,7 @@ OBJECTS := OBJECTS += $(OBJDIR)/wren_compiler.o OBJECTS += $(OBJDIR)/wren_core.o OBJECTS += $(OBJDIR)/wren_debug.o +OBJECTS += $(OBJDIR)/wren_debugger.o OBJECTS += $(OBJDIR)/wren_opt_meta.o OBJECTS += $(OBJDIR)/wren_opt_random.o OBJECTS += $(OBJDIR)/wren_primitive.o @@ -193,6 +194,9 @@ $(OBJDIR)/wren_core.o: ../../src/vm/wren_core.c $(OBJDIR)/wren_debug.o: ../../src/vm/wren_debug.c @echo $(notdir $<) $(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<" +$(OBJDIR)/wren_debugger.o: ../../src/vm/wren_debugger.c + @echo $(notdir $<) + $(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<" $(OBJDIR)/wren_primitive.o: ../../src/vm/wren_primitive.c @echo $(notdir $<) $(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<" diff --git a/projects/make.mac/wren_shared.make b/projects/make.mac/wren_shared.make index 862d88d0..098b0f8d 100644 --- a/projects/make.mac/wren_shared.make +++ b/projects/make.mac/wren_shared.make @@ -111,6 +111,7 @@ OBJECTS := OBJECTS += $(OBJDIR)/wren_compiler.o OBJECTS += $(OBJDIR)/wren_core.o OBJECTS += $(OBJDIR)/wren_debug.o +OBJECTS += $(OBJDIR)/wren_debugger.o OBJECTS += $(OBJDIR)/wren_opt_meta.o OBJECTS += $(OBJDIR)/wren_opt_random.o OBJECTS += $(OBJDIR)/wren_primitive.o @@ -193,6 +194,9 @@ $(OBJDIR)/wren_core.o: ../../src/vm/wren_core.c $(OBJDIR)/wren_debug.o: ../../src/vm/wren_debug.c @echo $(notdir $<) $(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<" +$(OBJDIR)/wren_debugger.o: ../../src/vm/wren_debugger.c + @echo $(notdir $<) + $(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<" $(OBJDIR)/wren_primitive.o: ../../src/vm/wren_primitive.c @echo $(notdir $<) $(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<" diff --git a/projects/make/wren.make b/projects/make/wren.make index 9ef71928..da553972 100644 --- a/projects/make/wren.make +++ b/projects/make/wren.make @@ -103,6 +103,7 @@ OBJECTS := OBJECTS += $(OBJDIR)/wren_compiler.o OBJECTS += $(OBJDIR)/wren_core.o OBJECTS += $(OBJDIR)/wren_debug.o +OBJECTS += $(OBJDIR)/wren_debugger.o OBJECTS += $(OBJDIR)/wren_opt_meta.o OBJECTS += $(OBJDIR)/wren_opt_random.o OBJECTS += $(OBJDIR)/wren_primitive.o @@ -185,6 +186,9 @@ $(OBJDIR)/wren_core.o: ../../src/vm/wren_core.c $(OBJDIR)/wren_debug.o: ../../src/vm/wren_debug.c @echo $(notdir $<) $(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<" +$(OBJDIR)/wren_debugger.o: ../../src/vm/wren_debugger.c + @echo $(notdir $<) + $(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<" $(OBJDIR)/wren_primitive.o: ../../src/vm/wren_primitive.c @echo $(notdir $<) $(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<" diff --git a/projects/make/wren_shared.make b/projects/make/wren_shared.make index 938aefe0..5353563a 100644 --- a/projects/make/wren_shared.make +++ b/projects/make/wren_shared.make @@ -103,6 +103,7 @@ OBJECTS := OBJECTS += $(OBJDIR)/wren_compiler.o OBJECTS += $(OBJDIR)/wren_core.o OBJECTS += $(OBJDIR)/wren_debug.o +OBJECTS += $(OBJDIR)/wren_debugger.o OBJECTS += $(OBJDIR)/wren_opt_meta.o OBJECTS += $(OBJDIR)/wren_opt_random.o OBJECTS += $(OBJDIR)/wren_primitive.o @@ -185,6 +186,9 @@ $(OBJDIR)/wren_core.o: ../../src/vm/wren_core.c $(OBJDIR)/wren_debug.o: ../../src/vm/wren_debug.c @echo $(notdir $<) $(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<" +$(OBJDIR)/wren_debugger.o: ../../src/vm/wren_debugger.c + @echo $(notdir $<) + $(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<" $(OBJDIR)/wren_primitive.o: ../../src/vm/wren_primitive.c @echo $(notdir $<) $(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<" diff --git a/projects/vs2017/wren.vcxproj b/projects/vs2017/wren.vcxproj index cdcad5aa..68bdfbf2 100644 --- a/projects/vs2017/wren.vcxproj +++ b/projects/vs2017/wren.vcxproj @@ -55,7 +55,7 @@ true Win32Proj wren - 10.0.18362.0 + 10.0.19041.0 @@ -256,6 +256,9 @@ + + + @@ -268,6 +271,7 @@ + diff --git a/projects/vs2017/wren.vcxproj.filters b/projects/vs2017/wren.vcxproj.filters index 16730bd8..06ab71d3 100644 --- a/projects/vs2017/wren.vcxproj.filters +++ b/projects/vs2017/wren.vcxproj.filters @@ -33,6 +33,15 @@ vm + + vm + + + vm + + + vm + vm @@ -65,6 +74,9 @@ vm + + vm + vm diff --git a/projects/vs2017/wren_shared.vcxproj b/projects/vs2017/wren_shared.vcxproj index 38d4f8f5..fe1aa2ed 100644 --- a/projects/vs2017/wren_shared.vcxproj +++ b/projects/vs2017/wren_shared.vcxproj @@ -55,7 +55,7 @@ true Win32Proj wren_shared - 10.0.18362.0 + 10.0.19041.0 @@ -268,6 +268,9 @@ + + + @@ -280,6 +283,7 @@ + diff --git a/projects/vs2017/wren_shared.vcxproj.filters b/projects/vs2017/wren_shared.vcxproj.filters index 16730bd8..06ab71d3 100644 --- a/projects/vs2017/wren_shared.vcxproj.filters +++ b/projects/vs2017/wren_shared.vcxproj.filters @@ -33,6 +33,15 @@ vm + + vm + + + vm + + + vm + vm @@ -65,6 +74,9 @@ vm + + vm + vm diff --git a/projects/vs2017/wren_test.vcxproj b/projects/vs2017/wren_test.vcxproj index 59ed29a1..8da6cb87 100644 --- a/projects/vs2017/wren_test.vcxproj +++ b/projects/vs2017/wren_test.vcxproj @@ -55,7 +55,7 @@ true Win32Proj wren_test - 10.0.18362.0 + 10.0.19041.0 diff --git a/projects/vs2019/wren.vcxproj b/projects/vs2019/wren.vcxproj index fdc79da8..00a7fb76 100644 --- a/projects/vs2019/wren.vcxproj +++ b/projects/vs2019/wren.vcxproj @@ -256,6 +256,9 @@ + + + @@ -268,6 +271,7 @@ + diff --git a/projects/vs2019/wren.vcxproj.filters b/projects/vs2019/wren.vcxproj.filters index 16730bd8..06ab71d3 100644 --- a/projects/vs2019/wren.vcxproj.filters +++ b/projects/vs2019/wren.vcxproj.filters @@ -33,6 +33,15 @@ vm + + vm + + + vm + + + vm + vm @@ -65,6 +74,9 @@ vm + + vm + vm diff --git a/projects/vs2019/wren_shared.vcxproj b/projects/vs2019/wren_shared.vcxproj index c87d5e32..6fb9cd13 100644 --- a/projects/vs2019/wren_shared.vcxproj +++ b/projects/vs2019/wren_shared.vcxproj @@ -268,6 +268,9 @@ + + + @@ -280,6 +283,7 @@ + diff --git a/projects/vs2019/wren_shared.vcxproj.filters b/projects/vs2019/wren_shared.vcxproj.filters index 16730bd8..06ab71d3 100644 --- a/projects/vs2019/wren_shared.vcxproj.filters +++ b/projects/vs2019/wren_shared.vcxproj.filters @@ -33,6 +33,15 @@ vm + + vm + + + vm + + + vm + vm @@ -65,6 +74,9 @@ vm + + vm + vm diff --git a/projects/xcode/wren.xcodeproj/project.pbxproj b/projects/xcode/wren.xcodeproj/project.pbxproj index e3d44133..af1697e8 100644 --- a/projects/xcode/wren.xcodeproj/project.pbxproj +++ b/projects/xcode/wren.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 032ED59302D1B8C53D266BD3 /* wren_opt_random.c in Sources */ = {isa = PBXBuildFile; fileRef = 737815DBCADD88CD7CEBA41B /* wren_opt_random.c */; }; 20773D38B5EDFC6A248A5378 /* wren_debug.c in Sources */ = {isa = PBXBuildFile; fileRef = 39C26800695436F244617640 /* wren_debug.c */; }; + 2607F7143831D3C66C22BD54 /* wren_debugger.c in Sources */ = {isa = PBXBuildFile; fileRef = D1E9031CCBAABF8E05A9C15C /* wren_debugger.c */; }; 76585F8088823C32BC7325C0 /* wren_compiler.c in Sources */ = {isa = PBXBuildFile; fileRef = C5FFB2C8BFC16F3AF9C07108 /* wren_compiler.c */; }; 76CC0A9CBADFDBCEAEB160DC /* wren_primitive.c in Sources */ = {isa = PBXBuildFile; fileRef = 6F616124E0840216964AAF64 /* wren_primitive.c */; }; 7FA807D04313C98281B76E10 /* wren_vm.c in Sources */ = {isa = PBXBuildFile; fileRef = B65A1C18AFC01D8AA35B7A58 /* wren_vm.c */; }; @@ -19,8 +20,10 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 144549CEDAD3414048D5680E /* wren_debugger_types.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = wren_debugger_types.h; path = ../../src/vm/wren_debugger_types.h; sourceTree = ""; }; 14FE11AE703F65204399AFEE /* wren_common.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = wren_common.h; path = ../../src/vm/wren_common.h; sourceTree = ""; }; 27B7A19221795E045B785FD2 /* wren_compiler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = wren_compiler.h; path = ../../src/vm/wren_compiler.h; sourceTree = ""; }; + 33A0F1E62D62AE586761B026 /* wren_debugger.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = wren_debugger.h; path = ../../src/vm/wren_debugger.h; sourceTree = ""; }; 39C26800695436F244617640 /* wren_debug.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = wren_debug.c; path = ../../src/vm/wren_debug.c; sourceTree = ""; }; 44AC34EA743E03DC4F4B432A /* wren_debug.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = wren_debug.h; path = ../../src/vm/wren_debug.h; sourceTree = ""; }; 49DA870B4D4B593DCEB7FD4B /* libwren.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; name = libwren.a; path = libwren.a; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -29,6 +32,7 @@ 6F616124E0840216964AAF64 /* wren_primitive.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = wren_primitive.c; path = ../../src/vm/wren_primitive.c; sourceTree = ""; }; 737815DBCADD88CD7CEBA41B /* wren_opt_random.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = wren_opt_random.c; path = ../../src/optional/wren_opt_random.c; sourceTree = ""; }; 7BB4B776AC98AF68BFB0E5B6 /* wren_opcodes.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = wren_opcodes.h; path = ../../src/vm/wren_opcodes.h; sourceTree = ""; }; + 80BCD9B0D74A4422AFC357F0 /* wren_math.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = wren_math.h; path = ../../src/vm/wren_math.h; sourceTree = ""; }; 8BA7E8EEE2355360BAAE672E /* wren_core.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = wren_core.h; path = ../../src/vm/wren_core.h; sourceTree = ""; }; A24154A4F8CEBF16D147D2E4 /* wren_core.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = wren_core.c; path = ../../src/vm/wren_core.c; sourceTree = ""; }; A5CED894D560A786B06DE6D4 /* wren_utils.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = wren_utils.c; path = ../../src/vm/wren_utils.c; sourceTree = ""; }; @@ -39,6 +43,7 @@ B819F656E7ABC548C2B90496 /* wren_value.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = wren_value.h; path = ../../src/vm/wren_value.h; sourceTree = ""; }; B8CA70B14B583AA3B430DEF1 /* wren_opt_meta.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = wren_opt_meta.h; path = ../../src/optional/wren_opt_meta.h; sourceTree = ""; }; C5FFB2C8BFC16F3AF9C07108 /* wren_compiler.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = wren_compiler.c; path = ../../src/vm/wren_compiler.c; sourceTree = ""; }; + D1E9031CCBAABF8E05A9C15C /* wren_debugger.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = wren_debugger.c; path = ../../src/vm/wren_debugger.c; sourceTree = ""; }; ECE6D43F03F96F71F4156A7F /* wren.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = wren.h; path = ../../src/include/wren.h; sourceTree = ""; }; FCC7D88E6DEA798023B126CE /* wren_primitive.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = wren_primitive.h; path = ../../src/vm/wren_primitive.h; sourceTree = ""; }; /* End PBXFileReference section */ @@ -94,6 +99,10 @@ 8BA7E8EEE2355360BAAE672E /* wren_core.h */, 39C26800695436F244617640 /* wren_debug.c */, 44AC34EA743E03DC4F4B432A /* wren_debug.h */, + D1E9031CCBAABF8E05A9C15C /* wren_debugger.c */, + 33A0F1E62D62AE586761B026 /* wren_debugger.h */, + 144549CEDAD3414048D5680E /* wren_debugger_types.h */, + 80BCD9B0D74A4422AFC357F0 /* wren_math.h */, 7BB4B776AC98AF68BFB0E5B6 /* wren_opcodes.h */, 6F616124E0840216964AAF64 /* wren_primitive.c */, FCC7D88E6DEA798023B126CE /* wren_primitive.h */, @@ -172,6 +181,7 @@ 76585F8088823C32BC7325C0 /* wren_compiler.c in Sources */, B9CCD11CEBC61BCE65A5575C /* wren_core.c in Sources */, 20773D38B5EDFC6A248A5378 /* wren_debug.c in Sources */, + 2607F7143831D3C66C22BD54 /* wren_debugger.c in Sources */, 76CC0A9CBADFDBCEAEB160DC /* wren_primitive.c in Sources */, 8D5FBD0C22D67C3E9172D34C /* wren_utils.c in Sources */, E1DB0F647751CE96E5EE25A4 /* wren_value.c in Sources */, diff --git a/projects/xcode/wren_shared.xcodeproj/project.pbxproj b/projects/xcode/wren_shared.xcodeproj/project.pbxproj index 443e7e0f..6283fcaf 100644 --- a/projects/xcode/wren_shared.xcodeproj/project.pbxproj +++ b/projects/xcode/wren_shared.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 032ED59302D1B8C53D266BD3 /* wren_opt_random.c in Sources */ = {isa = PBXBuildFile; fileRef = 737815DBCADD88CD7CEBA41B /* wren_opt_random.c */; }; 20773D38B5EDFC6A248A5378 /* wren_debug.c in Sources */ = {isa = PBXBuildFile; fileRef = 39C26800695436F244617640 /* wren_debug.c */; }; + 2607F7143831D3C66C22BD54 /* wren_debugger.c in Sources */ = {isa = PBXBuildFile; fileRef = D1E9031CCBAABF8E05A9C15C /* wren_debugger.c */; }; 76585F8088823C32BC7325C0 /* wren_compiler.c in Sources */ = {isa = PBXBuildFile; fileRef = C5FFB2C8BFC16F3AF9C07108 /* wren_compiler.c */; }; 76CC0A9CBADFDBCEAEB160DC /* wren_primitive.c in Sources */ = {isa = PBXBuildFile; fileRef = 6F616124E0840216964AAF64 /* wren_primitive.c */; }; 7FA807D04313C98281B76E10 /* wren_vm.c in Sources */ = {isa = PBXBuildFile; fileRef = B65A1C18AFC01D8AA35B7A58 /* wren_vm.c */; }; @@ -19,8 +20,10 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 144549CEDAD3414048D5680E /* wren_debugger_types.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = wren_debugger_types.h; path = ../../src/vm/wren_debugger_types.h; sourceTree = ""; }; 14FE11AE703F65204399AFEE /* wren_common.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = wren_common.h; path = ../../src/vm/wren_common.h; sourceTree = ""; }; 27B7A19221795E045B785FD2 /* wren_compiler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = wren_compiler.h; path = ../../src/vm/wren_compiler.h; sourceTree = ""; }; + 33A0F1E62D62AE586761B026 /* wren_debugger.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = wren_debugger.h; path = ../../src/vm/wren_debugger.h; sourceTree = ""; }; 39C26800695436F244617640 /* wren_debug.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = wren_debug.c; path = ../../src/vm/wren_debug.c; sourceTree = ""; }; 44AC34EA743E03DC4F4B432A /* wren_debug.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = wren_debug.h; path = ../../src/vm/wren_debug.h; sourceTree = ""; }; 4BF57B22455B7C9438F6D962 /* wren_vm.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = wren_vm.h; path = ../../src/vm/wren_vm.h; sourceTree = ""; }; @@ -28,6 +31,7 @@ 6F616124E0840216964AAF64 /* wren_primitive.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = wren_primitive.c; path = ../../src/vm/wren_primitive.c; sourceTree = ""; }; 737815DBCADD88CD7CEBA41B /* wren_opt_random.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = wren_opt_random.c; path = ../../src/optional/wren_opt_random.c; sourceTree = ""; }; 7BB4B776AC98AF68BFB0E5B6 /* wren_opcodes.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = wren_opcodes.h; path = ../../src/vm/wren_opcodes.h; sourceTree = ""; }; + 80BCD9B0D74A4422AFC357F0 /* wren_math.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = wren_math.h; path = ../../src/vm/wren_math.h; sourceTree = ""; }; 89E69C5EA0F937909115329E /* libwren.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; name = libwren.dylib; path = libwren.dylib; sourceTree = BUILT_PRODUCTS_DIR; }; 8BA7E8EEE2355360BAAE672E /* wren_core.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = wren_core.h; path = ../../src/vm/wren_core.h; sourceTree = ""; }; A24154A4F8CEBF16D147D2E4 /* wren_core.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = wren_core.c; path = ../../src/vm/wren_core.c; sourceTree = ""; }; @@ -39,6 +43,7 @@ B819F656E7ABC548C2B90496 /* wren_value.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = wren_value.h; path = ../../src/vm/wren_value.h; sourceTree = ""; }; B8CA70B14B583AA3B430DEF1 /* wren_opt_meta.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = wren_opt_meta.h; path = ../../src/optional/wren_opt_meta.h; sourceTree = ""; }; C5FFB2C8BFC16F3AF9C07108 /* wren_compiler.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = wren_compiler.c; path = ../../src/vm/wren_compiler.c; sourceTree = ""; }; + D1E9031CCBAABF8E05A9C15C /* wren_debugger.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = wren_debugger.c; path = ../../src/vm/wren_debugger.c; sourceTree = ""; }; ECE6D43F03F96F71F4156A7F /* wren.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = wren.h; path = ../../src/include/wren.h; sourceTree = ""; }; FCC7D88E6DEA798023B126CE /* wren_primitive.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = wren_primitive.h; path = ../../src/vm/wren_primitive.h; sourceTree = ""; }; /* End PBXFileReference section */ @@ -83,6 +88,10 @@ 8BA7E8EEE2355360BAAE672E /* wren_core.h */, 39C26800695436F244617640 /* wren_debug.c */, 44AC34EA743E03DC4F4B432A /* wren_debug.h */, + D1E9031CCBAABF8E05A9C15C /* wren_debugger.c */, + 33A0F1E62D62AE586761B026 /* wren_debugger.h */, + 144549CEDAD3414048D5680E /* wren_debugger_types.h */, + 80BCD9B0D74A4422AFC357F0 /* wren_math.h */, 7BB4B776AC98AF68BFB0E5B6 /* wren_opcodes.h */, 6F616124E0840216964AAF64 /* wren_primitive.c */, FCC7D88E6DEA798023B126CE /* wren_primitive.h */, @@ -172,6 +181,7 @@ 76585F8088823C32BC7325C0 /* wren_compiler.c in Sources */, B9CCD11CEBC61BCE65A5575C /* wren_core.c in Sources */, 20773D38B5EDFC6A248A5378 /* wren_debug.c in Sources */, + 2607F7143831D3C66C22BD54 /* wren_debugger.c in Sources */, 76CC0A9CBADFDBCEAEB160DC /* wren_primitive.c in Sources */, 8D5FBD0C22D67C3E9172D34C /* wren_utils.c in Sources */, E1DB0F647751CE96E5EE25A4 /* wren_value.c in Sources */, diff --git a/src/include/wren.h b/src/include/wren.h index 7845911c..631899a3 100644 --- a/src/include/wren.h +++ b/src/include/wren.h @@ -5,6 +5,9 @@ #include #include +// This lives in two places atm cos including wren_common.h requires build settings changes +#define WREN_DEBUGGER 1 + // The Wren semantic version number components. #define WREN_VERSION_MAJOR 0 #define WREN_VERSION_MINOR 4 @@ -150,6 +153,12 @@ typedef struct typedef WrenForeignClassMethods (*WrenBindForeignClassFn)( WrenVM* vm, const char* module, const char* className); +#if WREN_DEBUGGER + // Returns the path to the physical file representing the module [name], + // using the optional [root] as a base path if needed. Returns null if the file is not physical. + typedef const char* (*WrenModulePathFn)(WrenVM* vm, const char* name, const char* root); +#endif + typedef struct { // The callback Wren will use to allocate, reallocate, and deallocate memory. @@ -269,6 +278,12 @@ typedef struct // User-defined data associated with the VM. void* userData; + #if WREN_DEBUGGER + WrenModulePathFn modulePathFn; + const char* debuggerPort; //:todo: char* probably bad? + bool enableDebugger; //:todo: temp + #endif + } WrenConfiguration; typedef enum diff --git a/src/vm/wren_common.h b/src/vm/wren_common.h index 4ee9e7e2..ce9253f5 100644 --- a/src/vm/wren_common.h +++ b/src/vm/wren_common.h @@ -55,6 +55,7 @@ #define WREN_OPT_RANDOM 1 #endif +#define WREN_DEBUGGER 1 // These flags are useful for debugging and hacking on Wren itself. They are not // intended to be used for production code. They default to off. diff --git a/src/vm/wren_compiler.c b/src/vm/wren_compiler.c index 92b16cac..a793c973 100644 --- a/src/vm/wren_compiler.c +++ b/src/vm/wren_compiler.c @@ -417,6 +417,10 @@ static const int stackEffects[] = { #undef OPCODE }; +#if WREN_DEBUGGER + void wrenDebuggerAddFnDebugVarInfo(Compiler* compiler, const char* name, int length, int index, bool upvalue); + void wrenDebuggerAddClassInfo(Compiler* compiler, ObjString* className, ClassInfo* classInfo); +#endif static void printError(Parser* parser, int line, const char* label, const char* format, va_list args) { @@ -1385,6 +1389,11 @@ static int addLocal(Compiler* compiler, const char* name, int length) local->length = length; local->depth = compiler->scopeDepth; local->isUpvalue = false; + + #if WREN_DEBUGGER + wrenDebuggerAddFnDebugVarInfo(compiler, name, length, compiler->numLocals, false); + #endif + return compiler->numLocals++; } @@ -1507,6 +1516,15 @@ static int discardLocals(Compiler* compiler, int depth) else { emitByte(compiler, CODE_POP); + + #if WREN_DEBUGGER + //:todo: check for not found? shouldn't happen though + int debugIdx = wrenSymbolTableFind( + &compiler->fn->debug->locals.locals, + compiler->locals[local].name, + compiler->locals[local].length); + compiler->fn->debug->locals.endLines.data[debugIdx] = compiler->parser->previous.line; + #endif } @@ -1548,7 +1566,7 @@ static int resolveLocal(Compiler* compiler, const char* name, int length) // Adds an upvalue to [compiler]'s function with the given properties. Does not // add one if an upvalue for that variable is already in the list. Returns the // index of the upvalue. -static int addUpvalue(Compiler* compiler, bool isLocal, int index) +static int addUpvalue(Compiler* compiler, bool isLocal, int index, const char* name, int length) { // Look for an existing one. for (int i = 0; i < compiler->fn->numUpvalues; i++) @@ -1560,6 +1578,17 @@ static int addUpvalue(Compiler* compiler, bool isLocal, int index) // If we got here, it's a new upvalue. compiler->upvalues[compiler->fn->numUpvalues].isLocal = isLocal; compiler->upvalues[compiler->fn->numUpvalues].index = index; + + #if WREN_DEBUGGER + //:todo: correct line range for upvalues, this'll be triggered on first use of the variable, + // but really upvalues are available the entire range of the function + wrenDebuggerAddFnDebugVarInfo(compiler, name, length, compiler->fn->numUpvalues, true); + + //add variable name & index of upvalue to function debug info + //Add var type info (definitely needs local vs upvalue, is module needed? + // could just look for name in debug, if not existent, look in module, if not there fail) + #endif + return compiler->fn->numUpvalues++; } @@ -1591,7 +1620,7 @@ static int findUpvalue(Compiler* compiler, const char* name, int length) // scope. compiler->parent->locals[local].isUpvalue = true; - return addUpvalue(compiler, true, local); + return addUpvalue(compiler, true, local, name, length); } // See if it's an upvalue in the immediately enclosing function. In other @@ -1603,7 +1632,7 @@ static int findUpvalue(Compiler* compiler, const char* name, int length) int upvalue = findUpvalue(compiler->parent, name, length); if (upvalue != -1) { - return addUpvalue(compiler, false, upvalue); + return addUpvalue(compiler, false, upvalue, name, length); } // If we got here, we walked all the way up the parent chain and couldn't @@ -1695,6 +1724,15 @@ static ObjFn* endCompiler(Compiler* compiler, emitByte(compiler->parent, compiler->upvalues[i].index); } } + + #if WREN_DEBUGGER + //End any debug var line ranges that weren't ended by a scope inside the function + for(int i=0; ifn->debug->locals.localIndexes.count; i++) { + if(compiler->fn->debug->locals.endLines.data[i] == -1) { + compiler->fn->debug->locals.endLines.data[i] = compiler->parser->previous.line; + } + } + #endif // Pop this compiler off the stack. compiler->parser->vm->compiler = compiler->parent; @@ -3617,6 +3655,10 @@ static void classDefinition(Compiler* compiler, bool isForeign) compiler->fn->code.data[numFieldsInstruction] = (uint8_t)classInfo.fields.count; } + + #if WREN_DEBUGGER + wrenDebuggerAddClassInfo(compiler, className, &classInfo); + #endif // Clear symbol tables for tracking field and method names. wrenSymbolTableClear(compiler->parser->vm, &classInfo.fields); @@ -4132,3 +4174,54 @@ static void copyMethodAttributes(Compiler* compiler, bool isForeign, wrenPopRoot(vm); } + + +#if WREN_DEBUGGER + +void wrenDebuggerAddFnDebugVarInfo(Compiler* compiler, const char* name, int length, int index, bool upvalue) { + //write name + wrenSymbolTableAdd(compiler->parser->vm, &compiler->fn->debug->locals.locals, name, length); + //write local index + wrenIntBufferWrite(compiler->parser->vm, &compiler->fn->debug->locals.localIndexes, index); + wrenIntBufferWrite(compiler->parser->vm, &compiler->fn->debug->locals.isUpvalue, upvalue ? 1 : 0); + //:todo: upvalues are valid the entire span of the function + wrenIntBufferWrite(compiler->parser->vm, &compiler->fn->debug->locals.startLines, compiler->parser->previous.line); + wrenIntBufferWrite(compiler->parser->vm, &compiler->fn->debug->locals.endLines, -1); +} + +void wrenDebuggerAddClassInfo(Compiler* compiler, ObjString* className, ClassInfo* classInfo) { + + ObjModule* module = compiler->parser->module; + + // Make an entry for this class' field indices + wrenSymbolTableAdd(compiler->parser->vm, &module->classDebug.classIndices, className->value, className->length); + + SymbolTableBuffer* fieldIndices = &module->classDebug.fieldIndices; + IntBufferBuffer* fieldSlots = &module->classDebug.fieldSlots; + + SymbolTable placeholderSymbolTable; + IntBuffer placeholderIntTable; + wrenSymbolTableInit(&placeholderSymbolTable); + wrenIntBufferInit(&placeholderIntTable); + + wrenSymbolTableBufferWrite(compiler->parser->vm, fieldIndices, placeholderSymbolTable); + wrenIntBufferBufferWrite(compiler->parser->vm, fieldSlots, placeholderIntTable); + + wrenSymbolTableInit(&fieldIndices->data[fieldIndices->count - 1]); + wrenIntBufferInit(&fieldSlots->data[fieldSlots->count - 1]); + + // Add the field names and slots into their respective buffers + for(int i=0; ifields.count; i++) { + wrenSymbolTableAdd(compiler->parser->vm, + &fieldIndices->data[fieldIndices->count - 1], + classInfo->fields.data[i]->value, + classInfo->fields.data[i]->length); + // field slots are sequential starting from 0 at this point (they later get patched), so i is their slot + wrenIntBufferWrite(compiler->parser->vm, &fieldSlots->data[fieldSlots->count - 1], i); + } + + //:todo: statics that aren't used in a method won't be visible to the debugger in that method + // maybe solved by walking up scopes to look for variables? + +} +#endif \ No newline at end of file diff --git a/src/vm/wren_core.c b/src/vm/wren_core.c index d0a121f8..3a4aa2d1 100644 --- a/src/vm/wren_core.c +++ b/src/vm/wren_core.c @@ -1224,7 +1224,7 @@ static ObjClass* defineClass(WrenVM* vm, ObjModule* module, const char* name) ObjString* nameString = AS_STRING(wrenNewString(vm, name)); wrenPushRoot(vm, (Obj*)nameString); - ObjClass* classObj = wrenNewSingleClass(vm, 0, nameString); + ObjClass* classObj = wrenNewSingleClass(vm, 0, nameString, module); wrenDefineVariable(vm, module, name, nameString->length, OBJ_VAL(classObj), NULL); diff --git a/src/vm/wren_debugger.c b/src/vm/wren_debugger.c new file mode 100644 index 00000000..3fc4bd79 --- /dev/null +++ b/src/vm/wren_debugger.c @@ -0,0 +1,913 @@ +#include "wren_debugger.h" + +#if WREN_DEBUGGER + +#include +#include +#include + +#ifdef _WIN32 +#include +#include +#else +#include +#include +#include +#endif + +#include +#include +#include + +#include "wren_vm.h" +#include "wren_debug.h" + +#if WREN_OPT_META + #include "wren_opt_meta.h" +#endif +#if WREN_OPT_RANDOM + #include "wren_opt_random.h" +#endif + +DEFINE_BUFFER(SymbolTable, SymbolTable); +DEFINE_BUFFER(IntBuffer, IntBuffer); + +#define EVENT_BUFFER_SIZE 4096 + +static char msg[256]; +static char* msg_cursor; + +void wrenInitDebugger(WrenVM* vm) { + + vm->debugger.num_breakpoints = 0; + vm->debugger.next_id = 0; + + vm->debugger.listen_sock = -1; + vm->debugger.comm_sock = -1; + + if(vm->config.enableDebugger) { + + int status; + struct addrinfo hints; + struct addrinfo *servinfo; // will point to the results + + memset(&hints, 0, sizeof hints); // make sure the struct is empty + hints.ai_family = AF_INET; // use IPv4 + hints.ai_socktype = SOCK_STREAM; // TCP stream sockets + hints.ai_flags = AI_PASSIVE; // fill in my IP for me + + status = getaddrinfo(NULL, vm->config.debuggerPort, &hints, &servinfo); + + if (status != 0) { + #ifdef _WIN32 + fprintf(stderr, "getaddrinfo error: %s\n", gai_strerrorA(status)); + #else + fprintf(stderr, "getaddrinfo error: %s\n", gai_strerror(status)); + #endif + // exit(1); + } else { + // servinfo now points to a linked list of 1 or more struct addrinfos + vm->debugger.listen_sock = socket(servinfo->ai_family, servinfo->ai_socktype, servinfo->ai_protocol); + + #ifdef _WIN32 + unsigned long non_blocking = 1; + ioctlsocket(vm->debugger.listen_sock, FIONBIO, &non_blocking); + #else + fcntl(vm->debugger.listen_sock, F_SETFL, O_NONBLOCK); + #endif + + bind(vm->debugger.listen_sock, servinfo->ai_addr, servinfo->ai_addrlen); + + freeaddrinfo(servinfo); // free the linked-list + + listen(vm->debugger.listen_sock, 10); + } + + // printf("luxe / wren / debugger / started\n"); //:todo: these won't land in log + } //if enableDebugger + + msg_cursor = msg; + memset(msg, 0, sizeof msg); + + wrenResetDebugger(vm); +} + +void wrenFreeDebugger(WrenVM* vm) { + #ifdef _WIN32 + closesocket(vm->debugger.comm_sock); + closesocket(vm->debugger.listen_sock); + #else + close(vm->debugger.comm_sock); + close(vm->debugger.listen_sock); + #endif + + // printf("luxe / wren / debugger / shutdown\n"); //:todo: these won't land in log +} + +void wrenResetDebugger(WrenVM* vm) { + vm->debugger.state = WREN_DEBUGGER_STATE_RUNNING; + vm->debugger.last_line = -1; + vm->debugger.last_frame = -1; + + vm->debugger.last_step_line = -1; + vm->debugger.last_step_frame = -1; + + vm->debugger.target_step_out_frame = -1; + + vm->debugger.last_fiber = vm->fiber; +} + +void wrenRunDebugger(WrenVM* vm, ObjFn* fn, int i) { + if(fn->module == NULL || fn->module->name == NULL) { + return; //internal functions don't have modules assigned, for now we just don't allow stopping here + } + + int line = fn->debug->sourceLines.data[i]; + int frameIdx = vm->fiber->numFrames - 1; + const char* module = fn->module->name->value; + + DebuggerStopReason reason = debuggerShouldBreak(&vm->debugger, vm->fiber, frameIdx, module, line); + if(reason != WREN_DEBUGGER_STOP_DIDNT) { + vm->debugger.state = WREN_DEBUGGER_STATE_HALTING; + + char out[64]; + int out_len = sprintf(out, "stopped %d\n", reason); + debuggerSendEvent(vm->debugger.comm_sock, WREN_DEBUGGER_EVENT_STOPPED, out, out_len, true); + } + + if(frameIdx != vm->debugger.last_frame) { + for(int i=0; idebugger.num_breakpoints; i++) { + if(vm->debugger.breakpoints[i].stopped_in_frame > frameIdx) { //if we stepped out of a frame, reset all breakpoints in deeper frames we may have triggered + vm->debugger.breakpoints[i].stopped_in_frame = -1; + } + } + } + + //treat fiber switches as a complete callframe reset for now + if(vm->fiber != vm->debugger.last_fiber) { + for(int i=0; idebugger.num_breakpoints; i++) { + vm->debugger.breakpoints[i].stopped_in_frame = -1; + } + } + + vm->debugger.last_fiber = vm->fiber; + vm->debugger.last_frame = frameIdx; + vm->debugger.last_line = line; + + while(vm->debugger.state == WREN_DEBUGGER_STATE_HALTING) { + #ifdef _WIN32 + Sleep(100); + #else + usleep(100000); //0.1 seconds + #endif + + DebuggerCmd cmd = debuggerGetCmd(vm, i); + + switch(cmd) { + case WREN_DEBUGGER_CMD_CONTINUE: + vm->debugger.state = WREN_DEBUGGER_STATE_RUNNING; + break; + + case WREN_DEBUGGER_CMD_STEP_OVER: + vm->debugger.last_step_line = line; + vm->debugger.last_step_frame = frameIdx; + vm->debugger.state = WREN_DEBUGGER_STATE_STEPPING_OVER; + break; + + case WREN_DEBUGGER_CMD_STEP_INTO: + vm->debugger.state = WREN_DEBUGGER_STATE_STEPPING_INTO; + break; + + case WREN_DEBUGGER_CMD_STEP_OUT: + if(frameIdx == 0) break; //Ignore if there's nowhere to step out to + vm->debugger.state = WREN_DEBUGGER_STATE_STEPPING_OUT; + vm->debugger.target_step_out_frame = frameIdx - 1; + break; + + case WREN_DEBUGGER_CMD_NONE: + break; + } + } +} + + + //next: add/remove with module, update command parsing to also grab module string +int debuggerAddBreakpoint(WrenDebugger* debugger, const char* module, int line) { + for(int i=0; i < debugger->num_breakpoints; ++i) { + Breakpoint* breakpoint = &debugger->breakpoints[i]; + if(breakpoint->line == line && (strcmp(breakpoint->module, module) == 0)) return -1; + } + + if(debugger->num_breakpoints < WREN_MAX_BREAKPOINTS) { + debugger->breakpoints[debugger->num_breakpoints].line = line; + strcpy(debugger->breakpoints[debugger->num_breakpoints].module, module); + debugger->breakpoints[debugger->num_breakpoints].id = debugger->next_id; + debugger->breakpoints[debugger->num_breakpoints].stopped_in_frame = -1; + debugger->next_id++; + debugger->num_breakpoints++; + return debugger->next_id - 1; + } else { + printf("Debugger / add brekapoint / Reached the maximum number breakpoints allowed (%d)\n", WREN_MAX_BREAKPOINTS); + return -1; + } +} + +void debuggerSendStack(WrenVM* vm, const char* msg, int ip) { + + char root_path[4096]; + int status = sscanf(msg, "stack \"%4096[^\"]\"", root_path); + if(status != 1) { + + } + + ObjFiber* fiber = vm->fiber; + + int num_frames = fiber->numFrames; + + char out[6144]; //typical max path length * 1.5 should be a reasonable default + + for(int i=0; iframes[i]; + ObjFn* fn = frame->closure->fn; + + //:todo: this follows the behaviour the of wrenDebugPrintStackTrace, see later if this makes sense for step through debugging + + //skip over stub functions for calling methods from the C API + if(fn->module == NULL) continue; + + //skip over core modules that have no name + if(fn->module->name == NULL) continue; + + char* module = fn->module->name->value; + char* fnName = fn->debug->name; + // -1 because IP has advanced past the instruction that it just executed. + int line = fn->debug->sourceLines.data[frame->ip - fn->code.data - 1]; + if(i == num_frames - 1) { + line = fn->debug->sourceLines.data[ip]; //the last frame doesn't have its IP updated in the interpreter loop, so apply the one we know from being called during the loop + } + + //:todo: if modulePathFn is not specified this is blow up city + const char* path = vm->config.modulePathFn(vm, module, root_path); + const char* print_path = (path == NULL) ? "" : path; + + //:todo: module could contain '|' and mess up the pattern + int out_len = sprintf(out, "%d|%s|%s|%s|%d\n", i, module, print_path, fnName, line); + debuggerSendEvent(vm->debugger.comm_sock, WREN_DEBUGGER_EVENT_STACK, out, out_len, false); + + free((void *)path); //:todo: redo memory management to not require malloc/free + } + + debuggerSendEvent(vm->debugger.comm_sock, WREN_DEBUGGER_EVENT_STACK, NULL, 0, true); +} + +void debuggerSendSource(WrenVM* vm, const char* msg) { + char module[128]; + int status = sscanf(msg, "source \"%127[^\"]\"", module); + + const char* source = NULL; + int source_len = 0; + + if(status == 1) { + + // Let the host try to provide the module. + if (vm->config.loadModuleFn != NULL) + { + //:todo: Update comments in config, this causes the load fn to be called more than once + WrenLoadModuleResult res = vm->config.loadModuleFn(vm, module); + source = res.source; + } + + // If the host didn't provide it, see if it's a built in optional module. + if (source == NULL) + { + #if WREN_OPT_META + if (strncmp(module, "meta", 4) == 0) source = wrenMetaSource(); + #endif + #if WREN_OPT_RANDOM + if (strncmp(module, "random", 6) == 0) source = wrenRandomSource(); + #endif + } + + if(source != NULL) source_len = strlen(source); + } + + //for source_len == 0 this will just send an event message with empty payload, to ensure there is always some response to the source request + debuggerSendEvent(vm->debugger.comm_sock, WREN_DEBUGGER_EVENT_SOURCE, source, source_len, true); +} + +void debuggerDumpObject(Obj* obj) { + switch (obj->type) + { + case OBJ_CLASS: + printf("Class %s", ((ObjClass*)obj)->name->value); + break; + case OBJ_CLOSURE: + printf("Function %s", ((ObjClosure*)obj)->fn->debug->name); + break; + case OBJ_FIBER: printf("Fiber %p", obj); break; + case OBJ_FN: printf("[fn %p]", obj); break; + case OBJ_FOREIGN: printf("[foreign %p]", obj); break; + case OBJ_INSTANCE: + printf("Instance of class %s", obj->classObj->name->value); + break; + case OBJ_LIST: printf("[list %p]", obj); break; + case OBJ_MAP: printf("[map %p]", obj); break; + case OBJ_MODULE: printf("[module %p]", obj); break; + case OBJ_RANGE: printf("[range %p]", obj); break; + case OBJ_STRING: printf("%s", ((ObjString*)obj)->value); break; + case OBJ_UPVALUE: printf("[upvalue %p]", obj); break; + default: printf("[unknown object %d]", obj->type); break; + } +} + +void debuggerDumpValue(Value value) { +#if WREN_NAN_TAGGING + if (IS_NUM(value)) + { + printf("%.14g", AS_NUM(value)); + } + else if (IS_OBJ(value)) + { + debuggerDumpObject(AS_OBJ(value)); + } + else + { + switch (GET_TAG(value)) + { + case TAG_FALSE: printf("false"); break; + case TAG_NAN: printf("NaN"); break; + case TAG_NULL: printf("null"); break; + case TAG_TRUE: printf("true"); break; + case TAG_UNDEFINED: UNREACHABLE(); + } + } +#else + switch (value.type) + { + case VAL_FALSE: printf("false"); break; + case VAL_NULL: printf("null"); break; + case VAL_NUM: printf("%.14g", AS_NUM(value)); break; + case VAL_TRUE: printf("true"); break; + case VAL_OBJ: debuggerDumpObject(AS_OBJ(value)); break; + case VAL_UNDEFINED: UNREACHABLE(); + } +#endif +} + +void debuggerSendVar(WrenVM* vm, const char* msg) { + char var[MAX_VARIABLE_NAME]; + //:todo: 64 in format string should be MAX_VARIABLE_NAME, but it requires some additional macros + int status = sscanf(msg, "var \"%64[^\"]\"", var); + + if(status != 1) { + return; + } + + int var_name_len = strlen(var); + if(var_name_len == 0) { + printf("Variable name can't be empty\n"); + return; + } + + Value val; + bool found = false; + + CallFrame* frame = &vm->fiber->frames[vm->fiber->numFrames - 1]; + FnDebug* debug = frame->closure->fn->debug; + + int local = wrenSymbolTableFind(&debug->locals.locals, var, var_name_len); + if(local != -1) { // local or upvalue + + int startLine = debug->locals.startLines.data[local]; + int endLine = debug->locals.endLines.data[local]; + + if(startLine < vm->debugger.last_line && vm->debugger.last_line <= endLine) { + int slot = debug->locals.localIndexes.data[local]; + int isUpvalue = debug->locals.isUpvalue.data[local]; + + if(isUpvalue) { + ObjUpvalue** upvalues = frame->closure->upvalues; + val = *upvalues[slot]->value; + found = true; + } else { + val = frame->stackStart[slot]; + found = true; + } + } + } else { // field or module + // It's a field if it starts with exactly one _ + if(var[0] == '_' && (var_name_len == 1 || var[1] != '_')) { + Value receiver = frame->stackStart[0]; + + if(IS_INSTANCE(receiver)) { + ObjInstance* instance = AS_INSTANCE(receiver); + ObjClass* classObj = instance->obj.classObj; + ObjModule* module = classObj->module; + + int moduleClassIdx = wrenSymbolTableFind(&module->classDebug.classIndices, classObj->name->value, classObj->name->length); + + if(moduleClassIdx != -1) { + SymbolTable* fields = &module->classDebug.fieldIndices.data[moduleClassIdx]; + IntBuffer* slots = &module->classDebug.fieldSlots.data[moduleClassIdx]; + + int fieldIdx = wrenSymbolTableFind(fields, var, var_name_len); + if(fieldIdx != -1) { + int slot = slots->data[fieldIdx]; + val = instance->fields[slot]; + found = true; + } + } + } + } else { // module variable + ObjModule* module = frame->closure->fn->module; + int slot = wrenSymbolTableFind(&module->variableNames, var, var_name_len); + if(slot != -1) { + val = module->variables.data[slot]; + found = true; + } + } + } + + if(found) { + debuggerDumpValue(val); + printf("\n"); + } else { + printf("Variable is not defined here.\n"); + } +} + +int sprintObj(char* buffer, Obj* obj) { + switch (obj->type) + { + case OBJ_CLASS: + return sprintf(buffer, "Class %s", ((ObjClass*)obj)->name->value); + case OBJ_CLOSURE: + return sprintf(buffer, "Function %s", ((ObjClosure*)obj)->fn->debug->name); + case OBJ_FIBER: return sprintf(buffer, "Fiber %p", obj); + case OBJ_FN: return sprintf(buffer, "[fn %p]", obj); + case OBJ_FOREIGN: return sprintf(buffer, "[foreign %p]", obj); + case OBJ_INSTANCE: + return sprintf(buffer, "Instance of class %s", obj->classObj->name->value); + case OBJ_LIST: return sprintf(buffer, "[list %p]", obj); + case OBJ_MAP: return sprintf(buffer, "[map %p]", obj); + case OBJ_MODULE: return sprintf(buffer, "[module %p]", obj); + case OBJ_RANGE: return sprintf(buffer, "[range %p]", obj); + case OBJ_STRING: return sprintf(buffer, "%s", ((ObjString*)obj)->value); + case OBJ_UPVALUE: return sprintf(buffer, "[upvalue %p]", obj); + default: return sprintf(buffer, "[unknown object %d]", obj->type); + } +} + +const char * const objTypeNames[] = { + "Class", + "Closure", + "Fiber", + "Fn", + "Foreign", + "Instance", + "List", + "Map", + "Module", + "Range", + "String", + "Upvalue" +}; + +const char* valueTypeName(const Value value) { + #if WREN_NAN_TAGGING + if(IS_NUM(value)) return "Num"; + if(IS_OBJ(value)) { + return objTypeNames[AS_OBJ(value)->type]; + } + //not num or obj, so a singleton as specified by tags + switch(GET_TAG(value)) { + case TAG_NAN: return "NaN"; //:todo: Should this count as Num? + case TAG_NULL: return "Null"; + case TAG_FALSE: //intentional fallthrough + case TAG_TRUE: return "Bool"; + case TAG_UNDEFINED: UNREACHABLE(); + } + #else + switch (value.type) + { + case VAL_FALSE: //intentional fallthrough + case VAL_TRUE: return "Bool"; + case VAL_NULL: return "Null"; + case VAL_NUM: return "Num"; + + case VAL_OBJ: return objTypeNames[AS_OBJ(value)->type]; + case VAL_UNDEFINED: UNREACHABLE(); + } + #endif + + return NULL; //shouldn't happen due to unreachables above +} + + //:todo: find a better solution for this. Static here since allocating this + //in an if statement and pointing to it will free the buffer when exiting the if + //so permanent storage is needed +static char printBuffer[310];//max value of num is ~1.8 x 10^308 (which is the max int in a 64bit double) + // so 310 chars should be enough to hold the maximum length printed value with \0 and a char extra to be safe +void internalSendVar(WrenVM* vm, const ObjString* name, const Value val) { + char* valuePrinted; + int printedLen = 0; + + #if WREN_NAN_TAGGING + if (IS_OBJ(val) && AS_OBJ(val)->type == OBJ_STRING) { + valuePrinted = AS_STRING(val)->value; + printedLen = AS_STRING(val)->length; + } else { + valuePrinted = printBuffer; + if (IS_NUM(val)) { + printedLen = sprintf(valuePrinted, "%.14g", AS_NUM(val)); + } else if (IS_OBJ(val)) { + printedLen = sprintObj(valuePrinted, AS_OBJ(val)); + } else { + switch (GET_TAG(val)) + { + case TAG_FALSE: printedLen = sprintf(valuePrinted, "false"); break; + case TAG_NAN: printedLen = sprintf(valuePrinted, "NaN"); break; + case TAG_NULL: printedLen = sprintf(valuePrinted, "null"); break; + case TAG_TRUE: printedLen = sprintf(valuePrinted, "true"); break; + case TAG_UNDEFINED: UNREACHABLE(); + } + } + } + #else + if(val.type == VAL_OBJ && AS_OBJ(val)->type == OBJ_STRING) { + valuePrinted = AS_STRING(val)->value; + printedLen = AS_STRING(val)->length; + } else { + valuePrinted = printBuffer; + + switch (val.type) + { + case VAL_FALSE: printedLen = sprintf(valuePrinted, "false"); break; + case VAL_NULL: printedLen = sprintf(valuePrinted, "null"); break; + case VAL_NUM: printedLen = sprintf(valuePrinted, "%.14g", AS_NUM(value)); break; + case VAL_TRUE: printedLen = sprintf(valuePrinted, "true"); break; + case VAL_OBJ: printedLen = sprintObj(valuePrinted, AS_OBJ(value)); break; + case VAL_UNDEFINED: UNREACHABLE(); + } + } + #endif + + //send the pattern of "varname|printedLen|printedValue" where printedLen gives the length of printedValue in bytes, to avoid having to escape strings + + debuggerSendEvent(vm->debugger.comm_sock, WREN_DEBUGGER_EVENT_VARS, name->value, name->length, false); + debuggerSendEvent(vm->debugger.comm_sock, WREN_DEBUGGER_EVENT_VARS, "|", 1, false); + const char* typeName = valueTypeName(val); + int typeNameLen = strlen(typeName); + debuggerSendEvent(vm->debugger.comm_sock, WREN_DEBUGGER_EVENT_VARS, typeName, typeNameLen, false); + debuggerSendEvent(vm->debugger.comm_sock, WREN_DEBUGGER_EVENT_VARS, "|", 1, false); + char lenBuffer[11]; //Positive 32bit int can bet at most 10 digits printed, + \0 + int lenBufferLen = sprintf(lenBuffer, "%d", printedLen); //:todo: oh god better naming please + + debuggerSendEvent(vm->debugger.comm_sock, WREN_DEBUGGER_EVENT_VARS, lenBuffer, lenBufferLen, false); + debuggerSendEvent(vm->debugger.comm_sock, WREN_DEBUGGER_EVENT_VARS, "|", 1, false); + + debuggerSendEvent(vm->debugger.comm_sock, WREN_DEBUGGER_EVENT_VARS, valuePrinted, printedLen, false); + debuggerSendEvent(vm->debugger.comm_sock, WREN_DEBUGGER_EVENT_VARS, "\n", 1, false); +} + + //:todo: naming + //gets the stack frame corresponding to the index into the stackframe we'd print out + //Crucially, printed stackframes skip frames from internal code (core module, foreign functions), + //so we have to skip these when counting +CallFrame* getStackIdxFrame(WrenVM* vm, int stack_idx) { + ObjFiber* fiber = vm->fiber; + + int running_idx = 0; + for(int i=0; inumFrames; i++) { + CallFrame* frame = &fiber->frames[i]; + ObjFn* fn = frame->closure->fn; + + //:todo: this follows the behaviour the of wrenDebugPrintStackTrace, see later if this makes sense for step through debugging + + //skip over stub functions for calling methods from the C API + if(fn->module == NULL) continue; + + //skip over core modules that have no name + if(fn->module->name == NULL) continue; + + if(running_idx == stack_idx) { + return frame; + } + + running_idx++; + } + + return NULL; +} + +void debuggerSendModuleInfo(WrenVM* vm, int stack_idx) { + CallFrame* frame = getStackIdxFrame(vm, stack_idx); + if(!frame) return; + ObjModule* module = frame->closure->fn->module; + + for(int i=0; ivariableNames.count; i++) { + ObjString* name = module->variableNames.data[i]; + Value val = module->variables.data[i]; + + internalSendVar(vm, name, val); + } + + debuggerSendEvent(vm->debugger.comm_sock, WREN_DEBUGGER_EVENT_VARS, NULL, 0, true); +} + +void debuggerSendFunctionInfo(WrenVM* vm, int stack_idx) { + CallFrame* frame = getStackIdxFrame(vm, stack_idx); + if(!frame) return; + FnDebug* debug = frame->closure->fn->debug; + + for(int i=0; ilocals.localIndexes.count; i++) { + int startLine = debug->locals.startLines.data[i]; + int endLine = debug->locals.endLines.data[i]; + + if(startLine < vm->debugger.last_line && vm->debugger.last_line <= endLine) { + int slot = debug->locals.localIndexes.data[i]; + int isUpvalue = debug->locals.isUpvalue.data[i]; + + Value val; + + if(isUpvalue) { + ObjUpvalue** upvalues = frame->closure->upvalues; + val = *upvalues[slot]->value; + } else { + val = frame->stackStart[slot]; + } + + ObjString* name = debug->locals.locals.data[i]; + internalSendVar(vm, name, val); + } + } + + Value receiver = frame->stackStart[0]; + + if(IS_INSTANCE(receiver)) { + ObjInstance* instance = AS_INSTANCE(receiver); + ObjClass* classObj = instance->obj.classObj; + ObjModule* module = classObj->module; + + int moduleClassIdx = wrenSymbolTableFind(&module->classDebug.classIndices, classObj->name->value, classObj->name->length); + + if(moduleClassIdx != -1) { + SymbolTable* fields = &module->classDebug.fieldIndices.data[moduleClassIdx]; + IntBuffer* slots = &module->classDebug.fieldSlots.data[moduleClassIdx]; + + for(int i=0; icount; i++) { + ObjString* name = fields->data[i]; + int slot = slots->data[i]; + Value val = instance->fields[slot]; + + internalSendVar(vm, name, val); + } + } + } + + debuggerSendEvent(vm->debugger.comm_sock, WREN_DEBUGGER_EVENT_VARS, NULL, 0, true); +} + +static char event_buffer[EVENT_BUFFER_SIZE]; +static int event_cursor = 0; + + //copies as much data as possible to the buffer, and then dispatches an event message if the buffer is full or finish is true + //returns the number of characters consumed from data +static int internalSendEvent(int socket, DebuggerEventType type, const char* data, int length, bool finish) { + int copy_len = 0; + if(length != 0) { + copy_len = length < (EVENT_BUFFER_SIZE - event_cursor) ? length : (EVENT_BUFFER_SIZE - event_cursor); + memcpy(event_buffer + event_cursor, data, copy_len); + + event_cursor += copy_len; + } + + if(event_cursor == EVENT_BUFFER_SIZE || finish) { + char out[64]; + int out_len = sprintf(out, "event: %d\n", type); + send(socket, out, out_len, 0); + + out_len = sprintf(out, "length: %d\n", event_cursor); + send(socket, out, out_len, 0); + + out_len = sprintf(out, "final: %d\n", (event_cursor == EVENT_BUFFER_SIZE) ? 0 : 1); + send(socket, out, out_len, 0); + + send(socket, event_buffer, event_cursor, 0); + + event_cursor = 0; + } + + return copy_len; +} + + //build up an event of the given type, with length characters from data copied to the payload of the event + //multiple calls in sequence will append additional data to the payload + //if the payload reaches EVENT_BUFFER_SIZE, an event message will be sent, and the remaining data will be copied to the next message's payload + //setting finish to true ensures that any remaining data is sent, or an empty message if no data was specified since the last call with finish == true +void debuggerSendEvent(int socket, DebuggerEventType type, const char* data, int length, bool finish) { + int data_offset = 0; + //do-while to ensure at least one call even if length is 0. This enables "finalizing" a send without any additional data + do { + int consumed = internalSendEvent(socket, type, data + data_offset, length - data_offset, finish); + data_offset += consumed; + } while(data_offset < length); +} + +bool debuggerRemoveBreakpoint(WrenDebugger* debugger, const char* module, int line) { + for (int i = 0; i < debugger->num_breakpoints; ++i) { + Breakpoint* breakpoint = &debugger->breakpoints[i]; + if(breakpoint->line == line && (strcmp(breakpoint->module, module) == 0)) { + breakpoint->line = debugger->breakpoints[debugger->num_breakpoints - 1].line; + strcpy(breakpoint->module, debugger->breakpoints[debugger->num_breakpoints - 1].module); + debugger->num_breakpoints--; + return true; + } + } + + return false; +} + +DebuggerStopReason debuggerShouldBreak(WrenDebugger* debugger, const ObjFiber* fiber, int frame, const char* module, int line) { + + //fiber switching breaks assumptions on stepping over/out, so for now all switches cause a break + //For stepping into, we want to break upon switch anyway, so we can just always break while not running + if(debugger->state != WREN_DEBUGGER_STATE_RUNNING) { + if(fiber != debugger->last_fiber) { + //:todo: clear stepping values maybe? The frame idx was for the old fiber, it's not valid anymore + return WREN_DEBUGGER_STOP_SWITCH; + } + } + + bool step_line_diff = line != debugger->last_step_line; + bool step_frame_diff = frame != debugger->last_step_frame; + + if(debugger->state == WREN_DEBUGGER_STATE_STEPPING_OVER) { + if(step_line_diff && !step_frame_diff) { + return WREN_DEBUGGER_STOP_STEP; + } else if(!step_line_diff && !step_frame_diff) { + //definitely step over the line we started from + //the line might've had a breakpoint on it, so we don't want the later code to make us break there again + return WREN_DEBUGGER_STOP_DIDNT; + } + } + + bool line_diff = line != debugger->last_line; + bool frame_diff = frame != debugger->last_frame; + + if(debugger->state == WREN_DEBUGGER_STATE_STEPPING_INTO) { + if(line_diff || frame_diff) { + return WREN_DEBUGGER_STOP_STEP; + } + } + + if(debugger->state == WREN_DEBUGGER_STATE_STEPPING_OUT) { + if(frame == debugger->target_step_out_frame) return WREN_DEBUGGER_STOP_STEP; + } + + if(line_diff || frame_diff) { + for(int i=0; inum_breakpoints; i++) { + if(debugger->breakpoints[i].stopped_in_frame < frame) { //we don't want to re-hit a breakpoint if we already triggered it at the same callframe level + if(debugger->breakpoints[i].line == line && (strcmp(debugger->breakpoints[i].module, module) == 0)) { + debugger->breakpoints[i].stopped_in_frame = frame; + return WREN_DEBUGGER_STOP_BREAKPOINT; + } + } + } + } + + return WREN_DEBUGGER_STOP_DIDNT; +} + +bool debuggerPollInput(WrenVM* vm) { + if(vm->debugger.comm_sock == -1) { + vm->debugger.comm_sock = accept(vm->debugger.listen_sock, (struct sockaddr*) NULL, NULL); + if(vm->debugger.comm_sock > 0) { + printf("luxe / wren / debugger / connection opened\n"); + #ifdef _WIN32 + unsigned long non_blocking = 1; + ioctlsocket(vm->debugger.comm_sock, FIONBIO, &non_blocking); + #else + fcntl(vm->debugger.comm_sock, F_SETFL, O_NONBLOCK); + #endif + } + } else { + //if we're not at the end of the string, advance to the next line + if(*msg_cursor != '\0') { + while(*msg_cursor != '\n') { + msg_cursor++; + } + msg_cursor++; //step past the \n + } + + //if it wasn't the last line, we got more input to process + if(*msg_cursor != '\0') { + return true; + } + + //otherwise reset the cursor and read new data + msg_cursor = msg; + memset(msg, 0, sizeof msg); + int result = recv(vm->debugger.comm_sock, msg, 256, 0); + + + if(result == -1) { + if(errno == EWOULDBLOCK || errno == EAGAIN) return false; + } else if(result > 0) { + return true; + } else if(result == 0) { + #ifdef _WIN32 + closesocket(vm->debugger.comm_sock); + #else + close(vm->debugger.comm_sock); + #endif + vm->debugger.comm_sock = -1; + printf("luxe / wren / debugger / connection closed\n"); + } + } + return false; +} + +void wrenDebuggerPollConfigCmds(WrenVM* vm) { + bool got_msg = debuggerPollInput(vm); + if(got_msg) { + // printf("Got input config: \"%s\"\n", msg_cursor); + debuggerProcessConfigCmds(vm, msg_cursor); + } +} + +DebuggerCmd debuggerGetCmd(WrenVM* vm, int ip) { + bool got_msg = debuggerPollInput(vm); + + if(got_msg) { + // printf("Got input cmd: \"%s\"\n", msg_cursor); + bool processed = debuggerProcessConfigCmds(vm, msg_cursor); + if(!processed) { + return debuggerProcessStoppedCmds(vm, msg_cursor, ip); + } + } + return WREN_DEBUGGER_CMD_NONE; +} + +bool debuggerProcessConfigCmds(WrenVM* vm, const char* msg) { + int line = -1; + int status = -1; + char module[MAX_NAME_LEN]; + status = sscanf(msg, "addp %s %d", module, &line); + if(status == 2) { + int added_id = debuggerAddBreakpoint(&vm->debugger, module, line); + + char out[64]; + int out_len = sprintf(out, "created %d\n", added_id); + debuggerSendEvent(vm->debugger.comm_sock, WREN_DEBUGGER_EVENT_CREATED_BREAKPOINT, out, out_len, true); + + return true; + } + + status = sscanf(msg, "delp %s %d", module, &line); + if(status == 2) { + debuggerRemoveBreakpoint(&vm->debugger, module, line); + return true; + } + + if(strncmp(msg, "source", 6) == 0) { + debuggerSendSource(vm, msg); + return true; + } + + + + return false; +} + +DebuggerCmd debuggerProcessStoppedCmds(WrenVM* vm, const char* msg, int ip) { + if(strncmp(msg, "stack", 5) == 0) { + debuggerSendStack(vm, msg, ip); + } + + if(strncmp(msg, "var", 3) == 0) { + debuggerSendVar(vm, msg); + } + + if(strncmp(msg, "info ", 5) == 0) { + char info_section[9]; //"function\0" at most + int stack_idx = -1; + int matched = sscanf(msg + 5, "%9s %d", info_section, &stack_idx); + if(matched == 2) { + if(strncmp(info_section, "module", 6) == 0) { + debuggerSendModuleInfo(vm, stack_idx); + } else if(strncmp(info_section, "function", 8) == 0) { + debuggerSendFunctionInfo(vm, stack_idx); + } + } + } + + //:todo: this only checks for prefix, not the whole command. Trim and strcmp instead + if(strncmp(msg, "cont", 4) == 0) return WREN_DEBUGGER_CMD_CONTINUE; + if(strncmp(msg, "over", 4) == 0) return WREN_DEBUGGER_CMD_STEP_OVER; + if(strncmp(msg, "into", 4) == 0) return WREN_DEBUGGER_CMD_STEP_INTO; + if(strncmp(msg, "out", 3) == 0) return WREN_DEBUGGER_CMD_STEP_OUT; + return WREN_DEBUGGER_CMD_NONE; +} + +#endif \ No newline at end of file diff --git a/src/vm/wren_debugger.h b/src/vm/wren_debugger.h new file mode 100644 index 00000000..d5aeea60 --- /dev/null +++ b/src/vm/wren_debugger.h @@ -0,0 +1,138 @@ +#ifndef wren_debugger_h +#define wren_debugger_h + +#include "wren.h" +#include "wren_value.h" +#include "wren_common.h" +#include "wren_compiler.h" + +#ifdef _WIN32 +#pragma comment(lib, "ws2_32.lib") +#endif + +#if WREN_DEBUGGER + +#define WREN_MAX_BREAKPOINTS 32 +#define MAX_NAME_LEN 256 + +typedef struct { + int id; + int line; + int stopped_in_frame; //the idx of the callframe during which we last stopped on this breakpoint (or -1 if we have left that callframe) + char module[MAX_NAME_LEN]; //This should probably be dynamic to avoid the length restriction, but for it's easier this way +} Breakpoint; + +typedef enum { + //Execution is halted until we get a command to advance + WREN_DEBUGGER_STATE_HALTING, + + //Exeuction is running + WREN_DEBUGGER_STATE_RUNNING, + + //Exeuction is running until we've stepped over the whole line we're on + WREN_DEBUGGER_STATE_STEPPING_OVER, + + //Execution is running until we've entered the next function on this line, or moved on to the next line + WREN_DEBUGGER_STATE_STEPPING_INTO, + + //Execution is running until we arrive back at the calling function + WREN_DEBUGGER_STATE_STEPPING_OUT +} DebuggerState; + +typedef enum { + // Don't do anything + WREN_DEBUGGER_CMD_NONE, + + // Continue execution until the next breakpoint + WREN_DEBUGGER_CMD_CONTINUE, + + //Step over current line + WREN_DEBUGGER_CMD_STEP_OVER, + + //Step into next function on this line + WREN_DEBUGGER_CMD_STEP_INTO, + + //Step out of the current function (ignored if triggered during the root function) + WREN_DEBUGGER_CMD_STEP_OUT + +} DebuggerCmd; + +typedef enum { + //Didn't stop + WREN_DEBUGGER_STOP_DIDNT, + + //Stopped due to a fiber switch + WREN_DEBUGGER_STOP_SWITCH, + + //Stopped due to stepping in/out/over + WREN_DEBUGGER_STOP_STEP, + + //Stopped due to a breakpoint + WREN_DEBUGGER_STOP_BREAKPOINT + +} DebuggerStopReason; + +typedef enum { + + WREN_DEBUGGER_EVENT_CREATED_BREAKPOINT, + + WREN_DEBUGGER_EVENT_STOPPED, + + WREN_DEBUGGER_EVENT_STACK, + + WREN_DEBUGGER_EVENT_SOURCE, + + WREN_DEBUGGER_EVENT_VARS + +} DebuggerEventType; + +typedef struct { + int listen_sock; + int comm_sock; + + Breakpoint breakpoints[WREN_MAX_BREAKPOINTS]; //:todo: this should be a module name -> lines map + int num_breakpoints; + int next_id; + + DebuggerState state; + + //:todo: weird way to identify our fiber, but we never dereference it so should be fine for GC. + ObjFiber* last_fiber; + + int last_line; + int last_frame; + + //Info about the last place we were before stepping over a line + int last_step_line; + int last_step_frame; + + int target_step_out_frame; +} WrenDebugger; + +void wrenInitDebugger(WrenVM* vm); +void wrenFreeDebugger(WrenVM* vm); +void wrenResetDebugger(WrenVM* vm); + +void wrenRunDebugger(WrenVM* vm, ObjFn* fn, int i); + +int debuggerAddBreakpoint(WrenDebugger* debugger, const char* module, int line); +bool debuggerRemoveBreakpoint(WrenDebugger* debugger, const char* module, int line); +void debuggerSendStack(WrenVM* vm, const char* msg, int ip); +void debuggerSendSource(WrenVM* vm, const char* msg); +void debuggerSendVar(WrenVM* vm, const char* msg); +void debuggerSendModuleInfo(WrenVM* vm, int stack_idx); +void debuggerSendFunctionInfo(WrenVM* vm, int stack_idx); + +void debuggerSendEvent(int socket, DebuggerEventType type, const char* data, int length, bool finish); + +DebuggerStopReason debuggerShouldBreak(WrenDebugger* debugger, const ObjFiber* fiber, int frame, const char* module, int line); + +bool debuggerPollInput(WrenVM* vm); +void wrenDebuggerPollConfigCmds(WrenVM* vm); +DebuggerCmd debuggerGetCmd(WrenVM* vm, int ip); +bool debuggerProcessConfigCmds(WrenVM* vm, const char* msg); +DebuggerCmd debuggerProcessStoppedCmds(WrenVM* vm, const char* msg, int ip); + +#endif //WREN_DEBUGGER + +#endif \ No newline at end of file diff --git a/src/vm/wren_debugger_types.h b/src/vm/wren_debugger_types.h new file mode 100644 index 00000000..c05a10fe --- /dev/null +++ b/src/vm/wren_debugger_types.h @@ -0,0 +1,37 @@ +#pragma once +#ifndef wren_debugger_types_h +#define wren_debugger_types_h + +#include "wren_common.h" +#include "wren_utils.h" + +#if WREN_DEBUGGER + +DECLARE_BUFFER(SymbolTable, SymbolTable); +DECLARE_BUFFER(IntBuffer, IntBuffer); + +typedef struct { + + //The locals table stores an index for each local variable in the function + //Indexes correspond to entries in [localIndixes], which give the index into the function stack + SymbolTable locals; + IntBuffer localIndexes; + //:todo: an int buffer is wasteful + IntBuffer isUpvalue; + IntBuffer startLines; //When a local was defined + IntBuffer endLines; //Last line that a local was defined on + +} FnDebugLocals; + +typedef struct { + // Maps class name to an index into [fieldIndices] and [fieldSlots] + SymbolTable classIndices; + // For each class in this module, stores the class' field name -> index into fieldSlots + SymbolTableBuffer fieldIndices; + // For each field class in this module, stores the actual slot index of the given field + IntBufferBuffer fieldSlots; +} ObjModuleDebug; + + +#endif //WREN_DEBUGGER +#endif //wren_debugger_types_h \ No newline at end of file diff --git a/src/vm/wren_value.c b/src/vm/wren_value.c index c49a3b6b..de220a99 100644 --- a/src/vm/wren_value.c +++ b/src/vm/wren_value.c @@ -43,7 +43,7 @@ static void initObj(WrenVM* vm, Obj* obj, ObjType type, ObjClass* classObj) vm->first = obj; } -ObjClass* wrenNewSingleClass(WrenVM* vm, int numFields, ObjString* name) +ObjClass* wrenNewSingleClass(WrenVM* vm, int numFields, ObjString* name, ObjModule* module) { ObjClass* classObj = ALLOCATE(vm, ObjClass); initObj(vm, &classObj->obj, OBJ_CLASS, NULL); @@ -51,6 +51,7 @@ ObjClass* wrenNewSingleClass(WrenVM* vm, int numFields, ObjString* name) classObj->numFields = numFields; classObj->name = name; classObj->attributes = NULL_VAL; + classObj->module = module; wrenPushRoot(vm, (Obj*)classObj); wrenMethodBufferInit(&classObj->methods); @@ -84,13 +85,13 @@ void wrenBindSuperclass(WrenVM* vm, ObjClass* subclass, ObjClass* superclass) } ObjClass* wrenNewClass(WrenVM* vm, ObjClass* superclass, int numFields, - ObjString* name) + ObjString* name, ObjModule* module) { // Create the metaclass. Value metaclassName = wrenStringFormat(vm, "@ metaclass", OBJ_VAL(name)); wrenPushRoot(vm, AS_OBJ(metaclassName)); - ObjClass* metaclass = wrenNewSingleClass(vm, 0, AS_STRING(metaclassName)); + ObjClass* metaclass = wrenNewSingleClass(vm, 0, AS_STRING(metaclassName), NULL); metaclass->obj.classObj = vm->classClass; wrenPopRoot(vm); @@ -102,7 +103,7 @@ ObjClass* wrenNewClass(WrenVM* vm, ObjClass* superclass, int numFields, // hierarchy. wrenBindSuperclass(vm, metaclass, vm->classClass); - ObjClass* classObj = wrenNewSingleClass(vm, numFields, name); + ObjClass* classObj = wrenNewSingleClass(vm, numFields, name, module); // Make sure the class isn't collected while the inherited methods are being // bound. @@ -110,6 +111,23 @@ ObjClass* wrenNewClass(WrenVM* vm, ObjClass* superclass, int numFields, classObj->obj.classObj = metaclass; wrenBindSuperclass(vm, classObj, superclass); + + #if WREN_DEBUGGER + if(module != NULL) { + int classIndex = wrenSymbolTableFind(&module->classDebug.classIndices, name->value, name->length); + // printf("looking for %.*s\n", name->length, name->value); + // if(classIndex == -1) { + // classIndex = wrenSymbolTableFind(&module->classDebug.classIndices, name->value, name->length); + // } + + ASSERT(classIndex != -1, "Each class should have an entry in module->classIndices."); + + IntBuffer* fieldSlots = &module->classDebug.fieldSlots.data[classIndex]; + for(int i=0; icount; i++) { + fieldSlots->data[i] += superclass->numFields; + } + } + #endif //WREN_DEBUGGER wrenPopRoot(vm); wrenPopRoot(vm); @@ -247,6 +265,14 @@ ObjFn* wrenNewFunction(WrenVM* vm, ObjModule* module, int maxSlots) debug->name = NULL; wrenIntBufferInit(&debug->sourceLines); + #if WREN_DEBUGGER + wrenSymbolTableInit(&debug->locals.locals); + wrenIntBufferInit(&debug->locals.localIndexes); + wrenIntBufferInit(&debug->locals.isUpvalue); + wrenIntBufferInit(&debug->locals.startLines); + wrenIntBufferInit(&debug->locals.endLines); + #endif + ObjFn* fn = ALLOCATE(vm, ObjFn); initObj(vm, &fn->obj, OBJ_FN, vm->fnClass); @@ -654,6 +680,12 @@ ObjModule* wrenNewModule(WrenVM* vm, ObjString* name) wrenSymbolTableInit(&module->variableNames); wrenValueBufferInit(&module->variables); + #if WREN_DEBUGGER + wrenSymbolTableInit(&module->classDebug.classIndices); + wrenSymbolTableBufferInit(&module->classDebug.fieldIndices); + wrenIntBufferBufferInit(&module->classDebug.fieldSlots); + #endif + module->name = name; wrenPopRoot(vm); @@ -1160,6 +1192,13 @@ static void blackenModule(WrenVM* vm, ObjModule* module) wrenBlackenSymbolTable(vm, &module->variableNames); + #if WREN_DEBUGGER + wrenBlackenSymbolTable(vm, &module->classDebug.classIndices); + // :todo: Do these need to be grayed? If so, how? + // wrenGrayBuffer(vm, &module->classDebug.fieldIndices); + // wrenGrayBuffer(vm, &module->classDebug.fieldSlots); + #endif + wrenGrayObj(vm, (Obj*)module->name); // Keep track of how much memory is still in use. @@ -1251,6 +1290,15 @@ void wrenFreeObj(WrenVM* vm, Obj* obj) wrenValueBufferClear(vm, &fn->constants); wrenByteBufferClear(vm, &fn->code); wrenIntBufferClear(vm, &fn->debug->sourceLines); + + #if WREN_DEBUGGER + wrenSymbolTableClear(vm, &fn->debug->locals.locals); + wrenIntBufferClear(vm, &fn->debug->locals.localIndexes); + wrenIntBufferClear(vm, &fn->debug->locals.isUpvalue); + wrenIntBufferClear(vm, &fn->debug->locals.startLines); + wrenIntBufferClear(vm, &fn->debug->locals.endLines); + #endif + DEALLOCATE(vm, fn->debug->name); DEALLOCATE(vm, fn->debug); break; @@ -1271,6 +1319,13 @@ void wrenFreeObj(WrenVM* vm, Obj* obj) case OBJ_MODULE: wrenSymbolTableClear(vm, &((ObjModule*)obj)->variableNames); wrenValueBufferClear(vm, &((ObjModule*)obj)->variables); + + #if WREN_DEBUGGER + wrenSymbolTableClear(vm, &((ObjModule*)obj)->classDebug.classIndices); + wrenSymbolTableBufferClear(vm, &((ObjModule*)obj)->classDebug.fieldIndices); + wrenIntBufferBufferClear(vm, &((ObjModule*)obj)->classDebug.fieldSlots); + #endif + break; case OBJ_CLOSURE: diff --git a/src/vm/wren_value.h b/src/vm/wren_value.h index 2ca0cfdc..2810b37b 100644 --- a/src/vm/wren_value.h +++ b/src/vm/wren_value.h @@ -8,6 +8,10 @@ #include "wren_math.h" #include "wren_utils.h" +#if WREN_DEBUGGER + #include "wren_debugger_types.h" +#endif + // This defines the built-in types and their core representations in memory. // Since Wren is dynamically typed, any variable can hold a value of any type, // and the type can change at runtime. Implementing this efficiently is @@ -215,6 +219,11 @@ typedef struct // bytecode in the function's bytecode array. The value of that element is // the line in the source code that generated that instruction. IntBuffer sourceLines; + + #if WREN_DEBUGGER + FnDebugLocals locals; + #endif + } FnDebug; // A loaded module and the top-level variables it defines. @@ -231,6 +240,10 @@ typedef struct // Symbol table for the names of all module variables. Indexes here directly // correspond to entries in [variables]. SymbolTable variableNames; + + #if WREN_DEBUGGER + ObjModuleDebug classDebug; + #endif // The name of the module. ObjString* name; @@ -385,6 +398,7 @@ typedef struct WrenForeignMethodFn foreign; ObjClosure* closure; } as; + } Method; DECLARE_BUFFER(Method, Method); @@ -413,6 +427,9 @@ struct sObjClass // The ClassAttribute for the class, if any Value attributes; + + // The module the class was defined in. + ObjModule* module; }; typedef struct @@ -620,7 +637,7 @@ typedef struct // Creates a new "raw" class. It has no metaclass or superclass whatsoever. // This is only used for bootstrapping the initial Object and Class classes, // which are a little special. -ObjClass* wrenNewSingleClass(WrenVM* vm, int numFields, ObjString* name); +ObjClass* wrenNewSingleClass(WrenVM* vm, int numFields, ObjString* name, ObjModule* module); // Makes [superclass] the superclass of [subclass], and causes subclass to // inherit its methods. This should be called before any methods are defined @@ -629,7 +646,7 @@ void wrenBindSuperclass(WrenVM* vm, ObjClass* subclass, ObjClass* superclass); // Creates a new class object as well as its associated metaclass. ObjClass* wrenNewClass(WrenVM* vm, ObjClass* superclass, int numFields, - ObjString* name); + ObjString* name, ObjModule* module); void wrenBindMethod(WrenVM* vm, ObjClass* classObj, int symbol, Method method); diff --git a/src/vm/wren_vm.c b/src/vm/wren_vm.c index 254d0b03..20a3ed64 100644 --- a/src/vm/wren_vm.c +++ b/src/vm/wren_vm.c @@ -21,6 +21,10 @@ #include #endif +#if WREN_DEBUGGER + #include "wren_debugger.h" +#endif + // The behavior of realloc() when the size is 0 is implementation defined. It // may return a non-NULL pointer which must not be dereferenced but nevertheless // should be freed. To prevent that, we avoid calling realloc() with a zero @@ -54,6 +58,12 @@ void wrenInitConfiguration(WrenConfiguration* config) config->minHeapSize = 1024 * 1024; config->heapGrowthPercent = 50; config->userData = NULL; + + #if WREN_DEBUGGER + config->modulePathFn = NULL; + config->debuggerPort = NULL; + config->enableDebugger = false; + #endif } WrenVM* wrenNewVM(WrenConfiguration* config) @@ -89,6 +99,10 @@ WrenVM* wrenNewVM(WrenConfiguration* config) vm->gray = (Obj**)reallocate(NULL, vm->grayCapacity * sizeof(Obj*), userData); vm->nextGC = vm->config.initialHeapSize; + #if WREN_DEBUGGER + wrenInitDebugger(vm); + #endif + wrenSymbolTableInit(&vm->methodNames); vm->modules = wrenNewMap(vm); @@ -119,6 +133,10 @@ void wrenFreeVM(WrenVM* vm) wrenSymbolTableClear(vm, &vm->methodNames); + #if WREN_DEBUGGER + wrenFreeDebugger(vm); + #endif + DEALLOCATE(vm, vm); } @@ -649,7 +667,7 @@ static void createClass(WrenVM* vm, int numFields, ObjModule* module) if (wrenHasError(vm->fiber)) return; ObjClass* classObj = wrenNewClass(vm, AS_CLASS(superclass), numFields, - AS_STRING(name)); + AS_STRING(name), module); vm->fiber->stackTop[-1] = OBJ_VAL(classObj); if (numFields == -1) bindForeignClass(vm, classObj, module); @@ -838,7 +856,7 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber) register ObjFn* fn; // These macros are designed to only be invoked within this function. - #define PUSH(value) (*fiber->stackTop++ = value) + #define PUSH(value) do { *fiber->stackTop = (value); fiber->stackTop++; } while (false) #define POP() (*(--fiber->stackTop)) #define DROP() (fiber->stackTop--) #define PEEK() (*(fiber->stackTop - 1)) @@ -887,6 +905,20 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber) #define DEBUG_TRACE_INSTRUCTIONS() do { } while (false) #endif + +#if WREN_DEBUGGER + #define CALL_DEBUGGER() \ + do \ + { \ + if(vm->config.enableDebugger) { \ + wrenRunDebugger(vm, fn, (int)(ip - fn->code.data)); \ + } \ + } \ + while (false) + #else + #define CALL_DEBUGGER() do { } while (false) + #endif + #if WREN_COMPUTED_GOTO static void* dispatchTable[] = { @@ -902,6 +934,7 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber) do \ { \ DEBUG_TRACE_INSTRUCTIONS(); \ + CALL_DEBUGGER(); \ goto *dispatchTable[instruction = (Code)READ_BYTE()]; \ } while (false) @@ -910,6 +943,7 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber) #define INTERPRET_LOOP \ loop: \ DEBUG_TRACE_INSTRUCTIONS(); \ + CALL_DEBUGGER(); \ switch (instruction = (Code)READ_BYTE()) #define CASE_CODE(name) case CODE_##name @@ -919,6 +953,10 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber) LOAD_FRAME(); + #if WREN_DEBUGGER + wrenResetDebugger(vm); + #endif + Code instruction; INTERPRET_LOOP { @@ -1304,7 +1342,7 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber) CASE_CODE(CLASS): { - createClass(vm, READ_BYTE(), NULL); + createClass(vm, READ_BYTE(), fn->module); if (wrenHasError(fiber)) RUNTIME_ERROR(); DISPATCH(); } diff --git a/src/vm/wren_vm.h b/src/vm/wren_vm.h index 7ab74c9e..0cf23bb2 100644 --- a/src/vm/wren_vm.h +++ b/src/vm/wren_vm.h @@ -6,6 +6,10 @@ #include "wren_value.h" #include "wren_utils.h" +#if WREN_DEBUGGER + #include "wren_debugger.h" +#endif + // The maximum number of temporary objects that can be made visible to the GC // at one time. #define WREN_MAX_TEMP_ROOTS 8 @@ -100,6 +104,10 @@ struct WrenVM Value* apiStack; WrenConfiguration config; + + #if WREN_DEBUGGER + WrenDebugger debugger; + #endif // Compiler and debugger data: