mirror of
https://github.com/zer0condition/GoodmansKernel/
synced 2026-08-19 01:14:35 +00:00
initial commit
This commit is contained in:
@@ -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"
|
||||
@@ -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 -> $(DeployDir)" Importance="high" />
|
||||
<Exec Command="if exist "$(DeployDir)\GoodmansTest.pfx" for /f "delims=" %%s in ('where signtool 2^>nul') do "%%s" sign /fd sha256 /f "$(DeployDir)\GoodmansTest.pfx" /p goodmans "$(SignedSys)"" 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
@@ -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
@@ -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();
|
||||
}
|
||||
@@ -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__)
|
||||
@@ -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 = ∩︀
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
/* corecrt.h - blocks ucrt/corecrt.h chain */
|
||||
#pragma once
|
||||
@@ -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"
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1,2 @@
|
||||
/* malloc.h - blocks km/crt/malloc.h, decls in stdlib.h */
|
||||
#pragma once
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 */
|
||||
@@ -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
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 */
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user