Open gfwilliams opened 2 years ago
Branch is: https://github.com/espruino/Espruino/compare/real_prototype_chain?expand=1
I just backed up the 'automatic' changes from scripts/refactor_json_comments.js, since they hide what's going on and will probably be a merge nightmare.
@gfwilliams Yeah, I tried to rebase on top of master over the weekend... it compiles, but looks like I made mistakes as the results are not behaving the same as the old branch. It looks like it doesn't look the 'prototype' correctly for the builtins...
Here https://github.com/opichals/Espruino/tree/real_proto if you would want to have a look.
Thanks! I'm just back from holiday today and still working through messages, so I may not get a chance to look into this for a few days.
Do you have some very simple code that shows the issue?
I found tests/test_array_fill.js
which fails, but that maybe seems to be because b = new Array(5);
creates an actual instance of the Array object when in that case it should create a jsvNewArray and Espruino should 'fake' the prototype
In the tests/test_pin_pwm.js
it seems that the Pin.prototype
is always re-created and the modifications (and therefore adding the pwm function doesn't work.
I have also been playing with tests/test_typed_array_sort.js
which seems to rather instantiate a generic Object
instead of the Uint8Array
it should.
Also this https://github.com/opichals/Espruino/commit/4429e29a380af9c48e8718cdc773914958ec3ea5 looks wrong - those are all global/root-visible types and so I should have left the jspNewObject
in use for these.
I am getting it correct?
it seems that the Pin.prototype is always re-created
I think normally we'd use jsvCreateNewChild
when we create something like Pin.prototype
that is new. It's kind of a weak link, so if you're just reading then Pin.prototype
never gets created, but if you write to it then it gets added to Pin
. That could be missing, which might explain that issue.
However:
tests/test_typed_array_sort.js which seems to rather instantiate a generic Object instead of the Uint8Array it should.
Thanks - that's a nice easy test and it looks like one of the big problems. Just took a look and as far as I can see what's happening is normally jswSymbols_global
would have contained a link to gen_jswrap_Uint8Array_Uint8Array
which called jswrap_typedarray_constructor
. But for some reason build_jswrapper
is now ignoring the built-in constructor and is just creating gen_jswrap_global_Uint8Array
which is a placeholder which creates an object (rather than the Uint8Array var type we actually want).
I've really got to catch up on all the forum messsages (which is a lot less fun) but I'll try and have a think. Pretty sure the bug is entirely in build_jswrapper
though. Looking at the diffs we may also have to be careful - I see FLASH_SECT
is missing from at least one decl so this could break the ESP8266 builds too.
Also this https://github.com/opichals/Espruino/commit/4429e29a380af9c48e8718cdc773914958ec3ea5 looks wrong
jspNewObject("LCD", "Graphics");
should create an object called LCD
of type Graphics
and jspNewHiddenObject wouldn't, so that one needs changing. jspNewHiddenObject
mentions:
/* FIXME: We should try and make sure there aren't duplicates, so if
* the users adds a function to a prototype on a hidden object, it
* is available to any instance. But how can this be done? */
I think probably we should actually back out all the jspNewHiddenObject changes and just use jspNewObject
for now. It won't be as fast but it won't be any slower than it was before. Then we could look at switching to jspNewHiddenObject later once everything else is working ok and we have a fast solution for handling the different prototype issue.
Thanks for going through the changes. I am sorry I didn't have the capacity to look at this the last week.
I have just dropped the extra jspNewHiddenObject
things I added in the branch and dropped the extra console logs I used. And also added back the FLASH_SECT for ESP8266.
I'm just taking a look at this again - it's totally doing my head in.
I'm wondering whether there's a way to split this up into a series of smaller changes that we can make and test one after the other...
Specifically:
scripts/refactor_json_comments.js
from common.py
so it works 'on the fly' - so we don't have to make any changes to the Espruino libraries. I'm finding that having all the changes all over the libraries makes merging super painful, as well as just diffing to try and see what's going on. Ideally we'd get this in and working, and then at some point when we were happy we'd merge all the changes to JSON pretty much at the time we merged into masterThe rebase was a bit hairy for myself. I am still getting lost in what JsVar
and the JsVar's name meanings, sorry. At this point I am unsure I can really help with this. You as an author could definitely split the work and do a much better job to get it to run at least the same amount of stuff it does in the original branch.
Hey @gfwilliams! :)
I have rebased the https://github.com/opichals/Espruino/tree/real_proto on top of current master.
I was also able to trace the root cause of the tests/test_typed_array_sort.js
not using the Uint8Array instance. The constructor was not called because of the patch below was missing.
diff --git a/src/jsparse.c b/src/jsparse.c
index 1e6379990..91f2dd84e 100644
--- a/src/jsparse.c
+++ b/src/jsparse.c
@@ -497,7 +497,7 @@ NO_INLINE JsVar *jspeFunctionCall(JsVar *function, JsVar *functionName, JsVar *t
* a) args were pre-parsed and we have to populate the function
* b) we parse our own args, which is possibly better
*/
- if (jsvIsNativeFunction(function)) { // ------------------------------------- NATIVE
+ if (jsvIsNativeFunction(function) || jsvIsNativeObject(function)) { // ------------------------------------- NATIVE
unsigned int argPtrSize = 0;
int boundArgs = 0;
Brilliant, thanks for revisiting this! I just went to build it with DEBUG=1 make
and I get a bunch of errors. Am I missing something, or were you building for a very specific device?
What's the current state of this? It now runs and passes all the same tests the current build does?
To build the branch I do:
make clean
node ./scripts/refactor_json_comments.js
DEBUG=1 make
The tests results are currently the following:
./bin/espruino --test-all
...
espruino: 322 of 377 tests passed
espruino: FAILS:
espruino: tests/test_new_string_from_constructor.js
espruino: tests/test_promise7.js
espruino: tests/test_array_length.js
espruino: tests/test_ledflash.js
espruino: tests/test_mandelbrot.js
espruino: tests/test_long_string_to_name.js
espruino: tests/test_promise3.js
espruino: tests/test_new_nested2_FAIL.js
espruino: tests/test_array_remove.js
espruino: tests/test_arrays_treated_as_references_in_functions.js
espruino: tests/test_getter.js
espruino: tests/test_exception_8.js
espruino: tests/test_promise2.js
espruino: tests/test_setter.js
espruino: tests/test_dgram_recvBufferSize.js
espruino: tests/test_object_hasOwnProperty.js
espruino: tests/test_this_1.js
espruino: tests/test_crypto.js
espruino: tests/test_promise9.js
espruino: tests/test_builtin_prototype_expansion.js
espruino: tests/test_array_vars_are_references.js
espruino: tests/test_promise_all.js
espruino: tests/test_switch_case_construct.js
espruino: tests/test_promise.js
espruino: tests/test_number_tostring.js
espruino: tests/test_graphics_wrapString.js
espruino: tests/test_graphics_fonts.js
espruino: tests/test_arrays_with_non_array_indices.js
espruino: tests/test_array_reduce.js
espruino: tests/test_uncaughtException.js
espruino: tests/test_string_array_indices.js
espruino: tests/test_crypt_md5.js
espruino: tests/test_array_for_in_loop_iteration_order.js
espruino: tests/manual/bangle2_fill.js
espruino: tests/test_object_proto.js
espruino: tests/test_262_FAIL.js
espruino: tests/test_object_constructor_jgalaor2.js
espruino: tests/test_object_keys_as_vars.js
espruino: tests/test_pin_type.js
espruino: tests/test_array_splice.js
espruino: tests/test_spi_prototype_shared.js
espruino: tests/test_arrow_functions_this.js
espruino: tests/test_array_reverse.js
espruino: tests/test_graphics_drawStringImage.js
espruino: tests/test_arraybuffer_inheritance.js
espruino: tests/test_promise_exception.js
espruino: tests/test_array_big_indices.js
espruino: tests/test_delete.js
espruino: tests/test_object_getOwnPropertyNames.js
espruino: tests/test_number_constructor_FAIL.js
espruino: tests/test_E_getSizeOf.js
espruino: tests/test_isarray.js
espruino: tests/test_object_this.js
espruino: tests/test_object_zero_in_keys.js
espruino: tests/test_tensorflow.js
From what I can see this is still not the same as your original real_prototype_chain
problem count :(
Here the Array index assignments are not working properly:
tests/test_array_length.js
tests/test_array_reverse.js
Than there is something with the Object/Array instance prototype access where the tests/test_array_remove.js
is signalling:
Uncaught Error: Function "remove" not found!
at line 14 col 3
a.remove(2);
and e.g. tests/test_this_1.js
responding with:
Uncaught Error: Function "foo" not found!
at line 12 col 9
"Hello".foo(100);
Ok, thanks! I'll try and take a look at this tomorrow - it could be maybe only one or two things that are broken.
Ahh - got it! I had some un-committed files which were causing node ./scripts/refactor_json_comments.js
to fail, and then it broke when running it a second time because it doesn't like parsing files it's already worked on :)
Just a note for later - I think jswrap_banglejs_gps_character might be broken now
I did see /home/gw/workspace/Espruino/tests/test_this_2.js
failing with an assert because the this
var was a name though, so I'm not sure if we have exactly the same code still?
I've spent ages on this so far and it's a bit of a can of worms. But as far as I can see one big issue was if you do: Array.x = 5
then Array.x
gets set, but Array
is immediately thrown away because it's not referenced. Next time you ask for Array.x
it's not found.
As I understand it, there might have been some code to work around this in jspeFactorMember by ensuring that it stored a chain of 'NewChild' vars so that when the value was finally written with jsvReplaceWith
everything was remembered - but jsvReplaceWith
was never modified to cope with that...
And those changes caused a bunch of other trouble with Functions getting called with this
set to a Name rather than the actual object they belonged to.
Before I figured all that out I tried to undo those changes and got a much higher pass rate, but at the end of the day I think they were needed, and had to undo that work. The other option would have been to detect in jsvReplaceWith
if the destination was a NativeObject, find out its name, and then add it - but that in itself is tricky and wouldn't fix it at a deeper level like String.fromCharCode.x = 5
Anyway my diffs are below if it helps - I don't seem able to attach them:
diff --git a/src/jsparse.c b/src/jsparse.c
index f4f95c6f7..57bcda697 100644
--- a/src/jsparse.c
+++ b/src/jsparse.c
@@ -1079,16 +1078,15 @@ NO_INLINE JsVar *jspeFactorMember(JsVar *a, JsVar **parentResult) {
// if no child found, create a pointer to where it could be
// as we don't want to allocate it until it's written
JsVar *nameVar = jsvNewNameFromString(jslGetTokenValueAsString());
- child = jsvCreateNewChild(aVar, nameVar, 0);
+ child = jsvCreateNewChild(a, nameVar, 0);
jsvUnLock(nameVar);
} else {
// could have been a string...
jsExceptionHere(JSET_ERROR, "Cannot read property '%s' of %s", name, jsvIsUndefined(aVar) ? "undefined" : "null");
}
}
- jsvUnLock(parent);
- parent = aVar;
- jsvUnLock(a);
+ jsvUnLock2(parent, aVar);
+ parent = a;
a = child;
}
// skip over current token (we checked above that it was an ID or reserved word)
@@ -1267,8 +1265,10 @@ NO_INLINE JsVar *jspeFactorFunctionCall() {
bool parseArgs = lex->tk=='(';
a = jspeConstruct(func, funcName, parseArgs);
isConstructor = false; // don't treat subsequent brackets as constructors
- } else
+ } else {
+ parent = jsvSkipNameAndUnLock(parent); // 'parent' could be a NewChild now
a = jspeFunctionCall(func, funcName, parent, true, 0, 0);
+ }
jsvUnLock3(funcName, func, parent);
parent=0;
a = jspeFactorMember(a, &parent);
@@ -1464,7 +1464,7 @@ NO_INLINE JsVar *jspeFactorTypeOf() {
NO_INLINE JsVar *jspeFactorDelete() {
JSP_ASSERT_MATCH(LEX_R_DELETE);
JsVar *parent = 0;
- JsVar *a = jsvSkipNameAndUnLock(jspeFactorMember(jspeFactor(), &parent));
+ JsVar *a = jspeFactorMember(jspeFactor(), &parent);
JsVar *result = 0;
if (JSP_SHOULD_EXECUTE) {
bool ok = false;
diff --git a/src/jsvar.c b/src/jsvar.c
index 19e824c93..882b68ec7 100644
--- a/src/jsvar.c
+++ b/src/jsvar.c
@@ -2220,6 +2220,22 @@ void jsvReplaceWith(JsVar *dst, JsVar *src) {
if (jsvIsNewChild(dst)) {
// Get what it should have been a child of
JsVar *parent = jsvLock(jsvGetNextSibling(dst));
+ if (jsvIsNewChild(parent)) { // check if this NewChild links to another NewChild (eg Array.x=1 where Array was never used before)
+ // FIXME: what about great grandparents?
+ JsVar *grandparent = jsvLock(jsvGetNextSibling(parent));
+ // Remove the 'new child' flagging
+ jsvUnRef(grandparent);
+ jsvSetNextSibling(parent, 0);
+ jsvUnRef(grandparent);
+ jsvSetPrevSibling(parent, 0);
+ // Add to the grandparent
+ jsvAddName(grandparent, parent);
+ jsvUnLock(grandparent);
+ parent = jsvSkipNameAndUnLock(parent);
+ jsvRef(parent); // no idea why we need this
+ jsvRef(parent); // no idea why we need this
+ }
+ //jsiConsolePrintf("PARENT ");jsvTrace(parent,0);
if (!jsvIsString(parent)) {
// if we can't find a char in a string we still return a NewChild,
// but we can't add character back in
@@ -2378,8 +2394,10 @@ JsVar *jsvSkipNameWithParent(JsVar *a, bool repeat, JsVar *parent) {
}
#ifndef SAVE_ON_FLASH
if (jsvIsGetterOrSetter(pa)) {
- JsVar *getterParent = jsvIsNewChild(a)?jsvLock(jsvGetNextSibling(a)):0;
- JsVar *v = jsvExecuteGetter(getterParent?getterParent:parent, pa);
+ // FIXME tests/test_getter.js is now broken - might be related
+ JsVar *getterParent = jsvIsNewChild(a)?jsvSkipNameAndUnLock(jsvLock(jsvGetNextSibling(a))):0;
+ if (!getterParent) getterParent = jsvSkipName(parent);
+ JsVar *v = jsvExecuteGetter(getterParent, pa);
jsvUnLock2(getterParent,pa);
pa = v;
}
@@ -3827,10 +3844,14 @@ void _jsvTrace(JsVar *var, int indent, JsVar *baseVar, int level) {
}
if (jsvIsNewChild(var)) {
- jsiConsolePrint("NewChild PARENT:");
+ jsiConsolePrint("NewChild\n");
+ for (i=0;i<indent;i++) jsiConsolePrint(" ");
+ jsiConsolePrint(" PARENT: ");
JsVar *parent = jsvGetAddressOf(jsvGetNextSibling(var));
_jsvTrace(parent, indent+2, baseVar, level+1);
- jsiConsolePrint("CHILD: ");
+ jsiConsolePrint("\n");
+ for (i=0;i<indent;i++) jsiConsolePrint(" ");
+ jsiConsolePrint(" CHILD: ");
} else if (jsvIsName(var)) jsiConsolePrint("Name ");
char endBracket = ' ';
diff --git a/src/jswrap_object.c b/src/jswrap_object.c
index a05914d13..1f098a6ec 100644
--- a/src/jswrap_object.c
+++ b/src/jswrap_object.c
@@ -24,25 +24,33 @@
#endif//__MINGW32__
/*JSON{
- "type" : "class",
- "class" : "Object",
+ "type" : "variable",
+ "name" : "global",
+ "memberOf" : "global",
+ "generate_full" : "jsvLockAgain(execInfo.root)",
+ "return" : ["JsVar","An Object"]
+}
+This references the global scope. For instance `global.print == print`
+*/
+/*JSON{
+ "type" : "object",
+ "name" : "Object",
"memberOf" : "global",
"check" : "jsvIsObject(var)"
}
This is the built-in class for Objects
With that I'm still only seeing 308 passes.
I realised that in normal Espruino we just add any object that is a child of the global object automatically and there is no need for the stuff in ReplaceWith- so actually my original hunch was probably right. On Monday I'll have a go at making it more like the original code - which should increase the pass rate significantly
Getting there now! 368 tests passing - only a few that aren't.
./scripts/refactor_json_comments.js
doesn't just remove entries (eg sortorder) it doesn't understand and warns of problemsjswCreateFromSymbolTable
, when presumably we could just skip thosetest_object_getOwnPropertyNames.js
getOwnPropertyNames returns stuff it shouldn't [].__proto__
doesn't work - an array instance returns jswSymbols_Array, which has prototype
not __proto__
- so should []
actually return jswSymbols_Array_prototype
, or do we need a third symbol table entry for Array instances? I can't quite get my head around that yet(jsvIsNativeObject(proto) && jsvIsNativeObject(bproto) && proto->varData.nativeObject==bproto->varData.nativeObject)
check I just addedI have my changes on a branch: https://github.com/espruino/Espruino/compare/master...gfwilliams:Espruino:real_proto_opichals
Note that I have one commit with the result of refactor_json_comments in just so I can avoid diffs all over the place - when merging I'll remove that and then do one big commit based on the most recent changes.
As of now, the only test fail that doesn't fail on the main repo is:
... so looking quite good. As mentioned in https://github.com/espruino/Espruino/issues/2181#issuecomment-1548146740 there are still a few outstanding things though
Mentioned in:
https://github.com/espruino/Espruino/issues/1348 https://github.com/espruino/Espruino/issues/737
Basically this branch avoids the hacky handling of built-in objects, and implements prototypes properly (rather than as separate to objects). It should also allow much faster lookup of builtins (since the current system uses a large if..then..else)