1
0
mirror of https://github.com/vlang/v.git synced 2023-08-10 21:13:21 +03:00

wasm: add a webassembly compiler backend, based on using binaryen (#17368)

This commit is contained in:
l-m 2023-03-01 08:58:53 +11:00 committed by GitHub
parent b9a8a21094
commit 0625caad56
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
51 changed files with 7981 additions and 8 deletions

View File

@ -10,7 +10,6 @@ on:
- 'vlib/builtin/**.v'
- 'vlib/v/ast/**.v'
- 'vlib/v/scanner/**.v'
- 'vlib/v/scanner/**.v'
- 'vlib/v/parser/**.v'
- 'vlib/v/checker/**.v'
- 'vlib/v/gen/c/**.v'
@ -31,7 +30,6 @@ on:
- 'vlib/builtin/**.v'
- 'vlib/v/ast/**.v'
- 'vlib/v/scanner/**.v'
- 'vlib/v/scanner/**.v'
- 'vlib/v/parser/**.v'
- 'vlib/v/checker/**.v'
- 'vlib/v/gen/c/**.v'

View File

@ -0,0 +1,95 @@
name: wasm backend CI
on:
push:
paths:
- '!**'
- '!**.md'
- 'cmd/tools/builders/**.v'
- 'vlib/builtin/**.v'
- 'vlib/v/ast/**.v'
- 'vlib/v/scanner/**.v'
- 'vlib/v/parser/**.v'
- 'vlib/v/checker/**.v'
- 'vlib/v/gen/c/**.v'
- 'vlib/v/builder/**.v'
- 'vlib/v/cflag/**.v'
- 'vlib/v/live/**.v'
- 'vlib/v/util/**.v'
- 'vlib/v/markused/**.v'
- 'vlib/v/preludes/**.v'
- 'vlib/v/gen/wasm/**.v'
- 'vlib/v/gen/wasm/tests/**.v'
pull_request:
paths:
- '!**'
- '!**.md'
- 'cmd/tools/builders/**.v'
- 'vlib/builtin/**.v'
- 'vlib/v/ast/**.v'
- 'vlib/v/scanner/**.v'
- 'vlib/v/parser/**.v'
- 'vlib/v/checker/**.v'
- 'vlib/v/gen/c/**.v'
- 'vlib/v/builder/**.v'
- 'vlib/v/cflag/**.v'
- 'vlib/v/live/**.v'
- 'vlib/v/util/**.v'
- 'vlib/v/markused/**.v'
- 'vlib/v/preludes/**.v'
- 'vlib/v/gen/wasm/**.v'
- 'vlib/v/gen/wasm/tests/**.v'
concurrency:
group: wasm-backend-ci-${{ github.event.pull_request.number || github.sha }}
cancel-in-progress: true
jobs:
wasm-backend-ubuntu:
runs-on: ubuntu-22.04
if: github.event_name != 'push' || github.event.ref == 'refs/heads/master' || github.event.repository.full_name != 'vlang/v'
timeout-minutes: 121
steps:
- uses: actions/checkout@v3
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install --quiet -y clang gcc
- name: Build V
run: make -j4 && ./v symlink -githubci
- name: Install binaryen as build dependency for the V WASM backend
run: ./v cmd/tools/install_binaryen.vsh
- name: Build the V WASM backend
run: ./v -cc clang -showcc -v cmd/tools/builders/wasm_builder.v
- name: Test the WASM backend
run: ./v test vlib/v/gen/wasm/tests/
- name: Build examples
run: VTEST_ONLY=wasm ./v build-examples
## wasm-backend-macos:
## runs-on: macOS-12
## if: github.event_name != 'push' || github.event.ref == 'refs/heads/master' || github.event.repository.full_name != 'vlang/v'
## timeout-minutes: 121
## steps:
## - uses: actions/checkout@v3
##
## - name: Build V
## run: make -j4 && ./v symlink -githubci
##
## - name: Install binaryen as build dependency for the V WASM backend
## run: ./v cmd/tools/install_binaryen.vsh
##
## - name: Build the V WASM backend
## run: ./v -cc clang -showcc -v cmd/tools/builders/wasm_builder.v
##
## - name: Test the WASM backend
## run: ./v test vlib/v/gen/wasm/tests/
##
## - name: Build examples
## run: VTEST_ONLY=wasm ./v build-examples

2
.gitignore vendored
View File

@ -108,6 +108,7 @@ flake.nix
.envrc
thirdparty/stdatomic/nix/cpp/*.h
thirdparty/binaryen*
# ignore VLS log
vls.log
@ -120,3 +121,4 @@ vls.log
# ignore Intellij files
.idea/
/*.iml
wasm.v

View File

@ -0,0 +1,7 @@
module main
import v.builder.wasmbuilder
fn main() {
wasmbuilder.start()
}

68
cmd/tools/install_binaryen.vsh Executable file
View File

@ -0,0 +1,68 @@
#!/usr/bin/env -S v -raw-vsh-tmp-prefix tmp
import net.http
import json
import os
struct JQ {
tag_name string
}
fn main() {
root := os.real_path(os.dir(os.getenv_opt('VEXE') or { @VEXE }))
os.chdir(root)! // make sure that the workfolder is stable
tloc := os.join_path(root, 'thirdparty')
loc := os.join_path(tloc, 'binaryen')
if os.exists(loc) {
eprintln('thirdparty/binaryen exists, will not overwrite')
eprintln('delete the folder, and execute again')
exit(1)
}
jq := http.get_text('https://api.github.com/repos/WebAssembly/binaryen/releases/latest')
tag := json.decode(JQ, jq)!.tag_name
name := $if windows {
'x86_64-windows'
} $else $if macos {
$if arm64 {
'arm64-macos'
} $else {
'x86_64-macos'
}
} $else $if linux {
'x86_64-linux'
} $else {
eprintln('A premade binary library is not available for your system.')
eprintln('Build it from source, following the documentation here: https://github.com/WebAssembly/binaryen/#building')
exit(1)
}
fname := 'binaryen-${tag}'
url := 'https://github.com/WebAssembly/binaryen/releases/download/${tag}/${fname}-${name}.tar.gz'
saveloc := os.join_path(tloc, '${fname}.tar.gz')
if !os.exists(saveloc) {
println('Downloading archive: ${saveloc}, from url: ${url} ...')
http.download_file(url, saveloc)!
// defer { os.rm(saveloc) or {}! }
}
mkdir_all(loc)!
println(loc)
println('Extracting `${tloc}/${fname}` to `${tloc}/binaryen` ...')
cmd := 'tar -xvf ${saveloc} --directory ${tloc}'
if os.system(cmd) != 0 {
eprintln('`${cmd}` exited with a non zero exit code')
exit(1)
}
println(cmd)
println('Moving `${tloc}/${fname}` to `${tloc}/binaryen` ...')
os.rename_dir('${tloc}/${fname}', loc)!
println('Done. You can now use `v -b wasm file.v` .')
}

View File

@ -252,6 +252,18 @@ pub fn new_test_session(_vargs string, will_compile bool) TestSession {
$if !macos {
skip_files << 'examples/macos_tray/tray.v'
}
// examples/wasm/mandelbrot/mandelbrot.v requires special compilation flags: `-b wasm -os browser`, skip it for now:
skip_files << 'examples/wasm/mandelbrot/mandelbrot.v'
// TODO: always build the wasm_builder in the future, not just when it was build manually before:
wasm_builder_executable := $if !windows {
'cmd/tools/builders/wasm_builder'
} $else {
'cmd/tools/builders/wasm_builder.exe'
}
if !os.exists(wasm_builder_executable) {
skip_files << os.join_path('cmd/tools/builders/wasm_builder.v')
}
}
vargs := _vargs.replace('-progress', '')
vexe := pref.vexe_path()

View File

@ -3,7 +3,7 @@ module main
import os
import testing
const vroot = @VMODROOT
const vroot = os.dir(os.real_path(os.getenv_opt('VEXE') or { @VEXE }))
// build as a project folder
const efolders = [
@ -12,11 +12,14 @@ const efolders = [
'examples/vweb_fullstack',
]
pub fn normalised_vroot_path(path string) string {
return os.real_path(os.join_path_single(vroot, path)).replace('\\', '/')
}
fn main() {
args_string := os.args[1..].join(' ')
params := args_string.all_before('build-examples')
skip_prefixes := efolders.map(os.real_path(os.join_path_single(vroot, it)).replace('\\',
'/'))
mut skip_prefixes := efolders.map(normalised_vroot_path(it))
res := testing.v_build_failing_skipped(params, 'examples', skip_prefixes, fn (mut session testing.TestSession) {
for x in efolders {
pathsegments := x.split_any('/')

View File

@ -329,6 +329,10 @@ fn main() {
tsession.skip_files << 'vlib/net/udp_test.v'
}
if !os.exists('cmd/tools/builders/wasm_builder') {
tsession.skip_files << 'vlib/v/gen/wasm/tests/wasm_test.v'
}
mut werror := false
mut sanitize_memory := false
mut sanitize_address := false

View File

@ -192,5 +192,19 @@ fn rebuild(prefs &pref.Preferences) {
println('using Go WIP backend...')
util.launch_tool(prefs.is_verbose, 'builders/golang_builder', os.args[1..])
}
.wasm {
assert_wasm_backend_thirdparty()
util.launch_tool(prefs.is_verbose, 'builders/wasm_builder', os.args[1..])
}
}
}
fn assert_wasm_backend_thirdparty() {
vroot := os.dir(pref.vexe_path())
if !os.exists('${vroot}/cmd/tools/builders/wasm_builder')
&& !os.exists('${vroot}/thirdparty/binaryen') {
eprintln('The WebAssembly backend requires `binaryen`, an external library dependency')
eprintln('This can be installed with `./cmd/tools/install_binaryen.vsh`, to download prebuilt libraries for your platform')
exit(1)
}
}

1
examples/wasm/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
*.wasm

57
examples/wasm/functions.v Normal file
View File

@ -0,0 +1,57 @@
// `pub` functions will be exported.
//
// ```
// v -b wasm -no-builtin functions.v
// wasmer functions.wasm -i main.powi <a> <b>
// wasmer functions.wasm -i main.gcd <a> <b>
// ```
pub fn gcd(a_ i64, b_ i64) i64 {
mut a := a_
mut b := b_
if a < 0 {
a = -a
}
if b < 0 {
b = -b
}
for b != 0 {
a %= b
if a == 0 {
return b
}
b %= a
}
return a
}
pub fn powi(a i64, b i64) i64 {
mut b_ := b
mut p := a
mut v := i64(1)
if b_ < 0 { // exponent < 0
if a == 0 {
return -1 // division by 0
}
return if a * a != 1 {
0
} else {
if (b_ & 1) > 0 {
a
} else {
1
}
}
}
for b_ > 0 {
if b_ & 1 > 0 {
v *= p
}
p *= p
b_ >>= 1
}
return v
}

View File

@ -0,0 +1,9 @@
//
// $ v -b wasm hello_world.v
// $ wasmer hello_world.wasm
// Hello WASI!
//
fn main() {
println('Hello WASI!')
}

View File

@ -0,0 +1,12 @@
# V Mandelbrot Example
1. First, create `mandelbrot.wasm`. Compile with `-os browser`.
```
v -b wasm -os browser mandelbrot.v
```
2. Then, open the `mandelbrot.html` file in the browser.
- CORS errors do not allow `mandelbrot.wasm` to be loaded.
- Use `python -m http.server 8080`
- Use `emrun mandelbrot.html`

View File

@ -0,0 +1,47 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>V Mandelbrot WebAssembly Example</title>
</head>
<body>
<canvas id="canvas" width="200" height="200" style="width:100%;height:100%;image-rendering: crisp-edges;"></canvas>
<script>
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var memory;
function get_string(ptr, len) {
const buf = new Uint8Array(memory.buffer, ptr, len);
const str = new TextDecoder("utf8").decode(buf);
return str
}
const env = {
canvas_x: () => canvas.width,
canvas_y: () => canvas.height,
setpixel: (x, y, c) => {
ctx.fillStyle = "rgba(1,1,1,"+(c/255)+")";
ctx.fillRect(x, y, 1, 1);
},
__writeln: (ptr, len) => {
console.log(get_string(ptr, len))
},
__panic_abort: (ptr, len) => {
throw get_string(ptr, len);
}
}
WebAssembly.instantiateStreaming(fetch("mandelbrot.wasm"), {env: env}).then((res) => {
memory = res.instance.exports['memory'];
console.time('main.main')
res.instance.exports['main.main']()
console.timeEnd('main.main')
});
</script>
</body>
</html>

View File

@ -0,0 +1,41 @@
fn JS.canvas_x() int
fn JS.canvas_y() int
fn JS.setpixel(x int, y int, c f64)
// `main` must be public!
pub fn main() {
max_x := JS.canvas_x()
max_y := JS.canvas_y()
println('starting main.main!')
mut y := 0
for y < max_y {
y += 1
mut x := 0
for x < max_x {
x += 1
e := (f64(y) / 50) - 1.5
f := (f64(x) / 50) - 1.0
mut a := 0.0
mut b := 0.0
mut i := 0.0
mut j := 0.0
mut c := 0.0
for i * i + j * j < 4 && c < 255 {
i = a * a - b * b + e
j = 2 * a * b + f
a = i
b = j
c += 1
}
JS.setpixel(x, y, c)
}
}
panic('reached the end!')
}

81
vlib/builtin/wasm/alloc.v Normal file
View File

@ -0,0 +1,81 @@
[has_globals]
module builtin
// Shitty `sbrk` basic `malloc` and `free` impl
// TODO: implement pure V `walloc` later
const wasm_page_size = 64 * 1024
__global g_heap_base = isize(0)
fn init() {
g_heap_base = __memory_grow(3)
if g_heap_base == -1 {
panic('g_heap_base: malloc() == nil')
}
g_heap_base *= wasm_page_size
}
// 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.
// unlike the `calloc` family of functions - malloc will not zero the memory block.
[unsafe]
pub fn malloc(n isize) &u8 {
if n <= 0 {
panic('malloc(n <= 0)')
}
res := g_heap_base
g_heap_base += n
return &u8(res)
}
// free allows for manually freeing memory allocated at the address `ptr`.
// currently does not free any memory.
[unsafe]
pub fn free(ptr voidptr) {
_ := ptr
}
// 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.
// Unlike `v_calloc` vcalloc checks for negative values given in `n`.
[unsafe]
pub fn vcalloc(n isize) &u8 {
if n <= 0 {
panic('vcalloc(n <= 0)')
} else if n == 0 {
return &u8(0)
}
res := unsafe { malloc(n) }
__memory_fill(res, 0, n)
return res
}
// vmemcpy copies n bytes from memory area src to memory area dest.
// 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)
return dest
}
// vmemmove copies n bytes from memory area src to memory area dest.
// 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)
return dest
}
// vmemset fills the first `n` bytes of the memory area pointed to by `s`,
// 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)
return s
}

View File

@ -0,0 +1,16 @@
module builtin
fn JS.__panic_abort(&u8, int)
fn JS.__writeln(&u8, int)
// panic calls the `__panic_abort` JS panic handler.
[noreturn]
pub fn panic(s string) {
JS.__panic_abort(s.str, s.len)
for {}
}
// println prints a message with a line end, to stdout. stdout is flushed.
pub fn println(s string) {
JS.__writeln(s.str, s.len)
}

View File

@ -0,0 +1,5 @@
module builtin
fn __memory_grow(size isize) isize
fn __memory_fill(dest &u8, value isize, size isize)
fn __memory_copy(dest &u8, src &u8, size isize)

View File

@ -0,0 +1,7 @@
module builtin
pub struct string {
pub:
str &u8
len int
}

View File

@ -0,0 +1,61 @@
module builtin
// print prints a message to stdout. Unlike `println` stdout is not automatically flushed.
pub fn print(s string) {
elm := CIOVec{
buf: s.str
len: usize(s.len)
}
WASM.fd_write(1, &elm, 1, -1)
}
// println prints a message with a line end, to stdout.
pub fn println(s string) {
elm := [CIOVec{
buf: s.str
len: usize(s.len)
}, CIOVec{
buf: c'\n'
len: 1
}]!
WASM.fd_write(1, &elm[0], 2, -1)
}
// eprint prints a message to stderr.
pub fn eprint(s string) {
elm := CIOVec{
buf: s.str
len: usize(s.len)
}
WASM.fd_write(2, &elm, 1, -1)
}
// eprintln prints a message with a line end, to stderr.
pub fn eprintln(s string) {
elm := [CIOVec{
buf: s.str
len: usize(s.len)
}, CIOVec{
buf: c'\n'
len: 1
}]!
WASM.fd_write(2, &elm[0], 2, -1)
}
// exit terminates execution immediately and returns exit `code` to the shell.
[noreturn]
pub fn exit(code int) {
WASM.proc_exit(code)
}
// panic prints a nice error message, then exits the process with exit code of 1.
[noreturn]
pub fn panic(s string) {
eprint('V panic: ')
eprintln(s)
exit(1)
}

View File

@ -0,0 +1,232 @@
module builtin
type byte = u8
type i32 = int
const (
// digit pairs in reverse order
digit_pairs = '00102030405060708090011121314151617181910212223242526272829203132333435363738393041424344454647484940515253545556575859506162636465666768696071727374757677787970818283848586878889809192939495969798999'
)
// This implementation is the quickest with gcc -O2
// str_l returns the string representation of the integer nn with max chars.
[direct_array_access; inline]
fn (nn int) str_l(max int) string {
unsafe {
mut n := i64(nn)
mut d := 0
if n == 0 {
return '0'
}
mut is_neg := false
if n < 0 {
n = -n
is_neg = true
}
mut index := max
mut buf := malloc(max + 1)
buf[index] = 0
index--
for n > 0 {
n1 := int(n / 100)
// calculate the digit_pairs start index
d = int(u32(int(n) - (n1 * 100)) << 1)
n = n1
buf[index] = digit_pairs.str[d]
index--
d++
buf[index] = digit_pairs.str[d]
index--
}
index++
// remove head zero
if d < 20 {
index++
}
// Prepend - if it's negative
if is_neg {
index--
buf[index] = `-`
}
diff := max - index
vmemmove(buf, voidptr(buf + index), diff + 1)
/*
// === manual memory move for bare metal ===
mut c:= 0
for c < diff {
buf[c] = buf[c+index]
c++
}
buf[c] = 0
*/
return tos(buf, diff)
// return tos(memdup(&buf[0] + index, (max - index)), (max - index))
}
}
// str returns the value of the `i8` as a `string`.
// Example: assert i8(-2).str() == '-2'
pub fn (n i8) str() string {
return int(n).str_l(5)
}
// str returns the value of the `i16` as a `string`.
// Example: assert i16(-20).str() == '-20'
pub fn (n i16) str() string {
return int(n).str_l(7)
}
// str returns the value of the `u16` as a `string`.
// Example: assert u16(20).str() == '20'
pub fn (n u16) str() string {
return int(n).str_l(7)
}
// str returns the value of the `int` as a `string`.
// Example: assert int(-2020).str() == '-2020'
pub fn (n int) str() string {
return n.str_l(12)
}
// str returns the value of the `u32` as a `string`.
// Example: assert u32(20000).str() == '20000'
[direct_array_access; inline]
pub fn (nn u32) str() string {
unsafe {
mut n := nn
mut d := u32(0)
if n == 0 {
return '0'
}
max := 12
mut buf := malloc(max + 1)
mut index := max
buf[index] = 0
index--
for n > 0 {
n1 := n / u32(100)
d = ((n - (n1 * u32(100))) << u32(1))
n = n1
buf[index] = digit_pairs[d]
index--
d++
buf[index] = digit_pairs[d]
index--
}
index++
// remove head zero
if d < u32(20) {
index++
}
diff := max - index
vmemmove(buf, voidptr(buf + index), diff + 1)
return tos(buf, diff)
// return tos(memdup(&buf[0] + index, (max - index)), (max - index))
}
}
// str returns the value of the `int_literal` as a `string`.
[inline]
pub fn (n int_literal) str() string {
return i64(n).str()
}
// str returns the value of the `i64` as a `string`.
// Example: assert i64(-200000).str() == '-200000'
[direct_array_access; inline]
pub fn (nn i64) str() string {
unsafe {
mut n := nn
mut d := i64(0)
if n == 0 {
return '0'
} else if n == i64(-9223372036854775807 - 1) {
// math.min_i64
return '-9223372036854775808'
}
max := 20
mut buf := malloc(max + 1)
mut is_neg := false
if n < 0 {
n = -n
is_neg = true
}
mut index := max
buf[index] = 0
index--
for n > 0 {
n1 := n / i64(100)
d = (u32(n - (n1 * i64(100))) << i64(1))
n = n1
buf[index] = digit_pairs[d]
index--
d++
buf[index] = digit_pairs[d]
index--
}
index++
// remove head zero
if d < i64(20) {
index++
}
// Prepend - if it's negative
if is_neg {
index--
buf[index] = `-`
}
diff := max - index
vmemmove(buf, voidptr(buf + index), diff + 1)
return tos(buf, diff)
// return tos(memdup(&buf[0] + index, (max - index)), (max - index))
}
}
// str returns the value of the `u64` as a `string`.
// Example: assert u64(2000000).str() == '2000000'
[direct_array_access; inline]
pub fn (nn u64) str() string {
unsafe {
mut n := nn
mut d := u64(0)
if n == 0 {
return '0'
}
max := 20
mut buf := malloc(max + 1)
mut index := max
buf[index] = 0
index--
for n > 0 {
n1 := n / 100
d = ((n - (n1 * 100)) << 1)
n = n1
buf[index] = digit_pairs[d]
index--
d++
buf[index] = digit_pairs[d]
index--
}
index++
// remove head zero
if d < 20 {
index++
}
diff := max - index
vmemmove(buf, voidptr(buf + index), diff + 1)
return tos(buf, diff)
// return tos(memdup(&buf[0] + index, (max - index)), (max - index))
}
}
// str returns the value of the `bool` as a `string`.
// Example: assert (2 > 1).str() == 'true'
pub fn (b bool) str() string {
if b {
return 'true'
}
return 'false'
}

