Skip to content

Add builtins for LLVM Wasm intrinsics #5507

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

Closed
wants to merge 10 commits into from
Closed
Show file tree
Hide file tree
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
43 changes: 43 additions & 0 deletions doc/langref.html.in
Original file line number Diff line number Diff line change
Expand Up @@ -7643,6 +7643,49 @@ mem.copy(u8, dest[0..byte_count], source[0..byte_count]);{#endsyntax#}</pre>
mem.set(u8, dest, c);{#endsyntax#}</pre>
{#header_close#}

{#header_open|@wasmMemorySize#}
<pre>{#syntax#}@wasmMemorySize(index: u32) u32{#endsyntax#}</pre>
<p>
This function returns the size of the Wasm memory identified by {#syntax#}index{#endsyntax#} as
an unsigned value in units of Wasm pages. Note that each Wasm page is 64KB in size.
</p>
<p>
This function is a low level intrinsic with no safety mechanisms usually useful for allocator
designers targeting Wasm. So unless you are writing a new allocator from scratch, you should use
something like {#syntax#}@import("std").heap.WasmPageAllocator{#endsyntax#}.
</p>
{#see_also|@wasmMemoryGrow#}
{#header_close#}

{#header_open|@wasmMemoryGrow#}
<pre>{#syntax#}@wasmMemoryGrow(index: u32, delta: u32) i32{#endsyntax#}</pre>
<p>
This function increases the size of the Wasm memory identified by {#syntax#}index{#endsyntax#} by
{#syntax#}delta{#endsyntax#} in units of unsigned number of Wasm pages. Note that each Wasm page
is 64KB in size. On success, returns previous memory size; on failure, if the allocation fails,
returns -1.
</p>
<p>
This function is a low level intrinsic with no safety mechanisms usually useful for allocator
designers targeting Wasm. So unless you are writing a new allocator from scratch, you should use
something like {#syntax#}@import("std").heap.WasmPageAllocator{#endsyntax#}.
</p>
{#code_begin|test#}
const std = @import("std");
const builtin = @import("builtin");
const assert = std.debug.assert;

test "@wasmMemoryGrow" {
if (builtin.arch != .wasm32) return error.SkipZigTest;

var prev = @wasmMemorySize(0);
assert(prev == @wasmMemoryGrow(0, 1));
assert(prev + 1 == @wasmMemorySize(0));
}
{#code_end#}
{#see_also|@wasmMemorySize#}
{#header_close#}

{#header_open|@mod#}
<pre>{#syntax#}@mod(numerator: T, denominator: T) T{#endsyntax#}</pre>
<p>
Expand Down
7 changes: 1 addition & 6 deletions lib/std/heap.zig
Original file line number Diff line number Diff line change
Expand Up @@ -250,11 +250,6 @@ const PageAllocator = struct {
}
};

// TODO Exposed LLVM intrinsics is a bug
// See: https://github.com/ziglang/zig/issues/2291
extern fn @"llvm.wasm.memory.size.i32"(u32) u32;
extern fn @"llvm.wasm.memory.grow.i32"(u32, u32) i32;

const WasmPageAllocator = struct {
comptime {
if (!std.Target.current.isWasm()) {
Expand Down Expand Up @@ -357,7 +352,7 @@ const WasmPageAllocator = struct {
return idx + extendedOffset();
}

const prev_page_count = @"llvm.wasm.memory.grow.i32"(0, @intCast(u32, page_count));
const prev_page_count = @wasmMemoryGrow(0, @intCast(u32, page_count));
if (prev_page_count <= 0) {
return error.OutOfMemory;
}
Expand Down
34 changes: 34 additions & 0 deletions src/all_types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1825,6 +1825,8 @@ enum BuiltinFnId {
BuiltinFnIdAs,
BuiltinFnIdCall,
BuiltinFnIdBitSizeof,
BuiltinFnIdWasmMemorySize,
BuiltinFnIdWasmMemoryGrow,
};

struct BuiltinFnEntry {
Expand Down Expand Up @@ -2075,6 +2077,8 @@ struct CodeGen {
LLVMValueRef err_name_table;
LLVMValueRef safety_crash_err_fn;
LLVMValueRef return_err_fn;
LLVMValueRef wasm_memory_size;
LLVMValueRef wasm_memory_grow;
LLVMTypeRef anyframe_fn_type;

// reminder: hash tables must be initialized before use
Expand Down Expand Up @@ -2748,6 +2752,8 @@ enum IrInstSrcId {
IrInstSrcIdResume,
IrInstSrcIdSpillBegin,
IrInstSrcIdSpillEnd,
IrInstSrcIdWasmMemorySize,
IrInstSrcIdWasmMemoryGrow,
};

// ir_render_* functions in codegen.cpp consume Gen instructions and produce LLVM IR.
Expand Down Expand Up @@ -2840,6 +2846,8 @@ enum IrInstGenId {
IrInstGenIdVectorExtractElem,
IrInstGenIdAlloca,
IrInstGenIdConst,
IrInstGenIdWasmMemorySize,
IrInstGenIdWasmMemoryGrow,
};

// Common fields between IrInstSrc and IrInstGen. This allows future passes
Expand Down Expand Up @@ -3727,6 +3735,32 @@ struct IrInstGenMemcpy {
IrInstGen *count;
};

struct IrInstSrcWasmMemorySize {
IrInstSrc base;

IrInstSrc *index;
};

struct IrInstGenWasmMemorySize {
IrInstGen base;

IrInstGen *index;
};

struct IrInstSrcWasmMemoryGrow {
IrInstSrc base;

IrInstSrc *index;
IrInstSrc *delta;
};

struct IrInstGenWasmMemoryGrow {
IrInstGen base;

IrInstGen *index;
IrInstGen *delta;
};

struct IrInstSrcSlice {
IrInstSrc base;

Expand Down
54 changes: 54 additions & 0 deletions src/codegen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1045,6 +1045,37 @@ static void gen_assertion(CodeGen *g, PanicMsgId msg_id, IrInstGen *source_instr
return gen_assertion_scope(g, msg_id, source_instruction->base.scope);
}

static LLVMValueRef gen_wasm_memory_size(CodeGen *g) {
if (g->wasm_memory_size)
return g->wasm_memory_size;

// TODO adjust for wasm64 as well
// declare i32 @llvm.wasm.memory.size.i32(i32) nounwind readonly
LLVMTypeRef param_type = LLVMInt32Type();
LLVMTypeRef fn_type = LLVMFunctionType(LLVMInt32Type(), &param_type, 1, false);
g->wasm_memory_size = LLVMAddFunction(g->module, "llvm.wasm.memory.size.i32", fn_type);
assert(LLVMGetIntrinsicID(g->wasm_memory_size));

return g->wasm_memory_size;
}

static LLVMValueRef gen_wasm_memory_grow(CodeGen *g) {
if (g->wasm_memory_grow)
return g->wasm_memory_grow;

// TODO adjust for wasm64 as well
// declare i32 @llvm.wasm.memory.grow.i32(i32, i32) nounwind
LLVMTypeRef param_types[] = {
LLVMInt32Type(),
LLVMInt32Type(),
};
LLVMTypeRef fn_type = LLVMFunctionType(LLVMInt32Type(), param_types, 2, false);
g->wasm_memory_grow = LLVMAddFunction(g->module, "llvm.wasm.memory.grow.i32", fn_type);
assert(LLVMGetIntrinsicID(g->wasm_memory_grow));

return g->wasm_memory_grow;
}

static LLVMValueRef get_stacksave_fn_val(CodeGen *g) {
if (g->stacksave_fn_val)
return g->stacksave_fn_val;
Expand Down Expand Up @@ -5588,6 +5619,23 @@ static LLVMValueRef ir_render_memcpy(CodeGen *g, IrExecutableGen *executable, Ir
return nullptr;
}

static LLVMValueRef ir_render_wasm_memory_size(CodeGen *g, IrExecutableGen *executable, IrInstGenWasmMemorySize *instruction) {
// TODO adjust for wasm64
LLVMValueRef param = ir_llvm_value(g, instruction->index);
LLVMValueRef val = LLVMBuildCall(g->builder, gen_wasm_memory_size(g), &param, 1, "");
return val;
}

static LLVMValueRef ir_render_wasm_memory_grow(CodeGen *g, IrExecutableGen *executable, IrInstGenWasmMemoryGrow *instruction) {
// TODO adjust for wasm64
LLVMValueRef params[] = {
ir_llvm_value(g, instruction->index),
ir_llvm_value(g, instruction->delta),
};
LLVMValueRef val = LLVMBuildCall(g->builder, gen_wasm_memory_grow(g), params, 2, "");
return val;
}

static LLVMValueRef ir_render_slice(CodeGen *g, IrExecutableGen *executable, IrInstGenSlice *instruction) {
Error err;

Expand Down Expand Up @@ -6798,6 +6846,10 @@ static LLVMValueRef ir_render_instruction(CodeGen *g, IrExecutableGen *executabl
return ir_render_splat(g, executable, (IrInstGenSplat *) instruction);
case IrInstGenIdVectorExtractElem:
return ir_render_vector_extract_elem(g, executable, (IrInstGenVectorExtractElem *) instruction);
case IrInstGenIdWasmMemorySize:
return ir_render_wasm_memory_size(g, executable, (IrInstGenWasmMemorySize *) instruction);
case IrInstGenIdWasmMemoryGrow:
return ir_render_wasm_memory_grow(g, executable, (IrInstGenWasmMemoryGrow *) instruction);
}
zig_unreachable();
}
Expand Down Expand Up @@ -8660,6 +8712,8 @@ static void define_builtin_fns(CodeGen *g) {
create_builtin_fn(g, BuiltinFnIdAs, "as", 2);
create_builtin_fn(g, BuiltinFnIdCall, "call", 3);
create_builtin_fn(g, BuiltinFnIdBitSizeof, "bitSizeOf", 1);
create_builtin_fn(g, BuiltinFnIdWasmMemorySize, "wasmMemorySize", 1);
create_builtin_fn(g, BuiltinFnIdWasmMemoryGrow, "wasmMemoryGrow", 2);
}

static const char *bool_to_str(bool b) {
Expand Down
Loading