forked from WebKit/WebKit
-
Notifications
You must be signed in to change notification settings - Fork 55
Add resolve_and_get_from_scope (fused resolve_scope + get_from_scope) #516
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Jarred-Sumner
wants to merge
2
commits into
main
Choose a base branch
from
claude/resolve-and-get-from-scope
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,141 @@ | ||
| // resolve_scope + get_from_scope fused into resolve_and_get_from_scope: the cases that differ in how they resolve. | ||
|
|
||
| function shouldBe(actual, expected) { | ||
| if (actual !== expected) | ||
| throw new Error(`bad value: ${String(actual)}, expected ${String(expected)}`); | ||
| } | ||
| function shouldThrow(func, errorType, message) { | ||
| let error; | ||
| try { | ||
| func(); | ||
| } catch (e) { | ||
| error = e; | ||
| } | ||
| if (!(error instanceof errorType)) | ||
| throw new Error(`expected ${errorType.name}, got ${String(error)}`); | ||
| if (message !== undefined && error.message !== message) | ||
| throw new Error(`bad message: ${error.message}`); | ||
| } | ||
|
|
||
| // GlobalVar, GlobalLexicalVar, GlobalProperty. | ||
| var globalVar = 1; | ||
| let globalLet = 2; | ||
| globalThis.globalProp = 3; | ||
| function readGlobals() { return globalVar + globalLet + globalProp; } | ||
| for (let i = 0; i < 1e4; ++i) | ||
| shouldBe(readGlobals(), 6); | ||
| globalVar = 10; | ||
| globalLet = 20; | ||
| globalThis.globalProp = 30; | ||
| shouldBe(readGlobals(), 60); | ||
|
|
||
| // A global that does not exist yet: UnresolvedProperty, then GlobalProperty once defined. | ||
| function readLate() { return lateGlobal; } | ||
| shouldThrow(readLate, ReferenceError); | ||
| globalThis.lateGlobal = 7; | ||
| for (let i = 0; i < 1e4; ++i) | ||
| shouldBe(readLate(), 7); | ||
| delete globalThis.lateGlobal; | ||
| shouldThrow(readLate, ReferenceError); | ||
|
|
||
| // typeof on an undeclared name must not throw. | ||
| function typeofUndeclared() { return typeof neverDeclaredAnywhere; } | ||
| for (let i = 0; i < 1e4; ++i) | ||
| shouldBe(typeofUndeclared(), "undefined"); | ||
|
|
||
| // A global lexical in its TDZ. | ||
| function readTDZ() { return tdzLet; } | ||
| shouldThrow(readTDZ, ReferenceError); | ||
| let tdzLet = 5; | ||
| shouldBe(readTDZ(), 5); | ||
|
|
||
| // ClosureVar through several scope levels. | ||
| function outer() { | ||
| let a = 1; | ||
| return function middle() { | ||
| let b = 2; | ||
| return function inner() { | ||
| return a + b; | ||
| }; | ||
| }(); | ||
| } | ||
| { | ||
| const inner = outer(); | ||
| for (let i = 0; i < 1e4; ++i) | ||
| shouldBe(inner(), 3); | ||
| } | ||
|
|
||
| // A bare call resolved through a scope passes undefined as `this`: sloppy callee sees globalThis, strict sees undefined. | ||
| function sloppyThis() { return this; } | ||
| function strictThis() { "use strict"; return this; } | ||
| function callThem() { return [sloppyThis(), strictThis()]; } | ||
| for (let i = 0; i < 1e4; ++i) { | ||
| const [sloppy, strict] = callThem(); | ||
| shouldBe(sloppy, globalThis); | ||
| shouldBe(strict, undefined); | ||
| } | ||
| { | ||
| let closureSloppy = function () { return this; }; | ||
| let closureStrict = function () { "use strict"; return this; }; | ||
| function callClosures() { return [closureSloppy(), closureStrict()]; } | ||
| for (let i = 0; i < 1e4; ++i) { | ||
| const [sloppy, strict] = callClosures(); | ||
| shouldBe(sloppy, globalThis); | ||
| shouldBe(strict, undefined); | ||
| } | ||
| } | ||
|
|
||
| // Tagged templates resolved through a scope likewise. | ||
| function tag(strings) { return [this, strings[0]]; } | ||
| function callTag() { return tag`x`; } | ||
| for (let i = 0; i < 1e4; ++i) { | ||
| const [thisValue, str] = callTag(); | ||
| shouldBe(thisValue, globalThis); | ||
| shouldBe(str, "x"); | ||
| } | ||
|
|
||
| // Inside `with`, resolution is dynamic and stays unfused: the with object wins and is `this` for calls. | ||
| function withRead(obj) { | ||
| with (obj) | ||
| return [globalVar, f()]; | ||
| } | ||
| { | ||
| const obj = { globalVar: 99, f() { return this; } }; | ||
| for (let i = 0; i < 1e4; ++i) { | ||
| const [value, thisValue] = withRead(obj); | ||
| shouldBe(value, 99); | ||
| shouldBe(thisValue, obj); | ||
| } | ||
| const [value, thisValue] = withRead({ f() { return this; } }); | ||
| shouldBe(value, 10); | ||
| } | ||
|
|
||
| // Sloppy direct eval injecting a var flips the *WithVarInjectionChecks types. | ||
| function injected() { | ||
| eval("var injectedVar = 1"); | ||
| function read() { return injectedVar; } | ||
| for (let i = 0; i < 1e4; ++i) | ||
| shouldBe(read(), 1); | ||
| eval("var injectedVar = 2"); | ||
| shouldBe(read(), 2); | ||
| } | ||
| injected(); | ||
|
|
||
| // A read of a global whose lexical binding epoch changes after caching. | ||
| function readShadowed() { return shadowedLater; } | ||
| globalThis.shadowedLater = "prop"; | ||
| for (let i = 0; i < 1e4; ++i) | ||
| shouldBe(readShadowed(), "prop"); | ||
|
|
||
| // A function nested inside `with` resolves through the with object at runtime even though its own scope chain is static. | ||
| { | ||
| const obj = { h() { return this; }, nestedX: 1 }; | ||
| function makeNested() { with (obj) { return function nested() { return [h(), nestedX, typeof nestedX]; }; } } | ||
| const nested = makeNested(); | ||
| for (let i = 0; i < 1e4; ++i) { | ||
| const [thisValue, value, type] = nested(); | ||
| shouldBe(thisValue, obj); | ||
| shouldBe(value, 1); | ||
| shouldBe(type, "number"); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟡 Nit: this new stress test hardcodes
1e4in 11 loop bounds (lines 25, 36, 43, 64, 72, 81, 91, 104, 117, 127, 135); JSTests/README.md rule 2 requires new tests to usetestLoopCountso the harness can scale iterations per configuration. Replace1e4withtestLoopCount.Extended reasoning...
What the issue is
JSTests/stress/resolve-and-get-from-scope.jsis a newly-added test file, and every one of its tier-up loops uses a hardcoded1e4bound:This appears at 11 sites: lines 25, 36, 43, 64, 72, 81, 91, 104, 117, 127, and 135.
Why this violates a repository requirement
JSTests/README.md:20(pulled into the directory-scoped instructions viaJSTests/CLAUDE.md) states, under "New tests are required to adhere to the following rules":This is not a stylistic suggestion — it is listed as a required rule for newly-added test files. The convention is widely followed: ~2200+ files under
JSTests/referencetestLoopCount.Why nothing else prevents it
The test happens to work with
1e4because that is roughly the default tier-up threshold, but the point oftestLoopCountis that thejscshell sets it per configuration: no-JIT / cloop configurations set it low so the test exits quickly instead of wasting 10 000 iterations that will never tier up, while eager-tier configurations may set it higher/lower as needed. A hardcoded1e4defeats that scaling in every configuration this file is run under.Step-by-step
run-jsc-stress-tests JSTests/stressrunsresolve-and-get-from-scope.jsunder many configurations (e.g..no-llint,.no-cjit,.ftl-eager-no-cjit, cloop, etc.).testLoopCountto the appropriate iteration count for that configuration.1e4times.1e4may be far more than needed to reach FTL. The test still passes — this is purely a harness-integration/convention violation, not a correctness bug.Impact
No runtime correctness impact — the test produces the same pass/fail result either way. The impact is on test-suite hygiene: it violates a documented repository requirement for new tests and won't scale iteration count with the harness.
Fix
Replace each
1e4withtestLoopCount:(applied to all 11 loop bounds listed above).