Commit ce3f0077cf

Jakub Konka <kubkon@jakubkonka.com>
2020-05-31 21:11:06
Add builtin for llvm.wasm.memory.size.i32 instrinsic
This will allow the developer to poll the runtime for currently allocated memory in the number of Wasm pages. Typical usage: ```zig var wasm_pages = @wasmMemorySize(); @import("std").debug.assert(wasm_pages > 0); ```
1 parent 12051b0
src/all_types.hpp
@@ -1825,6 +1825,7 @@ enum BuiltinFnId {
     BuiltinFnIdAs,
     BuiltinFnIdCall,
     BuiltinFnIdBitSizeof,
+    BuiltinFnIdWasmMemorySize,
 };
 
 struct BuiltinFnEntry {
@@ -2075,6 +2076,7 @@ struct CodeGen {
     LLVMValueRef err_name_table;
     LLVMValueRef safety_crash_err_fn;
     LLVMValueRef return_err_fn;
+    LLVMValueRef wasm_memory_size;
     LLVMTypeRef anyframe_fn_type;
 
     // reminder: hash tables must be initialized before use
@@ -2748,6 +2750,7 @@ enum IrInstSrcId {
     IrInstSrcIdResume,
     IrInstSrcIdSpillBegin,
     IrInstSrcIdSpillEnd,
+    IrInstSrcIdWasmMemorySize,
 };
 
 // ir_render_* functions in codegen.cpp consume Gen instructions and produce LLVM IR.
@@ -2840,6 +2843,7 @@ enum IrInstGenId {
     IrInstGenIdVectorExtractElem,
     IrInstGenIdAlloca,
     IrInstGenIdConst,
+    IrInstGenIdWasmMemorySize,
 };
 
 // Common fields between IrInstSrc and IrInstGen. This allows future passes
@@ -3727,6 +3731,14 @@ struct IrInstGenMemcpy {
     IrInstGen *count;
 };
 
+struct IrInstSrcWasmMemorySize {
+  IrInstSrc base;
+};
+
+struct IrInstGenWasmMemorySize {
+  IrInstGen base;
+};
+
 struct IrInstSrcSlice {
     IrInstSrc base;
 
src/codegen.cpp
@@ -1045,6 +1045,19 @@ 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;
+
+    // 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 get_stacksave_fn_val(CodeGen *g) {
     if (g->stacksave_fn_val)
         return g->stacksave_fn_val;
@@ -5588,6 +5601,17 @@ 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) {
+    // When Wasm lands multi-memory support, we can relax this to permit the user specify
+    // memory index to inquire about. For now, we pass in the recommended default of index 0.
+    //
+    // More info:
+    // https://github.com/sunfishcode/wasm-reference-manual/blob/master/WebAssembly.md#current-linear-memory-size
+    LLVMValueRef zero = LLVMConstNull(g->builtin_types.entry_i32->llvm_type);
+    LLVMValueRef val = LLVMBuildCall(g->builder, gen_wasm_memory_size(g), &zero, 1, "");
+    return val;
+}
+
 static LLVMValueRef ir_render_slice(CodeGen *g, IrExecutableGen *executable, IrInstGenSlice *instruction) {
     Error err;
 
@@ -6798,6 +6822,8 @@ 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);
     }
     zig_unreachable();
 }
@@ -8660,6 +8686,7 @@ 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", 0);
 }
 
 static const char *bool_to_str(bool b) {
src/ir.cpp
@@ -556,6 +556,8 @@ static void destroy_instruction_src(IrInstSrc *inst) {
             return heap::c_allocator.destroy(reinterpret_cast<IrInstSrcSpillEnd *>(inst));
         case IrInstSrcIdCallArgs:
             return heap::c_allocator.destroy(reinterpret_cast<IrInstSrcCallArgs *>(inst));
+        case IrInstSrcIdWasmMemorySize:
+            return heap::c_allocator.destroy(reinterpret_cast<IrInstSrcWasmMemorySize *>(inst));
     }
     zig_unreachable();
 }
