mirror of https://github.com/vlang/v.git
847 lines
18 KiB
V
847 lines
18 KiB
V
// 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 wasm
|
|
import v.ast
|
|
import v.gen.wasm.serialise
|
|
import encoding.binary
|
|
|
|
pub struct Var {
|
|
name string
|
|
mut:
|
|
typ ast.Type
|
|
idx wasm.LocalIndex
|
|
is_address bool
|
|
is_global bool
|
|
g_idx wasm.GlobalIndex
|
|
offset int
|
|
}
|
|
|
|
pub 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 g.local_vars.len == 0 {
|
|
g.w_error('get_var_from_ident: g.local_vars.len == 0')
|
|
}
|
|
mut c := g.local_vars.len
|
|
for {
|
|
c--
|
|
if g.local_vars[c].name == obj.name {
|
|
return g.local_vars[c]
|
|
}
|
|
if c == 0 {
|
|
break
|
|
}
|
|
}
|
|
g.w_error('get_var_from_ident: unreachable, variable not found')
|
|
}
|
|
ast.ConstField {
|
|
if gbl := g.global_vars[obj.name] {
|
|
return gbl.v
|
|
}
|
|
gbl := g.new_global(obj.name, obj.typ, obj.expr, false)
|
|
g.global_vars[obj.name] = gbl
|
|
return gbl.v
|
|
}
|
|
ast.GlobalField {
|
|
if gbl := g.global_vars[obj.name] {
|
|
return gbl.v
|
|
}
|
|
gbl := g.new_global(obj.name, obj.typ, obj.expr, true)
|
|
g.global_vars[obj.name] = gbl
|
|
return gbl.v
|
|
}
|
|
else {
|
|
g.w_error('unsupported variable type type:${obj} name:${ident.name}')
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn (mut g Gen) get_var_from_expr(node ast.Expr) ?Var {
|
|
match node {
|
|
ast.Ident {
|
|
return g.get_var_from_ident(node)
|
|
}
|
|
ast.ParExpr {
|
|
return g.get_var_from_expr(node.expr)
|
|
}
|
|
ast.SelectorExpr {
|
|
mut addr := g.get_var_from_expr(node.expr) or {
|
|
// if place {
|
|
// g.field_offset(node.expr_type, node.field_name)
|
|
// }
|
|
return none
|
|
}
|
|
addr.is_address = true
|
|
|
|
offset := g.get_field_offset(node.expr_type, node.field_name)
|
|
return g.offset(addr, node.typ, offset)
|
|
}
|
|
else {
|
|
// g.w_error('get_var_from_expr: unexpected `${node.type_name()}`')
|
|
return none
|
|
}
|
|
}
|
|
}
|
|
|
|
// ONLY call this with the LHS of an assign, an lvalue
|
|
// use get_var_from_expr in all other cases
|
|
pub fn (mut g Gen) get_var_or_make_from_expr(node ast.Expr, typ ast.Type) Var {
|
|
if v := g.get_var_from_expr(node) {
|
|
return v
|
|
}
|
|
|
|
mut v := g.new_local('__tmp', ast.voidptr_type)
|
|
g.needs_address = true
|
|
{
|
|
g.set_with_expr(node, v)
|
|
}
|
|
g.needs_address = false
|
|
|
|
v.typ = typ
|
|
v.is_address = true
|
|
|
|
return v
|
|
}
|
|
|
|
pub fn (mut g Gen) bp() wasm.LocalIndex {
|
|
if g.bp_idx == -1 {
|
|
g.bp_idx = g.func.new_local_named(.i32_t, '__vbp')
|
|
}
|
|
return g.bp_idx
|
|
}
|
|
|
|
pub fn (mut g Gen) sp() wasm.GlobalIndex {
|
|
if sp := g.sp_global {
|
|
return sp
|
|
}
|
|
// (64KiB - 1KiB) of stack space, grows downwards.
|
|
// Memory addresses from 0 to 1024 are forbidden.
|
|
g.sp_global = g.mod.new_global('__vsp', false, .i32_t, true, wasm.constexpr_value(0))
|
|
return g.sp()
|
|
}
|
|
|
|
pub fn (mut g Gen) new_local(name string, typ_ ast.Type) Var {
|
|
mut typ := typ_
|
|
ts := g.table.sym(typ)
|
|
|
|
match ts.info {
|
|
ast.Enum {
|
|
typ = ts.info.typ
|
|
}
|
|
ast.Alias {
|
|
typ = ts.info.parent_type
|
|
}
|
|
else {}
|
|
}
|
|
|
|
is_address := !g.is_pure_type(typ)
|
|
wtyp := g.get_wasm_type(typ)
|
|
|
|
mut v := Var{
|
|
name: name
|
|
typ: typ
|
|
is_address: is_address
|
|
}
|
|
|
|
if !is_address {
|
|
v.idx = g.func.new_local_named(wtyp, g.dbg_type_name(name, typ_))
|
|
g.local_vars << v
|
|
return v
|
|
}
|
|
v.idx = g.bp()
|
|
|
|
// allocate memory, then assign an offset
|
|
//
|
|
match ts.info {
|
|
ast.Struct, ast.ArrayFixed {
|
|
size, align := g.pool.type_size(typ)
|
|
padding := calc_padding(g.stack_frame, align)
|
|
address := g.stack_frame
|
|
g.stack_frame += size + padding
|
|
|
|
v.offset = address
|
|
}
|
|
else {
|
|
g.w_error('new_local: type `${*ts}` (${ts.info.type_name()}) is not a supported local type')
|
|
}
|
|
}
|
|
g.local_vars << v
|
|
return v
|
|
}
|
|
|
|
pub fn (mut g Gen) literal_to_constant_expression(typ_ ast.Type, init ast.Expr) ?wasm.ConstExpression {
|
|
typ := ast.mktyp(typ_)
|
|
match init {
|
|
ast.BoolLiteral {
|
|
return wasm.constexpr_value(int(init.val))
|
|
}
|
|
ast.CharLiteral {
|
|
return wasm.constexpr_value(int(init.val.runes()[0]))
|
|
}
|
|
ast.FloatLiteral {
|
|
if typ == ast.f32_type {
|
|
return wasm.constexpr_value(init.val.f32())
|
|
} else if typ == ast.f64_type {
|
|
return wasm.constexpr_value(init.val.f64())
|
|
}
|
|
}
|
|
ast.IntegerLiteral {
|
|
if !typ.is_pure_int() {
|
|
return none
|
|
}
|
|
t := g.get_wasm_type(typ)
|
|
match t {
|
|
.i32_t { return wasm.constexpr_value(init.val.int()) }
|
|
.i64_t { return wasm.constexpr_value(init.val.i64()) }
|
|
else {}
|
|
}
|
|
}
|
|
else {}
|
|
}
|
|
return none
|
|
}
|
|
|
|
pub fn (mut g Gen) new_global(name string, typ_ ast.Type, init ast.Expr, is_global_mut bool) Global {
|
|
mut typ := typ_
|
|
ts := g.table.sym(typ)
|
|
|
|
match ts.info {
|
|
ast.Enum {
|
|
typ = ts.info.typ
|
|
}
|
|
ast.Alias {
|
|
typ = ts.info.parent_type
|
|
}
|
|
else {}
|
|
}
|
|
|
|
mut is_mut := false
|
|
is_address := !g.is_pure_type(typ)
|
|
mut init_expr := ?ast.Expr(none)
|
|
|
|
cexpr := if cexpr_v := g.literal_to_constant_expression(unpack_literal_int(typ), init) {
|
|
is_mut = is_global_mut
|
|
cexpr_v
|
|
} else {
|
|
// Isn't a literal ...
|
|
if is_address {
|
|
// ... allocate memory and append
|
|
pos, is_init := g.pool.append(init, typ)
|
|
if !is_init {
|
|
// ... AND wait for init in `_vinit`
|
|
init_expr = init
|
|
}
|
|
wasm.constexpr_value(g.data_base + pos)
|
|
} else {
|
|
// ... wait for init in `_vinit`
|
|
init_expr = init
|
|
is_mut = true
|
|
|
|
t := g.get_wasm_type(typ)
|
|
wasm.constexpr_value_zero(t)
|
|
}
|
|
}
|
|
|
|
mut glbl := Global{
|
|
init: init_expr
|
|
v: Var{
|
|
name: name
|
|
typ: typ
|
|
is_address: is_address
|
|
is_global: true
|
|
g_idx: g.mod.new_global(g.dbg_type_name(name, typ), false, g.get_wasm_type_int_literal(typ),
|
|
is_mut, cexpr)
|
|
}
|
|
}
|
|
|
|
return glbl
|
|
}
|
|
|
|
// is_pure_type(voidptr) == true
|
|
// is_pure_type(&Struct) == false
|
|
pub 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_any_kind_of_pointer() || typ.is_bool() {
|
|
return true
|
|
}
|
|
ts := g.table.sym(typ)
|
|
match ts.info {
|
|
ast.Alias {
|
|
return g.is_pure_type(ts.info.parent_type)
|
|
}
|
|
ast.Enum {
|
|
return g.is_pure_type(ts.info.typ)
|
|
}
|
|
else {}
|
|
}
|
|
return false
|
|
}
|
|
|
|
pub fn log2(size int) int {
|
|
return match size {
|
|
1 { 0 }
|
|
2 { 1 }
|
|
4 { 2 }
|
|
8 { 3 }
|
|
else { panic('unreachable') }
|
|
}
|
|
}
|
|
|
|
pub fn (mut g Gen) load(typ ast.Type, offset int) {
|
|
size, align := g.pool.type_size(typ)
|
|
wtyp := g.as_numtype(g.get_wasm_type(typ))
|
|
|
|
match size {
|
|
1 { g.func.load8(wtyp, typ.is_signed(), log2(align), offset) }
|
|
2 { g.func.load16(wtyp, typ.is_signed(), log2(align), offset) }
|
|
else { g.func.load(wtyp, log2(align), offset) }
|
|
}
|
|
}
|
|
|
|
pub fn (mut g Gen) store(typ ast.Type, offset int) {
|
|
size, align := g.pool.type_size(typ)
|
|
wtyp := g.as_numtype(g.get_wasm_type(typ))
|
|
|
|
match size {
|
|
1 { g.func.store8(wtyp, log2(align), offset) }
|
|
2 { g.func.store16(wtyp, log2(align), offset) }
|
|
else { g.func.store(wtyp, log2(align), offset) }
|
|
}
|
|
}
|
|
|
|
pub fn (mut g Gen) get(v Var) {
|
|
if v.is_global {
|
|
g.func.global_get(v.g_idx)
|
|
} else {
|
|
g.func.local_get(v.idx)
|
|
}
|
|
|
|
if v.is_address && g.is_pure_type(v.typ) {
|
|
g.load(v.typ, v.offset)
|
|
} else if v.is_address && v.offset != 0 {
|
|
g.func.i32_const(v.offset)
|
|
g.func.add(.i32_t)
|
|
}
|
|
}
|
|
|
|
pub fn (mut g Gen) mov(to Var, v Var) {
|
|
if !v.is_address || g.is_pure_type(v.typ) {
|
|
g.get(v)
|
|
g.cast(v.typ, to.typ)
|
|
g.set(to)
|
|
return
|
|
}
|
|
|
|
size, _ := g.pool.type_size(v.typ)
|
|
|
|
if size > 16 {
|
|
g.ref(to)
|
|
g.ref(v)
|
|
g.func.i32_const(size)
|
|
g.func.memory_copy()
|
|
return
|
|
}
|
|
|
|
mut sz := size
|
|
mut oz := 0
|
|
for sz > 0 {
|
|
g.ref_ignore_offset(to)
|
|
g.ref_ignore_offset(v)
|
|
if sz - 8 >= 0 {
|
|
g.load(ast.u64_type_idx, v.offset + oz)
|
|
g.store(ast.u64_type_idx, to.offset + oz)
|
|
sz -= 8
|
|
oz += 8
|
|
} else if sz - 4 >= 0 {
|
|
g.load(ast.u32_type_idx, v.offset + oz)
|
|
g.store(ast.u32_type_idx, to.offset + oz)
|
|
sz -= 4
|
|
oz += 4
|
|
} else if sz - 2 >= 0 {
|
|
g.load(ast.u16_type_idx, v.offset + oz)
|
|
g.store(ast.u16_type_idx, to.offset + oz)
|
|
sz -= 2
|
|
oz += 2
|
|
} else if sz - 1 >= 0 {
|
|
g.load(ast.u8_type_idx, v.offset + oz)
|
|
g.store(ast.u8_type_idx, to.offset + oz)
|
|
sz -= 1
|
|
oz += 1
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn (mut g Gen) set_prepare(v Var) {
|
|
if !v.is_address {
|
|
return
|
|
}
|
|
|
|
if g.is_pure_type(v.typ) {
|
|
if v.is_global {
|
|
g.func.global_get(v.g_idx)
|
|
} else {
|
|
g.func.local_get(v.idx)
|
|
}
|
|
return
|
|
}
|
|
}
|
|
|
|
pub fn (mut g Gen) set_set(v Var) {
|
|
if !v.is_address {
|
|
if v.is_global {
|
|
g.func.global_set(v.g_idx)
|
|
} else {
|
|
g.func.local_set(v.idx)
|
|
}
|
|
return
|
|
}
|
|
|
|
if g.is_pure_type(v.typ) {
|
|
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_set(from.idx)
|
|
g.mov(v, from)
|
|
}
|
|
|
|
// set structures with pointer, memcpy
|
|
// set pointers with value, get local, store value
|
|
// set value, set local
|
|
// -- set works with a single value present on the stack beforehand
|
|
// -- not optimial for copying stack memory or shuffling structs
|
|
// -- use mov instead
|
|
pub fn (mut g Gen) set(v Var) {
|
|
if !v.is_address {
|
|
if v.is_global {
|
|
g.func.global_set(v.g_idx)
|
|
} else {
|
|
g.func.local_set(v.idx)
|
|
}
|
|
return
|
|
}
|
|
|
|
if g.is_pure_type(v.typ) {
|
|
l := g.new_local('__tmp', v.typ)
|
|
g.func.local_set(l.idx)
|
|
|
|
if v.is_global {
|
|
g.func.global_get(v.g_idx)
|
|
} else {
|
|
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_set(from.idx)
|
|
g.mov(v, from)
|
|
}
|
|
|
|
pub fn (mut g Gen) ref(v Var) {
|
|
g.ref_ignore_offset(v)
|
|
|
|
if v.offset != 0 {
|
|
g.func.i32_const(v.offset)
|
|
g.func.add(.i32_t)
|
|
}
|
|
}
|
|
|
|
pub fn (mut g Gen) ref_ignore_offset(v Var) {
|
|
if !v.is_address {
|
|
panic('unreachable')
|
|
}
|
|
|
|
if v.is_global {
|
|
g.func.global_get(v.g_idx)
|
|
} else {
|
|
g.func.local_get(v.idx)
|
|
}
|
|
}
|
|
|
|
// creates a new pointer variable with the offset `offset` and type `typ`
|
|
pub fn (mut g Gen) offset(v Var, typ ast.Type, offset int) Var {
|
|
if !v.is_address {
|
|
panic('unreachable')
|
|
}
|
|
|
|
nv := Var{
|
|
...v
|
|
typ: typ
|
|
offset: v.offset + offset
|
|
}
|
|
|
|
return nv
|
|
}
|
|
|
|
pub fn (mut g Gen) zero_fill(v Var, size int) {
|
|
assert size > 0
|
|
|
|
// TODO: support coalescing `zero_fill` calls together.
|
|
// maybe with some kind of context?
|
|
//
|
|
// ```v
|
|
// struct AA {
|
|
// a bool
|
|
// b int = 20
|
|
// c int
|
|
// d int
|
|
// }
|
|
// ```
|
|
//
|
|
// ```wast
|
|
// (i32.store8
|
|
// (local.get $0)
|
|
// (i32.const 0)
|
|
// )
|
|
// (i32.store offset=4
|
|
// (i32.const 20)
|
|
// (local.get $0)
|
|
// ) ;; /- join these together.
|
|
// (i32.store offset=8 ;;-\
|
|
// (local.get $0) ;; |
|
|
// (i32.const 0) ;; |
|
|
// ) ;; |
|
|
// (i32.store offset=12 ;; |
|
|
// (local.get $0) ;; |
|
|
// (i32.const 0) ;; |
|
|
// ) ;;-/
|
|
// ```
|
|
|
|
if size > 16 {
|
|
g.ref(v)
|
|
g.func.i32_const(0)
|
|
g.func.i32_const(size)
|
|
g.func.memory_fill()
|
|
return
|
|
}
|
|
|
|
mut sz := size
|
|
mut oz := 0
|
|
for sz > 0 {
|
|
g.ref_ignore_offset(v)
|
|
if sz - 8 >= 0 {
|
|
g.func.i64_const(0)
|
|
g.store(ast.u64_type_idx, v.offset + oz)
|
|
sz -= 8
|
|
oz += 8
|
|
} else if sz - 4 >= 0 {
|
|
g.func.i32_const(0)
|
|
g.store(ast.u32_type_idx, v.offset + oz)
|
|
sz -= 4
|
|
oz += 4
|
|
} else if sz - 2 >= 0 {
|
|
g.func.i32_const(0)
|
|
g.store(ast.u16_type_idx, v.offset + oz)
|
|
sz -= 2
|
|
oz += 2
|
|
} else if sz - 1 >= 0 {
|
|
g.func.i32_const(0)
|
|
g.store(ast.u8_type_idx, v.offset + oz)
|
|
sz -= 1
|
|
oz += 1
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn (g &Gen) is_param(v Var) bool {
|
|
if v.is_global {
|
|
return false
|
|
}
|
|
|
|
return v.idx < g.fn_local_idx_end
|
|
}
|
|
|
|
pub fn (mut g Gen) set_with_multi_expr(init ast.Expr, expected ast.Type, existing_rvars []Var) {
|
|
// misleading name: this doesn't really perform similar to `set_with_expr`
|
|
match init {
|
|
ast.ConcatExpr {
|
|
mut r := 0
|
|
types := g.unpack_type(expected)
|
|
for idx, expr in init.vals {
|
|
typ := types[idx]
|
|
if g.is_param_type(typ) {
|
|
if rhs := g.get_var_from_expr(expr) {
|
|
g.mov(existing_rvars[r], rhs)
|
|
} else {
|
|
g.set_with_expr(expr, existing_rvars[r])
|
|
}
|
|
r++
|
|
} else {
|
|
g.expr(expr, typ)
|
|
}
|
|
}
|
|
}
|
|
ast.IfExpr {
|
|
g.if_expr(init, expected, existing_rvars)
|
|
}
|
|
ast.CallExpr {
|
|
g.call_expr(init, expected, existing_rvars)
|
|
}
|
|
else {
|
|
if existing_rvars.len > 1 {
|
|
g.w_error('wasm.set_with_multi_expr(): (existing_rvars.len > 1) node: ' +
|
|
init.type_name())
|
|
}
|
|
|
|
if existing_rvars.len == 1 && g.is_param_type(expected) {
|
|
if rhs := g.get_var_from_expr(init) {
|
|
g.mov(existing_rvars[0], rhs)
|
|
} else {
|
|
g.set_with_expr(init, existing_rvars[0])
|
|
}
|
|
} else {
|
|
g.expr(init, expected)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn (mut g Gen) set_with_expr(init ast.Expr, v Var) {
|
|
match init {
|
|
ast.StructInit {
|
|
size, _ := g.pool.type_size(v.typ)
|
|
ts := g.table.sym(v.typ)
|
|
ts_info := ts.info as ast.Struct
|
|
si := g.pool.type_struct_info(v.typ) or { panic('unreachable') }
|
|
|
|
if init.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.
|
|
g.zero_fill(v, size)
|
|
return
|
|
}
|
|
|
|
for i, f in ts_info.fields {
|
|
field_to_be_set := init.init_fields.map(it.name).contains(f.name)
|
|
|
|
if !field_to_be_set {
|
|
offset := si.offsets[i]
|
|
offset_var := g.offset(v, f.typ, offset)
|
|
|
|
fsize, _ := g.pool.type_size(f.typ)
|
|
|
|
if f.has_default_expr {
|
|
g.set_with_expr(f.default_expr, offset_var)
|
|
} else {
|
|
g.zero_fill(offset_var, fsize)
|
|
}
|
|
}
|
|
}
|
|
|
|
for f in init.init_fields {
|
|
field := ts.find_field(f.name) or {
|
|
g.w_error('could not find field `${f.name}` on init')
|
|
}
|
|
|
|
offset := si.offsets[field.i]
|
|
offset_var := g.offset(v, f.expected_type, offset)
|
|
|
|
g.set_with_expr(f.expr, offset_var)
|
|
}
|
|
}
|
|
ast.StringLiteral {
|
|
val := serialise.eval_escape_codes(init) or { panic('unreachable') }
|
|
str_pos := g.pool.append_string(val)
|
|
|
|
if v.typ != ast.string_type {
|
|
// c'str'
|
|
g.set_prepare(v)
|
|
{
|
|
g.literalint(g.data_base + str_pos, ast.voidptr_type)
|
|
}
|
|
g.set_set(v)
|
|
return
|
|
}
|
|
|
|
// init struct
|
|
// fields: str, len
|
|
g.ref(v)
|
|
g.literalint(g.data_base + str_pos, ast.voidptr_type)
|
|
g.store_field(ast.string_type, ast.voidptr_type, 'str')
|
|
g.ref(v)
|
|
g.literalint(val.len, ast.int_type)
|
|
g.store_field(ast.string_type, ast.int_type, 'len')
|
|
}
|
|
ast.CallExpr {
|
|
// `set_with_expr` is never called with a multireturn call expression
|
|
is_pt := g.is_param_type(v.typ)
|
|
|
|
g.call_expr(init, v.typ, if is_pt { [v] } else { []Var{} })
|
|
if !is_pt {
|
|
g.set(v)
|
|
}
|
|
}
|
|
ast.ArrayInit {
|
|
if !init.is_fixed {
|
|
g.v_error('wasm backend does not support non fixed arrays yet', init.pos)
|
|
}
|
|
|
|
elm_typ := init.elem_type
|
|
elm_size, _ := g.pool.type_size(elm_typ)
|
|
|
|
if !init.has_val {
|
|
arr_size, _ := g.pool.type_size(v.typ)
|
|
|
|
g.zero_fill(v, arr_size)
|
|
return
|
|
}
|
|
|
|
mut voff := g.offset(v, elm_typ, 0) // index zero
|
|
|
|
for e in init.exprs {
|
|
g.set_with_expr(e, voff)
|
|
voff = g.offset(voff, elm_typ, elm_size)
|
|
}
|
|
}
|
|
else {
|
|
// impl of set but taken out
|
|
|
|
if !v.is_address {
|
|
g.expr(init, v.typ)
|
|
if v.is_global {
|
|
g.func.global_set(v.g_idx)
|
|
} else {
|
|
g.func.local_set(v.idx)
|
|
}
|
|
return
|
|
}
|
|
|
|
if g.is_pure_type(v.typ) {
|
|
if v.is_global {
|
|
g.func.global_get(v.g_idx)
|
|
} else {
|
|
g.func.local_get(v.idx)
|
|
}
|
|
g.expr(init, v.typ)
|
|
g.store(v.typ, v.offset)
|
|
return
|
|
}
|
|
|
|
if var := g.get_var_from_expr(init) {
|
|
g.mov(v, var)
|
|
return
|
|
}
|
|
|
|
from := Var{
|
|
typ: v.typ
|
|
idx: g.func.new_local_named(.i32_t, '__tmp<voidptr>')
|
|
is_address: v.is_address // true
|
|
}
|
|
|
|
g.expr(init, v.typ)
|
|
g.func.local_set(from.idx)
|
|
g.mov(v, from)
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn calc_padding(value int, alignment int) int {
|
|
if alignment == 0 {
|
|
return value
|
|
}
|
|
return (alignment - value % alignment) % alignment
|
|
}
|
|
|
|
pub fn calc_align(value int, alignment int) int {
|
|
if alignment == 0 {
|
|
return value
|
|
}
|
|
return (value + alignment - 1) / alignment * alignment
|
|
}
|
|
|
|
pub fn (mut g Gen) make_vinit() {
|
|
g.func = g.mod.new_function('_vinit', [], [])
|
|
func_start := g.func.patch_pos()
|
|
{
|
|
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) {
|
|
g.func.call(init_fn_name)
|
|
}
|
|
cleanup_fn_name := if mod_name != 'builtin' { '${mod_name}.cleanup' } else { 'cleanup' }
|
|
if _ := g.table.find_fn(cleanup_fn_name) {
|
|
g.func.call(cleanup_fn_name)
|
|
}
|
|
}
|
|
for _, gv in g.global_vars {
|
|
if init := gv.init {
|
|
g.set_with_expr(init, gv.v)
|
|
}
|
|
}
|
|
g.bare_function_frame(func_start)
|
|
}
|
|
g.mod.commit(g.func, false)
|
|
g.bare_function_end()
|
|
}
|
|
|
|
pub fn (mut g Gen) housekeeping() {
|
|
g.make_vinit()
|
|
|
|
heap_base := calc_align(g.data_base + g.pool.buf.len, 16) // 16?
|
|
page_boundary := calc_align(g.data_base + g.pool.buf.len, 64 * 1024)
|
|
preallocated_pages := page_boundary / (64 * 1024)
|
|
|
|
if g.pref.is_verbose {
|
|
eprintln('housekeeping(): acceptable addresses are > 1024')
|
|
eprintln('housekeeping(): stack top: ${g.stack_top}, data_base: ${g.data_base} (size: ${g.pool.buf.len}), heap_base: ${heap_base}')
|
|
eprintln('housekeeping(): preallocated pages: ${preallocated_pages}')
|
|
}
|
|
|
|
if sp := g.sp_global {
|
|
g.mod.assign_global_init(sp, wasm.constexpr_value(g.stack_top))
|
|
}
|
|
if g.sp_global != none || g.pool.buf.len > 0 {
|
|
g.mod.assign_memory('memory', true, u32(preallocated_pages), none)
|
|
if g.pool.buf.len > 0 {
|
|
mut buf := g.pool.buf.clone()
|
|
|
|
for reloc in g.pool.relocs {
|
|
binary.little_endian_put_u32_at(mut buf, u32(g.data_base + reloc.offset),
|
|
reloc.pos)
|
|
}
|
|
g.mod.new_data_segment(none, g.data_base, buf)
|
|
}
|
|
}
|
|
if hp := g.heap_base {
|
|
g.mod.assign_global_init(hp, wasm.constexpr_value(heap_base))
|
|
}
|
|
|
|
if g.pref.os == .wasi {
|
|
mut fn_start := g.mod.new_function('_start', [], [])
|
|
{
|
|
fn_start.call('_vinit')
|
|
fn_start.call('main.main')
|
|
}
|
|
g.mod.commit(fn_start, true)
|
|
} else {
|
|
g.mod.assign_start('_vinit')
|
|
}
|
|
}
|