Skip to content

Commit c882db5

Browse files
authored
Recognize client-local native values in optimizer locality analysis (#1221)
1 parent ac1b93e commit c882db5

2 files changed

Lines changed: 188 additions & 9 deletions

File tree

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/intermediatelang/optimizer/LocalPlayerContextAnalyzer.java

Lines changed: 52 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,57 @@
1717

1818
/**
1919
* Conservative, flow-insensitive analysis for values and functions which may
20-
* depend on {@code GetLocalPlayer()}.
20+
* depend on client-local native values such as {@code GetLocalPlayer()} or
21+
* camera state.
2122
*
2223
* Optimizers use this analysis as a barrier. False positives only cost an
2324
* optimization; false negatives could move synchronized work into a
2425
* client-local control-flow region.
2526
*/
2627
public final class LocalPlayerContextAnalyzer {
2728

29+
/**
30+
* Native return values which may differ between clients during the same
31+
* synchronized execution without requiring user code to mutate local state.
32+
* Event responses are synchronized, while handles and UI/audio/visual state
33+
* made local by user code remain the user's responsibility.
34+
*/
35+
private static final Set<String> CLIENT_LOCAL_VALUE_SOURCES = Set.of(
36+
// Player identity and values explicitly documented as asynchronous.
37+
"GetLocalPlayer",
38+
"GetLocationZ",
39+
40+
// Camera state belongs to the local client's camera.
41+
"GetCameraMargin",
42+
"GetCameraBoundMinX",
43+
"GetCameraBoundMinY",
44+
"GetCameraBoundMaxX",
45+
"GetCameraBoundMaxY",
46+
"GetCameraField",
47+
"GetCameraTargetPositionX",
48+
"GetCameraTargetPositionY",
49+
"GetCameraTargetPositionZ",
50+
"GetCameraTargetPositionLoc",
51+
"GetCameraEyePositionX",
52+
"GetCameraEyePositionY",
53+
"GetCameraEyePositionZ",
54+
"GetCameraEyePositionLoc",
55+
56+
// Localized data may vary with the client's language.
57+
"GetLocalizedString",
58+
"GetLocalizedHotkey",
59+
"GetObjectName",
60+
61+
// Reforged client-local world and client state.
62+
"BlzGetLocalUnitZ",
63+
"BlzGetUnitZ",
64+
"BlzGetLocalClientWidth",
65+
"BlzGetLocalClientHeight",
66+
"BlzIsLocalClientActive",
67+
"BlzGetMouseFocusUnit",
68+
"BlzGetLocale"
69+
);
70+
2871
private final Set<ImVar> localPlayerDependentVars =
2972
Collections.newSetFromMap(new IdentityHashMap<>());
3073
private final Set<ImFunction> localPlayerDependentReturns =
@@ -75,7 +118,7 @@ public boolean isLocalPlayerDependent(Element element) {
75118
}
76119
if (element instanceof ImFunctionCall) {
77120
ImFunctionCall call = (ImFunctionCall) element;
78-
if (isGetLocalPlayer(call.getFunc())
121+
if (isClientLocalValueSource(call.getFunc())
79122
|| localPlayerDependentReturns.contains(call.getFunc())) {
80123
return true;
81124
}
@@ -98,12 +141,12 @@ public boolean isLocalPlayerDependent(Element element) {
98141

99142
public boolean functionUsesLocalPlayer(ImFunction function) {
100143
return function != null
101-
&& (isGetLocalPlayer(function) || functionsUsingLocalPlayer.contains(function));
144+
&& (isClientLocalValueSource(function) || functionsUsingLocalPlayer.contains(function));
102145
}
103146

104147
public boolean functionInliningIsLocalPlayerSensitive(ImFunction function) {
105148
return function != null
106-
&& (isGetLocalPlayer(function)
149+
&& (isClientLocalValueSource(function)
107150
|| functionsDirectlyUsingLocalPlayer.contains(function)
108151
|| localPlayerDependentReturns.contains(function));
109152
}
@@ -113,15 +156,15 @@ public boolean isLocalPlayerDependent(ImVar variable) {
113156
}
114157

115158
public boolean isLocalPlayerSource(ImFunction function) {
116-
return isGetLocalPlayer(function);
159+
return isClientLocalValueSource(function);
117160
}
118161

119162
private void analyze(ImProg prog) {
120163
sourceFacts.add(unknownDispatchSource);
121164
for (ImFunction function : ImHelper.calculateFunctionsOfProg(prog)) {
122165
returnFact(function);
123166
useFact(function);
124-
if (isGetLocalPlayer(function)) {
167+
if (isClientLocalValueSource(function)) {
125168
addLocalPlayerSource(function);
126169
} else if (!function.isNative()) {
127170
indexElement(function.getBody(), function, entryControlFact(function));
@@ -298,7 +341,7 @@ private void indexFunctionCall(ImFunctionCall call, ImFunction owner, Object con
298341
if (!called.isNative()) {
299342
addEnclosingControlDependency(controlContext, entryControlFact(called));
300343
}
301-
if (isGetLocalPlayer(called)) {
344+
if (isClientLocalValueSource(called)) {
302345
functionsDirectlyUsingLocalPlayer.add(owner);
303346
addLocalPlayerSource(called);
304347
}
@@ -508,9 +551,9 @@ private Fact(FactKind kind, Object subject) {
508551
}
509552
}
510553

511-
private static boolean isGetLocalPlayer(ImFunction function) {
554+
private static boolean isClientLocalValueSource(ImFunction function) {
512555
return function != null
513556
&& function.isNative()
514-
&& "GetLocalPlayer".equals(function.getName());
557+
&& CLIENT_LOCAL_VALUE_SOURCES.contains(function.getName());
515558
}
516559
}

de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/OptimizerTests.java

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2158,6 +2158,142 @@ public void functionUsingGetLocalPlayerMustNotBeInlined() throws Exception {
21582158
"transitive GetLocalPlayer wrappers must remain explicit calls");
21592159
}
21602160

2161+
@Test
2162+
public void branchMergerMustNotHoistAcrossClientLocalConditions() throws Exception {
2163+
test().lines(
2164+
"type unit extends handle",
2165+
"package test",
2166+
"@extern native GetCameraTargetPositionX() returns real",
2167+
"@extern native BlzGetUnitZ(unit whichUnit) returns real",
2168+
"@extern native BlzIsLocalClientActive() returns boolean",
2169+
"native getUnit() returns unit",
2170+
"native print(integer i)",
2171+
"integer cameraResult = 0",
2172+
"integer unitResult = 0",
2173+
"integer activeClientResult = 0",
2174+
"init",
2175+
" real cameraX = GetCameraTargetPositionX()",
2176+
" if cameraX > 0.",
2177+
" cameraResult = 41",
2178+
" else",
2179+
" cameraResult = 41",
2180+
" real unitZ = BlzGetUnitZ(getUnit())",
2181+
" if unitZ > 0.",
2182+
" unitResult = 43",
2183+
" else",
2184+
" unitResult = 43",
2185+
" boolean activeClient = BlzIsLocalClientActive()",
2186+
" if activeClient",
2187+
" activeClientResult = 53",
2188+
" else",
2189+
" activeClientResult = 53",
2190+
" print(cameraResult)",
2191+
" print(unitResult)",
2192+
" print(activeClientResult)"
2193+
);
2194+
2195+
String optimized = Files.toString(
2196+
new File("test-output/OptimizerTests_branchMergerMustNotHoistAcrossClientLocalConditions_opt.j"),
2197+
Charsets.UTF_8);
2198+
assertTrue(countOccurrences(optimized, "test_cameraResult = 41") >= 2,
2199+
"statements must not be hoisted across a client-local camera condition");
2200+
assertTrue(countOccurrences(optimized, "test_unitResult = 43") >= 2,
2201+
"statements must not be hoisted across a client-local unit Z condition");
2202+
assertTrue(countOccurrences(optimized, "test_activeClientResult = 53") >= 2,
2203+
"statements must not be hoisted across local-client activity state");
2204+
}
2205+
2206+
@Test
2207+
public void clientLocalNativeValuesAreLocalitySources() {
2208+
java.util.Set<String> localValueSources = new java.util.LinkedHashSet<>(java.util.Arrays.asList(
2209+
"GetLocalPlayer",
2210+
"GetLocationZ",
2211+
"GetCameraMargin",
2212+
"GetCameraBoundMinX",
2213+
"GetCameraBoundMinY",
2214+
"GetCameraBoundMaxX",
2215+
"GetCameraBoundMaxY",
2216+
"GetCameraField",
2217+
"GetCameraTargetPositionX",
2218+
"GetCameraTargetPositionY",
2219+
"GetCameraTargetPositionZ",
2220+
"GetCameraTargetPositionLoc",
2221+
"GetCameraEyePositionX",
2222+
"GetCameraEyePositionY",
2223+
"GetCameraEyePositionZ",
2224+
"GetCameraEyePositionLoc",
2225+
"GetLocalizedString",
2226+
"GetLocalizedHotkey",
2227+
"GetObjectName",
2228+
"BlzGetLocalUnitZ",
2229+
"BlzGetUnitZ",
2230+
"BlzGetLocalClientWidth",
2231+
"BlzGetLocalClientHeight",
2232+
"BlzIsLocalClientActive",
2233+
"BlzGetMouseFocusUnit",
2234+
"BlzGetLocale"
2235+
));
2236+
java.util.Set<String> intentionallyExcludedSources = new java.util.LinkedHashSet<>(java.util.Arrays.asList(
2237+
"BlzGetTriggerPlayerMouseX",
2238+
"BlzGetTriggerPlayerKey",
2239+
"BlzGetTriggerFrameValue",
2240+
"BlzFrameIsVisible",
2241+
"BlzGetLocalSpecialEffectX",
2242+
"AddLightning",
2243+
"MoveLightning",
2244+
"LoadEffectHandle",
2245+
"LoadLightningHandle",
2246+
"LoadFrameHandle",
2247+
"GetSoundIsPlaying",
2248+
"BlzIsSelectionEnabled"
2249+
));
2250+
Element trace = Ast.NoExpr();
2251+
ImFunctions functions = JassIm.ImFunctions();
2252+
java.util.Map<String, ImFunction> functionsByName = new java.util.LinkedHashMap<>();
2253+
for (String name : localValueSources) {
2254+
ImFunction nativeFunction = nativeIntFunction(trace, name);
2255+
functions.add(nativeFunction);
2256+
functionsByName.put(name, nativeFunction);
2257+
}
2258+
for (String name : intentionallyExcludedSources) {
2259+
ImFunction nativeFunction = nativeIntFunction(trace, name);
2260+
functions.add(nativeFunction);
2261+
functionsByName.put(name, nativeFunction);
2262+
}
2263+
ImProg prog = JassIm.ImProg(
2264+
trace,
2265+
JassIm.ImVars(),
2266+
functions,
2267+
JassIm.ImMethods(),
2268+
JassIm.ImClasses(),
2269+
JassIm.ImTypeClassFuncs(),
2270+
new java.util.HashMap<>()
2271+
);
2272+
LocalPlayerContextAnalyzer analyzer = new LocalPlayerContextAnalyzer(prog);
2273+
2274+
for (String name : localValueSources) {
2275+
assertTrue(analyzer.isLocalPlayerSource(functionsByName.get(name)),
2276+
name + " must be treated as a client-local value source");
2277+
}
2278+
for (String name : intentionallyExcludedSources) {
2279+
assertFalse(analyzer.isLocalPlayerSource(functionsByName.get(name)),
2280+
name + " is synchronized event data or user-managed local state");
2281+
}
2282+
}
2283+
2284+
private static ImFunction nativeIntFunction(Element trace, String name) {
2285+
return JassIm.ImFunction(
2286+
trace,
2287+
name,
2288+
JassIm.ImTypeVars(),
2289+
JassIm.ImVars(),
2290+
TypesHelper.imInt(),
2291+
JassIm.ImVars(),
2292+
JassIm.ImStmts(),
2293+
Collections.singletonList(FunctionFlagEnum.IS_NATIVE)
2294+
);
2295+
}
2296+
21612297
@Test(timeOut = 10_000)
21622298
public void deeplyNestedIndependentCallsDoNotCauseExponentialLocalPlayerAnalysis() {
21632299
String nestedCall = "Player(0)";

0 commit comments

Comments
 (0)