@@ -736,6 +738,8 @@ void destroy_instruction_gen(IrInstGen *inst) {
             return heap::c_allocator.destroy(reinterpret_cast<IrInstGenNegation *>(inst));
         case IrInstGenIdNegationWrapping:
             return heap::c_allocator.destroy(reinterpret_cast<IrInstGenNegationWrapping *>(inst));
+        case IrInstGenIdWasmMemorySize:
+            return heap::c_allocator.destroy(reinterpret_cast<IrInstGenWasmMemorySize *>(inst));
     }
     zig_unreachable();
 }
@@ -1334,6 +1338,10 @@ static constexpr IrInstSrcId ir_inst_id(IrInstSrcBoolNot *) {
     return IrInstSrcIdBoolNot;
 }
 
+static constexpr IrInstSrcId ir_inst_id(IrInstSrcWasmMemorySize *) {
+    return IrInstSrcIdWasmMemorySize;
+}
+
 static constexpr IrInstSrcId ir_inst_id(IrInstSrcMemset *) {
     return IrInstSrcIdMemset;
 }
@@ -1955,6 +1963,10 @@ static constexpr IrInstGenId ir_inst_id(IrInstGenConst *) {
     return IrInstGenIdConst;
 }
 
+static constexpr IrInstGenId ir_inst_id(IrInstGenWasmMemorySize *) {
+  return IrInstGenIdWasmMemorySize;
+}
+
 template<typename T>
 static T *ir_create_instruction(IrBuilderSrc *irb, Scope *scope, AstNode *source_node) {
     T *special_instruction = heap::c_allocator.create<T>();
@@ -3642,6 +3654,20 @@ static IrInstGen *ir_build_bool_not_gen(IrAnalyze *ira, IrInst *source_instr, Ir
     return &instruction->base;
 }
 
+static IrInstSrc *ir_build_wasm_memory_size_src(IrBuilderSrc *irb, Scope *scope, AstNode *source_node) {
+    IrInstSrcWasmMemorySize *instruction = ir_build_instruction<IrInstSrcWasmMemorySize>(irb, scope, source_node);
+
+    return &instruction->base;
+}
+
+static IrInstGen *ir_build_wasm_memory_size_gen(IrAnalyze *ira, IrInst *source_instr) {
+    IrInstGenWasmMemorySize *instruction = ir_build_inst_gen<IrInstGenWasmMemorySize>(&ira->new_irb,
+            source_instr->scope, source_instr->source_node);
+    instruction->base.value->type = ira->codegen->builtin_types.entry_i32;
+
+    return &instruction->base;
+}
+
 static IrInstSrc *ir_build_memset_src(IrBuilderSrc *irb, Scope *scope, AstNode *source_node,
     IrInstSrc *dest_ptr, IrInstSrc *byte, IrInstSrc *count)
 {
@@ -6754,6 +6780,11 @@ static IrInstSrc *ir_gen_builtin_fn_call(IrBuilderSrc *irb, Scope *scope, AstNod
                 IrInstSrc *ir_memset = ir_build_memset_src(irb, scope, node, arg0_value, arg1_value, arg2_value);
                 return ir_lval_wrap(irb, scope, ir_memset, lval, result_loc);
             }
+        case BuiltinFnIdWasmMemorySize:
+            {
+                IrInstSrc *ir_wasm_memory_size = ir_build_wasm_memory_size_src(irb, scope, node);
+                return ir_lval_wrap(irb, scope, ir_wasm_memory_size, lval, result_loc);
+            }
         case BuiltinFnIdField:
             {
                 AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
@@ -27651,6 +27682,16 @@ static IrInstGen *ir_analyze_instruction_has_field(IrAnalyze *ira, IrInstSrcHasF
     return ir_const_bool(ira, &instruction->base.base, result);
 }
 
+static IrInstGen *ir_analyze_instruction_wasm_memory_size(IrAnalyze *ira, IrInstSrcWasmMemorySize *instruction) {
+    if (!target_is_wasm(ira->codegen->zig_target)) {
+        ir_add_error_node(ira, instruction->base.base.source_node,
+            buf_sprintf("@wasmMemorySize is a wasm feature only"));
+        return ira->codegen->invalid_inst_gen;
+    }
+
+    return ir_build_wasm_memory_size_gen(ira, &instruction->base.base);
+}
+
 static IrInstGen *ir_analyze_instruction_breakpoint(IrAnalyze *ira, IrInstSrcBreakpoint *instruction) {
     return ir_build_breakpoint_gen(ira, &instruction->base.base);
 }
@@ -30892,6 +30933,8 @@ static IrInstGen *ir_analyze_instruction_base(IrAnalyze *ira, IrInstSrc *instruc
             return ir_analyze_instruction_spill_begin(ira, (IrInstSrcSpillBegin *)instruction);
         case IrInstSrcIdSpillEnd:
             return ir_analyze_instruction_spill_end(ira, (IrInstSrcSpillEnd *)instruction);
+        case IrInstSrcIdWasmMemorySize:
+            return ir_analyze_instruction_wasm_memory_size(ira, (IrInstSrcWasmMemorySize *)instruction);
     }
     zig_unreachable();
 }
@@ -31117,6 +31160,7 @@ bool ir_inst_gen_has_side_effects(IrInstGen *instruction) {
         case IrInstGenIdBinaryNot:
         case IrInstGenIdNegation:
         case IrInstGenIdNegationWrapping:
+        case IrInstGenIdWasmMemorySize:
             return false;
 
         case IrInstGenIdAsm:
@@ -31283,6 +31327,7 @@ bool ir_inst_src_has_side_effects(IrInstSrc *instruction) {
         case IrInstSrcIdHasDecl:
         case IrInstSrcIdAlloca:
         case IrInstSrcIdSpillEnd:
+        case IrInstSrcIdWasmMemorySize:
             return false;
 
         case IrInstSrcIdAsm:
src/ir_print.cpp
@@ -321,6 +321,8 @@ const char* ir_inst_src_type_str(IrInstSrcId id) {
             return "SrcSpillBegin";
         case IrInstSrcIdSpillEnd:
             return "SrcSpillEnd";
+        case IrInstSrcIdWasmMemorySize:
+            return "SrcWasmMemorySize";
     }
     zig_unreachable();
 }
@@ -501,6 +503,8 @@ const char* ir_inst_gen_type_str(IrInstGenId id) {
             return "GenNegation";
         case IrInstGenIdNegationWrapping:
             return "GenNegationWrapping";
+        case IrInstGenIdWasmMemorySize:
+            return "GenWasmMemorySize";
     }
     zig_unreachable();
 }
@@ -1708,6 +1712,14 @@ static void ir_print_bool_not(IrPrintGen *irp, IrInstGenBoolNot *instruction) {
     ir_print_other_inst_gen(irp, instruction->value);
 }
 
+static void ir_print_wasm_memory_size(IrPrintSrc *irp, IrInstSrcWasmMemorySize *instruction) {
+    fprintf(irp->f, "@wasmMemorySize()");
+}
+
+static void ir_print_wasm_memory_size(IrPrintGen *irp, IrInstGenWasmMemorySize *instruction) {
+    fprintf(irp->f, "@wasmMemorySize()");
+}
+
 static void ir_print_memset(IrPrintSrc *irp, IrInstSrcMemset *instruction) {
     fprintf(irp->f, "@memset(");
     ir_print_other_inst_src(irp, instruction->dest_ptr);
@@ -2952,6 +2964,9 @@ static void ir_print_inst_src(IrPrintSrc *irp, IrInstSrc *instruction, bool trai
         case IrInstSrcIdClz:
             ir_print_clz(irp, (IrInstSrcClz *)instruction);
             break;
+        case IrInstSrcIdWasmMemorySize:
+            ir_print_wasm_memory_size(irp, (IrInstSrcWasmMemorySize *)instruction);
+            break;
     }
     fprintf(irp->f, "\n");
 }
@@ -3219,6 +3234,9 @@ static void ir_print_inst_gen(IrPrintGen *irp, IrInstGen *instruction, bool trai
         case IrInstGenIdNegationWrapping:
             ir_print_negation_wrapping(irp, (IrInstGenNegationWrapping *)instruction);
             break;
+        case IrInstGenIdWasmMemorySize:
+            ir_print_wasm_memory_size(irp, (IrInstGenWasmMemorySize *)instruction);
+            break;
     }
     fprintf(irp->f, "\n");
 }