initial commit

This commit is contained in:
zer0condition
2026-08-19 01:23:15 +05:30
commit 00a3adf890
89 changed files with 21608 additions and 0 deletions
+48
View File
@@ -0,0 +1,48 @@
; Goodmans.inf - minimal WDM install
[Version]
Signature = "$WINDOWS NT$"
Class = System
ClassGuid = {4d36e97d-e325-11ce-bfc1-08002be10318}
Provider = %Provider%
DriverVer =
CatalogFile = Goodmans.cat
PnpLockdown = 1
[DestinationDirs]
DefaultDestDir = 12
[SourceDisksNames]
1 = %DiskName%
[SourceDisksFiles]
Goodmans.sys = 1
[Manufacturer]
%Provider% = Standard,NT$ARCH$
[Standard.NT$ARCH$]
%DeviceDesc% = Goodmans_Install,Root\Goodmans
[Goodmans_Install.NT]
CopyFiles = Goodmans.CopyFiles
[Goodmans_Install.NT.Services]
AddService = Goodmans,%SPSVCINST_ASSOCSERVICE%,Goodmans_Service
[Goodmans.CopyFiles]
Goodmans.sys
[Goodmans_Service]
DisplayName = %ServiceDesc%
ServiceType = 1
StartType = 3
ErrorControl = 1
ServiceBinary = %12%\Goodmans.sys
[Strings]
SPSVCINST_ASSOCSERVICE = 0x00000002
Provider = "Goodmans"
DeviceDesc = "Goodmans WASM Kernel VM"
ServiceDesc = "Goodmans"
DiskName = "Goodmans Install Disk"
+124
View File
@@ -0,0 +1,124 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{6D9E2A50-1F1A-4B4E-8D9F-1B0C0D0A0001}</ProjectGuid>
<TemplateGuid>{497e31cb-056b-4f31-abb8-447fd55ee5a5}</TemplateGuid>
<TemplateVersion>10.0.26100.0</TemplateVersion>
<MinimumVisualStudioVersion>17.0</MinimumVisualStudioVersion>
<Configuration Condition="'$(Configuration)' == ''">Debug</Configuration>
<Platform Condition="'$(Platform)' == ''">x64</Platform>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<RootNamespace>Goodmans</RootNamespace>
<WindowsTargetPlatformVersion>10.0.26100.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Label="Configuration">
<TargetVersion>Windows10</TargetVersion>
<UseDebugLibraries Condition="'$(Configuration)'=='Debug'">true</UseDebugLibraries>
<UseDebugLibraries Condition="'$(Configuration)'=='Release'">false</UseDebugLibraries>
<PlatformToolset>WindowsKernelModeDriver10.0</PlatformToolset>
<ConfigurationType>Driver</ConfigurationType>
<DriverType>WDM</DriverType>
<DriverTargetPlatform>Universal</DriverTargetPlatform>
<SpectreMitigation>false</SpectreMitigation>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings" />
<ImportGroup Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup />
<ItemDefinitionGroup>
<ClCompile>
<TreatWarningAsError>false</TreatWarningAsError>
<WarningLevel>Level3</WarningLevel>
<AdditionalIncludeDirectories>$(ProjectDir);$(ProjectDir)kshim;$(ProjectDir)wasm3;$(ProjectDir)inc;$(ProjectDir)..\shared;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>_KERNEL_MODE;d_m3HasFloat=0;d_m3RecordBacktraces=0;d_m3LogTimestamps=0;d_m3VerboseErrorMessages=0;d_m3EnableValidation=0;d_m3SkipStackCheck=1;d_m3SkipMemoryBoundsCheck=1;d_m3CascadedOpcodes=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ExceptionHandling>false</ExceptionHandling>
<BufferSecurityCheck>false</BufferSecurityCheck>
<DisableSpecificWarnings>4100;4127;4152;4189;4201;4204;4214;4245;4267;4310;4459;4505;4706;4996;4055;4131;4132;4244;4324;4456;4457;4090</DisableSpecificWarnings>
<ForcedIncludeFiles>
</ForcedIncludeFiles>
</ClCompile>
<Link>
<SubSystem>Native</SubSystem>
<!-- /INTEGRITYCHECK sets IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY.
required for PsSetCreateProcessNotifyRoutineEx to succeed. -->
<AdditionalOptions>/INTEGRITYCHECK %(AdditionalOptions)</AdditionalOptions>
</Link>
<Inf>
<TimeStamp>*</TimeStamp>
</Inf>
<DriverSign>
<FileDigestAlgorithm>sha256</FileDigestAlgorithm>
</DriverSign>
</ItemDefinitionGroup>
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
<EnableInf2cat>false</EnableInf2cat>
<SignMode>Off</SignMode>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'">
<EnableInf2cat>false</EnableInf2cat>
<SignMode>Off</SignMode>
</PropertyGroup>
<ItemGroup>
<ClCompile Include="driver.c" />
<ClCompile Include="ioctl_handler.c" />
<ClCompile Include="module_table.c" />
<ClCompile Include="host_imports.c" />
<ClCompile Include="wasm_call.c" />
<ClCompile Include="log_ring.c" />
<ClCompile Include="trace_ring.c" />
<ClCompile Include="watchdog.c" />
<ClCompile Include="ih.c" />
<ClCompile Include="kshim\kshim.c" />
<ClCompile Include="wasm3\m3_bind.c" />
<ClCompile Include="wasm3\m3_code.c" />
<ClCompile Include="wasm3\m3_compile.c" />
<ClCompile Include="wasm3\m3_core.c" />
<ClCompile Include="wasm3\m3_env.c" />
<ClCompile Include="wasm3\m3_exec.c" />
<ClCompile Include="wasm3\m3_function.c" />
<ClCompile Include="wasm3\m3_info.c" />
<ClCompile Include="wasm3\m3_module.c" />
<ClCompile Include="wasm3\m3_parse.c" />
<ClCompile Include="wasm3\m3_validate.c" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="inc\gvm.h" />
<ClInclude Include="kshim\kshim.h" />
<ClInclude Include="kshim\stdio.h" />
<ClInclude Include="kshim\stdlib.h" />
<ClInclude Include="kshim\malloc.h" />
<ClInclude Include="kshim\corecrt.h" />
<ClInclude Include="..\shared\goodmans_ioctl.h" />
</ItemGroup>
<ItemGroup>
<FilesToPackage Include="$(TargetPath)" />
</ItemGroup>
<ItemGroup>
<Inf Include="Goodmans.inf" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets" />
<Target Name="StageToDeploy" AfterTargets="Build">
<PropertyGroup>
<DeployDir>$(SolutionDir)deploy</DeployDir>
<SignedSys>$(OutDir)Goodmans.sys</SignedSys>
</PropertyGroup>
<Message Text="[deploy] staging driver -&gt; $(DeployDir)" Importance="high" />
<Exec Command="if exist &quot;$(DeployDir)\GoodmansTest.pfx&quot; for /f &quot;delims=&quot; %%s in ('where signtool 2^&gt;nul') do &quot;%%s&quot; sign /fd sha256 /f &quot;$(DeployDir)\GoodmansTest.pfx&quot; /p goodmans &quot;$(SignedSys)&quot;" IgnoreExitCode="true" ContinueOnError="true" />
<Copy SourceFiles="$(SignedSys)" DestinationFolder="$(DeployDir)" SkipUnchangedFiles="true" ContinueOnError="true" Retries="1" RetryDelayMilliseconds="200" />
<Copy SourceFiles="$(MSBuildProjectDirectory)\Goodmans.inf" DestinationFolder="$(DeployDir)" SkipUnchangedFiles="true" ContinueOnError="true" Retries="1" RetryDelayMilliseconds="200" />
</Target>
</Project>
+121
View File
@@ -0,0 +1,121 @@
/* driver.c - entry, device create, dispatch */
#include "inc/gvm.h"
#include "../shared/goodmans_ioctl.h"
DRIVER_INITIALIZE DriverEntry;
static DRIVER_UNLOAD gvm_unload;
static DRIVER_DISPATCH gvm_create_close;
static DRIVER_DISPATCH gvm_device_control;
PDEVICE_OBJECT g_device;
static UNICODE_STRING g_symlink;
NTSTATUS
DriverEntry(_In_ PDRIVER_OBJECT drv, _In_ PUNICODE_STRING regpath)
{
UNREFERENCED_PARAMETER(regpath);
// filter mask so DbgView captures our lines without Verbose
DbgSetDebugFilterState(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, TRUE);
DbgSetDebugFilterState(DPFLTR_IHVDRIVER_ID, DPFLTR_WARNING_LEVEL, TRUE);
DbgSetDebugFilterState(DPFLTR_IHVDRIVER_ID, DPFLTR_TRACE_LEVEL, TRUE);
DbgSetDebugFilterState(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, TRUE);
gvm_log_init();
gvm_trace_init();
gvm_watchdog_start();
gvm_log("driver load");
UNICODE_STRING dev_name;
RtlInitUnicodeString(&dev_name, GVM_DEVICE_NAME_U);
NTSTATUS s = IoCreateDevice(drv, 0, &dev_name, GVM_DEVICE_TYPE,
FILE_DEVICE_SECURE_OPEN, FALSE, &g_device);
if (!NT_SUCCESS(s)) {
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "[goodmans] IoCreateDevice=%08x\n", s);
return s;
}
RtlInitUnicodeString(&g_symlink, GVM_SYMLINK_NAME_U);
s = IoCreateSymbolicLink(&g_symlink, &dev_name);
if (!NT_SUCCESS(s)) {
IoDeleteDevice(g_device);
g_device = NULL;
return s;
}
s = gvm_modtab_init();
if (!NT_SUCCESS(s)) {
IoDeleteSymbolicLink(&g_symlink);
IoDeleteDevice(g_device);
g_device = NULL;
return s;
}
drv->MajorFunction[IRP_MJ_CREATE] = gvm_create_close;
drv->MajorFunction[IRP_MJ_CLOSE] = gvm_create_close;
drv->MajorFunction[IRP_MJ_DEVICE_CONTROL] = gvm_device_control;
drv->DriverUnload = gvm_unload;
g_device->Flags |= DO_BUFFERED_IO;
g_device->Flags &= ~DO_DEVICE_INITIALIZING;
gvm_log("driver ready, device \\\\.\\Goodmans");
return STATUS_SUCCESS;
}
static VOID
gvm_unload(_In_ PDRIVER_OBJECT drv)
{
UNREFERENCED_PARAMETER(drv);
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "[goodmans] unload\n");
gvm_ih_teardown();
gvm_watchdog_stop();
gvm_notify_teardown();
gvm_modtab_teardown();
if (g_symlink.Buffer)
IoDeleteSymbolicLink(&g_symlink);
if (g_device)
IoDeleteDevice(g_device);
}
static NTSTATUS
gvm_create_close(_In_ PDEVICE_OBJECT dev, _Inout_ PIRP irp)
{
UNREFERENCED_PARAMETER(dev);
irp->IoStatus.Status = STATUS_SUCCESS;
irp->IoStatus.Information = 0;
IoCompleteRequest(irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
static NTSTATUS
gvm_device_control(_In_ PDEVICE_OBJECT dev, _Inout_ PIRP irp)
{
UNREFERENCED_PARAMETER(dev);
PIO_STACK_LOCATION sp = IoGetCurrentIrpStackLocation(irp);
NTSTATUS s = STATUS_INVALID_DEVICE_REQUEST;
switch (sp->Parameters.DeviceIoControl.IoControlCode) {
case IOCTL_GVM_LOAD_MODULE: s = gvm_ioctl_load(irp, sp); break;
case IOCTL_GVM_CALL_EXPORT: s = gvm_ioctl_call(irp, sp); break;
case IOCTL_GVM_UNLOAD_MODULE: s = gvm_ioctl_unload(irp, sp); break;
case IOCTL_GVM_LIST_MODULES: s = gvm_ioctl_list(irp, sp); break;
case IOCTL_GVM_MODULE_INFO: s = gvm_ioctl_info(irp, sp); break;
case IOCTL_GVM_UNLOAD_ALL: s = gvm_ioctl_unload_all(irp, sp); break;
case IOCTL_GVM_TAIL_LOG: s = gvm_ioctl_tail_log(irp, sp); break;
case IOCTL_GVM_READ_GUEST: s = gvm_ioctl_read_guest(irp, sp); break;
case IOCTL_GVM_TAIL_TRACE: s = gvm_ioctl_tail_trace(irp, sp); break;
case IOCTL_GVM_TRACE_CTL: s = gvm_ioctl_trace_ctl(irp, sp); break;
case IOCTL_GVM_FORCE_UNLOAD: s = gvm_ioctl_force_unload(irp, sp); break;
case IOCTL_GVM_NOTIFY_STOP: s = gvm_ioctl_notify_stop(irp, sp); break;
default: break;
}
irp->IoStatus.Status = s;
IoCompleteRequest(irp, IO_NO_INCREMENT);
return s;
}
File diff suppressed because it is too large Load Diff
+94
View File
@@ -0,0 +1,94 @@
/* ih.c - the ONE piece of InfinityHook that cannot live in wasm.
*
* this file exposes a native trampoline that guests install into
* WMI_LOGGER_CONTEXT.GetCpuClock. everything else (nt base discovery,
* EtwpDebuggerData pattern scan, offset resolution, atomic pointer swap)
* lives in sample_guest/infinity_hook.c as a portable wasm demo.
*
* the trampoline runs at ETW callback IRQL (up to DISPATCH_LEVEL), samples
* calls, pushes a SYSCALL event onto the shared dispatch ring, and returns
* the real QPC. the ring is drained by the same dispatch worker that
* handles process/image notify events.
*/
#include "inc/gvm.h"
#include "../shared/goodmans_ioctl.h"
extern void gvm_push_event_generic(unsigned int kind, uint32_t pid,
uint64_t a, uint64_t b, uint32_t c);
static volatile LONG64 g_ih_hits = 0;
static volatile LONG g_ih_inflight = 0;
static volatile LONG g_ih_rate = 1000;
// caller (guest) sets this before installing the hook so the trampoline
// can reject callers outside nt's image range (avoids false positives from
// unrelated ETW paths).
static volatile UINT64 g_ih_nt_lo = 0;
static volatile UINT64 g_ih_nt_hi = 0;
// signature matches WMI_LOGGER_CONTEXT.GetCpuClock: takes no args, returns
// a QPC value. must be __stdcall/default x64 ABI (matches).
static UINT64 gvm_ih_trampoline(void)
{
InterlockedIncrement(&g_ih_inflight);
LONG64 hits = InterlockedIncrement64(&g_ih_hits);
LONG rate = g_ih_rate;
if (rate <= 0) rate = 1;
if ((hits % rate) == 0) {
PVOID retaddr = _ReturnAddress();
UINT64 lo = g_ih_nt_lo, hi = g_ih_nt_hi;
BOOLEAN in_nt = (lo && (UINT64)(uintptr_t)retaddr >= lo &&
(UINT64)(uintptr_t)retaddr < hi);
if (in_nt || !lo) {
HANDLE tid = PsGetCurrentThreadId();
gvm_push_event_generic(3, // SYSCALL kind
(uint32_t)(uintptr_t)tid,
(UINT64)(uintptr_t)retaddr, // a1 in on_syscall
0,
(UINT32)hits); // a2 in on_syscall
}
}
LARGE_INTEGER qpc = KeQueryPerformanceCounter(NULL);
UINT64 rv = (UINT64)qpc.QuadPart;
InterlockedDecrement(&g_ih_inflight);
return rv;
}
// exposed as host imports: guests get the trampoline VA to plug into the
// GetCpuClock slot they located, and configure sampling / nt-range so the
// trampoline can skip non-syscall callers.
UINT64 gvm_ih_trampoline_addr(void)
{
return (UINT64)(uintptr_t)&gvm_ih_trampoline;
}
void gvm_ih_configure(UINT32 rate, UINT64 nt_base, UINT32 nt_size)
{
if (rate > 0) InterlockedExchange(&g_ih_rate, (LONG)rate);
g_ih_nt_lo = nt_base;
g_ih_nt_hi = nt_base + nt_size;
}
// spin until any in-flight trampoline calls drain. call after guest writes
// the original pointer back to the GetCpuClock slot but before it unloads.
void gvm_ih_wait_quiescent(void)
{
for (int i = 0; i < 1000 && g_ih_inflight > 0; i++) {
LARGE_INTEGER li; li.QuadPart = -10000; // 1ms
KeDelayExecutionThread(KernelMode, FALSE, &li);
}
}
// stats (guest asks for hit count so it can report accurately)
UINT64 gvm_ih_hit_count(void) { return (UINT64)g_ih_hits; }
// driver unload safety: nothing to tear down since we don't own the slot,
// but we can spin briefly in case guests left the hook installed.
void gvm_ih_teardown(void)
{
gvm_ih_wait_quiescent();
}
+103
View File
@@ -0,0 +1,103 @@
/* gvm.h - internal driver-wide declarations */
#pragma once
#include <ntddk.h>
#include "kshim/kshim.h"
#include "wasm3/wasm3.h"
#define GVM_TAG_MOD 'doMG'
#define GVM_TAG_WBUF 'BWMG'
#define GVM_STACK_DEFAULT (64u * 1024u)
typedef struct _gvm_module {
unsigned int id;
BOOLEAN used;
IM3Environment env;
IM3Runtime runtime;
IM3Module module;
unsigned char* wasm_bytes;
unsigned int wasm_size;
unsigned long long hash;
volatile LONG refcount; // touch only with Interlocked*
KMUTEX call_mutex; // held around all m3_* activity on this module
// pool budget for host_alloc/host_free
volatile LONG64 pool_used;
LONG64 pool_budget;
// capability bitmask parsed from the guest's __gvm_caps export.
// absent export defaults to GVM_CAP_ALL for backward compat.
unsigned int caps;
// cooperative abort deadline in QPC ticks. 0 = no limit
volatile ULONG64 exec_deadline_qpc;
// QPC tick at which call_mutex was acquired. watchdog polls this to
// find guests stuck past GVM_WATCHDOG_MAX_MS. 0 = not held
volatile ULONG64 mutex_hold_qpc;
// watchdog sets this to reject further calls into a wedged guest.
// subsequent force-unload skips waiting on call_mutex
volatile LONG poisoned;
char name[64];
} gvm_module;
// ioctl_handler.c
unsigned int gvm_read_module_caps(gvm_module* mod);
BOOLEAN gvm_deadline_exceeded(gvm_module* mod);
// wasm_call.c
M3Result gvm_call_locked(gvm_module* mod, IM3Function fn, unsigned int argc, const void** argp);
void gvm_set_deadline_ms(gvm_module* mod, unsigned int timeout_ms);
NTSTATUS gvm_modtab_init(void);
void gvm_modtab_teardown(void);
gvm_module* gvm_modtab_alloc(void);
gvm_module* gvm_modtab_get(unsigned int id);
gvm_module* gvm_modtab_find_by_hash(unsigned long long hash);
gvm_module* gvm_modtab_find_by_hash_incref(unsigned long long hash, unsigned int expected_size);
gvm_module* gvm_modtab_iter(unsigned int idx); // returns null past end
gvm_module* gvm_modtab_owner_of_runtime(IM3Runtime rt);
void gvm_modtab_free(gvm_module* m);
M3Result gvm_link_host_imports(IM3Module module);
M3Result gvm_link_kernel_fallback(IM3Module module);
void gvm_notify_init(void);
void gvm_notify_teardown(void);
// callback dispatch (host_imports.c)
void gvm_dispatch_start(void);
void gvm_dispatch_stop_signal(void); // just sets shutdown, returns
void gvm_dispatch_stop_wait(void); // signal + wait for worker exit (unload only)
NTSTATUS gvm_ioctl_load(PIRP irp, PIO_STACK_LOCATION sp);
NTSTATUS gvm_ioctl_call(PIRP irp, PIO_STACK_LOCATION sp);
NTSTATUS gvm_ioctl_unload(PIRP irp, PIO_STACK_LOCATION sp);
NTSTATUS gvm_ioctl_list(PIRP irp, PIO_STACK_LOCATION sp);
NTSTATUS gvm_ioctl_info(PIRP irp, PIO_STACK_LOCATION sp);
NTSTATUS gvm_ioctl_unload_all(PIRP irp, PIO_STACK_LOCATION sp);
NTSTATUS gvm_ioctl_tail_log(PIRP irp, PIO_STACK_LOCATION sp);
NTSTATUS gvm_ioctl_read_guest(PIRP irp, PIO_STACK_LOCATION sp);
NTSTATUS gvm_ioctl_tail_trace(PIRP irp, PIO_STACK_LOCATION sp);
NTSTATUS gvm_ioctl_trace_ctl(PIRP irp, PIO_STACK_LOCATION sp);
NTSTATUS gvm_ioctl_force_unload(PIRP irp, PIO_STACK_LOCATION sp);
NTSTATUS gvm_ioctl_notify_stop(PIRP irp, PIO_STACK_LOCATION sp);
// watchdog thread (watchdog.c)
void gvm_watchdog_start(void);
void gvm_watchdog_stop(void);
// infinity-hook (ih.c) - only the trampoline. install/uninstall happens in
// wasm via kernel FFI + gvm_write_u64.
unsigned long long gvm_ih_trampoline_addr(void);
void gvm_ih_configure(unsigned int rate, unsigned long long nt_base, unsigned int nt_size);
void gvm_ih_wait_quiescent(void);
unsigned long long gvm_ih_hit_count(void);
void gvm_ih_teardown(void);
// trace ring (trace_ring.c)
void gvm_trace_init(void);
void gvm_trace_push(unsigned int module_id, unsigned int kind, const char* name,
unsigned int argc, const unsigned long long* argv, unsigned long long rv);
BOOLEAN gvm_trace_enabled_for(unsigned int module_id);
// log ring (log_ring.c)
void gvm_log_init(void);
void gvm_log_push(const char* fmt, ...);
#define gvm_log(fmt, ...) gvm_log_push(fmt, ##__VA_ARGS__)
+457
View File
@@ -0,0 +1,457 @@
/* ioctl_handler.c - LOAD / CALL / UNLOAD / LIST / INFO / UNLOAD_ALL */
#include "inc/gvm.h"
#include "../shared/goodmans_ioctl.h"
#include "wasm3/m3_env.h"
#include "wasm3/m3_function.h"
static void
copy_errmsg(char* dst, size_t dst_sz, const char* src)
{
if (!dst || dst_sz == 0) return;
dst[0] = 0;
if (src) RtlStringCbCopyA(dst, dst_sz, src);
}
// FNV-1a 64-bit hash for wasm-blob dedup
static unsigned long long fnv1a64(const unsigned char* p, size_t n)
{
unsigned long long h = 0xcbf29ce484222325ULL;
for (size_t i = 0; i < n; i++) {
h ^= p[i];
h *= 0x100000001b3ULL;
}
return h;
}
// reads the module's capability bitmask by invoking its exported __gvm_caps
// function. must run AFTER link so the guest can use hosts inside its manifest
// function if it needs to (typical case: returns a constant). if the export
// is absent, returns GVM_CAP_ALL (open policy, backward compat).
unsigned int gvm_read_module_caps(gvm_module* mod)
{
if (!mod || !mod->runtime) return GVM_CAP_ALL;
IM3Function fn = NULL;
M3Result r = m3_FindFunction(&fn, mod->runtime, "__gvm_caps");
if (r || !fn) return GVM_CAP_ALL;
// must be called under the mutex + big stack, but at this point no one
// else has a handle on this module yet. still, use the standard path.
// start with wide-open caps so the manifest call itself isn't gated.
mod->caps = GVM_CAP_ALL;
r = gvm_call_locked(mod, fn, 0, NULL);
if (r) return GVM_CAP_ALL;
unsigned int caps = 0;
void* rvp = &caps;
m3_GetResults(fn, 1, (const void**)&rvp);
return caps ? caps : GVM_CAP_ALL;
}
BOOLEAN gvm_deadline_exceeded(gvm_module* mod)
{
if (!mod || mod->exec_deadline_qpc == 0) return FALSE;
LARGE_INTEGER now = KeQueryPerformanceCounter(NULL);
return (ULONG64)now.QuadPart >= mod->exec_deadline_qpc;
}
NTSTATUS
gvm_ioctl_load(PIRP irp, PIO_STACK_LOCATION sp)
{
ULONG in_len = sp->Parameters.DeviceIoControl.InputBufferLength;
ULONG out_len = sp->Parameters.DeviceIoControl.OutputBufferLength;
void* buf = irp->AssociatedIrp.SystemBuffer;
if (in_len < sizeof(gvm_load_in) || out_len < sizeof(gvm_load_out) || !buf) {
irp->IoStatus.Information = 0;
return STATUS_INVALID_PARAMETER;
}
gvm_load_in in;
RtlCopyMemory(&in, buf, sizeof(in));
if (in.wasm_size == 0 || in.wasm_size > (16u * 1024u * 1024u)) {
irp->IoStatus.Information = 0;
return STATUS_INVALID_PARAMETER;
}
if (in_len < sizeof(gvm_load_in) + in.wasm_size) {
irp->IoStatus.Information = 0;
return STATUS_INVALID_PARAMETER;
}
gvm_load_out out = { 0 };
unsigned char* wasm_start = (unsigned char*)buf + sizeof(gvm_load_in);
// dedup: hash first, return existing module id if match. atomic incref
// via find_by_hash_incref so a concurrent unload can't win the race.
unsigned long long h = fnv1a64(wasm_start, in.wasm_size);
gvm_module* existing = gvm_modtab_find_by_hash_incref(h, in.wasm_size);
if (existing) {
out.module_id = existing->id;
out.status = 0;
copy_errmsg(out.err_msg, sizeof(out.err_msg), "reused");
goto done;
}
gvm_module* mod = gvm_modtab_alloc();
if (!mod) {
out.status = -1;
copy_errmsg(out.err_msg, sizeof(out.err_msg), "module table full");
goto done;
}
mod->wasm_bytes = (unsigned char*)ExAllocatePoolWithTag(NonPagedPoolNx, in.wasm_size, GVM_TAG_WBUF);
if (!mod->wasm_bytes) {
gvm_modtab_free(mod);
out.status = -2;
copy_errmsg(out.err_msg, sizeof(out.err_msg), "wasm alloc failed");
goto done;
}
RtlCopyMemory(mod->wasm_bytes, wasm_start, in.wasm_size);
mod->wasm_size = in.wasm_size;
mod->hash = h;
mod->pool_budget = in.pool_budget ? (LONG64)in.pool_budget : (4LL * 1024 * 1024);
mod->pool_used = 0;
mod->caps = GVM_CAP_ALL; // set to real value after link+manifest call
mod->exec_deadline_qpc = 0;
RtlStringCbCopyA(mod->name, sizeof(mod->name), in.name[0] ? in.name : "guest");
mod->env = m3_NewEnvironment();
if (!mod->env) {
gvm_modtab_free(mod);
out.status = -3;
copy_errmsg(out.err_msg, sizeof(out.err_msg), "NewEnvironment failed");
goto done;
}
unsigned int stack = in.stack_bytes ? in.stack_bytes : GVM_STACK_DEFAULT;
mod->runtime = m3_NewRuntime(mod->env, stack, NULL);
if (!mod->runtime) {
gvm_modtab_free(mod);
out.status = -4;
copy_errmsg(out.err_msg, sizeof(out.err_msg), "NewRuntime failed");
goto done;
}
M3Result r = m3_ParseModule(mod->env, &mod->module, mod->wasm_bytes, mod->wasm_size);
if (r) {
gvm_modtab_free(mod);
out.status = -5;
copy_errmsg(out.err_msg, sizeof(out.err_msg), r);
goto done;
}
r = m3_LoadModule(mod->runtime, mod->module);
if (r) {
gvm_modtab_free(mod);
out.status = -6;
copy_errmsg(out.err_msg, sizeof(out.err_msg), r);
goto done;
}
r = gvm_link_host_imports(mod->module);
if (r && r != m3Err_functionLookupFailed) {
gvm_modtab_free(mod);
out.status = -7;
copy_errmsg(out.err_msg, sizeof(out.err_msg), r);
goto done;
}
// resolve any leftover env.* imports against ntoskrnl/hal export table
gvm_link_kernel_fallback(mod->module);
// read the guest's declared capability manifest (if any) and lock down.
mod->caps = gvm_read_module_caps(mod);
gvm_log("load module_id=%u name=%s wasm=%u bytes hash=%016llx caps=%08x",
mod->id, mod->name, mod->wasm_size, mod->hash, mod->caps);
out.module_id = mod->id;
out.status = 0;
copy_errmsg(out.err_msg, sizeof(out.err_msg), "ok");
done:
RtlCopyMemory(buf, &out, sizeof(out));
irp->IoStatus.Information = sizeof(out);
return STATUS_SUCCESS;
}
NTSTATUS
gvm_ioctl_call(PIRP irp, PIO_STACK_LOCATION sp)
{
ULONG in_len = sp->Parameters.DeviceIoControl.InputBufferLength;
ULONG out_len = sp->Parameters.DeviceIoControl.OutputBufferLength;
void* buf = irp->AssociatedIrp.SystemBuffer;
if (in_len < sizeof(gvm_call_in) || out_len < sizeof(gvm_call_out) || !buf) {
irp->IoStatus.Information = 0;
return STATUS_INVALID_PARAMETER;
}
gvm_call_in in;
RtlCopyMemory(&in, buf, sizeof(in));
in.export_name[sizeof(in.export_name) - 1] = 0;
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL,
"[goodmans] IOCTL_CALL mod=%u export=%s argc=%u\n",
in.module_id, in.export_name, in.argc);
gvm_call_out out = { 0 };
gvm_module* mod = gvm_modtab_get(in.module_id);
if (!mod) {
out.status = -1;
copy_errmsg(out.err_msg, sizeof(out.err_msg), "no such module");
goto done;
}
IM3Function fn = NULL;
M3Result r = m3Err_none;
__try {
r = m3_FindFunction(&fn, mod->runtime, in.export_name);
} __except (EXCEPTION_EXECUTE_HANDLER) {
r = "m3_FindFunction raised kernel exception";
}
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL,
"[goodmans] m3_FindFunction export=%s fn=%p r=%s\n",
in.export_name, fn, r ? r : "ok");
if (r || !fn) {
out.status = -2;
copy_errmsg(out.err_msg, sizeof(out.err_msg), r ? r : "export not found");
goto done;
}
if (in.argc > GVM_MAX_ARGS) in.argc = GVM_MAX_ARGS;
const void* argp[GVM_MAX_ARGS];
for (unsigned int i = 0; i < in.argc; i++)
argp[i] = &in.argv[i];
gvm_set_deadline_ms(mod, in.timeout_ms);
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL,
"[goodmans] pre-gvm_call_locked mod=%u fn=%p export=%s\n",
in.module_id, fn, in.export_name);
r = gvm_call_locked(mod, fn, in.argc, argp);
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL,
"[goodmans] post-gvm_call_locked r=%s\n", r ? r : "ok");
if (!r && gvm_deadline_exceeded(mod)) r = "execution deadline exceeded";
if (r) {
out.status = -3;
copy_errmsg(out.err_msg, sizeof(out.err_msg), r);
M3ErrorInfo einfo = { 0 };
m3_GetErrorInfo(mod->runtime, &einfo);
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
"[goodmans] m3_Call fail: r=%s message=%s file=%s line=%u\n",
r, einfo.message ? einfo.message : "(none)",
einfo.file ? einfo.file : "(none)", einfo.line);
goto done;
}
unsigned long long rv = 0;
void* rvp = &rv;
r = m3_GetResults(fn, 1, (const void**)&rvp);
// r may be non-null when the export returns void
out.rv = rv;
out.status = 0;
copy_errmsg(out.err_msg, sizeof(out.err_msg), "ok");
gvm_log("call mod=%u %s(argc=%u) rv=0x%llx", in.module_id, in.export_name, in.argc, rv);
done:
RtlCopyMemory(buf, &out, sizeof(out));
irp->IoStatus.Information = sizeof(out);
return STATUS_SUCCESS;
}
NTSTATUS
gvm_ioctl_unload(PIRP irp, PIO_STACK_LOCATION sp)
{
ULONG in_len = sp->Parameters.DeviceIoControl.InputBufferLength;
void* buf = irp->AssociatedIrp.SystemBuffer;
if (in_len < sizeof(gvm_unload_in) || !buf) {
irp->IoStatus.Information = 0;
return STATUS_INVALID_PARAMETER;
}
gvm_unload_in in;
RtlCopyMemory(&in, buf, sizeof(in));
gvm_module* mod = gvm_modtab_get(in.module_id);
if (!mod) {
irp->IoStatus.Information = 0;
return STATUS_NOT_FOUND;
}
gvm_modtab_free(mod);
irp->IoStatus.Information = 0;
return STATUS_SUCCESS;
}
static void fill_entry(gvm_module_entry* e, const gvm_module* m)
{
e->id = m->id;
e->wasm_size = m->wasm_size;
e->hash = m->hash;
e->exports = 0;
e->mem_pages = 0;
e->pool_bytes = (unsigned long long)m->pool_used;
if (m->runtime) {
uint32_t mem_sz = 0;
m3_GetMemory(m->runtime, &mem_sz, 0);
e->mem_pages = mem_sz / 65536;
}
RtlCopyMemory(e->name, m->name, sizeof(e->name));
}
NTSTATUS
gvm_ioctl_list(PIRP irp, PIO_STACK_LOCATION sp)
{
ULONG out_len = sp->Parameters.DeviceIoControl.OutputBufferLength;
void* buf = irp->AssociatedIrp.SystemBuffer;
if (out_len < sizeof(gvm_list_out) || !buf) {
irp->IoStatus.Information = 0;
return STATUS_INVALID_PARAMETER;
}
gvm_list_out out = { 0 };
for (unsigned int i = 0; i < GVM_MAX_MODULES; i++) {
gvm_module* m = gvm_modtab_iter(i);
if (!m || !m->used) continue;
if (out.count >= GVM_MAX_MODULES) break;
fill_entry(&out.entries[out.count], m);
out.count++;
}
RtlCopyMemory(buf, &out, sizeof(out));
irp->IoStatus.Information = sizeof(out);
return STATUS_SUCCESS;
}
NTSTATUS
gvm_ioctl_info(PIRP irp, PIO_STACK_LOCATION sp)
{
ULONG in_len = sp->Parameters.DeviceIoControl.InputBufferLength;
ULONG out_len = sp->Parameters.DeviceIoControl.OutputBufferLength;
void* buf = irp->AssociatedIrp.SystemBuffer;
if (in_len < sizeof(gvm_info_in) || out_len < sizeof(gvm_info_out) || !buf) {
irp->IoStatus.Information = 0;
return STATUS_INVALID_PARAMETER;
}
gvm_info_in in;
RtlCopyMemory(&in, buf, sizeof(in));
gvm_info_out out = { 0 };
gvm_module* mod = gvm_modtab_get(in.module_id);
if (!mod) {
out.status = -1;
copy_errmsg(out.err_msg, sizeof(out.err_msg), "no such module");
goto done;
}
fill_entry(&out.base, mod);
// enumerate exports + imports from the wasm3 module chain
if (mod->runtime) {
IM3Module m = mod->runtime->modules;
while (m) {
for (u32 i = 0; i < m->numFunctions; i++) {
IM3Function fn = &m->functions[i];
if (i < m->numFuncImports && fn->import.fieldUtf8 && out.import_count < GVM_MAX_INFO_IMPORTS) {
RtlStringCbCopyA(out.imports[out.import_count], GVM_INFO_NAME_LEN, fn->import.fieldUtf8);
out.import_count++;
}
if (fn->export_name && out.export_count < GVM_MAX_INFO_EXPORTS) {
RtlStringCbCopyA(out.exports[out.export_count], GVM_INFO_NAME_LEN, fn->export_name);
out.export_count++;
}
}
m = m->next;
}
}
out.status = 0;
copy_errmsg(out.err_msg, sizeof(out.err_msg), "ok");
done:
RtlCopyMemory(buf, &out, sizeof(out));
irp->IoStatus.Information = sizeof(out);
return STATUS_SUCCESS;
}
NTSTATUS
gvm_ioctl_unload_all(PIRP irp, PIO_STACK_LOCATION sp)
{
UNREFERENCED_PARAMETER(sp);
for (unsigned int i = 0; i < GVM_MAX_MODULES; i++) {
gvm_module* m = gvm_modtab_iter(i);
if (!m || !m->used) continue;
// drain refcount atomically to 1, then free (which decrements to 0)
while (InterlockedCompareExchange(&m->refcount, 1, m->refcount) != 1) {
if (!m->used) break;
}
gvm_modtab_free(m);
}
irp->IoStatus.Information = 0;
return STATUS_SUCCESS;
}
NTSTATUS
gvm_ioctl_read_guest(PIRP irp, PIO_STACK_LOCATION sp)
{
ULONG in_len = sp->Parameters.DeviceIoControl.InputBufferLength;
ULONG out_len = sp->Parameters.DeviceIoControl.OutputBufferLength;
void* buf = irp->AssociatedIrp.SystemBuffer;
if (in_len < sizeof(gvm_read_guest_in) || out_len < sizeof(gvm_read_guest_out) || !buf) {
irp->IoStatus.Information = 0;
return STATUS_INVALID_PARAMETER;
}
gvm_read_guest_in in;
RtlCopyMemory(&in, buf, sizeof(in));
gvm_read_guest_out out;
RtlZeroMemory(&out, sizeof(out));
if (in.length == 0 || in.length > GVM_MAX_READ_GUEST_BYTES) {
out.status = -1;
copy_errmsg(out.err_msg, sizeof(out.err_msg), "bad length");
goto done;
}
gvm_module* mod = gvm_modtab_get(in.module_id);
if (!mod || !mod->runtime) {
out.status = -2;
copy_errmsg(out.err_msg, sizeof(out.err_msg), "no such module");
goto done;
}
uint32_t mem_sz = 0;
uint8_t* mem = m3_GetMemory(mod->runtime, &mem_sz, 0);
if (!mem) {
out.status = -3;
copy_errmsg(out.err_msg, sizeof(out.err_msg), "no memory");
goto done;
}
if ((uint64_t)in.offset + in.length > mem_sz) {
out.status = -4;
copy_errmsg(out.err_msg, sizeof(out.err_msg), "out of range");
goto done;
}
RtlCopyMemory(out.data, mem + in.offset, in.length);
out.length = in.length;
out.status = 0;
copy_errmsg(out.err_msg, sizeof(out.err_msg), "ok");
done:
RtlCopyMemory(buf, &out, sizeof(out));
irp->IoStatus.Information = sizeof(out);
return STATUS_SUCCESS;
}
+2
View File
@@ -0,0 +1,2 @@
/* corecrt.h - blocks ucrt/corecrt.h chain */
#pragma once
+28
View File
@@ -0,0 +1,28 @@
/* inttypes.h - printf macros wasm3 uses */
#pragma once
#include <stdint.h>
#define PRId8 "d"
#define PRIi8 "i"
#define PRIu8 "u"
#define PRIx8 "x"
#define PRIX8 "X"
#define PRId16 "d"
#define PRIi16 "i"
#define PRIu16 "u"
#define PRIx16 "x"
#define PRIX16 "X"
#define PRId32 "d"
#define PRIi32 "i"
#define PRIu32 "u"
#define PRIx32 "x"
#define PRIX32 "X"
#define PRId64 "lld"
#define PRIi64 "lli"
#define PRIu64 "llu"
#define PRIx64 "llx"
#define PRIX64 "llX"
+248
View File
@@ -0,0 +1,248 @@
/* kshim.c - stdlib/stdio bodies wasm3 links against */
#include "kshim.h"
#include "stdio.h"
#include "stdlib.h"
FILE* const stdout = (FILE*)(void*)1;
FILE* const stderr = (FILE*)(void*)2;
FILE* const stdin = (FILE*)(void*)3;
typedef struct {
size_t size;
unsigned char data[1];
} kshim_hdr;
#define HDR_OFF FIELD_OFFSET(kshim_hdr, data)
void* __cdecl malloc(size_t n)
{
if (n == 0)
return NULL;
kshim_hdr* h = (kshim_hdr*)ExAllocatePoolWithTag(NonPagedPoolNx, n + HDR_OFF, GVM_POOL_TAG);
if (!h)
return NULL;
h->size = n;
return h->data;
}
void* __cdecl calloc(size_t n, size_t sz)
{
size_t total = n * sz;
if (sz != 0 && total / sz != n)
return NULL;
void* p = malloc(total);
if (p)
RtlZeroMemory(p, total);
return p;
}
void __cdecl free(void* p)
{
if (!p)
return;
kshim_hdr* h = (kshim_hdr*)((unsigned char*)p - HDR_OFF);
ExFreePoolWithTag(h, GVM_POOL_TAG);
}
void* __cdecl realloc(void* p, size_t new_sz)
{
if (!p)
return malloc(new_sz);
if (new_sz == 0) {
free(p);
return NULL;
}
kshim_hdr* h = (kshim_hdr*)((unsigned char*)p - HDR_OFF);
size_t old_sz = h->size;
if (old_sz >= new_sz)
return p;
void* np = malloc(new_sz);
if (!np)
return NULL;
RtlCopyMemory(np, p, old_sz);
free(p);
return np;
}
void __cdecl abort(void)
{
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "[goodmans] abort\n");
__fastfail(0);
}
void __cdecl exit(int status)
{
UNREFERENCED_PARAMETER(status);
abort();
}
int __cdecl printf(const char* fmt, ...)
{
va_list ap;
va_start(ap, fmt);
ULONG r = vDbgPrintExWithPrefix("[goodmans] ", DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, fmt, ap);
va_end(ap);
return (int)r;
}
int __cdecl fprintf(FILE* stream, const char* fmt, ...)
{
UNREFERENCED_PARAMETER(stream);
va_list ap;
va_start(ap, fmt);
ULONG r = vDbgPrintExWithPrefix("[goodmans] ", DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, fmt, ap);
va_end(ap);
return (int)r;
}
int __cdecl snprintf(char* buf, size_t sz, const char* fmt, ...)
{
va_list ap;
va_start(ap, fmt);
NTSTATUS s = RtlStringCbVPrintfA(buf, sz, fmt, ap);
va_end(ap);
if (!NT_SUCCESS(s))
return -1;
size_t len = 0;
while (buf[len]) len++;
return (int)len;
}
int __cdecl vsnprintf(char* buf, size_t sz, const char* fmt, va_list ap)
{
NTSTATUS s = RtlStringCbVPrintfA(buf, sz, fmt, ap);
if (!NT_SUCCESS(s))
return -1;
size_t len = 0;
while (buf[len]) len++;
return (int)len;
}
int __cdecl puts(const char* s)
{
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "[goodmans] %s\n", s ? s : "");
return 0;
}
int __cdecl fputs(const char* s, FILE* stream)
{
UNREFERENCED_PARAMETER(stream);
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "[goodmans] %s", s ? s : "");
return 0;
}
int __cdecl fputc(int c, FILE* stream)
{
UNREFERENCED_PARAMETER(stream);
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "%c", c);
return c;
}
int __cdecl putchar(int c)
{
return fputc(c, NULL);
}
int __cdecl fflush(FILE* stream)
{
UNREFERENCED_PARAMETER(stream);
return 0;
}
// m3_CallArgv wants strtoul/strtoull, base-10 or 0x hex
static int _kshim_digit(int c, int base)
{
int v = -1;
if (c >= '0' && c <= '9') v = c - '0';
else if (c >= 'a' && c <= 'z') v = c - 'a' + 10;
else if (c >= 'A' && c <= 'Z') v = c - 'A' + 10;
if (v < 0 || v >= base) return -1;
return v;
}
unsigned long __cdecl strtoul(const char* nptr, char** endptr, int base)
{
const char* p = nptr;
while (*p == ' ' || *p == '\t') p++;
if (*p == '+') p++;
if (base == 0) {
if (p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) { base = 16; p += 2; }
else if (p[0] == '0') { base = 8; p++; }
else base = 10;
} else if (base == 16 && p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) {
p += 2;
}
unsigned long acc = 0;
int d;
while ((d = _kshim_digit((unsigned char)*p, base)) >= 0) {
acc = acc * (unsigned long)base + (unsigned long)d;
p++;
}
if (endptr) *endptr = (char*)p;
return acc;
}
unsigned long long __cdecl strtoull(const char* nptr, char** endptr, int base)
{
const char* p = nptr;
while (*p == ' ' || *p == '\t') p++;
if (*p == '+') p++;
if (base == 0) {
if (p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) { base = 16; p += 2; }
else if (p[0] == '0') { base = 8; p++; }
else base = 10;
} else if (base == 16 && p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) {
p += 2;
}
unsigned long long acc = 0;
int d;
while ((d = _kshim_digit((unsigned char)*p, base)) >= 0) {
acc = acc * (unsigned long long)base + (unsigned long long)d;
p++;
}
if (endptr) *endptr = (char*)p;
return acc;
}
// kshim_* aliases for direct kshim.h consumers
void* kshim_alloc(size_t n) { return malloc(n); }
void* kshim_calloc(size_t n, size_t sz) { return calloc(n, sz); }
void* kshim_realloc(void* p, size_t new_sz){ return realloc(p, new_sz); }
void kshim_free(void* p) { free(p); }
void kshim_abort(const char* msg) { UNREFERENCED_PARAMETER(msg); abort(); }
int kshim_printf(const char* fmt, ...)
{
va_list ap;
va_start(ap, fmt);
ULONG r = vDbgPrintExWithPrefix("[goodmans] ", DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, fmt, ap);
va_end(ap);
return (int)r;
}
int kshim_snprintf(char* buf, size_t sz, const char* fmt, ...)
{
va_list ap;
va_start(ap, fmt);
NTSTATUS s = RtlStringCbVPrintfA(buf, sz, fmt, ap);
va_end(ap);
if (!NT_SUCCESS(s)) return -1;
size_t len = 0; while (buf[len]) len++;
return (int)len;
}
int kshim_vsnprintf(char* buf, size_t sz, const char* fmt, va_list ap)
{
NTSTATUS s = RtlStringCbVPrintfA(buf, sz, fmt, ap);
if (!NT_SUCCESS(s)) return -1;
size_t len = 0; while (buf[len]) len++;
return (int)len;
}
+24
View File
@@ -0,0 +1,24 @@
/* kshim.h - interface for kshim.c and driver TUs */
#pragma once
#include <ntddk.h>
#include <ntstrsafe.h>
#define GVM_POOL_TAG 'MVoG'
#ifdef __cplusplus
extern "C" {
#endif
void* kshim_alloc(size_t n);
void* kshim_calloc(size_t n, size_t sz);
void* kshim_realloc(void* p, size_t new_sz);
void kshim_free(void* p);
void kshim_abort(const char* msg);
int kshim_printf(const char* fmt, ...);
int kshim_snprintf(char* buf, size_t sz, const char* fmt, ...);
int kshim_vsnprintf(char* buf, size_t sz, const char* fmt, va_list ap);
#ifdef __cplusplus
}
#endif
+2
View File
@@ -0,0 +1,2 @@
/* malloc.h - blocks km/crt/malloc.h, decls in stdlib.h */
#pragma once
+29
View File
@@ -0,0 +1,29 @@
/* stdio.h - kshim decls, bodies in kshim.c */
#pragma once
#include <stdarg.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef void FILE;
extern FILE* const stdout;
extern FILE* const stderr;
extern FILE* const stdin;
int __cdecl printf(const char* fmt, ...);
int __cdecl fprintf(FILE* stream, const char* fmt, ...);
int __cdecl snprintf(char* buf, size_t sz, const char* fmt, ...);
int __cdecl vsnprintf(char* buf, size_t sz, const char* fmt, va_list ap);
int __cdecl puts(const char* s);
int __cdecl fputs(const char* s, FILE* stream);
int __cdecl fputc(int c, FILE* stream);
int __cdecl putchar(int c);
int __cdecl fflush(FILE* stream);
#ifdef __cplusplus
}
#endif
+25
View File
@@ -0,0 +1,25 @@
/* stdlib.h - kshim decls */
#pragma once
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
#ifndef NULL
#define NULL ((void*)0)
#endif
void* __cdecl malloc(size_t n);
void* __cdecl calloc(size_t n, size_t sz);
void* __cdecl realloc(void* p, size_t new_sz);
void __cdecl free(void* p);
void __cdecl abort(void);
void __cdecl exit(int status);
#define _abs64(x) ((x) < 0 ? -(x) : (x))
#ifdef __cplusplus
}
#endif
+95
View File
@@ -0,0 +1,95 @@
/* log_ring.c - kernel-side ring of recent debug lines. GUI polls via ioctl. */
#include "inc/gvm.h"
#include "../shared/goodmans_ioctl.h"
static gvm_log_entry g_ring[GVM_LOG_ENTRIES];
static ULONG64 g_seq = 0; // strictly increasing, monotonic
static ULONG g_head = 0; // next write slot
static KSPIN_LOCK g_lock;
static BOOLEAN g_init = FALSE;
void
gvm_log_init(void)
{
if (g_init) return;
RtlZeroMemory(g_ring, sizeof(g_ring));
KeInitializeSpinLock(&g_lock);
g_init = TRUE;
}
void
gvm_log_push(const char* fmt, ...)
{
if (!g_init) return;
char tmp[GVM_LOG_ENTRY_LEN];
va_list ap; va_start(ap, fmt);
RtlStringCbVPrintfA(tmp, sizeof(tmp), fmt, ap);
va_end(ap);
// also mirror to DbgPrint so DebugView keeps working
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "[goodmans] %s\n", tmp);
LARGE_INTEGER now;
KeQuerySystemTimePrecise(&now);
KIRQL irql;
KeAcquireSpinLock(&g_lock, &irql);
gvm_log_entry* e = &g_ring[g_head];
e->timestamp_100ns = (ULONG64)now.QuadPart;
RtlStringCbCopyA(e->line, sizeof(e->line), tmp);
g_seq++;
g_head = (g_head + 1) % GVM_LOG_ENTRIES;
KeReleaseSpinLock(&g_lock, irql);
}
NTSTATUS
gvm_ioctl_tail_log(PIRP irp, PIO_STACK_LOCATION sp)
{
ULONG in_len = sp->Parameters.DeviceIoControl.InputBufferLength;
ULONG out_len = sp->Parameters.DeviceIoControl.OutputBufferLength;
void* buf = irp->AssociatedIrp.SystemBuffer;
if (in_len < sizeof(gvm_tail_in) || out_len < sizeof(gvm_tail_out) || !buf) {
irp->IoStatus.Information = 0;
return STATUS_INVALID_PARAMETER;
}
gvm_tail_in in;
RtlCopyMemory(&in, buf, sizeof(in));
gvm_tail_out* out = (gvm_tail_out*)ExAllocatePoolWithTag(
NonPagedPoolNx, sizeof(gvm_tail_out), 'gLog');
if (!out) return STATUS_INSUFFICIENT_RESOURCES;
RtlZeroMemory(out, sizeof(*out));
KIRQL irql;
KeAcquireSpinLock(&g_lock, &irql);
ULONG64 seq_here = g_seq;
ULONG64 want_from = in.last_seq;
ULONG64 available = 0;
if (seq_here > want_from) {
available = seq_here - want_from;
if (available > GVM_LOG_ENTRIES) {
out->dropped = (unsigned int)(available - GVM_LOG_ENTRIES);
available = GVM_LOG_ENTRIES;
}
}
// walk backwards from head, taking `available` newest entries in order
ULONG head = g_head;
for (ULONG64 i = 0; i < available; i++) {
ULONG idx = (head + GVM_LOG_ENTRIES - (ULONG)available + (ULONG)i) % GVM_LOG_ENTRIES;
out->entries[i] = g_ring[idx];
}
out->count = (unsigned int)available;
out->next_seq = seq_here;
KeReleaseSpinLock(&g_lock, irql);
RtlCopyMemory(buf, out, sizeof(*out));
ExFreePoolWithTag(out, 'gLog');
irp->IoStatus.Information = sizeof(gvm_tail_out);
return STATUS_SUCCESS;
}
+150
View File
@@ -0,0 +1,150 @@
/* module_table.c - fixed-size slot allocator for loaded modules.
* refcount is atomic (LONG). teardown holds the module's call_mutex so
* any in-flight m3_Call has to finish before the runtime is freed. */
#include "inc/gvm.h"
#include "../shared/goodmans_ioctl.h"
static gvm_module g_modules[GVM_MAX_MODULES];
static KSPIN_LOCK g_lock;
NTSTATUS
gvm_modtab_init(void)
{
RtlZeroMemory(g_modules, sizeof(g_modules));
KeInitializeSpinLock(&g_lock);
// init every mutex up front so iterators that wait on all slots (e.g. the
// callback dispatch worker) don't touch an uninitialized DISPATCHER_HEADER
for (unsigned int i = 0; i < GVM_MAX_MODULES; i++)
KeInitializeMutex(&g_modules[i].call_mutex, 0);
return STATUS_SUCCESS;
}
void
gvm_modtab_teardown(void)
{
for (unsigned int i = 0; i < GVM_MAX_MODULES; i++) {
if (g_modules[i].used) {
g_modules[i].refcount = 1; // force teardown
gvm_modtab_free(&g_modules[i]);
}
}
}
gvm_module*
gvm_modtab_alloc(void)
{
KIRQL irql;
gvm_module* out = NULL;
KeAcquireSpinLock(&g_lock, &irql);
for (unsigned int i = 0; i < GVM_MAX_MODULES; i++) {
if (!g_modules[i].used) {
g_modules[i].used = TRUE;
g_modules[i].id = i + 1;
g_modules[i].refcount = 1;
KeInitializeMutex(&g_modules[i].call_mutex, 0);
out = &g_modules[i];
break;
}
}
KeReleaseSpinLock(&g_lock, irql);
return out;
}
gvm_module*
gvm_modtab_get(unsigned int id)
{
if (id == 0 || id > GVM_MAX_MODULES) return NULL;
gvm_module* m = &g_modules[id - 1];
return m->used ? m : NULL;
}
gvm_module*
gvm_modtab_find_by_hash(unsigned long long hash)
{
KIRQL irql;
gvm_module* out = NULL;
KeAcquireSpinLock(&g_lock, &irql);
for (unsigned int i = 0; i < GVM_MAX_MODULES; i++) {
if (g_modules[i].used && g_modules[i].hash == hash) {
out = &g_modules[i];
break;
}
}
KeReleaseSpinLock(&g_lock, irql);
return out;
}
// atomic find + increment under the table lock. safer than
// find_by_hash + separate refcount++ which races with concurrent unload.
gvm_module*
gvm_modtab_find_by_hash_incref(unsigned long long hash, unsigned int expected_size)
{
KIRQL irql;
gvm_module* out = NULL;
KeAcquireSpinLock(&g_lock, &irql);
for (unsigned int i = 0; i < GVM_MAX_MODULES; i++) {
if (g_modules[i].used
&& g_modules[i].hash == hash
&& g_modules[i].wasm_size == expected_size) {
InterlockedIncrement(&g_modules[i].refcount);
out = &g_modules[i];
break;
}
}
KeReleaseSpinLock(&g_lock, irql);
return out;
}
gvm_module*
gvm_modtab_iter(unsigned int idx)
{
if (idx >= GVM_MAX_MODULES) return NULL;
return &g_modules[idx];
}
gvm_module*
gvm_modtab_owner_of_runtime(IM3Runtime rt)
{
if (!rt) return NULL;
for (unsigned int i = 0; i < GVM_MAX_MODULES; i++) {
if (g_modules[i].used && g_modules[i].runtime == rt)
return &g_modules[i];
}
return NULL;
}
void
gvm_modtab_free(gvm_module* m)
{
if (!m || !m->used) return;
// only the last reference proceeds to teardown
if (InterlockedDecrement(&m->refcount) > 0) return;
// block until any in-flight m3_Call finishes so the runtime is quiescent
KeWaitForSingleObject(&m->call_mutex, Executive, KernelMode, FALSE, NULL);
if (m->runtime) {
m3_FreeRuntime(m->runtime);
m->runtime = NULL;
}
if (m->env) {
m3_FreeEnvironment(m->env);
m->env = NULL;
}
if (m->wasm_bytes) {
ExFreePoolWithTag(m->wasm_bytes, GVM_TAG_WBUF);
m->wasm_bytes = NULL;
}
m->module = NULL;
m->wasm_size = 0;
m->hash = 0;
// publish used=FALSE before releasing so waiters see it on retry
m->used = FALSE;
KeReleaseMutex(&m->call_mutex, FALSE);
}
+137
View File
@@ -0,0 +1,137 @@
/* trace_ring.c - per-import call trace for wasm guest debugging.
* ring buffer holds recent host-import invocations, export calls, and traps.
* GUI polls via ioctl for live view.
*/
#include "inc/gvm.h"
#include "../shared/goodmans_ioctl.h"
static gvm_trace_entry g_ring[GVM_TRACE_ENTRIES];
static ULONG64 g_seq = 0;
static ULONG g_head = 0;
static KSPIN_LOCK g_lock;
static BOOLEAN g_init = FALSE;
static volatile LONG g_mode = GVM_TRACE_OFF;
static volatile LONG g_only = 0; // module_id filter when mode==ON_MODULE
void
gvm_trace_init(void)
{
if (g_init) return;
RtlZeroMemory(g_ring, sizeof(g_ring));
KeInitializeSpinLock(&g_lock);
g_init = TRUE;
}
BOOLEAN
gvm_trace_enabled_for(unsigned int module_id)
{
LONG m = g_mode;
if (m == GVM_TRACE_OFF) return FALSE;
if (m == GVM_TRACE_ON_ALL) return TRUE;
return (LONG)module_id == g_only;
}
void
gvm_trace_push(unsigned int module_id, unsigned int kind, const char* name,
unsigned int argc, const ULONG64* argv, ULONG64 rv)
{
if (!g_init) return;
if (!gvm_trace_enabled_for(module_id)) return;
LARGE_INTEGER now;
KeQuerySystemTimePrecise(&now);
// capture pre-lock so lock IRQL doesn't confuse the value
unsigned int cap_irql = (unsigned int)KeGetCurrentIrql();
unsigned int cap_tid = (unsigned int)(ULONG_PTR)PsGetCurrentThreadId();
KIRQL irql;
KeAcquireSpinLock(&g_lock, &irql);
gvm_trace_entry* e = &g_ring[g_head];
RtlZeroMemory(e, sizeof(*e));
e->timestamp_100ns = (ULONG64)now.QuadPart;
e->module_id = module_id;
e->kind = kind;
e->thread_id = cap_tid;
e->irql = cap_irql;
e->argc = argc > 4 ? 4 : argc;
for (unsigned int i = 0; i < e->argc; i++) e->argv[i] = argv[i];
e->rv = rv;
if (name) RtlStringCbCopyA(e->name, sizeof(e->name), name);
g_seq++;
g_head = (g_head + 1) % GVM_TRACE_ENTRIES;
KeReleaseSpinLock(&g_lock, irql);
}
NTSTATUS
gvm_ioctl_tail_trace(PIRP irp, PIO_STACK_LOCATION sp)
{
ULONG in_len = sp->Parameters.DeviceIoControl.InputBufferLength;
ULONG out_len = sp->Parameters.DeviceIoControl.OutputBufferLength;
void* buf = irp->AssociatedIrp.SystemBuffer;
if (in_len < sizeof(gvm_trace_in) || out_len < sizeof(gvm_trace_out) || !buf) {
irp->IoStatus.Information = 0;
return STATUS_INVALID_PARAMETER;
}
gvm_trace_in in;
RtlCopyMemory(&in, buf, sizeof(in));
gvm_trace_out* out = (gvm_trace_out*)ExAllocatePoolWithTag(
NonPagedPoolNx, sizeof(gvm_trace_out), 'gTrc');
if (!out) return STATUS_INSUFFICIENT_RESOURCES;
RtlZeroMemory(out, sizeof(*out));
KIRQL irql;
KeAcquireSpinLock(&g_lock, &irql);
ULONG64 seq_here = g_seq;
ULONG64 want_from = in.last_seq;
ULONG64 available = 0;
ULONG64 dropped = 0;
if (seq_here > want_from) {
available = seq_here - want_from;
if (available > GVM_TRACE_ENTRIES) {
dropped = available - GVM_TRACE_ENTRIES;
available = GVM_TRACE_ENTRIES;
}
ULONG start = (g_head + GVM_TRACE_ENTRIES - (ULONG)available) % GVM_TRACE_ENTRIES;
for (ULONG64 i = 0; i < available; i++) {
ULONG src = (start + (ULONG)i) % GVM_TRACE_ENTRIES;
out->entries[i] = g_ring[src];
}
}
out->next_seq = seq_here;
out->count = (unsigned int)available;
out->dropped = (unsigned int)dropped;
KeReleaseSpinLock(&g_lock, irql);
RtlCopyMemory(buf, out, sizeof(*out));
ExFreePoolWithTag(out, 'gTrc');
irp->IoStatus.Information = sizeof(gvm_trace_out);
return STATUS_SUCCESS;
}
NTSTATUS
gvm_ioctl_trace_ctl(PIRP irp, PIO_STACK_LOCATION sp)
{
ULONG in_len = sp->Parameters.DeviceIoControl.InputBufferLength;
void* buf = irp->AssociatedIrp.SystemBuffer;
if (in_len < sizeof(gvm_trace_ctl_in) || !buf) {
irp->IoStatus.Information = 0;
return STATUS_INVALID_PARAMETER;
}
gvm_trace_ctl_in in;
RtlCopyMemory(&in, buf, sizeof(in));
InterlockedExchange(&g_mode, (LONG)in.mode);
InterlockedExchange(&g_only, (LONG)in.module_id);
gvm_log_push("trace mode=%u module=%u", in.mode, in.module_id);
irp->IoStatus.Information = 0;
return STATUS_SUCCESS;
}
+175
View File
@@ -0,0 +1,175 @@
//
// m3_bind.c
//
// Created by Steven Massey on 4/29/19.
// Copyright © 2019 Steven Massey. All rights reserved.
//
#include "m3_env.h"
#include "m3_exception.h"
#include "m3_info.h"
u8 ConvertTypeCharToTypeId (char i_code)
{
switch (i_code) {
case 'v': return c_m3Type_none;
case 'i': return c_m3Type_i32;
case 'I': return c_m3Type_i64;
case 'f': return c_m3Type_f32;
case 'F': return c_m3Type_f64;
case '*': return c_m3Type_i32;
}
return c_m3Type_unknown;
}
M3Result SignatureToFuncType (IM3FuncType * o_functionType, ccstr_t i_signature)
{
IM3FuncType funcType = NULL;
_try {
if (not o_functionType)
_throw ("null function type");
if (not i_signature)
_throw ("null function signature");
cstr_t sig = i_signature;
size_t maxNumTypes = strlen (i_signature);
// assume min signature is "()"
_throwif (m3Err_malformedFunctionSignature, maxNumTypes < 2);
maxNumTypes -= 2;
_throwif (m3Err_tooManyArgsRets, maxNumTypes > d_m3MaxSaneFunctionArgRetCount);
_ (AllocFuncType (& funcType, (u32) maxNumTypes));
u8 * typelist = funcType->types;
bool parsingRets = true;
while (* sig)
{
char typeChar = * sig++;
if (typeChar == '(')
{
parsingRets = false;
continue;
}
else if ( typeChar == ' ')
continue;
else if (typeChar == ')')
break;
u8 type = ConvertTypeCharToTypeId (typeChar);
_throwif ("unknown argument type char", c_m3Type_unknown == type);
if (type == c_m3Type_none)
continue;
if (parsingRets)
{
_throwif ("malformed signature; return count overflow", funcType->numRets >= maxNumTypes);
funcType->numRets++;
*typelist++ = type;
}
else
{
_throwif ("malformed signature; arg count overflow", (u32)(funcType->numRets) + funcType->numArgs >= maxNumTypes);
funcType->numArgs++;
*typelist++ = type;
}
}
} _catch:
if (result)
m3_Free (funcType);
* o_functionType = funcType;
return result;
}
static
M3Result ValidateSignature (IM3Function i_function, ccstr_t i_linkingSignature)
{
M3Result result = m3Err_none;
IM3FuncType ftype = NULL;
_ (SignatureToFuncType (& ftype, i_linkingSignature));
if (not AreFuncTypesEqual (ftype, i_function->funcType))
{
m3log (module, "expected: %s", SPrintFuncTypeSignature (ftype));
m3log (module, " found: %s", SPrintFuncTypeSignature (i_function->funcType));
_throw ("function signature mismatch");
}
_catch:
m3_Free (ftype);
return result;
}
M3Result FindAndLinkFunction (IM3Module io_module,
ccstr_t i_moduleName,
ccstr_t i_functionName,
ccstr_t i_signature,
voidptr_t i_function,
voidptr_t i_userdata)
{
_try {
_throwif(m3Err_moduleNotLinked, !io_module->runtime);
const bool wildcardModule = (strcmp (i_moduleName, "*") == 0);
result = m3Err_functionLookupFailed;
for (u32 i = 0; i < io_module->numFunctions; ++i)
{
const IM3Function f = & io_module->functions [i];
if (f->import.moduleUtf8 and f->import.fieldUtf8)
{
if (strcmp (f->import.fieldUtf8, i_functionName) == 0 and
(wildcardModule or strcmp (f->import.moduleUtf8, i_moduleName) == 0))
{
if (i_signature) {
_ (ValidateSignature (f, i_signature));
}
_ (CompileRawFunction (io_module, f, i_function, i_userdata));
}
}
}
} _catch:
return result;
}
M3Result m3_LinkRawFunctionEx (IM3Module io_module,
const char * const i_moduleName,
const char * const i_functionName,
const char * const i_signature,
M3RawCall i_function,
const void * i_userdata)
{
return FindAndLinkFunction (io_module, i_moduleName, i_functionName, i_signature, (voidptr_t)i_function, i_userdata);
}
M3Result m3_LinkRawFunction (IM3Module io_module,
const char * const i_moduleName,
const char * const i_functionName,
const char * const i_signature,
M3RawCall i_function)
{
return FindAndLinkFunction (io_module, i_moduleName, i_functionName, i_signature, (voidptr_t)i_function, NULL);
}
+20
View File
@@ -0,0 +1,20 @@
//
// m3_bind.h
//
// Created by Steven Massey on 2/27/20.
// Copyright © 2020 Steven Massey. All rights reserved.
//
#ifndef m3_bind_h
#define m3_bind_h
#include "m3_env.h"
d_m3BeginExternC
u8 ConvertTypeCharToTypeId (char i_code);
M3Result SignatureToFuncType (IM3FuncType * o_functionType, ccstr_t i_signature);
d_m3EndExternC
#endif /* m3_bind_h */
+246
View File
@@ -0,0 +1,246 @@
//
// m3_code.c
//
// Created by Steven Massey on 4/19/19.
// Copyright © 2019 Steven Massey. All rights reserved.
//
#include <limits.h>
#include "m3_code.h"
#include "m3_env.h"
//---------------------------------------------------------------------------------------------------------------------------------
IM3CodePage NewCodePage (IM3Runtime i_runtime, u32 i_minNumLines)
{
IM3CodePage page;
// check multiplication overflow
if (i_minNumLines > UINT_MAX / sizeof (code_t)) {
return NULL;
}
u32 pageSize = sizeof (M3CodePageHeader) + sizeof (code_t) * i_minNumLines;
// check addition overflow
if (pageSize < sizeof (M3CodePageHeader)) {
return NULL;
}
pageSize = (pageSize + (d_m3CodePageAlignSize-1)) & ~(d_m3CodePageAlignSize-1); // align
// check alignment overflow
if (pageSize == 0) {
return NULL;
}
page = (IM3CodePage)m3_Malloc ("M3CodePage", pageSize);
if (page)
{
page->info.sequence = ++i_runtime->newCodePageSequence;
page->info.numLines = (pageSize - sizeof (M3CodePageHeader)) / sizeof (code_t);
#if d_m3RecordBacktraces
u32 pageSizeBt = sizeof (M3CodeMappingPage) + sizeof (M3CodeMapEntry) * page->info.numLines;
page->info.mapping = (M3CodeMappingPage *)m3_Malloc ("M3CodeMappingPage", pageSizeBt);
if (page->info.mapping)
{
page->info.mapping->size = 0;
page->info.mapping->capacity = page->info.numLines;
}
else
{
m3_Free (page);
return NULL;
}
page->info.mapping->basePC = GetPageStartPC(page);
#endif // d_m3RecordBacktraces
m3log (runtime, "new page: %p; seq: %d; bytes: %d; lines: %d", GetPagePC (page), page->info.sequence, pageSize, page->info.numLines);
}
return page;
}
void FreeCodePages (IM3CodePage * io_list)
{
IM3CodePage page = * io_list;
while (page)
{
m3log (code, "free page: %d; %p; util: %3.1f%%", page->info.sequence, page, 100. * page->info.lineIndex / page->info.numLines);
IM3CodePage next = page->info.next;
#if d_m3RecordBacktraces
m3_Free (page->info.mapping);
#endif // d_m3RecordBacktraces
m3_Free (page);
page = next;
}
* io_list = NULL;
}
u32 NumFreeLines (IM3CodePage i_page)
{
d_m3Assert (i_page->info.lineIndex <= i_page->info.numLines);
return i_page->info.numLines - i_page->info.lineIndex;
}
void EmitWord_impl (IM3CodePage i_page, void * i_word)
{ d_m3Assert (i_page->info.lineIndex+1 <= i_page->info.numLines);
i_page->code [i_page->info.lineIndex++] = i_word;
}
void EmitWord32 (IM3CodePage i_page, const u32 i_word)
{ d_m3Assert (i_page->info.lineIndex+1 <= i_page->info.numLines);
memcpy (& i_page->code[i_page->info.lineIndex++], & i_word, sizeof(i_word));
}
void EmitWord64 (IM3CodePage i_page, const u64 i_word)
{
#if M3_SIZEOF_PTR == 4
d_m3Assert (i_page->info.lineIndex+2 <= i_page->info.numLines);
memcpy (& i_page->code[i_page->info.lineIndex], & i_word, sizeof(i_word));
i_page->info.lineIndex += 2;
#else
d_m3Assert (i_page->info.lineIndex+1 <= i_page->info.numLines);
memcpy (& i_page->code[i_page->info.lineIndex], & i_word, sizeof(i_word));
i_page->info.lineIndex += 1;
#endif
}
#if d_m3RecordBacktraces
void EmitMappingEntry (IM3CodePage i_page, u32 i_moduleOffset)
{
M3CodeMappingPage * page = i_page->info.mapping;
d_m3Assert (page->size < page->capacity);
M3CodeMapEntry * entry = & page->entries[page->size++];
pc_t pc = GetPagePC (i_page);
entry->pcOffset = pc - page->basePC;
entry->moduleOffset = i_moduleOffset;
}
#endif // d_m3RecordBacktraces
pc_t GetPageStartPC (IM3CodePage i_page)
{
return & i_page->code [0];
}
pc_t GetPagePC (IM3CodePage i_page)
{
if (i_page)
return & i_page->code [i_page->info.lineIndex];
else
return NULL;
}
void PushCodePage (IM3CodePage * i_list, IM3CodePage i_codePage)
{
IM3CodePage next = * i_list;
i_codePage->info.next = next;
* i_list = i_codePage;
}
IM3CodePage PopCodePage (IM3CodePage * i_list)
{
IM3CodePage page = * i_list;
* i_list = page->info.next;
page->info.next = NULL;
return page;
}
u32 FindCodePageEnd (IM3CodePage i_list, IM3CodePage * o_end)
{
u32 numPages = 0;
* o_end = NULL;
while (i_list)
{
* o_end = i_list;
++numPages;
i_list = i_list->info.next;
}
return numPages;
}
u32 CountCodePages (IM3CodePage i_list)
{
IM3CodePage unused;
return FindCodePageEnd (i_list, & unused);
}
IM3CodePage GetEndCodePage (IM3CodePage i_list)
{
IM3CodePage end;
FindCodePageEnd (i_list, & end);
return end;
}
#if d_m3RecordBacktraces
bool ContainsPC (IM3CodePage i_page, pc_t i_pc)
{
return GetPageStartPC (i_page) <= i_pc && i_pc < GetPagePC (i_page);
}
bool MapPCToOffset (IM3CodePage i_page, pc_t i_pc, u32 * o_moduleOffset)
{
M3CodeMappingPage * mapping = i_page->info.mapping;
u32 pcOffset = i_pc - mapping->basePC;
u32 left = 0;
u32 right = mapping->size;
while (left < right)
{
u32 mid = left + (right - left) / 2;
if (mapping->entries[mid].pcOffset < pcOffset)
{
left = mid + 1;
}
else if (mapping->entries[mid].pcOffset > pcOffset)
{
right = mid;
}
else
{
*o_moduleOffset = mapping->entries[mid].moduleOffset;
return true;
}
}
// Getting here means left is now one more than the element we want.
if (left > 0)
{
left--;
*o_moduleOffset = mapping->entries[left].moduleOffset;
return true;
}
else return false;
}
#endif // d_m3RecordBacktraces
//---------------------------------------------------------------------------------------------------------------------------------
+80
View File
@@ -0,0 +1,80 @@
//
// m3_code.h
//
// Created by Steven Massey on 4/19/19.
// Copyright © 2019 Steven Massey. All rights reserved.
//
#ifndef m3_code_h
#define m3_code_h
#include "m3_core.h"
d_m3BeginExternC
typedef struct M3CodePage
{
M3CodePageHeader info;
code_t code [1];
}
M3CodePage;
typedef M3CodePage * IM3CodePage;
IM3CodePage NewCodePage (IM3Runtime i_runtime, u32 i_minNumLines);
void FreeCodePages (IM3CodePage * io_list);
u32 NumFreeLines (IM3CodePage i_page);
pc_t GetPageStartPC (IM3CodePage i_page);
pc_t GetPagePC (IM3CodePage i_page);
void EmitWord_impl (IM3CodePage i_page, void* i_word);
void EmitWord32 (IM3CodePage i_page, u32 i_word);
void EmitWord64 (IM3CodePage i_page, u64 i_word);
# if d_m3RecordBacktraces
void EmitMappingEntry (IM3CodePage i_page, u32 i_moduleOffset);
# endif // d_m3RecordBacktraces
void PushCodePage (IM3CodePage * io_list, IM3CodePage i_codePage);
IM3CodePage PopCodePage (IM3CodePage * io_list);
IM3CodePage GetEndCodePage (IM3CodePage i_list); // i_list = NULL is valid
u32 CountCodePages (IM3CodePage i_list); // i_list = NULL is valid
# if d_m3RecordBacktraces
bool ContainsPC (IM3CodePage i_page, pc_t i_pc);
bool MapPCToOffset (IM3CodePage i_page, pc_t i_pc, u32 * o_moduleOffset);
# endif // d_m3RecordBacktraces
# ifdef DEBUG
void dump_code_page (IM3CodePage i_codePage, pc_t i_startPC);
# endif
#define EmitWord(page, val) EmitWord_impl(page, (void*)(val))
//---------------------------------------------------------------------------------------------------------------------------------
# if d_m3RecordBacktraces
typedef struct M3CodeMapEntry
{
u32 pcOffset;
u32 moduleOffset;
}
M3CodeMapEntry;
typedef struct M3CodeMappingPage
{
pc_t basePC;
u32 size;
u32 capacity;
M3CodeMapEntry entries [];
}
M3CodeMappingPage;
# endif // d_m3RecordBacktraces
d_m3EndExternC
#endif // m3_code_h
File diff suppressed because it is too large Load Diff
+208
View File
@@ -0,0 +1,208 @@
//
// m3_compile.h
//
// Created by Steven Massey on 4/17/19.
// Copyright © 2019 Steven Massey. All rights reserved.
//
#ifndef m3_compile_h
#define m3_compile_h
#include "m3_code.h"
#include "m3_exec_defs.h"
#include "m3_function.h"
d_m3BeginExternC
enum
{
c_waOp_block = 0x02,
c_waOp_loop = 0x03,
c_waOp_if = 0x04,
c_waOp_else = 0x05,
c_waOp_end = 0x0b,
c_waOp_branch = 0x0c,
c_waOp_branchTable = 0x0e,
c_waOp_branchIf = 0x0d,
c_waOp_call = 0x10,
c_waOp_getLocal = 0x20,
c_waOp_setLocal = 0x21,
c_waOp_teeLocal = 0x22,
c_waOp_getGlobal = 0x23,
c_waOp_store_f32 = 0x38,
c_waOp_store_f64 = 0x39,
c_waOp_i32_const = 0x41,
c_waOp_i64_const = 0x42,
c_waOp_f32_const = 0x43,
c_waOp_f64_const = 0x44,
c_waOp_extended = 0xfc,
c_waOp_memoryCopy = 0xfc0a,
c_waOp_memoryFill = 0xfc0b
};
#define d_FuncRetType(ftype,i) ((ftype)->types[(i)])
#define d_FuncArgType(ftype,i) ((ftype)->types[(ftype)->numRets + (i)])
//-----------------------------------------------------------------------------------------------------------------------------------
typedef struct M3CompilationScope
{
struct M3CompilationScope * outer;
pc_t pc; // used by ContinueLoop's
pc_t patches;
i32 depth;
u16 exitStackIndex;
u16 blockStackIndex;
// u16 topSlot;
IM3FuncType type;
m3opcode_t opcode;
bool isPolymorphic;
}
M3CompilationScope;
typedef M3CompilationScope * IM3CompilationScope;
typedef struct
{
IM3Runtime runtime;
IM3Module module;
bytes_t wasm;
bytes_t wasmEnd;
bytes_t lastOpcodeStart;
M3CompilationScope block;
IM3Function function;
IM3CodePage page;
#ifdef DEBUG
u32 numEmits;
u32 numOpcodes;
#endif
u16 stackFirstDynamicIndex; // args and locals are pushed to the stack so that their slot locations can be tracked. the wasm model itself doesn't
// treat these values as being on the stack, so stackFirstDynamicIndex marks the start of the real Wasm stack
u16 stackIndex; // current stack top
u16 slotFirstConstIndex;
u16 slotMaxConstIndex; // as const's are encountered during compilation this tracks their location in the "real" stack
u16 slotFirstLocalIndex;
u16 slotFirstDynamicIndex; // numArgs + numLocals + numReservedConstants. the first mutable slot available to the compiler.
u16 maxStackSlots;
m3slot_t constants [d_m3MaxConstantTableSize];
// 'wasmStack' holds slot locations
u16 wasmStack [d_m3MaxFunctionStackHeight];
u8 typeStack [d_m3MaxFunctionStackHeight];
// 'm3Slots' contains allocation usage counts
u8 m3Slots [d_m3MaxFunctionSlots];
u16 slotMaxAllocatedIndexPlusOne;
u16 regStackIndexPlusOne [2];
m3opcode_t previousOpcode;
bool isInitExpr; // walking a constant expression, not a function body
}
M3Compilation;
typedef M3Compilation * IM3Compilation;
typedef M3Result (* M3Compiler) (IM3Compilation, m3opcode_t);
//-----------------------------------------------------------------------------------------------------------------------------------
typedef struct M3OpInfo
{
#ifdef DEBUG
const char * const name;
#endif
i8 stackOffset;
u8 type;
// for most operations:
// [0]= top operand in register, [1]= top operand in stack, [2]= both operands in stack
IM3Operation operations [4];
M3Compiler compiler;
}
M3OpInfo;
typedef const M3OpInfo * IM3OpInfo;
IM3OpInfo GetOpInfo (m3opcode_t opcode);
// TODO: This helper should be removed, when MultiValue is implemented
static inline
u8 GetSingleRetType(IM3FuncType ftype) {
return (ftype && ftype->numRets) ? ftype->types[0] : (u8)c_m3Type_none;
}
static const u16 c_m3RegisterUnallocated = 0;
static const u16 c_slotUnused = 0xffff;
static inline
bool IsRegisterAllocated (IM3Compilation o, u32 i_register)
{
return (o->regStackIndexPlusOne [i_register] != c_m3RegisterUnallocated);
}
static inline
bool IsStackPolymorphic (IM3Compilation o)
{
return o->block.isPolymorphic;
}
static inline bool IsRegisterSlotAlias (u16 i_slot) { return (i_slot >= d_m3Reg0SlotAlias and i_slot != c_slotUnused); }
static inline bool IsFpRegisterSlotAlias (u16 i_slot) { return (i_slot == d_m3Fp0SlotAlias); }
static inline bool IsIntRegisterSlotAlias (u16 i_slot) { return (i_slot == d_m3Reg0SlotAlias); }
#ifdef DEBUG
#define M3OP(...) { __VA_ARGS__ }
#define M3OP_RESERVED { "reserved" }
#else
// Strip-off name
#define M3OP(name, ...) { __VA_ARGS__ }
#define M3OP_RESERVED { 0 }
#endif
#if d_m3HasFloat
#define M3OP_F M3OP
#elif d_m3NoFloatDynamic
#define M3OP_F(n,o,t,op,...) M3OP(n, o, t, { op_Unsupported, op_Unsupported, op_Unsupported, op_Unsupported }, __VA_ARGS__)
#else
#define M3OP_F(...) { 0 }
#endif
//-----------------------------------------------------------------------------------------------------------------------------------
u16 GetMaxUsedSlotPlusOne (IM3Compilation o);
M3Result CompileBlock (IM3Compilation io, IM3FuncType i_blockType, m3opcode_t i_blockOpcode);
M3Result CompileBlockStatements (IM3Compilation io);
M3Result CompileFunction (IM3Function io_function);
M3Result CompileRawFunction (IM3Module io_module, IM3Function io_function, const void * i_function, const void * i_userdata);
d_m3EndExternC
#endif // m3_compile_h
+168
View File
@@ -0,0 +1,168 @@
//
// m3_config.h
//
// Created by Steven Massey on 5/4/19.
// Copyright © 2019 Steven Massey. All rights reserved.
//
#ifndef m3_config_h
#define m3_config_h
#include "m3_config_platforms.h"
// general --------------------------------------------------------------------
# ifndef d_m3CodePageAlignSize
# define d_m3CodePageAlignSize 32*1024
# endif
# ifndef d_m3MaxFunctionStackHeight
# define d_m3MaxFunctionStackHeight 2000 // max: 32768
# endif
# ifndef d_m3MaxLinearMemoryPages
# define d_m3MaxLinearMemoryPages 65536
# endif
# ifndef d_m3MaxFunctionSlots
# define d_m3MaxFunctionSlots ((d_m3MaxFunctionStackHeight)*2)
# endif
# ifndef d_m3ValStack // validator operand and local type stacks:
# define d_m3ValStack (d_m3MaxFunctionStackHeight) // the same operand stack the compiler bounds
# endif
# ifndef d_m3ValCtrlDepth // validator block nesting depth. Each frame is bigger than an
# define d_m3ValCtrlDepth ((d_m3MaxFunctionStackHeight)/8)// operand entry, so this dominates the validator's stack usage
# endif
# ifndef d_m3MaxConstantTableSize
# define d_m3MaxConstantTableSize 120
# endif
# ifndef d_m3MaxDuplicateFunctionImpl
# define d_m3MaxDuplicateFunctionImpl 3
# endif
# ifndef d_m3CascadedOpcodes // Cascaded opcodes are slightly faster at the expense of some memory
# define d_m3CascadedOpcodes 1 // Adds ~3Kb to operations table in m3_compile.c
# endif
# ifndef d_m3VerboseErrorMessages
# define d_m3VerboseErrorMessages 1
# endif
# ifndef d_m3FixedHeap
# define d_m3FixedHeap false
//# define d_m3FixedHeap (32*1024)
# endif
# ifndef d_m3FixedHeapAlign
# define d_m3FixedHeapAlign 16
# endif
# ifndef d_m3Use32BitSlots
# define d_m3Use32BitSlots 1
# endif
# ifndef d_m3ProfilerSlotMask
# define d_m3ProfilerSlotMask 0xFFFF
# endif
# ifndef d_m3RecordBacktraces
# define d_m3RecordBacktraces 0
# endif
# ifndef d_m3EnableExceptionBreakpoint
# define d_m3EnableExceptionBreakpoint 0 // see m3_exception.h
# endif
// profiling and tracing ------------------------------------------------------
# ifndef d_m3EnableOpProfiling
# define d_m3EnableOpProfiling 0 // opcode usage counters
# endif
# ifndef d_m3EnableOpTracing
# define d_m3EnableOpTracing 0 // only works with DEBUG
# endif
# ifndef d_m3EnableWasiTracing
# define d_m3EnableWasiTracing 0
# endif
# ifndef d_m3EnableStrace
# define d_m3EnableStrace 0 // 1 - trace exported function calls
// 2 - trace all calls (structured)
// 3 - all calls + loops + memory operations
# endif
// logging --------------------------------------------------------------------
# ifndef d_m3LogParse
# define d_m3LogParse 0 // .wasm binary decoding info
# endif
# ifndef d_m3LogModule
# define d_m3LogModule 0 // wasm module info
# endif
# ifndef d_m3LogCompile
# define d_m3LogCompile 0 // wasm -> metacode generation phase
# endif
# ifndef d_m3LogWasmStack
# define d_m3LogWasmStack 0 // dump the wasm stack when pushed or popped
# endif
# ifndef d_m3LogEmit
# define d_m3LogEmit 0 // metacode generation info
# endif
# ifndef d_m3LogCodePages
# define d_m3LogCodePages 0 // dump metacode pages when released
# endif
# ifndef d_m3LogRuntime
# define d_m3LogRuntime 0 // higher-level runtime information
# endif
# ifndef d_m3LogNativeStack
# define d_m3LogNativeStack 0 // track the memory usage of the C-stack
# endif
# ifndef d_m3LogHeapOps
# define d_m3LogHeapOps 0 // track heap usage
# endif
# ifndef d_m3LogTimestamps
# define d_m3LogTimestamps 0 // track timestamps on heap logs
# endif
// other ----------------------------------------------------------------------
# ifndef d_m3HasFloat
# define d_m3HasFloat 1 // implement floating point ops
# endif
#if !d_m3HasFloat && !defined(d_m3NoFloatDynamic)
# define d_m3NoFloatDynamic 1 // if no floats, do not fail until flops are actually executed
#endif
# ifndef d_m3EnableValidation
# define d_m3EnableValidation 1 // pre-pass bytecode type validation
# endif
# ifndef d_m3SkipStackCheck
# define d_m3SkipStackCheck 0 // skip stack overrun checks
# endif
# ifndef d_m3SkipMemoryBoundsCheck
# define d_m3SkipMemoryBoundsCheck 0 // skip memory bounds checks
# endif
#define d_m3EnableCodePageRefCounting 0 // not supported currently
#endif // m3_config_h
+216
View File
@@ -0,0 +1,216 @@
//
// m3_config_platforms.h
//
// Created by Volodymyr Shymanskyy on 11/20/19.
// Copyright © 2019 Volodymyr Shymanskyy. All rights reserved.
//
#ifndef m3_config_platforms_h
#define m3_config_platforms_h
#include "wasm3_defs.h"
/*
* Internal helpers
*/
# if !defined(__cplusplus) || defined(_MSC_VER)
# define not !
# define and &&
# define or ||
# endif
/*
* Detect/define features
*/
# if defined(M3_COMPILER_MSVC)
# include <stdint.h>
# if UINTPTR_MAX == 0xFFFFFFFF
# define M3_SIZEOF_PTR 4
# elif UINTPTR_MAX == 0xFFFFFFFFFFFFFFFFu
# define M3_SIZEOF_PTR 8
# else
# error "Pointer size not supported"
# endif
# elif defined(__SIZEOF_POINTER__)
# define M3_SIZEOF_PTR __SIZEOF_POINTER__
#else
# error "Pointer size not detected"
# endif
# if defined(M3_BIG_ENDIAN)
# define M3_BSWAP_u8(X) {}
# define M3_BSWAP_u16(X) { (X)=m3_bswap16((X)); }
# define M3_BSWAP_u32(X) { (X)=m3_bswap32((X)); }
# define M3_BSWAP_u64(X) { (X)=m3_bswap64((X)); }
# define M3_BSWAP_i8(X) {}
# define M3_BSWAP_i16(X) M3_BSWAP_u16(X)
# define M3_BSWAP_i32(X) M3_BSWAP_u32(X)
# define M3_BSWAP_i64(X) M3_BSWAP_u64(X)
# define M3_BSWAP_f32(X) { union { f32 f; u32 i; } u; u.f = (X); M3_BSWAP_u32(u.i); (X) = u.f; }
# define M3_BSWAP_f64(X) { union { f64 f; u64 i; } u; u.f = (X); M3_BSWAP_u64(u.i); (X) = u.f; }
# else
# define M3_BSWAP_u8(X) {}
# define M3_BSWAP_u16(x) {}
# define M3_BSWAP_u32(x) {}
# define M3_BSWAP_u64(x) {}
# define M3_BSWAP_i8(X) {}
# define M3_BSWAP_i16(X) {}
# define M3_BSWAP_i32(X) {}
# define M3_BSWAP_i64(X) {}
# define M3_BSWAP_f32(X) {}
# define M3_BSWAP_f64(X) {}
# endif
# if defined(M3_COMPILER_MSVC)
# define M3_WEAK //__declspec(selectany)
# define M3_NO_UBSAN
# define M3_NOINLINE
# elif defined(__MINGW32__) || defined(__CYGWIN__)
# define M3_WEAK //__attribute__((selectany))
# define M3_NO_UBSAN
# define M3_NOINLINE __attribute__((noinline))
# else
# define M3_WEAK __attribute__((weak))
# define M3_NO_UBSAN //__attribute__((no_sanitize("undefined")))
// Workaround for Cosmopolitan noinline conflict: https://github.com/jart/cosmopolitan/issues/310
# if defined(noinline)
# define M3_NOINLINE noinline
# else
# define M3_NOINLINE __attribute__((noinline))
# endif
# endif
# if !defined(M3_HAS_TAIL_CALL)
# if defined(__EMSCRIPTEN__)
# define M3_HAS_TAIL_CALL 0
# else
# define M3_HAS_TAIL_CALL 1
# endif
# endif
# if M3_HAS_TAIL_CALL && M3_COMPILER_HAS_ATTRIBUTE(musttail)
# define M3_MUSTTAIL __attribute__((musttail))
# else
# define M3_MUSTTAIL
# endif
# ifndef M3_MIN
# define M3_MIN(A,B) (((A) < (B)) ? (A) : (B))
# endif
# ifndef M3_MAX
# define M3_MAX(A,B) (((A) > (B)) ? (A) : (B))
# endif
#define M3_INIT(field) memset(&field, 0, sizeof(field))
#define M3_COUNT_OF(x) ((sizeof(x)/sizeof(0[x])) / ((size_t)(!(sizeof(x) % sizeof(0[x])))))
#if defined(__AVR__)
#include <inttypes.h>
# define PRIu64 "llu"
# define PRIi64 "lli"
# define d_m3ShortTypesDefined
typedef double f64;
typedef float f32;
typedef uint64_t u64;
typedef int64_t i64;
typedef uint32_t u32;
typedef int32_t i32;
typedef short unsigned u16;
typedef short i16;
typedef uint8_t u8;
typedef int8_t i8;
#endif
/*
* Apply settings
*/
# if defined (M3_COMPILER_MSVC)
# define vectorcall // For MSVC, better not to specify any call convention
# elif defined(__x86_64__)
# define vectorcall
//# elif defined(__riscv) && (__riscv_xlen == 64)
//# define vectorcall
# elif defined(__MINGW32__)
# define vectorcall
# elif defined(WIN32)
# define vectorcall __vectorcall
# elif defined (ESP8266)
# include <c_types.h>
# define vectorcall //ICACHE_FLASH_ATTR
# elif defined (ESP32)
# if defined(M3_IN_IRAM) // the interpreter is in IRAM, attribute not needed
# define vectorcall
# else
# include "esp_system.h"
# define vectorcall IRAM_ATTR
# endif
# elif defined (FOMU)
# define vectorcall __attribute__((section(".ramtext")))
# endif
#ifndef vectorcall
#define vectorcall
#endif
/*
* Device-specific defaults
*/
# ifndef d_m3MaxFunctionStackHeight
# if defined(ESP8266) || defined(ESP32) || defined(ARDUINO_AMEBA) || defined(TEENSYDUINO)
# define d_m3MaxFunctionStackHeight 256
# endif
# endif
# ifndef d_m3FixedHeap
# if defined(ARDUINO_AMEBA)
# define d_m3FixedHeap (128*1024)
# elif defined(BLUE_PILL) || defined(FOMU)
# define d_m3FixedHeap (12*1024)
# elif defined(ARDUINO_ARCH_ARC32) // Arduino 101
# define d_m3FixedHeap (10*1024)
# endif
# endif
/*
* Platform-specific defaults
*/
# if defined(ARDUINO) || defined(PARTICLE) || defined(PLATFORMIO) || defined(__MBED__) || \
defined(ESP8266) || defined(ESP32) || defined(BLUE_PILL) || defined(WM_W600) || defined(FOMU)
# ifndef d_m3CascadedOpcodes
# define d_m3CascadedOpcodes 0
# endif
# ifndef d_m3VerboseErrorMessages
# define d_m3VerboseErrorMessages 0
# endif
# ifndef d_m3MaxConstantTableSize
# define d_m3MaxConstantTableSize 64
# endif
# ifndef d_m3MaxFunctionStackHeight
# define d_m3MaxFunctionStackHeight 128
# endif
# ifndef d_m3CodePageAlignSize
# define d_m3CodePageAlignSize 1024
# endif
# endif
/*
* Arch-specific defaults
*/
#if defined(__riscv) && (__riscv_xlen == 64)
# ifndef d_m3Use32BitSlots
# define d_m3Use32BitSlots 0
# endif
#endif
#endif // m3_config_platforms_h
+717
View File
@@ -0,0 +1,717 @@
//
// m3_core.c
//
// Created by Steven Massey on 4/15/19.
// Copyright © 2019 Steven Massey. All rights reserved.
//
#define M3_IMPLEMENT_ERROR_STRINGS
#include "m3_config.h"
#include "wasm3.h"
#include "m3_core.h"
#include "m3_env.h"
void m3_Abort(const char* message) {
#ifdef DEBUG
fprintf(stderr, "Error: %s\n", message);
#endif
abort();
}
M3_WEAK
M3Result m3_Yield ()
{
return m3Err_none;
}
#if d_m3LogTimestamps
#include <time.h>
#define SEC_TO_US(sec) ((sec)*1000000)
#define NS_TO_US(ns) ((ns)/1000)
static uint64_t initial_ts = -1;
uint64_t m3_GetTimestamp()
{
if (initial_ts == -1) {
initial_ts = 0;
initial_ts = m3_GetTimestamp();
}
struct timespec ts;
timespec_get(&ts, TIME_UTC);
uint64_t us = SEC_TO_US((uint64_t)ts.tv_sec) + NS_TO_US((uint64_t)ts.tv_nsec);
return us - initial_ts;
}
#endif
#if d_m3FixedHeap
static u8 fixedHeap[d_m3FixedHeap];
static u8* fixedHeapPtr = fixedHeap;
static u8* const fixedHeapEnd = fixedHeap + d_m3FixedHeap;
static u8* fixedHeapLast = NULL;
#if d_m3FixedHeapAlign > 1
# define HEAP_ALIGN_PTR(P) P = (u8*)(((size_t)(P)+(d_m3FixedHeapAlign-1)) & ~ (d_m3FixedHeapAlign-1));
#else
# define HEAP_ALIGN_PTR(P)
#endif
void * m3_Malloc_Impl (size_t i_size)
{
u8 * ptr = fixedHeapPtr;
fixedHeapPtr += i_size;
HEAP_ALIGN_PTR(fixedHeapPtr);
if (fixedHeapPtr >= fixedHeapEnd)
{
return NULL;
}
memset (ptr, 0x0, i_size);
fixedHeapLast = ptr;
return ptr;
}
void m3_Free_Impl (void * i_ptr)
{
// Handle the last chunk
if (i_ptr && i_ptr == fixedHeapLast) {
fixedHeapPtr = fixedHeapLast;
fixedHeapLast = NULL;
} else {
//printf("== free %p [failed]\n", io_ptr);
}
}
void * m3_Realloc_Impl (void * i_ptr, size_t i_newSize, size_t i_oldSize)
{
if (M3_UNLIKELY(i_newSize == i_oldSize)) return i_ptr;
void * newPtr;
// Handle the last chunk
if (i_ptr && i_ptr == fixedHeapLast) {
fixedHeapPtr = fixedHeapLast + i_newSize;
HEAP_ALIGN_PTR(fixedHeapPtr);
if (fixedHeapPtr >= fixedHeapEnd)
{
return NULL;
}
newPtr = i_ptr;
} else {
newPtr = m3_Malloc_Impl(i_newSize);
if (!newPtr) {
return NULL;
}
if (i_ptr) {
memcpy(newPtr, i_ptr, i_oldSize);
}
}
if (i_newSize > i_oldSize) {
memset ((u8 *) newPtr + i_oldSize, 0x0, i_newSize - i_oldSize);
}
return newPtr;
}
#else
void * m3_Malloc_Impl (size_t i_size)
{
return calloc (i_size, 1);
}
void m3_Free_Impl (void * io_ptr)
{
free (io_ptr);
}
void * m3_Realloc_Impl (void * i_ptr, size_t i_newSize, size_t i_oldSize)
{
if (M3_UNLIKELY(i_newSize == i_oldSize)) return i_ptr;
void * newPtr = realloc (i_ptr, i_newSize);
if (M3_LIKELY(newPtr))
{
if (i_newSize > i_oldSize) {
memset ((u8 *) newPtr + i_oldSize, 0x0, i_newSize - i_oldSize);
}
return newPtr;
}
return NULL;
}
#endif
void * m3_CopyMem (const void * i_from, size_t i_size)
{
void * ptr = m3_Malloc("CopyMem", i_size);
if (ptr) {
memcpy (ptr, i_from, i_size);
}
return ptr;
}
//--------------------------------------------------------------------------------------------
#if d_m3LogNativeStack
static size_t stack_start;
static size_t stack_end;
void m3StackCheckInit ()
{
char stack;
stack_end = stack_start = (size_t)&stack;
}
void m3StackCheck ()
{
char stack;
size_t addr = (size_t)&stack;
size_t stackEnd = stack_end;
stack_end = M3_MIN (stack_end, addr);
// if (stackEnd != stack_end)
// printf ("maxStack: %ld\n", m3StackGetMax ());
}
int m3StackGetMax ()
{
return stack_start - stack_end;
}
#endif
//--------------------------------------------------------------------------------------------
M3Result NormalizeType (u8 * o_type, i8 i_convolutedWasmType)
{
M3Result result = m3Err_none;
u8 type = -i_convolutedWasmType;
if (type == 0x40)
type = c_m3Type_none;
// Accept v128 (wasm-encoded as 0x7b → -i_convolutedWasmType == 5)
// as an opaque slot so modules with v128 in signatures or local
// declarations parse. Actual v128 opcodes still hit
// m3Err_unknownOpcode at compile time - we just stop refusing
// unused SIMD slots that auto-vectorization emits.
else if (type < c_m3Type_i32 or type > c_m3Type_v128)
result = m3Err_invalidTypeId;
* o_type = type;
return result;
}
bool IsFpType (u8 i_m3Type)
{
return (i_m3Type == c_m3Type_f32 or i_m3Type == c_m3Type_f64);
}
bool IsIntType (u8 i_m3Type)
{
return (i_m3Type == c_m3Type_i32 or i_m3Type == c_m3Type_i64);
}
bool Is64BitType (u8 i_m3Type)
{
if (i_m3Type == c_m3Type_i64 or i_m3Type == c_m3Type_f64)
return true;
else if (i_m3Type == c_m3Type_i32 or i_m3Type == c_m3Type_f32 or i_m3Type == c_m3Type_none)
return false;
else
return (sizeof (voidptr_t) == 8); // all other cases are pointers
}
u32 SizeOfType (u8 i_m3Type)
{
if (i_m3Type == c_m3Type_i32 or i_m3Type == c_m3Type_f32)
return sizeof (i32);
return sizeof (i64);
}
//-- Binary Wasm parsing utils ------------------------------------------------------------------------------------------
M3Result Read_u64 (u64 * o_value, bytes_t * io_bytes, cbytes_t i_end)
{
const u8 * ptr = * io_bytes;
ptr += sizeof (u64);
if (ptr <= i_end)
{
memcpy(o_value, * io_bytes, sizeof(u64));
M3_BSWAP_u64(*o_value);
* io_bytes = ptr;
return m3Err_none;
}
else return m3Err_wasmUnderrun;
}
M3Result Read_u32 (u32 * o_value, bytes_t * io_bytes, cbytes_t i_end)
{
const u8 * ptr = * io_bytes;
ptr += sizeof (u32);
if (ptr <= i_end)
{
memcpy(o_value, * io_bytes, sizeof(u32));
M3_BSWAP_u32(*o_value);
* io_bytes = ptr;
return m3Err_none;
}
else return m3Err_wasmUnderrun;
}
#if d_m3ImplementFloat
M3Result Read_f64 (f64 * o_value, bytes_t * io_bytes, cbytes_t i_end)
{
const u8 * ptr = * io_bytes;
ptr += sizeof (f64);
if (ptr <= i_end)
{
memcpy(o_value, * io_bytes, sizeof(f64));
M3_BSWAP_f64(*o_value);
* io_bytes = ptr;
return m3Err_none;
}
else return m3Err_wasmUnderrun;
}
M3Result Read_f32 (f32 * o_value, bytes_t * io_bytes, cbytes_t i_end)
{
const u8 * ptr = * io_bytes;
ptr += sizeof (f32);
if (ptr <= i_end)
{
memcpy(o_value, * io_bytes, sizeof(f32));
M3_BSWAP_f32(*o_value);
* io_bytes = ptr;
return m3Err_none;
}
else return m3Err_wasmUnderrun;
}
#endif
M3Result Read_u8 (u8 * o_value, bytes_t * io_bytes, cbytes_t i_end)
{
const u8 * ptr = * io_bytes;
if (ptr < i_end)
{
* o_value = * ptr;
* io_bytes = ptr + 1;
return m3Err_none;
}
else return m3Err_wasmUnderrun;
}
M3Result Read_opcode (m3opcode_t * o_value, bytes_t * io_bytes, cbytes_t i_end)
{
const u8 * ptr = * io_bytes;
if (ptr < i_end)
{
m3opcode_t opcode = * ptr++;
#if d_m3CascadedOpcodes == 0
if (M3_UNLIKELY(opcode == c_waOp_extended))
{
if (ptr < i_end)
{
opcode = (opcode << 8) | (* ptr++);
}
else return m3Err_wasmUnderrun;
}
#endif
* o_value = opcode;
* io_bytes = ptr;
return m3Err_none;
}
else return m3Err_wasmUnderrun;
}
M3Result ReadLebUnsigned (u64 * o_value, u32 i_maxNumBits, bytes_t * io_bytes, cbytes_t i_end)
{
M3Result result = m3Err_wasmUnderrun;
u64 value = 0;
u32 shift = 0;
const u8 * ptr = * io_bytes;
while (ptr < i_end)
{
u64 byte = * (ptr++);
value |= ((byte & 0x7f) << shift);
shift += 7;
if ((byte & 0x80) == 0)
{
result = m3Err_none;
#if d_m3EnableValidation
// The last byte must not carry bits past i_maxNumBits
if (shift > i_maxNumBits)
{
u32 numUsedBits = i_maxNumBits + 7 - shift;
if (byte >> numUsedBits)
result = m3Err_lebOverflow;
}
#endif
break;
}
if (shift >= i_maxNumBits)
{
result = m3Err_lebOverflow;
break;
}
}
* o_value = value;
* io_bytes = ptr;
return result;
}
M3Result ReadLebSigned (i64 * o_value, u32 i_maxNumBits, bytes_t * io_bytes, cbytes_t i_end)
{
M3Result result = m3Err_wasmUnderrun;
i64 value = 0;
u32 shift = 0;
const u8 * ptr = * io_bytes;
while (ptr < i_end)
{
u64 byte = * (ptr++);
value |= ((byte & 0x7f) << shift);
shift += 7;
if ((byte & 0x80) == 0)
{
result = m3Err_none;
#if d_m3EnableValidation
// The bits of the last byte past i_maxNumBits must all repeat the
// sign bit, otherwise the value doesn't fit
if (shift > i_maxNumBits)
{
u32 numUsedBits = i_maxNumBits + 7 - shift;
u8 signBits = (u8) ((0x7f << (numUsedBits - 1)) & 0x7f);
u8 bits = (u8) (byte & signBits);
if (bits != 0 and bits != signBits)
result = m3Err_lebOverflow;
}
#endif
if ((byte & 0x40) and (shift < 64)) // do sign extension
{
u64 extend = 0;
value |= (~extend << shift);
}
break;
}
if (shift >= i_maxNumBits)
{
result = m3Err_lebOverflow;
break;
}
}
* o_value = value;
* io_bytes = ptr;
return result;
}
M3Result ReadLEB_u32 (u32 * o_value, bytes_t * io_bytes, cbytes_t i_end)
{
u64 value;
M3Result result = ReadLebUnsigned (& value, 32, io_bytes, i_end);
* o_value = (u32) value;
return result;
}
M3Result ReadLEB_u7 (u8 * o_value, bytes_t * io_bytes, cbytes_t i_end)
{
u64 value;
M3Result result = ReadLebUnsigned (& value, 7, io_bytes, i_end);
* o_value = (u8) value;
return result;
}
M3Result ReadLEB_i7 (i8 * o_value, bytes_t * io_bytes, cbytes_t i_end)
{
i64 value;
M3Result result = ReadLebSigned (& value, 7, io_bytes, i_end);
* o_value = (i8) value;
return result;
}
M3Result ReadLEB_i32 (i32 * o_value, bytes_t * io_bytes, cbytes_t i_end)
{
i64 value;
M3Result result = ReadLebSigned (& value, 32, io_bytes, i_end);
* o_value = (i32) value;
return result;
}
M3Result ReadLEB_i64 (i64 * o_value, bytes_t * io_bytes, cbytes_t i_end)
{
i64 value;
M3Result result = ReadLebSigned (& value, 64, io_bytes, i_end);
* o_value = value;
return result;
}
#if d_m3EnableValidation
// Validate that a byte sequence is well-formed UTF-8 per the Unicode spec.
// Returns true if valid, false otherwise.
static bool IsValidUtf8 (const u8 * i_data, u32 i_length)
{
const u8 * ptr = i_data;
const u8 * end = i_data + i_length;
while (ptr < end)
{
u8 b0 = *ptr++;
if (b0 < 0x80)
{
// single-byte: 0xxxxxxx
continue;
}
else if ((b0 & 0xE0) == 0xC0)
{
// two-byte: 110xxxxx 10xxxxxx
if (b0 < 0xC2) return false; // overlong
if (ptr >= end) return false;
u8 b1 = *ptr++;
if ((b1 & 0xC0) != 0x80) return false;
}
else if ((b0 & 0xF0) == 0xE0)
{
// three-byte: 1110xxxx 10xxxxxx 10xxxxxx
if (ptr + 1 >= end) return false;
u8 b1 = *ptr++;
u8 b2 = *ptr++;
if ((b1 & 0xC0) != 0x80) return false;
if ((b2 & 0xC0) != 0x80) return false;
// reject overlong
if (b0 == 0xE0 && b1 < 0xA0) return false;
// reject surrogates U+D800..U+DFFF
if (b0 == 0xED && b1 >= 0xA0) return false;
}
else if ((b0 & 0xF8) == 0xF0)
{
// four-byte: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
if (b0 > 0xF4) return false; // above U+10FFFF
if (ptr + 2 >= end) return false;
u8 b1 = *ptr++;
u8 b2 = *ptr++;
u8 b3 = *ptr++;
if ((b1 & 0xC0) != 0x80) return false;
if ((b2 & 0xC0) != 0x80) return false;
if ((b3 & 0xC0) != 0x80) return false;
// reject overlong
if (b0 == 0xF0 && b1 < 0x90) return false;
// reject above U+10FFFF
if (b0 == 0xF4 && b1 > 0x8F) return false;
}
else
{
// invalid leading byte (0x80..0xBF or 0xF5..0xFF)
return false;
}
}
return true;
}
#endif // d_m3EnableValidation
M3Result Read_utf8 (cstr_t * o_utf8, bytes_t * io_bytes, cbytes_t i_end)
{
*o_utf8 = NULL;
u32 utf8Length;
M3Result result = ReadLEB_u32 (& utf8Length, io_bytes, i_end);
if (not result)
{
if (utf8Length <= d_m3MaxSaneUtf8Length)
{
const u8 * ptr = * io_bytes;
const u8 * end = ptr + utf8Length;
if (end <= i_end)
{
#if d_m3EnableValidation
if (not IsValidUtf8 (ptr, utf8Length))
{
* io_bytes = end;
return m3Err_wasmMalformed;
}
#endif // d_m3EnableValidation
char * utf8 = (char *)m3_Malloc ("UTF8", utf8Length + 1);
if (utf8)
{
memcpy (utf8, ptr, utf8Length);
utf8 [utf8Length] = 0;
* o_utf8 = utf8;
}
* io_bytes = end;
}
else result = m3Err_wasmUnderrun;
}
else result = m3Err_missingUTF8;
}
return result;
}
#if d_m3RecordBacktraces
u32 FindModuleOffset (IM3Runtime i_runtime, pc_t i_pc)
{
// walk the code pages
IM3CodePage curr = i_runtime->pagesOpen;
bool pageFound = false;
while (curr)
{
if (ContainsPC (curr, i_pc))
{
pageFound = true;
break;
}
curr = curr->info.next;
}
if (!pageFound)
{
curr = i_runtime->pagesFull;
while (curr)
{
if (ContainsPC (curr, i_pc))
{
pageFound = true;
break;
}
curr = curr->info.next;
}
}
if (pageFound)
{
u32 result = 0;
bool pcFound = MapPCToOffset (curr, i_pc, & result);
d_m3Assert (pcFound);
return result;
}
else return 0;
}
void PushBacktraceFrame (IM3Runtime io_runtime, pc_t i_pc)
{
// don't try to push any more frames if we've already had an alloc failure
if (M3_UNLIKELY (io_runtime->backtrace.lastFrame == M3_BACKTRACE_TRUNCATED))
return;
M3BacktraceFrame * newFrame = m3_AllocStruct(M3BacktraceFrame);
if (!newFrame)
{
io_runtime->backtrace.lastFrame = M3_BACKTRACE_TRUNCATED;
return;
}
newFrame->moduleOffset = FindModuleOffset (io_runtime, i_pc);
if (!io_runtime->backtrace.frames || !io_runtime->backtrace.lastFrame)
io_runtime->backtrace.frames = newFrame;
else
io_runtime->backtrace.lastFrame->next = newFrame;
io_runtime->backtrace.lastFrame = newFrame;
}
void FillBacktraceFunctionInfo (IM3Runtime io_runtime, IM3Function i_function)
{
// If we've had an alloc failure then the last frame doesn't refer to the
// frame we want to fill in the function info for.
if (M3_UNLIKELY (io_runtime->backtrace.lastFrame == M3_BACKTRACE_TRUNCATED))
return;
if (!io_runtime->backtrace.lastFrame)
return;
io_runtime->backtrace.lastFrame->function = i_function;
}
void ClearBacktrace (IM3Runtime io_runtime)
{
M3BacktraceFrame * currentFrame = io_runtime->backtrace.frames;
while (currentFrame)
{
M3BacktraceFrame * nextFrame = currentFrame->next;
m3_Free (currentFrame);
currentFrame = nextFrame;
}
io_runtime->backtrace.frames = NULL;
io_runtime->backtrace.lastFrame = NULL;
}
#endif // d_m3RecordBacktraces
+311
View File
@@ -0,0 +1,311 @@
//
// m3_core.h
//
// Created by Steven Massey on 4/15/19.
// Copyright © 2019 Steven Massey. All rights reserved.
//
#ifndef m3_core_h
#define m3_core_h
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <assert.h>
#include "wasm3.h"
#include "m3_config.h"
# if defined(__cplusplus)
# define d_m3BeginExternC extern "C" {
# define d_m3EndExternC }
# else
# define d_m3BeginExternC
# define d_m3EndExternC
# endif
d_m3BeginExternC
#define d_m3ImplementFloat (d_m3HasFloat || d_m3NoFloatDynamic)
#if !defined(d_m3ShortTypesDefined)
typedef uint64_t u64;
typedef int64_t i64;
typedef uint32_t u32;
typedef int32_t i32;
typedef uint16_t u16;
typedef int16_t i16;
typedef uint8_t u8;
typedef int8_t i8;
#if d_m3ImplementFloat
typedef double f64;
typedef float f32;
#endif
#endif // d_m3ShortTypesDefined
#define PRIf32 "f"
#define PRIf64 "lf"
typedef const void * m3ret_t;
typedef const void * voidptr_t;
typedef const char * cstr_t;
typedef const char * const ccstr_t;
typedef const u8 * bytes_t;
typedef const u8 * const cbytes_t;
typedef u16 m3opcode_t;
typedef i64 m3reg_t;
# if d_m3Use32BitSlots
typedef u32 m3slot_t;
# else
typedef u64 m3slot_t;
# endif
typedef m3slot_t * m3stack_t;
typedef
const void * const cvptr_t;
# if defined (DEBUG)
# define d_m3Log(CATEGORY, FMT, ...) printf (" %8s | " FMT, #CATEGORY, ##__VA_ARGS__);
# if d_m3LogParse
# define m3log_parse(CATEGORY, FMT, ...) d_m3Log(CATEGORY, FMT, ##__VA_ARGS__)
# else
# define m3log_parse(...) {}
# endif
# if d_m3LogCompile
# define m3log_compile(CATEGORY, FMT, ...) d_m3Log(CATEGORY, FMT, ##__VA_ARGS__)
# else
# define m3log_compile(...) {}
# endif
# if d_m3LogEmit
# define m3log_emit(CATEGORY, FMT, ...) d_m3Log(CATEGORY, FMT, ##__VA_ARGS__)
# else
# define m3log_emit(...) {}
# endif
# if d_m3LogCodePages
# define m3log_code(CATEGORY, FMT, ...) d_m3Log(CATEGORY, FMT, ##__VA_ARGS__)
# else
# define m3log_code(...) {}
# endif
# if d_m3LogModule
# define m3log_module(CATEGORY, FMT, ...) d_m3Log(CATEGORY, FMT, ##__VA_ARGS__)
# else
# define m3log_module(...) {}
# endif
# if d_m3LogRuntime
# define m3log_runtime(CATEGORY, FMT, ...) d_m3Log(CATEGORY, FMT, ##__VA_ARGS__)
# else
# define m3log_runtime(...) {}
# endif
# define m3log(CATEGORY, FMT, ...) m3log_##CATEGORY (CATEGORY, FMT "\n", ##__VA_ARGS__)
# else
# define d_m3Log(CATEGORY, FMT, ...) {}
# define m3log(CATEGORY, FMT, ...) {}
# endif
# if defined(ASSERTS) || (defined(DEBUG) && !defined(NASSERTS))
# define d_m3Assert(ASS) if (!(ASS)) { printf("Assertion failed at %s:%d : %s\n", __FILE__, __LINE__, #ASS); abort(); }
# else
# define d_m3Assert(ASS)
# endif
typedef void /*const*/ * code_t;
typedef code_t const * /*__restrict__*/ pc_t;
typedef struct M3MemoryHeader
{
IM3Runtime runtime;
void * maxStack;
size_t length;
}
M3MemoryHeader;
struct M3CodeMappingPage;
typedef struct M3CodePageHeader
{
struct M3CodePage * next;
u32 lineIndex;
u32 numLines;
u32 sequence; // this is just used for debugging; could be removed
u32 usageCount;
# if d_m3RecordBacktraces
struct M3CodeMappingPage * mapping;
# endif // d_m3RecordBacktraces
}
M3CodePageHeader;
#define d_m3CodePageFreeLinesThreshold 4+2 // max is: select _sss & CallIndirect + 2 for bridge
#define d_m3DefaultMemPageSize 65536
#define d_m3Reg0SlotAlias 60000
#define d_m3Fp0SlotAlias (d_m3Reg0SlotAlias + 2)
#define d_m3MaxSaneTypesCount 1000000
#define d_m3MaxSaneFunctionsCount 1000000
#define d_m3MaxSaneImportsCount 100000
#define d_m3MaxSaneExportsCount 100000
#define d_m3MaxSaneGlobalsCount 1000000
#define d_m3MaxSaneElementSegments 10000000
#define d_m3MaxSaneDataSegments 100000
#define d_m3MaxSaneTableSize 10000000
#define d_m3MaxSaneUtf8Length 10000
#define d_m3MaxSaneFunctionArgRetCount 1000 // still insane, but whatever
#define d_externalKind_function 0
#define d_externalKind_table 1
#define d_externalKind_memory 2
#define d_externalKind_global 3
static const char * const c_waTypes [] = { "nil", "i32", "i64", "f32", "f64", "unknown" };
static const char * const c_waCompactTypes [] = { "_", "i", "I", "f", "F", "?" };
# if d_m3VerboseErrorMessages
M3Result m3Error (M3Result i_result, IM3Runtime i_runtime, IM3Module i_module, IM3Function i_function,
const char * const i_file, u32 i_lineNum, const char * const i_errorMessage, ...);
# define _m3Error(RESULT, RT, MOD, FUN, FILE, LINE, FORMAT, ...) \
m3Error (RESULT, RT, MOD, FUN, FILE, LINE, FORMAT, ##__VA_ARGS__)
# else
# define _m3Error(RESULT, RT, MOD, FUN, FILE, LINE, FORMAT, ...) (RESULT)
# endif
#define ErrorRuntime(RESULT, RUNTIME, FORMAT, ...) _m3Error (RESULT, RUNTIME, NULL, NULL, __FILE__, __LINE__, FORMAT, ##__VA_ARGS__)
#define ErrorModule(RESULT, MOD, FORMAT, ...) _m3Error (RESULT, MOD->runtime, MOD, NULL, __FILE__, __LINE__, FORMAT, ##__VA_ARGS__)
#define ErrorCompile(RESULT, COMP, FORMAT, ...) _m3Error (RESULT, COMP->runtime, COMP->module, NULL, __FILE__, __LINE__, FORMAT, ##__VA_ARGS__)
#if d_m3LogNativeStack
void m3StackCheckInit ();
void m3StackCheck ();
int m3StackGetMax ();
#else
#define m3StackCheckInit()
#define m3StackCheck()
#define m3StackGetMax() 0
#endif
#if d_m3LogTimestamps
#define PRIts "%llu"
uint64_t m3_GetTimestamp ();
#else
#define PRIts "%s"
#define m3_GetTimestamp() ""
#endif
void m3_Abort (const char* message);
void * m3_Malloc_Impl (size_t i_size);
void * m3_Realloc_Impl (void * i_ptr, size_t i_newSize, size_t i_oldSize);
void m3_Free_Impl (void * i_ptr);
void * m3_CopyMem (const void * i_from, size_t i_size);
#if d_m3LogHeapOps
// Tracing format: timestamp;heap:OpCode;name;size(bytes);new items;new ptr;old items;old ptr
static inline void * m3_AllocStruct_Impl(ccstr_t name, size_t i_size) {
void * result = m3_Malloc_Impl(i_size);
fprintf(stderr, PRIts ";heap:AllocStruct;%s;%zu;;%p;;\n", m3_GetTimestamp(), name, i_size, result);
return result;
}
static inline void * m3_AllocArray_Impl(ccstr_t name, size_t i_num, size_t i_size) {
void * result = m3_Malloc_Impl(i_size * i_num);
fprintf(stderr, PRIts ";heap:AllocArr;%s;%zu;%zu;%p;;\n", m3_GetTimestamp(), name, i_size, i_num, result);
return result;
}
static inline void * m3_ReallocArray_Impl(ccstr_t name, void * i_ptr_old, size_t i_num_new, size_t i_num_old, size_t i_size) {
void * result = m3_Realloc_Impl (i_ptr_old, i_size * i_num_new, i_size * i_num_old);
fprintf(stderr, PRIts ";heap:ReallocArr;%s;%zu;%zu;%p;%zu;%p\n", m3_GetTimestamp(), name, i_size, i_num_new, result, i_num_old, i_ptr_old);
return result;
}
static inline void * m3_Malloc (ccstr_t name, size_t i_size) {
void * result = m3_Malloc_Impl (i_size);
fprintf(stderr, PRIts ";heap:AllocMem;%s;%zu;;%p;;\n", m3_GetTimestamp(), name, i_size, result);
return result;
}
static inline void * m3_Realloc (ccstr_t name, void * i_ptr, size_t i_newSize, size_t i_oldSize) {
void * result = m3_Realloc_Impl (i_ptr, i_newSize, i_oldSize);
fprintf(stderr, PRIts ";heap:ReallocMem;%s;;%zu;%p;%zu;%p\n", m3_GetTimestamp(), name, i_newSize, result, i_oldSize, i_ptr);
return result;
}
#define m3_AllocStruct(STRUCT) (STRUCT *)m3_AllocStruct_Impl (#STRUCT, sizeof (STRUCT))
#define m3_AllocArray(STRUCT, NUM) (STRUCT *)m3_AllocArray_Impl (#STRUCT, NUM, sizeof (STRUCT))
#define m3_ReallocArray(STRUCT, PTR, NEW, OLD) (STRUCT *)m3_ReallocArray_Impl (#STRUCT, (void *)(PTR), (NEW), (OLD), sizeof (STRUCT))
#define m3_Free(P) do { void* p = (void*)(P); \
if (p) { fprintf(stderr, PRIts ";heap:FreeMem;;;;%p;\n", m3_GetTimestamp(), p); } \
m3_Free_Impl (p); (P) = NULL; } while(0)
#else
#define m3_Malloc(NAME, SIZE) m3_Malloc_Impl(SIZE)
#define m3_Realloc(NAME, PTR, NEW, OLD) m3_Realloc_Impl(PTR, NEW, OLD)
#define m3_AllocStruct(STRUCT) (STRUCT *)m3_Malloc_Impl (sizeof (STRUCT))
#define m3_AllocArray(STRUCT, NUM) (STRUCT *)m3_Malloc_Impl (sizeof (STRUCT) * (NUM))
#define m3_ReallocArray(STRUCT, PTR, NEW, OLD) (STRUCT *)m3_Realloc_Impl ((void *)(PTR), sizeof (STRUCT) * (NEW), sizeof (STRUCT) * (OLD))
#define m3_Free(P) do { m3_Free_Impl ((void*)(P)); (P) = NULL; } while(0)
#endif
M3Result NormalizeType (u8 * o_type, i8 i_convolutedWasmType);
bool IsIntType (u8 i_wasmType);
bool IsFpType (u8 i_wasmType);
bool Is64BitType (u8 i_m3Type);
u32 SizeOfType (u8 i_m3Type);
M3Result Read_u64 (u64 * o_value, bytes_t * io_bytes, cbytes_t i_end);
M3Result Read_u32 (u32 * o_value, bytes_t * io_bytes, cbytes_t i_end);
#if d_m3ImplementFloat
M3Result Read_f64 (f64 * o_value, bytes_t * io_bytes, cbytes_t i_end);
M3Result Read_f32 (f32 * o_value, bytes_t * io_bytes, cbytes_t i_end);
#endif
M3Result Read_u8 (u8 * o_value, bytes_t * io_bytes, cbytes_t i_end);
M3Result Read_opcode (m3opcode_t * o_value, bytes_t * io_bytes, cbytes_t i_end);
M3Result ReadLebUnsigned (u64 * o_value, u32 i_maxNumBits, bytes_t * io_bytes, cbytes_t i_end);
M3Result ReadLebSigned (i64 * o_value, u32 i_maxNumBits, bytes_t * io_bytes, cbytes_t i_end);
M3Result ReadLEB_u32 (u32 * o_value, bytes_t * io_bytes, cbytes_t i_end);
M3Result ReadLEB_u7 (u8 * o_value, bytes_t * io_bytes, cbytes_t i_end);
M3Result ReadLEB_i7 (i8 * o_value, bytes_t * io_bytes, cbytes_t i_end);
M3Result ReadLEB_i32 (i32 * o_value, bytes_t * io_bytes, cbytes_t i_end);
M3Result ReadLEB_i64 (i64 * o_value, bytes_t * io_bytes, cbytes_t i_end);
M3Result Read_utf8 (cstr_t * o_utf8, bytes_t * io_bytes, cbytes_t i_end);
cstr_t SPrintValue (void * i_value, u8 i_type);
size_t SPrintArg (char * o_string, size_t i_stringBufferSize, voidptr_t i_sp, u8 i_type);
void ReportError (IM3Runtime io_runtime, IM3Module i_module, IM3Function i_function, ccstr_t i_errorMessage, ccstr_t i_file, u32 i_lineNum);
# if d_m3RecordBacktraces
void PushBacktraceFrame (IM3Runtime io_runtime, pc_t i_pc);
void FillBacktraceFunctionInfo (IM3Runtime io_runtime, IM3Function i_function);
void ClearBacktrace (IM3Runtime io_runtime);
# endif
d_m3EndExternC
#endif // m3_core_h
File diff suppressed because it is too large Load Diff
+221
View File
@@ -0,0 +1,221 @@
//
// m3_env.h
//
// Created by Steven Massey on 4/19/19.
// Copyright © 2019 Steven Massey. All rights reserved.
//
#ifndef m3_env_h
#define m3_env_h
#include "wasm3.h"
#include "m3_code.h"
#include "m3_compile.h"
d_m3BeginExternC
//---------------------------------------------------------------------------------------------------------------------------------
typedef struct M3MemoryInfo
{
u32 initPages;
u32 maxPages;
u32 pageSize;
}
M3MemoryInfo;
typedef struct M3Memory
{
M3MemoryHeader * mallocated;
u32 numPages;
u32 maxPages;
u32 pageSize;
}
M3Memory;
typedef M3Memory * IM3Memory;
//---------------------------------------------------------------------------------------------------------------------------------
typedef struct M3DataSegment
{
const u8 * initExpr; // wasm code
const u8 * data;
u32 initExprSize;
u32 memoryRegion;
u32 size;
}
M3DataSegment;
//---------------------------------------------------------------------------------------------------------------------------------
typedef struct M3Global
{
M3ImportInfo import;
union
{
i32 i32Value;
i64 i64Value;
#if d_m3HasFloat
f64 f64Value;
f32 f32Value;
#endif
};
cstr_t name;
bytes_t initExpr; // wasm code
u32 initExprSize;
u8 type;
bool imported;
bool isMutable;
}
M3Global;
//---------------------------------------------------------------------------------------------------------------------------------
typedef struct M3Module
{
struct M3Runtime * runtime;
struct M3Environment * environment;
bytes_t wasmStart;
bytes_t wasmEnd;
cstr_t name;
u32 numFuncTypes;
IM3FuncType * funcTypes; // array of pointers to list of FuncTypes
u32 numFuncImports;
u32 numFunctions;
u32 allFunctions; // allocated functions count
M3Function * functions;
i32 startFunction;
u32 numDataSegments;
M3DataSegment * dataSegments;
//u32 importedGlobals;
u32 numGlobals;
M3Global * globals;
u32 numElementSegments;
bytes_t elementSection;
bytes_t elementSectionEnd;
IM3Function * table0;
u32 table0Size;
const char* table0ExportName;
bool hasTable;
M3MemoryInfo memoryInfo;
M3ImportInfo memoryImport;
bool memoryImported;
bool memoryDeclared; // has a memory section entry
const char* memoryExportName;
//bool hasWasmCodeCopy;
struct M3Module * next;
}
M3Module;
M3Result Module_AddGlobal (IM3Module io_module, IM3Global * o_global, u8 i_type, bool i_mutable, bool i_isImported);
M3Result Module_PreallocFunctions (IM3Module io_module, u32 i_totalFunctions);
M3Result Module_AddFunction (IM3Module io_module, u32 i_typeIndex, IM3ImportInfo i_importInfo /* can be null */);
IM3Function Module_GetFunction (IM3Module i_module, u32 i_functionIndex);
void Module_GenerateNames (IM3Module i_module);
void FreeImportInfo (M3ImportInfo * i_info);
//---------------------------------------------------------------------------------------------------------------------------------
typedef struct M3Environment
{
// struct M3Runtime * runtimes;
IM3FuncType funcTypes; // linked list of unique M3FuncType structs that can be compared using pointer-equivalence
IM3FuncType retFuncTypes [c_m3Type_unknown]; // these 'point' to elements in the linked list above.
// the number of elements must match the basic types as per M3ValueType
M3CodePage * pagesReleased;
M3SectionHandler customSectionHandler;
}
M3Environment;
void Environment_Release (IM3Environment i_environment);
// takes ownership of io_funcType and returns a pointer to the persistent version (could be same or different)
void Environment_AddFuncType (IM3Environment i_environment, IM3FuncType * io_funcType);
//---------------------------------------------------------------------------------------------------------------------------------
typedef struct M3Runtime
{
M3Compilation compilation;
IM3Environment environment;
M3CodePage * pagesOpen; // linked list of code pages with writable space on them
M3CodePage * pagesFull; // linked list of at-capacity pages
u32 numCodePages;
u32 numActiveCodePages;
IM3Module modules; // linked list of imported modules
void * stack;
void * originStack;
u32 stackSize;
u32 numStackSlots;
IM3Function lastCalled; // last function that successfully executed
void * userdata;
M3Memory memory;
u32 memoryLimit;
#if d_m3EnableStrace >= 2
u32 callDepth;
#endif
M3ErrorInfo error;
#if d_m3VerboseErrorMessages
char error_message[256]; // the actual buffer. M3ErrorInfo can point to this
#endif
#if d_m3RecordBacktraces
M3BacktraceInfo backtrace;
#endif
u32 newCodePageSequence;
}
M3Runtime;
void InitRuntime (IM3Runtime io_runtime, u32 i_stackSizeInBytes);
void Runtime_Release (IM3Runtime io_runtime);
M3Result ResizeMemory (IM3Runtime io_runtime, u32 i_numPages);
typedef void * (* ModuleVisitor) (IM3Module i_module, void * i_info);
void * ForEachModule (IM3Runtime i_runtime, ModuleVisitor i_visitor, void * i_info);
void * v_FindFunction (IM3Module i_module, const char * const i_name);
IM3CodePage AcquireCodePage (IM3Runtime io_runtime);
IM3CodePage AcquireCodePageWithCapacity (IM3Runtime io_runtime, u32 i_lineCount);
void ReleaseCodePage (IM3Runtime io_runtime, IM3CodePage i_codePage);
d_m3EndExternC
#endif // m3_env_h
+33
View File
@@ -0,0 +1,33 @@
//
// m3_exception.h
//
// Created by Steven Massey on 7/5/19.
// Copyright © 2019 Steven Massey. All rights reserved.
//
// some macros to emulate try/catch
#ifndef m3_exception_h
#define m3_exception_h
#include "m3_config.h"
# if d_m3EnableExceptionBreakpoint
// declared in m3_info.c
void ExceptionBreakpoint (cstr_t i_exception, cstr_t i_message);
# define EXCEPTION_PRINT(ERROR) ExceptionBreakpoint (ERROR, (__FILE__ ":" M3_STR(__LINE__)))
# else
# define EXCEPTION_PRINT(...)
# endif
#define _try M3Result result = m3Err_none;
#define _(TRY) { result = TRY; if (M3_UNLIKELY(result)) { EXCEPTION_PRINT (result); goto _catch; } }
#define _throw(ERROR) { result = ERROR; EXCEPTION_PRINT (result); goto _catch; }
#define _throwif(ERROR, COND) if (M3_UNLIKELY(COND)) { _throw(ERROR); }
#define _throwifnull(PTR) _throwif (m3Err_mallocFailed, !(PTR))
#endif // m3_exception_h
+8
View File
@@ -0,0 +1,8 @@
//
// m3_exec.c
//
// Created by Steven Massey on 4/17/19.
// Copyright © 2019 Steven Massey. All rights reserved.
//
// EMPTY FOR NOW
File diff suppressed because it is too large Load Diff
+76
View File
@@ -0,0 +1,76 @@
//
// m3_exec_defs.h
//
// Created by Steven Massey on 5/1/19.
// Copyright © 2019 Steven Massey. All rights reserved.
//
#ifndef m3_exec_defs_h
#define m3_exec_defs_h
#include "m3_core.h"
d_m3BeginExternC
# define m3MemData(mem) (u8*)(((M3MemoryHeader*)(mem))+1)
# define m3MemRuntime(mem) (((M3MemoryHeader*)(mem))->runtime)
# define m3MemInfo(mem) (&(((M3MemoryHeader*)(mem))->runtime->memory))
# define d_m3BaseOpSig pc_t _pc, m3stack_t _sp, M3MemoryHeader * _mem, m3reg_t _r0
# define d_m3BaseOpArgs _sp, _mem, _r0
# define d_m3BaseOpAllArgs _pc, _sp, _mem, _r0
# define d_m3BaseOpDefaultArgs 0
# define d_m3BaseClearRegisters _r0 = 0;
# define d_m3BaseCstr ""
# define d_m3ExpOpSig(...) d_m3BaseOpSig, __VA_ARGS__
# define d_m3ExpOpArgs(...) d_m3BaseOpArgs, __VA_ARGS__
# define d_m3ExpOpAllArgs(...) d_m3BaseOpAllArgs, __VA_ARGS__
# define d_m3ExpOpDefaultArgs(...) d_m3BaseOpDefaultArgs, __VA_ARGS__
# define d_m3ExpClearRegisters(...) d_m3BaseClearRegisters; __VA_ARGS__
# if d_m3HasFloat
# define d_m3OpSig d_m3ExpOpSig (f64 _fp0)
# define d_m3OpArgs d_m3ExpOpArgs (_fp0)
# define d_m3OpAllArgs d_m3ExpOpAllArgs (_fp0)
# define d_m3OpDefaultArgs d_m3ExpOpDefaultArgs (0.)
# define d_m3ClearRegisters d_m3ExpClearRegisters (_fp0 = 0.;)
# else
# define d_m3OpSig d_m3BaseOpSig
# define d_m3OpArgs d_m3BaseOpArgs
# define d_m3OpAllArgs d_m3BaseOpAllArgs
# define d_m3OpDefaultArgs d_m3BaseOpDefaultArgs
# define d_m3ClearRegisters d_m3BaseClearRegisters
# endif
#define d_m3RetSig static inline m3ret_t vectorcall
# if (d_m3EnableOpProfiling || d_m3EnableOpTracing)
typedef m3ret_t (vectorcall * IM3Operation) (d_m3OpSig, cstr_t i_operationName);
# define d_m3Op(NAME) M3_NO_UBSAN d_m3RetSig op_##NAME (d_m3OpSig, cstr_t i_operationName)
# define nextOpImpl() ((IM3Operation)(* _pc))(_pc + 1, d_m3OpArgs, __FUNCTION__)
# define jumpOpImpl(PC) ((IM3Operation)(* PC))( PC + 1, d_m3OpArgs, __FUNCTION__)
# else
typedef m3ret_t (vectorcall * IM3Operation) (d_m3OpSig);
# define d_m3Op(NAME) M3_NO_UBSAN d_m3RetSig op_##NAME (d_m3OpSig)
# define nextOpImpl() ((IM3Operation)(* _pc))(_pc + 1, d_m3OpArgs)
# define jumpOpImpl(PC) ((IM3Operation)(* PC))( PC + 1, d_m3OpArgs)
# endif
#define nextOpDirect() M3_MUSTTAIL return nextOpImpl()
#define jumpOpDirect(PC) M3_MUSTTAIL return jumpOpImpl((pc_t)(PC))
# if (d_m3EnableOpProfiling || d_m3EnableOpTracing)
d_m3RetSig RunCode (d_m3OpSig, cstr_t i_operationName)
# else
d_m3RetSig RunCode (d_m3OpSig)
# endif
{
nextOpDirect();
}
d_m3EndExternC
#endif // m3_exec_defs_h
+233
View File
@@ -0,0 +1,233 @@
//
// m3_function.c
//
// Created by Steven Massey on 4/7/21.
// Copyright © 2021 Steven Massey. All rights reserved.
//
#include "m3_function.h"
#include "m3_env.h"
M3Result AllocFuncType (IM3FuncType * o_functionType, u32 i_numTypes)
{
*o_functionType = (IM3FuncType) m3_Malloc ("M3FuncType", sizeof (M3FuncType) + i_numTypes);
return (*o_functionType) ? m3Err_none : m3Err_mallocFailed;
}
bool AreFuncTypesEqual (const IM3FuncType i_typeA, const IM3FuncType i_typeB)
{
if (i_typeA->numRets == i_typeB->numRets && i_typeA->numArgs == i_typeB->numArgs)
{
return (memcmp (i_typeA->types, i_typeB->types, i_typeA->numRets + i_typeA->numArgs) == 0);
}
return false;
}
u16 GetFuncTypeNumParams (const IM3FuncType i_funcType)
{
return i_funcType ? i_funcType->numArgs : 0;
}
u8 GetFuncTypeParamType (const IM3FuncType i_funcType, u16 i_index)
{
u8 type = c_m3Type_unknown;
if (i_funcType)
{
if (i_index < i_funcType->numArgs)
{
type = i_funcType->types [i_funcType->numRets + i_index];
}
}
return type;
}
u16 GetFuncTypeNumResults (const IM3FuncType i_funcType)
{
return i_funcType ? i_funcType->numRets : 0;
}
u8 GetFuncTypeResultType (const IM3FuncType i_funcType, u16 i_index)
{
u8 type = c_m3Type_unknown;
if (i_funcType)
{
if (i_index < i_funcType->numRets)
{
type = i_funcType->types [i_index];
}
}
return type;
}
//---------------------------------------------------------------------------------------------------------------
void FreeImportInfo (M3ImportInfo * i_info)
{
m3_Free (i_info->moduleUtf8);
m3_Free (i_info->fieldUtf8);
}
void Function_Release (IM3Function i_function)
{
m3_Free (i_function->constants);
for (int i = 0; i < i_function->numNames; i++)
{
// name can be an alias of fieldUtf8
if (i_function->names[i] != i_function->import.fieldUtf8)
{
m3_Free (i_function->names[i]);
}
}
FreeImportInfo (& i_function->import);
if (i_function->ownsWasmCode)
m3_Free (i_function->wasm);
// Function_FreeCompiledCode (func);
# if (d_m3EnableCodePageRefCounting)
{
m3_Free (i_function->codePageRefs);
i_function->numCodePageRefs = 0;
}
# endif
}
void Function_FreeCompiledCode (IM3Function i_function)
{
# if (d_m3EnableCodePageRefCounting)
{
i_function->compiled = NULL;
while (i_function->numCodePageRefs--)
{
IM3CodePage page = i_function->codePageRefs [i_function->numCodePageRefs];
if (--(page->info.usageCount) == 0)
{
// printf ("free %p\n", page);
}
}
m3_Free (i_function->codePageRefs);
Runtime_ReleaseCodePages (i_function->module->runtime);
}
# endif
}
cstr_t m3_GetFunctionName (IM3Function i_function)
{
u16 numNames = 0;
cstr_t *names = GetFunctionNames(i_function, &numNames);
if (numNames > 0)
return names[0];
else
return "<unnamed>";
}
IM3Module m3_GetFunctionModule (IM3Function i_function)
{
return i_function ? i_function->module : NULL;
}
cstr_t * GetFunctionNames (IM3Function i_function, u16 * o_numNames)
{
if (!i_function || !o_numNames)
return NULL;
if (i_function->import.fieldUtf8)
{
*o_numNames = 1;
return &i_function->import.fieldUtf8;
}
else
{
*o_numNames = i_function->numNames;
return i_function->names;
}
}
cstr_t GetFunctionImportModuleName (IM3Function i_function)
{
return (i_function->import.moduleUtf8) ? i_function->import.moduleUtf8 : "";
}
u16 GetFunctionNumArgs (IM3Function i_function)
{
u16 numArgs = 0;
if (i_function)
{
if (i_function->funcType)
numArgs = i_function->funcType->numArgs;
}
return numArgs;
}
u8 GetFunctionArgType (IM3Function i_function, u32 i_index)
{
u8 type = c_m3Type_none;
if (i_index < GetFunctionNumArgs (i_function))
{
u32 numReturns = i_function->funcType->numRets;
type = i_function->funcType->types [numReturns + i_index];
}
return type;
}
u16 GetFunctionNumReturns (IM3Function i_function)
{
u16 numReturns = 0;
if (i_function)
{
if (i_function->funcType)
numReturns = i_function->funcType->numRets;
}
return numReturns;
}
u8 GetFunctionReturnType (const IM3Function i_function, u16 i_index)
{
return i_function ? GetFuncTypeResultType (i_function->funcType, i_index) : c_m3Type_unknown;
}
u32 GetFunctionNumArgsAndLocals (IM3Function i_function)
{
if (i_function)
return i_function->numLocals + GetFunctionNumArgs (i_function);
else
return 0;
}
+103
View File
@@ -0,0 +1,103 @@
//
// m3_function.h
//
// Created by Steven Massey on 4/7/21.
// Copyright © 2021 Steven Massey. All rights reserved.
//
#ifndef m3_function_h
#define m3_function_h
#include "m3_core.h"
d_m3BeginExternC
//---------------------------------------------------------------------------------------------------------------------------------
typedef struct M3FuncType
{
struct M3FuncType * next;
u16 numRets;
u16 numArgs;
u8 types []; // returns, then args
}
M3FuncType;
typedef M3FuncType * IM3FuncType;
M3Result AllocFuncType (IM3FuncType * o_functionType, u32 i_numTypes);
bool AreFuncTypesEqual (const IM3FuncType i_typeA, const IM3FuncType i_typeB);
u16 GetFuncTypeNumParams (const IM3FuncType i_funcType);
u8 GetFuncTypeParamType (const IM3FuncType i_funcType, u16 i_index);
u16 GetFuncTypeNumResults (const IM3FuncType i_funcType);
u8 GetFuncTypeResultType (const IM3FuncType i_funcType, u16 i_index);
//---------------------------------------------------------------------------------------------------------------------------------
typedef struct M3Function
{
struct M3Module * module;
M3ImportInfo import;
bytes_t wasm;
bytes_t wasmEnd;
cstr_t names[d_m3MaxDuplicateFunctionImpl];
cstr_t export_name; // should be a part of "names"
u16 numNames; // maximum of d_m3MaxDuplicateFunctionImpl
IM3FuncType funcType;
pc_t compiled;
# if (d_m3EnableCodePageRefCounting)
IM3CodePage * codePageRefs; // array of all pages used
u32 numCodePageRefs;
# endif
# if defined (DEBUG)
u32 hits;
u32 index;
# endif
u16 maxStackSlots;
u16 numRetSlots;
u16 numRetAndArgSlots;
u16 numLocals; // not including args
u16 numLocalBytes;
bool ownsWasmCode;
u16 numConstantBytes;
void * constants;
}
M3Function;
void Function_Release (IM3Function i_function);
void Function_FreeCompiledCode (IM3Function i_function);
cstr_t GetFunctionImportModuleName (IM3Function i_function);
cstr_t * GetFunctionNames (IM3Function i_function, u16 * o_numNames);
u16 GetFunctionNumArgs (IM3Function i_function);
u8 GetFunctionArgType (IM3Function i_function, u32 i_index);
u16 GetFunctionNumReturns (IM3Function i_function);
u8 GetFunctionReturnType (const IM3Function i_function, u16 i_index);
u32 GetFunctionNumArgsAndLocals (IM3Function i_function);
cstr_t SPrintFunctionArgList (IM3Function i_function, m3stack_t i_sp);
//---------------------------------------------------------------------------------------------------------------------------------
d_m3EndExternC
#endif /* m3_function_h */
+564
View File
@@ -0,0 +1,564 @@
//
// m3_info.c
//
// Created by Steven Massey on 4/27/19.
// Copyright © 2019 Steven Massey. All rights reserved.
//
#include "m3_env.h"
#include "m3_info.h"
#include "m3_compile.h"
#if defined(DEBUG) || (d_m3EnableStrace >= 2)
size_t SPrintArg (char * o_string, size_t i_stringBufferSize, voidptr_t i_sp, u8 i_type)
{
int len = 0;
* o_string = 0;
if (i_type == c_m3Type_i32)
len = snprintf (o_string, i_stringBufferSize, "%" PRIi32, * (i32 *) i_sp);
else if (i_type == c_m3Type_i64)
len = snprintf (o_string, i_stringBufferSize, "%" PRIi64, * (i64 *) i_sp);
#if d_m3HasFloat
else if (i_type == c_m3Type_f32)
len = snprintf (o_string, i_stringBufferSize, "%" PRIf32, * (f32 *) i_sp);
else if (i_type == c_m3Type_f64)
len = snprintf (o_string, i_stringBufferSize, "%" PRIf64, * (f64 *) i_sp);
#endif
len = M3_MAX (0, len);
return len;
}
cstr_t SPrintFunctionArgList (IM3Function i_function, m3stack_t i_sp)
{
int ret;
static char string [256];
char * s = string;
ccstr_t e = string + sizeof(string) - 1;
ret = snprintf (s, e-s, "(");
s += M3_MAX (0, ret);
u64 * argSp = (u64 *) i_sp;
IM3FuncType funcType = i_function->funcType;
if (funcType)
{
u32 numArgs = funcType->numArgs;
for (u32 i = 0; i < numArgs; ++i)
{
u8 type = d_FuncArgType(funcType, i);
ret = snprintf (s, e-s, "%s: ", c_waTypes [type]);
s += M3_MAX (0, ret);
s += SPrintArg (s, e-s, argSp + i, type);
if (i != numArgs - 1) {
ret = snprintf (s, e-s, ", ");
s += M3_MAX (0, ret);
}
}
}
else printf ("null signature");
ret = snprintf (s, e-s, ")");
s += M3_MAX (0, ret);
return string;
}
#endif
#ifdef DEBUG
// a central function you can be breakpoint:
void ExceptionBreakpoint (cstr_t i_exception, cstr_t i_message)
{
printf ("\nexception: '%s' @ %s\n", i_exception, i_message);
return;
}
typedef struct OpInfo
{
IM3OpInfo info;
m3opcode_t opcode;
}
OpInfo;
void m3_PrintM3Info ()
{
printf ("\n-- m3 configuration --------------------------------------------\n");
// printf (" sizeof M3CodePage : %zu bytes (%d slots) \n", sizeof (M3CodePage), c_m3CodePageNumSlots);
printf (" sizeof M3MemPage : %u bytes \n", d_m3DefaultMemPageSize);
printf (" sizeof M3Compilation : %zu bytes \n", sizeof (M3Compilation));
printf (" sizeof M3Function : %zu bytes \n", sizeof (M3Function));
printf ("----------------------------------------------------------------\n\n");
}
void * v_PrintEnvModuleInfo (IM3Module i_module, u32 * io_index)
{
printf (" module [%u] name: '%s'; funcs: %d \n", * io_index++, i_module->name, i_module->numFunctions);
return NULL;
}
void m3_PrintRuntimeInfo (IM3Runtime i_runtime)
{
printf ("\n-- m3 runtime -------------------------------------------------\n");
printf (" stack-size: %zu \n\n", i_runtime->numStackSlots * sizeof (m3slot_t));
u32 moduleIndex = 0;
ForEachModule (i_runtime, (ModuleVisitor) v_PrintEnvModuleInfo, & moduleIndex);
printf ("----------------------------------------------------------------\n\n");
}
cstr_t GetTypeName (u8 i_m3Type)
{
if (i_m3Type < 5)
return c_waTypes [i_m3Type];
else
return "?";
}
// TODO: these 'static char string []' aren't thread-friendly. though these functions are
// mainly for simple diagnostics during development, it'd be nice if they were fully reliable.
cstr_t SPrintFuncTypeSignature (IM3FuncType i_funcType)
{
static char string [256];
sprintf (string, "(");
for (u32 i = 0; i < i_funcType->numArgs; ++i)
{
if (i != 0)
strcat (string, ", ");
strcat (string, GetTypeName (d_FuncArgType(i_funcType, i)));
}
strcat (string, ") -> ");
for (u32 i = 0; i < i_funcType->numRets; ++i)
{
if (i != 0)
strcat (string, ", ");
strcat (string, GetTypeName (d_FuncRetType(i_funcType, i)));
}
return string;
}
cstr_t SPrintValue (void * i_value, u8 i_type)
{
static char string [100];
SPrintArg (string, 100, (m3stack_t) i_value, i_type);
return string;
}
static
OpInfo find_operation_info (IM3Operation i_operation)
{
OpInfo opInfo = { NULL, 0 };
if (!i_operation) return opInfo;
// TODO: find also extended opcodes
for (u32 i = 0; i <= 0xff; ++i)
{
IM3OpInfo oi = GetOpInfo (i);
if (oi->type != c_m3Type_unknown)
{
for (u32 o = 0; o < 4; ++o)
{
if (oi->operations [o] == i_operation)
{
opInfo.info = oi;
opInfo.opcode = i;
break;
}
}
}
else break;
}
return opInfo;
}
#undef fetch
#define fetch(TYPE) (* (TYPE *) ((*o_pc)++))
#define d_m3Decoder(FUNC) void Decode_##FUNC (char * o_string, u8 i_opcode, IM3Operation i_operation, IM3OpInfo i_opInfo, pc_t * o_pc)
d_m3Decoder (Call)
{
void * function = fetch (void *);
i32 stackOffset = fetch (i32);
sprintf (o_string, "%p; stack-offset: %d", function, stackOffset);
}
d_m3Decoder (Entry)
{
IM3Function function = fetch (IM3Function);
// only prints out the first registered name for the function
sprintf (o_string, "%s", m3_GetFunctionName(function));
}
d_m3Decoder (f64_Store)
{
if (i_operation == i_opInfo->operations [0])
{
u32 operand = fetch (u32);
u32 offset = fetch (u32);
sprintf (o_string, "offset= slot:%d + immediate:%d", operand, offset);
}
// sprintf (o_string, "%s", function->name);
}
d_m3Decoder (Branch)
{
void * target = fetch (void *);
sprintf (o_string, "%p", target);
}
d_m3Decoder (BranchTable)
{
u32 slot = fetch (u32);
o_string += sprintf (o_string, "slot: %" PRIu32 "; targets: ", slot);
// IM3Function function = fetch2 (IM3Function);
i32 targets = fetch (i32);
for (i32 i = 0; i < targets; ++i)
{
pc_t addr = fetch (pc_t);
o_string += sprintf (o_string, "%" PRIi32 "=%p, ", i, addr);
}
pc_t addr = fetch (pc_t);
sprintf (o_string, "def=%p ", addr);
}
d_m3Decoder (Const)
{
u64 value = fetch (u64); i32 offset = fetch (i32);
sprintf (o_string, " slot [%d] = %" PRIu64, offset, value);
}
#undef fetch
void DecodeOperation (char * o_string, u8 i_opcode, IM3Operation i_operation, IM3OpInfo i_opInfo, pc_t * o_pc)
{
#define d_m3Decode(OPCODE, FUNC) case OPCODE: Decode_##FUNC (o_string, i_opcode, i_operation, i_opInfo, o_pc); break;
switch (i_opcode)
{
// d_m3Decode (0xc0, Const)
d_m3Decode (0xc5, Entry)
d_m3Decode (c_waOp_call, Call)
d_m3Decode (c_waOp_branch, Branch)
d_m3Decode (c_waOp_branchTable, BranchTable)
d_m3Decode (0x39, f64_Store)
}
}
// WARNING/TODO: this isn't fully implemented. it blindly assumes each word is a Operation pointer
// and, if an operation happens to missing from the c_operations table it won't be recognized here
void dump_code_page (IM3CodePage i_codePage, pc_t i_startPC)
{
m3log (code, "code page seq: %d", i_codePage->info.sequence);
pc_t pc = i_startPC ? i_startPC : GetPageStartPC (i_codePage);
pc_t end = GetPagePC (i_codePage);
m3log (code, "---------------------------------------------------------------------------------------");
while (pc < end)
{
pc_t operationPC = pc;
IM3Operation op = (IM3Operation) (* pc++);
OpInfo i = find_operation_info (op);
if (i.info)
{
char infoString [8*1024] = { 0 };
DecodeOperation (infoString, i.opcode, op, i.info, & pc);
m3log (code, "%p | %20s %s", operationPC, i.info->name, infoString);
}
else
m3log (code, "%p | %p", operationPC, op);
}
m3log (code, "---------------------------------------------------------------------------------------");
m3log (code, "free-lines: %d", i_codePage->info.numLines - i_codePage->info.lineIndex);
}
void dump_type_stack (IM3Compilation o)
{
/* Reminders about how the stack works! :)
-- args & locals remain on the type stack for duration of the function. Denoted with a constant 'A' and 'L' in this dump.
-- the initial stack dumps originate from the CompileLocals () function, so these identifiers won't/can't be
applied until this compilation stage is finished
-- constants are not statically represented in the type stack (like args & constants) since they don't have/need
write counts
-- the number shown for static args and locals (value in wasmStack [i]) represents the write count for the variable
-- (does Wasm ever write to an arg? I dunno/don't remember.)
-- the number for the dynamic stack values represents the slot number.
-- if the slot index points to arg, local or constant it's denoted with a lowercase 'a', 'l' or 'c'
*/
// for the assert at end of dump:
i32 regAllocated [2] = { (i32) IsRegisterAllocated (o, 0), (i32) IsRegisterAllocated (o, 1) };
// display whether r0 or fp0 is allocated. these should then also be reflected somewhere in the stack too.
d_m3Log(stack, "\n");
d_m3Log(stack, " ");
printf ("%s %s ", regAllocated [0] ? "(r0)" : " ", regAllocated [1] ? "(fp0)" : " ");
printf("\n");
for (u32 p = 1; p <= 2; ++p)
{
d_m3Log(stack, " ");
for (u16 i = 0; i < o->stackIndex; ++i)
{
if (i > 0 and i == o->stackFirstDynamicIndex)
printf ("#");
if (i == o->block.blockStackIndex)
printf (">");
const char * type = c_waCompactTypes [o->typeStack [i]];
const char * location = "";
i32 slot = o->wasmStack [i];
if (IsRegisterSlotAlias (slot))
{
bool isFp = IsFpRegisterSlotAlias (slot);
location = isFp ? "/f" : "/r";
regAllocated [isFp]--;
slot = -1;
}
else
{
if (slot < o->slotFirstDynamicIndex)
{
if (slot >= o->slotFirstConstIndex)
location = "c";
else if (slot >= o->function->numRetAndArgSlots)
location = "L";
else
location = "a";
}
}
char item [100];
if (slot >= 0)
sprintf (item, "%s%s%d", type, location, slot);
else
sprintf (item, "%s%s", type, location);
if (p == 1)
{
size_t s = strlen (item);
sprintf (item, "%d", i);
while (strlen (item) < s)
strcat (item, " ");
}
printf ("|%s ", item);
}
printf ("\n");
}
// for (u32 r = 0; r < 2; ++r)
// d_m3Assert (regAllocated [r] == 0); // reg allocation & stack out of sync
u16 maxSlot = GetMaxUsedSlotPlusOne (o);
if (maxSlot > o->slotFirstDynamicIndex)
{
d_m3Log (stack, " -");
for (u16 i = o->slotFirstDynamicIndex; i < maxSlot; ++i)
printf ("----");
printf ("\n");
d_m3Log (stack, " slot |");
for (u16 i = o->slotFirstDynamicIndex; i < maxSlot; ++i)
printf ("%3d|", i);
printf ("\n");
d_m3Log (stack, " alloc |");
for (u16 i = o->slotFirstDynamicIndex; i < maxSlot; ++i)
{
printf ("%3d|", o->m3Slots [i]);
}
printf ("\n");
}
d_m3Log(stack, "\n");
}
static const char * GetOpcodeIndentionString (i32 blockDepth)
{
blockDepth += 1;
if (blockDepth < 0)
blockDepth = 0;
static const char * s_spaces = ".......................................................................................";
const char * indent = s_spaces + strlen (s_spaces);
indent -= (blockDepth * 2);
if (indent < s_spaces)
indent = s_spaces;
return indent;
}
const char * get_indention_string (IM3Compilation o)
{
return GetOpcodeIndentionString (o->block.depth+4);
}
void log_opcode (IM3Compilation o, m3opcode_t i_opcode)
{
i32 depth = o->block.depth;
if (i_opcode == c_waOp_end or i_opcode == c_waOp_else)
depth--;
m3log (compile, "%4d | 0x%02x %s %s", o->numOpcodes++, i_opcode, GetOpcodeIndentionString (depth), GetOpInfo(i_opcode)->name);
}
void log_emit (IM3Compilation o, IM3Operation i_operation)
{
OpInfo i = find_operation_info (i_operation);
d_m3Log(emit, "");
if (i.info)
{
printf ("%p: %s\n", GetPagePC (o->page), i.info->name);
}
else printf ("not found: %p\n", i_operation);
}
#endif // DEBUG
# if d_m3EnableOpProfiling
typedef struct M3ProfilerSlot
{
cstr_t opName;
u64 hitCount;
}
M3ProfilerSlot;
static M3ProfilerSlot s_opProfilerCounts [d_m3ProfilerSlotMask + 1] = {};
void ProfileHit (cstr_t i_operationName)
{
u64 ptr = (u64) i_operationName;
M3ProfilerSlot * slot = & s_opProfilerCounts [ptr & d_m3ProfilerSlotMask];
if (slot->opName)
{
if (slot->opName != i_operationName)
{
m3_Abort ("profiler slot collision; increase d_m3ProfilerSlotMask");
}
}
slot->opName = i_operationName;
slot->hitCount++;
}
void m3_PrintProfilerInfo ()
{
M3ProfilerSlot dummy;
M3ProfilerSlot * maxSlot = & dummy;
do
{
maxSlot->hitCount = 0;
for (u32 i = 0; i <= d_m3ProfilerSlotMask; ++i)
{
M3ProfilerSlot * slot = & s_opProfilerCounts [i];
if (slot->opName)
{
if (slot->hitCount > maxSlot->hitCount)
maxSlot = slot;
}
}
if (maxSlot->opName)
{
fprintf (stderr, "%13llu %s\n", maxSlot->hitCount, maxSlot->opName);
maxSlot->opName = NULL;
}
}
while (maxSlot->hitCount);
}
# else
void m3_PrintProfilerInfo () {}
# endif
+38
View File
@@ -0,0 +1,38 @@
//
// m3_info.h
//
// Created by Steven Massey on 12/6/19.
// Copyright © 2019 Steven Massey. All rights reserved.
//
#ifndef m3_info_h
#define m3_info_h
#include "m3_compile.h"
d_m3BeginExternC
void ProfileHit (cstr_t i_operationName);
#ifdef DEBUG
void dump_type_stack (IM3Compilation o);
void log_opcode (IM3Compilation o, m3opcode_t i_opcode);
const char * get_indention_string (IM3Compilation o);
void log_emit (IM3Compilation o, IM3Operation i_operation);
cstr_t SPrintFuncTypeSignature (IM3FuncType i_funcType);
#else // DEBUG
#define dump_type_stack(...) {}
#define log_opcode(...) {}
#define get_indention_string(...) ""
#define emit_stack_dump(...) {}
#define log_emit(...) {}
#endif // DEBUG
d_m3EndExternC
#endif // m3_info_h
+316
View File
@@ -0,0 +1,316 @@
//
// m3_math_utils.h
//
// Created by Volodymyr Shymanksyy on 8/10/19.
// Copyright © 2019 Volodymyr Shymanskyy. All rights reserved.
//
#ifndef m3_math_utils_h
#define m3_math_utils_h
#include "m3_core.h"
#include <limits.h>
#if defined(M3_COMPILER_MSVC)
#include <intrin.h>
#define __builtin_popcount __popcnt
static inline
int __builtin_ctz(uint32_t x) {
unsigned long ret;
_BitScanForward(&ret, x);
return (int)ret;
}
static inline
int __builtin_clz(uint32_t x) {
unsigned long ret;
_BitScanReverse(&ret, x);
return (int)(31 ^ ret);
}
#ifdef _WIN64
#define __builtin_popcountll __popcnt64
static inline
int __builtin_ctzll(uint64_t value) {
unsigned long ret;
_BitScanForward64(&ret, value);
return (int)ret;
}
static inline
int __builtin_clzll(uint64_t value) {
unsigned long ret;
_BitScanReverse64(&ret, value);
return (int)(63 ^ ret);
}
#else // _WIN64
#define __builtin_popcountll(x) (__popcnt((x) & 0xFFFFFFFF) + __popcnt((x) >> 32))
static inline
int __builtin_ctzll(uint64_t value) {
//if (value == 0) return 64; // Note: ctz(0) result is undefined anyway
uint32_t msh = (uint32_t)(value >> 32);
uint32_t lsh = (uint32_t)(value & 0xFFFFFFFF);
if (lsh != 0) return __builtin_ctz(lsh);
return 32 + __builtin_ctz(msh);
}
static inline
int __builtin_clzll(uint64_t value) {
//if (value == 0) return 64; // Note: clz(0) result is undefined anyway
uint32_t msh = (uint32_t)(value >> 32);
uint32_t lsh = (uint32_t)(value & 0xFFFFFFFF);
if (msh != 0) return __builtin_clz(msh);
return 32 + __builtin_clz(lsh);
}
#endif // _WIN64
#endif // defined(M3_COMPILER_MSVC)
// TODO: not sure why, signbit is actually defined in math.h
#if (defined(ESP8266) || defined(ESP32)) && !defined(signbit)
#define signbit(__x) \
((sizeof(__x) == sizeof(float)) ? __signbitf(__x) : __signbitd(__x))
#endif
#if defined(__AVR__)
static inline
float rintf( float arg ) {
union { float f; uint32_t i; } u;
u.f = arg;
uint32_t ux = u.i & 0x7FFFFFFF;
if (M3_UNLIKELY(ux == 0 || ux > 0x5A000000)) {
return arg;
}
return (float)lrint(arg);
}
static inline
double rint( double arg ) {
union { double f; uint32_t i[2]; } u;
u.f = arg;
uint32_t ux = u.i[1] & 0x7FFFFFFF;
if (M3_UNLIKELY((ux == 0 && u.i[0] == 0) || ux > 0x433FFFFF)) {
return arg;
}
return (double)lrint(arg);
}
static inline
uint64_t strtoull(const char* str, char** endptr, int base) {
uint64_t result = 0;
const char* p = str;
while (*p == ' ' || *p == '\t') p++;
if (base == 0) {
if (*p == '0' && (*(p+1) == 'x' || *(p+1) == 'X')) {
base = 16; p += 2;
} else if (*p == '0') {
base = 8; p++;
} else {
base = 10;
}
} else if (base == 16 && *p == '0' && (*(p+1) == 'x' || *(p+1) == 'X')) {
p += 2;
}
while (*p) {
int digit;
if (*p >= '0' && *p <= '9') digit = *p - '0';
else if (*p >= 'a' && *p <= 'f') digit = *p - 'a' + 10;
else if (*p >= 'A' && *p <= 'F') digit = *p - 'A' + 10;
else break;
if (digit >= base) break;
result = result * base + digit;
p++;
}
if (endptr) *endptr = (char*)p;
return result;
}
#endif
/*
* Rotr, Rotl
*/
static inline
u32 rotl32(u32 n, unsigned c) {
const unsigned mask = CHAR_BIT * sizeof(n) - 1;
c &= mask & 31;
return (n << c) | (n >> ((-c) & mask));
}
static inline
u32 rotr32(u32 n, unsigned c) {
const unsigned mask = CHAR_BIT * sizeof(n) - 1;
c &= mask & 31;
return (n >> c) | (n << ((-c) & mask));
}
static inline
u64 rotl64(u64 n, unsigned c) {
const unsigned mask = CHAR_BIT * sizeof(n) - 1;
c &= mask & 63;
return (n << c) | (n >> ((-c) & mask));
}
static inline
u64 rotr64(u64 n, unsigned c) {
const unsigned mask = CHAR_BIT * sizeof(n) - 1;
c &= mask & 63;
return (n >> c) | (n << ((-c) & mask));
}
/*
* Integer Div, Rem
*/
#define OP_DIV_U(RES, A, B) \
if (M3_UNLIKELY(B == 0)) newTrap (m3Err_trapDivisionByZero); \
RES = A / B;
#define OP_REM_U(RES, A, B) \
if (M3_UNLIKELY(B == 0)) newTrap (m3Err_trapDivisionByZero); \
RES = A % B;
// 2's complement detection
#if (INT_MIN != -INT_MAX)
#define OP_DIV_S(RES, A, B, TYPE_MIN) \
if (M3_UNLIKELY(B == 0)) newTrap (m3Err_trapDivisionByZero); \
if (M3_UNLIKELY(B == -1 and A == TYPE_MIN)) { \
newTrap (m3Err_trapIntegerOverflow); \
} \
RES = A / B;
#define OP_REM_S(RES, A, B, TYPE_MIN) \
if (M3_UNLIKELY(B == 0)) newTrap (m3Err_trapDivisionByZero); \
if (M3_UNLIKELY(B == -1 and A == TYPE_MIN)) RES = 0; \
else RES = A % B;
#else
#define OP_DIV_S(RES, A, B, TYPE_MIN) OP_DIV_U(RES, A, B)
#define OP_REM_S(RES, A, B, TYPE_MIN) OP_REM_U(RES, A, B)
#endif
/*
* Trunc
*/
#define OP_TRUNC(RES, A, TYPE, RMIN, RMAX) \
if (M3_UNLIKELY(isnan(A))) { \
newTrap (m3Err_trapIntegerConversion); \
} \
if (M3_UNLIKELY(A <= RMIN or A >= RMAX)) { \
newTrap (m3Err_trapIntegerOverflow); \
} \
RES = (TYPE)A;
#define OP_I32_TRUNC_F32(RES, A) OP_TRUNC(RES, A, i32, -2147483904.0f, 2147483648.0f)
#define OP_U32_TRUNC_F32(RES, A) OP_TRUNC(RES, A, u32, -1.0f, 4294967296.0f)
#define OP_I32_TRUNC_F64(RES, A) OP_TRUNC(RES, A, i32, -2147483649.0 , 2147483648.0 )
#define OP_U32_TRUNC_F64(RES, A) OP_TRUNC(RES, A, u32, -1.0 , 4294967296.0 )
#define OP_I64_TRUNC_F32(RES, A) OP_TRUNC(RES, A, i64, -9223373136366403584.0f, 9223372036854775808.0f)
#define OP_U64_TRUNC_F32(RES, A) OP_TRUNC(RES, A, u64, -1.0f, 18446744073709551616.0f)
#define OP_I64_TRUNC_F64(RES, A) OP_TRUNC(RES, A, i64, -9223372036854777856.0 , 9223372036854775808.0 )
#define OP_U64_TRUNC_F64(RES, A) OP_TRUNC(RES, A, u64, -1.0 , 18446744073709551616.0 )
#define OP_TRUNC_SAT(RES, A, TYPE, RMIN, RMAX, IMIN, IMAX) \
if (M3_UNLIKELY(isnan(A))) { \
RES = 0; \
} else if (M3_UNLIKELY(A <= RMIN)) { \
RES = IMIN; \
} else if (M3_UNLIKELY(A >= RMAX)) { \
RES = IMAX; \
} else { \
RES = (TYPE)A; \
}
#define OP_I32_TRUNC_SAT_F32(RES, A) OP_TRUNC_SAT(RES, A, i32, -2147483904.0f, 2147483648.0f, INT32_MIN, INT32_MAX)
#define OP_U32_TRUNC_SAT_F32(RES, A) OP_TRUNC_SAT(RES, A, u32, -1.0f, 4294967296.0f, 0UL, UINT32_MAX)
#define OP_I32_TRUNC_SAT_F64(RES, A) OP_TRUNC_SAT(RES, A, i32, -2147483649.0 , 2147483648.0, INT32_MIN, INT32_MAX)
#define OP_U32_TRUNC_SAT_F64(RES, A) OP_TRUNC_SAT(RES, A, u32, -1.0 , 4294967296.0, 0UL, UINT32_MAX)
#define OP_I64_TRUNC_SAT_F32(RES, A) OP_TRUNC_SAT(RES, A, i64, -9223373136366403584.0f, 9223372036854775808.0f, INT64_MIN, INT64_MAX)
#define OP_U64_TRUNC_SAT_F32(RES, A) OP_TRUNC_SAT(RES, A, u64, -1.0f, 18446744073709551616.0f, 0ULL, UINT64_MAX)
#define OP_I64_TRUNC_SAT_F64(RES, A) OP_TRUNC_SAT(RES, A, i64, -9223372036854777856.0 , 9223372036854775808.0, INT64_MIN, INT64_MAX)
#define OP_U64_TRUNC_SAT_F64(RES, A) OP_TRUNC_SAT(RES, A, u64, -1.0 , 18446744073709551616.0, 0ULL, UINT64_MAX)
/*
* Min, Max
*/
#if d_m3HasFloat
#include <math.h>
// Propagate a NaN operand the way the arithmetic ops do.
// keep its sign and payload, but force the quiet bit, since the spec requires
// min/max to produce an arithmetic NaN.
static inline
f32 quiet_nan_f32(f32 arg) {
union { f32 f; u32 i; } u;
u.f = arg;
u.i |= 0x00400000;
return u.f;
}
static inline
f64 quiet_nan_f64(f64 arg) {
union { f64 f; u64 i; } u;
u.f = arg;
u.i |= 0x0008000000000000ULL;
return u.f;
}
static inline
f32 min_f32(f32 a, f32 b) {
if (M3_UNLIKELY(isnan(a) or isnan(b))) return quiet_nan_f32(isnan(a) ? a : b);
if (M3_UNLIKELY(a == 0 and a == b)) return signbit(a) ? a : b;
return a > b ? b : a;
}
static inline
f32 max_f32(f32 a, f32 b) {
if (M3_UNLIKELY(isnan(a) or isnan(b))) return quiet_nan_f32(isnan(a) ? a : b);
if (M3_UNLIKELY(a == 0 and a == b)) return signbit(a) ? b : a;
return a > b ? a : b;
}
static inline
f64 min_f64(f64 a, f64 b) {
if (M3_UNLIKELY(isnan(a) or isnan(b))) return quiet_nan_f64(isnan(a) ? a : b);
if (M3_UNLIKELY(a == 0 and a == b)) return signbit(a) ? a : b;
return a > b ? b : a;
}
static inline
f64 max_f64(f64 a, f64 b) {
if (M3_UNLIKELY(isnan(a) or isnan(b))) return quiet_nan_f64(isnan(a) ? a : b);
if (M3_UNLIKELY(a == 0 and a == b)) return signbit(a) ? b : a;
return a > b ? a : b;
}
#endif
#endif // m3_math_utils_h
+175
View File
@@ -0,0 +1,175 @@
//
// m3_module.c
//
// Created by Steven Massey on 5/7/19.
// Copyright © 2019 Steven Massey. All rights reserved.
//
#include "m3_env.h"
#include "m3_exception.h"
void Module_FreeFunctions (IM3Module i_module)
{
for (u32 i = 0; i < i_module->numFunctions; ++i)
{
IM3Function func = & i_module->functions [i];
Function_Release (func);
}
}
void m3_FreeModule (IM3Module i_module)
{
if (i_module)
{
m3log (module, "freeing module: %s (funcs: %d; segments: %d)",
i_module->name, i_module->numFunctions, i_module->numDataSegments);
Module_FreeFunctions (i_module);
m3_Free (i_module->functions);
//m3_Free (i_module->imports);
m3_Free (i_module->funcTypes);
m3_Free (i_module->dataSegments);
m3_Free (i_module->table0);
for (u32 i = 0; i < i_module->numGlobals; ++i)
{
m3_Free (i_module->globals[i].name);
FreeImportInfo(&(i_module->globals[i].import));
}
m3_Free (i_module->globals);
m3_Free (i_module->memoryExportName);
m3_Free (i_module->table0ExportName);
FreeImportInfo(&i_module->memoryImport);
m3_Free (i_module);
}
}
M3Result Module_AddGlobal (IM3Module io_module, IM3Global * o_global, u8 i_type, bool i_mutable, bool i_isImported)
{
_try {
u32 index = io_module->numGlobals++;
io_module->globals = m3_ReallocArray (M3Global, io_module->globals, io_module->numGlobals, index);
_throwifnull (io_module->globals);
M3Global * global = & io_module->globals [index];
global->type = i_type;
global->imported = i_isImported;
global->isMutable = i_mutable;
if (o_global)
* o_global = global;
} _catch:
return result;
}
M3Result Module_PreallocFunctions (IM3Module io_module, u32 i_totalFunctions)
{
_try {
if (i_totalFunctions > io_module->allFunctions) {
io_module->functions = m3_ReallocArray (M3Function, io_module->functions, i_totalFunctions, io_module->allFunctions);
io_module->allFunctions = i_totalFunctions;
_throwifnull (io_module->functions);
}
} _catch:
return result;
}
M3Result Module_AddFunction (IM3Module io_module, u32 i_typeIndex, IM3ImportInfo i_importInfo)
{
_try {
u32 index = io_module->numFunctions++;
_ (Module_PreallocFunctions(io_module, io_module->numFunctions));
_throwif ("type sig index out of bounds", i_typeIndex >= io_module->numFuncTypes);
IM3FuncType ft = io_module->funcTypes [i_typeIndex];
IM3Function func = Module_GetFunction (io_module, index);
func->funcType = ft;
# ifdef DEBUG
func->index = index;
# endif
if (i_importInfo and func->numNames == 0)
{
func->import = * i_importInfo;
func->names[0] = i_importInfo->fieldUtf8;
func->numNames = 1;
}
m3log (module, " added function: %3d; sig: %d", index, i_typeIndex);
} _catch:
return result;
}
#ifdef DEBUG
void Module_GenerateNames (IM3Module i_module)
{
for (u32 i = 0; i < i_module->numFunctions; ++i)
{
IM3Function func = & i_module->functions [i];
if (func->numNames == 0)
{
char* buff = m3_AllocArray(char, 16);
snprintf(buff, 16, "$func%d", i);
func->names[0] = buff;
func->numNames = 1;
}
}
for (u32 i = 0; i < i_module->numGlobals; ++i)
{
IM3Global global = & i_module->globals [i];
if (global->name == NULL)
{
char* buff = m3_AllocArray(char, 16);
snprintf(buff, 16, "$global%d", i);
global->name = buff;
}
}
}
#endif
IM3Function Module_GetFunction (IM3Module i_module, u32 i_functionIndex)
{
IM3Function func = NULL;
if (i_functionIndex < i_module->numFunctions)
{
func = & i_module->functions [i_functionIndex];
//func->module = i_module;
}
return func;
}
const char* m3_GetModuleName (IM3Module i_module)
{
if (!i_module || !i_module->name)
return ".unnamed";
return i_module->name;
}
void m3_SetModuleName (IM3Module i_module, const char* name)
{
if (i_module) i_module->name = name;
}
IM3Runtime m3_GetModuleRuntime (IM3Module i_module)
{
return i_module ? i_module->runtime : NULL;
}
+846
View File
@@ -0,0 +1,846 @@
//
// m3_parse.c
//
// Created by Steven Massey on 4/19/19.
// Copyright © 2019 Steven Massey. All rights reserved.
//
#include "m3_env.h"
#include "m3_compile.h"
#include "m3_exception.h"
#include "m3_info.h"
M3Result ParseType_Table (IM3Module io_module, bytes_t i_bytes, cbytes_t i_end)
{
M3Result result = m3Err_none;
u32 numTables;
_ (ReadLEB_u32 (& numTables, & i_bytes, i_end)); m3log (parse, "** Table [%d]", numTables);
// MVP: at most one table, counting any that was already imported
_throwif (m3Err_wasmMalformed, numTables > 1);
_throwif (m3Err_wasmMalformed, numTables and io_module->hasTable);
for (u32 i = 0; i < numTables; ++i)
{
u8 elemType;
_ (Read_u8 (& elemType, & i_bytes, i_end));
// Spec: element type must be funcref (0x70)
_throwif (m3Err_wasmMalformed, elemType != 0x70);
u8 flag;
_ (ReadLEB_u7 (& flag, & i_bytes, i_end));
u32 initSize;
_ (ReadLEB_u32 (& initSize, & i_bytes, i_end));
if (flag & 1) {
u32 maxSize;
_ (ReadLEB_u32 (& maxSize, & i_bytes, i_end));
_throwif (m3Err_wasmMalformed, maxSize < initSize);
}
io_module->hasTable = true;
}
_throwif (m3Err_wasmMalformed, i_bytes != i_end); // section size mismatch
_catch: return result;
}
M3Result ParseType_Memory (M3MemoryInfo * o_memory, bytes_t * io_bytes, cbytes_t i_end)
{
M3Result result = m3Err_none;
u8 flag;
_ (ReadLEB_u7 (& flag, io_bytes, i_end)); // really a u1
_ (ReadLEB_u32 (& o_memory->initPages, io_bytes, i_end));
o_memory->maxPages = 0;
if (flag & (1u << 0))
{
_ (ReadLEB_u32 (& o_memory->maxPages, io_bytes, i_end));
// Spec: memory limits validation - max must not be less than init
_throwif (m3Err_wasmMalformed, o_memory->maxPages < o_memory->initPages);
}
o_memory->pageSize = 0;
if (flag & (1u << 3)) {
u32 logPageSize;
_ (ReadLEB_u32 (& logPageSize, io_bytes, i_end));
o_memory->pageSize = 1u << logPageSize;
}
// Spec: memory limits must be valid within range 2^16 (65536 pages)
// Only enforce for standard page size (no custom page size flag)
if (!(flag & (1u << 3)))
{
_throwif (m3Err_wasmMalformed, o_memory->initPages > 65536);
if (flag & (1u << 0))
_throwif (m3Err_wasmMalformed, o_memory->maxPages > 65536);
}
_catch: return result;
}
M3Result ParseSection_Type (IM3Module io_module, bytes_t i_bytes, cbytes_t i_end)
{
IM3FuncType ftype = NULL;
_try {
u32 numTypes;
_ (ReadLEB_u32 (& numTypes, & i_bytes, i_end)); m3log (parse, "** Type [%d]", numTypes);
_throwif("too many types", numTypes > d_m3MaxSaneTypesCount);
if (numTypes)
{
// table of IM3FuncType (that point to the actual M3FuncType struct in the Environment)
io_module->funcTypes = m3_AllocArray (IM3FuncType, numTypes);
_throwifnull (io_module->funcTypes);
io_module->numFuncTypes = numTypes;
for (u32 i = 0; i < numTypes; ++i)
{
i8 form;
_ (ReadLEB_i7 (& form, & i_bytes, i_end));
_throwif (m3Err_wasmMalformed, form != -32); // for Wasm MVP
u32 numArgs;
_ (ReadLEB_u32 (& numArgs, & i_bytes, i_end));
_throwif (m3Err_tooManyArgsRets, numArgs > d_m3MaxSaneFunctionArgRetCount);
#if defined(M3_COMPILER_MSVC)
u8 argTypes [d_m3MaxSaneFunctionArgRetCount];
#else
u8 argTypes[numArgs+1]; // make ubsan happy
#endif
for (u32 a = 0; a < numArgs; ++a)
{
i8 wasmType;
u8 argType;
_ (ReadLEB_i7 (& wasmType, & i_bytes, i_end));
_ (NormalizeType (& argType, wasmType));
argTypes[a] = argType;
}
u32 numRets;
_ (ReadLEB_u32 (& numRets, & i_bytes, i_end));
_throwif (m3Err_tooManyArgsRets, (u64)(numRets) + numArgs > d_m3MaxSaneFunctionArgRetCount);
_ (AllocFuncType (& ftype, numRets + numArgs));
ftype->numArgs = numArgs;
ftype->numRets = numRets;
for (u32 r = 0; r < numRets; ++r)
{
i8 wasmType;
u8 retType;
_ (ReadLEB_i7 (& wasmType, & i_bytes, i_end));
_ (NormalizeType (& retType, wasmType));
ftype->types[r] = retType;
}
memcpy (ftype->types + numRets, argTypes, numArgs); m3log (parse, " type %2d: %s", i, SPrintFuncTypeSignature (ftype));
Environment_AddFuncType (io_module->environment, & ftype);
io_module->funcTypes [i] = ftype;
ftype = NULL; // ownership transferred to environment
}
}
_throwif (m3Err_wasmMalformed, i_bytes != i_end); // section size mismatch
} _catch:
if (result)
{
m3_Free (ftype);
// FIX: M3FuncTypes in the table are leaked
m3_Free (io_module->funcTypes);
io_module->numFuncTypes = 0;
}
return result;
}
M3Result ParseSection_Function (IM3Module io_module, bytes_t i_bytes, cbytes_t i_end)
{
M3Result result = m3Err_none;
u32 numFunctions;
_ (ReadLEB_u32 (& numFunctions, & i_bytes, i_end)); m3log (parse, "** Function [%d]", numFunctions);
_throwif("too many functions", numFunctions > d_m3MaxSaneFunctionsCount);
_ (Module_PreallocFunctions(io_module, io_module->numFunctions + numFunctions));
for (u32 i = 0; i < numFunctions; ++i)
{
u32 funcTypeIndex;
_ (ReadLEB_u32 (& funcTypeIndex, & i_bytes, i_end));
_ (Module_AddFunction (io_module, funcTypeIndex, NULL /* import info */));
}
_throwif (m3Err_wasmMalformed, i_bytes != i_end); // section size mismatch
_catch: return result;
}
M3Result ParseSection_Import (IM3Module io_module, bytes_t i_bytes, cbytes_t i_end)
{
M3Result result = m3Err_none;
M3ImportInfo import = { NULL, NULL }, clearImport = { NULL, NULL };
u32 numImports;
_ (ReadLEB_u32 (& numImports, & i_bytes, i_end)); m3log (parse, "** Import [%d]", numImports);
_throwif("too many imports", numImports > d_m3MaxSaneImportsCount);
// Most imports are functions, so we won't waste much space anyway (if any)
_ (Module_PreallocFunctions(io_module, numImports));
for (u32 i = 0; i < numImports; ++i)
{
u8 importKind;
_ (Read_utf8 (& import.moduleUtf8, & i_bytes, i_end));
_ (Read_utf8 (& import.fieldUtf8, & i_bytes, i_end));
_ (Read_u8 (& importKind, & i_bytes, i_end)); m3log (parse, " kind: %d '%s.%s' ",
(u32) importKind, import.moduleUtf8, import.fieldUtf8);
switch (importKind)
{
case d_externalKind_function:
{
u32 typeIndex;
_ (ReadLEB_u32 (& typeIndex, & i_bytes, i_end))
_ (Module_AddFunction (io_module, typeIndex, & import))
import = clearImport;
io_module->numFuncImports++;
}
break;
case d_externalKind_table:
{
// Parse and validate table type (elem type + limits)
u8 elemType;
_ (Read_u8 (& elemType, & i_bytes, i_end));
_throwif (m3Err_wasmMalformed, elemType != 0x70); // must be funcref
u8 flag;
_ (ReadLEB_u7 (& flag, & i_bytes, i_end));
u32 initSize;
_ (ReadLEB_u32 (& initSize, & i_bytes, i_end));
if (flag & 1) {
u32 maxSize;
_ (ReadLEB_u32 (& maxSize, & i_bytes, i_end));
}
io_module->hasTable = true;
}
break;
case d_externalKind_memory:
{
_ (ParseType_Memory (& io_module->memoryInfo, & i_bytes, i_end));
io_module->memoryImported = true;
io_module->memoryImport = import;
import = clearImport;
}
break;
case d_externalKind_global:
{
i8 waType;
u8 type, isMutable;
_ (ReadLEB_i7 (& waType, & i_bytes, i_end));
_ (NormalizeType (& type, waType));
_ (ReadLEB_u7 (& isMutable, & i_bytes, i_end)); m3log (parse, " global: %s mutable=%d", c_waTypes [type], (u32) isMutable);
_throwif (m3Err_wasmMalformed, isMutable > 1);
IM3Global global;
_ (Module_AddGlobal (io_module, & global, type, isMutable, true /* isImport */));
global->import = import;
import = clearImport;
}
break;
default:
_throw (m3Err_wasmMalformed);
}
FreeImportInfo (& import);
}
_throwif (m3Err_wasmMalformed, i_bytes != i_end); // section size mismatch
_catch:
FreeImportInfo (& import);
return result;
}
M3Result ParseSection_Export (IM3Module io_module, bytes_t i_bytes, cbytes_t i_end)
{
M3Result result = m3Err_none;
const char * utf8 = NULL;
#if d_m3EnableValidation
// We store name pointers + lengths to handle embedded NUL bytes correctly
typedef struct { const u8 * ptr; u16 len; } ExportName;
ExportName * exportNames = NULL;
#endif
u32 numExports;
_ (ReadLEB_u32 (& numExports, & i_bytes, i_end)); m3log (parse, "** Export [%d]", numExports);
_throwif("too many exports", numExports > d_m3MaxSaneExportsCount);
#if d_m3EnableValidation
// Spec: all export names must be different
if (numExports > 1)
{
exportNames = (ExportName *) m3_Malloc ("exportNames", sizeof(ExportName) * numExports);
}
#endif
for (u32 i = 0; i < numExports; ++i)
{
u8 exportKind;
u32 index;
// Read name length and remember raw position for uniqueness check
#if d_m3EnableValidation
const u8 * nameStart = i_bytes;
u32 nameLen = 0;
{
bytes_t tmp = i_bytes;
M3Result rl = ReadLEB_u32 (& nameLen, & tmp, i_end);
if (rl) { m3_Free(exportNames); _throw(rl); }
nameStart = tmp; // points to the raw name bytes
}
#endif
_ (Read_utf8 (& utf8, & i_bytes, i_end));
_ (Read_u8 (& exportKind, & i_bytes, i_end));
_ (ReadLEB_u32 (& index, & i_bytes, i_end)); m3log (parse, " index: %3d; kind: %d; export: '%s'; ", index, (u32) exportKind, utf8);
#if d_m3EnableValidation
if (exportNames)
{
for (u32 j = 0; j < i; ++j)
{
if (exportNames[j].len == nameLen &&
memcmp (exportNames[j].ptr, nameStart, nameLen) == 0)
{
m3_Free (exportNames);
_throw (m3Err_wasmMalformed); // duplicate export name
}
}
exportNames[i].ptr = nameStart;
exportNames[i].len = (u16)nameLen;
}
#endif
if (exportKind == d_externalKind_function)
{
_throwif(m3Err_wasmMalformed, index >= io_module->numFunctions);
IM3Function func = &(io_module->functions [index]);
if (func->numNames < d_m3MaxDuplicateFunctionImpl)
{
func->names[func->numNames++] = utf8;
func->export_name = utf8;
utf8 = NULL; // ownership transferred to M3Function
}
}
else if (exportKind == d_externalKind_global)
{
_throwif(m3Err_wasmMalformed, index >= io_module->numGlobals);
IM3Global global = &(io_module->globals [index]);
m3_Free (global->name);
global->name = utf8;
utf8 = NULL; // ownership transferred to M3Global
}
else if (exportKind == d_externalKind_memory)
{
_throwif(m3Err_wasmMalformed, index != 0);
_throwif(m3Err_wasmMalformed, not (io_module->memoryImported or io_module->memoryDeclared));
m3_Free (io_module->memoryExportName);
io_module->memoryExportName = utf8;
utf8 = NULL; // ownership transferred to M3Module
}
else if (exportKind == d_externalKind_table)
{
_throwif(m3Err_wasmMalformed, index != 0);
_throwif(m3Err_wasmMalformed, not io_module->hasTable);
m3_Free (io_module->table0ExportName);
io_module->table0ExportName = utf8;
utf8 = NULL; // ownership transferred to M3Module
}
m3_Free (utf8);
}
_throwif (m3Err_wasmMalformed, i_bytes != i_end); // section size mismatch
_catch:
m3_Free (utf8);
#if d_m3EnableValidation
m3_Free (exportNames);
#endif
return result;
}
M3Result ParseSection_Start (IM3Module io_module, bytes_t i_bytes, cbytes_t i_end)
{
M3Result result = m3Err_none;
u32 startFuncIndex;
_ (ReadLEB_u32 (& startFuncIndex, & i_bytes, i_end)); m3log (parse, "** Start Function: %d", startFuncIndex);
if (startFuncIndex < io_module->numFunctions)
{
// Spec: start function type must be [] -> []
IM3Function func = & io_module->functions [startFuncIndex];
if (func->funcType)
{
_throwif (m3Err_wasmMalformed,
func->funcType->numArgs != 0 || func->funcType->numRets != 0);
}
io_module->startFunction = startFuncIndex;
}
else result = "start function index out of bounds";
_throwif (m3Err_wasmMalformed, i_bytes != i_end); // section size mismatch
_catch: return result;
}
M3Result Parse_InitExpr (M3Module * io_module, bytes_t * io_bytes, cbytes_t i_end)
{
M3Result result = m3Err_none;
// this doesn't generate code pages. just walks the wasm bytecode to find the end
#if defined(d_m3PreferStaticAlloc)
static M3Compilation compilation;
#else
M3Compilation compilation;
#endif
compilation = (M3Compilation){ .runtime = NULL, .module = io_module, .wasm = * io_bytes, .wasmEnd = i_end, .isInitExpr = true };
result = CompileBlockStatements (& compilation);
* io_bytes = compilation.wasm;
return result;
}
M3Result ParseSection_Element (IM3Module io_module, bytes_t i_bytes, cbytes_t i_end)
{
M3Result result = m3Err_none;
u32 numSegments;
bytes_t pos;
_ (ReadLEB_u32 (& numSegments, & i_bytes, i_end)); m3log (parse, "** Element [%d]", numSegments);
_throwif ("too many element segments", numSegments > d_m3MaxSaneElementSegments);
// Element segments need a table to populate
_throwif (m3Err_wasmMalformed, numSegments and not io_module->hasTable);
io_module->elementSection = i_bytes;
io_module->elementSectionEnd = i_end;
io_module->numElementSegments = numSegments;
// Walk the section to validate structure and detect section size mismatch.
// The actual element initialization happens later in InitElements.
pos = i_bytes;
for (u32 i = 0; i < numSegments; ++i)
{
u32 tableIndex;
_ (ReadLEB_u32 (& tableIndex, & pos, i_end));
// Walk the init expression (offset) to find its end
_ (Parse_InitExpr (io_module, & pos, i_end));
u32 numElements;
_ (ReadLEB_u32 (& numElements, & pos, i_end));
for (u32 e = 0; e < numElements; ++e)
{
u32 funcIndex;
_ (ReadLEB_u32 (& funcIndex, & pos, i_end));
}
}
_throwif (m3Err_wasmMalformed, pos != i_end); // section size mismatch
_catch: return result;
}
M3Result ParseSection_Code (M3Module * io_module, bytes_t i_bytes, cbytes_t i_end)
{
M3Result result;
u32 numFunctions;
_ (ReadLEB_u32 (& numFunctions, & i_bytes, i_end)); m3log (parse, "** Code [%d]", numFunctions);
if (numFunctions != io_module->numFunctions - io_module->numFuncImports)
{
_throw ("mismatched function count in code section");
}
for (u32 f = 0; f < numFunctions; ++f)
{
const u8 * start = i_bytes;
u32 size;
_ (ReadLEB_u32 (& size, & i_bytes, i_end));
if (size)
{
const u8 * ptr = i_bytes;
i_bytes += size;
if (i_bytes <= i_end)
{
/*
u32 numLocalBlocks;
_ (ReadLEB_u32 (& numLocalBlocks, & ptr, i_end)); m3log (parse, " code size: %-4d", size);
u32 numLocals = 0;
for (u32 l = 0; l < numLocalBlocks; ++l)
{
u32 varCount;
i8 wasmType;
u8 normalType;
_ (ReadLEB_u32 (& varCount, & ptr, i_end));
_ (ReadLEB_i7 (& wasmType, & ptr, i_end));
_ (NormalizeType (& normalType, wasmType));
numLocals += varCount; m3log (parse, " %2d locals; type: '%s'", varCount, c_waTypes [normalType]);
}
*/
IM3Function func = Module_GetFunction (io_module, f + io_module->numFuncImports);
func->module = io_module;
func->wasm = start;
func->wasmEnd = i_bytes;
//func->ownsWasmCode = io_module->hasWasmCodeCopy;
// func->numLocals = numLocals;
}
else _throw (m3Err_wasmSectionOverrun);
}
}
_catch:
if (not result and i_bytes != i_end)
result = m3Err_wasmSectionUnderrun;
return result;
}
M3Result ParseSection_Data (M3Module * io_module, bytes_t i_bytes, cbytes_t i_end)
{
M3Result result = m3Err_none;
u32 numDataSegments;
_ (ReadLEB_u32 (& numDataSegments, & i_bytes, i_end)); m3log (parse, "** Data [%d]", numDataSegments);
_throwif("too many data segments", numDataSegments > d_m3MaxSaneDataSegments);
io_module->dataSegments = m3_AllocArray (M3DataSegment, numDataSegments);
_throwifnull(io_module->dataSegments);
io_module->numDataSegments = numDataSegments;
for (u32 i = 0; i < numDataSegments; ++i)
{
M3DataSegment * segment = & io_module->dataSegments [i];
_ (ReadLEB_u32 (& segment->memoryRegion, & i_bytes, i_end));
// Spec: MVP only supports memory index 0, and it has to exist
_throwif (m3Err_wasmMalformed, segment->memoryRegion != 0);
_throwif (m3Err_wasmMalformed, not (io_module->memoryImported or io_module->memoryDeclared));
segment->initExpr = i_bytes;
_ (Parse_InitExpr (io_module, & i_bytes, i_end));
segment->initExprSize = (u32) (i_bytes - segment->initExpr);
_throwif (m3Err_wasmMissingInitExpr, segment->initExprSize <= 1);
_ (ReadLEB_u32 (& segment->size, & i_bytes, i_end));
segment->data = i_bytes; m3log (parse, " segment [%u] memory: %u; expr-size: %d; size: %d",
i, segment->memoryRegion, segment->initExprSize, segment->size);
i_bytes += segment->size;
_throwif("data segment underflow", i_bytes > i_end);
}
_throwif (m3Err_wasmMalformed, i_bytes != i_end); // section size mismatch
_catch:
return result;
}
M3Result ParseSection_Memory (M3Module * io_module, bytes_t i_bytes, cbytes_t i_end)
{
M3Result result = m3Err_none;
// TODO: MVP; assert no memory imported
u32 numMemories;
_ (ReadLEB_u32 (& numMemories, & i_bytes, i_end)); m3log (parse, "** Memory [%d]", numMemories);
_throwif (m3Err_tooManyMemorySections, numMemories > 1);
if (numMemories)
{
_ (ParseType_Memory (& io_module->memoryInfo, & i_bytes, i_end));
io_module->memoryDeclared = true;
}
_throwif (m3Err_wasmMalformed, i_bytes != i_end); // section size mismatch
_catch: return result;
}
M3Result ParseSection_Global (M3Module * io_module, bytes_t i_bytes, cbytes_t i_end)
{
M3Result result = m3Err_none;
u32 numGlobals;
_ (ReadLEB_u32 (& numGlobals, & i_bytes, i_end)); m3log (parse, "** Global [%d]", numGlobals);
_throwif("too many globals", numGlobals > d_m3MaxSaneGlobalsCount);
for (u32 i = 0; i < numGlobals; ++i)
{
i8 waType;
u8 type, isMutable;
_ (ReadLEB_i7 (& waType, & i_bytes, i_end));
_ (NormalizeType (& type, waType));
_ (ReadLEB_u7 (& isMutable, & i_bytes, i_end)); m3log (parse, " global: [%d] %s mutable: %d", i, c_waTypes [type], (u32) isMutable);
_throwif (m3Err_wasmMalformed, isMutable > 1);
IM3Global global;
_ (Module_AddGlobal (io_module, & global, type, isMutable, false /* isImport */));
global->initExpr = i_bytes;
_ (Parse_InitExpr (io_module, & i_bytes, i_end));
global->initExprSize = (u32) (i_bytes - global->initExpr);
_throwif (m3Err_wasmMissingInitExpr, global->initExprSize <= 1);
}
_throwif (m3Err_wasmMalformed, i_bytes != i_end); // section size mismatch
_catch: return result;
}
M3Result ParseSection_Name (M3Module * io_module, bytes_t i_bytes, cbytes_t i_end)
{
M3Result result = m3Err_none;
cstr_t name;
while (i_bytes < i_end)
{
u8 nameType;
u32 payloadLength;
_ (ReadLEB_u7 (& nameType, & i_bytes, i_end));
_ (ReadLEB_u32 (& payloadLength, & i_bytes, i_end));
bytes_t start = i_bytes;
if (nameType == 1)
{
u32 numNames;
_ (ReadLEB_u32 (& numNames, & i_bytes, i_end));
_throwif("too many names", numNames > d_m3MaxSaneFunctionsCount);
for (u32 i = 0; i < numNames; ++i)
{
u32 index;
_ (ReadLEB_u32 (& index, & i_bytes, i_end));
_ (Read_utf8 (& name, & i_bytes, i_end));
if (index < io_module->numFunctions)
{
IM3Function func = &(io_module->functions [index]);
if (func->numNames == 0)
{
func->names[0] = name; m3log (parse, " naming function%5d: %s", index, name);
func->numNames = 1;
name = NULL; // transfer ownership
}
// else m3log (parse, "prenamed: %s", io_module->functions [index].name);
}
m3_Free (name);
}
}
i_bytes = start + payloadLength;
}
_catch: return result;
}
M3Result ParseSection_Custom (M3Module * io_module, bytes_t i_bytes, cbytes_t i_end)
{
M3Result result;
cstr_t name;
_ (Read_utf8 (& name, & i_bytes, i_end));
m3log (parse, "** Custom: '%s'", name);
if (strcmp (name, "name") == 0) {
_ (ParseSection_Name(io_module, i_bytes, i_end));
} else if (io_module->environment->customSectionHandler) {
_ (io_module->environment->customSectionHandler(io_module, name, i_bytes, i_end));
}
m3_Free (name);
_catch: return result;
}
M3Result ParseModuleSection (M3Module * o_module, u8 i_sectionType, bytes_t i_bytes, u32 i_numBytes)
{
M3Result result = m3Err_none;
typedef M3Result (* M3Parser) (M3Module *, bytes_t, cbytes_t);
static M3Parser s_parsers [] =
{
ParseSection_Custom, // 0
ParseSection_Type, // 1
ParseSection_Import, // 2
ParseSection_Function, // 3
ParseType_Table, // 4
ParseSection_Memory, // 5
ParseSection_Global, // 6
ParseSection_Export, // 7
ParseSection_Start, // 8
ParseSection_Element, // 9
ParseSection_Code, // 10
ParseSection_Data, // 11
NULL, // 12: TODO DataCount
};
M3Parser parser = NULL;
if (i_sectionType <= 12)
parser = s_parsers [i_sectionType];
if (parser)
{
cbytes_t end = i_bytes + i_numBytes;
result = parser (o_module, i_bytes, end);
}
else
{
m3log (parse, " skipped section type: %d", (u32) i_sectionType);
}
return result;
}
M3Result m3_ParseModule (IM3Environment i_environment, IM3Module * o_module, cbytes_t i_bytes, u32 i_numBytes)
{
IM3Module module; m3log (parse, "load module: %d bytes", i_numBytes);
_try {
module = m3_AllocStruct (M3Module);
_throwifnull (module);
module->name = ".unnamed"; m3log (parse, "load module: %d bytes", i_numBytes);
module->startFunction = -1;
//module->hasWasmCodeCopy = false;
module->environment = i_environment;
const u8 * pos = i_bytes;
const u8 * end = pos + i_numBytes;
module->wasmStart = pos;
module->wasmEnd = end;
u32 magic, version;
_ (Read_u32 (& magic, & pos, end));
_ (Read_u32 (& version, & pos, end));
_throwif (m3Err_wasmMalformed, magic != 0x6d736100);
_throwif (m3Err_incompatibleWasmVersion, version != 1);
static const u8 sectionsOrder[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 10, 11, 0 }; // 0 is a placeholder
u8 expectedSection = 0;
while (pos < end)
{
u8 section;
_ (ReadLEB_u7 (& section, & pos, end));
if (section != 0) {
// Ensure sections appear only once and in order
while (sectionsOrder[expectedSection++] != section) {
_throwif(m3Err_misorderedWasmSection, expectedSection >= 12);
}
}
u32 sectionLength;
_ (ReadLEB_u32 (& sectionLength, & pos, end));
_throwif(m3Err_wasmMalformed, pos + sectionLength > end);
_ (ParseModuleSection (module, section, pos, sectionLength));
pos += sectionLength;
}
// Spec: if a function section exists, a code section must also exist with
// matching count (and vice versa). ParseSection_Code checks the other
// direction; this covers the case where the code section is missing entirely.
if (module->numFunctions > module->numFuncImports)
{
IM3Function firstNonImport = & module->functions [module->numFuncImports];
_throwif (m3Err_wasmMalformed, firstNonImport->wasm == NULL);
}
} _catch:
if (result)
{
m3_FreeModule (module);
module = NULL;
}
* o_module = module;
return result;
}
+881
View File
@@ -0,0 +1,881 @@
//
// m3_validate.c
//
// Pre-pass WebAssembly bytecode validator.
// Implements the spec's type-checking algorithm with operand/control stacks.
//
#include "m3_validate.h"
#include "m3_exception.h"
#include "m3_info.h"
#if d_m3EnableValidation
// Sentinel type for polymorphic (unknown) operands
#define c_valUnknown 0xFF
// ---------- Control frame ----------
typedef struct {
m3opcode_t opcode;
u16 height; // operand stack height at block entry
u16 param_count;
u16 result_count;
IM3FuncType type; // block type (for params/results)
bool is_unreachable;
} ValCtrlFrame;
// ---------- Validator context ----------
typedef struct {
bytes_t wasm;
bytes_t wasmEnd;
IM3Module module;
IM3Function function;
u8 opd [d_m3ValStack];
u16 opdTop;
ValCtrlFrame ctrl [d_m3ValCtrlDepth];
u16 ctrlTop;
u8 localTypes [d_m3ValStack];
u16 numLocals;
} ValCtx;
// A memory op is only valid if the module defines or imports one
static bool v_has_memory (ValCtx * v)
{
return v->module and (v->module->memoryImported or v->module->memoryDeclared);
}
// Spec: the alignment immediate of a memory access must not be larger than the
// natural alignment of the operation. Natural alignment: 8-bit=0, 16-bit=1,
// 32-bit=2, 64-bit=3.
static u32 v_max_align (m3opcode_t opcode)
{
switch (opcode) {
case 0x2c: case 0x2d: // i32.load8_s, i32.load8_u
case 0x30: case 0x31: // i64.load8_s, i64.load8_u
case 0x3a: // i32.store8
case 0x3c: // i64.store8
return 0;
case 0x2e: case 0x2f: // i32.load16_s, i32.load16_u
case 0x32: case 0x33: // i64.load16_s, i64.load16_u
case 0x3b: // i32.store16
case 0x3d: // i64.store16
return 1;
case 0x29: // i64.load
case 0x2b: // f64.load
case 0x37: // i64.store
case 0x39: // f64.store
return 3;
default: // 32-bit accesses, and a safe fallback
return 2;
}
}
// ---------- Operand stack ----------
static M3Result v_push (ValCtx * v, u8 type)
{
if (v->opdTop >= d_m3ValStack)
return m3Err_functionStackOverflow;
v->opd[v->opdTop++] = type;
return m3Err_none;
}
static M3Result v_pop (ValCtx * v, u8 * o_type)
{
ValCtrlFrame * f = &v->ctrl[v->ctrlTop - 1];
if (v->opdTop == f->height) {
if (f->is_unreachable) { *o_type = c_valUnknown; return m3Err_none; }
return m3Err_functionStackUnderrun;
}
*o_type = v->opd[--v->opdTop];
return m3Err_none;
}
static M3Result v_pop_expect (ValCtx * v, u8 expect, u8 * o_actual)
{
u8 actual;
M3Result r = v_pop(v, &actual);
if (r) return r;
if (expect != c_valUnknown && actual != c_valUnknown && actual != expect)
return m3Err_typeMismatch;
*o_actual = (actual == c_valUnknown) ? expect : actual;
return m3Err_none;
}
// ---------- Control stack ----------
static M3Result v_push_ctrl (ValCtx * v, m3opcode_t op, IM3FuncType type)
{
if (v->ctrlTop >= d_m3ValCtrlDepth)
return m3Err_functionStackOverflow;
ValCtrlFrame * f = &v->ctrl[v->ctrlTop++];
f->opcode = op;
f->type = type;
f->param_count = type ? type->numArgs : 0;
f->result_count = type ? type->numRets : 0;
f->height = v->opdTop;
f->is_unreachable = false;
return m3Err_none;
}
static M3Result v_pop_ctrl (ValCtx * v, ValCtrlFrame * o_frame)
{
if (v->ctrlTop == 0)
return m3Err_wasmMalformed;
ValCtrlFrame * f = &v->ctrl[v->ctrlTop - 1];
// pop result types
if (f->type) {
for (u16 i = f->result_count; i > 0; i--) {
u8 a;
M3Result r = v_pop_expect(v, f->type->types[i - 1], &a);
if (r) return r;
}
}
if (v->opdTop != f->height)
return m3Err_typeCountMismatch;
if (o_frame) *o_frame = *f;
v->ctrlTop--;
return m3Err_none;
}
static void v_unreachable (ValCtx * v)
{
ValCtrlFrame * f = &v->ctrl[v->ctrlTop - 1];
v->opdTop = f->height;
f->is_unreachable = true;
}
// Label types: loop -> params, block/if/else/func -> results
static u16 v_label_n (ValCtrlFrame * f)
{
return (f->opcode == 0x03) ? f->param_count : f->result_count;
}
static u8 v_label_t (ValCtrlFrame * f, u16 i)
{
if (!f->type) return c_m3Type_none;
if (f->opcode == 0x03)
return f->type->types[f->type->numRets + i]; // params
return f->type->types[i]; // results
}
// Pop label types for branch target
static M3Result v_pop_labels (ValCtx * v, ValCtrlFrame * tgt)
{
u16 n = v_label_n(tgt);
for (u16 i = n; i > 0; i--) {
u8 a;
M3Result r = v_pop_expect(v, v_label_t(tgt, i - 1), &a);
if (r) return r;
}
return m3Err_none;
}
// Push label types back
static M3Result v_push_labels (ValCtx * v, ValCtrlFrame * tgt)
{
u16 n = v_label_n(tgt);
for (u16 i = 0; i < n; i++) {
M3Result r = v_push(v, v_label_t(tgt, i));
if (r) return r;
}
return m3Err_none;
}
// ---------- Block type resolution ----------
static M3Result v_read_blocktype (ValCtx * v, IM3FuncType * o_type)
{
if (v->wasm >= v->wasmEnd)
return m3Err_wasmUnderrun;
i64 type;
M3Result r = ReadLebSigned(&type, 33, &v->wasm, v->wasmEnd);
if (r) return r;
if (type < 0) {
u8 valtype;
r = NormalizeType(&valtype, (i8)type);
if (r) return r;
IM3Environment env = v->module->environment;
*o_type = env->retFuncTypes[valtype];
} else {
if ((u32)type >= v->module->numFuncTypes) return m3Err_wasmMalformed;
*o_type = v->module->funcTypes[(u32)type];
}
return m3Err_none;
}
// ---------- Convenience ----------
static M3Result v_unop (ValCtx * v, u8 in, u8 out)
{
u8 a; M3Result r = v_pop_expect(v, in, &a);
if (r) return r;
return v_push(v, out);
}
static M3Result v_binop (ValCtx * v, u8 t)
{
u8 a; M3Result r;
r = v_pop_expect(v, t, &a); if (r) return r;
r = v_pop_expect(v, t, &a); if (r) return r;
return v_push(v, t);
}
static M3Result v_relop (ValCtx * v, u8 t)
{
u8 a; M3Result r;
r = v_pop_expect(v, t, &a); if (r) return r;
r = v_pop_expect(v, t, &a); if (r) return r;
return v_push(v, c_m3Type_i32);
}
static M3Result v_testop (ValCtx * v, u8 t)
{
return v_unop(v, t, c_m3Type_i32);
}
static M3Result v_cvtop (ValCtx * v, u8 in, u8 out)
{
return v_unop(v, in, out);
}
// ---------- Main validation loop ----------
static M3Result v_validate_body (ValCtx * v)
{
M3Result r = m3Err_none;
u8 a;
while (v->wasm < v->wasmEnd)
{
m3opcode_t opcode;
r = Read_opcode(&opcode, &v->wasm, v->wasmEnd);
if (r) return r;
switch (opcode)
{
// ---- Control ----
case 0x00: // unreachable
v_unreachable(v);
break;
case 0x01: // nop
break;
case 0x02: // block
case 0x03: // loop
case 0x04: // if
{
IM3FuncType bt;
r = v_read_blocktype(v, &bt);
if (r) return r;
if (opcode == 0x04) {
r = v_pop_expect(v, c_m3Type_i32, &a);
if (r) return r;
}
// Pop block params from caller stack
if (bt) {
for (u16 i = bt->numArgs; i > 0; i--) {
r = v_pop_expect(v, bt->types[bt->numRets + i - 1], &a);
if (r) return r;
}
}
r = v_push_ctrl(v, opcode, bt);
if (r) return r;
// Push params inside block
if (bt) {
for (u16 i = 0; i < bt->numArgs; i++) {
r = v_push(v, bt->types[bt->numRets + i]);
if (r) return r;
}
}
break;
}
case 0x05: // else
{
ValCtrlFrame frame;
r = v_pop_ctrl(v, &frame);
if (r) return r;
if (frame.opcode != 0x04)
return m3Err_wasmMalformed;
r = v_push_ctrl(v, 0x05, frame.type);
if (r) return r;
if (frame.type) {
for (u16 i = 0; i < frame.type->numArgs; i++) {
r = v_push(v, frame.type->types[frame.type->numRets + i]);
if (r) return r;
}
}
break;
}
case 0x0b: // end
{
ValCtrlFrame frame;
r = v_pop_ctrl(v, &frame);
if (r) return r;
// Push results
if (frame.type) {
for (u16 i = 0; i < frame.result_count; i++) {
r = v_push(v, frame.type->types[i]);
if (r) return r;
}
}
// If this was the outermost frame, we're done
if (v->ctrlTop == 0)
return m3Err_none;
break;
}
case 0x0c: // br
{
u32 depth;
r = ReadLEB_u32(&depth, &v->wasm, v->wasmEnd);
if (r) return r;
if (depth >= v->ctrlTop) return m3Err_wasmMalformed;
ValCtrlFrame * tgt = &v->ctrl[v->ctrlTop - 1 - depth];
r = v_pop_labels(v, tgt);
if (r) return r;
v_unreachable(v);
break;
}
case 0x0d: // br_if
{
u32 depth;
r = ReadLEB_u32(&depth, &v->wasm, v->wasmEnd);
if (r) return r;
if (depth >= v->ctrlTop) return m3Err_wasmMalformed;
r = v_pop_expect(v, c_m3Type_i32, &a);
if (r) return r;
ValCtrlFrame * tgt = &v->ctrl[v->ctrlTop - 1 - depth];
r = v_pop_labels(v, tgt);
if (r) return r;
r = v_push_labels(v, tgt);
if (r) return r;
break;
}
case 0x0e: // br_table
{
u32 count;
r = ReadLEB_u32(&count, &v->wasm, v->wasmEnd);
if (r) return r;
u32 defDepth = 0;
u16 arity = 0;
// First pass: read all depths and validate arity + types match default
bytes_t savedPos = v->wasm;
// Read all targets to find the default (last one)
for (u32 i = 0; i <= count; i++) {
u32 d;
r = ReadLEB_u32(&d, &v->wasm, v->wasmEnd);
if (r) return r;
if (d >= v->ctrlTop) return m3Err_wasmMalformed;
if (i == count) defDepth = d;
}
// Now validate all labels match the default's types
ValCtrlFrame * defTgt = &v->ctrl[v->ctrlTop - 1 - defDepth];
arity = v_label_n(defTgt);
v->wasm = savedPos;
for (u32 i = 0; i <= count; i++) {
u32 d;
r = ReadLEB_u32(&d, &v->wasm, v->wasmEnd);
if (r) return r;
ValCtrlFrame * t = &v->ctrl[v->ctrlTop - 1 - d];
u16 n = v_label_n(t);
if (n != arity) return m3Err_typeCountMismatch;
// Spec: label types must be identical, not just same arity
for (u16 j = 0; j < n; j++) {
if (v_label_t(t, j) != v_label_t(defTgt, j))
return m3Err_typeMismatch;
}
}
r = v_pop_expect(v, c_m3Type_i32, &a);
if (r) return r;
ValCtrlFrame * dt = &v->ctrl[v->ctrlTop - 1 - defDepth];
r = v_pop_labels(v, dt);
if (r) return r;
v_unreachable(v);
break;
}
case 0x0f: // return
{
IM3FuncType ft = v->function->funcType;
if (ft) {
for (u16 i = ft->numRets; i > 0; i--) {
r = v_pop_expect(v, ft->types[i - 1], &a);
if (r) return r;
}
}
v_unreachable(v);
break;
}
// ---- Call ----
case 0x10: // call
{
u32 idx;
r = ReadLEB_u32(&idx, &v->wasm, v->wasmEnd);
if (r) return r;
if (idx >= v->module->numFunctions) return m3Err_wasmMalformed;
IM3FuncType ft = v->module->functions[idx].funcType;
if (ft) {
for (u16 i = ft->numArgs; i > 0; i--) {
r = v_pop_expect(v, ft->types[ft->numRets + i - 1], &a);
if (r) return r;
}
for (u16 i = 0; i < ft->numRets; i++) {
r = v_push(v, ft->types[i]);
if (r) return r;
}
}
break;
}
case 0x11: // call_indirect
{
u32 typeIdx;
r = ReadLEB_u32(&typeIdx, &v->wasm, v->wasmEnd);
if (r) return r;
u32 tableIdx;
r = ReadLEB_u32(&tableIdx, &v->wasm, v->wasmEnd);
if (r) return r;
if (typeIdx >= v->module->numFuncTypes) return m3Err_wasmMalformed;
// Spec: table must exist (MVP requires table index 0 and table must be defined)
if (tableIdx != 0) return m3Err_wasmMalformed;
if (!v->module->hasTable) return m3Err_wasmMalformed;
IM3FuncType ft = v->module->funcTypes[typeIdx];
r = v_pop_expect(v, c_m3Type_i32, &a); // table index operand
if (r) return r;
if (ft) {
for (u16 i = ft->numArgs; i > 0; i--) {
r = v_pop_expect(v, ft->types[ft->numRets + i - 1], &a);
if (r) return r;
}
for (u16 i = 0; i < ft->numRets; i++) {
r = v_push(v, ft->types[i]);
if (r) return r;
}
}
break;
}
// ---- Parametric ----
case 0x1a: // drop
r = v_pop(v, &a);
if (r) return r;
break;
case 0x1b: // select
{
r = v_pop_expect(v, c_m3Type_i32, &a);
if (r) return r;
u8 t2;
r = v_pop(v, &t2);
if (r) return r;
u8 t1;
r = v_pop_expect(v, t2, &t1);
if (r) return r;
r = v_push(v, (t2 == c_valUnknown) ? t1 : t2);
if (r) return r;
break;
}
// ---- Variable ----
case 0x20: // local.get
{
u32 idx;
r = ReadLEB_u32(&idx, &v->wasm, v->wasmEnd);
if (r) return r;
if (idx >= v->numLocals) return m3Err_wasmMalformed;
r = v_push(v, v->localTypes[idx]);
if (r) return r;
break;
}
case 0x21: // local.set
{
u32 idx;
r = ReadLEB_u32(&idx, &v->wasm, v->wasmEnd);
if (r) return r;
if (idx >= v->numLocals) return m3Err_wasmMalformed;
r = v_pop_expect(v, v->localTypes[idx], &a);
if (r) return r;
break;
}
case 0x22: // local.tee
{
u32 idx;
r = ReadLEB_u32(&idx, &v->wasm, v->wasmEnd);
if (r) return r;
if (idx >= v->numLocals) return m3Err_wasmMalformed;
r = v_pop_expect(v, v->localTypes[idx], &a);
if (r) return r;
r = v_push(v, v->localTypes[idx]);
if (r) return r;
break;
}
case 0x23: // global.get
{
u32 idx;
r = ReadLEB_u32(&idx, &v->wasm, v->wasmEnd);
if (r) return r;
if (idx >= v->module->numGlobals) return m3Err_wasmMalformed;
r = v_push(v, v->module->globals[idx].type);
if (r) return r;
break;
}
case 0x24: // global.set
{
u32 idx;
r = ReadLEB_u32(&idx, &v->wasm, v->wasmEnd);
if (r) return r;
if (idx >= v->module->numGlobals) return m3Err_wasmMalformed;
r = v_pop_expect(v, v->module->globals[idx].type, &a);
if (r) return r;
break;
}
// ---- Memory load ----
case 0x28: case 0x29: case 0x2a: case 0x2b: // i32/i64/f32/f64.load
case 0x2c: case 0x2d: case 0x2e: case 0x2f: // i32.load8/16 s/u
case 0x30: case 0x31: case 0x32: case 0x33: // i64.load8/16 s/u
case 0x34: case 0x35: // i64.load32 s/u
{
u32 align, offset;
r = ReadLEB_u32(&align, &v->wasm, v->wasmEnd); if (r) return r;
r = ReadLEB_u32(&offset, &v->wasm, v->wasmEnd); if (r) return r;
if (align > v_max_align(opcode)) return m3Err_wasmMalformed;
if (not v_has_memory(v)) return m3Err_wasmMalformed;
r = v_pop_expect(v, c_m3Type_i32, &a); if (r) return r;
u8 result;
if (opcode == 0x28) result = c_m3Type_i32;
else if (opcode == 0x29) result = c_m3Type_i64;
else if (opcode == 0x2a) result = c_m3Type_f32;
else if (opcode == 0x2b) result = c_m3Type_f64;
else if (opcode <= 0x2f) result = c_m3Type_i32;
else result = c_m3Type_i64;
r = v_push(v, result);
if (r) return r;
break;
}
// ---- Memory store ----
case 0x36: case 0x37: case 0x38: case 0x39: // i32/i64/f32/f64.store
case 0x3a: case 0x3b: // i32.store8/16
case 0x3c: case 0x3d: case 0x3e: // i64.store8/16/32
{
u32 align, offset;
r = ReadLEB_u32(&align, &v->wasm, v->wasmEnd); if (r) return r;
r = ReadLEB_u32(&offset, &v->wasm, v->wasmEnd); if (r) return r;
if (align > v_max_align(opcode)) return m3Err_wasmMalformed;
if (not v_has_memory(v)) return m3Err_wasmMalformed;
u8 valtype;
if (opcode == 0x36) valtype = c_m3Type_i32;
else if (opcode == 0x37) valtype = c_m3Type_i64;
else if (opcode == 0x38) valtype = c_m3Type_f32;
else if (opcode == 0x39) valtype = c_m3Type_f64;
else if (opcode <= 0x3b) valtype = c_m3Type_i32;
else valtype = c_m3Type_i64;
r = v_pop_expect(v, valtype, &a); if (r) return r;
r = v_pop_expect(v, c_m3Type_i32, &a); if (r) return r;
break;
}
// ---- Memory size/grow ----
case 0x3f: // memory.size
{
u32 memidx;
r = ReadLEB_u32(&memidx, &v->wasm, v->wasmEnd); if (r) return r;
if (memidx != 0 or not v_has_memory(v)) return m3Err_wasmMalformed;
r = v_push(v, c_m3Type_i32); if (r) return r;
break;
}
case 0x40: // memory.grow
{
u32 memidx;
r = ReadLEB_u32(&memidx, &v->wasm, v->wasmEnd); if (r) return r;
if (memidx != 0 or not v_has_memory(v)) return m3Err_wasmMalformed;
r = v_pop_expect(v, c_m3Type_i32, &a); if (r) return r;
r = v_push(v, c_m3Type_i32); if (r) return r;
break;
}
// ---- Constants ----
case 0x41: { // i32.const
i32 val;
r = ReadLEB_i32(&val, &v->wasm, v->wasmEnd); if (r) return r;
r = v_push(v, c_m3Type_i32); if (r) return r;
break;
}
case 0x42: { // i64.const
i64 val;
r = ReadLEB_i64(&val, &v->wasm, v->wasmEnd); if (r) return r;
r = v_push(v, c_m3Type_i64); if (r) return r;
break;
}
case 0x43: { // f32.const
if (v->wasm + 4 > v->wasmEnd) return m3Err_wasmUnderrun;
v->wasm += 4;
r = v_push(v, c_m3Type_f32); if (r) return r;
break;
}
case 0x44: { // f64.const
if (v->wasm + 8 > v->wasmEnd) return m3Err_wasmUnderrun;
v->wasm += 8;
r = v_push(v, c_m3Type_f64); if (r) return r;
break;
}
// ---- i32 comparison ----
case 0x45: r = v_testop(v, c_m3Type_i32); break; // i32.eqz
case 0x46: case 0x47: case 0x48: case 0x49: case 0x4a:
case 0x4b: case 0x4c: case 0x4d: case 0x4e: case 0x4f:
r = v_relop(v, c_m3Type_i32); break;
// ---- i64 comparison ----
case 0x50: r = v_testop(v, c_m3Type_i64); break; // i64.eqz
case 0x51: case 0x52: case 0x53: case 0x54: case 0x55:
case 0x56: case 0x57: case 0x58: case 0x59: case 0x5a:
r = v_relop(v, c_m3Type_i64); break;
// ---- f32 comparison ----
case 0x5b: case 0x5c: case 0x5d: case 0x5e: case 0x5f: case 0x60:
r = v_relop(v, c_m3Type_f32); break;
// ---- f64 comparison ----
case 0x61: case 0x62: case 0x63: case 0x64: case 0x65: case 0x66:
r = v_relop(v, c_m3Type_f64); break;
// ---- i32 unary ----
case 0x67: case 0x68: case 0x69: // clz, ctz, popcnt
r = v_unop(v, c_m3Type_i32, c_m3Type_i32); break;
// ---- i32 binary ----
case 0x6a: case 0x6b: case 0x6c: case 0x6d: case 0x6e: case 0x6f:
case 0x70: case 0x71: case 0x72: case 0x73: case 0x74: case 0x75:
case 0x76: case 0x77: case 0x78: // add..rotr
r = v_binop(v, c_m3Type_i32); break;
// ---- i64 unary ----
case 0x79: case 0x7a: case 0x7b: // clz, ctz, popcnt
r = v_unop(v, c_m3Type_i64, c_m3Type_i64); break;
// ---- i64 binary ----
case 0x7c: case 0x7d: case 0x7e: case 0x7f: case 0x80: case 0x81:
case 0x82: case 0x83: case 0x84: case 0x85: case 0x86: case 0x87:
case 0x88: case 0x89: case 0x8a: // add..rotr
r = v_binop(v, c_m3Type_i64); break;
// ---- f32 unary ----
case 0x8b: case 0x8c: case 0x8d: case 0x8e: case 0x8f:
case 0x90: case 0x91: // abs, neg, ceil, floor, trunc, nearest, sqrt
r = v_unop(v, c_m3Type_f32, c_m3Type_f32); break;
// ---- f32 binary ----
case 0x92: case 0x93: case 0x94: case 0x95: case 0x96:
case 0x97: case 0x98: // add, sub, mul, div, min, max, copysign
r = v_binop(v, c_m3Type_f32); break;
// ---- f64 unary ----
case 0x99: case 0x9a: case 0x9b: case 0x9c: case 0x9d:
case 0x9e: case 0x9f: // abs, neg, ceil, floor, trunc, nearest, sqrt
r = v_unop(v, c_m3Type_f64, c_m3Type_f64); break;
// ---- f64 binary ----
case 0xa0: case 0xa1: case 0xa2: case 0xa3: case 0xa4:
case 0xa5: case 0xa6: // add, sub, mul, div, min, max, copysign
r = v_binop(v, c_m3Type_f64); break;
// ---- Conversions ----
case 0xa7: r = v_cvtop(v, c_m3Type_i64, c_m3Type_i32); break; // i32.wrap/i64
case 0xa8: case 0xa9: // i32.trunc_s/f32, i32.trunc_u/f32
r = v_cvtop(v, c_m3Type_f32, c_m3Type_i32); break;
case 0xaa: case 0xab: // i32.trunc_s/f64, i32.trunc_u/f64
r = v_cvtop(v, c_m3Type_f64, c_m3Type_i32); break;
case 0xac: case 0xad: // i64.extend_s/i32, i64.extend_u/i32
r = v_cvtop(v, c_m3Type_i32, c_m3Type_i64); break;
case 0xae: case 0xaf: // i64.trunc_s/f32, i64.trunc_u/f32
r = v_cvtop(v, c_m3Type_f32, c_m3Type_i64); break;
case 0xb0: case 0xb1: // i64.trunc_s/f64, i64.trunc_u/f64
r = v_cvtop(v, c_m3Type_f64, c_m3Type_i64); break;
case 0xb2: case 0xb3: // f32.convert_s/i32, f32.convert_u/i32
r = v_cvtop(v, c_m3Type_i32, c_m3Type_f32); break;
case 0xb4: case 0xb5: // f32.convert_s/i64, f32.convert_u/i64
r = v_cvtop(v, c_m3Type_i64, c_m3Type_f32); break;
case 0xb6: // f32.demote/f64
r = v_cvtop(v, c_m3Type_f64, c_m3Type_f32); break;
case 0xb7: case 0xb8: // f64.convert_s/i32, f64.convert_u/i32
r = v_cvtop(v, c_m3Type_i32, c_m3Type_f64); break;
case 0xb9: case 0xba: // f64.convert_s/i64, f64.convert_u/i64
r = v_cvtop(v, c_m3Type_i64, c_m3Type_f64); break;
case 0xbb: // f64.promote/f32
r = v_cvtop(v, c_m3Type_f32, c_m3Type_f64); break;
case 0xbc: // i32.reinterpret/f32
r = v_cvtop(v, c_m3Type_f32, c_m3Type_i32); break;
case 0xbd: // i64.reinterpret/f64
r = v_cvtop(v, c_m3Type_f64, c_m3Type_i64); break;
case 0xbe: // f32.reinterpret/i32
r = v_cvtop(v, c_m3Type_i32, c_m3Type_f32); break;
case 0xbf: // f64.reinterpret/i64
r = v_cvtop(v, c_m3Type_i64, c_m3Type_f64); break;
// ---- Sign-extension (MVP post) ----
case 0xc0: case 0xc1: // i32.extend8_s, i32.extend16_s
r = v_unop(v, c_m3Type_i32, c_m3Type_i32); break;
case 0xc2: case 0xc3: case 0xc4: // i64.extend8/16/32_s
r = v_unop(v, c_m3Type_i64, c_m3Type_i64); break;
// ---- 0xFC prefix (saturating truncations + bulk memory) ----
case 0xfc:
{
u32 sub;
r = ReadLEB_u32(&sub, &v->wasm, v->wasmEnd);
if (r) return r;
switch (sub) {
case 0x00: case 0x01: // i32.trunc_sat_f32_s/u
r = v_cvtop(v, c_m3Type_f32, c_m3Type_i32); break;
case 0x02: case 0x03: // i32.trunc_sat_f64_s/u
r = v_cvtop(v, c_m3Type_f64, c_m3Type_i32); break;
case 0x04: case 0x05: // i64.trunc_sat_f32_s/u
r = v_cvtop(v, c_m3Type_f32, c_m3Type_i64); break;
case 0x06: case 0x07: // i64.trunc_sat_f64_s/u
r = v_cvtop(v, c_m3Type_f64, c_m3Type_i64); break;
case 0x0a: // memory.copy
{
u32 dst, src;
r = ReadLEB_u32(&dst, &v->wasm, v->wasmEnd); if (r) return r;
r = ReadLEB_u32(&src, &v->wasm, v->wasmEnd); if (r) return r;
r = v_pop_expect(v, c_m3Type_i32, &a); if (r) return r; // n
r = v_pop_expect(v, c_m3Type_i32, &a); if (r) return r; // src
r = v_pop_expect(v, c_m3Type_i32, &a); if (r) return r; // dst
break;
}
case 0x0b: // memory.fill
{
u32 memidx;
r = ReadLEB_u32(&memidx, &v->wasm, v->wasmEnd); if (r) return r;
r = v_pop_expect(v, c_m3Type_i32, &a); if (r) return r; // n
r = v_pop_expect(v, c_m3Type_i32, &a); if (r) return r; // val
r = v_pop_expect(v, c_m3Type_i32, &a); if (r) return r; // dst
break;
}
default:
// Unknown FC sub-opcode: skip validation (allow forward compat)
break;
}
break;
}
default:
// Unknown opcode - skip rather than fail for forward compat
// (the compiler will reject truly unsupported ops later)
break;
} // switch
if (r) return r;
} // while
// If we ran out of bytes without hitting the final end
return m3Err_wasmMalformed;
}
// ---------- Public entry point ----------
M3Result ValidateFunction (IM3Function i_function)
{
if (!i_function->wasm) return m3Err_none;
IM3FuncType funcType = i_function->funcType;
IM3Module module = i_function->module;
// Set up context on stack
ValCtx v;
memset(&v, 0, sizeof(v));
v.module = module;
v.function = i_function;
v.wasm = i_function->wasm;
v.wasmEnd = i_function->wasmEnd;
// Skip code size LEB
u32 size;
M3Result r = ReadLEB_u32(&size, &v.wasm, v.wasmEnd);
if (r) return r;
// Parse locals
u32 numLocalBlocks;
r = ReadLEB_u32(&numLocalBlocks, &v.wasm, v.wasmEnd);
if (r) return r;
// First: params. Running out of room has to be an error, not a truncation:
// a short localTypes would make later local.get indices read as unknown
u16 numParams = funcType ? funcType->numArgs : 0;
if (numParams > d_m3ValStack) return m3Err_functionStackOverflow;
for (u16 i = 0; i < numParams; i++) {
v.localTypes[v.numLocals++] = funcType->types[funcType->numRets + i];
}
// Then: declared locals
for (u32 b = 0; b < numLocalBlocks; b++) {
u32 count;
r = ReadLEB_u32(&count, &v.wasm, v.wasmEnd);
if (r) return r;
i8 waType;
r = ReadLEB_i7(&waType, &v.wasm, v.wasmEnd);
if (r) return r;
u8 normalized;
r = NormalizeType(&normalized, waType);
if (r) return r;
if (count > (u32) (d_m3ValStack - v.numLocals)) return m3Err_functionStackOverflow;
for (u32 c = 0; c < count; c++) {
v.localTypes[v.numLocals++] = normalized;
}
}
// Push the function-level control frame
r = v_push_ctrl(&v, 0x00, funcType); // opcode 0x00 marks function frame
if (r) return r;
// Push params onto operand stack (they're part of the function body's initial stack)
// Actually per the spec, locals are indexed but not on the operand stack.
// The function frame's params are NOT pushed to the operand stack.
// Only block params would be pushed (and for the function frame there are no block params
// since the function body's "block type" has results = function returns, params = 0).
// The function frame's label_types = results (since it's not a loop).
// Validate the body
r = v_validate_body(&v);
if (r) return r;
// After validation, control stack should be empty
if (v.ctrlTop != 0)
return m3Err_wasmMalformed;
return m3Err_none;
}
#else // !d_m3EnableValidation
M3Result ValidateFunction (IM3Function i_function)
{
(void)i_function;
return m3Err_none;
}
#endif // d_m3EnableValidation
+25
View File
@@ -0,0 +1,25 @@
//
// m3_validate.h
//
// Pre-pass WebAssembly bytecode validator using the spec's type-checking algorithm.
// Runs before compilation to catch type errors early.
//
#ifndef m3_validate_h
#define m3_validate_h
#include "m3_core.h"
#include "m3_compile.h"
#include "m3_env.h"
d_m3BeginExternC
// Validate a function's bytecode before compilation.
// Performs full type-checking per the WebAssembly spec algorithm:
// operand type stack + control stack with polymorphic handling.
// Returns m3Err_none on success or a validation error.
M3Result ValidateFunction (IM3Function i_function);
d_m3EndExternC
#endif // m3_validate_h
+391
View File
@@ -0,0 +1,391 @@
//
// Wasm3, high performance WebAssembly interpreter
//
// Copyright © 2019 Steven Massey, Volodymyr Shymanskyy.
// All rights reserved.
//
#ifndef wasm3_h
#define wasm3_h
#define M3_VERSION_MAJOR 0
#define M3_VERSION_MINOR 5
#define M3_VERSION_REV 2
#define M3_VERSION "0.5.2"
#include <stddef.h>
#include <stdlib.h>
#include <stdint.h>
#include <inttypes.h>
#include <stdarg.h>
#include <stddef.h>
#include "wasm3_defs.h"
// Constants
#define M3_BACKTRACE_TRUNCATED (IM3BacktraceFrame)(SIZE_MAX)
#if defined(__cplusplus)
extern "C" {
#endif
typedef const char * M3Result;
struct M3Environment; typedef struct M3Environment * IM3Environment;
struct M3Runtime; typedef struct M3Runtime * IM3Runtime;
struct M3Module; typedef struct M3Module * IM3Module;
struct M3Function; typedef struct M3Function * IM3Function;
struct M3Global; typedef struct M3Global * IM3Global;
typedef struct M3ErrorInfo
{
M3Result result;
IM3Runtime runtime;
IM3Module module;
IM3Function function;
const char * file;
uint32_t line;
const char * message;
} M3ErrorInfo;
typedef struct M3BacktraceFrame
{
uint32_t moduleOffset;
IM3Function function;
struct M3BacktraceFrame * next;
}
M3BacktraceFrame, * IM3BacktraceFrame;
typedef struct M3BacktraceInfo
{
IM3BacktraceFrame frames;
IM3BacktraceFrame lastFrame; // can be M3_BACKTRACE_TRUNCATED
}
M3BacktraceInfo, * IM3BacktraceInfo;
typedef enum M3ValueType
{
c_m3Type_none = 0,
c_m3Type_i32 = 1,
c_m3Type_i64 = 2,
c_m3Type_f32 = 3,
c_m3Type_f64 = 4,
// Opaque 16-byte slot used purely so wasm3 can PARSE modules
// whose function signatures or local-variable declarations
// mention v128 (the SIMD value type, wasm-encoded as 0x7B).
// Actual v128 OPCODES still error at compile-time with
// m3Err_unknownOpcode - we only avoid the parse-time
// m3Err_invalidTypeId rejection. LLVM's auto-vectorizer emits
// unused v128 locals into many `+simd128` modules even when no
// SIMD op executes; without this slot wasm3 rejects every such
// module before it ever sees a function body.
c_m3Type_v128 = 5,
c_m3Type_unknown
} M3ValueType;
typedef struct M3TaggedValue
{
M3ValueType type;
union M3ValueUnion
{
uint32_t i32;
uint64_t i64;
float f32;
double f64;
} value;
}
M3TaggedValue, * IM3TaggedValue;
typedef struct M3ImportInfo
{
const char * moduleUtf8;
const char * fieldUtf8;
}
M3ImportInfo, * IM3ImportInfo;
typedef struct M3ImportContext
{
void * userdata;
IM3Function function;
}
M3ImportContext, * IM3ImportContext;
// -------------------------------------------------------------------------------------------------------------------------------
// error codes
// -------------------------------------------------------------------------------------------------------------------------------
# if defined(M3_IMPLEMENT_ERROR_STRINGS)
# if defined(__cplusplus)
# define d_m3ErrorConst(LABEL, STRING) extern const M3Result m3Err_##LABEL = { STRING };
# else
# define d_m3ErrorConst(LABEL, STRING) const M3Result m3Err_##LABEL = { STRING };
# endif
# else
# define d_m3ErrorConst(LABEL, STRING) extern const M3Result m3Err_##LABEL;
# endif
// -------------------------------------------------------------------------------------------------------------------------------
d_m3ErrorConst (none, NULL)
// general errors
d_m3ErrorConst (mallocFailed, "memory allocation failed")
// parse errors
d_m3ErrorConst (incompatibleWasmVersion, "incompatible Wasm binary version")
d_m3ErrorConst (wasmMalformed, "malformed Wasm binary")
d_m3ErrorConst (misorderedWasmSection, "out of order Wasm section")
d_m3ErrorConst (wasmUnderrun, "underrun while parsing Wasm binary")
d_m3ErrorConst (wasmOverrun, "overrun while parsing Wasm binary")
d_m3ErrorConst (wasmMissingInitExpr, "missing init_expr in Wasm binary")
d_m3ErrorConst (lebOverflow, "LEB encoded value overflow")
d_m3ErrorConst (missingUTF8, "invalid length UTF-8 string")
d_m3ErrorConst (wasmSectionUnderrun, "section underrun while parsing Wasm binary")
d_m3ErrorConst (wasmSectionOverrun, "section overrun while parsing Wasm binary")
d_m3ErrorConst (invalidTypeId, "unknown value_type")
d_m3ErrorConst (tooManyMemorySections, "only one memory per module is supported")
d_m3ErrorConst (tooManyArgsRets, "too many arguments or return values")
// link errors
d_m3ErrorConst (moduleNotLinked, "attempting to use module that is not loaded")
d_m3ErrorConst (moduleAlreadyLinked, "attempting to bind module to multiple runtimes")
d_m3ErrorConst (functionLookupFailed, "function lookup failed")
d_m3ErrorConst (functionImportMissing, "missing imported function")
d_m3ErrorConst (malformedFunctionSignature, "malformed function signature")
// compilation errors
d_m3ErrorConst (noCompiler, "no compiler found for opcode")
d_m3ErrorConst (unknownOpcode, "unknown opcode")
d_m3ErrorConst (restrictedOpcode, "restricted opcode")
d_m3ErrorConst (functionStackOverflow, "compiling function overran its stack height limit")
d_m3ErrorConst (functionStackUnderrun, "compiling function underran the stack")
d_m3ErrorConst (mallocFailedCodePage, "memory allocation failed when acquiring a new M3 code page")
d_m3ErrorConst (settingImmutableGlobal, "attempting to set an immutable global")
d_m3ErrorConst (typeMismatch, "incorrect type on stack")
d_m3ErrorConst (typeCountMismatch, "incorrect value count on stack")
// runtime errors
d_m3ErrorConst (missingCompiledCode, "function is missing compiled m3 code")
d_m3ErrorConst (wasmMemoryOverflow, "runtime ran out of memory")
d_m3ErrorConst (globalMemoryNotAllocated, "global memory is missing from a module")
d_m3ErrorConst (globaIndexOutOfBounds, "global index is too large")
d_m3ErrorConst (argumentCountMismatch, "argument count mismatch")
d_m3ErrorConst (argumentTypeMismatch, "argument type mismatch")
d_m3ErrorConst (globalLookupFailed, "global lookup failed")
d_m3ErrorConst (globalTypeMismatch, "global type mismatch")
d_m3ErrorConst (globalNotMutable, "global is not mutable")
// traps
d_m3ErrorConst (trapOutOfBoundsMemoryAccess, "[trap] out of bounds memory access")
d_m3ErrorConst (trapDivisionByZero, "[trap] integer divide by zero")
d_m3ErrorConst (trapIntegerOverflow, "[trap] integer overflow")
d_m3ErrorConst (trapIntegerConversion, "[trap] invalid conversion to integer")
d_m3ErrorConst (trapIndirectCallTypeMismatch, "[trap] indirect call type mismatch")
d_m3ErrorConst (trapTableIndexOutOfRange, "[trap] undefined element")
d_m3ErrorConst (trapTableElementIsNull, "[trap] null table element")
d_m3ErrorConst (trapExit, "[trap] program called exit")
d_m3ErrorConst (trapAbort, "[trap] program called abort")
d_m3ErrorConst (trapUnreachable, "[trap] unreachable executed")
d_m3ErrorConst (trapStackOverflow, "[trap] stack overflow")
//-------------------------------------------------------------------------------------------------------------------------------
// configuration, can be found in m3_config.h, m3_config_platforms.h, m3_core.h)
//-------------------------------------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------------------------------------
// global environment than can host multiple runtimes
//-------------------------------------------------------------------------------------------------------------------------------
IM3Environment m3_NewEnvironment (void);
void m3_FreeEnvironment (IM3Environment i_environment);
typedef M3Result (* M3SectionHandler) (IM3Module i_module, const char* name, const uint8_t * start, const uint8_t * end);
void m3_SetCustomSectionHandler (IM3Environment i_environment, M3SectionHandler i_handler);
//-------------------------------------------------------------------------------------------------------------------------------
// execution context
//-------------------------------------------------------------------------------------------------------------------------------
IM3Runtime m3_NewRuntime (IM3Environment io_environment,
uint32_t i_stackSizeInBytes,
void * i_userdata);
void m3_FreeRuntime (IM3Runtime i_runtime);
// Wasm currently only supports one memory region. i_memoryIndex should be zero.
uint8_t * m3_GetMemory (IM3Runtime i_runtime,
uint32_t * o_memorySizeInBytes,
uint32_t i_memoryIndex);
// This is used internally by Raw Function helpers
uint32_t m3_GetMemorySize (IM3Runtime i_runtime);
void * m3_GetUserData (IM3Runtime i_runtime);
//-------------------------------------------------------------------------------------------------------------------------------
// modules
//-------------------------------------------------------------------------------------------------------------------------------
// i_wasmBytes data must be persistent during the lifetime of the module
M3Result m3_ParseModule (IM3Environment i_environment,
IM3Module * o_module,
const uint8_t * const i_wasmBytes,
uint32_t i_numWasmBytes);
// Only modules not loaded into a M3Runtime need to be freed. A module is considered unloaded if
// a. m3_LoadModule has not yet been called on that module. Or,
// b. m3_LoadModule returned a result.
void m3_FreeModule (IM3Module i_module);
// LoadModule transfers ownership of a module to the runtime. Do not free modules once successfully loaded into the runtime
M3Result m3_LoadModule (IM3Runtime io_runtime, IM3Module io_module);
// Optional, compiles all functions in the module
M3Result m3_CompileModule (IM3Module io_module);
// Calling m3_RunStart is optional
M3Result m3_RunStart (IM3Module i_module);
// Arguments and return values are passed in and out through the stack pointer _sp.
// Placeholder return value slots are first and arguments after. So, the first argument is at _sp [numReturns]
// Return values should be written into _sp [0] to _sp [num_returns - 1]
typedef const void * (* M3RawCall) (IM3Runtime runtime, IM3ImportContext _ctx, uint64_t * _sp, void * _mem);
M3Result m3_LinkRawFunction (IM3Module io_module,
const char * const i_moduleName,
const char * const i_functionName,
const char * const i_signature,
M3RawCall i_function);
M3Result m3_LinkRawFunctionEx (IM3Module io_module,
const char * const i_moduleName,
const char * const i_functionName,
const char * const i_signature,
M3RawCall i_function,
const void * i_userdata);
const char* m3_GetModuleName (IM3Module i_module);
void m3_SetModuleName (IM3Module i_module, const char* name);
IM3Runtime m3_GetModuleRuntime (IM3Module i_module);
//-------------------------------------------------------------------------------------------------------------------------------
// globals
//-------------------------------------------------------------------------------------------------------------------------------
IM3Global m3_FindGlobal (IM3Module io_module,
const char * const i_globalName);
M3Result m3_GetGlobal (IM3Global i_global,
IM3TaggedValue o_value);
M3Result m3_SetGlobal (IM3Global i_global,
const IM3TaggedValue i_value);
M3ValueType m3_GetGlobalType (IM3Global i_global);
//-------------------------------------------------------------------------------------------------------------------------------
// functions
//-------------------------------------------------------------------------------------------------------------------------------
M3Result m3_Yield (void);
// o_function is valid during the lifetime of the originating runtime
M3Result m3_FindFunction (IM3Function * o_function,
IM3Runtime i_runtime,
const char * const i_functionName);
M3Result m3_GetTableFunction (IM3Function * o_function,
IM3Module i_module,
uint32_t i_index);
uint32_t m3_GetArgCount (IM3Function i_function);
uint32_t m3_GetRetCount (IM3Function i_function);
M3ValueType m3_GetArgType (IM3Function i_function, uint32_t i_index);
M3ValueType m3_GetRetType (IM3Function i_function, uint32_t i_index);
M3Result m3_CallV (IM3Function i_function, ...);
M3Result m3_CallVL (IM3Function i_function, va_list i_args);
M3Result m3_Call (IM3Function i_function, uint32_t i_argc, const void * i_argptrs[]);
M3Result m3_CallArgv (IM3Function i_function, uint32_t i_argc, const char * i_argv[]);
M3Result m3_GetResultsV (IM3Function i_function, ...);
M3Result m3_GetResultsVL (IM3Function i_function, va_list o_rets);
M3Result m3_GetResults (IM3Function i_function, uint32_t i_retc, const void * o_retptrs[]);
void m3_GetErrorInfo (IM3Runtime i_runtime, M3ErrorInfo* o_info);
void m3_ResetErrorInfo (IM3Runtime i_runtime);
const char* m3_GetFunctionName (IM3Function i_function);
IM3Module m3_GetFunctionModule (IM3Function i_function);
//-------------------------------------------------------------------------------------------------------------------------------
// debug info
//-------------------------------------------------------------------------------------------------------------------------------
void m3_PrintRuntimeInfo (IM3Runtime i_runtime);
void m3_PrintM3Info (void);
void m3_PrintProfilerInfo (void);
// The runtime owns the backtrace, do not free the backtrace you obtain. Returns NULL if there's no backtrace.
IM3BacktraceInfo m3_GetBacktrace (IM3Runtime i_runtime);
//-------------------------------------------------------------------------------------------------------------------------------
// raw function definition helpers
//-------------------------------------------------------------------------------------------------------------------------------
# define m3ApiOffsetToPtr(offset) (void*)((uint8_t*)_mem + (uint32_t)(offset))
# define m3ApiPtrToOffset(ptr) (uint32_t)((uint8_t*)ptr - (uint8_t*)_mem)
# define m3ApiReturnType(TYPE) TYPE* raw_return = ((TYPE*) (_sp++));
# define m3ApiMultiValueReturnType(TYPE, NAME) TYPE* NAME = ((TYPE*) (_sp++));
# define m3ApiGetArg(TYPE, NAME) TYPE NAME = \
(sizeof(TYPE) >= sizeof(uint32_t)) ? \
(*((TYPE *)(_sp++))) : \
((TYPE)(*((uint32_t *)(_sp++))));
# define m3ApiGetArgMem(TYPE, NAME) TYPE NAME = (TYPE)m3ApiOffsetToPtr(* ((uint32_t *) (_sp++)));
# define m3ApiIsNullPtr(addr) ((void*)(addr) <= _mem)
# define m3ApiCheckMem(addr, len) { if (M3_UNLIKELY(((void*)(addr) < _mem) || ((uint64_t)(uintptr_t)(addr) + (len)) > ((uint64_t)(uintptr_t)(_mem)+m3_GetMemorySize(runtime)))) m3ApiTrap(m3Err_trapOutOfBoundsMemoryAccess); }
# define m3ApiRawFunction(NAME) const void * NAME (IM3Runtime runtime, IM3ImportContext _ctx, uint64_t * _sp, void * _mem)
# define m3ApiReturn(VALUE) { *raw_return = (VALUE); return m3Err_none;}
# define m3ApiMultiValueReturn(NAME, VALUE) { *NAME = (VALUE); }
# define m3ApiTrap(VALUE) { return VALUE; }
# define m3ApiSuccess() { return m3Err_none; }
# if defined(M3_BIG_ENDIAN)
# define m3ApiReadMem8(ptr) (* (uint8_t *)(ptr))
# define m3ApiReadMem16(ptr) m3_bswap16((* (uint16_t *)(ptr)))
# define m3ApiReadMem32(ptr) m3_bswap32((* (uint32_t *)(ptr)))
# define m3ApiReadMem64(ptr) m3_bswap64((* (uint64_t *)(ptr)))
# define m3ApiWriteMem8(ptr, val) { * (uint8_t *)(ptr) = (val); }
# define m3ApiWriteMem16(ptr, val) { * (uint16_t *)(ptr) = m3_bswap16((val)); }
# define m3ApiWriteMem32(ptr, val) { * (uint32_t *)(ptr) = m3_bswap32((val)); }
# define m3ApiWriteMem64(ptr, val) { * (uint64_t *)(ptr) = m3_bswap64((val)); }
# else
# define m3ApiReadMem8(ptr) (* (uint8_t *)(ptr))
# define m3ApiReadMem16(ptr) (* (uint16_t *)(ptr))
# define m3ApiReadMem32(ptr) (* (uint32_t *)(ptr))
# define m3ApiReadMem64(ptr) (* (uint64_t *)(ptr))
# define m3ApiWriteMem8(ptr, val) { * (uint8_t *)(ptr) = (val); }
# define m3ApiWriteMem16(ptr, val) { * (uint16_t *)(ptr) = (val); }
# define m3ApiWriteMem32(ptr, val) { * (uint32_t *)(ptr) = (val); }
# define m3ApiWriteMem64(ptr, val) { * (uint64_t *)(ptr) = (val); }
# endif
#if defined(__cplusplus)
}
#endif
#endif // wasm3_h
+293
View File
@@ -0,0 +1,293 @@
//
// wasm3_defs.h
//
// Created by Volodymyr Shymanskyy on 11/20/19.
// Copyright © 2019 Volodymyr Shymanskyy. All rights reserved.
//
#ifndef wasm3_defs_h
#define wasm3_defs_h
#define M3_STR__(x) #x
#define M3_STR(x) M3_STR__(x)
#define M3_CONCAT__(a,b) a##b
#define M3_CONCAT(a,b) M3_CONCAT__(a,b)
/*
* Detect compiler
*/
# if defined(__clang__)
# define M3_COMPILER_CLANG 1
# elif defined(__INTEL_COMPILER)
# define M3_COMPILER_ICC 1
# elif defined(__GNUC__) || defined(__GNUG__)
# define M3_COMPILER_GCC 1
# elif defined(_MSC_VER)
# define M3_COMPILER_MSVC 1
# else
# warning "Compiler not detected"
# endif
# if defined(M3_COMPILER_CLANG)
# if defined(WIN32)
# define M3_COMPILER_VER __VERSION__ " for Windows"
# else
# define M3_COMPILER_VER __VERSION__
# endif
# elif defined(M3_COMPILER_GCC)
# define M3_COMPILER_VER "GCC " __VERSION__
# elif defined(M3_COMPILER_ICC)
# define M3_COMPILER_VER __VERSION__
# elif defined(M3_COMPILER_MSVC)
# define M3_COMPILER_VER "MSVC " M3_STR(_MSC_VER)
# else
# define M3_COMPILER_VER "unknown"
# endif
# ifdef __has_feature
# define M3_COMPILER_HAS_FEATURE(x) __has_feature(x)
# else
# define M3_COMPILER_HAS_FEATURE(x) 0
# endif
# ifdef __has_builtin
# define M3_COMPILER_HAS_BUILTIN(x) __has_builtin(x)
# else
# define M3_COMPILER_HAS_BUILTIN(x) 0
# endif
# ifdef __has_attribute
# define M3_COMPILER_HAS_ATTRIBUTE(x) __has_attribute(x)
# else
# define M3_COMPILER_HAS_ATTRIBUTE(x) 0
# endif
/*
* Detect endianness
*/
# if defined(M3_COMPILER_MSVC)
# define M3_LITTLE_ENDIAN
# elif defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
# define M3_LITTLE_ENDIAN
# elif defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
# define M3_BIG_ENDIAN
# else
# error "Byte order not detected"
# endif
/*
* Detect platform
*/
# if defined(M3_COMPILER_CLANG) || defined(M3_COMPILER_GCC) || defined(M3_COMPILER_ICC)
# if defined(__wasm__)
# define M3_ARCH "wasm"
# elif defined(__x86_64__)
# define M3_ARCH "x86_64"
# elif defined(__i386__)
# define M3_ARCH "i386"
# elif defined(__aarch64__)
# define M3_ARCH "arm64-v8a"
# elif defined(__arm__)
# if defined(__ARM_ARCH_7A__)
# if defined(__ARM_NEON__)
# if defined(__ARM_PCS_VFP)
# define M3_ARCH "arm-v7a/NEON hard-float"
# else
# define M3_ARCH "arm-v7a/NEON"
# endif
# else
# if defined(__ARM_PCS_VFP)
# define M3_ARCH "arm-v7a hard-float"
# else
# define M3_ARCH "arm-v7a"
# endif
# endif
# else
# define M3_ARCH "arm"
# endif
# elif defined(__riscv)
# if defined(__riscv_32e)
# define _M3_ARCH_RV "rv32e"
# elif __riscv_xlen == 128
# define _M3_ARCH_RV "rv128i"
# elif __riscv_xlen == 64
# define _M3_ARCH_RV "rv64i"
# elif __riscv_xlen == 32
# define _M3_ARCH_RV "rv32i"
# endif
# if defined(__riscv_muldiv)
# define _M3_ARCH_RV_M _M3_ARCH_RV "m"
# else
# define _M3_ARCH_RV_M _M3_ARCH_RV
# endif
# if defined(__riscv_atomic)
# define _M3_ARCH_RV_A _M3_ARCH_RV_M "a"
# else
# define _M3_ARCH_RV_A _M3_ARCH_RV_M
# endif
# if defined(__riscv_flen)
# define _M3_ARCH_RV_F _M3_ARCH_RV_A "f"
# else
# define _M3_ARCH_RV_F _M3_ARCH_RV_A
# endif
# if defined(__riscv_flen) && __riscv_flen >= 64
# define _M3_ARCH_RV_D _M3_ARCH_RV_F "d"
# else
# define _M3_ARCH_RV_D _M3_ARCH_RV_F
# endif
# if defined(__riscv_compressed)
# define _M3_ARCH_RV_C _M3_ARCH_RV_D "c"
# else
# define _M3_ARCH_RV_C _M3_ARCH_RV_D
# endif
# define M3_ARCH _M3_ARCH_RV_C
# elif defined(__mips__)
# if defined(__MIPSEB__) && defined(__mips64)
# define M3_ARCH "mips64 " _MIPS_ARCH
# elif defined(__MIPSEL__) && defined(__mips64)
# define M3_ARCH "mips64el " _MIPS_ARCH
# elif defined(__MIPSEB__)
# define M3_ARCH "mips " _MIPS_ARCH
# elif defined(__MIPSEL__)
# define M3_ARCH "mipsel " _MIPS_ARCH
# endif
# elif defined(__PPC__)
# if defined(__PPC64__) && defined(__LITTLE_ENDIAN__)
# define M3_ARCH "ppc64le"
# elif defined(__PPC64__)
# define M3_ARCH "ppc64"
# else
# define M3_ARCH "ppc"
# endif
# elif defined(__sparc__)
# if defined(__arch64__)
# define M3_ARCH "sparc64"
# else
# define M3_ARCH "sparc"
# endif
# elif defined(__s390x__)
# define M3_ARCH "s390x"
# elif defined(__alpha__)
# define M3_ARCH "alpha"
# elif defined(__m68k__)
# define M3_ARCH "m68k"
# elif defined(__xtensa__)
# define M3_ARCH "xtensa"
# elif defined(__arc__)
# define M3_ARCH "arc32"
# elif defined(__AVR__)
# define M3_ARCH "avr"
# endif
# endif
# if defined(M3_COMPILER_MSVC)
# if defined(_M_X64)
# define M3_ARCH "x86_64"
# elif defined(_M_IX86)
# define M3_ARCH "i386"
# elif defined(_M_ARM64)
# define M3_ARCH "arm64"
# elif defined(_M_ARM)
# define M3_ARCH "arm"
# endif
# endif
# if !defined(M3_ARCH)
# warning "Architecture not detected"
# define M3_ARCH "unknown"
# endif
/*
* Byte swapping (for Big-Endian systems only)
*/
# if defined(M3_COMPILER_MSVC)
# define m3_bswap16(x) _byteswap_ushort((x))
# define m3_bswap32(x) _byteswap_ulong((x))
# define m3_bswap64(x) _byteswap_uint64((x))
# elif defined(M3_COMPILER_GCC) && ((__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8))
// __builtin_bswap32/64 added in gcc 4.3, __builtin_bswap16 added in gcc 4.8
# define m3_bswap16(x) __builtin_bswap16((x))
# define m3_bswap32(x) __builtin_bswap32((x))
# define m3_bswap64(x) __builtin_bswap64((x))
# elif defined(M3_COMPILER_CLANG) && M3_COMPILER_HAS_BUILTIN(__builtin_bswap16)
# define m3_bswap16(x) __builtin_bswap16((x))
# define m3_bswap32(x) __builtin_bswap32((x))
# define m3_bswap64(x) __builtin_bswap64((x))
# elif defined(M3_COMPILER_ICC)
# define m3_bswap16(x) __builtin_bswap16((x))
# define m3_bswap32(x) __builtin_bswap32((x))
# define m3_bswap64(x) __builtin_bswap64((x))
# else
# ifdef __linux__
# include <endian.h>
# else
# include <stdint.h>
# endif
# if defined(__bswap_16)
# define m3_bswap16(x) __bswap_16((x))
# define m3_bswap32(x) __bswap_32((x))
# define m3_bswap64(x) __bswap_64((x))
# else
# warning "Using naive (probably slow) bswap operations"
static inline
uint16_t m3_bswap16(uint16_t x) {
return ((( x >> 8 ) & 0xffu ) | (( x & 0xffu ) << 8 ));
}
static inline
uint32_t m3_bswap32(uint32_t x) {
return ((( x & 0xff000000u ) >> 24 ) |
(( x & 0x00ff0000u ) >> 8 ) |
(( x & 0x0000ff00u ) << 8 ) |
(( x & 0x000000ffu ) << 24 ));
}
static inline
uint64_t m3_bswap64(uint64_t x) {
return ((( x & 0xff00000000000000ull ) >> 56 ) |
(( x & 0x00ff000000000000ull ) >> 40 ) |
(( x & 0x0000ff0000000000ull ) >> 24 ) |
(( x & 0x000000ff00000000ull ) >> 8 ) |
(( x & 0x00000000ff000000ull ) << 8 ) |
(( x & 0x0000000000ff0000ull ) << 24 ) |
(( x & 0x000000000000ff00ull ) << 40 ) |
(( x & 0x00000000000000ffull ) << 56 ));
}
# endif
# endif
/*
* Bit ops
*/
#define m3_isBitSet(val, pos) ((val & (1 << pos)) != 0)
/*
* Other
*/
# if defined(M3_COMPILER_GCC) || defined(M3_COMPILER_CLANG) || defined(M3_COMPILER_ICC)
# define M3_UNLIKELY(x) __builtin_expect(!!(x), 0)
# define M3_LIKELY(x) __builtin_expect(!!(x), 1)
# else
# define M3_UNLIKELY(x) (x)
# define M3_LIKELY(x) (x)
# endif
#endif // wasm3_defs_h
+103
View File
@@ -0,0 +1,103 @@
/* wasm_call.c - serialized wasm3 call under a per-module mutex on a fresh 64KB
* kernel stack. one entry point everyone in the driver uses to invoke guests. */
#include "inc/gvm.h"
#include "../shared/goodmans_ioctl.h"
#include "wasm3/m3_function.h"
#include "wasm3/wasm3.h"
typedef struct {
IM3Function fn;
uint32_t argc;
const void** argp;
M3Result result;
} gvm_call_ctx;
static VOID gvm_call_callout(_In_ PVOID p)
{
gvm_call_ctx* c = (gvm_call_ctx*)p;
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL,
"[goodmans] pre-m3Call fn=%p name=%s\n",
c->fn, (c->fn && c->fn->export_name) ? c->fn->export_name : "?");
__try {
c->result = m3_Call(c->fn, c->argc, c->argp);
} __except (EXCEPTION_EXECUTE_HANDLER) {
c->result = "wasm3 dispatch raised kernel exception";
}
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL,
"[goodmans] post-m3Call result=%s\n", c->result ? c->result : "ok");
}
M3Result
gvm_call_locked(gvm_module* mod, IM3Function fn, unsigned int argc, const void** argp)
{
if (!mod || !fn) return "invalid module or function";
if (mod->poisoned) return "module poisoned by watchdog";
KeWaitForSingleObject(&mod->call_mutex, Executive, KernelMode, FALSE, NULL);
if (mod->poisoned) { KeReleaseMutex(&mod->call_mutex, FALSE); return "module poisoned"; }
// stamp mutex acquire for watchdog visibility
LARGE_INTEGER _f;
mod->mutex_hold_qpc = (ULONG64)KeQueryPerformanceCounter(&_f).QuadPart;
// trace: entering export
if (gvm_trace_enabled_for(mod->id)) {
unsigned long long targv[4] = {0,0,0,0};
unsigned int tac = argc > 4 ? 4 : argc;
for (unsigned int i = 0; i < tac; i++) targv[i] = argp[i] ? *(uint64_t*)argp[i] : 0;
gvm_trace_push(mod->id, GVM_TRK_CALL, fn->export_name ? fn->export_name : "?", tac, targv, 0);
}
gvm_call_ctx c = { fn, argc, argp, m3Err_none };
NTSTATUS s = KeExpandKernelStackAndCalloutEx(
gvm_call_callout, &c,
64 * 1024,
FALSE,
NULL);
// clear deadline after every call so it doesn't leak across invocations
mod->exec_deadline_qpc = 0;
// trace: return or trap
if (gvm_trace_enabled_for(mod->id)) {
if (!NT_SUCCESS(s)) {
unsigned long long argv[1] = { (unsigned long long)s };
gvm_trace_push(mod->id, GVM_TRK_TRAP, "kstack_callout_failed", 1, argv, 0);
} else if (c.result) {
// push the trap reason itself
gvm_trace_push(mod->id, GVM_TRK_TRAP, c.result, 0, NULL, 0);
// and each frame of the wasm backtrace as its own entry
IM3BacktraceInfo bt = m3_GetBacktrace(mod->runtime);
if (bt) {
unsigned int depth = 0;
for (IM3BacktraceFrame f = bt->frames; f && f != M3_BACKTRACE_TRUNCATED && depth < 16;
f = f->next, depth++) {
unsigned long long a[2] = { (unsigned long long)f->moduleOffset, depth };
const char* fname = (f->function && f->function->export_name)
? f->function->export_name : "<anon>";
gvm_trace_push(mod->id, GVM_TRK_TRAP, fname, 2, a, 0);
}
}
} else {
gvm_trace_push(mod->id, GVM_TRK_RETURN, fn->export_name ? fn->export_name : "?", 0, NULL, 0);
}
}
mod->mutex_hold_qpc = 0;
KeReleaseMutex(&mod->call_mutex, FALSE);
if (!NT_SUCCESS(s))
return "KeExpandKernelStackAndCalloutEx failed";
return c.result;
}
// caller sets timeout_ms > 0 before invoking gvm_call_locked to arm the deadline
void gvm_set_deadline_ms(gvm_module* mod, unsigned int timeout_ms)
{
if (!mod || timeout_ms == 0) { if (mod) mod->exec_deadline_qpc = 0; return; }
LARGE_INTEGER freq;
LARGE_INTEGER now = KeQueryPerformanceCounter(&freq);
ULONG64 ticks = ((ULONG64)freq.QuadPart * timeout_ms) / 1000ULL;
mod->exec_deadline_qpc = (ULONG64)now.QuadPart + ticks;
}
+103
View File
@@ -0,0 +1,103 @@
/* watchdog.c - system thread that patrols loaded modules for stuck guests.
* a module holding call_mutex longer than GVM_WATCHDOG_MAX_MS is flagged
* "poisoned" so no more calls dispatch, and the incident is logged.
* force-unload IOCTL bypasses the standard mutex wait for these cases.
*/
#include "inc/gvm.h"
#include "../shared/goodmans_ioctl.h"
#define GVM_WATCHDOG_MAX_MS 5000 // max wall-clock a single call may hold
#define GVM_WATCHDOG_PERIOD_MS 500 // scan cadence
static PETHREAD g_thread = NULL;
static KEVENT g_shutdown;
static BOOLEAN g_running = FALSE;
static VOID gvm_watchdog_body(PVOID ctx)
{
UNREFERENCED_PARAMETER(ctx);
LARGE_INTEGER wait; wait.QuadPart = -((LONGLONG)GVM_WATCHDOG_PERIOD_MS * 10 * 1000);
LARGE_INTEGER freq;
ULONG64 max_ticks = 0;
(void)KeQueryPerformanceCounter(&freq);
if (freq.QuadPart) max_ticks = ((ULONG64)freq.QuadPart * GVM_WATCHDOG_MAX_MS) / 1000ULL;
for (;;) {
NTSTATUS s = KeWaitForSingleObject(&g_shutdown, Executive, KernelMode, FALSE, &wait);
if (s == STATUS_SUCCESS) break; // shutdown signaled
if (!max_ticks) continue;
ULONG64 now = (ULONG64)KeQueryPerformanceCounter(NULL).QuadPart;
for (unsigned int i = 0; i < 32; i++) {
gvm_module* m = gvm_modtab_iter(i);
if (!m || !m->used) continue;
ULONG64 h = m->mutex_hold_qpc;
if (!h) continue;
if (now <= h) continue;
if ((now - h) < max_ticks) continue;
if (m->poisoned) continue;
InterlockedExchange(&m->poisoned, 1);
gvm_log("watchdog: module %u '%s' stuck >%u ms - marked poisoned",
m->id, m->name, GVM_WATCHDOG_MAX_MS);
}
}
PsTerminateSystemThread(STATUS_SUCCESS);
}
void gvm_watchdog_start(void)
{
if (g_running) return;
KeInitializeEvent(&g_shutdown, NotificationEvent, FALSE);
HANDLE h;
NTSTATUS s = PsCreateSystemThread(&h, THREAD_ALL_ACCESS, NULL, NULL, NULL, gvm_watchdog_body, NULL);
if (!NT_SUCCESS(s)) { gvm_log("watchdog: thread create failed 0x%x", s); return; }
ObReferenceObjectByHandle(h, THREAD_ALL_ACCESS, *PsThreadType, KernelMode, (PVOID*)&g_thread, NULL);
ZwClose(h);
g_running = TRUE;
gvm_log("watchdog: started (period=%ums, max_hold=%ums)", GVM_WATCHDOG_PERIOD_MS, GVM_WATCHDOG_MAX_MS);
}
void gvm_watchdog_stop(void)
{
if (!g_running) return;
KeSetEvent(&g_shutdown, IO_NO_INCREMENT, FALSE);
if (g_thread) {
KeWaitForSingleObject(g_thread, Executive, KernelMode, FALSE, NULL);
ObDereferenceObject(g_thread);
g_thread = NULL;
}
g_running = FALSE;
}
// IOCTL handler: bypass the normal unload path's mutex wait when a module is
// poisoned. safe only because poisoned modules can no longer make host calls.
NTSTATUS
gvm_ioctl_force_unload(PIRP irp, PIO_STACK_LOCATION sp)
{
ULONG in_len = sp->Parameters.DeviceIoControl.InputBufferLength;
void* buf = irp->AssociatedIrp.SystemBuffer;
if (in_len < sizeof(gvm_unload_in) || !buf) {
irp->IoStatus.Information = 0;
return STATUS_INVALID_PARAMETER;
}
gvm_unload_in in; RtlCopyMemory(&in, buf, sizeof(in));
gvm_module* m = gvm_modtab_get(in.module_id);
if (!m) { irp->IoStatus.Information = 0; return STATUS_NOT_FOUND; }
InterlockedExchange(&m->poisoned, 1);
gvm_log("force_unload: module %u '%s'", m->id, m->name);
// drain refcount to 1 then free (which decrements to 0). same pattern
// as unload_all; safe even on a wedged guest because poison stops new calls.
while (InterlockedCompareExchange(&m->refcount, 1, m->refcount) != 1) {
if (!m->used) break;
}
gvm_modtab_free(m);
irp->IoStatus.Information = 0;
return STATUS_SUCCESS;
}