From 27e79d71f3987d21a627b7d7d7b07bb123348d47 Mon Sep 17 00:00:00 2001 From: Chris Speciale Date: Thu, 12 Nov 2026 07:06:29 -0500 Subject: [PATCH 1/4] Reverse and resize an Array in interpreted cppia The interpreter took reverse() and resize() on an Array but had no case for either in runVoid or runObject, so both fell through to the default, which evaluated the array and did nothing else. resize never evaluated its argument either, so a call in it was lost. They now reverse and resize the array, as they do under the JIT and in compiled code, and are typed void like the other calls that change the array. Tests: test/regression/CppiaDynamicArrayReverseResize reverses an Array, then shrinks it, grows it and empties it with a counted size argument, interpreted and with the JIT, with a typed array as the control. Before, the interpreter left the array as it was and never called the size function. Fixes #409 --- Changes.md | 1 + src/hx/cppia/ArrayVirtual.cpp | 23 ++++++++++++++- .../CppiaDynamicArrayReverseResize/Main.hx | 13 +++++++++ .../CppiaDynamicArrayReverseResize/Script.hx | 28 +++++++++++++++++++ .../CppiaDynamicArrayReverseResize/build.hxml | 7 +++++ .../CppiaDynamicArrayReverseResize/stdout.txt | 12 ++++++++ 6 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 test/regression/CppiaDynamicArrayReverseResize/Main.hx create mode 100644 test/regression/CppiaDynamicArrayReverseResize/Script.hx create mode 100644 test/regression/CppiaDynamicArrayReverseResize/build.hxml create mode 100644 test/regression/CppiaDynamicArrayReverseResize/stdout.txt diff --git a/Changes.md b/Changes.md index e7b66623..6bf33bb9 100644 --- a/Changes.md +++ b/Changes.md @@ -2,6 +2,7 @@ libhxcpp 6.0.0 (unreleased) ------------------------------------------------------------ +* Fixed reverse() and resize() on an Array in interpreted cppia doing nothing: the interpreter had no case for them, and resize never evaluated its argument. They reverse and resize the array, as they do under the JIT and in compiled code (libhxcpp #409) * Made the collector's thread context on Linux a __thread pointer, read with one load from the thread pointer, instead of a call to pthread_getspecific each time an allocation leaves the inline path or a String or number is boxed. It was 3 to 4% of an OpenFL game's main thread. HXCPP_PTHREAD_FAST_TLS keeps pthread_getspecific (libhxcpp #429) * Fixed a call on a null Array in cppia ending the process under HXCPP_CHECK_POINTER: push, contains, remove, indexOf, lastIndexOf, join, toString, sort, insert and unshift used the array without checking it, interpreted and under the JIT. They throw Null Object Reference, as a typed array's calls and the other Array calls do * Fixed the 32-bit x86 collector missing live objects held only in EBP when frame pointers are omitted. It now captures EBP with the other callee-saved registers; the lost strings in the Linux 32 CFFI tests were one consequence (libhxcpp #430) diff --git a/src/hx/cppia/ArrayVirtual.cpp b/src/hx/cppia/ArrayVirtual.cpp index 4370d6d6..0f8baeaa 100644 --- a/src/hx/cppia/ArrayVirtual.cpp +++ b/src/hx/cppia/ArrayVirtual.cpp @@ -261,7 +261,8 @@ struct ArrayBuiltinAny : public ArrayBuiltinBase if (FUNC==afJoin || FUNC==afToString) return Dynamic(runString(ctx)).mPtr; - if (FUNC==afSort || FUNC==afInsert || FUNC==afUnshift || FUNC==af__SetSizeExact || FUNC==afBlit) + if (FUNC==afSort || FUNC==afInsert || FUNC==afUnshift || FUNC==af__SetSizeExact || FUNC==afBlit || + FUNC==afReverse || FUNC==afResize) { runVoid(ctx); return 0; @@ -456,6 +457,24 @@ struct ArrayBuiltinAny : public ArrayBuiltinBase thisVal->__SetSizeExact(size); return; } + if (FUNC==afResize) + { + ArrayAnyImpl *thisVal = (ArrayAnyImpl *)thisExpr->runObject(ctx); + BCR_VCHECK; + CPPIA_CHECK(thisVal); + int size = args[0]->runInt(ctx); + BCR_VCHECK; + thisVal->CALL(resize)(size); + return; + } + if (FUNC==afReverse) + { + ArrayAnyImpl *thisVal = (ArrayAnyImpl *)thisExpr->runObject(ctx); + BCR_VCHECK; + CPPIA_CHECK(thisVal); + thisVal->CALL(reverse)(); + return; + } if (FUNC==afBlit) { ArrayAnyImpl *thisVal = (ArrayAnyImpl *)thisExpr->runObject(ctx); @@ -586,6 +605,8 @@ struct ArrayBuiltinAny : public ArrayBuiltinBase case afUnshift: case af__SetSizeExact: case afBlit: + case afReverse: + case afResize: return etVoid; case afPush: diff --git a/test/regression/CppiaDynamicArrayReverseResize/Main.hx b/test/regression/CppiaDynamicArrayReverseResize/Main.hx new file mode 100644 index 00000000..e1b4cc7a --- /dev/null +++ b/test/regression/CppiaDynamicArrayReverseResize/Main.hx @@ -0,0 +1,13 @@ +// Runs the script interpreted and then under the JIT +function main() { + var script = haxe.io.Path.join([haxe.io.Path.directory(Sys.programPath()), "script.cppia"]); + for (jit in [false, true]) { + cpp.cppia.Host.enableJit(jit); + Sys.println(jit ? "jit" : "interpreted"); + try { + cpp.cppia.Host.runFile(script); + } catch (e:Dynamic) { + Sys.println("error: " + e); + } + } +} diff --git a/test/regression/CppiaDynamicArrayReverseResize/Script.hx b/test/regression/CppiaDynamicArrayReverseResize/Script.hx new file mode 100644 index 00000000..fffd735d --- /dev/null +++ b/test/regression/CppiaDynamicArrayReverseResize/Script.hx @@ -0,0 +1,28 @@ +// The interpreter took reverse() and resize() on an Array but did +// neither, and never evaluated resize's argument. The JIT and compiled code +// do both. A typed array is the control. +class Script { + static var calls = 0; + + static function size(n:Int):Int { + calls++; + return n; + } + + static function main() { + var typed = [1, 2, 3]; + typed.reverse(); + typed.resize(2); + Sys.println("typed: " + typed.join(",")); + + var a:Array = [1, "two", 3]; + a.reverse(); + Sys.println("reversed: " + a.join(",")); + a.resize(size(1)); + Sys.println("shrunk: " + a.join(",") + " calls=" + calls); + a.resize(size(3)); + Sys.println("grown: " + a.length + " " + a.join(",") + " calls=" + calls); + a.resize(size(0)); + Sys.println("emptied: " + a.length + " calls=" + calls); + } +} diff --git a/test/regression/CppiaDynamicArrayReverseResize/build.hxml b/test/regression/CppiaDynamicArrayReverseResize/build.hxml new file mode 100644 index 00000000..d20d36bd --- /dev/null +++ b/test/regression/CppiaDynamicArrayReverseResize/build.hxml @@ -0,0 +1,7 @@ +--main Script +-cppia bin/script.cppia +--next +--main Main +-cpp bin +-D scriptable +--dce no diff --git a/test/regression/CppiaDynamicArrayReverseResize/stdout.txt b/test/regression/CppiaDynamicArrayReverseResize/stdout.txt new file mode 100644 index 00000000..c1dfa5bd --- /dev/null +++ b/test/regression/CppiaDynamicArrayReverseResize/stdout.txt @@ -0,0 +1,12 @@ +interpreted +typed: 3,2 +reversed: 3,two,1 +shrunk: 3 calls=1 +grown: 3 3,null,null calls=2 +emptied: 0 calls=3 +jit +typed: 3,2 +reversed: 3,two,1 +shrunk: 3 calls=1 +grown: 3 3,null,null calls=2 +emptied: 0 calls=3 From 784ce0b8cb1b047f915a935eee969b4465c1f4c1 Mon Sep 17 00:00:00 2001 From: Chris Speciale Date: Thu, 12 Nov 2026 07:07:10 -0500 Subject: [PATCH 2/4] Pass fromIndex to Array searches under the cppia JIT For indexOf and lastIndexOf on an Array, the JIT generated only the item, and its helpers called the array without fromIndex, so every search covered the whole array. The argument was never evaluated, so a call or an exception in it was lost too. The JIT now generates it and passes it on, null when the call leaves it out, as the interpreter does. Tests: test/regression/CppiaDynamicArraySearchFrom searches with no fromIndex, a positive, a negative and an out-of-range one, one from a counted call and one that throws, interpreted and with the JIT, with a typed array as the control. Before, the JIT found the first or last match every time, never called the function and never threw. Fixes #410 --- Changes.md | 1 + src/hx/cppia/ArrayVirtual.cpp | 15 +++++---- .../CppiaDynamicArraySearchFrom/Main.hx | 13 ++++++++ .../CppiaDynamicArraySearchFrom/Script.hx | 33 +++++++++++++++++++ .../CppiaDynamicArraySearchFrom/build.hxml | 7 ++++ .../CppiaDynamicArraySearchFrom/stdout.txt | 16 +++++++++ 6 files changed, 79 insertions(+), 6 deletions(-) create mode 100644 test/regression/CppiaDynamicArraySearchFrom/Main.hx create mode 100644 test/regression/CppiaDynamicArraySearchFrom/Script.hx create mode 100644 test/regression/CppiaDynamicArraySearchFrom/build.hxml create mode 100644 test/regression/CppiaDynamicArraySearchFrom/stdout.txt diff --git a/Changes.md b/Changes.md index 6bf33bb9..11c18945 100644 --- a/Changes.md +++ b/Changes.md @@ -2,6 +2,7 @@ libhxcpp 6.0.0 (unreleased) ------------------------------------------------------------ +* Fixed indexOf and lastIndexOf on an Array under the cppia JIT ignoring fromIndex: the JIT never generated it, so a call or exception in it was lost too. The search starts from it, as it does interpreted and in compiled code (libhxcpp #410) * Fixed reverse() and resize() on an Array in interpreted cppia doing nothing: the interpreter had no case for them, and resize never evaluated its argument. They reverse and resize the array, as they do under the JIT and in compiled code (libhxcpp #409) * Made the collector's thread context on Linux a __thread pointer, read with one load from the thread pointer, instead of a call to pthread_getspecific each time an allocation leaves the inline path or a String or number is boxed. It was 3 to 4% of an OpenFL game's main thread. HXCPP_PTHREAD_FAST_TLS keeps pthread_getspecific (libhxcpp #429) * Fixed a call on a null Array in cppia ending the process under HXCPP_CHECK_POINTER: push, contains, remove, indexOf, lastIndexOf, join, toString, sort, insert and unshift used the array without checking it, interpreted and under the JIT. They throw Null Object Reference, as a typed array's calls and the other Array calls do diff --git a/src/hx/cppia/ArrayVirtual.cpp b/src/hx/cppia/ArrayVirtual.cpp index 0f8baeaa..b625e916 100644 --- a/src/hx/cppia/ArrayVirtual.cpp +++ b/src/hx/cppia/ArrayVirtual.cpp @@ -644,14 +644,14 @@ struct ArrayBuiltinAny : public ArrayBuiltinBase return 0; } - static int SLJIT_CALL runIndexOf( ArrayAnyImpl *inArray, hx::Object *inItem) + static int SLJIT_CALL runIndexOf( ArrayAnyImpl *inArray, hx::Object *inItem, hx::Object *inFrom) { - return inArray->indexOf(inItem); + return inArray->indexOf(inItem, inFrom); } - static int SLJIT_CALL runLastIndexOf( ArrayAnyImpl *inArray, hx::Object *inItem) + static int SLJIT_CALL runLastIndexOf( ArrayAnyImpl *inArray, hx::Object *inItem, hx::Object *inFrom) { - return inArray->lastIndexOf(inItem); + return inArray->lastIndexOf(inItem, inFrom); } @@ -748,8 +748,11 @@ struct ArrayBuiltinAny : public ArrayBuiltinBase case afIndexOf: case afLastIndexOf: { - args[0]->genCode(compiler, sJitArg1.as(jtPointer), etObject); - compiler->callNative( (void *)(FUNC==afIndexOf ? runIndexOf : runLastIndexOf), thisVal, sJitArg1.as(jtPointer)); + // The item, then fromIndex, which is null when the call leaves it out + JitTemp item(compiler,etObject); + args[0]->genCode(compiler, item, etObject); + args[1]->genCode(compiler, sJitArg2, etObject); + compiler->callNative( (void *)(FUNC==afIndexOf ? runIndexOf : runLastIndexOf), thisVal, item.as(jtPointer), sJitArg2.as(jtPointer)); compiler->convertReturnReg(etInt, inDest, destType ); } break; diff --git a/test/regression/CppiaDynamicArraySearchFrom/Main.hx b/test/regression/CppiaDynamicArraySearchFrom/Main.hx new file mode 100644 index 00000000..e1b4cc7a --- /dev/null +++ b/test/regression/CppiaDynamicArraySearchFrom/Main.hx @@ -0,0 +1,13 @@ +// Runs the script interpreted and then under the JIT +function main() { + var script = haxe.io.Path.join([haxe.io.Path.directory(Sys.programPath()), "script.cppia"]); + for (jit in [false, true]) { + cpp.cppia.Host.enableJit(jit); + Sys.println(jit ? "jit" : "interpreted"); + try { + cpp.cppia.Host.runFile(script); + } catch (e:Dynamic) { + Sys.println("error: " + e); + } + } +} diff --git a/test/regression/CppiaDynamicArraySearchFrom/Script.hx b/test/regression/CppiaDynamicArraySearchFrom/Script.hx new file mode 100644 index 00000000..1db4bc82 --- /dev/null +++ b/test/regression/CppiaDynamicArraySearchFrom/Script.hx @@ -0,0 +1,33 @@ +// The JIT ignored fromIndex in indexOf and lastIndexOf on an Array, +// and never evaluated it, so calls and exceptions in it were lost. The +// interpreter and compiled code use it. A typed array is the control. +class Script { + static var calls = 0; + + static function from(n:Int):Int { + calls++; + return n; + } + + static function fail():Int { + throw "bad index"; + } + + static function main() { + var typed = ["a", "b", "a", "b", "a"]; + Sys.println("typed: " + typed.indexOf("a", 1) + " " + typed.lastIndexOf("a", 3)); + + var a:Array = ["a", "b", "a", "b", "a"]; + Sys.println("omitted: " + a.indexOf("a") + " " + a.lastIndexOf("a")); + Sys.println("from 2: " + a.indexOf("a", 2) + " " + a.lastIndexOf("a", 2)); + Sys.println("negative: " + a.indexOf("a", -2) + " " + a.lastIndexOf("a", -2)); + Sys.println("past the end: " + a.indexOf("a", 9) + " " + a.lastIndexOf("a", 9)); + Sys.println("evaluated: " + a.indexOf("a", from(3)) + " " + a.lastIndexOf("a", from(3)) + " calls=" + calls); + try { + a.indexOf("a", fail()); + Sys.println("throwing fromIndex: not thrown"); + } catch (e:Dynamic) { + Sys.println("throwing fromIndex: " + e); + } + } +} diff --git a/test/regression/CppiaDynamicArraySearchFrom/build.hxml b/test/regression/CppiaDynamicArraySearchFrom/build.hxml new file mode 100644 index 00000000..d20d36bd --- /dev/null +++ b/test/regression/CppiaDynamicArraySearchFrom/build.hxml @@ -0,0 +1,7 @@ +--main Script +-cppia bin/script.cppia +--next +--main Main +-cpp bin +-D scriptable +--dce no diff --git a/test/regression/CppiaDynamicArraySearchFrom/stdout.txt b/test/regression/CppiaDynamicArraySearchFrom/stdout.txt new file mode 100644 index 00000000..27c97a9f --- /dev/null +++ b/test/regression/CppiaDynamicArraySearchFrom/stdout.txt @@ -0,0 +1,16 @@ +interpreted +typed: 2 2 +omitted: 0 4 +from 2: 2 2 +negative: 4 2 +past the end: -1 4 +evaluated: 4 2 calls=2 +throwing fromIndex: bad index +jit +typed: 2 2 +omitted: 0 4 +from 2: 2 2 +negative: 4 2 +past the end: -1 4 +evaluated: 4 2 calls=2 +throwing fromIndex: bad index From 2065e36693f529f236793a25d6085d4798e5c6ff Mon Sep 17 00:00:00 2001 From: Chris Speciale Date: Thu, 12 Nov 2026 07:07:41 -0500 Subject: [PATCH 3/4] Evaluate the array once for Array resize under the cppia JIT For resize() and __SetSizeExact, which cpp.NativeArray.setSize() calls, on an Array, the JIT evaluated the array before its switch, as for every call but blit, and then again in their cases, into a second temporary. A call in the array expression ran twice, and when it returned another array the second time, that one was resized. Both cases now use the array evaluated and null-checked above. Tests: test/regression/CppiaDynamicArrayResizeReceiver resizes the array a counted function returns, which is a different one the second time, with resize() and with cpp.NativeArray.setSize(), interpreted and with the JIT. Before, the JIT called the function twice and resized the second array. Fixes #411 --- Changes.md | 1 + src/hx/cppia/ArrayVirtual.cpp | 22 ++++++--------- .../CppiaDynamicArrayResizeReceiver/Main.hx | 13 +++++++++ .../CppiaDynamicArrayResizeReceiver/Script.hx | 28 +++++++++++++++++++ .../build.hxml | 7 +++++ .../stdout.txt | 6 ++++ 6 files changed, 63 insertions(+), 14 deletions(-) create mode 100644 test/regression/CppiaDynamicArrayResizeReceiver/Main.hx create mode 100644 test/regression/CppiaDynamicArrayResizeReceiver/Script.hx create mode 100644 test/regression/CppiaDynamicArrayResizeReceiver/build.hxml create mode 100644 test/regression/CppiaDynamicArrayResizeReceiver/stdout.txt diff --git a/Changes.md b/Changes.md index 11c18945..2df7974e 100644 --- a/Changes.md +++ b/Changes.md @@ -2,6 +2,7 @@ libhxcpp 6.0.0 (unreleased) ------------------------------------------------------------ +* Fixed resize() and cpp.NativeArray.setSize() on an Array under the cppia JIT evaluating the array expression twice, so a call in it ran twice and the array it returned the second time was the one resized. The JIT evaluates it once (libhxcpp #411) * Fixed indexOf and lastIndexOf on an Array under the cppia JIT ignoring fromIndex: the JIT never generated it, so a call or exception in it was lost too. The search starts from it, as it does interpreted and in compiled code (libhxcpp #410) * Fixed reverse() and resize() on an Array in interpreted cppia doing nothing: the interpreter had no case for them, and resize never evaluated its argument. They reverse and resize the array, as they do under the JIT and in compiled code (libhxcpp #409) * Made the collector's thread context on Linux a __thread pointer, read with one load from the thread pointer, instead of a call to pthread_getspecific each time an allocation leaves the inline path or a String or number is boxed. It was 3 to 4% of an OpenFL game's main thread. HXCPP_PTHREAD_FAST_TLS keeps pthread_getspecific (libhxcpp #429) diff --git a/src/hx/cppia/ArrayVirtual.cpp b/src/hx/cppia/ArrayVirtual.cpp index b625e916..4b577218 100644 --- a/src/hx/cppia/ArrayVirtual.cpp +++ b/src/hx/cppia/ArrayVirtual.cpp @@ -778,23 +778,17 @@ struct ArrayBuiltinAny : public ArrayBuiltinBase } break; + // The array is thisVal, evaluated and checked above: evaluating it + // again would run its side effects twice case af__SetSizeExact: - { - JitTemp thisVal(compiler,jtPointer); - thisExpr->genCode(compiler, thisVal, etObject); - args[0]->genCode(compiler, sJitArg1.as(jtInt), etInt); - compiler->callNative( (void *)arraySetSizeExact, thisVal, sJitArg1.as(jtInt)); - break; - } + args[0]->genCode(compiler, sJitArg1.as(jtInt), etInt); + compiler->callNative( (void *)arraySetSizeExact, thisVal, sJitArg1.as(jtInt)); + break; case afResize: - { - JitTemp thisVal(compiler,jtPointer); - thisExpr->genCode(compiler, thisVal, etObject); - args[0]->genCode(compiler, sJitArg1.as(jtInt), etInt); - compiler->callNative( (void *)arrayResize, thisVal, sJitArg1.as(jtInt)); - break; - } + args[0]->genCode(compiler, sJitArg1.as(jtInt), etInt); + compiler->callNative( (void *)arrayResize, thisVal, sJitArg1.as(jtInt)); + break; case afBlit: { diff --git a/test/regression/CppiaDynamicArrayResizeReceiver/Main.hx b/test/regression/CppiaDynamicArrayResizeReceiver/Main.hx new file mode 100644 index 00000000..e1b4cc7a --- /dev/null +++ b/test/regression/CppiaDynamicArrayResizeReceiver/Main.hx @@ -0,0 +1,13 @@ +// Runs the script interpreted and then under the JIT +function main() { + var script = haxe.io.Path.join([haxe.io.Path.directory(Sys.programPath()), "script.cppia"]); + for (jit in [false, true]) { + cpp.cppia.Host.enableJit(jit); + Sys.println(jit ? "jit" : "interpreted"); + try { + cpp.cppia.Host.runFile(script); + } catch (e:Dynamic) { + Sys.println("error: " + e); + } + } +} diff --git a/test/regression/CppiaDynamicArrayResizeReceiver/Script.hx b/test/regression/CppiaDynamicArrayResizeReceiver/Script.hx new file mode 100644 index 00000000..a33f584a --- /dev/null +++ b/test/regression/CppiaDynamicArrayResizeReceiver/Script.hx @@ -0,0 +1,28 @@ +// The JIT evaluated the receiver of resize() and cpp.NativeArray.setSize() on +// an Array twice, so a receiver with side effects ran twice, and the +// array it returned the second time was the one resized. +class Script { + static var calls = 0; + static var first:Array; + static var second:Array; + + static function receiver():Array { + calls++; + return calls == 1 ? first : second; + } + + static function reset() { + calls = 0; + first = [1, "two", 3]; + second = [4, "five", 6]; + } + + static function main() { + reset(); + receiver().resize(1); + Sys.println('resize: calls=$calls first=$first second=$second'); + reset(); + cpp.NativeArray.setSize(receiver(), 1); + Sys.println('setSize: calls=$calls first=$first second=$second'); + } +} diff --git a/test/regression/CppiaDynamicArrayResizeReceiver/build.hxml b/test/regression/CppiaDynamicArrayResizeReceiver/build.hxml new file mode 100644 index 00000000..d20d36bd --- /dev/null +++ b/test/regression/CppiaDynamicArrayResizeReceiver/build.hxml @@ -0,0 +1,7 @@ +--main Script +-cppia bin/script.cppia +--next +--main Main +-cpp bin +-D scriptable +--dce no diff --git a/test/regression/CppiaDynamicArrayResizeReceiver/stdout.txt b/test/regression/CppiaDynamicArrayResizeReceiver/stdout.txt new file mode 100644 index 00000000..b8c5ced0 --- /dev/null +++ b/test/regression/CppiaDynamicArrayResizeReceiver/stdout.txt @@ -0,0 +1,6 @@ +interpreted +resize: calls=1 first=[1] second=[4,five,6] +setSize: calls=1 first=[1] second=[4,five,6] +jit +resize: calls=1 first=[1] second=[4,five,6] +setSize: calls=1 first=[1] second=[4,five,6] From e297f7d9cad7e156bb7eaf68ec3ba5f0a374fa57 Mon Sep 17 00:00:00 2001 From: Chris Speciale Date: Thu, 12 Nov 2026 07:09:09 -0500 Subject: [PATCH 4/4] Catch blit, setSize and resize exceptions under the cppia JIT The JIT calls native helpers for blit, __SetSizeExact (which cpp.NativeArray.setSize() calls) and resize, and none of them had the exception bridge the helpers for sort, join and toString have. A range or size the array cannot take threw a C++ exception through the generated code, which has no unwind information, so the process ended instead of the script's catch running. On Windows the host exited with 0xE06D7363. The helpers now catch the exception and make it the script's pending one, and the generated code checks for it after each call, as it does after sort. For typed arrays they are three functions on ArrayBase, which does this work whatever the element type, in place of a copy for every element type and builtin: with exception handling in each copy, ArrayBuiltin.cpp needed more sections than MSVC's object file format allows without /bigobj, and the test/cppia host stopped building. Tests: test/regression/CppiaArrayHelperExceptions gives blit an out-of-range index, setSize a negative size and resize one too large to allocate, on an Array and an Array, each directly in a try, interpreted and with the JIT, with a valid blit as the control. Before, the JIT run ended at the first blit. test/cppia passes interpreted and with the JIT. Fixes #404 --- Changes.md | 1 + src/hx/cppia/ArrayBuiltin.cpp | 56 +++++++++++------- src/hx/cppia/ArrayVirtual.cpp | 11 ++++ .../CppiaArrayHelperExceptions/Main.hx | 13 +++++ .../CppiaArrayHelperExceptions/Script.hx | 57 +++++++++++++++++++ .../CppiaArrayHelperExceptions/build.hxml | 7 +++ .../CppiaArrayHelperExceptions/stdout.txt | 18 ++++++ 7 files changed, 142 insertions(+), 21 deletions(-) create mode 100644 test/regression/CppiaArrayHelperExceptions/Main.hx create mode 100644 test/regression/CppiaArrayHelperExceptions/Script.hx create mode 100644 test/regression/CppiaArrayHelperExceptions/build.hxml create mode 100644 test/regression/CppiaArrayHelperExceptions/stdout.txt diff --git a/Changes.md b/Changes.md index 2df7974e..b067fea0 100644 --- a/Changes.md +++ b/Changes.md @@ -2,6 +2,7 @@ libhxcpp 6.0.0 (unreleased) ------------------------------------------------------------ +* Fixed an exception from blit, setSize or resize under the cppia JIT ending the process instead of reaching the script's catch, on typed arrays and on Array: their native calls had no exception bridge. The script catches it, as it does interpreted and in compiled code (libhxcpp #404) * Fixed resize() and cpp.NativeArray.setSize() on an Array under the cppia JIT evaluating the array expression twice, so a call in it ran twice and the array it returned the second time was the one resized. The JIT evaluates it once (libhxcpp #411) * Fixed indexOf and lastIndexOf on an Array under the cppia JIT ignoring fromIndex: the JIT never generated it, so a call or exception in it was lost too. The search starts from it, as it does interpreted and in compiled code (libhxcpp #410) * Fixed reverse() and resize() on an Array in interpreted cppia doing nothing: the interpreter had no case for them, and resize never evaluated its argument. They reverse and resize the array, as they do under the JIT and in compiled code (libhxcpp #409) diff --git a/src/hx/cppia/ArrayBuiltin.cpp b/src/hx/cppia/ArrayBuiltin.cpp index 9e7ee958..6ecf7e99 100644 --- a/src/hx/cppia/ArrayBuiltin.cpp +++ b/src/hx/cppia/ArrayBuiltin.cpp @@ -99,6 +99,35 @@ static ArrayBase * SLJIT_CALL array_expand_size(ArrayBase *inArray, int inSize) return inArray; } +// blit, __SetSizeExact and resize throw for a range or size the array cannot +// take, and the exception becomes the script's pending one, which the code +// after the call checks. They are ArrayBase's whatever the element type, so +// each is one function here: a copy for every element type and builtin, each +// with its own exception handling, would take more sections than MSVC's +// object file format allows. +static void SLJIT_CALL array_set_size_exact(ArrayBase *inArray, int inSize) +{ + TRY_NATIVE + inArray->__SetSizeExact(inSize); + CATCH_NATIVE +} + +static void SLJIT_CALL array_resize(ArrayBase *inArray, int inSize) +{ + TRY_NATIVE + inArray->resize(inSize); + CATCH_NATIVE +} + +static void SLJIT_CALL array_blit(JitMultiArg *inArgs) +{ + // this, inDestElement, inSourceArray, inSourceElement, inElementCount + TRY_NATIVE + ArrayBase *dest = (ArrayBase *)inArgs[0].obj; + dest->Blit( inArgs[1].ival, (ArrayBase *)inArgs[2].obj, inArgs[3].ival, inArgs[4].ival ); + CATCH_NATIVE +} + #endif @@ -1245,16 +1274,6 @@ struct ArrayBuiltin : public ArrayBuiltinBase return inArray->keyValueIterator().mPtr; } - static void SLJIT_CALL runSetSizeExact( Array_obj *inArray, int size ) - { - inArray->__SetSizeExact(size); - } - - static void SLJIT_CALL runResize( Array_obj *inArray, int size ) - { - inArray->resize(size); - } - static void SLJIT_CALL runToString( Array_obj *inArray, String *outString ) { @@ -1268,14 +1287,6 @@ struct ArrayBuiltin : public ArrayBuiltinBase return inArray->copy().mPtr; } - static void SLJIT_CALL runBlit( JitMultiArg *inArgs) - { - // this, inDestElement, inSourceArray, inSourceElement, inElementCount - Array_obj *dest = (Array_obj *)inArgs[0].obj; - Array_obj *src = (Array_obj *)inArgs[2].obj; - dest->blit( inArgs[1].ival, src, inArgs[3].ival, inArgs[4].ival ); - } - static hx::Object * SLJIT_CALL runProcess( Array_obj *inArray, hx::Object *inFunction) { @@ -1891,7 +1902,8 @@ struct ArrayBuiltin : public ArrayBuiltinBase thisExpr->genCode(compiler, thisVal, etObject); genNullReferenceExceptionCheck(compiler, thisVal); args[0]->genCode(compiler, sJitArg1.as(jtInt), etInt); - compiler->callNative( (void *)runSetSizeExact, thisVal, sJitArg1.as(jtInt)); + compiler->callNative( (void *)array_set_size_exact, thisVal, sJitArg1.as(jtInt)); + compiler->checkException(); break; } @@ -1901,7 +1913,8 @@ struct ArrayBuiltin : public ArrayBuiltinBase thisExpr->genCode(compiler, thisVal, etObject); genNullReferenceExceptionCheck(compiler, thisVal); args[0]->genCode(compiler, sJitArg1.as(jtInt), etInt); - compiler->callNative( (void *)runResize, thisVal, sJitArg1.as(jtInt)); + compiler->callNative( (void *)array_resize, thisVal, sJitArg1.as(jtInt)); + compiler->checkException(); break; } @@ -1916,7 +1929,8 @@ struct ArrayBuiltin : public ArrayBuiltinBase args[1]->genCode(compiler, fargs.arg(2,jtPointer), etObject); args[2]->genCode(compiler, fargs.arg(3,jtInt), etInt); args[3]->genCode(compiler, fargs.arg(4,jtInt), etInt); - compiler->callNative( (void *)runBlit, fargs ); + compiler->callNative( (void *)array_blit, fargs ); + compiler->checkException(); break; } diff --git a/src/hx/cppia/ArrayVirtual.cpp b/src/hx/cppia/ArrayVirtual.cpp index 4b577218..04500f96 100644 --- a/src/hx/cppia/ArrayVirtual.cpp +++ b/src/hx/cppia/ArrayVirtual.cpp @@ -26,13 +26,19 @@ static hx::Object * SLJIT_CALL arrayConcat(ArrayAnyImpl *inObj, hx::Object *inVa { return inObj->concat(Dynamic(inValue)).mPtr; } +// These throw for a size or range the array cannot take: the exception becomes +// the script's pending one, which the code after the call checks static void SLJIT_CALL arraySetSizeExact(ArrayAnyImpl *inObj, int inSize) { + TRY_NATIVE inObj->__SetSizeExact(inSize); + CATCH_NATIVE } static void SLJIT_CALL arrayResize(ArrayAnyImpl *inObj, int inSize) { + TRY_NATIVE inObj->resize(inSize); + CATCH_NATIVE } @@ -142,9 +148,11 @@ static void SLJIT_CALL arrayUnshiftString( ArrayAnyImpl *inArray, String *inVal) static void SLJIT_CALL runBlit( JitMultiArg *inArgs) { // this, inDestElement, inSourceArray, inSourceElement, inElementCount + TRY_NATIVE ArrayAnyImpl *dest = (ArrayAnyImpl *)inArgs[0].obj; ArrayAnyImpl *src = (ArrayAnyImpl *)inArgs[2].obj; dest->blit( inArgs[1].ival, src, inArgs[3].ival, inArgs[4].ival ); + CATCH_NATIVE } @@ -783,11 +791,13 @@ struct ArrayBuiltinAny : public ArrayBuiltinBase case af__SetSizeExact: args[0]->genCode(compiler, sJitArg1.as(jtInt), etInt); compiler->callNative( (void *)arraySetSizeExact, thisVal, sJitArg1.as(jtInt)); + compiler->checkException(); break; case afResize: args[0]->genCode(compiler, sJitArg1.as(jtInt), etInt); compiler->callNative( (void *)arrayResize, thisVal, sJitArg1.as(jtInt)); + compiler->checkException(); break; case afBlit: @@ -802,6 +812,7 @@ struct ArrayBuiltinAny : public ArrayBuiltinBase args[2]->genCode(compiler, fargs.arg(3,jtInt), etInt); args[3]->genCode(compiler, fargs.arg(4,jtInt), etInt); compiler->callNative( (void *)runBlit, fargs ); + compiler->checkException(); break; } diff --git a/test/regression/CppiaArrayHelperExceptions/Main.hx b/test/regression/CppiaArrayHelperExceptions/Main.hx new file mode 100644 index 00000000..e1b4cc7a --- /dev/null +++ b/test/regression/CppiaArrayHelperExceptions/Main.hx @@ -0,0 +1,13 @@ +// Runs the script interpreted and then under the JIT +function main() { + var script = haxe.io.Path.join([haxe.io.Path.directory(Sys.programPath()), "script.cppia"]); + for (jit in [false, true]) { + cpp.cppia.Host.enableJit(jit); + Sys.println(jit ? "jit" : "interpreted"); + try { + cpp.cppia.Host.runFile(script); + } catch (e:Dynamic) { + Sys.println("error: " + e); + } + } +} diff --git a/test/regression/CppiaArrayHelperExceptions/Script.hx b/test/regression/CppiaArrayHelperExceptions/Script.hx new file mode 100644 index 00000000..0c3bb4a9 --- /dev/null +++ b/test/regression/CppiaArrayHelperExceptions/Script.hx @@ -0,0 +1,57 @@ +// Under the JIT, an exception from blit, setSize or resize called directly in +// a try went past the script's catch and ended the host, on a typed array +// and on Array. Each one throws here, and the script catches it and +// finds the array as it was. A valid blit is the control. +class Script { + static function main() { + var dest = [7, 8]; + var source = [1, 2]; + var values = [7, 8]; + var anyDest:Array = [7, "eight"]; + var anySource:Array = [1, "two"]; + var any:Array = [7, "eight"]; + try { + cpp.NativeArray.blit(dest, 1, source, 0, 1); + Sys.println("valid blit: ran, " + dest); + } catch (e:Dynamic) { + Sys.println("valid blit: caught " + e + ", " + dest); + } + try { + cpp.NativeArray.blit(dest, -1, source, 0, 1); + Sys.println("blit: ran, " + dest); + } catch (e:Dynamic) { + Sys.println("blit: caught " + e + ", " + dest); + } + try { + cpp.NativeArray.setSize(values, -1); + Sys.println("setSize: ran, " + values); + } catch (e:Dynamic) { + Sys.println("setSize: caught " + e + ", " + values); + } + try { + values.resize(0x40000000); + Sys.println("resize: ran, " + values); + } catch (e:Dynamic) { + Sys.println("resize: caught " + e + ", " + values); + } + try { + cpp.NativeArray.blit(anyDest, -1, anySource, 0, 1); + Sys.println("dynamic blit: ran, " + anyDest); + } catch (e:Dynamic) { + Sys.println("dynamic blit: caught " + e + ", " + anyDest); + } + try { + cpp.NativeArray.setSize(any, -1); + Sys.println("dynamic setSize: ran, " + any); + } catch (e:Dynamic) { + Sys.println("dynamic setSize: caught " + e + ", " + any); + } + try { + any.resize(0x40000000); + Sys.println("dynamic resize: ran, " + any); + } catch (e:Dynamic) { + Sys.println("dynamic resize: caught " + e + ", " + any); + } + Sys.println("continued"); + } +} diff --git a/test/regression/CppiaArrayHelperExceptions/build.hxml b/test/regression/CppiaArrayHelperExceptions/build.hxml new file mode 100644 index 00000000..d20d36bd --- /dev/null +++ b/test/regression/CppiaArrayHelperExceptions/build.hxml @@ -0,0 +1,7 @@ +--main Script +-cppia bin/script.cppia +--next +--main Main +-cpp bin +-D scriptable +--dce no diff --git a/test/regression/CppiaArrayHelperExceptions/stdout.txt b/test/regression/CppiaArrayHelperExceptions/stdout.txt new file mode 100644 index 00000000..502ffa29 --- /dev/null +++ b/test/regression/CppiaArrayHelperExceptions/stdout.txt @@ -0,0 +1,18 @@ +interpreted +valid blit: ran, [7,1] +blit: caught blit out of bounds, [7,1] +setSize: caught Array allocation too large, [7,8] +resize: caught Array allocation too large, [7,8] +dynamic blit: caught blit out of bounds, [7,eight] +dynamic setSize: caught Array allocation too large, [7,eight] +dynamic resize: caught Array allocation too large, [7,eight] +continued +jit +valid blit: ran, [7,1] +blit: caught blit out of bounds, [7,1] +setSize: caught Array allocation too large, [7,8] +resize: caught Array allocation too large, [7,8] +dynamic blit: caught blit out of bounds, [7,eight] +dynamic setSize: caught Array allocation too large, [7,eight] +dynamic resize: caught Array allocation too large, [7,eight] +continued