diff --git a/vlib/builtin/wasm/alloc.v b/vlib/builtin/wasm/alloc.v index de8497d2b1..2082d86c6a 100644 --- a/vlib/builtin/wasm/alloc.v +++ b/vlib/builtin/wasm/alloc.v @@ -4,7 +4,7 @@ module builtin // Shitty `sbrk` basic `malloc` and `free` impl // TODO: implement pure V `walloc` later -__global g_heap_base = usize(__heap_base()) +__global g_heap_base = usize(vwasm_heap_base()) // malloc dynamically allocates a `n` bytes block of memory on the heap. // malloc returns a `byteptr` pointing to the memory address of the allocated space. diff --git a/vlib/builtin/wasm/builtin.v b/vlib/builtin/wasm/builtin.v index a40e39992e..7c3e25b378 100644 --- a/vlib/builtin/wasm/builtin.v +++ b/vlib/builtin/wasm/builtin.v @@ -1,24 +1,41 @@ module builtin -fn __heap_base() voidptr -fn __memory_size() usize -fn __memory_grow(size usize) usize -fn __memory_fill(dest &u8, value isize, size isize) -fn __memory_copy(dest &u8, src &u8, size isize) +// vwasm_heap_base returns the base address of the heap. +pub fn vwasm_heap_base() voidptr { + mut rval := unsafe { nil } + asm wasm { + global.get __heap_base + local.set rval + ; =r (rval) + } + return rval +} -// add doc comments for the below functions +// vwasm_heap_size returns the size of the main wasm memory in pages. +// Analagous to the `memory.size` instruction. +pub fn vwasm_memory_size() int { + mut rval := 0 + asm wasm { + memory.size + local.set rval + ; =r (rval) + } + return rval +} -// __reinterpret_f32_u32 converts a `u32` to a `f32` without changing the bit pattern. -pub fn __reinterpret_f32_u32(v f32) u32 - -// __reinterpret_u32_f32 converts a `f32` to a `u32` without changing the bit pattern. -pub fn __reinterpret_u32_f32(v u32) f32 - -// __reinterpret_f64_u64 converts a `u64` to a `f64` without changing the bit pattern. -pub fn __reinterpret_f64_u64(v f64) u64 - -// __reinterpret_u64_f64 converts a `f64` to a `u64` without changing the bit pattern. -pub fn __reinterpret_u64_f64(v u64) f64 +// vwasm_memory_grow grows the main wasm memory by `size` pages. +// Analagous to the `memory.grow` instruction. +pub fn vwasm_memory_grow(size int) int { + mut rval := 0 + asm wasm { + local.get size + memory.grow + local.set rval + ; =r (rval) + ; r (size) + } + return rval +} // vcalloc dynamically allocates a zeroed `n` bytes block of memory on the heap. // vcalloc returns a `byteptr` pointing to the memory address of the allocated space. @@ -33,7 +50,14 @@ pub fn vcalloc(n isize) &u8 { res := unsafe { malloc(n) } - __memory_fill(res, 0, n) + asm wasm { + local.get res + i32.const 0x0 + local.get n + memory.fill + ; ; r (res) + r (n) + } return res } @@ -48,7 +72,15 @@ pub fn isnil(v voidptr) bool { // The memory areas **CAN** overlap. vmemcpy returns a pointer to `dest`. [unsafe] pub fn vmemcpy(dest voidptr, const_src voidptr, n isize) voidptr { - __memory_copy(dest, const_src, n) + asm wasm { + local.get dest + local.get const_src + local.get n + memory.copy + ; ; r (dest) + r (const_src) + r (n) + } return dest } @@ -56,7 +88,15 @@ pub fn vmemcpy(dest voidptr, const_src voidptr, n isize) voidptr { // The memory areas **CAN** overlap. vmemmove returns a pointer to `dest`. [unsafe] pub fn vmemmove(dest voidptr, const_src voidptr, n isize) voidptr { - __memory_copy(dest, const_src, n) + asm wasm { + local.get dest + local.get const_src + local.get n + memory.copy + ; ; r (dest) + r (const_src) + r (n) + } return dest } @@ -64,6 +104,14 @@ pub fn vmemmove(dest voidptr, const_src voidptr, n isize) voidptr { // with the constant byte `c`. It returns a pointer to the memory area `s`. [unsafe] pub fn vmemset(s voidptr, c int, n isize) voidptr { - __memory_fill(s, c, n) + asm wasm { + local.get s + local.get c + local.get n + memory.fill + ; ; r (s) + r (c) + r (n) + } return s } diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index 8fe4306415..303a5963d3 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -2411,6 +2411,9 @@ pub fn all_registers(mut t Table, arch pref.Arch) map[string]ScopeObject { res[k] = v } } + .wasm32 { + // no registers + } else { // TODO panic('all_registers: unhandled arch') } diff --git a/vlib/v/fmt/asm.v b/vlib/v/fmt/asm.v index 4dfa17bb30..09d51531ba 100644 --- a/vlib/v/fmt/asm.v +++ b/vlib/v/fmt/asm.v @@ -12,7 +12,12 @@ fn (mut f Fmt) asm_stmt(stmt ast.AsmStmt) { } else if stmt.is_goto { f.write('goto ') } - f.writeln('${stmt.arch} {') + lit_arch := if stmt.arch == .wasm32 { + 'wasm' + } else { + stmt.arch.str() + } + f.writeln('${lit_arch} {') f.indent++ f.asm_templates(stmt.templates) @@ -54,7 +59,7 @@ fn (mut f Fmt) asm_arg(arg ast.AsmArg) { f.write(arg.val.str()) } string { - f.write(arg) + f.string_literal(ast.StringLiteral{ val: arg }) } ast.AsmAddressing { if arg.segment != '' { diff --git a/vlib/v/gen/wasm/asm.v b/vlib/v/gen/wasm/asm.v new file mode 100644 index 0000000000..d7d8eaaa00 --- /dev/null +++ b/vlib/v/gen/wasm/asm.v @@ -0,0 +1,889 @@ +// Copyright (c) 2023 l-m.dev. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module wasm + +import v.ast + +pub fn (mut g Gen) asm_call(node ast.AsmTemplate) { + // call 'main.test' + // call 'main.Struct.+' + // + // call 'wasi_unstable' 'proc_exit' + // call 'console' 'log' + + if node.args.len !in [1, 2] { + g.v_error('incorrect number of arguments to `${node.name}`', node.pos) + } + + arg0 := node.args[0] + sarg0 := if arg0 is string { + arg0 + } else if node.args.len == 1 { + g.v_error('`${node.name}` must accept a string to call', node.pos) + } else { + g.v_error('`${node.name}` must accept a namespace for call', node.pos) + } + + if node.args.len == 1 { + g.func.call(sarg0) + return + } + + arg1 := node.args[1] + sarg1 := if arg1 is string { + arg1 + } else { + g.v_error('`${node.name}` must accept a string for call', node.pos) + } + + g.func.call_import(sarg0, sarg1) +} + +pub fn (mut g Gen) asm_local_get_set_or_tee(node ast.AsmTemplate, vars AsmVars) { + if node.args.len != 1 { + g.v_error('incorrect number of arguments to `${node.name}`', node.pos) + } + + arg0 := node.args[0] + alias := match arg0 { + ast.AsmAlias { + arg0 + } + else { + g.v_error('must reference local by identifier', node.pos) + } + } + + target_var := if alias.name == '__vbp' { + Var{ + idx: g.bp() + } + } else { + var := vars[alias.name] or { g.v_error('unknown identifier', alias.pos) } + var + } + // -- doesn't work, cgen error + // else if var := vars[alias.name] { + // var + // } + + if target_var.is_global { + g.v_error('`${alias.name}` is global, cannot use with this instruction', alias.pos) + } + + match node.name { + 'local.get' { + g.get(target_var) + } + 'local.set' { + g.set(target_var) + } + 'local.tee' { + g.tee(target_var) + } + else { + panic('unreachable') + } + } +} + +pub fn (mut g Gen) asm_global_get_or_set(node ast.AsmTemplate, vars AsmVars) { + if node.args.len != 1 { + g.v_error('incorrect number of arguments to `${node.name}`', node.pos) + } + + arg0 := node.args[0] + alias := match arg0 { + ast.AsmAlias { + arg0 + } + else { + g.v_error('must reference global by identifier', node.pos) + } + } + + target_var := if alias.name == '__vsp' { + Var{ + g_idx: g.sp() + is_global: true + } + } else if alias.name == '__heap_base' { + Var{ + g_idx: g.hp() + is_global: true + } + } else { + var := vars[alias.name] or { g.v_error('unknown identifier', alias.pos) } + var + } + + if !target_var.is_global { + g.v_error('`${alias.name}` is a local, cannot use with this instruction', alias.pos) + } + + match node.name { + 'global.get' { + g.get(target_var) + } + 'global.set' { + g.set(target_var) + } + else { + panic('unreachable') + } + } +} + +pub fn (mut g Gen) asm_literal_arg(node ast.AsmTemplate) { + // i32.const + // i64.const + // f32.const + // f64.const + + if node.args.len != 1 { + g.v_error('incorrect number of arguments to `${node.name}`', node.pos) + } + + is_float := node.name[0] == `f` + arg := node.args[0] + + if is_float { + literal := match arg { + ast.FloatLiteral { + arg.val + } + ast.IntegerLiteral { + arg.val + } + else { + g.v_error('must supply float value to `${node.name}`', node.pos) + } + } + + match node.name { + 'f32.const' { + g.func.f32_const(literal.f32()) + } + 'f64.const' { + g.func.f64_const(literal.f64()) + } + else { + panic('unreachable') + } + } + return + } + + literal := match arg { + ast.BoolLiteral { + if arg.val { + '1' + } else { + '0' + } + } + ast.CharLiteral { + u32(arg.val.runes()[0]).str() // there is a better way. + } + ast.IntegerLiteral { + arg.val + } + else { + g.v_error('must supply integer-like value to `${node.name}`', node.pos) + } + } + + match node.name { + 'i32.const' { + g.func.i32_const(i32(literal.int())) + } + 'i64.const' { + g.func.i64_const(literal.i64()) + } + else { + panic('unreachable') + } + } +} + +pub fn (mut g Gen) asm_parse_align_offset(node ast.AsmTemplate) (int, int) { + if node.args.len != 2 { + g.v_error('incorrect number of arguments to `${node.name}`', node.pos) + } + + arg0 := node.args[0] + arg1 := node.args[1] + + align := match arg0 { + ast.IntegerLiteral { + arg0.val.int() + } + else { + g.v_error('must supply integer value to align', node.pos) + } + } + + offset := match arg1 { + ast.IntegerLiteral { + arg1.val.int() + } + else { + g.v_error('must supply integer value to offset', node.pos) + } + } + + return align, offset +} + +pub fn (mut g Gen) asm_load_or_store(node ast.AsmTemplate) { + align, offset := g.asm_parse_align_offset(node) + + match node.name { + 'i32.load' { + g.func.load(.i32_t, align, offset) + } + 'i64.load' { + g.func.load(.i64_t, align, offset) + } + 'f32.load' { + g.func.load(.f32_t, align, offset) + } + 'f64.load' { + g.func.load(.f64_t, align, offset) + } + 'i32.store' { + g.func.store(.i32_t, align, offset) + } + 'i64.store' { + g.func.store(.i64_t, align, offset) + } + 'f32.store' { + g.func.store(.f32_t, align, offset) + } + 'f64.store' { + g.func.store(.f64_t, align, offset) + } + 'i32.load8_s' { + g.func.load8(.i32_t, true, align, offset) + } + 'i64.load8_s' { + g.func.load8(.i64_t, true, align, offset) + } + 'i32.load8_u' { + g.func.load8(.i32_t, false, align, offset) + } + 'i64.load8_u' { + g.func.load8(.i64_t, false, align, offset) + } + 'i32.load16_s' { + g.func.load16(.i32_t, true, align, offset) + } + 'i64.load16_s' { + g.func.load16(.i64_t, true, align, offset) + } + 'i32.load16_u' { + g.func.load16(.i32_t, false, align, offset) + } + 'i64.load16_u' { + g.func.load16(.i64_t, false, align, offset) + } + 'i64.load32_s' { + g.func.load32_i64(true, align, offset) + } + 'i64.load32_u' { + g.func.load32_i64(false, align, offset) + } + 'i32.store8' { + g.func.store8(.i32_t, align, offset) + } + 'i64.store8' { + g.func.store8(.i64_t, align, offset) + } + 'i32.store16' { + g.func.store16(.i32_t, align, offset) + } + 'i64.store16' { + g.func.store16(.i64_t, align, offset) + } + 'i64.store32' { + g.func.store32_i64(align, offset) + } + else { + panic('unreachable') + } + } +} + +pub fn (mut g Gen) asm_template(parent ast.AsmStmt, node ast.AsmTemplate, vars AsmVars) { + if node.is_label || node.is_directive { + g.v_error("`asm wasm` doesn't support labels or directives", node.pos) + } + + match node.name { + 'unreachable' { + g.func.unreachable() + } + 'nop' { + g.func.nop() + } + 'drop' { + g.func.drop() + } + 'return' { + g.func.c_return() + } + 'select' { + g.func.c_select() + } + 'i32.const' { + g.asm_literal_arg(node) + } + 'i64.const' { + g.asm_literal_arg(node) + } + 'f32.const' { + g.asm_literal_arg(node) + } + 'f64.const' { + g.asm_literal_arg(node) + } + 'local.get' { + g.asm_local_get_set_or_tee(node, vars) + } + 'local.set' { + g.asm_local_get_set_or_tee(node, vars) + } + 'local.tee' { + g.asm_local_get_set_or_tee(node, vars) + } + 'global.get' { + g.asm_global_get_or_set(node, vars) + } + 'global.set' { + g.asm_global_get_or_set(node, vars) + } + 'i32.load' { + g.asm_load_or_store(node) + } + 'i64.load' { + g.asm_load_or_store(node) + } + 'f32.load' { + g.asm_load_or_store(node) + } + 'f64.load' { + g.asm_load_or_store(node) + } + 'i32.store' { + g.asm_load_or_store(node) + } + 'i64.store' { + g.asm_load_or_store(node) + } + 'f32.store' { + g.asm_load_or_store(node) + } + 'f64.store' { + g.asm_load_or_store(node) + } + 'i32.load8_s' { + g.asm_load_or_store(node) + } + 'i64.load8_s' { + g.asm_load_or_store(node) + } + 'i32.load8_u' { + g.asm_load_or_store(node) + } + 'i64.load8_u' { + g.asm_load_or_store(node) + } + 'i32.load16_s' { + g.asm_load_or_store(node) + } + 'i64.load16_s' { + g.asm_load_or_store(node) + } + 'i32.load16_u' { + g.asm_load_or_store(node) + } + 'i64.load16_u' { + g.asm_load_or_store(node) + } + 'i64.load32_s' { + g.asm_load_or_store(node) + } + 'i64.load32_u' { + g.asm_load_or_store(node) + } + 'i32.store8' { + g.asm_load_or_store(node) + } + 'i64.store8' { + g.asm_load_or_store(node) + } + 'i32.store16' { + g.asm_load_or_store(node) + } + 'i64.store16' { + g.asm_load_or_store(node) + } + 'i64.store32' { + g.asm_load_or_store(node) + } + 'call' { + g.asm_call(node) + } + 'i32.add' { + g.func.add(.i32_t) + } + 'i32.sub' { + g.func.sub(.i32_t) + } + 'i32.mul' { + g.func.mul(.i32_t) + } + 'i32.div_s' { + g.func.div(.i32_t, true) + } + 'i32.div_u' { + g.func.div(.i32_t, false) + } + 'i32.rem_s' { + g.func.rem(.i32_t, true) + } + 'i32.rem_u' { + g.func.rem(.i32_t, false) + } + 'i64.add' { + g.func.add(.i64_t) + } + 'i64.sub' { + g.func.sub(.i64_t) + } + 'i64.mul' { + g.func.mul(.i64_t) + } + 'i64.div_s' { + g.func.div(.i64_t, true) + } + 'i64.div_u' { + g.func.div(.i64_t, false) + } + 'i64.rem_s' { + g.func.rem(.i64_t, true) + } + 'i64.rem_u' { + g.func.rem(.i64_t, false) + } + 'f32.add' { + g.func.add(.f32_t) + } + 'f32.sub' { + g.func.sub(.f32_t) + } + 'f32.mul' { + g.func.mul(.f32_t) + } + 'f32.div' { + g.func.div(.f32_t, true) + } + 'f64.add' { + g.func.add(.f64_t) + } + 'f64.sub' { + g.func.sub(.f64_t) + } + 'f64.mul' { + g.func.mul(.f64_t) + } + 'f64.div' { + g.func.div(.f64_t, true) + } + 'i32.eqz' { + g.func.eqz(.i32_t) + } + 'i32.eq' { + g.func.eq(.i32_t) + } + 'i32.ne' { + g.func.ne(.i32_t) + } + 'i32.lt_s' { + g.func.lt(.i32_t, true) + } + 'i32.lt_u' { + g.func.lt(.i32_t, false) + } + 'i32.gt_s' { + g.func.gt(.i32_t, true) + } + 'i32.gt_u' { + g.func.gt(.i32_t, false) + } + 'i32.le_s' { + g.func.le(.i32_t, true) + } + 'i32.le_u' { + g.func.le(.i32_t, false) + } + 'i32.ge_s' { + g.func.ge(.i32_t, true) + } + 'i32.ge_u' { + g.func.ge(.i32_t, false) + } + 'i64.eqz' { + g.func.eqz(.i64_t) + } + 'i64.eq' { + g.func.eq(.i64_t) + } + 'i64.ne' { + g.func.ne(.i64_t) + } + 'i64.lt_s' { + g.func.lt(.i64_t, true) + } + 'i64.lt_u' { + g.func.lt(.i64_t, false) + } + 'i64.gt_s' { + g.func.gt(.i64_t, true) + } + 'i64.gt_u' { + g.func.gt(.i64_t, false) + } + 'i64.le_s' { + g.func.le(.i64_t, true) + } + 'i64.le_u' { + g.func.le(.i64_t, false) + } + 'i64.ge_s' { + g.func.ge(.i64_t, true) + } + 'i64.ge_u' { + g.func.ge(.i64_t, false) + } + 'f32.eq' { + g.func.eq(.f32_t) + } + 'f32.ne' { + g.func.ne(.f32_t) + } + 'f32.lt' { + g.func.lt(.f32_t, true) + } + 'f32.gt' { + g.func.gt(.f32_t, true) + } + 'f32.le' { + g.func.le(.f32_t, true) + } + 'f32.ge' { + g.func.ge(.f32_t, true) + } + 'f64.eq' { + g.func.eq(.f64_t) + } + 'f64.ne' { + g.func.ne(.f64_t) + } + 'f64.lt' { + g.func.lt(.f64_t, true) + } + 'f64.gt' { + g.func.gt(.f64_t, true) + } + 'f64.le' { + g.func.le(.f64_t, true) + } + 'f64.ge' { + g.func.ge(.f64_t, true) + } + 'i32.and' { + g.func.b_and(.i32_t) + } + 'i32.or' { + g.func.b_or(.i32_t) + } + 'i32.xor' { + g.func.b_xor(.i32_t) + } + 'i32.shl' { + g.func.b_shl(.i32_t) + } + 'i32.shr_s' { + g.func.b_shr(.i32_t, true) + } + 'i32.shr_u' { + g.func.b_shr(.i32_t, true) + } + 'i32.rotl' { + g.func.rotl(.i32_t) + } + 'i32.rotr' { + g.func.rotr(.i32_t) + } + 'i32.clz' { + g.func.clz(.i32_t) + } + 'i32.ctz' { + g.func.ctz(.i32_t) + } + 'i32.popcnt' { + g.func.popcnt(.i32_t) + } + 'i64.and' { + g.func.b_and(.i64_t) + } + 'i64.or' { + g.func.b_or(.i64_t) + } + 'i64.xor' { + g.func.b_xor(.i64_t) + } + 'i64.shl' { + g.func.b_shl(.i64_t) + } + 'i64.shr_s' { + g.func.b_shr(.i64_t, true) + } + 'i64.shr_u' { + g.func.b_shr(.i64_t, false) + } + 'i64.rotl' { + g.func.rotl(.i64_t) + } + 'i64.rotr' { + g.func.rotr(.i64_t) + } + 'i64.clz' { + g.func.clz(.i64_t) + } + 'i64.ctz' { + g.func.ctz(.i64_t) + } + 'i64.popcnt' { + g.func.popcnt(.i64_t) + } + 'f32.neg' { + g.func.neg(.f32_t) + } + 'f32.ceil' { + g.func.ceil(.f32_t) + } + 'f32.floor' { + g.func.floor(.f32_t) + } + 'f32.trunc' { + g.func.trunc(.f32_t) + } + 'f32.nearest' { + g.func.nearest(.f32_t) + } + 'f32.sqrt' { + g.func.sqrt(.f32_t) + } + 'f32.min' { + g.func.min(.f32_t) + } + 'f32.max' { + g.func.max(.f32_t) + } + 'f32.copysign' { + g.func.copysign(.f32_t) + } + 'f64.abs' { + g.func.abs(.f64_t) + } + 'f64.neg' { + g.func.neg(.f64_t) + } + 'f64.ceil' { + g.func.ceil(.f64_t) + } + 'f64.floor' { + g.func.floor(.f64_t) + } + 'f64.trunc' { + g.func.trunc(.f64_t) + } + 'f64.nearest' { + g.func.nearest(.f64_t) + } + 'f64.sqrt' { + g.func.sqrt(.f64_t) + } + 'f64.min' { + g.func.min(.f64_t) + } + 'f64.max' { + g.func.max(.f64_t) + } + 'f64.copysign' { + g.func.copysign(.f64_t) + } + 'i32.wrap_i64' { + g.func.cast(.i64_t, false, .i32_t) + } + 'i32.trunc_f32_s' { + g.func.cast_trapping(.f32_t, true, .i32_t) + } + 'i32.trunc_f32_u' { + g.func.cast_trapping(.f32_t, false, .i32_t) + } + 'i32.trunc_f64_s' { + g.func.cast_trapping(.f64_t, true, .i32_t) + } + 'i32.trunc_f64_u' { + g.func.cast_trapping(.f64_t, false, .i32_t) + } + 'i64.extend_i32_s' { + g.func.cast(.i32_t, true, .i64_t) + } + 'i64.extend_i32_u' { + g.func.cast(.i32_t, false, .i64_t) + } + 'i64.trunc_f32_s' { + g.func.cast_trapping(.f32_t, true, .i64_t) + } + 'i64.trunc_f32_u' { + g.func.cast_trapping(.f32_t, false, .i64_t) + } + 'i64.trunc_f64_s' { + g.func.cast_trapping(.f64_t, true, .i64_t) + } + 'i64.trunc_f64_u' { + g.func.cast_trapping(.f64_t, false, .i64_t) + } + 'f32.convert_i32_s' { + g.func.cast(.i32_t, true, .f32_t) + } + 'f32.convert_i32_u' { + g.func.cast(.i32_t, false, .f32_t) + } + 'f32.convert_i64_s' { + g.func.cast(.i64_t, true, .f32_t) + } + 'f32.convert_i64_u' { + g.func.cast(.i64_t, false, .f32_t) + } + 'f32.demote_f64' { + g.func.cast(.f64_t, true, .f32_t) + } + 'f64.convert_i32_s' { + g.func.cast(.i32_t, true, .f64_t) + } + 'f64.convert_i32_u' { + g.func.cast(.i32_t, false, .f64_t) + } + 'f64.convert_i64_s' { + g.func.cast(.i64_t, true, .f64_t) + } + 'f64.convert_i64_u' { + g.func.cast(.i64_t, false, .f64_t) + } + 'f64.promote_f32' { + g.func.cast(.f32_t, true, .f64_t) + } + 'i32.reinterpret_f32' { + g.func.reinterpret(.f32_t) + } + 'i64.reinterpret_f64' { + g.func.reinterpret(.f64_t) + } + 'f32.reinterpret_i32' { + g.func.reinterpret(.i32_t) + } + 'f64.reinterpret_i64' { + g.func.reinterpret(.i64_t) + } + 'i32.extend8_s' { + g.func.sign_extend8(.i32_t) + } + 'i32.extend16_s' { + g.func.sign_extend16(.i32_t) + } + 'i64.extend8_s' { + g.func.sign_extend8(.i64_t) + } + 'i64.extend16_s' { + g.func.sign_extend16(.i64_t) + } + 'i64.extend32_s' { + g.func.sign_extend32() + } + 'i32.trunc_sat_f32_s' { + g.func.cast(.f32_t, true, .i32_t) + } + 'i32.trunc_sat_f32_u' { + g.func.cast(.f32_t, false, .i32_t) + } + 'i32.trunc_sat_f64_s' { + g.func.cast(.f64_t, true, .i32_t) + } + 'i32.trunc_sat_f64_u' { + g.func.cast(.f64_t, false, .i32_t) + } + 'i64.trunc_sat_f32_s' { + g.func.cast(.f32_t, true, .i64_t) + } + 'i64.trunc_sat_f32_u' { + g.func.cast(.f32_t, false, .i64_t) + } + 'i64.trunc_sat_f64_s' { + g.func.cast(.f64_t, true, .i64_t) + } + 'i64.trunc_sat_f64_u' { + g.func.cast(.f64_t, false, .i64_t) + } + 'memory.size' { + g.func.memory_size() + } + 'memory.grow' { + g.func.memory_grow() + } + 'memory.copy' { + g.func.memory_copy() + } + 'memory.fill' { + g.func.memory_fill() + } + // TODO: impl later + /* + 'ref.null' { + g.func.ref_null() + } + */ + else { + g.v_error('unknown opcode', node.pos) + } + } +} + +type AsmVars = map[string]Var + +pub fn (mut g Gen) asm_stmt(node ast.AsmStmt) { + mut vars := AsmVars(map[string]Var{}) + + for var_expr in node.output { + vars[var_expr.alias] = g.get_var_or_make_from_expr(var_expr.expr, var_expr.typ) + } + for var_expr in node.input { + vars[var_expr.alias] = g.get_var_or_make_from_expr(var_expr.expr, var_expr.typ) + } + + if node.clobbered.len != 0 { + g.v_error('wasm does not support clobber lists', node.pos) + } + if node.global_labels.len != 0 || node.local_labels.len != 0 { + g.v_error('wasm does not support labels', node.pos) + } + + for tmpl in node.templates { + g.asm_template(node, tmpl, vars) + } +} diff --git a/vlib/v/gen/wasm/comptime.v b/vlib/v/gen/wasm/comptime.v new file mode 100644 index 0000000000..2e662f7736 --- /dev/null +++ b/vlib/v/gen/wasm/comptime.v @@ -0,0 +1,132 @@ +// Copyright (c) 2023 l-m.dev. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. +module wasm + +import v.ast + +pub fn (mut g Gen) comptime_cond(cond ast.Expr, pkg_exists bool) bool { + match cond { + ast.BoolLiteral { + return cond.val + } + ast.ParExpr { + g.comptime_cond(cond.expr, pkg_exists) + } + ast.PrefixExpr { + if cond.op == .not { + return !g.comptime_cond(cond.right, pkg_exists) + } + } + ast.InfixExpr { + match cond.op { + .and { + return g.comptime_cond(cond.left, pkg_exists) + && g.comptime_cond(cond.right, pkg_exists) + } + .logical_or { + return g.comptime_cond(cond.left, pkg_exists) + || g.comptime_cond(cond.right, pkg_exists) + } + .eq { + return g.comptime_cond(cond.left, pkg_exists) == g.comptime_cond(cond.right, + pkg_exists) + } + .ne { + return g.comptime_cond(cond.left, pkg_exists) != g.comptime_cond(cond.right, + pkg_exists) + } + // wasm doesn't support generics + // .key_is, .not_is + else {} + } + } + ast.Ident { + return g.comptime_if_to_ifdef(cond.name, false) + } + ast.ComptimeCall { + return pkg_exists // more documentation needed here... + } + else {} + } + g.w_error('wasm.comptime_cond(): unhandled node: ' + cond.type_name()) +} + +pub fn (mut g Gen) comptime_if_expr(node ast.IfExpr, expected ast.Type, existing_rvars []Var) { + if !node.is_expr && !node.has_else && node.branches.len == 1 { + if node.branches[0].stmts.len == 0 { + // empty ifdef; result of target OS != conditional => skip + return + } + } + + for i, branch in node.branches { + has_expr := !(node.has_else && i + 1 >= node.branches.len) + + if has_expr && !g.comptime_cond(branch.cond, branch.pkg_exist) { + continue + } + // !node.is_expr || cond + // handles else case, and if cond is true + g.rvar_expr_stmts(branch.stmts, expected, existing_rvars) + break + } +} + +pub fn (mut g Gen) comptime_if_to_ifdef(name string, is_comptime_option bool) bool { + match name { + // platforms/os-es/compilers: + 'windows', 'ios', 'macos', 'mach', 'darwin', 'linux', 'freebsd', 'openbsd', 'bsd', + 'android', 'solaris', 'js_node', 'js_freestanding', 'js_browser', 'es5', 'js', 'native', + 'glibc', 'gcc', 'tinyc', 'clang', 'mingw', 'msvc', 'cplusplus', 'gcboehm', 'prealloc', + 'freestanding', 'amd64', 'aarch64', 'arm64' { + return false + } + 'wasm' { + return true + } + // + 'debug' { + return g.pref.is_debug + } + 'prod' { + return g.pref.is_prod + } + // wasm doesn't support testing + 'test' { + return false + } + // wasm doesn't support threads + 'threads' { + return false + } + 'no_bounds_checking' { + return g.pref.no_bounds_checking + } + // bitness: + 'x64' { + return false + } + 'x32' { + return true + } + // endianness: + 'little_endian' { + return true + } + 'big_endian' { + return false + } + else { + // taken from JS: what does it do?? + /* + if is_comptime_option + || (g.pref.compile_defines_all.len > 0 && name in g.pref.compile_defines_all) { + return 'checkDefine("CUSTOM_DEFINE_${name}")' + } + return error('bad os ifdef name "${name}"') + */ + } + } + g.w_error('wasm.comptime_if_to_ifdef(): unhandled `${name}`, is_comptime_option: ${is_comptime_option}') +} diff --git a/vlib/v/gen/wasm/gen.v b/vlib/v/gen/wasm/gen.v index f4ce69a6ef..a219356711 100644 --- a/vlib/v/gen/wasm/gen.v +++ b/vlib/v/gen/wasm/gen.v @@ -62,7 +62,11 @@ pub struct LoopBreakpoint { name string } +[noreturn] pub fn (mut g Gen) v_error(s string, pos token.Pos) { + util.show_compiler_message('error:', pos: pos, file_path: g.file_path, message: s) + exit(1) + /* if g.pref.output_mode == .stdout { util.show_compiler_message('error:', pos: pos, file_path: g.file_path, message: s) exit(1) @@ -70,10 +74,11 @@ pub fn (mut g Gen) v_error(s string, pos token.Pos) { g.errors << errors.Error{ file_path: g.file_path pos: pos - reporter: .gen + reporter: .gen message: s } } + */ } pub fn (mut g Gen) warning(s string, pos token.Pos) { @@ -444,50 +449,6 @@ pub fn (mut g Gen) infix_expr(node ast.InfixExpr, expected ast.Type) { g.func.cast(g.as_numtype(g.get_wasm_type(res_typ)), res_typ.is_signed(), g.as_numtype(g.get_wasm_type(expected))) } -pub fn (mut g Gen) wasm_builtin(name string, node ast.CallExpr) { - for idx, arg in node.args { - g.expr(arg.expr, node.expected_arg_types[idx]) - } - - match name { - '__memory_grow' { - g.func.memory_grow() - } - '__memory_fill' { - g.func.memory_fill() - } - '__memory_copy' { - g.func.memory_copy() - } - '__memory_size' { - g.func.memory_size() - } - '__heap_base' { - if hp := g.heap_base { - g.func.global_get(hp) - } - hp := g.mod.new_global('__heap_base', false, .i32_t, false, wasm.constexpr_value(0)) - g.func.global_get(hp) - g.heap_base = hp - } - '__reinterpret_f32_u32' { - g.func.reinterpret(.f32_t) - } - '__reinterpret_u32_f32' { - g.func.reinterpret(.i32_t) - } - '__reinterpret_f64_u64' { - g.func.reinterpret(.f64_t) - } - '__reinterpret_u64_f64' { - g.func.reinterpret(.i64_t) - } - else { - panic('unreachable') - } - } -} - pub fn (mut g Gen) prefix_expr(node ast.PrefixExpr, expected ast.Type) { match node.op { .minus { @@ -571,6 +532,11 @@ pub fn (mut g Gen) if_branch(ifexpr ast.IfExpr, expected ast.Type, unpacked_para } pub fn (mut g Gen) if_expr(ifexpr ast.IfExpr, expected ast.Type, existing_rvars []Var) { + if ifexpr.is_comptime { + g.comptime_if_expr(ifexpr, expected, existing_rvars) + return + } + params := if expected == ast.void_type { []wasm.ValType{} } else if existing_rvars.len == 0 { @@ -587,13 +553,6 @@ pub fn (mut g Gen) call_expr(node ast.CallExpr, expected ast.Type, existing_rvar is_print := name in ['panic', 'println', 'print', 'eprintln', 'eprint'] - if name in ['__memory_grow', '__memory_fill', '__memory_copy', '__memory_size', '__heap_base', - '__reinterpret_f32_u32', '__reinterpret_u32_f32', '__reinterpret_f64_u64', - '__reinterpret_u64_f64'] { - g.wasm_builtin(node.name, node) - return - } - if node.is_method { name = '${g.table.get_type_name(node.receiver_type)}.${node.name}' } @@ -1247,6 +1206,10 @@ pub fn (mut g Gen) expr_stmt(node ast.Stmt, expected ast.Type) { } } } + ast.AsmStmt { + // assumed expected == void + g.asm_stmt(node) + } else { g.w_error('wasm.expr_stmt(): unhandled node: ' + node.type_name()) } diff --git a/vlib/v/gen/wasm/mem.v b/vlib/v/gen/wasm/mem.v index 5bd6a87944..b939c14eb5 100644 --- a/vlib/v/gen/wasm/mem.v +++ b/vlib/v/gen/wasm/mem.v @@ -128,6 +128,15 @@ pub fn (mut g Gen) sp() wasm.GlobalIndex { return g.sp() } +pub fn (mut g Gen) hp() wasm.GlobalIndex { + if hp := g.heap_base { + return hp + } + hp := g.mod.new_global('__heap_base', false, .i32_t, false, wasm.constexpr_value(0)) + g.heap_base = hp + return hp +} + pub fn (mut g Gen) new_local(name string, typ_ ast.Type) Var { mut typ := typ_ ts := g.table.sym(typ) @@ -210,7 +219,7 @@ pub fn (mut g Gen) literal_to_constant_expression(typ_ ast.Type, init ast.Expr) } pub fn (mut g Gen) new_global(name string, typ_ ast.Type, init ast.Expr, is_global_mut bool) Global { - mut typ := typ_ + mut typ := ast.mktyp(typ_) ts := g.table.sym(typ) match ts.info { @@ -461,6 +470,36 @@ pub fn (mut g Gen) set(v Var) { g.mov(v, from) } +// to satisfy inline assembly needs +// never used by actual codegen +pub fn (mut g Gen) tee(v Var) { + assert !v.is_global + + if !v.is_address { + g.func.local_tee(v.idx) + return + } + + if g.is_pure_type(v.typ) { + l := g.new_local('__tmp', v.typ) + g.func.local_tee(l.idx) // tee here, leave on stack + + g.func.local_get(v.idx) + g.func.local_get(l.idx) + g.store(v.typ, v.offset) + return + } + + from := Var{ + typ: v.typ + idx: g.func.new_local_named(.i32_t, '__tmp') + is_address: v.is_address + } + + g.func.local_tee(from.idx) // tee here, leave on stack + g.mov(v, from) +} + pub fn (mut g Gen) ref(v Var) { g.ref_ignore_offset(v) diff --git a/vlib/v/gen/wasm/tests/asm.vv b/vlib/v/gen/wasm/tests/asm.vv new file mode 100644 index 0000000000..3b7d2b5084 --- /dev/null +++ b/vlib/v/gen/wasm/tests/asm.vv @@ -0,0 +1,150 @@ +fn add_int(init f64, val int) f64 { + mut ret := 0.0 + asm wasm { + local.get init + local.get val + f64.convert_i32_s + f64.add + local.set ret + ; =r (ret) + ; r (init) + r (val) + } + return ret +} + +fn add_f64(init int, val f64) int { + mut ret := 0 + asm wasm { + local.get init + local.get val + i32.trunc_sat_f64_s + i32.add + local.set ret + ; =r (ret) + ; r (init) + r (val) + } + return ret +} + +fn memory_size() int { + mut ret := 0 + asm wasm { + memory.size + local.set ret + ; =r (ret) + } + return ret +} + +fn memset() { + mut memory := [16]u8{} + + asm wasm { + local.get memory + i32.const 0x33 + i32.const 16 + memory.fill + ; =r (memory) + } + + for i := 0; i < 16; i++ { + print(memory[i]) + if i + 1 < 16 { + print(' ') + } + } + println('') +} + +fn literals() (int, i64, f32, f64) { + mut l1 := 0 + mut l2 := i64(0) + mut l3 := f32(0.0) + mut l4 := 0.0 + + asm wasm { + i32.const 999 + i64.const 999 + f32.const 999 + f64.const 999 + local.set l4 + local.set l3 + local.set l2 + local.set l1 + ; =r (l1) + =r (l2) + =r (l3) + =r (l4) + } + + return l1, l2, l3, l4 +} + +fn reinterpret_asm() string { + mut reinterp := 0 + + // reinterpret int value into f32, convert back + asm wasm { + i32.const 0x44424000 // 777.0 + f32.reinterpret_i32 + i32.trunc_sat_f32_s + local.set reinterp + ; =r (reinterp) + } + + mut lit := '' + + // call "int.str" to convert int to string + asm wasm { + local.get lit + local.get reinterp + call 'int.str' + ; =r (lit) + ; r (reinterp) + } + + return lit +} + +fn malloc_ptr() { + mut v := unsafe { &int(malloc(sizeof(int))) } + + asm wasm { + local.get v + i32.const 42 + i32.store 2, 0 // log2(sizeof(int)) == 2 + ; =r (v) + } + + println(*v) + + mut readp := 0 + asm wasm { + local.get v + i32.load 2, 0 // log2(sizeof(int)) == 2 + local.set readp + ; =r (readp) + ; r (v) + } + + println(readp) +} + +fn main() { + println(int(add_int(1.0, 2))) // no f64.str() + println(add_f64(1, 2.0)) + println(memory_size()) + memset() + + a, b, c, d := literals() + println(a) + println(b) + println(int(c)) // no f32.str() + println(int(d)) // no f64.str() + + seven_seven_seven := reinterpret_asm() + println(seven_seven_seven) + malloc_ptr() +} diff --git a/vlib/v/gen/wasm/tests/asm.vv.out b/vlib/v/gen/wasm/tests/asm.vv.out new file mode 100644 index 0000000000..47c0fbc605 --- /dev/null +++ b/vlib/v/gen/wasm/tests/asm.vv.out @@ -0,0 +1,11 @@ +3 +3 +1 +51 51 51 51 51 51 51 51 51 51 51 51 51 51 51 51 +999 +999 +999 +999 +777 +42 +42 \ No newline at end of file diff --git a/vlib/v/gen/wasm/tests/builtin.vv b/vlib/v/gen/wasm/tests/builtin.vv index d3ae20adb7..7a151e12bb 100644 --- a/vlib/v/gen/wasm/tests/builtin.vv +++ b/vlib/v/gen/wasm/tests/builtin.vv @@ -30,4 +30,8 @@ fn main() { assertions() // panic('nooo!') + + println('wasm builtins') + println(vwasm_memory_size()) + println(vwasm_memory_grow(0)) } diff --git a/vlib/v/gen/wasm/tests/builtin.vv.out b/vlib/v/gen/wasm/tests/builtin.vv.out index 3abad377ed..2fd5553266 100644 --- a/vlib/v/gen/wasm/tests/builtin.vv.out +++ b/vlib/v/gen/wasm/tests/builtin.vv.out @@ -3,4 +3,7 @@ hello!hello! false false true -110 \ No newline at end of file +110 +wasm builtins +1 +1 \ No newline at end of file diff --git a/vlib/v/gen/wasm/tests/comptime.vv b/vlib/v/gen/wasm/tests/comptime.vv new file mode 100644 index 0000000000..401396dab0 --- /dev/null +++ b/vlib/v/gen/wasm/tests/comptime.vv @@ -0,0 +1,13 @@ +fn main() { + $if wasm { + println('wasm') + } $else { + println('not wasm') + } + + $if !wasm { + println('not wasm') + } $else { + println('wasm') + } +} diff --git a/vlib/v/gen/wasm/tests/comptime.vv.out b/vlib/v/gen/wasm/tests/comptime.vv.out new file mode 100644 index 0000000000..ef0d90f95d --- /dev/null +++ b/vlib/v/gen/wasm/tests/comptime.vv.out @@ -0,0 +1,2 @@ +wasm +wasm \ No newline at end of file diff --git a/vlib/v/parser/parser.v b/vlib/v/parser/parser.v index 046b76c721..e3c43246da 100644 --- a/vlib/v/parser/parser.v +++ b/vlib/v/parser/parser.v @@ -1162,6 +1162,11 @@ fn (mut p Parser) asm_stmt(is_top_level bool) ast.AsmStmt { p.check(.key_asm) mut arch := pref.arch_from_string(p.tok.lit) or { pref.Arch._auto } + + if is_top_level && arch == .wasm32 { + p.error("wasm doesn't support toplevel assembly") + } + mut is_volatile := false mut is_goto := false if p.tok.kind == .key_volatile { @@ -1175,7 +1180,7 @@ fn (mut p Parser) asm_stmt(is_top_level bool) ast.AsmStmt { } if arch == ._auto && !p.pref.is_fmt { if p.tok.lit == '' { - p.error('missing assembly architecture. Try i386, amd64 or arm64.') + p.error('missing assembly architecture. Try i386, amd64, arm64, or wasm.') } p.error('unknown assembly architecture') } @@ -1217,7 +1222,7 @@ fn (mut p Parser) asm_stmt(is_top_level bool) ast.AsmStmt { if is_directive { p.next() } - if p.tok.kind in [.key_in, .key_lock, .key_orelse] { // `in`, `lock`, `or` are v keywords that are also x86/arm/riscv instructions. + if p.tok.kind in [.key_in, .key_lock, .key_orelse, .key_select, .key_return] { // `in`, `lock`, `or`, `select`, `return` are v keywords that are also x86/arm/riscv/wasm instructions. name += p.tok.kind.str() p.next() } else if p.tok.kind == .number { @@ -1227,13 +1232,19 @@ fn (mut p Parser) asm_stmt(is_top_level bool) ast.AsmStmt { name += p.tok.lit p.check(.name) } - // dots are part of instructions for some riscv extensions - if arch in [.rv32, .rv64] { + // dots are part of instructions for some riscv extensions and webassembly + if arch in [.rv32, .rv64, .wasm32] { for p.tok.kind == .dot { name += '.' p.next() - name += p.tok.lit - p.check(.name) + // wasm: i32.const + if arch == .wasm32 && p.tok.kind == .key_const { + name += 'const' + p.next() + } else { + name += p.tok.lit + p.check(.name) + } } } mut is_label := false @@ -1254,6 +1265,11 @@ fn (mut p Parser) asm_stmt(is_top_level bool) ast.AsmStmt { .name { args << p.reg_or_alias() } + .string { + // wasm: call 'wasi_unstable' 'proc_exit' + args << p.tok.lit + p.next() + } .number { number_lit := p.parse_number_literal() match number_lit { @@ -1293,6 +1309,9 @@ fn (mut p Parser) asm_stmt(is_top_level bool) ast.AsmStmt { break } .lsbr { + if arch == .wasm32 { + p.error("wasm doesn't have addressing operands") + } mut addressing := p.asm_addressing() addressing.segment = segment args << addressing diff --git a/vlib/v/pref/pref.v b/vlib/v/pref/pref.v index 64963571d6..15cc38ba04 100644 --- a/vlib/v/pref/pref.v +++ b/vlib/v/pref/pref.v @@ -835,6 +835,11 @@ pub fn parse_args_and_show_errors(known_external_commands []string, args []strin if b.is_js() { res.output_cross_c = true } + if b == .wasm { + res.compile_defines << 'wasm' + res.compile_defines_all << 'wasm' + res.arch = .wasm32 + } res.backend = b i++ } @@ -1096,7 +1101,7 @@ pub fn arch_from_string(arch_str string) !Arch { 'js_freestanding' { return .js_freestanding } - 'wasm32' { + 'wasm32', 'wasm' { return .wasm32 } '' { diff --git a/vlib/wasm/instructions.v b/vlib/wasm/instructions.v index 0bc9478732..00d309dda6 100644 --- a/vlib/wasm/instructions.v +++ b/vlib/wasm/instructions.v @@ -707,9 +707,9 @@ pub fn (mut func Function) sign_extend16(typ ValType) { } // sign_extend32_i64 extends the value of a 32-bit integer of type i64. -// WebAssembly instruction: `i64.extend64_s`. -pub fn (mut func Function) sign_extend32_i64() { - func.code << 0xC4 // i64.extend64_s +// WebAssembly instruction: `i64.extend32_s`. +pub fn (mut func Function) sign_extend32() { + func.code << 0xC4 // i64.extend32_s } // cast casts a value of type `a` with respect to `is_signed`, to type `b`. @@ -831,29 +831,57 @@ pub fn (mut func Function) cast(a NumType, is_signed bool, b NumType) { // - See function `cast` for the rest. pub fn (mut func Function) cast_trapping(a NumType, is_signed bool, b NumType) { if a in [.f32_t, .f64_t] { - if a == .f32_t { - match b { - .i32_t { - func.code << 0xA8 // i32.trunc_f32_s - return + if is_signed { + if a == .f32_t { + match b { + .i32_t { + func.code << 0xA8 // i32.trunc_f32_s + return + } + .i64_t { + func.code << 0xAE // i64.trunc_f32_s + return + } + else {} } - .i64_t { - func.code << 0xAE // i64.trunc_f32_s - return + } else { + match b { + .i32_t { + func.code << 0xAA // i32.trunc_f64_s + return + } + .i64_t { + func.code << 0xB0 // i64.trunc_f64_s + return + } + else {} } - else {} } } else { - match b { - .i32_t { - func.code << 0xAA // i32.trunc_f64_s - return + if a == .f32_t { + match b { + .i32_t { + func.code << 0xA9 // i32.trunc_f32_u + return + } + .i64_t { + func.code << 0xAF // i64.trunc_f32_u + return + } + else {} } - .i64_t { - func.code << 0xB0 // i64.trunc_f64_s - return + } else { + match b { + .i32_t { + func.code << 0xAB // i32.trunc_f64_u + return + } + .i64_t { + func.code << 0xB1 // i64.trunc_f64_u + return + } + else {} } - else {} } } }