-
Notifications
You must be signed in to change notification settings - Fork 47
/
Store.cs
384 lines (322 loc) · 13.7 KB
/
Store.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
using System;
using System.Collections.Concurrent;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
namespace Wasmtime
{
/// <summary>
/// Represents context about a <see cref="Wasmtime.Store"/>.
/// </summary>
internal readonly ref struct StoreContext
{
internal StoreContext(IntPtr handle)
{
this.handle = handle;
}
internal void GC()
{
Native.wasmtime_context_gc(handle);
}
internal ulong GetFuel()
{
var error = Native.wasmtime_context_get_fuel(handle, out ulong fuel);
if (error != IntPtr.Zero)
{
throw WasmtimeException.FromOwnedError(error);
}
return fuel;
}
internal void SetFuel(ulong fuel)
{
var error = Native.wasmtime_context_set_fuel(handle, fuel);
if (error != IntPtr.Zero)
{
throw WasmtimeException.FromOwnedError(error);
}
}
internal Store Store
{
get
{
var data = Native.wasmtime_context_get_data(handle);
// Since this is a weak handle, it could be `null` if the target object (`Store`)
// was already collected. However, this would be an error in wasmtime-dotnet
// itself because the `Store` must be kept alive when this is called, and
// therefore this should never happen (otherwise, when the `Store` was already
// GCed, its `Handle` might also be GCed and have run its finalizer, which
// would already have freed the `GCHandle` (from the Finalize callback) and thus
// it would already be undefined behavior to try to get the `GCHandle` from the
// `IntPtr` value).
var targetStore = (Store?)GCHandle.FromIntPtr(data).Target!;
return targetStore;
}
}
internal void SetWasiConfiguration(WasiConfiguration config)
{
var wasi = config.Build();
var error = Native.wasmtime_context_set_wasi(handle, wasi.DangerousGetHandle());
wasi.SetHandleAsInvalid();
if (error != IntPtr.Zero)
{
throw WasmtimeException.FromOwnedError(error);
}
}
/// <summary>
/// Configures the relative deadline at which point WebAssembly code will trap.
/// </summary>
/// <param name="deadline"></param>
public void SetEpochDeadline(ulong deadline)
{
Native.wasmtime_context_set_epoch_deadline(handle, deadline);
}
private static class Native
{
[DllImport(Engine.LibraryName)]
public static extern void wasmtime_context_gc(IntPtr handle);
[DllImport(Engine.LibraryName)]
public static extern IntPtr wasmtime_context_set_fuel(IntPtr handle, ulong fuel);
[DllImport(Engine.LibraryName)]
public static extern IntPtr wasmtime_context_get_fuel(IntPtr handle, out ulong fuel);
[DllImport(Engine.LibraryName)]
public static extern IntPtr wasmtime_context_set_wasi(IntPtr handle, IntPtr config);
[DllImport(Engine.LibraryName)]
public static extern void wasmtime_context_set_epoch_deadline(IntPtr handle, ulong ticksBeyondCurrent);
[DllImport(Engine.LibraryName)]
public static extern IntPtr wasmtime_context_get_data(IntPtr handle);
}
internal readonly IntPtr handle;
}
/// <summary>
/// Represents a Wasmtime store.
/// </summary>
/// <remarks>
/// A Wasmtime store may be sent between threads but cannot be used from more than one thread
/// simultaneously.
/// </remarks>
public class Store : IDisposable
{
/// <summary>
/// Constructs a new store.
/// </summary>
/// <param name="engine">The engine to use for the store.</param>
public Store(Engine engine) : this(engine, null) { }
/// <summary>
/// Constructs a new store with the given context data.
/// </summary>
/// <param name="engine">The engine to use for the store.</param>
/// <param name="data">The data to initialize the store with; this can later be accessed with the GetData function.</param>
public Store(Engine engine, object? data)
{
if (engine is null)
{
throw new ArgumentNullException(nameof(engine));
}
this.data = data;
// Allocate a weak GCHandle, so that it does not participate in keeping the Store alive.
// Otherwise, the circular reference would prevent the Store from being finalized even
// if it's no longer referenced by user code.
// The weak handle will be used to get the originating Store object from a Caller's
// context in host callbacks.
var storeHandle = GCHandle.Alloc(this, GCHandleType.Weak);
handle = new Handle(Native.wasmtime_store_new(engine.NativeHandle, (IntPtr)storeHandle, Finalizer));
contextHandle = Native.wasmtime_store_context(NativeHandle);
}
/// <summary>
/// Gets or sets the fuel available for WebAssembly code to consume while executing.
/// </summary>
/// <remarks>
/// <para>
/// For this property to work fuel consumption must be enabled via <see cref="Config.WithFuelConsumption(bool)"/>.
/// </para>
/// <para>
/// WebAssembly execution will automatically consume fuel but if so desired the embedder can also consume fuel manually
/// to account for relative costs of host functions, for example.
/// </para>
/// </remarks>
/// <value>The fuel available for WebAssembly code to consume while executing.</value>
public ulong Fuel
{
get
{
ulong fuel = Context.GetFuel();
System.GC.KeepAlive(this);
return fuel;
}
set
{
Context.SetFuel(value);
System.GC.KeepAlive(this);
}
}
/// <summary>
/// Limit the resources that this store may consume. Note that the limits are only used to limit the creation/growth of resources in the future,
/// this does not retroactively attempt to apply limits to the store.
/// </summary>
/// <param name="memorySize">the maximum number of bytes a linear memory can grow to. Growing a linear memory beyond this limit will fail.
/// Pass in a null value to use the default value (unlimited)</param>
/// <param name="tableElements">the maximum number of elements in a table. Growing a table beyond this limit will fail.
/// Pass in a null value to use the default value (unlimited)</param>
/// <param name="instances">the maximum number of instances that can be created for a Store. Module instantiation will fail if this limit is exceeded.
/// Pass in a null value to use the default value (10000)</param>
/// <param name="tables">the maximum number of tables that can be created for a Store. Module instantiation will fail if this limit is exceeded.
/// Pass in a null value to use the default value (10000)</param>
/// <param name="memories">the maximum number of linear memories that can be created for a Store. Instantiation will fail with an error if this limit is exceeded.
/// Pass in a null value to use the default value (10000)</param>
public void SetLimits(long? memorySize = null, uint? tableElements = null, long? instances = null, long? tables = null, long? memories = null)
{
if (memorySize.HasValue && memorySize.Value < 0)
{
throw new ArgumentOutOfRangeException(nameof(memorySize));
}
if (instances.HasValue && instances.Value < 0)
{
throw new ArgumentOutOfRangeException(nameof(instances));
}
if (tables.HasValue && tables.Value < 0)
{
throw new ArgumentOutOfRangeException(nameof(tables));
}
if (memories.HasValue && memories.Value < 0)
{
throw new ArgumentOutOfRangeException(nameof(memories));
}
long tableElements64 = -1;
if (tableElements.HasValue)
{
tableElements64 = tableElements.Value;
}
Native.wasmtime_store_limiter(NativeHandle, memorySize ?? -1, tableElements64, instances ?? -1, tables ?? -1, memories ?? -1);
}
/// <summary>
/// Perform garbage collection within the given store.
/// </summary>
public void GC()
{
Context.GC();
System.GC.KeepAlive(this);
}
/// <summary>
/// Configures WASI within the store.
/// </summary>
/// <param name="config">The WASI configuration to use.</param>
public void SetWasiConfiguration(WasiConfiguration config)
{
Context.SetWasiConfiguration(config);
System.GC.KeepAlive(this);
}
/// <summary>
/// Configures the relative deadline at which point WebAssembly code will trap.
/// </summary>
/// <param name="ticksBeyondCurrent"></param>
public void SetEpochDeadline(ulong ticksBeyondCurrent)
{
Context.SetEpochDeadline(ticksBeyondCurrent);
System.GC.KeepAlive(this);
}
/// <summary>
/// Retrieves the data stored in the Store context
/// </summary>
public object? GetData() => data;
/// <summary>
/// Replaces the data stored in the Store context
/// </summary>
public void SetData(object? data) => this.data = data;
/// <inheritdoc/>
public void Dispose()
{
handle.Dispose();
}
internal Handle NativeHandle
{
get
{
if (handle.IsInvalid || handle.IsClosed)
{
throw new ObjectDisposedException(typeof(Store).FullName);
}
return handle;
}
}
/// <summary>
/// Gets the context of the store.
/// </summary>
/// <remarks>
/// Note: Generally, you must keep the <see cref="Store"/> alive (by using
/// <see cref="GC.KeepAlive(object)"/>) until the <see cref="StoreContext"/> is no longer
/// used, to prevent the the <see cref="Handle"/> finalizer from prematurely deleting the
/// store handle in the GC finalizer thread while the <see cref="StoreContext"/> is still
/// in use.
/// </remarks>
internal StoreContext Context
{
get
{
if (handle.IsClosed)
{
throw new ObjectDisposedException(typeof(Store).FullName);
}
return new StoreContext(contextHandle);
}
}
internal class Handle : SafeHandleZeroOrMinusOneIsInvalid
{
public Handle(IntPtr handle)
: base(true)
{
SetHandle(handle);
}
protected override bool ReleaseHandle()
{
Native.wasmtime_store_delete(handle);
return true;
}
}
private static class Native
{
public delegate void Finalizer(IntPtr data);
[DllImport(Engine.LibraryName)]
public static extern IntPtr wasmtime_store_new(Engine.Handle engine, IntPtr data, Finalizer? finalizer);
[DllImport(Engine.LibraryName)]
public static extern IntPtr wasmtime_store_context(Handle store);
[DllImport(Engine.LibraryName)]
public static extern void wasmtime_store_delete(IntPtr store);
[DllImport(Engine.LibraryName)]
public static extern void wasmtime_store_limiter(Handle store, long memory_size, long table_elements, long instances, long tables, long memories);
}
private readonly IntPtr contextHandle;
private readonly Handle handle;
private object? data;
private static readonly Native.Finalizer Finalizer = (p) => GCHandle.FromIntPtr(p).Free();
private readonly ConcurrentDictionary<(ExternKind kind, ulong store, nuint index), object> _externCache = new();
internal Function GetCachedExtern(ExternFunc @extern)
{
var key = (ExternKind.Func, @extern.store, @extern.index);
if (!_externCache.TryGetValue(key, out var func))
{
func = new Function(this, @extern);
func = _externCache.GetOrAdd(key, func);
}
return (Function)func;
}
internal Memory GetCachedExtern(ExternMemory @extern)
{
var key = (ExternKind.Memory, @extern.store, @extern.index);
if (!_externCache.TryGetValue(key, out var mem))
{
mem = new Memory(this, @extern);
mem = _externCache.GetOrAdd(key, mem);
}
return (Memory)mem;
}
internal Global GetCachedExtern(ExternGlobal @extern)
{
var key = (ExternKind.Global, @extern.store, @extern.index);
if (!_externCache.TryGetValue(key, out var global))
{
global = new Global(this, @extern);
global = _externCache.GetOrAdd(key, global);
}
return (Global)global;
}
}
}