Skip to content

feat: enhance lowMemoryLimit to support larger than 64k memory limitation #2910

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cli/options.json
Original file line number Diff line number Diff line change
@@ -260,7 +260,7 @@
},
"lowMemoryLimit": {
"category": "Features",
"description": "Enforces very low (<64k) memory constraints.",
"description": "Enforces memory constraints.",
"default": 0,
"type": "i"
},
4 changes: 2 additions & 2 deletions src/compiler.ts
Original file line number Diff line number Diff line change
@@ -276,7 +276,7 @@ export class Options {
noUnsafe: bool = false;
/** If true, enables pedantic diagnostics. */
pedantic: bool = false;
/** Indicates a very low (<64k) memory limit. */
/** Indicates a memory limit. */
lowMemoryLimit: u32 = 0;
/** If true, exports the runtime helpers. */
exportRuntime: bool = false;
@@ -815,7 +815,7 @@ export class Compiler extends DiagnosticEmitter {
// check that we didn't exceed lowMemoryLimit already
let lowMemoryLimit32 = options.lowMemoryLimit;
if (lowMemoryLimit32) {
let lowMemoryLimit = i64_new(lowMemoryLimit32 & ~15);
let lowMemoryLimit = i64_new(lowMemoryLimit32);
if (i64_gt(memoryOffset, lowMemoryLimit)) {
this.error(
DiagnosticCode.Low_memory_limit_exceeded_by_static_data_0_1,
31 changes: 21 additions & 10 deletions std/assembly/rt/tlsf.ts
Original file line number Diff line number Diff line change
@@ -196,6 +196,11 @@ import { E_ALLOCATION_TOO_LARGE } from "../util/error";
);
}

// @ts-ignore: decorator
@inline function sizeRoundToPage(size: usize): i32 {
return <i32>((size + 0xffff) >>> 16);
}

/** Inserts a previously used block back into the free list. */
function insertBlock(root: Root, block: Block): void {
if (DEBUG) assert(block); // cannot be null
@@ -427,10 +432,6 @@ function addMemory(root: Root, start: usize, endU64: u64): bool {

/** Grows memory to fit at least another block of the specified size. */
function growMemory(root: Root, size: usize): void {
if (ASC_LOW_MEMORY_LIMIT) {
unreachable();
return;
}
// Here, both rounding performed in searchBlock ...
if (size >= SB_SIZE) {
size = roundSize(size);
@@ -439,13 +440,23 @@ function growMemory(root: Root, size: usize): void {
// to merge with the tail block, that's one time, otherwise it's two times.
let pagesBefore = memory.size();
size += BLOCK_OVERHEAD << usize((<usize>pagesBefore << 16) - BLOCK_OVERHEAD != changetype<usize>(GETTAIL(root)));
let pagesNeeded = <i32>(((size + 0xffff) & ~0xffff) >>> 16);
if (ASC_LOW_MEMORY_LIMIT) {
if ((<usize>pagesBefore << 16) + size > <usize>ASC_LOW_MEMORY_LIMIT) unreachable();
}
let pagesNeeded = sizeRoundToPage(size);
let pagesWanted = max(pagesBefore, pagesNeeded); // double memory
if (ASC_LOW_MEMORY_LIMIT) {
pagesWanted = min(pagesWanted, sizeRoundToPage(ASC_LOW_MEMORY_LIMIT) - pagesBefore);
}
if (memory.grow(pagesWanted) < 0) {
if (memory.grow(pagesNeeded) < 0) unreachable();
}
let pagesAfter = memory.size();
addMemory(root, <usize>pagesBefore << 16, <u64>pagesAfter << 16);
if (ASC_LOW_MEMORY_LIMIT) {
addMemory(root, <usize>pagesBefore << 16, min(<u64>pagesAfter << 16, <u64>ASC_LOW_MEMORY_LIMIT & ~AL_MASK));
} else {
addMemory(root, <usize>pagesBefore << 16, <u64>pagesAfter << 16);
}
}

/** Computes the size (excl. header) of a block. */
@@ -467,7 +478,7 @@ function initialize(): void {
if (isDefined(ASC_RTRACE)) oninit(__heap_base);
let rootOffset = (__heap_base + AL_MASK) & ~AL_MASK;
let pagesBefore = memory.size();
let pagesNeeded = <i32>((((rootOffset + ROOT_SIZE) + 0xffff) & ~0xffff) >>> 16);
let pagesNeeded = sizeRoundToPage(rootOffset + ROOT_SIZE);
if (pagesNeeded > pagesBefore && memory.grow(pagesNeeded - pagesBefore) < 0) unreachable();
let root = changetype<Root>(rootOffset);
root.flMap = 0;
@@ -480,9 +491,9 @@ function initialize(): void {
}
let memStart = rootOffset + ROOT_SIZE;
if (ASC_LOW_MEMORY_LIMIT) {
const memEnd = <u64>ASC_LOW_MEMORY_LIMIT & ~AL_MASK;
if (memStart <= memEnd) addMemory(root, memStart, memEnd);
else unreachable(); // low memory limit already exceeded
const limitedEnd: u64 = min(<u64>memory.size() << 16, <u64>ASC_LOW_MEMORY_LIMIT & ~AL_MASK);
if (<u64>memStart > limitedEnd) unreachable(); // low memory limit already exceeded
addMemory(root, memStart, limitedEnd);
} else {
addMemory(root, memStart, <u64>memory.size() << 16);
}
29 changes: 13 additions & 16 deletions tests/compiler.js
Original file line number Diff line number Diff line change
@@ -380,14 +380,13 @@ async function runTest(basename) {
if (config.skipInstantiate) {
instantiateDebug.end(SKIPPED);
} else {

if (!await testInstantiate(debugBuffer, glue, stderr)) {
if (!await testInstantiate(debugBuffer, glue, stderr, !!config.expectedFailed)) {
instantiateDebug.end(FAILURE);
return prepareResult(FAILURE, "instantiate error (debug)");
}
instantiateDebug.end(SUCCESS);
const instantiateRelease = section("instantiate release");
if (!await testInstantiate(releaseBuffer, glue, stderr)) {
if (!await testInstantiate(releaseBuffer, glue, stderr, !!config.expectedFailed)) {
instantiateRelease.end(FAILURE);
return prepareResult(FAILURE, "instantiate error (release)");
}
@@ -423,7 +422,7 @@ async function runTest(basename) {

const rtracedBuffer = stdout.toBuffer();
const instantiateRtrace = section("instantiate rtrace");
if (!await testInstantiate(rtracedBuffer, glue, stderr)) {
if (!await testInstantiate(rtracedBuffer, glue, stderr, !!config.expectedFailed)) {
instantiateRtrace.end(FAILURE);
return prepareResult(FAILURE, "rtrace error");
}
@@ -434,7 +433,7 @@ async function runTest(basename) {
}

// Tests if instantiation of a module succeeds
async function testInstantiate(binaryBuffer, glue, stderr) {
async function testInstantiate(binaryBuffer, glue, stderr, expectedFailed) {
let failed = false;
try {
const memory = new WebAssembly.Memory({ initial: 10 });
@@ -539,23 +538,21 @@ async function testInstantiate(binaryBuffer, glue, stderr) {
failed = true;
console.log(` memory leak detected: ${leakCount} leaking`);
}
if (!failed) {
if (rtrace.active) {
console.log(" " +
rtrace.allocCount + " allocs, " +
rtrace.freeCount + " frees, " +
rtrace.resizeCount + " resizes, " +
rtrace.moveCount + " moves"
);
}
return true;
if (rtrace.active) {
console.log(" " +
rtrace.allocCount + " allocs, " +
rtrace.freeCount + " frees, " +
rtrace.resizeCount + " resizes, " +
rtrace.moveCount + " moves"
);
}
} catch (err) {
failed = true;
stderr.write("---\n");
stderr.write(err.stack);
stderr.write("\n---\n");
}
return false;
return failed == expectedFailed;
}

// Evaluates the overall test result
Loading