1 /*
   2  * Copyright (c) 2018, 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 
  26 package jdk.internal.foreign;
  27 
  28 import java.foreign.layout.Layout;
  29 import java.foreign.memory.MemoryAddress;
  30 import java.foreign.memory.MemoryScope;
  31 import java.util.ArrayList;
  32 import java.util.HashSet;
  33 import java.util.Optional;
  34 import java.util.Set;
  35 
  36 import jdk.internal.misc.Unsafe;
  37 
  38 public final class MemoryScopeImpl implements MemoryScope {
  39 
  40     // FIXME: Move this, make it dynamic and correct
  41     private final static long ALLOC_ALIGNMENT = 8;
  42 
  43     private final static Unsafe U = Unsafe.getUnsafe();
  44     // 64KB block
  45     private final static long UNIT_SIZE = 64 * 1024;
  46 
  47     public State state = ALIVE;
  48     private Set<MemoryScope> descendants = new HashSet<>();
  49 
  50     // the address of allocated memory
  51     private long block;
  52     // the first offset of available memory
  53     private long free_offset;
  54     // the free offset when start transaction
  55     private long transaction_origin;
  56     // the scope owner
  57     private final MemoryScopeImpl parent;
  58     // the scope charateristics
  59     private final long charateristics;
  60     // the scope thread (if confined)
  61     private final Thread thread;
  62 
  63     //list of used blocks
  64     private final ArrayList<Long> used_blocks;
  65 
  66     static class State {
  67         boolean isActive() { return false; }
  68     }
  69 
  70     final static State ALIVE = new State() {
  71         boolean isActive() { return true; }
  72     };
  73 
  74     final static State CLOSED = new State();
  75     final static State MERGED = new State();
  76 
  77     public static final MemoryScopeImpl GLOBAL = new MemoryScopeImpl(null, PINNED);
  78     public static final MemoryScopeImpl UNCHECKED = new MemoryScopeImpl(null, PINNED | MemoryScope.UNCHECKED);
  79     
  80     public MemoryScopeImpl(MemoryScopeImpl parent, long charateristics) {
  81         this.parent = parent;
  82         block = U.allocateMemory(UNIT_SIZE);
  83         free_offset = 0;
  84         transaction_origin = -1;
  85         used_blocks = new ArrayList<>();
  86         this.charateristics = charateristics;
  87         this.thread = (charateristics & CONFINED) != 0 ?
  88             Thread.currentThread() : null;
  89     }
  90 
  91     public void checkAlive() {
  92         if (thread != null && thread != Thread.currentThread()) {
  93             throw new IllegalStateException("Attempt to access scope outside confinement thread!");
  94         } else if ((charateristics & MemoryScope.UNCHECKED) != 0 || state == ALIVE) {
  95             return;
  96         } else if (state == CLOSED) {
  97             throw new IllegalStateException("Scope is not alive");
  98         } else if (state == MERGED) {
  99             ((MemoryScopeImpl) parent()).checkAlive();
 100         }
 101     }
 102 
 103     @Override
 104     public Optional<Thread> confinementThread() {
 105         return Optional.ofNullable(thread);
 106     }
 107 
 108     @Override
 109     public MemoryScope parent() {
 110         return parent;
 111     }
 112 
 113     @Override
 114     public MemoryScope fork() {
 115         MemoryScope forkedScope = new MemoryScopeImpl(this, 0L);
 116         descendants.add(forkedScope);
 117         return forkedScope;
 118     }
 119 
 120     @Override
 121     public MemoryScope fork(long charateristics) {
 122         return new MemoryScopeImpl(this, charateristics);
 123     }
 124 
 125     @Override
 126     public long characteristics() {
 127         return 0;
 128     }
 129 
 130     @Override
 131     public MemoryAddress allocate(Layout layout) {
 132         return allocate(layout, 1);
 133     }
 134 
 135     public MemoryAddress allocate(Layout layout, int align) {
 136         // FIXME: when allocating structs align size up to 8 bytes to allow for raw reads/writes?
 137         long size = alignUp(layout.bitsSize() / 8, align);
 138         if (size < 0) {
 139             throw new UnsupportedOperationException("Unknown size for layout: " + layout);
 140         }
 141 
 142         if (size > Integer.MAX_VALUE) {
 143             throw new UnsupportedOperationException("allocate size to large");
 144         }
 145 
 146         return new MemoryAddressImpl(this, allocateRegion(size));
 147     }
 148 
 149     MemoryBoundInfo allocateRegion(long size) {
 150         checkAllocate();
 151         if (size == 0) {
 152             return MemoryBoundInfo.NOTHING;
 153         }
 154 
 155         return MemoryBoundInfo.ofNative(allocate(size), size);
 156     }
 157 
 158     private void rollbackAllocation() {
 159         if (transaction_origin < 0) {
 160             return;
 161         }
 162 
 163         free_offset = transaction_origin;
 164         transaction_origin = -1;
 165     }
 166 
 167     private long allocate(long size) {
 168         if (size <= 0) {
 169             rollbackAllocation();
 170             throw new IllegalArgumentException();
 171         }
 172 
 173         long boundary = free_offset + size;
 174 
 175         if (boundary > UNIT_SIZE) {
 176             try {
 177                 long newBuf;
 178                 if (size >= (UNIT_SIZE >> 1)) {
 179                     // Need more than half block, just allocate for it
 180                     newBuf = U.allocateMemory(size);
 181                     used_blocks.add(newBuf);
 182                 } else {
 183                     // less than half block left, start a new block
 184                     // shrink current block
 185                     newBuf = U.reallocateMemory(block, free_offset);
 186                     // We want to revisit strategy if shrink is need a copy
 187                     assert newBuf == block;
 188                     used_blocks.add(block);
 189                     // create a new block
 190                     newBuf = block = U.allocateMemory(UNIT_SIZE);
 191                     free_offset = alignUp(size, ALLOC_ALIGNMENT);
 192                 }
 193                 // new buffer allocated, commit partial transaction for simplification
 194                 transaction_origin = -1;
 195                 return newBuf;
 196             } catch (OutOfMemoryError ome) {
 197                 rollbackAllocation();
 198                 throw ome;
 199             }
 200         }
 201 
 202         long rv = block + free_offset;
 203         free_offset += alignUp(size, ALLOC_ALIGNMENT);
 204 
 205         if ((rv % ALLOC_ALIGNMENT) != 0) {
 206             throw new RuntimeException("Invalid alignment: 0x" + Long.toHexString(rv));
 207         }
 208 
 209         return rv;
 210     }
 211 
 212     public void checkAllocate() {
 213         if (!state.isActive()) {
 214             throw new IllegalStateException("Cannot allocate after close() or merge()");
 215         }
 216     }
 217 
 218     void checkTerminal() {
 219         if (thread != null && thread != Thread.currentThread()) {
 220             throw new IllegalStateException("Attempt to access scope outside confinement thread!");
 221         } else if ((charateristics & PINNED) != 0) {
 222             throw new IllegalStateException("Terminal operations close() or merge() not supported by this scope!");
 223         }
 224     }
 225 
 226     @Override
 227     public void merge() {
 228         checkTerminal();
 229         //copy descendants
 230         parent.descendants.addAll(descendants);
 231         descendants.clear();
 232         //copy used and current blocks
 233         parent.used_blocks.addAll(used_blocks);
 234         parent.used_blocks.add(block);
 235         //change state
 236         state = MERGED;
 237     }
 238 
 239     @Override
 240     public void close() {
 241         checkTerminal();
 242         state = CLOSED;
 243         for (Long addr: used_blocks) {
 244             U.freeMemory(addr);
 245         }
 246         used_blocks.clear();
 247         U.freeMemory(block);
 248         //close descendants
 249         for (MemoryScope s : descendants) {
 250             s.close();
 251         }
 252         //remove from parent
 253         parent.descendants.remove(this);
 254     }
 255 
 256     public static void checkAncestor(MemoryAddress a1, MemoryAddress a2) {
 257         if (!isAncestor(a1.scope(), a2.scope())) {
 258             throw new RuntimeException("Access denied");
 259         }
 260     }
 261 
 262     private static boolean isAncestor(MemoryScope s1, MemoryScope s2) {
 263         if ((s1.characteristics() & MemoryScope.UNCHECKED) != 0 ||
 264                 (s2.characteristics() & MemoryScope.UNCHECKED) != 0 ||
 265                 s1 == s2) {
 266             return true;
 267         } else if (s2 == null) {
 268             return false;
 269         } else {
 270             return isAncestor(s1, s2.parent());
 271         }
 272     }
 273 
 274     public static long alignUp(long n, long alignment) {
 275         return (n + alignment - 1) & ~(alignment - 1);
 276     }
 277 }