src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/SystemModulesPlugin.java
changeset 47216 71c04702a3d5
parent 46096 62c77b334012
child 47489 6d0e943bcd24
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/SystemModulesPlugin.java	Tue Sep 12 19:03:39 2017 +0200
@@ -0,0 +1,1882 @@
+/*
+ * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.tools.jlink.internal.plugins;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.module.Configuration;
+import java.lang.module.ModuleDescriptor;
+import java.lang.module.ModuleDescriptor.Exports;
+import java.lang.module.ModuleDescriptor.Opens;
+import java.lang.module.ModuleDescriptor.Provides;
+import java.lang.module.ModuleDescriptor.Requires;
+import java.lang.module.ModuleDescriptor.Version;
+import java.lang.module.ModuleFinder;
+import java.lang.module.ModuleReader;
+import java.lang.module.ModuleReference;
+import java.lang.module.ResolvedModule;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.function.IntSupplier;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+import jdk.internal.module.Checks;
+import jdk.internal.module.ClassFileAttributes;
+import jdk.internal.module.ClassFileConstants;
+import jdk.internal.module.DefaultRoots;
+import jdk.internal.module.IllegalAccessMaps;
+import jdk.internal.module.ModuleHashes;
+import jdk.internal.module.ModuleInfo.Attributes;
+import jdk.internal.module.ModuleInfoExtender;
+import jdk.internal.module.ModuleReferenceImpl;
+import jdk.internal.module.ModuleResolution;
+import jdk.internal.module.ModuleTarget;
+import jdk.internal.org.objectweb.asm.Attribute;
+import jdk.internal.org.objectweb.asm.ClassReader;
+import jdk.internal.org.objectweb.asm.ClassVisitor;
+import jdk.internal.org.objectweb.asm.ClassWriter;
+import jdk.internal.org.objectweb.asm.MethodVisitor;
+import jdk.internal.org.objectweb.asm.Opcodes;
+
+import static jdk.internal.org.objectweb.asm.Opcodes.*;
+
+import jdk.tools.jlink.internal.ModuleSorter;
+import jdk.tools.jlink.plugin.Plugin;
+import jdk.tools.jlink.plugin.PluginException;
+import jdk.tools.jlink.plugin.ResourcePool;
+import jdk.tools.jlink.plugin.ResourcePoolBuilder;
+import jdk.tools.jlink.plugin.ResourcePoolEntry;
+
+/**
+ * Jlink plugin to reconstitute module descriptors and other attributes for system
+ * modules. The plugin generates implementations of SystemModules to avoid parsing
+ * module-info.class files at startup. It also generates SystemModulesMap to return
+ * the SystemModules implementation for a specific initial module.
+ *
+ * As a side effect, the plugin adds the ModulePackages class file attribute to the
+ * module-info.class files that don't have the attribute.
+ *
+ * @see jdk.internal.module.SystemModuleFinders
+ * @see jdk.internal.module.SystemModules
+ */
+
+public final class SystemModulesPlugin implements Plugin {
+    private static final String NAME = "system-modules";
+    private static final String DESCRIPTION =
+            PluginsResourceBundle.getDescription(NAME);
+    private static final String SYSTEM_MODULES_MAP_CLASS =
+            "jdk/internal/module/SystemModulesMap";
+    private static final String SYSTEM_MODULES_CLASS_PREFIX =
+            "jdk/internal/module/SystemModules$";
+    private static final String ALL_SYSTEM_MODULES_CLASS =
+            SYSTEM_MODULES_CLASS_PREFIX + "all";
+    private static final String DEFAULT_SYSTEM_MODULES_CLASS =
+            SYSTEM_MODULES_CLASS_PREFIX + "default";
+
+    private boolean enabled;
+
+    public SystemModulesPlugin() {
+        this.enabled = true;
+    }
+
+    @Override
+    public String getName() {
+        return NAME;
+    }
+
+    @Override
+    public String getDescription() {
+        return DESCRIPTION;
+    }
+
+    @Override
+    public Set<State> getState() {
+        return enabled ? EnumSet.of(State.AUTO_ENABLED, State.FUNCTIONAL)
+                       : EnumSet.of(State.DISABLED);
+    }
+
+    @Override
+    public boolean hasArguments() {
+        return true;
+    }
+
+    @Override
+    public String getArgumentsDescription() {
+        return PluginsResourceBundle.getArgument(NAME);
+    }
+
+    @Override
+    public void configure(Map<String, String> config) {
+        String arg = config.get(NAME);
+        if (arg != null) {
+            throw new IllegalArgumentException(NAME + ": " + arg);
+        }
+    }
+
+    @Override
+    public ResourcePool transform(ResourcePool in, ResourcePoolBuilder out) {
+        if (!enabled) {
+            throw new PluginException(NAME + " was set");
+        }
+
+        // validate, transform (if needed), and add the module-info.class files
+        List<ModuleInfo> moduleInfos = transformModuleInfos(in, out);
+
+        // generate and add the SystemModuleMap and SystemModules classes
+        Set<String> generated = genSystemModulesClasses(moduleInfos, out);
+
+        // pass through all other resources
+        in.entries()
+            .filter(data -> !data.path().endsWith("/module-info.class")
+                    && !generated.contains(data.path()))
+            .forEach(data -> out.add(data));
+
+        return out.build();
+    }
+
+    /**
+     * Validates and transforms the module-info.class files in the modules, adding
+     * the ModulePackages class file attribute if needed.
+     *
+     * @return the list of ModuleInfo objects, the first element is java.base
+     */
+    List<ModuleInfo> transformModuleInfos(ResourcePool in, ResourcePoolBuilder out) {
+        List<ModuleInfo> moduleInfos = new ArrayList<>();
+
+        // Sort modules in the topological order so that java.base is always first.
+        new ModuleSorter(in.moduleView()).sorted().forEach(module -> {
+            ResourcePoolEntry data = module.findEntry("module-info.class").orElseThrow(
+                // automatic modules not supported
+                () ->  new PluginException("module-info.class not found for " +
+                        module.name() + " module")
+            );
+
+            assert module.name().equals(data.moduleName());
+
+            try {
+                byte[] content = data.contentBytes();
+                Set<String> packages = module.packages();
+                ModuleInfo moduleInfo = new ModuleInfo(content, packages);
+
+                // link-time validation
+                moduleInfo.validateNames();
+
+                // check if any exported or open package is not present
+                moduleInfo.validatePackages();
+
+                // module-info.class may be overridden to add ModulePackages
+                if (moduleInfo.shouldRewrite()) {
+                    data = data.copyWithContent(moduleInfo.getBytes());
+                }
+                moduleInfos.add(moduleInfo);
+
+                // add resource pool entry
+                out.add(data);
+            } catch (IOException e) {
+                throw new PluginException(e);
+            }
+        });
+
+        return moduleInfos;
+    }
+
+    /**
+     * Generates the SystemModules classes (at least one) and the SystemModulesMap
+     * class to map initial modules to a SystemModules class.
+     *
+     * @return the resource names of the resources added to the pool
+     */
+    private Set<String> genSystemModulesClasses(List<ModuleInfo> moduleInfos,
+                                                ResourcePoolBuilder out) {
+        int moduleCount = moduleInfos.size();
+        ModuleFinder finder = finderOf(moduleInfos);
+        assert finder.findAll().size() == moduleCount;
+
+        // map of initial module name to SystemModules class name
+        Map<String, String> map = new LinkedHashMap<>();
+
+        // the names of resources written to the pool
+        Set<String> generated = new HashSet<>();
+
+        // generate the SystemModules implementation to reconstitute all modules
+        Set<String> allModuleNames = moduleInfos.stream()
+                .map(ModuleInfo::moduleName)
+                .collect(Collectors.toSet());
+        String rn = genSystemModulesClass(moduleInfos,
+                                          resolve(finder, allModuleNames),
+                                          ALL_SYSTEM_MODULES_CLASS,
+                                          out);
+        generated.add(rn);
+
+        // generate, if needed, a SystemModules class to reconstitute the modules
+        // needed for the case that the initial module is the unnamed module.
+        String defaultSystemModulesClassName;
+        Configuration cf = resolve(finder, DefaultRoots.compute(finder));
+        if (cf.modules().size() == moduleCount) {
+            // all modules are resolved so no need to generate a class
+            defaultSystemModulesClassName = ALL_SYSTEM_MODULES_CLASS;
+        } else {
+            defaultSystemModulesClassName = DEFAULT_SYSTEM_MODULES_CLASS;
+            rn = genSystemModulesClass(sublist(moduleInfos, cf),
+                                       cf,
+                                       defaultSystemModulesClassName,
+                                       out);
+            generated.add(rn);
+        }
+
+        // Generate a SystemModules class for each module with a main class
+        int suffix = 0;
+        for (ModuleInfo mi : moduleInfos) {
+            if (mi.descriptor().mainClass().isPresent()) {
+                String moduleName = mi.moduleName();
+                cf = resolve(finder, Set.of(moduleName));
+                if (cf.modules().size() == moduleCount) {
+                    // resolves all modules so no need to generate a class
+                    map.put(moduleName, ALL_SYSTEM_MODULES_CLASS);
+                } else {
+                    String cn = SYSTEM_MODULES_CLASS_PREFIX + (suffix++);
+                    rn = genSystemModulesClass(sublist(moduleInfos, cf), cf, cn, out);
+                    map.put(moduleName, cn);
+                    generated.add(rn);
+                }
+            }
+        }
+
+        // generate SystemModulesMap
+        rn = genSystemModulesMapClass(ALL_SYSTEM_MODULES_CLASS,
+                                      defaultSystemModulesClassName,
+                                      map,
+                                      out);
+        generated.add(rn);
+
+        // return the resource names of the generated classes
+        return generated;
+    }
+
+    /**
+     * Resolves a collection of root modules, with service binding, to create
+     * configuration.
+     */
+    private Configuration resolve(ModuleFinder finder, Set<String> roots) {
+        return Configuration.empty().resolveAndBind(finder, ModuleFinder.of(), roots);
+    }
+
+    /**
+     * Returns the list of ModuleInfo objects that correspond to the modules in
+     * the given configuration.
+     */
+    private List<ModuleInfo> sublist(List<ModuleInfo> moduleInfos, Configuration cf) {
+        Set<String> names = cf.modules()
+                .stream()
+                .map(ResolvedModule::name)
+                .collect(Collectors.toSet());
+        return moduleInfos.stream()
+                .filter(mi -> names.contains(mi.moduleName()))
+                .collect(Collectors.toList());
+    }
+
+    /**
+     * Generate a SystemModules implementation class and add it as a resource.
+     *
+     * @return the name of the class resource added to the pool
+     */
+    private String genSystemModulesClass(List<ModuleInfo> moduleInfos,
+                                         Configuration cf,
+                                         String className,
+                                         ResourcePoolBuilder out) {
+        SystemModulesClassGenerator generator
+            = new SystemModulesClassGenerator(className, moduleInfos);
+        byte[] bytes = generator.getClassWriter(cf).toByteArray();
+        String rn = "/java.base/" + className + ".class";
+        ResourcePoolEntry e = ResourcePoolEntry.create(rn, bytes);
+        out.add(e);
+        return rn;
+    }
+
+    static class ModuleInfo {
+        private final ByteArrayInputStream bais;
+        private final Attributes attrs;
+        private final Set<String> packages;
+        private final boolean addModulePackages;
+        private ModuleDescriptor descriptor;  // may be different that the original one
+
+        ModuleInfo(byte[] bytes, Set<String> packages) throws IOException {
+            this.bais = new ByteArrayInputStream(bytes);
+            this.packages = packages;
+            this.attrs = jdk.internal.module.ModuleInfo.read(bais, null);
+
+            // If ModulePackages attribute is present, the packages from this
+            // module descriptor returns the packages in that attribute.
+            // If it's not present, ModuleDescriptor::packages only contains
+            // the exported and open packages from module-info.class
+            this.descriptor = attrs.descriptor();
+            if (descriptor.isAutomatic()) {
+                throw new InternalError("linking automatic module is not supported");
+            }
+
+            // add ModulePackages attribute if this module contains some packages
+            // and ModulePackages is not present
+            this.addModulePackages = packages.size() > 0 && !hasModulePackages();
+        }
+
+        String moduleName() {
+            return attrs.descriptor().name();
+        }
+
+        ModuleDescriptor descriptor() {
+            return descriptor;
+        }
+
+        Set<String> packages() {
+            return packages;
+        }
+
+        ModuleTarget target() {
+            return attrs.target();
+        }
+
+        ModuleHashes recordedHashes() {
+            return attrs.recordedHashes();
+        }
+
+        ModuleResolution moduleResolution() {
+            return attrs.moduleResolution();
+        }
+
+        /**
+         * Validates names in ModuleDescriptor
+         */
+        void validateNames() {
+            Checks.requireModuleName(descriptor.name());
+            for (Requires req : descriptor.requires()) {
+                Checks.requireModuleName(req.name());
+            }
+            for (Exports e : descriptor.exports()) {
+                Checks.requirePackageName(e.source());
+                if (e.isQualified())
+                    e.targets().forEach(Checks::requireModuleName);
+            }
+            for (Opens opens : descriptor.opens()) {
+                Checks.requirePackageName(opens.source());
+                if (opens.isQualified())
+                    opens.targets().forEach(Checks::requireModuleName);
+            }
+            for (Provides provides : descriptor.provides()) {
+                Checks.requireServiceTypeName(provides.service());
+                provides.providers().forEach(Checks::requireServiceProviderName);
+            }
+            for (String service : descriptor.uses()) {
+                Checks.requireServiceTypeName(service);
+            }
+            for (String pn : descriptor.packages()) {
+                Checks.requirePackageName(pn);
+            }
+            for (String pn : packages) {
+                Checks.requirePackageName(pn);
+            }
+        }
+
+        /**
+         * Validates if exported and open packages are present
+         */
+        void validatePackages() {
+            Set<String> nonExistPackages = new TreeSet<>();
+            descriptor.exports().stream()
+                .map(Exports::source)
+                .filter(pn -> !packages.contains(pn))
+                .forEach(nonExistPackages::add);
+
+            descriptor.opens().stream()
+                .map(Opens::source)
+                .filter(pn -> !packages.contains(pn))
+                .forEach(nonExistPackages::add);
+
+            if (!nonExistPackages.isEmpty()) {
+                throw new PluginException("Packages that are exported or open in "
+                    + descriptor.name() + " are not present: " + nonExistPackages);
+            }
+        }
+
+        boolean hasModulePackages() throws IOException {
+            Set<String> attrTypes = new HashSet<>();
+            ClassVisitor cv = new ClassVisitor(Opcodes.ASM5) {
+                @Override
+                public void visitAttribute(Attribute attr) {
+                    attrTypes.add(attr.type);
+                }
+            };
+
+            // prototype of attributes that should be parsed
+            Attribute[] attrs = new Attribute[] {
+                new ClassFileAttributes.ModulePackagesAttribute()
+            };
+
+            try (InputStream in = getInputStream()) {
+                // parse module-info.class
+                ClassReader cr = new ClassReader(in);
+                cr.accept(cv, attrs, 0);
+                return attrTypes.contains(ClassFileConstants.MODULE_PACKAGES);
+            }
+        }
+
+        /**
+         * Returns true if module-info.class should be rewritten to add the
+         * ModulePackages attribute.
+         */
+        boolean shouldRewrite() {
+            return addModulePackages;
+        }
+
+        /**
+         * Returns the bytes for the (possibly updated) module-info.class.
+         */
+        byte[] getBytes() throws IOException {
+            try (InputStream in = getInputStream()) {
+                if (shouldRewrite()) {
+                    ModuleInfoRewriter rewriter = new ModuleInfoRewriter(in);
+                    if (addModulePackages) {
+                        rewriter.addModulePackages(packages);
+                    }
+                    // rewritten module descriptor
+                    byte[] bytes = rewriter.getBytes();
+                    try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes)) {
+                        this.descriptor = ModuleDescriptor.read(bais);
+                    }
+                    return bytes;
+                } else {
+                    return in.readAllBytes();
+                }
+            }
+        }
+
+        /*
+         * Returns the input stream of the module-info.class
+         */
+        InputStream getInputStream() {
+            bais.reset();
+            return bais;
+        }
+
+        class ModuleInfoRewriter extends ByteArrayOutputStream {
+            final ModuleInfoExtender extender;
+            ModuleInfoRewriter(InputStream in) {
+                this.extender = ModuleInfoExtender.newExtender(in);
+            }
+
+            void addModulePackages(Set<String> packages) {
+                // Add ModulePackages attribute
+                if (packages.size() > 0) {
+                    extender.packages(packages);
+                }
+            }
+
+            byte[] getBytes() throws IOException {
+                extender.write(this);
+                return buf;
+            }
+        }
+    }
+
+    /**
+     * Generates a SystemModules class to reconstitute the ModuleDescriptor
+     * and other attributes of system modules.
+     */
+    static class SystemModulesClassGenerator {
+        private static final String MODULE_DESCRIPTOR_BUILDER =
+            "jdk/internal/module/Builder";
+        private static final String MODULE_DESCRIPTOR_ARRAY_SIGNATURE =
+            "[Ljava/lang/module/ModuleDescriptor;";
+        private static final String REQUIRES_MODIFIER_CLASSNAME =
+            "java/lang/module/ModuleDescriptor$Requires$Modifier";
+        private static final String EXPORTS_MODIFIER_CLASSNAME =
+            "java/lang/module/ModuleDescriptor$Exports$Modifier";
+        private static final String OPENS_MODIFIER_CLASSNAME =
+            "java/lang/module/ModuleDescriptor$Opens$Modifier";
+        private static final String MODULE_TARGET_CLASSNAME  =
+            "jdk/internal/module/ModuleTarget";
+        private static final String MODULE_TARGET_ARRAY_SIGNATURE  =
+            "[Ljdk/internal/module/ModuleTarget;";
+        private static final String MODULE_HASHES_ARRAY_SIGNATURE  =
+            "[Ljdk/internal/module/ModuleHashes;";
+        private static final String MODULE_RESOLUTION_CLASSNAME  =
+            "jdk/internal/module/ModuleResolution";
+        private static final String MODULE_RESOLUTIONS_ARRAY_SIGNATURE  =
+            "[Ljdk/internal/module/ModuleResolution;";
+
+        private static final int MAX_LOCAL_VARS = 256;
+
+        private final int BUILDER_VAR    = 0;
+        private final int MD_VAR         = 1;  // variable for ModuleDescriptor
+        private final int MT_VAR         = 1;  // variable for ModuleTarget
+        private final int MH_VAR         = 1;  // variable for ModuleHashes
+        private int nextLocalVar         = 2;  // index to next local variable
+
+        // Method visitor for generating the SystemModules::modules() method
+        private MethodVisitor mv;
+
+        // name of class to generate
+        private final String className;
+
+        // list of all ModuleDescriptorBuilders, invoked in turn when building.
+        private final List<ModuleInfo> moduleInfos;
+
+        // A builder to create one single Set instance for a given set of
+        // names or modifiers to reduce the footprint
+        // e.g. target modules of qualified exports
+        private final DedupSetBuilder dedupSetBuilder
+            = new DedupSetBuilder(this::getNextLocalVar);
+
+        public SystemModulesClassGenerator(String className,
+                                           List<ModuleInfo> moduleInfos) {
+            this.className = className;
+            this.moduleInfos = moduleInfos;
+            moduleInfos.forEach(mi -> dedups(mi.descriptor()));
+        }
+
+        private int getNextLocalVar() {
+            return nextLocalVar++;
+        }
+
+        /*
+         * Adds the given ModuleDescriptor to the system module list.
+         * It performs link-time validation and prepares mapping from various
+         * Sets to SetBuilders to emit an optimized number of sets during build.
+         */
+        private void dedups(ModuleDescriptor md) {
+            // exports
+            for (Exports e : md.exports()) {
+                dedupSetBuilder.stringSet(e.targets());
+                dedupSetBuilder.exportsModifiers(e.modifiers());
+            }
+
+            // opens
+            for (Opens opens : md.opens()) {
+                dedupSetBuilder.stringSet(opens.targets());
+                dedupSetBuilder.opensModifiers(opens.modifiers());
+            }
+
+            // requires
+            for (Requires r : md.requires()) {
+                dedupSetBuilder.requiresModifiers(r.modifiers());
+            }
+
+            // uses
+            dedupSetBuilder.stringSet(md.uses());
+        }
+
+        /**
+         * Generate SystemModules class
+         */
+        public ClassWriter getClassWriter(Configuration cf) {
+            ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS
+                                             + ClassWriter.COMPUTE_FRAMES);
+            cw.visit(Opcodes.V1_8,
+                     ACC_FINAL+ACC_SUPER,
+                     className,
+                     null,
+                     "java/lang/Object",
+                     new String[] { "jdk/internal/module/SystemModules" });
+
+            // generate <init>
+            genConstructor(cw);
+
+            // generate hasSplitPackages
+            genHasSplitPackages(cw);
+
+            // generate hasIncubatorModules
+            genIncubatorModules(cw);
+
+            // generate moduleDescriptors
+            genModuleDescriptorsMethod(cw);
+
+            // generate moduleTargets
+            genModuleTargetsMethod(cw);
+
+            // generate moduleHashes
+            genModuleHashesMethod(cw);
+
+            // generate moduleResolutions
+            genModuleResolutionsMethod(cw);
+
+            // generate moduleReads
+            genModuleReads(cw, cf);
+
+            // generate concealedPackagesToOpen and exportedPackagesToOpen
+            genXXXPackagesToOpenMethods(cw);
+
+            return cw;
+        }
+
+        /**
+         * Generate byteccode for no-arg constructor
+         */
+        private void genConstructor(ClassWriter cw) {
+            MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+            mv.visitVarInsn(ALOAD, 0);
+            mv.visitMethodInsn(INVOKESPECIAL,
+                               "java/lang/Object",
+                               "<init>",
+                               "()V",
+                               false);
+            mv.visitInsn(RETURN);
+            mv.visitMaxs(0, 0);
+            mv.visitEnd();
+        }
+
+        /**
+         * Generate bytecode for hasSplitPackages method
+         */
+        private void genHasSplitPackages(ClassWriter cw) {
+            boolean distinct = moduleInfos.stream()
+                    .map(ModuleInfo::packages)
+                    .flatMap(Set::stream)
+                    .allMatch(new HashSet<>()::add);
+            boolean hasSplitPackages = !distinct;
+
+            mv = cw.visitMethod(ACC_PUBLIC,
+                                "hasSplitPackages",
+                                "()Z",
+                                "()Z",
+                                null);
+            mv.visitCode();
+            if (hasSplitPackages) {
+                mv.visitInsn(ICONST_1);
+            } else {
+                mv.visitInsn(ICONST_0);
+            }
+            mv.visitInsn(IRETURN);
+            mv.visitMaxs(0, 0);
+            mv.visitEnd();
+        }
+
+        /**
+         * Generate bytecode for hasIncubatorModules method
+         */
+        private void genIncubatorModules(ClassWriter cw) {
+            boolean hasIncubatorModules = moduleInfos.stream()
+                    .map(ModuleInfo::moduleResolution)
+                    .filter(mres -> (mres != null && mres.hasIncubatingWarning()))
+                    .findFirst()
+                    .isPresent();
+
+            mv = cw.visitMethod(ACC_PUBLIC,
+                                "hasIncubatorModules",
+                                "()Z",
+                                "()Z",
+                                null);
+            mv.visitCode();
+            if (hasIncubatorModules) {
+                mv.visitInsn(ICONST_1);
+            } else {
+                mv.visitInsn(ICONST_0);
+            }
+            mv.visitInsn(IRETURN);
+            mv.visitMaxs(0, 0);
+            mv.visitEnd();
+        }
+
+        /**
+         * Generate bytecode for moduleDescriptors method
+         */
+        private void genModuleDescriptorsMethod(ClassWriter cw) {
+            this.mv = cw.visitMethod(ACC_PUBLIC,
+                                     "moduleDescriptors",
+                                     "()" + MODULE_DESCRIPTOR_ARRAY_SIGNATURE,
+                                     "()" + MODULE_DESCRIPTOR_ARRAY_SIGNATURE,
+                                     null);
+            mv.visitCode();
+            pushInt(mv, moduleInfos.size());
+            mv.visitTypeInsn(ANEWARRAY, "java/lang/module/ModuleDescriptor");
+            mv.visitVarInsn(ASTORE, MD_VAR);
+
+            for (int index = 0; index < moduleInfos.size(); index++) {
+                ModuleInfo minfo = moduleInfos.get(index);
+                new ModuleDescriptorBuilder(minfo.descriptor(),
+                                            minfo.packages(),
+                                            index).build();
+            }
+            mv.visitVarInsn(ALOAD, MD_VAR);
+            mv.visitInsn(ARETURN);
+            mv.visitMaxs(0, 0);
+            mv.visitEnd();
+        }
+
+        /**
+         * Generate bytecode for moduleTargets method
+         */
+        private void genModuleTargetsMethod(ClassWriter cw) {
+            MethodVisitor mv = cw.visitMethod(ACC_PUBLIC,
+                                              "moduleTargets",
+                                              "()" + MODULE_TARGET_ARRAY_SIGNATURE,
+                                              "()" + MODULE_TARGET_ARRAY_SIGNATURE,
+                                              null);
+            mv.visitCode();
+            pushInt(mv, moduleInfos.size());
+            mv.visitTypeInsn(ANEWARRAY, MODULE_TARGET_CLASSNAME);
+            mv.visitVarInsn(ASTORE, MT_VAR);
+
+
+            // if java.base has a ModuleTarget attribute then generate the array
+            // with one element, all other elements will be null.
+
+            ModuleInfo base = moduleInfos.get(0);
+            if (!base.moduleName().equals("java.base"))
+                throw new InternalError("java.base should be first module in list");
+            ModuleTarget target = base.target();
+
+            int count;
+            if (target != null && target.targetPlatform() != null) {
+                count = 1;
+            } else {
+                count = moduleInfos.size();
+            }
+
+            for (int index = 0; index < count; index++) {
+                ModuleInfo minfo = moduleInfos.get(index);
+                if (minfo.target() != null) {
+                    mv.visitVarInsn(ALOAD, MT_VAR);
+                    pushInt(mv, index);
+
+                    // new ModuleTarget(String)
+                    mv.visitTypeInsn(NEW, MODULE_TARGET_CLASSNAME);
+                    mv.visitInsn(DUP);
+                    mv.visitLdcInsn(minfo.target().targetPlatform());
+                    mv.visitMethodInsn(INVOKESPECIAL, MODULE_TARGET_CLASSNAME,
+                                       "<init>", "(Ljava/lang/String;)V", false);
+
+                    mv.visitInsn(AASTORE);
+                }
+            }
+
+            mv.visitVarInsn(ALOAD, MT_VAR);
+            mv.visitInsn(ARETURN);
+            mv.visitMaxs(0, 0);
+            mv.visitEnd();
+        }
+
+        /**
+         * Generate bytecode for moduleHashes method
+         */
+        private void genModuleHashesMethod(ClassWriter cw) {
+            MethodVisitor hmv =
+                cw.visitMethod(ACC_PUBLIC,
+                               "moduleHashes",
+                               "()" + MODULE_HASHES_ARRAY_SIGNATURE,
+                               "()" + MODULE_HASHES_ARRAY_SIGNATURE,
+                               null);
+            hmv.visitCode();
+            pushInt(hmv, moduleInfos.size());
+            hmv.visitTypeInsn(ANEWARRAY, "jdk/internal/module/ModuleHashes");
+            hmv.visitVarInsn(ASTORE, MH_VAR);
+
+            for (int index = 0; index < moduleInfos.size(); index++) {
+                ModuleInfo minfo = moduleInfos.get(index);
+                if (minfo.recordedHashes() != null) {
+                    new ModuleHashesBuilder(minfo.recordedHashes(),
+                                            index,
+                                            hmv).build();
+                }
+            }
+
+            hmv.visitVarInsn(ALOAD, MH_VAR);
+            hmv.visitInsn(ARETURN);
+            hmv.visitMaxs(0, 0);
+            hmv.visitEnd();
+        }
+
+        /**
+         * Generate bytecode for moduleResolutions method
+         */
+        private void genModuleResolutionsMethod(ClassWriter cw) {
+            MethodVisitor mresmv =
+                cw.visitMethod(ACC_PUBLIC,
+                               "moduleResolutions",
+                               "()" + MODULE_RESOLUTIONS_ARRAY_SIGNATURE,
+                               "()" + MODULE_RESOLUTIONS_ARRAY_SIGNATURE,
+                               null);
+            mresmv.visitCode();
+            pushInt(mresmv, moduleInfos.size());
+            mresmv.visitTypeInsn(ANEWARRAY, MODULE_RESOLUTION_CLASSNAME);
+            mresmv.visitVarInsn(ASTORE, 0);
+
+            for (int index=0; index < moduleInfos.size(); index++) {
+                ModuleInfo minfo = moduleInfos.get(index);
+                if (minfo.moduleResolution() != null) {
+                    mresmv.visitVarInsn(ALOAD, 0);
+                    pushInt(mresmv, index);
+                    mresmv.visitTypeInsn(NEW, MODULE_RESOLUTION_CLASSNAME);
+                    mresmv.visitInsn(DUP);
+                    mresmv.visitLdcInsn(minfo.moduleResolution().value());
+                    mresmv.visitMethodInsn(INVOKESPECIAL,
+                                           MODULE_RESOLUTION_CLASSNAME,
+                                           "<init>",
+                                           "(I)V", false);
+                    mresmv.visitInsn(AASTORE);
+                }
+            }
+            mresmv.visitVarInsn(ALOAD, 0);
+            mresmv.visitInsn(ARETURN);
+            mresmv.visitMaxs(0, 0);
+            mresmv.visitEnd();
+        }
+
+        /**
+         * Generate bytecode for moduleReads method
+         */
+        private void genModuleReads(ClassWriter cw, Configuration cf) {
+            // module name -> names of modules that it reads
+            Map<String, Set<String>> map = cf.modules().stream()
+                    .collect(Collectors.toMap(
+                            ResolvedModule::name,
+                            m -> m.reads().stream()
+                                    .map(ResolvedModule::name)
+                                    .collect(Collectors.toSet())));
+            generate(cw, "moduleReads", map, true);
+        }
+
+        /**
+         * Generate concealedPackagesToOpen and exportedPackagesToOpen methods.
+         */
+        private void genXXXPackagesToOpenMethods(ClassWriter cw) {
+            ModuleFinder finder = finderOf(moduleInfos);
+            IllegalAccessMaps maps = IllegalAccessMaps.generate(finder);
+            generate(cw, "concealedPackagesToOpen", maps.concealedPackagesToOpen(), false);
+            generate(cw, "exportedPackagesToOpen", maps.exportedPackagesToOpen(), false);
+        }
+
+        /**
+         * Generate method to return {@code Map<String, Set<String>>}.
+         *
+         * If {@code dedup} is true then the values are de-duplicated.
+         */
+        private void generate(ClassWriter cw,
+                              String methodName,
+                              Map<String, Set<String>> map,
+                              boolean dedup) {
+            MethodVisitor mv = cw.visitMethod(ACC_PUBLIC,
+                                              methodName,
+                                              "()Ljava/util/Map;",
+                                              "()Ljava/util/Map;",
+                                              null);
+            mv.visitCode();
+
+            // map of Set -> local
+            Map<Set<String>, Integer> locals;
+
+            // generate code to create the sets that are duplicated
+            if (dedup) {
+                Collection<Set<String>> values = map.values();
+                Set<Set<String>> duplicateSets = values.stream()
+                        .distinct()
+                        .filter(s -> Collections.frequency(values, s) > 1)
+                        .collect(Collectors.toSet());
+                locals = new HashMap<>();
+                int index = 1;
+                for (Set<String> s : duplicateSets) {
+                    genImmutableSet(mv, s);
+                    mv.visitVarInsn(ASTORE, index);
+                    locals.put(s, index);
+                    if (++index >= MAX_LOCAL_VARS) {
+                        break;
+                    }
+                }
+            } else {
+                locals = Map.of();
+            }
+
+            // new Map$Entry[size]
+            pushInt(mv, map.size());
+            mv.visitTypeInsn(ANEWARRAY, "java/util/Map$Entry");
+
+            int index = 0;
+            for (Map.Entry<String, Set<String>> e : map.entrySet()) {
+                String name = e.getKey();
+                Set<String> s = e.getValue();
+
+                mv.visitInsn(DUP);
+                pushInt(mv, index);
+                mv.visitLdcInsn(name);
+
+                // if de-duplicated then load the local, otherwise generate code
+                Integer varIndex = locals.get(s);
+                if (varIndex == null) {
+                    genImmutableSet(mv, s);
+                } else {
+                    mv.visitVarInsn(ALOAD, varIndex);
+                }
+
+                String desc = "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/util/Map$Entry;";
+                mv.visitMethodInsn(INVOKESTATIC,
+                                   "java/util/Map",
+                                   "entry",
+                                   desc,
+                                   true);
+                mv.visitInsn(AASTORE);
+                index++;
+            }
+
+            // invoke Map.ofEntries(Map$Entry[])
+            mv.visitMethodInsn(INVOKESTATIC, "java/util/Map", "ofEntries",
+                    "([Ljava/util/Map$Entry;)Ljava/util/Map;", true);
+            mv.visitInsn(ARETURN);
+            mv.visitMaxs(0, 0);
+            mv.visitEnd();
+        }
+
+        /**
+         * Generate code to generate an immutable set.
+         */
+        private void genImmutableSet(MethodVisitor mv, Set<String> set) {
+            int size = set.size();
+
+            // use Set.of(Object[]) when there are more than 2 elements
+            // use Set.of(Object) or Set.of(Object, Object) when fewer
+            if (size > 2) {
+                pushInt(mv, size);
+                mv.visitTypeInsn(ANEWARRAY, "java/lang/String");
+                int i = 0;
+                for (String element : set) {
+                    mv.visitInsn(DUP);
+                    pushInt(mv, i);
+                    mv.visitLdcInsn(element);
+                    mv.visitInsn(AASTORE);
+                    i++;
+                }
+                mv.visitMethodInsn(INVOKESTATIC,
+                        "java/util/Set",
+                        "of",
+                        "([Ljava/lang/Object;)Ljava/util/Set;",
+                        true);
+            } else {
+                StringBuilder sb = new StringBuilder("(");
+                for (String element : set) {
+                    mv.visitLdcInsn(element);
+                    sb.append("Ljava/lang/Object;");
+                }
+                sb.append(")Ljava/util/Set;");
+                mv.visitMethodInsn(INVOKESTATIC,
+                        "java/util/Set",
+                        "of",
+                        sb.toString(),
+                        true);
+            }
+        }
+
+        class ModuleDescriptorBuilder {
+            static final String BUILDER_TYPE = "Ljdk/internal/module/Builder;";
+            static final String EXPORTS_TYPE =
+                "Ljava/lang/module/ModuleDescriptor$Exports;";
+            static final String OPENS_TYPE =
+                "Ljava/lang/module/ModuleDescriptor$Opens;";
+            static final String PROVIDES_TYPE =
+                "Ljava/lang/module/ModuleDescriptor$Provides;";
+            static final String REQUIRES_TYPE =
+                "Ljava/lang/module/ModuleDescriptor$Requires;";
+
+            // method signature for static Builder::newExports, newOpens,
+            // newProvides, newRequires methods
+            static final String EXPORTS_MODIFIER_SET_STRING_SET_SIG =
+                "(Ljava/util/Set;Ljava/lang/String;Ljava/util/Set;)"
+                    + EXPORTS_TYPE;
+            static final String EXPORTS_MODIFIER_SET_STRING_SIG =
+                "(Ljava/util/Set;Ljava/lang/String;)" + EXPORTS_TYPE;
+            static final String OPENS_MODIFIER_SET_STRING_SET_SIG =
+                "(Ljava/util/Set;Ljava/lang/String;Ljava/util/Set;)"
+                    + OPENS_TYPE;
+            static final String OPENS_MODIFIER_SET_STRING_SIG =
+                "(Ljava/util/Set;Ljava/lang/String;)" + OPENS_TYPE;
+            static final String PROVIDES_STRING_LIST_SIG =
+                "(Ljava/lang/String;Ljava/util/List;)" + PROVIDES_TYPE;
+            static final String REQUIRES_SET_STRING_SIG =
+                "(Ljava/util/Set;Ljava/lang/String;)" + REQUIRES_TYPE;
+            static final String REQUIRES_SET_STRING_STRING_SIG =
+                "(Ljava/util/Set;Ljava/lang/String;Ljava/lang/String;)" + REQUIRES_TYPE;
+
+            // method signature for Builder instance methods that
+            // return this Builder instance
+            static final String EXPORTS_ARRAY_SIG =
+                "([" + EXPORTS_TYPE + ")" + BUILDER_TYPE;
+            static final String OPENS_ARRAY_SIG =
+                "([" + OPENS_TYPE + ")" + BUILDER_TYPE;
+            static final String PROVIDES_ARRAY_SIG =
+                "([" + PROVIDES_TYPE + ")" + BUILDER_TYPE;
+            static final String REQUIRES_ARRAY_SIG =
+                "([" + REQUIRES_TYPE + ")" + BUILDER_TYPE;
+            static final String SET_SIG = "(Ljava/util/Set;)" + BUILDER_TYPE;
+            static final String STRING_SIG = "(Ljava/lang/String;)" + BUILDER_TYPE;
+            static final String BOOLEAN_SIG = "(Z)" + BUILDER_TYPE;
+
+            final ModuleDescriptor md;
+            final Set<String> packages;
+            final int index;
+
+            ModuleDescriptorBuilder(ModuleDescriptor md, Set<String> packages, int index) {
+                if (md.isAutomatic()) {
+                    throw new InternalError("linking automatic module is not supported");
+                }
+                this.md = md;
+                this.packages = packages;
+                this.index = index;
+            }
+
+            void build() {
+                // new jdk.internal.module.Builder
+                newBuilder();
+
+                // requires
+                requires(md.requires());
+
+                // exports
+                exports(md.exports());
+
+                // opens
+                opens(md.opens());
+
+                // uses
+                uses(md.uses());
+
+                // provides
+                provides(md.provides());
+
+                // all packages
+                packages(packages);
+
+                // version
+                md.version().ifPresent(this::version);
+
+                // main class
+                md.mainClass().ifPresent(this::mainClass);
+
+                putModuleDescriptor();
+            }
+
+            void newBuilder() {
+                mv.visitTypeInsn(NEW, MODULE_DESCRIPTOR_BUILDER);
+                mv.visitInsn(DUP);
+                mv.visitLdcInsn(md.name());
+                mv.visitMethodInsn(INVOKESPECIAL, MODULE_DESCRIPTOR_BUILDER,
+                    "<init>", "(Ljava/lang/String;)V", false);
+                mv.visitVarInsn(ASTORE, BUILDER_VAR);
+                mv.visitVarInsn(ALOAD, BUILDER_VAR);
+
+                if (md.isOpen()) {
+                    setModuleBit("open", true);
+                }
+                if (md.modifiers().contains(ModuleDescriptor.Modifier.SYNTHETIC)) {
+                    setModuleBit("synthetic", true);
+                }
+                if (md.modifiers().contains(ModuleDescriptor.Modifier.MANDATED)) {
+                    setModuleBit("mandated", true);
+                }
+            }
+
+            /*
+             * Invoke Builder.<methodName>(boolean value)
+             */
+            void setModuleBit(String methodName, boolean value) {
+                mv.visitVarInsn(ALOAD, BUILDER_VAR);
+                if (value) {
+                    mv.visitInsn(ICONST_1);
+                } else {
+                    mv.visitInsn(ICONST_0);
+                }
+                mv.visitMethodInsn(INVOKEVIRTUAL, MODULE_DESCRIPTOR_BUILDER,
+                    methodName, BOOLEAN_SIG, false);
+                mv.visitInsn(POP);
+            }
+
+            /*
+             * Put ModuleDescriptor into the modules array
+             */
+            void putModuleDescriptor() {
+                mv.visitVarInsn(ALOAD, MD_VAR);
+                pushInt(mv, index);
+                mv.visitVarInsn(ALOAD, BUILDER_VAR);
+                mv.visitLdcInsn(md.hashCode());
+                mv.visitMethodInsn(INVOKEVIRTUAL, MODULE_DESCRIPTOR_BUILDER,
+                    "build", "(I)Ljava/lang/module/ModuleDescriptor;",
+                    false);
+                mv.visitInsn(AASTORE);
+            }
+
+            /*
+             * Call Builder::newRequires to create Requires instances and
+             * then pass it to the builder by calling:
+             *      Builder.requires(Requires[])
+             *
+             */
+            void requires(Set<Requires> requires) {
+                mv.visitVarInsn(ALOAD, BUILDER_VAR);
+                pushInt(mv, requires.size());
+                mv.visitTypeInsn(ANEWARRAY, "java/lang/module/ModuleDescriptor$Requires");
+                int arrayIndex = 0;
+                for (Requires require : requires) {
+                    String compiledVersion = null;
+                    if (require.compiledVersion().isPresent()) {
+                        compiledVersion = require.compiledVersion().get().toString();
+                    }
+
+                    mv.visitInsn(DUP);               // arrayref
+                    pushInt(mv, arrayIndex++);
+                    newRequires(require.modifiers(), require.name(), compiledVersion);
+                    mv.visitInsn(AASTORE);
+                }
+                mv.visitMethodInsn(INVOKEVIRTUAL, MODULE_DESCRIPTOR_BUILDER,
+                    "requires", REQUIRES_ARRAY_SIG, false);
+            }
+
+            /*
+             * Invoke Builder.newRequires(Set<Modifier> mods, String mn, String compiledVersion)
+             *
+             * Set<Modifier> mods = ...
+             * Builder.newRequires(mods, mn, compiledVersion);
+             */
+            void newRequires(Set<Requires.Modifier> mods, String name, String compiledVersion) {
+                int varIndex = dedupSetBuilder.indexOfRequiresModifiers(mods);
+                mv.visitVarInsn(ALOAD, varIndex);
+                mv.visitLdcInsn(name);
+                if (compiledVersion != null) {
+                    mv.visitLdcInsn(compiledVersion);
+                    mv.visitMethodInsn(INVOKESTATIC, MODULE_DESCRIPTOR_BUILDER,
+                        "newRequires", REQUIRES_SET_STRING_STRING_SIG, false);
+                } else {
+                    mv.visitMethodInsn(INVOKESTATIC, MODULE_DESCRIPTOR_BUILDER,
+                        "newRequires", REQUIRES_SET_STRING_SIG, false);
+                }
+            }
+
+            /*
+             * Call Builder::newExports to create Exports instances and
+             * then pass it to the builder by calling:
+             *      Builder.exports(Exports[])
+             *
+             */
+            void exports(Set<Exports> exports) {
+                mv.visitVarInsn(ALOAD, BUILDER_VAR);
+                pushInt(mv, exports.size());
+                mv.visitTypeInsn(ANEWARRAY, "java/lang/module/ModuleDescriptor$Exports");
+                int arrayIndex = 0;
+                for (Exports export : exports) {
+                    mv.visitInsn(DUP);    // arrayref
+                    pushInt(mv, arrayIndex++);
+                    newExports(export.modifiers(), export.source(), export.targets());
+                    mv.visitInsn(AASTORE);
+                }
+                mv.visitMethodInsn(INVOKEVIRTUAL, MODULE_DESCRIPTOR_BUILDER,
+                    "exports", EXPORTS_ARRAY_SIG, false);
+            }
+
+            /*
+             * Invoke
+             *     Builder.newExports(Set<Exports.Modifier> ms, String pn,
+             *                        Set<String> targets)
+             * or
+             *     Builder.newExports(Set<Exports.Modifier> ms, String pn)
+             *
+             * Set<String> targets = new HashSet<>();
+             * targets.add(t);
+             * :
+             * :
+             *
+             * Set<Modifier> mods = ...
+             * Builder.newExports(mods, pn, targets);
+             */
+            void newExports(Set<Exports.Modifier> ms, String pn, Set<String> targets) {
+                int modifiersSetIndex = dedupSetBuilder.indexOfExportsModifiers(ms);
+                if (!targets.isEmpty()) {
+                    int stringSetIndex = dedupSetBuilder.indexOfStringSet(targets);
+                    mv.visitVarInsn(ALOAD, modifiersSetIndex);
+                    mv.visitLdcInsn(pn);
+                    mv.visitVarInsn(ALOAD, stringSetIndex);
+                    mv.visitMethodInsn(INVOKESTATIC, MODULE_DESCRIPTOR_BUILDER,
+                        "newExports", EXPORTS_MODIFIER_SET_STRING_SET_SIG, false);
+                } else {
+                    mv.visitVarInsn(ALOAD, modifiersSetIndex);
+                    mv.visitLdcInsn(pn);
+                    mv.visitMethodInsn(INVOKESTATIC, MODULE_DESCRIPTOR_BUILDER,
+                        "newExports", EXPORTS_MODIFIER_SET_STRING_SIG, false);
+                }
+            }
+
+
+            /**
+             * Call Builder::newOpens to create Opens instances and
+             * then pass it to the builder by calling:
+             * Builder.opens(Opens[])
+             */
+            void opens(Set<Opens> opens) {
+                mv.visitVarInsn(ALOAD, BUILDER_VAR);
+                pushInt(mv, opens.size());
+                mv.visitTypeInsn(ANEWARRAY, "java/lang/module/ModuleDescriptor$Opens");
+                int arrayIndex = 0;
+                for (Opens open : opens) {
+                    mv.visitInsn(DUP);    // arrayref
+                    pushInt(mv, arrayIndex++);
+                    newOpens(open.modifiers(), open.source(), open.targets());
+                    mv.visitInsn(AASTORE);
+                }
+                mv.visitMethodInsn(INVOKEVIRTUAL, MODULE_DESCRIPTOR_BUILDER,
+                    "opens", OPENS_ARRAY_SIG, false);
+            }
+
+            /*
+             * Invoke
+             *     Builder.newOpens(Set<Opens.Modifier> ms, String pn,
+             *                        Set<String> targets)
+             * or
+             *     Builder.newOpens(Set<Opens.Modifier> ms, String pn)
+             *
+             * Set<String> targets = new HashSet<>();
+             * targets.add(t);
+             * :
+             * :
+             *
+             * Set<Modifier> mods = ...
+             * Builder.newOpens(mods, pn, targets);
+             */
+            void newOpens(Set<Opens.Modifier> ms, String pn, Set<String> targets) {
+                int modifiersSetIndex = dedupSetBuilder.indexOfOpensModifiers(ms);
+                if (!targets.isEmpty()) {
+                    int stringSetIndex = dedupSetBuilder.indexOfStringSet(targets);
+                    mv.visitVarInsn(ALOAD, modifiersSetIndex);
+                    mv.visitLdcInsn(pn);
+                    mv.visitVarInsn(ALOAD, stringSetIndex);
+                    mv.visitMethodInsn(INVOKESTATIC, MODULE_DESCRIPTOR_BUILDER,
+                        "newOpens", OPENS_MODIFIER_SET_STRING_SET_SIG, false);
+                } else {
+                    mv.visitVarInsn(ALOAD, modifiersSetIndex);
+                    mv.visitLdcInsn(pn);
+                    mv.visitMethodInsn(INVOKESTATIC, MODULE_DESCRIPTOR_BUILDER,
+                        "newOpens", OPENS_MODIFIER_SET_STRING_SIG, false);
+                }
+            }
+
+            /*
+             * Invoke Builder.uses(Set<String> uses)
+             */
+            void uses(Set<String> uses) {
+                int varIndex = dedupSetBuilder.indexOfStringSet(uses);
+                mv.visitVarInsn(ALOAD, BUILDER_VAR);
+                mv.visitVarInsn(ALOAD, varIndex);
+                mv.visitMethodInsn(INVOKEVIRTUAL, MODULE_DESCRIPTOR_BUILDER,
+                    "uses", SET_SIG, false);
+                mv.visitInsn(POP);
+            }
+
+            /*
+            * Call Builder::newProvides to create Provides instances and
+            * then pass it to the builder by calling:
+            *      Builder.provides(Provides[] provides)
+            *
+            */
+            void provides(Collection<Provides> provides) {
+                mv.visitVarInsn(ALOAD, BUILDER_VAR);
+                pushInt(mv, provides.size());
+                mv.visitTypeInsn(ANEWARRAY, "java/lang/module/ModuleDescriptor$Provides");
+                int arrayIndex = 0;
+                for (Provides provide : provides) {
+                    mv.visitInsn(DUP);    // arrayref
+                    pushInt(mv, arrayIndex++);
+                    newProvides(provide.service(), provide.providers());
+                    mv.visitInsn(AASTORE);
+                }
+                mv.visitMethodInsn(INVOKEVIRTUAL, MODULE_DESCRIPTOR_BUILDER,
+                    "provides", PROVIDES_ARRAY_SIG, false);
+            }
+
+            /*
+             * Invoke Builder.newProvides(String service, Set<String> providers)
+             *
+             * Set<String> providers = new HashSet<>();
+             * providers.add(impl);
+             * :
+             * :
+             * Builder.newProvides(service, providers);
+             */
+            void newProvides(String service, List<String> providers) {
+                mv.visitLdcInsn(service);
+                pushInt(mv, providers.size());
+                mv.visitTypeInsn(ANEWARRAY, "java/lang/String");
+                int arrayIndex = 0;
+                for (String provider : providers) {
+                    mv.visitInsn(DUP);    // arrayref
+                    pushInt(mv, arrayIndex++);
+                    mv.visitLdcInsn(provider);
+                    mv.visitInsn(AASTORE);
+                }
+                mv.visitMethodInsn(INVOKESTATIC, "java/util/List",
+                    "of", "([Ljava/lang/Object;)Ljava/util/List;", true);
+                mv.visitMethodInsn(INVOKESTATIC, MODULE_DESCRIPTOR_BUILDER,
+                    "newProvides", PROVIDES_STRING_LIST_SIG, false);
+            }
+
+            /*
+             * Invoke Builder.packages(String pn)
+             */
+            void packages(Set<String> packages) {
+                int varIndex = dedupSetBuilder.newStringSet(packages);
+                mv.visitVarInsn(ALOAD, BUILDER_VAR);
+                mv.visitVarInsn(ALOAD, varIndex);
+                mv.visitMethodInsn(INVOKEVIRTUAL, MODULE_DESCRIPTOR_BUILDER,
+                    "packages", SET_SIG, false);
+                mv.visitInsn(POP);
+            }
+
+            /*
+             * Invoke Builder.mainClass(String cn)
+             */
+            void mainClass(String cn) {
+                mv.visitVarInsn(ALOAD, BUILDER_VAR);
+                mv.visitLdcInsn(cn);
+                mv.visitMethodInsn(INVOKEVIRTUAL, MODULE_DESCRIPTOR_BUILDER,
+                    "mainClass", STRING_SIG, false);
+                mv.visitInsn(POP);
+            }
+
+            /*
+             * Invoke Builder.version(Version v);
+             */
+            void version(Version v) {
+                mv.visitVarInsn(ALOAD, BUILDER_VAR);
+                mv.visitLdcInsn(v.toString());
+                mv.visitMethodInsn(INVOKEVIRTUAL, MODULE_DESCRIPTOR_BUILDER,
+                    "version", STRING_SIG, false);
+                mv.visitInsn(POP);
+            }
+
+            void invokeBuilderMethod(String methodName, String value) {
+                mv.visitVarInsn(ALOAD, BUILDER_VAR);
+                mv.visitLdcInsn(value);
+                mv.visitMethodInsn(INVOKEVIRTUAL, MODULE_DESCRIPTOR_BUILDER,
+                    methodName, STRING_SIG, false);
+                mv.visitInsn(POP);
+            }
+        }
+
+        class ModuleHashesBuilder {
+            private static final String MODULE_HASHES_BUILDER =
+                "jdk/internal/module/ModuleHashes$Builder";
+            private static final String MODULE_HASHES_BUILDER_TYPE =
+                "L" + MODULE_HASHES_BUILDER + ";";
+            static final String STRING_BYTE_ARRAY_SIG =
+                "(Ljava/lang/String;[B)" + MODULE_HASHES_BUILDER_TYPE;
+
+            final ModuleHashes recordedHashes;
+            final MethodVisitor hmv;
+            final int index;
+
+            ModuleHashesBuilder(ModuleHashes hashes, int index, MethodVisitor hmv) {
+                this.recordedHashes = hashes;
+                this.hmv = hmv;
+                this.index = index;
+            }
+
+            /**
+             * Build ModuleHashes
+             */
+            void build() {
+                if (recordedHashes == null)
+                    return;
+
+                // new jdk.internal.module.ModuleHashes.Builder
+                newModuleHashesBuilder();
+
+                // Invoke ModuleHashes.Builder::hashForModule
+                recordedHashes
+                    .names()
+                    .forEach(mn -> hashForModule(mn, recordedHashes.hashFor(mn)));
+
+                // Put ModuleHashes into the hashes array
+                pushModuleHashes();
+            }
+
+
+            /*
+             * Create ModuleHashes.Builder instance
+             */
+            void newModuleHashesBuilder() {
+                hmv.visitTypeInsn(NEW, MODULE_HASHES_BUILDER);
+                hmv.visitInsn(DUP);
+                hmv.visitLdcInsn(recordedHashes.algorithm());
+                pushInt(hmv, ((4 * recordedHashes.names().size()) / 3) + 1);
+                hmv.visitMethodInsn(INVOKESPECIAL, MODULE_HASHES_BUILDER,
+                    "<init>", "(Ljava/lang/String;I)V", false);
+                hmv.visitVarInsn(ASTORE, BUILDER_VAR);
+                hmv.visitVarInsn(ALOAD, BUILDER_VAR);
+            }
+
+
+            /*
+             * Invoke ModuleHashes.Builder::build and put the returned
+             * ModuleHashes to the hashes array
+             */
+            void pushModuleHashes() {
+                hmv.visitVarInsn(ALOAD, MH_VAR);
+                pushInt(hmv, index);
+                hmv.visitVarInsn(ALOAD, BUILDER_VAR);
+                hmv.visitMethodInsn(INVOKEVIRTUAL, MODULE_HASHES_BUILDER,
+                    "build", "()Ljdk/internal/module/ModuleHashes;",
+                    false);
+                hmv.visitInsn(AASTORE);
+            }
+
+            /*
+             * Invoke ModuleHashes.Builder.hashForModule(String name, byte[] hash);
+             */
+            void hashForModule(String name, byte[] hash) {
+                hmv.visitVarInsn(ALOAD, BUILDER_VAR);
+                hmv.visitLdcInsn(name);
+
+                pushInt(hmv, hash.length);
+                hmv.visitIntInsn(NEWARRAY, T_BYTE);
+                for (int i = 0; i < hash.length; i++) {
+                    hmv.visitInsn(DUP);              // arrayref
+                    pushInt(hmv, i);
+                    hmv.visitIntInsn(BIPUSH, hash[i]);
+                    hmv.visitInsn(BASTORE);
+                }
+
+                hmv.visitMethodInsn(INVOKEVIRTUAL, MODULE_HASHES_BUILDER,
+                    "hashForModule", STRING_BYTE_ARRAY_SIG, false);
+                hmv.visitInsn(POP);
+            }
+        }
+
+        /*
+         * Wraps set creation, ensuring identical sets are properly deduplicated.
+         */
+        class DedupSetBuilder {
+            // map Set<String> to a specialized builder to allow them to be
+            // deduplicated as they are requested
+            final Map<Set<String>, SetBuilder<String>> stringSets = new HashMap<>();
+
+            // map Set<Requires.Modifier> to a specialized builder to allow them to be
+            // deduplicated as they are requested
+            final Map<Set<Requires.Modifier>, EnumSetBuilder<Requires.Modifier>>
+                requiresModifiersSets = new HashMap<>();
+
+            // map Set<Exports.Modifier> to a specialized builder to allow them to be
+            // deduplicated as they are requested
+            final Map<Set<Exports.Modifier>, EnumSetBuilder<Exports.Modifier>>
+                exportsModifiersSets = new HashMap<>();
+
+            // map Set<Opens.Modifier> to a specialized builder to allow them to be
+            // deduplicated as they are requested
+            final Map<Set<Opens.Modifier>, EnumSetBuilder<Opens.Modifier>>
+                opensModifiersSets = new HashMap<>();
+
+            private final int stringSetVar;
+            private final int enumSetVar;
+            private final IntSupplier localVarSupplier;
+
+            DedupSetBuilder(IntSupplier localVarSupplier) {
+                this.stringSetVar = localVarSupplier.getAsInt();
+                this.enumSetVar = localVarSupplier.getAsInt();
+                this.localVarSupplier = localVarSupplier;
+            }
+
+            /*
+             * Add the given set of strings to this builder.
+             */
+            void stringSet(Set<String> strings) {
+                stringSets.computeIfAbsent(strings,
+                    s -> new SetBuilder<>(s, stringSetVar, localVarSupplier)
+                ).increment();
+            }
+
+            /*
+             * Add the given set of Exports.Modifiers
+             */
+            void exportsModifiers(Set<Exports.Modifier> mods) {
+                exportsModifiersSets.computeIfAbsent(mods, s ->
+                                new EnumSetBuilder<>(s, EXPORTS_MODIFIER_CLASSNAME,
+                                        enumSetVar, localVarSupplier)
+                ).increment();
+            }
+
+            /*
+             * Add the given set of Opens.Modifiers
+             */
+            void opensModifiers(Set<Opens.Modifier> mods) {
+                opensModifiersSets.computeIfAbsent(mods, s ->
+                                new EnumSetBuilder<>(s, OPENS_MODIFIER_CLASSNAME,
+                                        enumSetVar, localVarSupplier)
+                ).increment();
+            }
+
+            /*
+             * Add the given set of Requires.Modifiers
+             */
+            void requiresModifiers(Set<Requires.Modifier> mods) {
+                requiresModifiersSets.computeIfAbsent(mods, s ->
+                    new EnumSetBuilder<>(s, REQUIRES_MODIFIER_CLASSNAME,
+                                         enumSetVar, localVarSupplier)
+                ).increment();
+            }
+
+            /*
+             * Retrieve the index to the given set of Strings. Emit code to
+             * generate it when SetBuilder::build is called.
+             */
+            int indexOfStringSet(Set<String> names) {
+                return stringSets.get(names).build();
+            }
+
+            /*
+             * Retrieve the index to the given set of Exports.Modifier.
+             * Emit code to generate it when EnumSetBuilder::build is called.
+             */
+            int indexOfExportsModifiers(Set<Exports.Modifier> mods) {
+                return exportsModifiersSets.get(mods).build();
+            }
+
+            /**
+             * Retrieve the index to the given set of Opens.Modifier.
+             * Emit code to generate it when EnumSetBuilder::build is called.
+             */
+            int indexOfOpensModifiers(Set<Opens.Modifier> mods) {
+                return opensModifiersSets.get(mods).build();
+            }
+
+
+            /*
+             * Retrieve the index to the given set of Requires.Modifier.
+             * Emit code to generate it when EnumSetBuilder::build is called.
+             */
+            int indexOfRequiresModifiers(Set<Requires.Modifier> mods) {
+                return requiresModifiersSets.get(mods).build();
+            }
+
+            /*
+             * Build a new string set without any attempt to deduplicate it.
+             */
+            int newStringSet(Set<String> names) {
+                int index = new SetBuilder<>(names, stringSetVar, localVarSupplier).build();
+                assert index == stringSetVar;
+                return index;
+            }
+        }
+
+        /*
+         * SetBuilder generates bytecode to create one single instance of Set
+         * for a given set of elements and assign to a local variable slot.
+         * When there is only one single reference to a Set<T>,
+         * it will reuse defaultVarIndex.  For a Set with multiple references,
+         * it will use a new local variable retrieved from the nextLocalVar
+         */
+        class SetBuilder<T> {
+            private final Set<T> elements;
+            private final int defaultVarIndex;
+            private final IntSupplier nextLocalVar;
+            private int refCount;
+            private int localVarIndex;
+
+            SetBuilder(Set<T> elements,
+                       int defaultVarIndex,
+                       IntSupplier nextLocalVar) {
+                this.elements = elements;
+                this.defaultVarIndex = defaultVarIndex;
+                this.nextLocalVar = nextLocalVar;
+            }
+
+            /*
+             * Increments the number of references to this particular set.
+             */
+            final void increment() {
+                refCount++;
+            }
+
+            /**
+             * Generate the appropriate instructions to load an object reference
+             * to the element onto the stack.
+             */
+            void visitElement(T element, MethodVisitor mv) {
+                mv.visitLdcInsn(element);
+            }
+
+            /*
+             * Build bytecode for the Set represented by this builder,
+             * or get the local variable index of a previously generated set
+             * (in the local scope).
+             *
+             * @return local variable index of the generated set.
+             */
+            final int build() {
+                int index = localVarIndex;
+                if (localVarIndex == 0) {
+                    // if non-empty and more than one set reference this builder,
+                    // emit to a unique local
+                    index = refCount <= 1 ? defaultVarIndex
+                                          : nextLocalVar.getAsInt();
+                    if (index < MAX_LOCAL_VARS) {
+                        localVarIndex = index;
+                    } else {
+                        // overflow: disable optimization by using localVarIndex = 0
+                        index = defaultVarIndex;
+                    }
+
+                    generateSetOf(index);
+                }
+                return index;
+            }
+
+            private void generateSetOf(int index) {
+                if (elements.size() <= 10) {
+                    // call Set.of(e1, e2, ...)
+                    StringBuilder sb = new StringBuilder("(");
+                    for (T t : elements) {
+                        sb.append("Ljava/lang/Object;");
+                        visitElement(t, mv);
+                    }
+                    sb.append(")Ljava/util/Set;");
+                    mv.visitMethodInsn(INVOKESTATIC, "java/util/Set",
+                            "of", sb.toString(), true);
+                } else {
+                    // call Set.of(E... elements)
+                    pushInt(mv, elements.size());
+                    mv.visitTypeInsn(ANEWARRAY, "java/lang/String");
+                    int arrayIndex = 0;
+                    for (T t : elements) {
+                        mv.visitInsn(DUP);    // arrayref
+                        pushInt(mv, arrayIndex);
+                        visitElement(t, mv);  // value
+                        mv.visitInsn(AASTORE);
+                        arrayIndex++;
+                    }
+                    mv.visitMethodInsn(INVOKESTATIC, "java/util/Set",
+                            "of", "([Ljava/lang/Object;)Ljava/util/Set;", true);
+                }
+                mv.visitVarInsn(ASTORE, index);
+            }
+        }
+
+        /*
+         * Generates bytecode to create one single instance of EnumSet
+         * for a given set of modifiers and assign to a local variable slot.
+         */
+        class EnumSetBuilder<T> extends SetBuilder<T> {
+
+            private final String className;
+
+            EnumSetBuilder(Set<T> modifiers, String className,
+                           int defaultVarIndex,
+                           IntSupplier nextLocalVar) {
+                super(modifiers, defaultVarIndex, nextLocalVar);
+                this.className = className;
+            }
+
+            /**
+             * Loads an Enum field.
+             */
+            void visitElement(T t, MethodVisitor mv) {
+                mv.visitFieldInsn(GETSTATIC, className, t.toString(),
+                                  "L" + className + ";");
+            }
+        }
+    }
+
+    /**
+     * Generate SystemModulesMap and add it as a resource.
+     *
+     * @return the name of the class resource added to the pool
+     */
+    private String genSystemModulesMapClass(String allSystemModulesClassName,
+                                            String defaultSystemModulesClassName,
+                                            Map<String, String> map,
+                                            ResourcePoolBuilder out) {
+        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS
+                                         + ClassWriter.COMPUTE_FRAMES);
+        cw.visit(Opcodes.V1_8,
+                 ACC_FINAL+ACC_SUPER,
+                 SYSTEM_MODULES_MAP_CLASS,
+                 null,
+                 "java/lang/Object",
+                 null);
+
+        // <init>
+        MethodVisitor mv = cw.visitMethod(0, "<init>", "()V", null, null);
+        mv.visitVarInsn(ALOAD, 0);
+        mv.visitMethodInsn(INVOKESPECIAL,
+                           "java/lang/Object",
+                           "<init>",
+                           "()V",
+                           false);
+        mv.visitInsn(RETURN);
+        mv.visitMaxs(0, 0);
+        mv.visitEnd();
+
+        // allSystemModules()
+        mv = cw.visitMethod(ACC_STATIC,
+                            "allSystemModules",
+                            "()Ljdk/internal/module/SystemModules;",
+                            "()Ljdk/internal/module/SystemModules;",
+                            null);
+        mv.visitCode();
+        mv.visitTypeInsn(NEW, allSystemModulesClassName);
+        mv.visitInsn(DUP);
+        mv.visitMethodInsn(INVOKESPECIAL,
+                           allSystemModulesClassName,
+                           "<init>",
+                           "()V",
+                           false);
+        mv.visitInsn(ARETURN);
+        mv.visitMaxs(0, 0);
+        mv.visitEnd();
+
+        // defaultSystemModules()
+        mv = cw.visitMethod(ACC_STATIC,
+                            "defaultSystemModules",
+                            "()Ljdk/internal/module/SystemModules;",
+                            "()Ljdk/internal/module/SystemModules;",
+                            null);
+        mv.visitCode();
+        mv.visitTypeInsn(NEW, defaultSystemModulesClassName);
+        mv.visitInsn(DUP);
+        mv.visitMethodInsn(INVOKESPECIAL,
+                           defaultSystemModulesClassName,
+                           "<init>",
+                           "()V",
+                           false);
+        mv.visitInsn(ARETURN);
+        mv.visitMaxs(0, 0);
+        mv.visitEnd();
+
+        // moduleNames()
+        mv = cw.visitMethod(ACC_STATIC,
+                            "moduleNames",
+                            "()[Ljava/lang/String;",
+                            "()[Ljava/lang/String;",
+                            null);
+        mv.visitCode();
+        pushInt(mv, map.size());
+        mv.visitTypeInsn(ANEWARRAY, "java/lang/String");
+
+        int index = 0;
+        for (String moduleName : map.keySet()) {
+            mv.visitInsn(DUP);                  // arrayref
+            pushInt(mv, index);
+            mv.visitLdcInsn(moduleName);
+            mv.visitInsn(AASTORE);
+            index++;
+        }
+
+        mv.visitInsn(ARETURN);
+        mv.visitMaxs(0, 0);
+        mv.visitEnd();
+
+        // classNames()
+        mv = cw.visitMethod(ACC_STATIC,
+                            "classNames",
+                            "()[Ljava/lang/String;",
+                            "()[Ljava/lang/String;",
+                            null);
+        mv.visitCode();
+        pushInt(mv, map.size());
+        mv.visitTypeInsn(ANEWARRAY, "java/lang/String");
+
+        index = 0;
+        for (String className : map.values()) {
+            mv.visitInsn(DUP);                  // arrayref
+            pushInt(mv, index);
+            mv.visitLdcInsn(className.replace('/', '.'));
+            mv.visitInsn(AASTORE);
+            index++;
+        }
+
+        mv.visitInsn(ARETURN);
+        mv.visitMaxs(0, 0);
+        mv.visitEnd();
+
+        // write the class file to the pool as a resource
+        String rn = "/java.base/" + SYSTEM_MODULES_MAP_CLASS + ".class";
+        ResourcePoolEntry e = ResourcePoolEntry.create(rn, cw.toByteArray());
+        out.add(e);
+
+        return rn;
+    }
+
+    /**
+     * Pushes an int constant
+     */
+    private static void pushInt(MethodVisitor mv, int value) {
+        if (value <= 5) {
+            mv.visitInsn(ICONST_0 + value);
+        } else if (value < Byte.MAX_VALUE) {
+            mv.visitIntInsn(BIPUSH, value);
+        } else if (value < Short.MAX_VALUE) {
+            mv.visitIntInsn(SIPUSH, value);
+        } else {
+            throw new IllegalArgumentException("exceed limit: " + value);
+        }
+    }
+
+    /**
+     * Returns a module finder that finds all modules in the given list
+     */
+    private static ModuleFinder finderOf(Collection<ModuleInfo> moduleInfos) {
+        Supplier<ModuleReader> readerSupplier = () -> null;
+        Map<String, ModuleReference> namesToReference = new HashMap<>();
+        for (ModuleInfo mi : moduleInfos) {
+            String name = mi.moduleName();
+            ModuleReference mref
+                = new ModuleReferenceImpl(mi.descriptor(),
+                                          URI.create("jrt:/" + name),
+                                          readerSupplier,
+                                          null,
+                                          mi.target(),
+                                          null,
+                                          null,
+                                          mi.moduleResolution());
+            namesToReference.put(name, mref);
+        }
+
+        return new ModuleFinder() {
+            @Override
+            public Optional<ModuleReference> find(String name) {
+                Objects.requireNonNull(name);
+                return Optional.ofNullable(namesToReference.get(name));
+            }
+            @Override
+            public Set<ModuleReference> findAll() {
+                return new HashSet<>(namesToReference.values());
+            }
+        };
+    }
+}