View File

@ -0,0 +1,16 @@
module builtin
// tos creates a V string, given a C style pointer to a 0 terminated block.
// Note: the memory block pointed by s is *reused, not copied*!
// It will panic, when the pointer `s` is 0.
// See also `tos_clone`.
[unsafe]
pub fn tos(s &u8, len int) string {
if s == 0 {
panic('tos(): nil string')
}
return string{
str: unsafe { s }
len: len
}
}

View File

@ -0,0 +1,14 @@
[wasm_import_namespace: 'wasi_snapshot_preview1']
module builtin
struct CIOVec {
buf &u8
len usize
}
type Errno = u16
type FileDesc = int
fn WASM.fd_write(fd FileDesc, iovs &CIOVec, iovs_len usize, retptr &usize) Errno
[noreturn]
fn WASM.proc_exit(rval int)

View File

@ -211,6 +211,24 @@ pub fn file_size(path string) u64 {
return 0
}
// rename_dir renames the folder from `src` to `dst`.
// Use mv to move or rename a file in a platform independent manner.
pub fn rename_dir(src string, dst string) ! {
$if windows {
w_src := src.replace('/', '\\')
w_dst := dst.replace('/', '\\')
ret := C._wrename(w_src.to_wide(), w_dst.to_wide())
if ret != 0 {
return error_with_code('failed to rename ${src} to ${dst}', int(ret))
}
} $else {
ret := C.rename(&char(src.str), &char(dst.str))
if ret != 0 {
return error_with_code('failed to rename ${src} to ${dst}', ret)
}
}
}
// rename renames the file or folder from `src` to `dst`.
// Use mv to move or rename a file in a platform independent manner.
pub fn rename(src string, dst string) ! {

View File

@ -889,6 +889,7 @@ pub:
is_c2v_prefix bool // for `--x` (`x--$`), only for translated code until c2v can handle it
pub mut:
expr Expr
typ Type
auto_locked string
}

