1 /*
  2  * Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.  Oracle designates this
  8  * particular file as subject to the "Classpath" exception as provided
  9  * by Oracle in the LICENSE file that accompanied this code.
 10  *
 11  * This code is distributed in the hope that it will be useful, but WITHOUT
 12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 14  * version 2 for more details (a copy is included in the LICENSE file that
 15  * accompanied this code).
 16  *
 17  * You should have received a copy of the GNU General Public License version
 18  * 2 along with this work; if not, write to the Free Software Foundation,
 19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 20  *
 21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 22  * or visit www.oracle.com if you need additional information or have any
 23  * questions.
 24  */
 25 package jdk.internal.foreign.abi;
 26 
 27 import java.lang.foreign.FunctionDescriptor;
 28 import java.lang.foreign.Linker;
 29 import java.util.HashMap;
 30 import java.util.Map;
 31 import java.util.Objects;
 32 import java.util.Set;
 33 import java.util.function.BiConsumer;
 34 import java.util.stream.Stream;
 35 
 36 public class LinkerOptions {
 37 
 38     private static final LinkerOptions EMPTY = new LinkerOptions(Map.of());
 39     private final Map<Class<?>, LinkerOptionImpl> optionsMap;
 40 
 41     private LinkerOptions(Map<Class<?>, LinkerOptionImpl> optionsMap) {
 42         this.optionsMap = optionsMap;
 43     }
 44 
 45     public static LinkerOptions forDowncall(FunctionDescriptor desc, Linker.Option... options) {
 46         return forShared(LinkerOptionImpl::validateForDowncall, desc, options);
 47     }
 48 
 49     public static LinkerOptions forUpcall(FunctionDescriptor desc, Linker.Option[] options) {
 50         return forShared(LinkerOptionImpl::validateForUpcall, desc, options);
 51     }
 52 
 53     private static LinkerOptions forShared(BiConsumer<LinkerOptionImpl, FunctionDescriptor> validator,
 54                                            FunctionDescriptor desc, Linker.Option... options) {
 55        Map<Class<?>, LinkerOptionImpl> optionMap = new HashMap<>();
 56 
 57         for (Linker.Option option : options) {
 58             if (optionMap.containsKey(option.getClass())) {
 59                 throw new IllegalArgumentException("Duplicate option: " + option);
 60             }
 61             LinkerOptionImpl opImpl = (LinkerOptionImpl) option;
 62             validator.accept(opImpl, desc);
 63             optionMap.put(option.getClass(), opImpl);
 64         }
 65 
 66         LinkerOptions linkerOptions = new LinkerOptions(optionMap);
 67         if (linkerOptions.hasCapturedCallState() && linkerOptions.isCritical()) {
 68             throw new IllegalArgumentException("Incompatible linker options: captureCallState, critical");
 69         }
 70         return linkerOptions;
 71     }
 72 
 73     public static LinkerOptions empty() {
 74         return EMPTY;
 75     }
 76 
 77     private <T extends Linker.Option> T getOption(Class<T> type) {
 78         return type.cast(optionsMap.get(type));
 79     }
 80 
 81     public boolean isVarargsIndex(int argIndex) {
 82         FirstVariadicArg fva = getOption(FirstVariadicArg.class);
 83         return fva != null && argIndex >= fva.index();
 84     }
 85 
 86     public boolean hasCapturedCallState() {
 87         return getOption(CaptureCallState.class) != null;
 88     }
 89 
 90     public Stream<CapturableState> capturedCallState() {
 91         CaptureCallState stl = getOption(CaptureCallState.class);
 92         return stl == null ? Stream.empty() : stl.saved().stream();
 93     }
 94 
 95     public boolean isVariadicFunction() {
 96         FirstVariadicArg fva = getOption(FirstVariadicArg.class);
 97         return fva != null;
 98     }
 99 
100     public int firstVariadicArgIndex() {
101         return getOption(FirstVariadicArg.class).index();
102     }
103 
104     public boolean isCritical() {
105         Critical c = getOption(Critical.class);
106         return c != null;
107     }
108 
109     @Override
110     public boolean equals(Object o) {
111         if (this == o) return true;
112         return o instanceof LinkerOptions that
113                 && Objects.equals(optionsMap, that.optionsMap);
114     }
115 
116     @Override
117     public int hashCode() {
118         return Objects.hash(optionsMap);
119     }
120 
121     public sealed interface LinkerOptionImpl extends Linker.Option
122             permits CaptureCallState, FirstVariadicArg, Critical {
123         default void validateForDowncall(FunctionDescriptor descriptor) {
124             throw new IllegalArgumentException("Not supported for downcall: " + this);
125         }
126 
127         default void validateForUpcall(FunctionDescriptor descriptor) {
128             throw new IllegalArgumentException("Not supported for upcall: " + this);
129         }
130     }
131 
132     public record FirstVariadicArg(int index) implements LinkerOptionImpl {
133         @Override
134         public void validateForDowncall(FunctionDescriptor descriptor) {
135             if (index < 0 || index > descriptor.argumentLayouts().size()) {
136                 throw new IllegalArgumentException("Index '" + index + "' not in bounds for descriptor: " + descriptor);
137             }
138         }
139     }
140 
141     public record CaptureCallState(Set<CapturableState> saved) implements LinkerOptionImpl {
142         @Override
143         public void validateForDowncall(FunctionDescriptor descriptor) {
144             // done during construction
145         }
146     }
147 
148     public record Critical() implements LinkerOptionImpl {
149         public static Critical INSTANCE = new Critical();
150 
151         @Override
152         public void validateForDowncall(FunctionDescriptor descriptor) {
153             // always allowed
154         }
155     }
156 }