diff --git a/compat/lmemorymanager.pas b/compat/lmemorymanager.pas index fda09ed4..b49e6e6d 100644 --- a/compat/lmemorymanager.pas +++ b/compat/lmemorymanager.pas @@ -22,12 +22,12 @@ type THeapPageHeader = memory.heap.THeapPageHeader; procedure init; -function kalloc(size : uint32) : void; -function klalloc(size : uint32) : void; -procedure klfree(address : uint32); -function kpalloc(address : uint32) : void; +function kalloc(size : SizeUInt) : void; +function klalloc(size : SizeUInt) : void; +procedure klfree(address : PtrUInt); +function kpalloc(address : PtrUInt) : void; procedure kfree(area : void); -function lmm_total_free : uint32; +function lmm_total_free : SizeUInt; function lmm_page_count : uint32; implementation @@ -37,22 +37,22 @@ begin memory.heap.init; end; -function kalloc(size : uint32) : void; inline; +function kalloc(size : SizeUInt) : void; inline; begin kalloc := memory.heap.kalloc(size); end; -function klalloc(size : uint32) : void; inline; +function klalloc(size : SizeUInt) : void; inline; begin klalloc := memory.heap.klalloc(size); end; -procedure klfree(address : uint32); inline; +procedure klfree(address : PtrUInt); inline; begin memory.heap.klfree(address); end; -function kpalloc(address : uint32) : void; inline; +function kpalloc(address : PtrUInt) : void; inline; begin kpalloc := memory.heap.kpalloc(address); end; @@ -62,7 +62,7 @@ begin memory.heap.kfree(area); end; -function lmm_total_free : uint32; inline; +function lmm_total_free : SizeUInt; inline; begin lmm_total_free := memory.heap.lmm_total_free; end; diff --git a/src/app/wasm/app.wasm.cleanup.pas b/src/app/wasm/app.wasm.cleanup.pas index 3ac2e900..067aa0b9 100644 --- a/src/app/wasm/app.wasm.cleanup.pas +++ b/src/app/wasm/app.wasm.cleanup.pas @@ -36,6 +36,7 @@ var begin if heap = nil then exit; if heap^.Memory <> nil then begin + if heap^.PageCount <> 0 then for i := 0 to heap^.PageCount - 1 do begin if heap^.Memory[i] <> nil then kfree(void(heap^.Memory[i])); @@ -73,6 +74,7 @@ var begin if tables = nil then exit; if tables^.Tables <> nil then begin + if tables^.TableCount <> 0 then for i := 0 to tables^.TableCount - 1 do begin if tables^.Tables[i].Elements <> nil then kfree(void(tables^.Tables[i].Elements)); @@ -90,6 +92,7 @@ var begin if ds = nil then exit; if ds^.Segments <> nil then begin + if ds^.SegmentCount <> 0 then for i := 0 to ds^.SegmentCount - 1 do begin if ds^.Segments[i].Data <> nil then kfree(void(ds^.Segments[i].Data)); @@ -107,6 +110,7 @@ var begin if es = nil then exit; if es^.Segments <> nil then begin + if es^.SegmentCount <> 0 then for i := 0 to es^.SegmentCount - 1 do begin if es^.Segments[i].FuncIndices <> nil then kfree(void(es^.Segments[i].FuncIndices)); @@ -124,6 +128,7 @@ var begin if ts = nil then exit; if ts^.Types <> nil then begin + if ts^.TypeCount <> 0 then for i := 0 to ts^.TypeCount - 1 do begin if ts^.Types[i].ParamTypes <> nil then kfree(void(ts^.Types[i].ParamTypes)); @@ -143,6 +148,7 @@ var begin if is_ = nil then exit; if is_^.Entries <> nil then begin + if is_^.ImportCount <> 0 then for i := 0 to is_^.ImportCount - 1 do begin if is_^.Entries[i].ModuleName <> nil then kfree(void(is_^.Entries[i].ModuleName)); @@ -172,6 +178,7 @@ var begin if es = nil then exit; if es^.Entries <> nil then begin + if es^.ExportCount <> 0 then for i := 0 to es^.ExportCount - 1 do begin if es^.Entries[i].Name <> nil then kfree(void(es^.Entries[i].Name)); @@ -189,6 +196,7 @@ var begin if cs = nil then exit; if cs^.Entries <> nil then begin + if cs^.CodeCount <> 0 then for i := 0 to cs^.CodeCount - 1 do begin if cs^.Entries[i].Code <> nil then kfree(void(cs^.Entries[i].Code)); diff --git a/src/app/wasm/app.wasm.io.pas b/src/app/wasm/app.wasm.io.pas index bdcc552e..7dd7b797 100644 --- a/src/app/wasm/app.wasm.io.pas +++ b/src/app/wasm/app.wasm.io.pas @@ -4,9 +4,7 @@ Provides concrete WASI callback functions that route fd 0/1/2 to the owning Asuro process's StdIn/StdOut/StdErr buffers. - An ActiveShim module-level variable is set before each tick batch - and cleared after. Hooks only fire synchronously within wasm_tick - so this is safe under preemptive scheduling. + Hooks resolve the shim from the currently scheduled process. @author(Kieron Morris ) } @@ -20,9 +18,7 @@ uses wasm.types.context, wasm.types.values; -{ Set/clear the active shim — call before/after each tick batch } -procedure setActiveShim(shim : PProcessWASMShim); -procedure clearActiveShim; + { WASI hook implementations — register via wasm_set_fd_write etc. } function asuro_fd_write(fd : TWASMUInt32; buf : TWASMPUInt8; len : TWASMUInt32) : TWASMUInt32; @@ -46,19 +42,13 @@ implementation uses io.stdio, proc.types, wasm.types.wasi, wasm.types.stack, wasm.types.heap, - arch.x86.bda, core.rand, core.util, arch.x86.util; + proc.mgr, arch.platform.interrupts, arch.common.legacyirq, core.rand, core.util, arch.platform.cpu; -var - ActiveShimPtr : PProcessWASMShim; - -procedure setActiveShim(shim : PProcessWASMShim); -begin - ActiveShimPtr := shim; -end; - -procedure clearActiveShim; +function ActiveShimPtr: PProcessWASMShim; begin ActiveShimPtr := nil; + if proc.mgr.CurrentProcess <> nil then + ActiveShimPtr := PProcessWASMShim(proc.mgr.CurrentProcess^.Local); end; { ---- fd_write: fd 1 = stdout, fd 2 = stderr ---- } @@ -81,7 +71,7 @@ begin end; end; - if outbuf = nil then exit; + if (outbuf = nil) or (len = 0) then exit; { Write byte-by-byte into the POutBuf } for i := 0 to len - 1 do @@ -111,7 +101,7 @@ begin avail := inbuf^.len; if avail = 0 then exit; if len < avail then copyLen := len else copyLen := avail; - memcpy(uint32(inbuf^.buf), uint32(buf), copyLen); + memcpy(PtrUInt(inbuf^.buf), PtrUInt(buf), copyLen); asuro_fd_read := copyLen; end; @@ -126,13 +116,16 @@ begin ActiveShimPtr^.ProcessCtx^.ExitCode := code; end; -{ ---- clock_time_get: use BIOS tick counter ---- } +{ ---- Monotonic time from the platform timer ---- } function asuro_clock_time_get(clock_id : TWASMUInt32; precision : TWASMUInt64; var time : TWASMUInt64) : TWASMUInt32; +var + ticks: uint64; begin - { Return time in nanoseconds based on BIOS 1024 Hz counter } - time := TWASMUInt64(arch.x86.bda.Counters.c64) * 976562; + ticks := arch.platform.interrupts.timerTicks; + time := (ticks div PIT_INPUT_HZ) * PIT_DIVISOR * 1000000000 + + ((ticks mod PIT_INPUT_HZ) * PIT_DIVISOR * 1000000000) div PIT_INPUT_HZ; asuro_clock_time_get := WASI_ESUCCESS; end; @@ -141,7 +134,7 @@ end; function asuro_clock_res_get(clock_id : TWASMUInt32; var resolution : TWASMUInt64) : TWASMUInt32; begin - resolution := 976562; { ~0.977ms in nanoseconds (1/1024 s) } + resolution := (uint64(PIT_DIVISOR) * 1000000000 + PIT_INPUT_HZ - 1) div PIT_INPUT_HZ; asuro_clock_res_get := WASI_ESUCCESS; end; diff --git a/src/app/wasm/app.wasm.pas b/src/app/wasm/app.wasm.pas index 4ae337e3..a7341afe 100644 --- a/src/app/wasm/app.wasm.pas +++ b/src/app/wasm/app.wasm.pas @@ -2,8 +2,7 @@ unit app.wasm; interface -uses - boot.mgr; +procedure init; implementation @@ -12,14 +11,15 @@ uses wasm.vm.io, wasm; -procedure init(); +var + initialized: boolean = false; + +procedure init; begin - { Let's test Wasuro! } + if initialized then exit; wasm.vm.io.io_set_writechar(@io.syslog.logchar); wasm.wasm_init; + initialized := true; end; -initialization - boot.mgr.registerBoot('app.wasm', @init, 'Initialize WASM VM Backend', BOOT_MGR_BARRIER_FINAL) - -end. \ No newline at end of file +end. diff --git a/src/app/wasm/app.wasm.runner.pas b/src/app/wasm/app.wasm.runner.pas index e617f9ee..8e807c3d 100644 --- a/src/app/wasm/app.wasm.runner.pas +++ b/src/app/wasm/app.wasm.runner.pas @@ -17,25 +17,36 @@ uses io.stdio, debug.tracer; procedure init; +procedure UnitTest; implementation uses - driver.storage.vfs, driver.storage.types, memory.heap, core.strings, io.syslog, core.util, arch.x86.util, + driver.storage.vfs, driver.storage.types, memory.heap, core.strings, io.syslog, core.util, arch.platform.cpu, proc.mgr, proc.types, driver.storage.filedispatch, - app.wasm.shim, app.wasm.io, app.wasm.cleanup, + app.wasm, app.wasm.shim, app.wasm.io, app.wasm.cleanup, + arch.platform.memory, wasm.types.stack, wasm.types.values, wasm, wasm.types.builtin, wasm.types.context, wasm.types.constants; const WASM_TICK_BUDGET = 1024; { opcodes per yield iteration } WASI_MODULE = 'wasi_snapshot_preview1'; +function memoryAvailable: boolean; +begin + { Wasuro allocates two fixed-capacity stacks before parsing. Include heap + region rounding and working space; reject low-memory launches up front. } + memoryAvailable := arch.platform.memory.freePhysicalBytes > + uint64(2) * DEFAULT_STACK_SIZE * sizeof(TWASMStackEntry) + + uint64(4) * PAGE_SIZE_LMM; +end; + { ---- Param serialization ---- } { Serialize a PParamList into a flat null-terminated buffer stored in the shim. Each param becomes a null-terminated string packed consecutively. Sets shim^.ArgCount, ArgBuf, ArgBufSize. } -procedure serializeParams(shim : PProcessWASMShim; params : PParamList); +function serializeParams(shim : PProcessWASMShim; params : PParamList): boolean; var p : PParamList; total : uint32; @@ -43,6 +54,7 @@ var slen : uint32; dest : uint32; begin + serializeParams := false; { First pass: count params and total buffer size } count := 0; total := 0; @@ -50,6 +62,8 @@ begin while p <> nil do begin if p^.Param <> nil then begin slen := core.strings.stringSize(p^.Param); + if (slen = High(uint32)) or (total > High(uint32) - slen - 1) then + exit; total := total + slen + 1; { string + null terminator } inc(count); end; @@ -61,24 +75,26 @@ begin if (count = 0) or (total = 0) then begin shim^.ArgBuf := nil; + serializeParams := true; exit; end; { Allocate and fill } shim^.ArgBuf := pchar(kalloc(total)); - memset(uint32(shim^.ArgBuf), 0, total); + if shim^.ArgBuf = nil then exit; dest := 0; p := params; while p <> nil do begin if p^.Param <> nil then begin slen := core.strings.stringSize(p^.Param); - memcpy(uint32(p^.Param), uint32(shim^.ArgBuf) + dest, slen); + memcpy(PtrUInt(p^.Param), PtrUInt(shim^.ArgBuf) + dest, slen); dest := dest + slen; shim^.ArgBuf[dest] := #0; { null terminator } inc(dest); end; p := p^.Next; end; + serializeParams := true; end; { ---- Process entry point ---- } @@ -114,12 +130,12 @@ begin exit; { Execute a batch of opcodes } - app.wasm.io.setActiveShim(shim); + for i := 1 to WASM_TICK_BUDGET do begin alive := wasm_tick(wctx); if not alive then break; end; - app.wasm.io.clearActiveShim; + { Yield to scheduler after each batch } proc.mgr.proc_yield; @@ -158,6 +174,11 @@ begin debug.tracer.push_trace('wasmrunner.handler'); wasm_file_handler := 0; + if not memoryAvailable then begin + io.stdio.bufWriteStrLn(stderr_buf, 'WASM: insufficient memory for VM stacks'); + exit; + end; + { 1. Open file once — omRead pre-loads the data and gives us the size } err := eNone; fh := driver.storage.vfs.OpenFile(path, omRead, @err); @@ -184,35 +205,36 @@ begin bytesRead := driver.storage.vfs.ReadFile(fh, 0, buf, fsize); driver.storage.vfs.CloseFile(fh); - if bytesRead = 0 then begin + if bytesRead <> fsize then begin kfree(void(buf)); io.stdio.bufWriteStrLn(stderr_buf, 'WASM: file read failed'); exit; end; - { 3. Parse the WASM binary } - wctx := wasm_load(buf, TWASMPUInt8(uint32(buf) + bytesRead)); - if (wctx = nil) or (not wctx^.ValidBinary) then begin - kfree(void(buf)); - io.stdio.bufWriteStrLn(stderr_buf, 'WASM: invalid binary'); - exit; - end; - - { 4. Create the shim } + { Establish ownership before parsing so every failure uses one cleanup. } shim := PProcessWASMShim(kalloc(SizeOf(TProcessWASMShim))); if shim = nil then begin kfree(void(buf)); io.stdio.bufWriteStrLn(stderr_buf, 'WASM: out of memory (shim)'); exit; end; - memset(uint32(shim), 0, SizeOf(TProcessWASMShim)); - shim^.WASMCtx := wctx; shim^.FileBuffer := buf; shim^.FileSize := bytesRead; shim^.FaultKind := wfNone; + wctx := wasm_load(buf, TWASMPUInt8(PtrUInt(buf) + bytesRead)); + shim^.WASMCtx := wctx; + if (wctx = nil) or (not wctx^.ValidBinary) then begin + app.wasm.cleanup.wasm_resource_cleanup(void(shim)); + io.stdio.bufWriteStrLn(stderr_buf, 'WASM: invalid binary'); + exit; + end; { 5. Serialize command-line params into the shim } - serializeParams(shim, params); + if not serializeParams(shim, params) then begin + app.wasm.cleanup.wasm_resource_cleanup(void(shim)); + io.stdio.bufWriteStrLn(stderr_buf, 'WASM: cannot allocate arguments'); + exit; + end; { 6. Wire up WASI hooks to our IO bridge } wasm_set_fd_write(wctx, @app.wasm.io.asuro_fd_write); @@ -234,11 +256,9 @@ begin wasm_register_wasi_preview1(wctx); { 7. Create the process } - processCtx := proc.mgr.create('WASM', @wasm_entry, void(shim), 5); + processCtx := proc.mgr.createSuspended('WASM', @wasm_entry, void(shim), 5); if processCtx = nil then begin - kfree(void(buf)); - if shim^.ArgBuf <> nil then kfree(void(shim^.ArgBuf)); - kfree(void(shim)); + app.wasm.cleanup.wasm_resource_cleanup(void(shim)); io.stdio.bufWriteStrLn(stderr_buf, 'WASM: failed to create process'); exit; end; @@ -255,10 +275,72 @@ begin proc.mgr.bindResource(processCtx, rkCustom, void(shim), @app.wasm.cleanup.wasm_resource_cleanup); wasm_file_handler := processCtx^.ProcessID; + processCtx^.State := psReady; end; { ---- Initialisation ---- } +procedure UnitTest; +const + { (module (import "wasi_snapshot_preview1" "proc_exit" (func (param i32))) + (func (export "_start") i32.const 42 call 0)) } + binary: array[0..81] of uint8 = ( + $00,$61,$73,$6D,$01,$00,$00,$00, + $01,$08,$02,$60,$01,$7F,$00,$60,$00,$00, + $02,$24,$01,$16, + $77,$61,$73,$69,$5F,$73,$6E,$61,$70,$73,$68,$6F,$74,$5F,$70,$72,$65,$76,$69,$65,$77,$31, + $09,$70,$72,$6F,$63,$5F,$65,$78,$69,$74,$00,$00, + $03,$02,$01,$01, + $07,$0A,$01,$06,$5F,$73,$74,$61,$72,$74,$00,$01, + $0A,$08,$01,$06,$00,$41,$2A,$10,$00,$0B); +var + shim: PProcessWASMShim; + previousLocal: void; + previousExit, steps: uint32; + ok: boolean; +begin + if not memoryAvailable then begin + io.syslog.logln('WASM', 'SKIP: insufficient memory for native WASM execution test'); + exit; + end; + shim := PProcessWASMShim(kalloc(sizeof(TProcessWASMShim))); + if shim = nil then exit; + shim^.FileBuffer := puint8(kalloc(sizeof(binary))); + if shim^.FileBuffer = nil then begin + kfree(void(shim)); + exit; + end; + memcpy(PtrUInt(@binary[0]), PtrUInt(shim^.FileBuffer), sizeof(binary)); + shim^.WASMCtx := wasm_load(shim^.FileBuffer, + TWASMPUInt8(PtrUInt(shim^.FileBuffer) + sizeof(binary))); + ok := shim^.WASMCtx <> nil; + if ok then ok := shim^.WASMCtx^.ValidBinary; + if ok then begin + previousLocal := proc.mgr.CurrentProcess^.Local; + previousExit := proc.mgr.CurrentProcess^.ExitCode; + shim^.ProcessCtx := proc.mgr.CurrentProcess; + proc.mgr.CurrentProcess^.Local := void(shim); + wasm_set_proc_exit(shim^.WASMCtx, @app.wasm.io.asuro_proc_exit); + wasm_register_wasi_preview1(shim^.WASMCtx); + ok := wasm_prepare_start(shim^.WASMCtx); + steps := 0; + while ok and shim^.WASMCtx^.ExecutionState.Running and (steps < 16) do begin + wasm_tick(shim^.WASMCtx); + inc(steps); + end; + ok := ok and (not shim^.WASMCtx^.ExecutionState.Running) and + (shim^.WASMCtx^.ExitCode = 42) and + (proc.mgr.CurrentProcess^.ExitCode = 42); + proc.mgr.CurrentProcess^.Local := previousLocal; + proc.mgr.CurrentProcess^.ExitCode := previousExit; + end; + app.wasm.cleanup.wasm_resource_cleanup(void(shim)); + if ok then + io.syslog.logln('WASM', 'PASS: native WASM executes WASI proc_exit and releases its context') + else + io.syslog.logln('WASM', 'FAIL: native WASM execution'); +end; + procedure init; var magic : array[0..3] of uint8; @@ -267,7 +349,7 @@ begin io.syslog.logln('WASMRUNNER', 'INIT BEGIN.'); { One-time WASURO VM initialisation } - wasm_init; + app.wasm.init; { Register WASM magic ($00 $61 $73 $6D = "\0asm") with file dispatcher } magic[0] := $00; diff --git a/src/driver/storage/ctl/driver.storage.ctl.ram.pas b/src/driver/storage/ctl/driver.storage.ctl.ram.pas index fc50021b..8e12ec92 100644 --- a/src/driver/storage/ctl/driver.storage.ctl.ram.pas +++ b/src/driver/storage/ctl/driver.storage.ctl.ram.pas @@ -36,7 +36,7 @@ procedure init; implementation uses - core.strings, memory.heap, core.ds.lists, core.util, arch.x86.util; + core.strings, memory.heap, core.ds.lists, core.util, arch.platform.cpu; const MAX_RAM_FILES = 32; @@ -53,6 +53,7 @@ var Files : array[0..MAX_RAM_FILES-1] of TRAMFile; FileCount : uint32; RAMVolume : TStorage_Volume; + RAMDevice : PStorage_Device; RAMFS : TFilesystem; { ---- Internal helpers ---- } @@ -75,7 +76,7 @@ end; function stripLeadingSlash(path : pchar) : pchar; begin if (path <> nil) and (path[0] = '/') then - stripLeadingSlash := pchar(uint32(path) + 1) + stripLeadingSlash := pchar(PtrUInt(path) + 1) else stripLeadingSlash := path; end; @@ -103,7 +104,7 @@ begin avail := Files[idx].Size - offset; if byteCount < avail then copyLen := byteCount else copyLen := avail; - memcpy(uint32(Files[idx].Data) + offset, uint32(buffer), copyLen); + memcpy(PtrUInt(Files[idx].Data) + offset, PtrUInt(buffer), copyLen); rd_ReadOffset := copyLen; end; @@ -193,8 +194,9 @@ begin if (data = nil) or (size = 0) then exit; buf := puint8(kalloc(size)); if buf = nil then exit; - memcpy(uint32(data), uint32(buf), size); + memcpy(PtrUInt(data), PtrUInt(buf), size); storeFileCopy := storeFile(name, buf, size); + if not storeFileCopy then kfree(void(buf)); end; procedure removeFile(name : pchar; freeBuf : boolean); @@ -255,7 +257,7 @@ begin FileCount := 0; { Set up a minimal filesystem descriptor with our callbacks } - memset(uint32(@RAMFS), 0, sizeof(TFilesystem)); + memset(PtrUInt(@RAMFS), 0, sizeof(TFilesystem)); RAMFS.sName := 'ramfs'; RAMFS.readDirCallback := @rd_ReadDir; RAMFS.readOffsetCallback := @rd_ReadOffset; @@ -263,8 +265,14 @@ begin RAMFS.identifyCallback := @rd_Identify; { Set up a virtual volume backed by our filesystem } - memset(uint32(@RAMVolume), 0, sizeof(TStorage_Volume)); + memset(PtrUInt(@RAMVolume), 0, sizeof(TStorage_Volume)); RAMVolume.filesystem := @RAMFS; + RAMDevice := PStorage_Device(kalloc(sizeof(TStorage_Device))); + if RAMDevice = nil then exit; + RAMDevice^.controller := ControllerRAM; + RAMDevice^.sectorSize := 512; + RAMVolume.device := RAMDevice; + RAMVolume.sectorSize := 512; { Mount into VFS at /disk/ram } driver.storage.vfs.mountVolume('/disk/ram', @RAMVolume);