Skip to content

Commit 990ed71

Browse files
committed
Version 1.0.3, complete mappings & performance improvments
Read jars into Hypo in a single pass and re-use it for subsequent pages. Also, fix jars using Hypo's executor service in parallel.
1 parent ccf3c87 commit 990ed71

9 files changed

+253
-200
lines changed

build.gradle.kts

+2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ dependencies {
2222
implementation(platform(libs.hypo.platform))
2323
implementation(libs.bundles.hypo)
2424

25+
implementation(libs.lorenz)
26+
2527
implementation(libs.feather.core)
2628
implementation(libs.feather.gson)
2729

gradle.properties

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
group = io.papermc.codebook
2-
version = 1.0.3-SNAPSHOT
2+
version = 1.0.3

gradle/libs.versions.toml

+5-2
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,15 @@ asm-tree = { module = "org.ow2.asm:asm-tree", version.ref = "asm" }
4141
unpick-format = { module = "net.fabricmc.unpick:unpick-format-utils", version.ref = "unpick" }
4242
unpick-cli = { module = "net.fabricmc.unpick:unpick-cli", version.ref = "unpick" }
4343

44-
hypo-platform = "dev.denwav.hypo:hypo-platform:2.0.0"
44+
hypo-platform = "dev.denwav.hypo:hypo-platform:2.1.0"
4545
hypo-model = { module = "dev.denwav.hypo:hypo-model" }
4646
hypo-core = { module = "dev.denwav.hypo:hypo-core" }
4747
hypo-asm = { module = "dev.denwav.hypo:hypo-asm" }
4848
hypo-hydrate = { module = "dev.denwav.hypo:hypo-hydrate" }
4949
hypo-asm-hydrate = { module = "dev.denwav.hypo:hypo-asm-hydrate" }
50+
hypo-mappings = { module = "dev.denwav.hypo:hypo-mappings" }
51+
52+
lorenz = "org.cadixdev:lorenz:0.5.8"
5053

5154
feather-core = { module = "org.parchmentmc:feather", version.ref = "feather" }
5255
feather-gson = { module = "org.parchmentmc.feather:io-gson", version.ref = "feather" }
@@ -63,4 +66,4 @@ mockito-junit = { module = "org.mockito:mockito-junit-jupiter", version.ref = "m
6366

6467
[bundles]
6568
asm = ["asm", "asm-util", "asm-tree"]
66-
hypo = ["hypo-model", "hypo-core", "hypo-asm", "hypo-hydrate", "hypo-asm-hydrate"]
69+
hypo = ["hypo-model", "hypo-core", "hypo-asm", "hypo-hydrate", "hypo-asm-hydrate", "hypo-mappings"]

src/main/java/io/papermc/codebook/CodeBook.java

+15-2
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,18 @@
2727
import com.google.inject.Injector;
2828
import com.google.inject.Module;
2929
import com.google.inject.util.Providers;
30+
import dev.denwav.hypo.asm.AsmOutputWriter;
31+
import dev.denwav.hypo.core.HypoContext;
3032
import io.papermc.codebook.config.CodeBookClasspathResource;
3133
import io.papermc.codebook.config.CodeBookContext;
3234
import io.papermc.codebook.config.CodeBookJarInput;
3335
import io.papermc.codebook.config.CodeBookResource;
36+
import io.papermc.codebook.exceptions.UnexpectedException;
3437
import io.papermc.codebook.exceptions.UserErrorException;
3538
import io.papermc.codebook.pages.CodeBookPage;
3639
import io.papermc.codebook.pages.ExtractVanillaJarPage;
3740
import io.papermc.codebook.pages.FixJarPage;
41+
import io.papermc.codebook.pages.InspectJarPage;
3842
import io.papermc.codebook.pages.RemapJarPage;
3943
import io.papermc.codebook.pages.RemapLvtPage;
4044
import io.papermc.codebook.pages.UnpickPage;
@@ -71,16 +75,25 @@ private void exec(final Path tempDir) {
7175
final var book = List.of(
7276
ExtractVanillaJarPage.class,
7377
RemapJarPage.class,
74-
FixJarPage.class,
7578
UnpickPage.class,
79+
InspectJarPage.class,
80+
FixJarPage.class,
7681
RemapLvtPage.class);
7782

7883
Module module = this.createInitialModule(tempDir);
7984
for (final var page : book) {
8085
module = injector(module).getInstance(page).exec(module);
8186
}
8287

83-
final Path resultJar = injector(module).getInstance(CodeBookPage.InputJar.KEY);
88+
final HypoContext context = injector(module).getInstance(CodeBookPage.Hypo.KEY);
89+
final Path resultJar;
90+
try (context) {
91+
resultJar = tempDir.resolve("final_output.jar");
92+
AsmOutputWriter.to(resultJar).write(context);
93+
} catch (final Exception e) {
94+
throw new UnexpectedException("Failed to write output file", e);
95+
}
96+
8497
IOUtil.move(resultJar, this.ctx.outputJar());
8598
}
8699

src/main/java/io/papermc/codebook/lvt/LvtNamer.java

+29-64
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,6 @@
2929
import dev.denwav.hypo.hydrate.generic.HypoHydration;
3030
import dev.denwav.hypo.hydrate.generic.MethodClosure;
3131
import dev.denwav.hypo.model.data.ClassData;
32-
import dev.denwav.hypo.model.data.ClassKind;
33-
import dev.denwav.hypo.model.data.FieldData;
3432
import dev.denwav.hypo.model.data.HypoKey;
3533
import dev.denwav.hypo.model.data.MethodData;
3634
import dev.denwav.hypo.model.data.types.JvmType;
@@ -40,25 +38,29 @@
4038
import java.util.HashSet;
4139
import java.util.List;
4240
import java.util.Map;
41+
import java.util.Optional;
4342
import java.util.Set;
4443
import java.util.concurrent.ConcurrentHashMap;
4544
import java.util.concurrent.atomic.AtomicInteger;
45+
import org.cadixdev.lorenz.MappingSet;
46+
import org.cadixdev.lorenz.model.Mapping;
47+
import org.cadixdev.lorenz.model.MethodMapping;
4648
import org.checkerframework.checker.nullness.qual.Nullable;
4749
import org.objectweb.asm.tree.LocalVariableNode;
4850
import org.objectweb.asm.tree.MethodNode;
4951
import org.objectweb.asm.tree.ParameterNode;
50-
import org.parchmentmc.feather.mapping.MappingDataContainer;
5152

5253
public class LvtNamer {
5354

5455
public static final HypoKey<Set<String>> SCOPED_NAMES = HypoKey.create("Scoped Names");
5556

56-
private final MappingDataContainer mappings;
5757
private final HypoContext context;
58+
private final MappingSet mappings;
5859
private final LvtSuggester lvtSuggester;
60+
5961
public final Map<String, AtomicInteger> missedNameSuggestions = new ConcurrentHashMap<>();
6062

61-
public LvtNamer(final HypoContext context, final MappingDataContainer mappings) throws IOException {
63+
public LvtNamer(final HypoContext context, final MappingSet mappings) throws IOException {
6264
this.mappings = mappings;
6365
this.context = context;
6466
this.lvtSuggester = new LvtSuggester(context, this.missedNameSuggestions);
@@ -74,6 +76,10 @@ public void fillNames(final MethodData method) throws IOException {
7476
final @Nullable Set<String> names = method.get(SCOPED_NAMES);
7577
if (names != null) {
7678
// If scoped names is already filled out, this method has already been visited
79+
// We don't need to be concerned with a single thread being processed by multiple threads,
80+
// it can happen, but that will simply result in the same output, which is still consistent.
81+
// This is fast enough that a little bit of duplicate work is acceptable, not worth trying
82+
// to prevent it.
7783
return;
7884
}
7985

@@ -131,13 +137,9 @@ public void fillNames(final MethodData method) throws IOException {
131137
this.fillNames(outerMethod);
132138
}
133139

134-
final MappingDataContainer.@Nullable MethodData methodMapping;
135-
final MappingDataContainer.@Nullable ClassData classMapping = this.mappings.getClass(parentClass.name());
136-
if (classMapping == null) {
137-
methodMapping = null;
138-
} else {
139-
methodMapping = classMapping.getMethod(method.name(), method.descriptorText());
140-
}
140+
final Optional<MethodMapping> methodMapping = this.mappings
141+
.getClassMapping(parentClass.name())
142+
.flatMap(c -> c.getMethodMapping(method.name(), method.descriptorText()));
141143

142144
// We inherit names from our outer scope, if it exists. These names will be included in our scope for any
143145
// potential inner scopes (other nested lambdas or local classes) that are present in this method too
@@ -162,12 +164,12 @@ public void fillNames(final MethodData method) throws IOException {
162164

163165
for (int i = 0; i < paramCount; i++) {
164166
// always (i + 1) because abstract methods are never static
165-
final MappingDataContainer.@Nullable ParameterData paramMapping =
166-
methodMapping != null ? methodMapping.getParameter((byte) (i + 1)) : null;
167-
@Nullable String paramName = null;
168-
if (paramMapping != null) {
169-
paramName = paramMapping.getName();
170-
}
167+
final int fi = i;
168+
@Nullable
169+
String paramName = methodMapping
170+
.flatMap(m -> m.getParameterMapping(fi + 1))
171+
.map(Mapping::getDeobfuscatedName)
172+
.orElse(null);
171173

172174
if (paramName == null) {
173175
paramName = LvtTypeSuggester.suggestNameFromType(this.context, paramTypes.get(i));
@@ -267,31 +269,18 @@ public void fillNames(final MethodData method) throws IOException {
267269
}
268270
}
269271

272+
final @Nullable String paramName = methodMapping
273+
.flatMap(m -> m.getParameterMapping(lvt.index))
274+
.map(Mapping::getDeobfuscatedName)
275+
.orElse(null);
276+
270277
@Nullable String mappedName = null;
271-
if (methodMapping != null) {
272-
final MappingDataContainer.@Nullable ParameterData paramMapping =
273-
methodMapping.getParameter((byte) lvt.index);
274-
if (paramMapping != null) {
275-
final @Nullable String paramName = paramMapping.getName();
276-
if (paramName != null) {
277-
mappedName = LvtSuggester.determineFinalName(paramName, scopedNames);
278-
}
279-
}
278+
if (paramName != null) {
279+
mappedName = LvtSuggester.determineFinalName(paramName, scopedNames);
280280
}
281281

282-
final String selectedName;
283-
if (mappedName != null) {
284-
selectedName = mappedName;
285-
} else {
286-
@Nullable String name = null;
287-
if (parentClass.kind() == ClassKind.RECORD && method.name().equals("<init>")) {
288-
name = this.remapRecordParameter(lvt, method);
289-
}
290-
if (name == null) {
291-
name = this.lvtSuggester.suggestName(node, lvt, scopedNames);
292-
}
293-
selectedName = name;
294-
}
282+
final String selectedName =
283+
mappedName != null ? mappedName : this.lvtSuggester.suggestName(node, lvt, scopedNames);
295284

296285
lvt.name = selectedName;
297286
usedNames[usedNameIndex++] = new UsedLvtName(lvt.name, lvt.desc, lvt.index);
@@ -308,30 +297,6 @@ public void fillNames(final MethodData method) throws IOException {
308297

309298
private record UsedLvtName(String name, String desc, int index) {}
310299

311-
private @Nullable String remapRecordParameter(final LocalVariableNode lvt, final MethodData method) {
312-
// use record component names for primary constructor
313-
final int paramIndex = fromLvtToParamIndex(lvt.index, method);
314-
if (paramIndex == -1) {
315-
return null;
316-
}
317-
final @Nullable List<FieldData> comp = method.parentClass().recordComponents();
318-
if (comp == null) {
319-
throw new IllegalStateException("No record components found on record");
320-
}
321-
322-
if (comp.size() != method.params().size()) {
323-
return null;
324-
} else {
325-
for (int i = 0; i < comp.size(); i++) {
326-
if (!comp.get(i).fieldType().equals(method.param(i))) {
327-
return null;
328-
}
329-
}
330-
}
331-
332-
return comp.get(paramIndex).name();
333-
}
334-
335300
private static int find(final int[] array, final int value) {
336301
return find(array, value, array.length);
337302
}

src/main/java/io/papermc/codebook/pages/CodeBookPage.java

+10
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import com.google.inject.binder.LinkedBindingBuilder;
3030
import com.google.inject.util.Modules;
3131
import com.google.inject.util.Providers;
32+
import dev.denwav.hypo.core.HypoContext;
3233
import io.papermc.codebook.config.CodeBookContext;
3334
import jakarta.inject.Qualifier;
3435
import java.lang.annotation.ElementType;
@@ -38,6 +39,7 @@
3839
import java.nio.file.Path;
3940
import java.util.IdentityHashMap;
4041
import java.util.List;
42+
import org.cadixdev.lorenz.MappingSet;
4143
import org.checkerframework.checker.nullness.qual.Nullable;
4244

4345
public abstract class CodeBookPage {
@@ -124,6 +126,7 @@ public void to(final @Nullable T value) {
124126
@Retention(RetentionPolicy.RUNTIME)
125127
public @interface ParamMappings {
126128
Key<Path> PATH_KEY = Key.get(Path.class, ParamMappings.class);
129+
Key<MappingSet> KEY = Key.get(MappingSet.class, ParamMappings.class);
127130
}
128131

129132
@Qualifier
@@ -146,4 +149,11 @@ public void to(final @Nullable T value) {
146149
public @interface TempDir {
147150
Key<Path> KEY = Key.get(Path.class, TempDir.class);
148151
}
152+
153+
@Qualifier
154+
@Target(ElementType.PARAMETER)
155+
@Retention(RetentionPolicy.RUNTIME)
156+
public @interface Hypo {
157+
Key<HypoContext> KEY = Key.get(HypoContext.class, Hypo.class);
158+
}
149159
}

src/main/java/io/papermc/codebook/pages/FixJarPage.java

+22-45
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,11 @@
2626

2727
import com.google.common.collect.Iterables;
2828
import dev.denwav.hypo.asm.AsmClassData;
29-
import dev.denwav.hypo.asm.AsmClassDataProvider;
3029
import dev.denwav.hypo.asm.AsmConstructorData;
3130
import dev.denwav.hypo.asm.AsmFieldData;
3231
import dev.denwav.hypo.asm.AsmMethodData;
33-
import dev.denwav.hypo.asm.AsmOutputWriter;
34-
import dev.denwav.hypo.asm.hydrate.BridgeMethodHydrator;
3532
import dev.denwav.hypo.core.HypoContext;
36-
import dev.denwav.hypo.hydrate.HydrationManager;
3733
import dev.denwav.hypo.hydrate.generic.HypoHydration;
38-
import dev.denwav.hypo.model.ClassProviderRoot;
39-
import dev.denwav.hypo.model.HypoModelUtil;
4034
import dev.denwav.hypo.model.data.ClassData;
4135
import dev.denwav.hypo.model.data.ClassKind;
4236
import dev.denwav.hypo.model.data.FieldData;
@@ -45,9 +39,10 @@
4539
import io.papermc.codebook.exceptions.UnexpectedException;
4640
import jakarta.inject.Inject;
4741
import java.io.IOException;
48-
import java.nio.file.Path;
4942
import java.util.ArrayList;
5043
import java.util.List;
44+
import java.util.concurrent.ExecutionException;
45+
import java.util.concurrent.Future;
5146
import org.checkerframework.checker.nullness.qual.Nullable;
5247
import org.objectweb.asm.Opcodes;
5348
import org.objectweb.asm.tree.AnnotationNode;
@@ -56,58 +51,40 @@
5651

5752
public final class FixJarPage extends CodeBookPage {
5853

59-
private final Path inputJar;
60-
private final List<Path> classpath;
61-
private final Path tempDir;
54+
private final HypoContext context;
6255

6356
@Inject
64-
public FixJarPage(
65-
@InputJar final Path inputJar, @ClasspathJars final List<Path> classpath, @TempDir final Path tempDir) {
66-
this.inputJar = inputJar;
67-
this.classpath = classpath;
68-
this.tempDir = tempDir;
57+
public FixJarPage(@Hypo final HypoContext context) {
58+
this.context = context;
6959
}
7060

7161
@Override
7262
public void exec() {
73-
final HypoContext context;
7463
try {
75-
context = this.createContext();
64+
this.fixJar();
7665
} catch (final IOException e) {
77-
throw new UnexpectedException("Failed to create context for bytecode analysis", e);
78-
}
79-
80-
try (context) {
81-
HydrationManager.createDefault()
82-
.register(BridgeMethodHydrator.create())
83-
.hydrate(context);
84-
85-
final Path result = this.fixWithContext(context);
86-
this.bind(InputJar.KEY).to(result);
87-
} catch (final Exception e) {
8866
throw new UnexpectedException("Failed to fix jar", e);
8967
}
9068
}
9169

92-
private HypoContext createContext() throws IOException {
93-
return HypoContext.builder()
94-
.withProvider(AsmClassDataProvider.of(ClassProviderRoot.fromJar(this.inputJar)))
95-
.withContextProvider(AsmClassDataProvider.of(this.classpath.stream()
96-
.map(HypoModelUtil.wrapFunction(ClassProviderRoot::fromJar))
97-
.toList()))
98-
.withContextProvider(AsmClassDataProvider.of(ClassProviderRoot.ofJdk()))
99-
.build();
100-
}
101-
102-
private Path fixWithContext(final HypoContext context) throws IOException {
103-
for (final ClassData classData : context.getProvider().allClasses()) {
104-
this.processClass((AsmClassData) classData);
70+
private void fixJar() throws IOException {
71+
final var tasks = new ArrayList<Future<?>>();
72+
for (final ClassData classData : this.context.getProvider().allClasses()) {
73+
final var task = this.context.getExecutor().submit(() -> {
74+
this.processClass((AsmClassData) classData);
75+
});
76+
tasks.add(task);
10577
}
10678

107-
final Path fixedJar = this.tempDir.resolve("fixed.jar");
108-
AsmOutputWriter.to(fixedJar).write(context);
109-
110-
return fixedJar;
79+
try {
80+
for (final Future<?> task : tasks) {
81+
task.get();
82+
}
83+
} catch (final ExecutionException e) {
84+
throw new UnexpectedException("Failed to fix jar", e.getCause());
85+
} catch (final InterruptedException e) {
86+
throw new UnexpectedException("Jar fixing interrupted", e);
87+
}
11188
}
11289

11390
private void processClass(final AsmClassData classData) {

0 commit comments

Comments
 (0)