View File

@ -36,6 +36,7 @@ pub enum Language {
v
c
js
wasm
amd64 // aka x86_64
i386
arm64 // 64-bit arm
@ -887,6 +888,7 @@ pub fn (t &Table) type_size(typ Type) (int, int) {
.placeholder, .void, .none_, .generic_inst {}
.voidptr, .byteptr, .charptr, .function, .usize, .isize, .any, .thread, .chan {
size = t.pointer_size
align = t.pointer_size
}
.i8, .u8, .char, .bool {
size = 1
@ -1075,6 +1077,7 @@ pub:
is_flag bool
is_multi_allowed bool
uses_exprs bool
typ Type
}
[minify]

View File

@ -197,6 +197,16 @@ pub fn (v Builder) get_builtin_files() []string {
if v.pref.backend.is_js() {
builtin_files << v.v_files_from_dir(os.join_path(location, 'builtin',
'js'))
} else if v.pref.backend == .wasm {
builtin_files << v.v_files_from_dir(os.join_path(location, 'builtin',
'wasm'))
if v.pref.os == .browser {
builtin_files << v.v_files_from_dir(os.join_path(location, 'builtin',
'wasm', 'browser'))
} else {
builtin_files << v.v_files_from_dir(os.join_path(location, 'builtin',
'wasm', 'wasi'))
}
} else {
builtin_files << v.v_files_from_dir(os.join_path(location, 'builtin'))
}

View File

@ -0,0 +1,36 @@
module wasmbuilder
import v.pref
import v.util
import v.builder
import v.gen.wasm
pub fn start() {
mut args_and_flags := util.join_env_vflags_and_os_args()[1..]
prefs, _ := pref.parse_args([], args_and_flags)
builder.compile('build', prefs, compile_wasm)
}
pub fn compile_wasm(mut b builder.Builder) {
mut files := b.get_builtin_files()
files << b.get_user_files()
b.set_module_lookup_paths()
if b.pref.is_verbose {
println('all .v files:')
println(files)
}
mut name := b.pref.out_name
if name.ends_with('/-') || name.ends_with(r'\-') || name == '-' {
name = '-'
} else if !name.ends_with('.wasm') {
name += '.wasm'
}
build_wasm(mut b, files, name)
}
pub fn build_wasm(mut b builder.Builder, v_files []string, out_file string) {
b.front_and_middle_stages(v_files) or { return }
util.timing_start('WebAssembly GEN')
wasm.gen(b.parsed_files, b.table, out_file, b.pref)
util.timing_measure('WebAssembly GEN')
}

View File

@ -35,5 +35,6 @@ fn (mut c Checker) postfix_expr(mut node ast.PostfixExpr) ast.Type {
} else {
node.auto_locked, _ = c.fail_if_immutable(node.expr)
}
node.typ = typ
return typ
}

View File

@ -196,6 +196,7 @@ fn (mut f Fmt) write_language_prefix(lang ast.Language) {
match lang {
.c { f.write('C.') }
.js { f.write('JS.') }
.wasm { f.write('WASM.') }
else {}
}
}

File diff suppressed because it is too large Load Diff

91
vlib/v/gen/wasm/cast.v Normal file
View File

@ -0,0 +1,91 @@
module wasm
import v.ast
import v.gen.wasm.binaryen
fn (mut g Gen) is_signed(typ ast.Type) bool {
if typ.is_pure_float() {
return true
}
return typ.is_signed()
}
fn (mut g Gen) unary_cast(from binaryen.Type, is_signed bool, to binaryen.Type) binaryen.Op {
if is_signed {
match from {
type_i32 {
match to {
type_i64 { return binaryen.extendsint32() }
type_f32 { return binaryen.convertsint32tofloat32() }
type_f64 { return binaryen.convertsint32tofloat64() }
else {}
}
}
type_i64 {
match to {
type_i32 { return binaryen.wrapint64() }
type_f32 { return binaryen.convertsint64tofloat32() }
type_f64 { return binaryen.convertsint64tofloat64() }
else {}
}
}
type_f32 {
match to {
type_i32 { return binaryen.truncsfloat32toint32() }
type_i64 { return binaryen.truncsfloat32toint64() }
type_f64 { return binaryen.promotefloat32() }
else {}
}
}
type_f64 {
match to {
type_i32 { return binaryen.truncsfloat64toint32() }
type_i64 { return binaryen.truncsfloat64toint64() }
type_f32 { return binaryen.demotefloat64() }
else {}
}
}
else {}
}
} else {
match from {
type_i32 {
match to {
type_i64 { return binaryen.extenduint32() }
type_f32 { return binaryen.convertuint32tofloat32() }
type_f64 { return binaryen.convertuint32tofloat64() }
else {}
}
}
type_i64 {
match to {
type_i32 { return binaryen.wrapint64() }
type_f32 { return binaryen.convertuint64tofloat32() }
type_f64 { return binaryen.convertuint64tofloat64() }
else {}
}
}
else {}
}
}
g.w_error('bad cast: from ${from} (is signed: ${is_signed}) to ${to}')
}
fn (mut g Gen) cast_t(expr binaryen.Expression, from ast.Type, to ast.Type) binaryen.Expression {
return g.cast(expr, g.get_wasm_type(from), g.is_signed(from), g.get_wasm_type(to))
}
fn (mut g Gen) cast(expr binaryen.Expression, from binaryen.Type, is_signed bool, to binaryen.Type) binaryen.Expression {
if from == to {
return expr
}
// In the official spec, integers are represented in twos complement.
// WebAssembly does not keep signedness information in it's types
// and uses instructions with variants for signed or unsigned values.
//
// You only need to know if the original type is signed or not to
// perform casting.
return binaryen.unary(g.mod, g.unary_cast(from, is_signed, to), expr)
}

1227
vlib/v/gen/wasm/gen.v Normal file

File diff suppressed because it is too large Load Diff

745
vlib/v/gen/wasm/mem.v Normal file
View File

@ -0,0 +1,745 @@
module wasm
import v.ast
import v.gen.wasm.binaryen
import strconv
type Var = Global | Stack | Temporary | ast.Ident
fn (v Var) ast_typ() int {
if v is Temporary {
return v.ast_typ
}
if v is Stack {
return v.ast_typ
}
if v is Global {
return v.ast_typ
}
panic('unreachable')
}
fn (v Var) address() int {
return (v as Stack).address
}
struct Temporary {
name string
typ binaryen.Type
ast_typ ast.Type
//
idx int
}
struct Stack {
name string
ast_typ ast.Type
//
address int
}
struct Global {
name string
ast_typ ast.Type
//
abs_address int
}
fn (g Gen) is_pure_type(typ ast.Type) bool {
if typ.is_pure_int() || typ.is_pure_float() || typ == ast.char_type_idx || typ.is_real_pointer()
|| typ.is_bool() {
return true
}
ts := g.table.sym(typ)
if ts.info is ast.Alias {
return g.is_pure_type(ts.info.parent_type)
}
return false
}
fn (mut g Gen) new_global_const(obj ast.ScopeObject) {
obj_expr := match obj {
ast.ConstField { obj.expr }
ast.GlobalField { obj.expr }
else { panic('unreachable') }
}
typ := ast.mktyp(obj.typ)
size, align := g.get_type_size_align(typ)
padding := (align - g.constant_data_offset % align) % align
g.globals[obj.name] = GlobalData{
init: obj_expr
ast_typ: typ
abs_address: g.constant_data_offset
}
g.constant_data_offset += size + padding
}
fn (mut g Gen) get_var_from_ident(ident ast.Ident) Var {
mut obj := ident.obj
if obj !in [ast.Var, ast.ConstField, ast.GlobalField, ast.AsmRegister] {
obj = ident.scope.find(ident.name) or { g.w_error('unknown variable ${ident.name}') }
}
match mut obj {
ast.Var {
if obj.name !in g.local_addresses {
return g.local_temporaries[g.get_local_temporary(obj.name)]
}
return g.local_addresses[obj.name]
}
ast.ConstField {
if obj.name !in g.globals {
g.new_global_const(obj)
}
return g.globals[obj.name].to_var(obj.name)
}
ast.GlobalField {
if obj.name !in g.globals {
g.new_global_const(obj)
}
return g.globals[obj.name].to_var(obj.name)
}
else {
g.w_error('unsupported variable type type:${obj} name:${ident.name}')
}
}
}
type LocalOrPointer = Var | binaryen.Expression
fn (mut g Gen) get_or_lea_lop(lp LocalOrPointer, expected ast.Type) binaryen.Expression {
size, _ := g.table.type_size(expected)
mut offset := 0
mut parent_typ := expected
mut is_expr := false
expr := match lp {
binaryen.Expression {
is_expr = true
lp
}
Var {
match lp {
Temporary {
parent_typ = lp.ast_typ
binaryen.localget(g.mod, lp.idx, g.get_wasm_type(expected))
}
Stack {
parent_typ = lp.ast_typ
offset = lp.address
g.get_bp()
}
Global {
parent_typ = lp.ast_typ
is_expr = true
g.literalint(lp.abs_address, ast.int_type)
}
else {
panic('unreachable')
}
}
}
}
if !is_expr && parent_typ == expected {
return expr
}
return binaryen.load(g.mod, u32(size), g.is_signed(expected), u32(offset), 0, g.get_wasm_type(expected),
expr, c'memory')
}
fn (mut g Gen) lea_lop(lp LocalOrPointer, expected ast.Type) binaryen.Expression {
expr := match lp {
binaryen.Expression {
lp
}
Var {
match lp {
Temporary {
binaryen.localget(g.mod, lp.idx, g.get_wasm_type(expected))
}
Stack {
g.get_bp()
}
Global {
g.literalint(lp.abs_address, ast.int_type)
}
else {
panic('unreachable')
}
}
}
}
return expr
}
fn (mut g Gen) lea_var_from_expr(node ast.Expr) binaryen.Expression {
var := g.get_var_from_expr(node)
return match var {
binaryen.Expression {
var
}
Var {
match var {
Temporary {
if g.is_pure_type(var.ast_typ) {
g.w_error('lea_var_from_expr: you cannot take the address of a pure temporary')
}
binaryen.localget(g.mod, var.idx, type_i32)
}
Stack {
g.lea_address(var.address)
}
Global {
g.literalint(var.abs_address, ast.int_type)
}
else {
panic('unreachable')
}
}
}
}
}
fn (mut g Gen) local_or_pointer_add_offset(v Var, offset int) LocalOrPointer {
return match v {
Temporary {
binaryen.binary(g.mod, binaryen.addint32(), binaryen.localget(g.mod, v.idx,
type_i32), binaryen.constant(g.mod, binaryen.literalint32(offset)))
}
Stack {
Var(Stack{
...v
address: v.address + offset
})
}
Global {
Var(Global{
...v
abs_address: v.abs_address + offset
})
}
ast.Ident {
panic('unreachable')
}
}
}
// TODO: return `Var | binaryen.Expression`
fn (mut g Gen) get_var_from_expr(node ast.Expr) LocalOrPointer {
return match node {
ast.Ident {
g.get_var_from_ident(node)
}
ast.SelectorExpr {
address := g.get_var_from_expr(node.expr)
offset := g.get_field_offset(node.expr_type, node.field_name)
match address {
binaryen.Expression {
binaryen.binary(g.mod, binaryen.addint32(), address, binaryen.constant(g.mod,
binaryen.literalint32(offset)))
}
Var {
g.local_or_pointer_add_offset(address, offset)
}
}
}
ast.IndexExpr {
address := g.get_var_from_expr(node.left)
ts := g.table.sym(node.left_type)
deref_type := if g.is_pure_type(node.left_type) {
node.left_type.set_nr_muls(node.left_type.nr_muls() - 1)
} else if ts.kind == .array_fixed {
(ts.info as ast.ArrayFixed).elem_type
} else {
node.left_type
}
size, _ := g.get_type_size_align(deref_type)
index := g.expr(node.index, ast.int_type)
mut ptr_address := binaryen.Expression(0)
if address is binaryen.Expression {
ptr_address = address
}
if address is Var {
ptr_address = g.get_var_t(address, ast.voidptr_type)
}
// ptr + index * size
binaryen.binary(g.mod, binaryen.addint32(), ptr_address, binaryen.binary(g.mod,
binaryen.mulint32(), index, g.literalint(size, ast.int_type)))
}
ast.PrefixExpr {
g.lea_lop(g.get_var_from_expr(node.right), ast.voidptr_type)
}
else {
g.w_error('get_var_from_expr: unexpected `${node.type_name()}`')
}
}
}
fn (mut g Gen) get_local_temporary(name string) int {
if g.local_temporaries.len == 0 {
g.w_error('get_local: g.local_temporaries.len == 0')
}
mut c := g.local_temporaries.len
for {
c--
if g.local_temporaries[c].name == name {
return c
}
if c == 0 {
break
}
}
g.w_error("get_local: cannot get '${name}'")
}
fn (mut g Gen) new_local_temporary_anon_wtyp(w_typ binaryen.Type) int {
ret := g.local_temporaries.len
g.local_temporaries << Temporary{
name: '_'
typ: w_typ
idx: ret
}
return ret
}
fn (mut g Gen) new_local_temporary_anon(typ ast.Type) int {
ret := g.local_temporaries.len
g.local_temporaries << Temporary{
name: '_'
typ: g.get_wasm_type(typ)
ast_typ: typ
idx: ret
}
return ret
}
fn (mut g Gen) new_local_temporary(name string, typ ast.Type) Temporary {
idx := g.local_temporaries.len
var := Temporary{
name: name
typ: g.get_wasm_type(typ)
ast_typ: typ
idx: idx
}
g.local_temporaries << var
return var
}
fn (mut g Gen) new_local(var ast.Ident, typ ast.Type) {
if g.is_pure_type(typ) {
g.new_local_temporary(var.name, typ)
return
}
ts := g.table.sym(typ)
match ts.info {
ast.Struct, ast.ArrayFixed {
g.allocate_local_var(var.name, typ)
}
ast.Enum {
g.new_local_temporary(var.name, ts.info.typ)
}
else {
g.w_error('new_local: type `${*ts}` (${ts.info.type_name()}) is not a supported local type')
}
}
}
fn (mut g Gen) deref(expr binaryen.Expression, expected ast.Type) binaryen.Expression {
size, _ := g.get_type_size_align(expected)
return binaryen.load(g.mod, u32(size), g.is_signed(expected), 0, 0, g.get_wasm_type(expected),
expr, c'memory')
}
fn (mut g Gen) get_field_offset(typ ast.Type, name string) int {
ts := g.table.sym(typ)
field := ts.find_field(name) or { g.w_error('could not find field `${name}` on init') }
if typ !in g.structs {
g.get_type_size_align(typ.idx())
}
return g.structs[typ.idx()].offsets[field.i]
}
fn (mut g Gen) get_type_size_align(typ ast.Type) (int, int) {
ts := g.table.sym(typ)
if ts.size != -1 && typ in g.structs {
return ts.size, ts.align
}
if ts.info !is ast.Struct {
return g.table.type_size(typ)
}
ti := ts.info as ast.Struct
// Code borrowed from native, hope you don't mind!
mut strc := StructInfo{}
mut size := 0
mut align := 1
for f in ti.fields {
f_size, f_align := g.table.type_size(f.typ)
if f_size == 0 {
strc.offsets << 0
continue
}
padding := (f_align - size % f_align) % f_align
strc.offsets << size + padding
size += f_size + padding
if f_align > align {
align = f_align
}
}
size = (size + align - 1) / align * align
g.structs[typ.idx()] = strc
mut ts_ := g.table.sym(typ)
ts_.size = size
ts_.align = align
return size, align
}
fn (mut g Gen) allocate_local_var(name string, typ ast.Type) int {
size, align := g.get_type_size_align(typ)
padding := (align - g.stack_frame % align) % align
address := g.stack_frame
g.stack_frame += size + padding
g.local_addresses[name] = Stack{
name: name
ast_typ: typ
address: address
}
return address
}
fn (mut g Gen) get_bp() binaryen.Expression {
return binaryen.localget(g.mod, g.bp_idx, type_i32)
}
fn (mut g Gen) lea_address(address int) binaryen.Expression {
return if address != 0 {
binaryen.binary(g.mod, binaryen.addint32(), g.get_bp(), binaryen.constant(g.mod,
binaryen.literalint32(address)))
} else {
g.get_bp()
}
}
// Will automatcally cast value from `var` to `ast_type`, will ignore if struct value.
// TODO: When supporting base types on the stack, actually cast them.
fn (mut g Gen) get_var_t(var Var, ast_typ ast.Type) binaryen.Expression {
return match var {
ast.Ident {
g.get_var_t(g.get_var_from_ident(var), ast_typ)
}
Temporary {
expr := binaryen.localget(g.mod, var.idx, var.typ)
g.cast_t(expr, var.ast_typ, ast_typ)
}
Stack {
address := if var.address != 0 {
binaryen.binary(g.mod, binaryen.addint32(), g.get_bp(), binaryen.constant(g.mod,
binaryen.literalint32(var.address)))
} else {
g.get_bp()
}
if g.is_pure_type(var.ast_typ) {
g.cast_t(g.deref(address, var.ast_typ), var.ast_typ, ast_typ)
} else {
address
}
}
Global {
address := g.literalint(var.abs_address, ast.int_type)
if g.is_pure_type(var.ast_typ) {
g.cast_t(g.deref(address, var.ast_typ), var.ast_typ, ast_typ)
} else {
address
}
}
}
}
[params]
struct SetConfig {
offset int
ast_typ ast.Type
}
fn (mut g Gen) set_to_address(address_expr binaryen.Expression, expr binaryen.Expression, ast_typ ast.Type) binaryen.Expression {
return if !g.is_pure_type(ast_typ) {
// `expr` is pointer
g.blit(expr, ast_typ, address_expr)
} else {
size, _ := g.table.type_size(ast_typ)
binaryen.store(g.mod, u32(size), 0, 0, address_expr, expr, g.get_wasm_type(ast_typ),
c'memory')
}
}
fn (mut g Gen) set_var_v(address Var, expr binaryen.Expression, cfg SetConfig) binaryen.Expression {
return g.set_var(address, expr, cfg)
}
fn (mut g Gen) set_var(address LocalOrPointer, expr binaryen.Expression, cfg SetConfig) binaryen.Expression {
ast_typ := if cfg.ast_typ != 0 {
cfg.ast_typ
} else {
(address as Var).ast_typ()
}
match address {
binaryen.Expression {
return g.set_to_address(address, expr, ast_typ)
}
Var {
var := address
return match var {
ast.Ident {
g.set_var(g.get_var_from_ident(var), expr, cfg)
}
Temporary {
binaryen.localset(g.mod, var.idx, expr)
}
Stack {
if !g.is_pure_type(ast_typ) {
// `expr` is pointer
g.blit_local(expr, ast_typ, var.address + cfg.offset)
} else {
size, _ := g.table.type_size(ast_typ)
// println("address: ${var.address}, offset: ${cfg.offset}")
binaryen.store(g.mod, u32(size), u32(var.address + cfg.offset),
0, g.get_bp(), expr, g.get_wasm_type(ast_typ), c'memory')
}
}
Global {
address_expr := g.literalint(var.abs_address + cfg.offset, ast.int_type)
g.set_to_address(address_expr, expr, ast_typ)
}
}
}
}
}
// zero out stack memory in known local `address`.
fn (mut g Gen) zero_fill(ast_typ ast.Type, address int) binaryen.Expression {
size, _ := g.get_type_size_align(ast_typ)
if size <= 4 {
zero := g.literalint(0, ast.int_type)
return binaryen.store(g.mod, u32(size), u32(address), 0, g.get_bp(), zero, type_i32,
c'memory')
} else if size <= 8 {
zero := g.literalint(0, ast.i64_type)
return binaryen.store(g.mod, u32(size), u32(address), 0, g.get_bp(), zero, type_i64,
c'memory')
}
return binaryen.memoryfill(g.mod, g.lea_address(address), g.literalint(0, ast.int_type),
g.literalint(size, ast.int_type), c'memory')
}
// `memcpy` from `ptr` to known local `address` in stack memory.
fn (mut g Gen) blit_local(ptr binaryen.Expression, ast_typ ast.Type, address int) binaryen.Expression {
size, _ := g.get_type_size_align(ast_typ)
return binaryen.memorycopy(g.mod, g.lea_address(address), ptr, binaryen.constant(g.mod,
binaryen.literalint32(size)), c'memory', c'memory')
}
// `memcpy` from `ptr` to `dest`
fn (mut g Gen) blit(ptr binaryen.Expression, ast_typ ast.Type, dest binaryen.Expression) binaryen.Expression {
size, _ := g.get_type_size_align(ast_typ)
return binaryen.memorycopy(g.mod, dest, ptr, binaryen.constant(g.mod, binaryen.literalint32(size)),
c'memory', c'memory')
}
fn (mut g Gen) init_struct(var Var, init ast.StructInit) binaryen.Expression {
match var {
ast.Ident {
return g.init_struct(g.get_var_from_ident(var), init)
}
Stack {
mut exprs := []binaryen.Expression{}
ts := g.table.sym(var.ast_typ)
match ts.info {
ast.Struct {
if init.fields.len == 0 && !(ts.info.fields.any(it.has_default_expr)) {
// Struct definition contains no default initialisers
// AND struct init contains no set values.
return g.mknblock('STRUCTINIT(ZERO)', [
g.zero_fill(var.ast_typ, var.address),
])
}
for i, f in ts.info.fields {
field_to_be_set := init.fields.map(it.name).contains(f.name)
fts := g.table.sym(f.typ)
if !field_to_be_set {
g.get_type_size_align(var.ast_typ)
offset := g.structs[var.ast_typ.idx()].offsets[i]
if f.has_default_expr {
init_expr := g.expr(f.default_expr, f.typ) // or `unaliased_typ`?
exprs << g.set_var_v(var, init_expr, ast_typ: f.typ, offset: offset)
} else {
if fts.info is ast.Struct {
exprs << g.init_struct(Stack{
address: var.address + offset
ast_typ: f.typ
}, ast.StructInit{})
} else {
exprs << g.zero_fill(f.typ, var.address + offset)
}
}
// TODO: replace invocations of `set_var` with `assign_expr_to_var`?
}
}
}
else {}
}
for f in init.fields {
field := ts.find_field(f.name) or {
g.w_error('could not find field `${f.name}` on init')
}
offset := g.structs[var.ast_typ.idx()].offsets[field.i]
initexpr := g.expr(f.expr, f.expected_type)
exprs << g.set_var_v(var, initexpr, ast_typ: f.expected_type, offset: offset)
}
return g.mknblock('STRUCTINIT', exprs)
}
else {}
}
panic('unreachable')
}
// From native, this should be taken out into `StringLiteral.eval_escape_codes()`
fn (mut g Gen) eval_escape_codes(str_lit ast.StringLiteral) string {
if str_lit.is_raw {
return str_lit.val
}
str := str_lit.val
mut buffer := []u8{}
mut i := 0
for i < str.len {
if str[i] != `\\` {
buffer << str[i]
i++
continue
}
// skip \
i++
match str[i] {
`\\`, `'`, `"` {
buffer << str[i]
i++
}
`a`, `b`, `f` {
buffer << str[i] - u8(90)
i++
}
`n` {
buffer << `\n`
i++
}
`r` {
buffer << `\r`
i++
}
`t` {
buffer << `\t`
i++
}
`u` {
i++
utf8 := strconv.parse_int(str[i..i + 4], 16, 16) or {
g.w_error('invalid \\u escape code (${str[i..i + 4]})')
0
}
i += 4
buffer << u8(utf8)
buffer << u8(utf8 >> 8)
}
`v` {
buffer << `\v`
i++
}
`x` {
i++
c := strconv.parse_int(str[i..i + 2], 16, 8) or {
g.w_error('invalid \\x escape code (${str[i..i + 2]})')
0
}
i += 2
buffer << u8(c)
}
`0`...`7` {
c := strconv.parse_int(str[i..i + 3], 8, 8) or {
g.w_error('invalid escape code \\${str[i..i + 3]}')
0
}
i += 3
buffer << u8(c)
}
else {
g.w_error('invalid escape code \\${str[i]}')
}
}
}
return buffer.bytestr()
}
struct ConstantData {
offset int
data []u8
}
fn (mut g Gen) constant_data_intern_offset(data []u8) ?(int, int) {
for d in g.constant_data {
if d.data == data {
return d.offset, d.data.len
}
}
return none
}
// (offset, len)
fn (mut g Gen) allocate_string(node ast.StringLiteral) (int, int) {
data := g.eval_escape_codes(node).bytes()
// `-prod` will only intern strings.
if g.pref.is_prod {
if offset, len := g.constant_data_intern_offset(data) {
return offset, len
}
}
offset := g.constant_data_offset
g.constant_data << ConstantData{
offset: offset
data: data
}
padding := (8 - offset % 8) % 8
g.constant_data_offset += data.len + padding
return offset, data.len
}

320
vlib/v/gen/wasm/ops.v Normal file
View File

@ -0,0 +1,320 @@
module wasm
import v.ast
import v.token
import v.gen.wasm.binaryen
const (
type_none = binaryen.typenone()
type_auto = binaryen.typeauto()
type_i32 = binaryen.typeint32()
type_i64 = binaryen.typeint64()
type_f32 = binaryen.typefloat32()
type_f64 = binaryen.typefloat64()
)
// "Register size" types such as int, i64 and bool boil down to their WASM counterparts.
// Structures and unions are pointers, i32.
fn (mut g Gen) get_wasm_type(typ_ ast.Type) binaryen.Type {
typ := ast.mktyp(typ_)
if typ == ast.void_type_idx {
return wasm.type_none
}
if typ.is_real_pointer() {
return wasm.type_i32
}
if typ in ast.number_type_idxs {
return match typ {
ast.isize_type_idx, ast.usize_type_idx, ast.i8_type_idx, ast.u8_type_idx,
ast.char_type_idx, ast.rune_type_idx, ast.i16_type_idx, ast.u16_type_idx,
ast.int_type_idx, ast.u32_type_idx {
wasm.type_i32
}
ast.i64_type_idx, ast.u64_type_idx, ast.int_literal_type_idx {
wasm.type_i64
}
ast.f32_type_idx {
wasm.type_f32
}
ast.f64_type_idx, ast.float_literal_type_idx {
wasm.type_f64
}
else {
wasm.type_i32
}
}
}
if typ == ast.bool_type_idx {
return wasm.type_i32
}
ts := g.table.sym(typ)
match ts.info {
ast.Struct {
g.get_type_size_align(typ)
return wasm.type_i32 // pointer
}
ast.MultiReturn {
// TODO: cache??
mut paraml := ts.info.types.map(g.get_wasm_type(it))
return binaryen.typecreate(paraml.data, paraml.len)
}
ast.Alias {
return g.get_wasm_type(ts.info.parent_type)
}
ast.ArrayFixed {
return wasm.type_i32
}
else {}
}
g.w_error("get_wasm_type: unreachable type '${*g.table.sym(typ)}' ${ts.info}")
}
fn infix_kind_return_bool(op token.Kind) bool {
return op in [.eq, .ne, .gt, .lt, .ge, .le]
}
fn (mut g Gen) infix_from_typ(typ ast.Type, op token.Kind) binaryen.Op {
wasm_typ := g.get_wasm_type(typ)
match wasm_typ {
wasm.type_i32 {
match op {
.plus {
return binaryen.addint32()
}
.minus {
return binaryen.subint32()
}
.mul {
return binaryen.mulint32()
}
.mod {
if typ.is_signed() {
return binaryen.remsint32()
} else {
return binaryen.remuint32()
}
}
.div {
if typ.is_signed() {
return binaryen.divsint32()
} else {
return binaryen.divuint32()
}
}
.eq {
return binaryen.eqint32()
}
.ne {
return binaryen.neint32()
}
.gt {
if typ.is_signed() {
return binaryen.gtsint32()
} else {
return binaryen.gtuint32()
}
}
.lt {
if typ.is_signed() {
return binaryen.ltsint32()
} else {
return binaryen.ltuint32()
}
}
.ge {
if typ.is_signed() {
return binaryen.gesint32()
} else {
return binaryen.geuint32()
}
}
.le {
if typ.is_signed() {
return binaryen.lesint32()
} else {
return binaryen.leuint32()
}
}
/*.logical_or {
return binaryen.orint32() // TODO: logical or
}*/
.xor {
return binaryen.xorint32()
}
.pipe {
return binaryen.orint32()
}
.amp {
return binaryen.andint32()
}
.left_shift {
return binaryen.shlint32()
}
.right_shift {
return binaryen.shrsint32()
}
.unsigned_right_shift {
return binaryen.shruint32()
}
else {}
}
}
wasm.type_i64 {
match op {
.plus {
return binaryen.addint64()
}
.minus {
return binaryen.subint64()
}
.mul {
return binaryen.mulint64()
}
.mod {
if typ.is_signed() {
return binaryen.remsint64()
} else {
return binaryen.remuint64()
}
}
.div {
if typ.is_signed() {
return binaryen.divsint64()
} else {
return binaryen.divuint64()
}
}
.eq {
return binaryen.eqint64()
}
.ne {
return binaryen.neint64()
}
.gt {
if typ.is_signed() {
return binaryen.gtsint64()
} else {
return binaryen.gtuint64()
}
}
.lt {
if typ.is_signed() {
return binaryen.ltsint64()
} else {
return binaryen.ltuint64()
}
}
.ge {
if typ.is_signed() {
return binaryen.gesint64()
} else {
return binaryen.geuint64()
}
}
.le {
if typ.is_signed() {
return binaryen.lesint64()
} else {
return binaryen.leuint64()
}
}
/*.logical_or {
return binaryen.orint64() // TODO: logical or
}*/
.xor {
return binaryen.xorint64()
}
.pipe {
return binaryen.orint64()
}
.amp {
return binaryen.andint64()
}
.left_shift {
return binaryen.shlint64()
}
.right_shift {
return binaryen.shrsint64()
}
.unsigned_right_shift {
return binaryen.shruint64()
}
else {}
}
}
wasm.type_f32 {
match op {
.plus {
return binaryen.addfloat32()
}
.minus {
return binaryen.subfloat32()
}
.mul {
return binaryen.mulfloat32()
}
.div {
return binaryen.divfloat32()
}
.eq {
return binaryen.eqfloat32()
}
.ne {
return binaryen.nefloat32()
}
.gt {
return binaryen.gtfloat32()
}
.lt {
return binaryen.ltfloat32()
}
.ge {
return binaryen.gefloat32()
}
.le {
return binaryen.lefloat32()
}
else {}
}
}
wasm.type_f64 {
match op {
.plus {
return binaryen.addfloat64()
}
.minus {
return binaryen.subfloat64()
}
.mul {
return binaryen.mulfloat64()
}
.div {
return binaryen.divfloat64()
}
.eq {
return binaryen.eqfloat64()
}
.ne {
return binaryen.nefloat64()
}
.gt {
return binaryen.gtfloat64()
}
.lt {
return binaryen.ltfloat64()
}
.ge {
return binaryen.gefloat64()
}
.le {
return binaryen.lefloat64()
}
else {}
}
}
else {}
}
g.w_error('bad infix: op `${op}`')
}

View File

@ -0,0 +1,161 @@
module wasm
import v.ast
import v.gen.wasm.binaryen
import encoding.binary as bin
import math.bits
fn (mut g Gen) bake_constants_plus_initialisers() []GlobalData {
mut initialisers := []GlobalData{}
for _, global in g.globals {
match global.init {
/*
ast.ArrayInit {
// TODO: call a seraliser recursively over all elements
if !global.init.is_fixed {
g.w_error('wasm backend does not support non fixed arrays yet')
}
for global.init
}*/
ast.BoolLiteral {
g.constant_data << ConstantData{
offset: global.abs_address
data: [u8(global.init.val)]
}
}
ast.FloatLiteral {
mut buf := []u8{len: 8}
wtyp := g.get_wasm_type(global.ast_typ)
match wtyp {
type_f32 {
bin.little_endian_put_u32(mut buf, bits.f32_bits(global.init.val.f32()))
}
type_f64 {
bin.little_endian_put_u64(mut buf, bits.f64_bits(global.init.val.f64()))
unsafe {
buf.len = 4
}
}
else {}
}
g.constant_data << ConstantData{
offset: global.abs_address
data: buf
}
}
ast.StringLiteral {
offset, len := g.allocate_string(global.init)
if g.table.sym(global.ast_typ).info !is ast.Struct {
mut buf := []u8{len: 4}
bin.little_endian_put_u32(mut buf, u32(offset))
g.constant_data << ConstantData{
offset: global.abs_address
data: buf
}
} else {
mut buf := []u8{len: 8}
bin.little_endian_put_u32(mut buf, u32(offset))
bin.little_endian_put_u32_at(mut buf, u32(len), 4)
g.constant_data << ConstantData{
offset: global.abs_address
data: buf
}
}
}
ast.IntegerLiteral {
mut buf := []u8{len: 8}
wtyp := g.get_wasm_type(global.ast_typ)
match wtyp {
type_i32 {
bin.little_endian_put_u32(mut buf, u32(global.init.val.int()))
}
type_i64 {
bin.little_endian_put_u64(mut buf, u64(global.init.val.i64()))
unsafe {
buf.len = 4
}
}
else {}
}
g.constant_data << ConstantData{
offset: global.abs_address
data: buf
}
}
else {
initialisers << global
}
}
}
return initialisers
}
fn round_up_to_multiple(val int, multiple int) int {
return val + (multiple - val % multiple) % multiple
}
fn (mut g Gen) make_vinit() binaryen.Function {
runtime_inits := g.bake_constants_plus_initialisers()
g.bare_function_start()
mut body := runtime_inits.map(g.set_var_v(it.to_var(''), g.expr(it.init, it.ast_typ)))
for mod_name in g.table.modules {
if mod_name == 'v.reflection' {
g.w_error('the wasm backend does not implement `v.reflection` yet')
}
init_fn_name := if mod_name != 'builtin' { '${mod_name}.init' } else { 'init' }
if _ := g.table.find_fn(init_fn_name) {
body << binaryen.call(g.mod, init_fn_name.str, unsafe { nil }, 0, type_none)
}
cleanup_fn_name := if mod_name != 'builtin' { '${mod_name}.cleanup' } else { 'cleanup' }
if _ := g.table.find_fn(cleanup_fn_name) {
body << binaryen.call(g.mod, cleanup_fn_name.str, unsafe { nil }, 0, type_none)
}
}
return g.bare_function('_vinit', g.mkblock(body))
}
fn (mut g Gen) housekeeping() {
// `_vinit` should be used to initialise the WASM module,
// then `main.main` can be called safely.
vinit := g.make_vinit()
if g.needs_stack || g.constant_data.len != 0 {
data := g.constant_data.map(it.data.data)
data_len := g.constant_data.map(it.data.len)
data_offsets := g.constant_data.map(binaryen.constant(g.mod, binaryen.literalint32(it.offset)))
passive := []bool{len: g.constant_data.len, init: false}
binaryen.setmemory(g.mod, 1, 4, c'memory', data.data, passive.data, data_offsets.data,
data_len.data, data.len, false, false, c'memory')
}
if g.needs_stack {
// `g.constant_data_offset` rounded up to a multiple of 1024
offset := round_up_to_multiple(g.constant_data_offset, 1024)
binaryen.addglobal(g.mod, c'__vsp', type_i32, true, g.literalint(offset, ast.int_type))
}
if g.pref.os == .wasi {
main_expr := g.mkblock([binaryen.call(g.mod, c'_vinit', unsafe { nil }, 0, type_none),
binaryen.call(g.mod, c'main.main', unsafe { nil }, 0, type_none)])
binaryen.addfunction(g.mod, c'_start', type_none, type_none, unsafe { nil }, 0,
main_expr)
binaryen.addfunctionexport(g.mod, c'_start', c'_start')
} else {
// In `browser` mode, and function can be exported and called regardless.
// To avoid uninitialised data, `_vinit` is set to be ran immediately on
// WASM module creation.
binaryen.setstart(g.mod, vinit)
}
}

View File

@ -0,0 +1,86 @@
fn mul(a i64, unsigned u32) f64 {
mut one := a
one *= 2 / unsigned
return one
}
fn typ(a int) i64 {
mut one := a
sec := one + i64(a)
return sec
}
pub fn gcd(a_ i64, b_ i64) i64 {
mut a := a_
mut b := b_
if a < 0 {
a = -a
}
if b < 0 {
b = -b
}
for b != 0 {
a %= b
if a == 0 {
return b
}
b %= a
}
return a
}
pub fn lcm(a i64, b i64) i64 {
if a == 0 {
return a
}
res := a * (b / gcd(b, a))
if res < 0 {
return -res
}
return res
}
pub fn inc(a f64) int {
mut b := a
b++
b--
return int(b)
}
pub fn negate(a int) i64 {
return ~a + 1
}
pub fn powi(a i64, b i64) i64 {
mut b_ := b
mut p := a
mut v := i64(1)
if b_ < 0 { // exponent < 0
if a == 0 {
return -1 // division by 0
}
return if a * a != 1 {
0
} else {
if (b_ & 1) > 0 {
a
} else {
1
}
}
}
for b_ > 0 {
if b_ & 1 > 0 {
v *= p
}
p *= p
b_ >>= 1
}
return v
}

View File

@ -0,0 +1,28 @@
struct TEST {
a int
b i64
}
fn static_arrays() {
a := [8]int{}
b := [10, 12, 150]!
c := [TEST{}, TEST{
b: 10
}]!
}
fn index_expression() {
b := [10, 12, 150]!
a := b[2]
c := 'hello'[4]
d := c'hello'[2]
}
fn test_this(index int) int {
a := 'hello'
if index < a.len && index >= 0 {
return a[index]
}
return 10
}

View File

@ -0,0 +1,18 @@
fn test() {
print('hello!')
println('hello!')
panic('nooo!')
}
fn str_methods() {
print(128.str())
println(i64(-192322).str())
println(false.str())
}
fn str_implicit() {
println(false)
println(true)
a := 100
println(a + 10)
}

View File

@ -0,0 +1,91 @@
fn func(a int, cond bool) i64 {
mut src := 0
if cond {
src = a
} else if cond {
src = 22
} else if cond {
src = 25
}
if cond {
src = a
} else if cond {
src = 22
} else if cond {
src = 25
} else {
src = src + src
}
return src
}
fn test(cond bool) int {
return if cond {
2
} else if !cond {
5
} else {
6
}
}
fn boolfor() int {
mut val := 0
for val == 0 {
val++
}
return val
}
fn inffor() int {
mut val := 0
for {
if val != 0 {
break
}
val++
}
return val
}
fn addcfor() int {
mut val := 0
for i := 0; i < 10; i++ {
val += i
}
return val
}
fn labelcfor() {
mut val := 0
hello: for {
for {
if val == 10 {
continue hello
}
val++
if val == 100 {
break hello
}
}
break
}
}
fn infcfor() int {
mut val := 0
for i := 0; true; i++ {
if val >= 10 {
return val
}
val += i
}
return 0
}

View File

@ -0,0 +1,57 @@
enum Hello as u64 {
a
b
c = 20 + 10
d
e
}
fn enums() {
mut a := Hello.a
a = .c
}
struct AA {
a u8
b i64
}
fn of() {
a := __offsetof(AA, b)
b := sizeof(AA)
}
fn constant() int {
return 100
}
const hello = 'hello\n'
const float = 1.0
const integer = 888
const runtime_init = constant()
struct EE {
a int
b int
}
fn ptr_arith() {
mut a := EE{}
mut b := &a.b
unsafe {
*b = 12
}
println(a.b.str())
unsafe {
*b = 14
}
println(a.b.str())
unsafe {
*b = 102
}
println((*b).str())
}

View File

@ -0,0 +1,30 @@
fn multi(a i16) i64 {
one, two := a, 10
return one + two
}
pub fn multireturn(a int) (int, f64, i64) {
return 2, a + 2, 10 - a
}
pub fn test() (int, int) {
return 25, 15
}
pub fn accept() int {
mut a, _ := test()
a += 20
return a
}
pub fn side_effect() int {
return 22
}
pub fn test_side_effect() int {
mut a := 15
_, a = side_effect(), 10
return a
}

View File

@ -0,0 +1,179 @@
struct AA {
a int = 22
b i64
}
pub fn zeroed() {
_ := AA{}
}
pub fn field() {
_ := AA{
a: 23
}
}
pub fn selector(input int) int {
mut a := AA{}
c := 10 + a.a
return c
}
pub fn reassign(input int) int {
mut a := AA{}
a = AA{
b: input
}
return int(a.b + input)
}
struct BB {
mut:
a i64 = 22
b i64
c i64
}
pub fn give(val int) int {
mut a := BB{}
a.b = val
return take(a)
}
pub fn take(input BB) int {
return int(input.b)
}
struct BB_ {
mut:
a i64 = 22
b AA_
}
struct AA_ {
mut:
a i64 = 91
b i64 = 92
c i64 = 93
}
fn e() BB_ {
return BB_{
a: 2
}
}
pub fn make(nval AA_) i64 {
val := BB_{
b: nval
}
return val.b.b
}
pub fn return_make(nval int) int {
val := make(AA_{ b: nval })
return int(val)
}
fn my_func(val int) AA_ {
return AA_{
b: val
}
}
fn accept() {
my_func(20)
}
struct CC {
mut:
a i64 = 91
b i64 = 92
c i64 = 93
}
fn my_func_multi(val int) (CC, CC) {
return CC{
b: val
}, CC{
a: val
}
}
pub fn accept_multi(val int) int {
a, b := my_func_multi(val)
return int(a.b + b.a)
}
struct Vector {
x int
y int
}
fn add(a Vector, b Vector) Vector {
return Vector{a.x + b.x, a.y + b.y}
}
pub fn test(a int, b int) (int, int) {
vec := Vector{a, b}
ret := add(vec, Vector{10, 5})
return ret.x, ret.y
}
struct Aello {
a int
b i64
c int
}
struct Hello {
a int = 20
b Aello
c int = 222
}
pub fn recurse() {
a := Hello{}
}
struct DD {
x int
y int
}
fn (a DD) + (b DD) DD {
return DD{a.x + b.x, a.y + b.y}
}
pub fn valer() (int, int) {
mut a := DD{10, 15}
a += DD{10, 15}
return a.x, a.y
}
struct TEST {
mut:
a int
b int
}
fn postfix_test() {
mut a := TEST{}
a.b++
a.a++
}
fn postfix_test_mut(mut a TEST) {
a.b++
}

View File

@ -0,0 +1,78 @@
import os
import benchmark
import term
const is_verbose = os.getenv('VTEST_SHOW_CMD') != ''
// TODO some logic copy pasted from valgrind_test.v and compiler_test.v, move to a module
fn test_wasm() {
mut bench := benchmark.new_benchmark()
vexe := os.getenv('VEXE')
vroot := os.dir(vexe)
dir := os.join_path(vroot, 'vlib/v/gen/wasm/tests')
files := os.ls(dir) or { panic(err) }
//
wrkdir := os.join_path(os.vtmp_dir(), 'v', 'tests', 'wasm')
os.mkdir_all(wrkdir) or { panic(err) }
defer {
os.rmdir_all(wrkdir) or {}
}
os.chdir(wrkdir) or {}
tests := files.filter(it.ends_with('.vv'))
if tests.len == 0 {
println('no wasm tests found')
assert false
}
bench.set_total_expected_steps(tests.len)
for test in tests {
bench.step()
full_test_path := os.real_path(os.join_path(dir, test))
test_file_name := os.file_name(test)
relative_test_path := full_test_path.replace(vroot + '/', '')
work_test_path := '${wrkdir}/${test_file_name}'
tmperrfile := '${dir}/${test}.tmperr'
outfile := '${dir}/${test}.out'
// force binaryen to print without colour
cmd := '${os.quoted_path(vexe)} -o - -b wasm ${os.quoted_path(full_test_path)} 2> ${os.quoted_path(tmperrfile)}'
if is_verbose {
println(cmd)
}
res_wasm := os.execute(cmd)
if res_wasm.exit_code != 0 {
bench.fail()
eprintln(bench.step_message_fail(cmd))
if os.exists(tmperrfile) {
err := os.read_file(tmperrfile) or { panic(err) }
eprintln(err)
}
continue
}
os.rm(tmperrfile) or {}
if expected_ := os.read_file(outfile) {
mut expected := expected_
expected = expected.trim_right('\r\n').replace('\r\n', '\n')
mut found := res_wasm.output.trim_right('\r\n').replace('\r\n', '\n')
found = found.trim_space()
if expected != found {
println(term.red('FAIL'))
println('============')
println('expected: "${expected}" len=${expected.len}')
println('============')
println('found:"${found}" len=${found.len}')
println('============\n')
bench.fail()
continue
}
}
bench.ok()
eprintln(bench.step_message_ok(relative_test_path))
}
bench.stop()
eprintln(term.h_divider('-'))
eprintln(bench.total_message('wasm'))
if bench.nfail > 0 {
exit(1)
}
}

View File

@ -14,6 +14,8 @@ pub fn (mut p Parser) call_expr(language ast.Language, mod string) ast.CallExpr
'C.${p.check_name()}'
} else if language == .js {
'JS.${p.check_js_name()}'
} else if language == .wasm {
'WASM.${p.check_name()}'
} else if mod.len > 0 {
'${mod}.${p.check_name()}'
} else {
@ -242,6 +244,8 @@ fn (mut p Parser) fn_decl() ast.FnDecl {
language = .c
} else if p.tok.kind == .name && p.tok.lit == 'JS' {
language = .js
} else if p.tok.kind == .name && p.tok.lit == 'WASM' {
language = .wasm
}
p.fn_language = language
if language != .v {
@ -474,6 +478,8 @@ run them via `v file.v` instead',
name = 'C.${name}'
} else if language == .js {
name = 'JS.${name}'
} else if language == .wasm {
name = 'WASM.${name}'
} else {
name = p.prepend_mod(name)
}

View File

@ -300,6 +300,8 @@ pub fn (mut p Parser) parse_language() ast.Language {
ast.Language.c
} else if p.tok.lit == 'JS' {
ast.Language.js
} else if p.tok.lit == 'WASM' {
ast.Language.wasm
} else {
ast.Language.v
}

View File

@ -2387,6 +2387,9 @@ pub fn (mut p Parser) name_expr() ast.Expr {
} else if p.tok.lit == 'JS' {
language = ast.Language.js
p.check_for_impure_v(language, p.tok.pos())
} else if p.tok.lit == 'WASM' {
language = ast.Language.wasm
p.check_for_impure_v(language, p.tok.pos())
}
mut mod := ''
// p.warn('resetting')
@ -2484,6 +2487,8 @@ pub fn (mut p Parser) name_expr() ast.Expr {
mod = 'C'
} else if language == .js {
mod = 'JS'
} else if language == .wasm {
mod = 'WASM'
} else {
if p.tok.lit in p.imports {
// mark the imported module as used
@ -3421,6 +3426,12 @@ fn (mut p Parser) module_decl() ast.Module {
'translated' {
p.is_translated = true
}
'wasm_import_namespace' {
if !p.pref.is_fmt && p.pref.backend != .wasm {
p.error_with_pos('[wasm_import_namespace] is allowed only in the wasm backend',
ma.pos)
}
}
else {
p.error_with_pos('unknown module attribute `[${ma.name}]`', ma.pos)
return mod_node
@ -3899,6 +3910,7 @@ fn (mut p Parser) enum_decl() ast.EnumDecl {
is_flag: is_flag
is_multi_allowed: is_multi_allowed
uses_exprs: uses_exprs
typ: enum_type
}
is_pub: is_pub
})

View File

@ -29,6 +29,8 @@ fn (mut p Parser) struct_decl(is_anon bool) ast.StructDecl {
ast.Language.c
} else if p.tok.lit == 'JS' && p.peek_tok.kind == .dot {
ast.Language.js
} else if p.tok.lit == 'WASM' && p.peek_tok.kind == .dot {
ast.Language.wasm
} else {
ast.Language.v
}
@ -88,6 +90,9 @@ fn (mut p Parser) struct_decl(is_anon bool) ast.StructDecl {
} else if language == .js {
name = 'JS.${name}'
orig_name = name
} else if language == .wasm {
name = 'WASM.${name}'
orig_name = name
} else {
name = p.prepend_mod(name)
}
@ -510,7 +515,7 @@ fn (mut p Parser) interface_decl() ast.InterfaceDecl {
ast.Language.v
}
if language != .v {
p.next() // C || JS
p.next() // C || JS | WASM
p.next() // .
}
name_pos := p.tok.pos()

View File

@ -112,7 +112,7 @@ pub fn (mut p Preferences) fill_with_defaults() {
}
if p.os == ._auto {
// No OS specifed? Use current system
p.os = get_host_os()
p.os = if p.backend != .wasm { get_host_os() } else { .wasi }
}
//
p.try_to_use_tcc_by_default()

View File

@ -27,6 +27,8 @@ pub enum OS {
wasm32
wasm32_emscripten
wasm32_wasi
browser // -b wasm -os browser
wasi // -b wasm -os wasi
raw
all
}
@ -107,6 +109,13 @@ pub fn os_from_string(os_str string) !OS {
'wasm32_emscripten' {
return .wasm32_emscripten
}
// Native WASM options:
'browser' {
return .browser
}
'wasi' {
return .wasi
}
else {
// handle deprecated names:
match os_str {
@ -148,6 +157,8 @@ pub fn (o OS) str() string {
.wasm32 { return 'WebAssembly' }
.wasm32_emscripten { return 'WebAssembly(Emscripten)' }
.wasm32_wasi { return 'WebAssembly(WASI)' }
.browser { return 'browser' }
.wasi { return 'wasi' }
.raw { return 'Raw' }
.all { return 'all' }
}

View File

@ -53,6 +53,7 @@ pub enum Backend {
js_browser // The JavaScript browser backend
js_freestanding // The JavaScript freestanding backend
native // The Native backend
wasm // The WebAssembly backend
}
pub fn (b Backend) is_js() bool {
@ -694,7 +695,7 @@ pub fn parse_args_and_show_errors(known_external_commands []string, args []strin
res.build_options << '${arg} ${sbackend}'
b := backend_from_string(sbackend) or {
eprintln('Unknown V backend: ${sbackend}')
eprintln('Valid -backend choices are: c, go, interpret, js, js_node, js_browser, js_freestanding, native')
eprintln('Valid -backend choices are: c, go, interpret, js, js_node, js_browser, js_freestanding, native, wasm')
exit(1)
}
if b.is_js() {
@ -786,6 +787,14 @@ pub fn parse_args_and_show_errors(known_external_commands []string, args []strin
eprintln_cond(show_output, "Note: building an optimized binary takes much longer. It shouldn't be used with `v run`.")
eprintln_cond(show_output, 'Use `v run` without optimization, or build an optimized binary with -prod first, then run it separately.')
}
if res.os in [.browser, .wasi] && res.backend != .wasm {
eprintln('OS `${res.os}` forbidden for backends other than wasm')
exit(1)
}
if res.backend == .wasm && res.os !in [.browser, .wasi, ._auto] {
eprintln('Native WebAssembly backend OS must be `browser` or `wasi`')
exit(1)
}
// res.use_cache = true
if command != 'doc' && res.out_name.ends_with('.v') {
@ -980,6 +989,7 @@ pub fn backend_from_string(s string) !Backend {
'js_browser' { return .js_browser }
'js_freestanding' { return .js_freestanding }
'native' { return .native }
'wasm' { return .wasm }
else { return error('Unknown backend type ${s}') }
}
}