Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -352,4 +352,15 @@ public void fill(byte value, int fromIndex, int toIndex) {
public void drop(int segment) {
dataSegments[segment] = PassiveDataSegment.EMPTY;
}

@Override
public ByteArrayMemory copy() {
ByteArrayMemory copy = new ByteArrayMemory(this.limits, this.allocStrategy);
copy.nPages = this.nPages;
copy.buffer = this.buffer.clone();
if (this.dataSegments != null) {
copy.dataSegments = this.dataSegments.clone();
}
return copy;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -346,4 +346,23 @@ public void fill(byte value, int fromIndex, int toIndex) {
public void drop(int segment) {
dataSegments[segment] = PassiveDataSegment.EMPTY;
}

@Override
public ByteBufferMemory copy() {
ByteBufferMemory copy = new ByteBufferMemory(this.limits, this.allocStrategy);
copy.nPages = this.nPages;

// Copy the ByteBuffer content
copy.buffer = ByteBuffer.allocate(this.buffer.capacity()).order(ByteOrder.LITTLE_ENDIAN);
int originalPosition = this.buffer.position();
this.buffer.position(0);
copy.buffer.put(this.buffer);
this.buffer.position(originalPosition);
copy.buffer.position(originalPosition);

if (this.dataSegments != null) {
copy.dataSegments = this.dataSegments.clone();
}
return copy;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,13 @@ public void setInstance(Instance instance) {
public MutabilityType getMutabilityType() {
return mutabilityType;
}

public GlobalInstance copy() {
GlobalInstance copy =
new GlobalInstance(
this.valueLow, this.valueHigh, this.valType, this.mutabilityType);
// The instance field will be set by the calling code
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The need of resetting/recomputing fields shows that this is a leaky abstraction.

copy.instance = null;
return copy;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -191,4 +191,11 @@ public ImportValues build() {
return importValues;
}
}

public ImportValues copy() {
// Create a new instance using the existing constructor
// which already clones the arrays properly
return new ImportValues(
this.functions, this.globals, this.memories, this.tables, this.tags);
}
}
124 changes: 103 additions & 21 deletions runtime/src/main/java/com/dylibso/chicory/runtime/Instance.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import static java.util.Objects.requireNonNullElse;
import static java.util.Objects.requireNonNullElseGet;

import com.dylibso.chicory.runtime.internal.ExperimentalCopier;
import com.dylibso.chicory.wasm.ChicoryException;
import com.dylibso.chicory.wasm.InvalidException;
import com.dylibso.chicory.wasm.UninstantiableException;
Expand Down Expand Up @@ -44,30 +45,34 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Function;
import java.util.stream.Collectors;

public class Instance {
public static final String START_FUNCTION_NAME = "_start";

private final WasmModule module;
private final Machine machine;
private final FunctionBody[] functions;
private final Memory memory;
private final DataSegment[] dataSegments;
private final Global[] globalInitializers;
private final GlobalInstance[] globals;
private final FunctionType[] types;
private final int[] functionTypes;
private final ImportValues imports;
private final TableInstance[] tables;
private final Element[] elements;
private final TagInstance[] tags;
private final Map<String, Export> exports;
private final ExecutionListener listener;
private final Exports fluentExports;

private final Map<Integer, WasmException> exnRefs;
private static final AtomicLong NEXT_INSTANCE_ID = new AtomicLong(1);

private final long id = NEXT_INSTANCE_ID.getAndIncrement();
protected final WasmModule module;
protected final Machine machine;
protected final FunctionBody[] functions;
protected final Memory memory;
protected final DataSegment[] dataSegments;
protected final Global[] globalInitializers;
protected final GlobalInstance[] globals;
protected final FunctionType[] types;
protected final int[] functionTypes;
protected final ImportValues imports;
protected final TableInstance[] tables;
protected final Element[] elements;
protected final TagInstance[] tags;
protected final Map<String, Export> exports;
protected final ExecutionListener listener;
protected final Exports fluentExports;

protected final Map<Integer, WasmException> exnRefs;
protected final Function<Instance, Machine> machineFactory;

Instance(
WasmModule module,
Expand Down Expand Up @@ -95,6 +100,7 @@ public class Instance {
this.types = types.clone();
this.functionTypes = functionTypes.clone();
this.imports = imports;
this.machineFactory = machineFactory;
this.machine = machineFactory.apply(this);
this.tables = new TableInstance[tables.length];
this.elements = elements.clone();
Expand All @@ -119,6 +125,76 @@ public class Instance {
}
}

protected Instance(Instance original, ExperimentalCopier.Options options) {

this.imports = options.imports() != null ? options.imports() : original.imports.copy();
this.listener = options.listener() != null ? options.listener() : original.listener;

this.module = original.module;
this.globalInitializers = original.globalInitializers.clone();
this.functions = original.functions.clone();
this.memory = (original.memory != null) ? original.memory.copy() : null;
this.dataSegments = original.dataSegments.clone();
this.types = original.types.clone();
this.functionTypes = original.functionTypes.clone();
this.elements = original.elements.clone();
this.exports = original.exports;
this.fluentExports = new Exports(this);

// Copy the current state of tags
this.tags = new TagInstance[original.tags.length];
for (int i = 0; i < original.tags.length; i++) {
TagType type = original.tags[i].tagType();
this.tags[i] = new TagInstance(type);
this.tags[i].setType(types[(type.typeIdx())]);
}

// Copy the current state of globals
this.globals = new GlobalInstance[original.globals.length];
for (int i = 0; i < original.globals.length; i++) {
if (original.globals[i] != null) {
this.globals[i] = original.globals[i].copy();
this.globals[i].setInstance(this);
}
}

// Copy the current state of tables and update instance references
this.tables = new TableInstance[original.tables.length];
for (int i = 0; i < original.tables.length; i++) {
if (original.tables[i] != null) {
this.tables[i] = original.tables[i].copy();
// Update Instance references in the table to point to the copied instance
for (int j = 0; j < this.tables[i].size(); j++) {
if (this.tables[i].instance(j) == original) {
this.tables[i].setRef(j, this.tables[i].ref(j), this);
}
}
}
}

// Copy the current state of tags
for (int i = 0; i < original.tags.length; i++) {
if (original.tags[i] != null) {
this.tags[i] = original.tags[i].copy();
}
}

// Copy exception references
this.exnRefs = new HashMap<>();
for (var entry : original.exnRefs.entrySet()) {
WasmException value = entry.getValue();
if (value.instance() == original) {
// If the exception is from the original instance, copy it
value = new WasmException(this, value.tagIdx(), value.args());
}
this.exnRefs.put(entry.getKey(), value);
}

// Create a new machine instance using the original's machine factory
this.machineFactory = original.machineFactory;
this.machine = this.machineFactory.apply(this);
}

public Instance initialize(boolean start) {
for (var el : elements) {
if (el instanceof ActiveElement) {
Expand Down Expand Up @@ -182,6 +258,10 @@ public Instance initialize(boolean start) {
return this;
}

public long id() {
return id;
}

public FunctionType exportType(String name) {
return type(functionType(exports.get(name).index()));
}
Expand Down Expand Up @@ -384,7 +464,8 @@ public Builder withMemoryFactory(Function<MemoryLimits, Memory> memoryFactory) {
}

/*
* This method is experimental and might be dropped without notice in future releases.
* This method is experimental and might be dropped without notice in future
* releases.
*/
public Builder withUnsafeExecutionListener(ExecutionListener listener) {
this.listener = listener;
Expand Down Expand Up @@ -527,7 +608,8 @@ private void validateHostMemoryType(MemoryImport i, ImportMemory m) {
? Memory.RUNTIME_MAX_PAGES
: i.limits().maximumPages();

// HostMem bounds [x,y] must be within the import bounds [a, b]; i.e., a <= x, y >= b.
// HostMem bounds [x,y] must be within the import bounds [a, b]; i.e., a <= x, y
// >= b.
// In other words, the bounds are not valid when:
// - HostMem current number of pages cannot be less than the import lower bound.
// - HostMem upper bound cannot be larger than the given upper bound.
Expand Down
2 changes: 2 additions & 0 deletions runtime/src/main/java/com/dylibso/chicory/runtime/Memory.java
Original file line number Diff line number Diff line change
Expand Up @@ -140,5 +140,7 @@ default void copy(int dest, int src, int size) {
write(dest, readBytes(src, size));
}

Memory copy();

void drop(int segment);
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,14 @@ public void reset() {
this.refs[i] = REF_NULL_VALUE;
}
}

public TableInstance copy() {
TableInstance copy = new TableInstance(this.table, this.refs.length > 0 ? this.refs[0] : 0);
// Deep copy the mutable arrays
copy.instances = this.instances.clone();
copy.refs = this.refs.clone();
// Note: The Instance references in the instances array will need to be
// updated by the calling code to point to the copied instances
return copy;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,8 @@ public void setType(FunctionType type) {
public FunctionType type() {
return type;
}

public TagInstance copy() {
return new TagInstance(this.tag, this.type);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package com.dylibso.chicory.runtime.internal;

import com.dylibso.chicory.runtime.ExecutionListener;
import com.dylibso.chicory.runtime.ImportValues;
import com.dylibso.chicory.runtime.Instance;

/**
* This class is experimental and may change in future releases.
* It provides a way to copy an Instance with options for execution listeners and import values.
* Copying an Instance is dangerous and should be done with caution. Potential issues include:
* <ul>
* <li>Linking breaks.</li>
* <li>Stateful host functions break: therefore it also breaks WASI.
* </ul>
*/
public final class ExperimentalCopier {

private ExperimentalCopier() {
// Prevent instantiation
}

/**
* Options for copying Instances.
* This class allows setting an execution listener and import values for the copy operation.
*/
public static class Options {
private ExecutionListener listener;
private ImportValues imports;

/**
* Set this if you want to replace the execution listener set on the original Instance.
*/
public Options withListener(ExecutionListener listener) {
this.listener = listener;
return this;
}

/**
* Set this if you want to replace the imports used on the original Instance.
*
* @param imports The import values to use when copying the Instance.
* @return This Options instance for method chaining.
*/
public Options withImports(ImportValues imports) {
this.imports = imports;
return this;
}

public ExecutionListener listener() {
return listener;
}

public ImportValues imports() {
return imports;
}
}

/**
* Creates a new Options instance for copying Instances.
*
* @return A new Options instance with default settings.
*/
public static Options options() {
return new Options();
}

/**
* Copies an Instance with default options.
*
* @param original The original Instance to copy.
* @return A new Instance that is a copy of the original.
*/
public static Instance copy(Instance original) {
return copy(original, options());
}

/**
* Copies an Instance with the specified options.
*
* @param original The original Instance to copy.
* @param options The options for copying, including execution listener and import values.
* @return A new Instance that is a copy of the original.
*/
public static Instance copy(Instance original, Options options) {
// We have to subclass Instance to access this protected constructor
return new Instance(original, options) {};
}
}
Loading
Loading