mirror of
https://github.com/vlang/v.git
synced 2025-09-13 22:42:26 +03:00
wasm: implement inline assembly (#19686)
This commit is contained in:
parent
705eea8dcc
commit
c32b04d5be
17 changed files with 1419 additions and 105 deletions
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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')
|
||||
}
|
||||
|
|
|
@ -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 != '' {
|
||||
|
|
889
vlib/v/gen/wasm/asm.v
Normal file
889
vlib/v/gen/wasm/asm.v
Normal file
|
@ -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)
|
||||
}
|
||||
}
|
132
vlib/v/gen/wasm/comptime.v
Normal file
132
vlib/v/gen/wasm/comptime.v
Normal file
|
@ -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}')
|
||||
}
|
|
@ -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)
|
||||
|
@ -74,6 +78,7 @@ pub fn (mut g Gen) v_error(s string, pos token.Pos) {
|
|||
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())
|
||||
}
|
||||
|
|
|
@ -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<voidptr>')
|
||||
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)
|
||||
|
||||
|
|
150
vlib/v/gen/wasm/tests/asm.vv
Normal file
150
vlib/v/gen/wasm/tests/asm.vv
Normal file
|
@ -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()
|
||||
}
|
11
vlib/v/gen/wasm/tests/asm.vv.out
Normal file
11
vlib/v/gen/wasm/tests/asm.vv.out
Normal file
|
@ -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
|
|
@ -30,4 +30,8 @@ fn main() {
|
|||
assertions()
|
||||
|
||||
// panic('nooo!')
|
||||
|
||||
println('wasm builtins')
|
||||
println(vwasm_memory_size())
|
||||
println(vwasm_memory_grow(0))
|
||||
}
|
||||
|
|
|
@ -4,3 +4,6 @@ false
|
|||
false
|
||||
true
|
||||
110
|
||||
wasm builtins
|
||||
1
|
||||
1
|
13
vlib/v/gen/wasm/tests/comptime.vv
Normal file
13
vlib/v/gen/wasm/tests/comptime.vv
Normal file
|
@ -0,0 +1,13 @@
|
|||
fn main() {
|
||||
$if wasm {
|
||||
println('wasm')
|
||||
} $else {
|
||||
println('not wasm')
|
||||
}
|
||||
|
||||
$if !wasm {
|
||||
println('not wasm')
|
||||
} $else {
|
||||
println('wasm')
|
||||
}
|
||||
}
|
2
vlib/v/gen/wasm/tests/comptime.vv.out
Normal file
2
vlib/v/gen/wasm/tests/comptime.vv.out
Normal file
|
@ -0,0 +1,2 @@
|
|||
wasm
|
||||
wasm
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
'' {
|
||||
|
|
|
@ -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 {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue