package frontend
import (
"bytes"
"math"
"github.com/tetratelabs/wazero/internal/engine/wazevo/ssa"
"github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi"
"github.com/tetratelabs/wazero/internal/wasm"
)
type Compiler struct {
m *wasm .Module
offset *wazevoapi .ModuleContextOffsetData
ssaBuilder ssa .Builder
signatures map [*wasm .FunctionType ]*ssa .Signature
listenerSignatures map [*wasm .FunctionType ][2 ]*ssa .Signature
memoryGrowSig ssa .Signature
memoryWait32Sig ssa .Signature
memoryWait64Sig ssa .Signature
memoryNotifySig ssa .Signature
checkModuleExitCodeSig ssa .Signature
tableGrowSig ssa .Signature
refFuncSig ssa .Signature
memmoveSig ssa .Signature
ensureTermination bool
wasmLocalToVariable [] ssa .Variable
wasmLocalFunctionIndex wasm .Index
wasmFunctionTypeIndex wasm .Index
wasmFunctionTyp *wasm .FunctionType
wasmFunctionLocalTypes []wasm .ValueType
wasmFunctionBody []byte
wasmFunctionBodyOffsetInCodeSection uint64
memoryBaseVariable, memoryLenVariable ssa .Variable
needMemory bool
memoryShared bool
globalVariables []ssa .Variable
globalVariablesTypes []ssa .Type
mutableGlobalVariablesIndexes []wasm .Index
needListener bool
needSourceOffsetInfo bool
br *bytes .Reader
loweringState loweringState
knownSafeBounds [] knownSafeBound
knownSafeBoundsSet []ssa .ValueID
knownSafeBoundsAtTheEndOfBlocks [] knownSafeBoundsAtTheEndOfBlock
varLengthKnownSafeBoundWithIDPool wazevoapi .VarLengthPool [knownSafeBoundWithID ]
execCtxPtrValue, moduleCtxPtrValue ssa .Value
pointers []int
bounds [][]knownSafeBoundWithID
}
type (
knownSafeBound struct {
bound uint64
absoluteAddr ssa .Value
}
knownSafeBoundWithID struct {
knownSafeBound
id ssa .ValueID
}
knownSafeBoundsAtTheEndOfBlock = wazevoapi .VarLength [knownSafeBoundWithID ]
)
var knownSafeBoundsAtTheEndOfBlockNil = wazevoapi .NewNilVarLength [knownSafeBoundWithID ]()
func NewFrontendCompiler (m *wasm .Module , ssaBuilder ssa .Builder , offset *wazevoapi .ModuleContextOffsetData , ensureTermination bool , listenerOn bool , sourceInfo bool ) *Compiler {
c := &Compiler {
m : m ,
ssaBuilder : ssaBuilder ,
br : bytes .NewReader (nil ),
offset : offset ,
ensureTermination : ensureTermination ,
needSourceOffsetInfo : sourceInfo ,
varLengthKnownSafeBoundWithIDPool : wazevoapi .NewVarLengthPool [knownSafeBoundWithID ](),
}
c .declareSignatures (listenerOn )
return c
}
func (c *Compiler ) declareSignatures (listenerOn bool ) {
m := c .m
c .signatures = make (map [*wasm .FunctionType ]*ssa .Signature , len (m .TypeSection )+2 )
if listenerOn {
c .listenerSignatures = make (map [*wasm .FunctionType ][2 ]*ssa .Signature , len (m .TypeSection ))
}
for i := range m .TypeSection {
wasmSig := &m .TypeSection [i ]
sig := SignatureForWasmFunctionType (wasmSig )
sig .ID = ssa .SignatureID (i )
c .signatures [wasmSig ] = &sig
c .ssaBuilder .DeclareSignature (&sig )
if listenerOn {
beforeSig , afterSig := SignatureForListener (wasmSig )
beforeSig .ID = ssa .SignatureID (i ) + ssa .SignatureID (len (m .TypeSection ))
afterSig .ID = ssa .SignatureID (i ) + ssa .SignatureID (len (m .TypeSection ))*2
c .listenerSignatures [wasmSig ] = [2 ]*ssa .Signature {beforeSig , afterSig }
c .ssaBuilder .DeclareSignature (beforeSig )
c .ssaBuilder .DeclareSignature (afterSig )
}
}
begin := ssa .SignatureID (len (m .TypeSection ))
if listenerOn {
begin *= 3
}
c .memoryGrowSig = ssa .Signature {
ID : begin ,
Params : []ssa .Type {ssa .TypeI64 , ssa .TypeI32 },
Results : []ssa .Type {ssa .TypeI32 },
}
c .ssaBuilder .DeclareSignature (&c .memoryGrowSig )
c .checkModuleExitCodeSig = ssa .Signature {
ID : c .memoryGrowSig .ID + 1 ,
Params : []ssa .Type {ssa .TypeI64 },
}
c .ssaBuilder .DeclareSignature (&c .checkModuleExitCodeSig )
c .tableGrowSig = ssa .Signature {
ID : c .checkModuleExitCodeSig .ID + 1 ,
Params : []ssa .Type {ssa .TypeI64 , ssa .TypeI32 , ssa .TypeI32 , ssa .TypeI64 },
Results : []ssa .Type {ssa .TypeI32 },
}
c .ssaBuilder .DeclareSignature (&c .tableGrowSig )
c .refFuncSig = ssa .Signature {
ID : c .tableGrowSig .ID + 1 ,
Params : []ssa .Type {ssa .TypeI64 , ssa .TypeI32 },
Results : []ssa .Type {ssa .TypeI64 },
}
c .ssaBuilder .DeclareSignature (&c .refFuncSig )
c .memmoveSig = ssa .Signature {
ID : c .refFuncSig .ID + 1 ,
Params : []ssa .Type {ssa .TypeI64 , ssa .TypeI64 , ssa .TypeI64 },
}
c .ssaBuilder .DeclareSignature (&c .memmoveSig )
c .memoryWait32Sig = ssa .Signature {
ID : c .memmoveSig .ID + 1 ,
Params : []ssa .Type {ssa .TypeI64 , ssa .TypeI64 , ssa .TypeI32 , ssa .TypeI64 },
Results : []ssa .Type {ssa .TypeI32 },
}
c .ssaBuilder .DeclareSignature (&c .memoryWait32Sig )
c .memoryWait64Sig = ssa .Signature {
ID : c .memoryWait32Sig .ID + 1 ,
Params : []ssa .Type {ssa .TypeI64 , ssa .TypeI64 , ssa .TypeI64 , ssa .TypeI64 },
Results : []ssa .Type {ssa .TypeI32 },
}
c .ssaBuilder .DeclareSignature (&c .memoryWait64Sig )
c .memoryNotifySig = ssa .Signature {
ID : c .memoryWait64Sig .ID + 1 ,
Params : []ssa .Type {ssa .TypeI64 , ssa .TypeI32 , ssa .TypeI64 },
Results : []ssa .Type {ssa .TypeI32 },
}
c .ssaBuilder .DeclareSignature (&c .memoryNotifySig )
}
func SignatureForWasmFunctionType (typ *wasm .FunctionType ) ssa .Signature {
sig := ssa .Signature {
Params : make ([]ssa .Type , len (typ .Params )+2 ),
Results : make ([]ssa .Type , len (typ .Results )),
}
sig .Params [0 ] = executionContextPtrTyp
sig .Params [1 ] = moduleContextPtrTyp
for j , typ := range typ .Params {
sig .Params [j +2 ] = WasmTypeToSSAType (typ )
}
for j , typ := range typ .Results {
sig .Results [j ] = WasmTypeToSSAType (typ )
}
return sig
}
func (c *Compiler ) Init (idx , typIndex wasm .Index , typ *wasm .FunctionType , localTypes []wasm .ValueType , body []byte , needListener bool , bodyOffsetInCodeSection uint64 ) {
c .ssaBuilder .Init (c .signatures [typ ])
c .loweringState .reset ()
c .wasmFunctionTypeIndex = typIndex
c .wasmLocalFunctionIndex = idx
c .wasmFunctionTyp = typ
c .wasmFunctionLocalTypes = localTypes
c .wasmFunctionBody = body
c .wasmFunctionBodyOffsetInCodeSection = bodyOffsetInCodeSection
c .needListener = needListener
c .clearSafeBounds ()
c .varLengthKnownSafeBoundWithIDPool .Reset ()
c .knownSafeBoundsAtTheEndOfBlocks = c .knownSafeBoundsAtTheEndOfBlocks [:0 ]
}
const executionContextPtrTyp, moduleContextPtrTyp = ssa .TypeI64 , ssa .TypeI64
func (c *Compiler ) LowerToSSA () {
builder := c .ssaBuilder
entryBlock := builder .AllocateBasicBlock ()
builder .SetCurrentBlock (entryBlock )
c .execCtxPtrValue = entryBlock .AddParam (builder , executionContextPtrTyp )
c .moduleCtxPtrValue = entryBlock .AddParam (builder , moduleContextPtrTyp )
builder .AnnotateValue (c .execCtxPtrValue , "exec_ctx" )
builder .AnnotateValue (c .moduleCtxPtrValue , "module_ctx" )
for i , typ := range c .wasmFunctionTyp .Params {
st := WasmTypeToSSAType (typ )
variable := builder .DeclareVariable (st )
value := entryBlock .AddParam (builder , st )
builder .DefineVariable (variable , value , entryBlock )
c .setWasmLocalVariable (wasm .Index (i ), variable )
}
c .declareWasmLocals ()
c .declareNecessaryVariables ()
c .lowerBody (entryBlock )
}
func (c *Compiler ) localVariable (index wasm .Index ) ssa .Variable {
return c .wasmLocalToVariable [index ]
}
func (c *Compiler ) setWasmLocalVariable (index wasm .Index , variable ssa .Variable ) {
idx := int (index )
if idx >= len (c .wasmLocalToVariable ) {
c .wasmLocalToVariable = append (c .wasmLocalToVariable , make ([]ssa .Variable , idx +1 -len (c .wasmLocalToVariable ))...)
}
c .wasmLocalToVariable [idx ] = variable
}
func (c *Compiler ) declareWasmLocals () {
localCount := wasm .Index (len (c .wasmFunctionTyp .Params ))
for i , typ := range c .wasmFunctionLocalTypes {
st := WasmTypeToSSAType (typ )
variable := c .ssaBuilder .DeclareVariable (st )
c .setWasmLocalVariable (wasm .Index (i )+localCount , variable )
c .ssaBuilder .InsertZeroValue (st )
}
}
func (c *Compiler ) declareNecessaryVariables () {
if c .needMemory = c .m .MemorySection != nil ; c .needMemory {
c .memoryShared = c .m .MemorySection .IsShared
} else if c .needMemory = c .m .ImportMemoryCount > 0 ; c .needMemory {
for _ , imp := range c .m .ImportSection {
if imp .Type == wasm .ExternTypeMemory {
c .memoryShared = imp .DescMem .IsShared
break
}
}
}
if c .needMemory {
c .memoryBaseVariable = c .ssaBuilder .DeclareVariable (ssa .TypeI64 )
c .memoryLenVariable = c .ssaBuilder .DeclareVariable (ssa .TypeI64 )
}
c .globalVariables = c .globalVariables [:0 ]
c .mutableGlobalVariablesIndexes = c .mutableGlobalVariablesIndexes [:0 ]
c .globalVariablesTypes = c .globalVariablesTypes [:0 ]
for _ , imp := range c .m .ImportSection {
if imp .Type == wasm .ExternTypeGlobal {
desc := imp .DescGlobal
c .declareWasmGlobal (desc .ValType , desc .Mutable )
}
}
for _ , g := range c .m .GlobalSection {
desc := g .Type
c .declareWasmGlobal (desc .ValType , desc .Mutable )
}
}
func (c *Compiler ) declareWasmGlobal (typ wasm .ValueType , mutable bool ) {
var st ssa .Type
switch typ {
case wasm .ValueTypeI32 :
st = ssa .TypeI32
case wasm .ValueTypeI64 ,
wasm .ValueTypeExternref , wasm .ValueTypeFuncref :
st = ssa .TypeI64
case wasm .ValueTypeF32 :
st = ssa .TypeF32
case wasm .ValueTypeF64 :
st = ssa .TypeF64
case wasm .ValueTypeV128 :
st = ssa .TypeV128
default :
panic ("TODO: " + wasm .ValueTypeName (typ ))
}
v := c .ssaBuilder .DeclareVariable (st )
index := wasm .Index (len (c .globalVariables ))
c .globalVariables = append (c .globalVariables , v )
c .globalVariablesTypes = append (c .globalVariablesTypes , st )
if mutable {
c .mutableGlobalVariablesIndexes = append (c .mutableGlobalVariablesIndexes , index )
}
}
func WasmTypeToSSAType (vt wasm .ValueType ) ssa .Type {
switch vt {
case wasm .ValueTypeI32 :
return ssa .TypeI32
case wasm .ValueTypeI64 ,
wasm .ValueTypeExternref , wasm .ValueTypeFuncref :
return ssa .TypeI64
case wasm .ValueTypeF32 :
return ssa .TypeF32
case wasm .ValueTypeF64 :
return ssa .TypeF64
case wasm .ValueTypeV128 :
return ssa .TypeV128
default :
panic ("TODO: " + wasm .ValueTypeName (vt ))
}
}
func (c *Compiler ) addBlockParamsFromWasmTypes (tps []wasm .ValueType , blk ssa .BasicBlock ) {
for _ , typ := range tps {
st := WasmTypeToSSAType (typ )
blk .AddParam (c .ssaBuilder , st )
}
}
func (c *Compiler ) formatBuilder () string {
return c .ssaBuilder .Format ()
}
func SignatureForListener (wasmSig *wasm .FunctionType ) (*ssa .Signature , *ssa .Signature ) {
beforeSig := &ssa .Signature {}
beforeSig .Params = make ([]ssa .Type , len (wasmSig .Params )+2 )
beforeSig .Params [0 ] = ssa .TypeI64
beforeSig .Params [1 ] = ssa .TypeI32
for i , p := range wasmSig .Params {
beforeSig .Params [i +2 ] = WasmTypeToSSAType (p )
}
afterSig := &ssa .Signature {}
afterSig .Params = make ([]ssa .Type , len (wasmSig .Results )+2 )
afterSig .Params [0 ] = ssa .TypeI64
afterSig .Params [1 ] = ssa .TypeI32
for i , p := range wasmSig .Results {
afterSig .Params [i +2 ] = WasmTypeToSSAType (p )
}
return beforeSig , afterSig
}
func (c *Compiler ) getKnownSafeBound (v ssa .ValueID ) *knownSafeBound {
if int (v ) >= len (c .knownSafeBounds ) {
return nil
}
return &c .knownSafeBounds [v ]
}
func (c *Compiler ) recordKnownSafeBound (v ssa .ValueID , safeBound uint64 , absoluteAddr ssa .Value ) {
if int (v ) >= len (c .knownSafeBounds ) {
c .knownSafeBounds = append (c .knownSafeBounds , make ([]knownSafeBound , v +1 )...)
}
if exiting := c .knownSafeBounds [v ]; exiting .bound == 0 {
c .knownSafeBounds [v ] = knownSafeBound {
bound : safeBound ,
absoluteAddr : absoluteAddr ,
}
c .knownSafeBoundsSet = append (c .knownSafeBoundsSet , v )
} else if safeBound > exiting .bound {
c .knownSafeBounds [v ].bound = safeBound
}
}
func (c *Compiler ) clearSafeBounds () {
for _ , v := range c .knownSafeBoundsSet {
ptr := &c .knownSafeBounds [v ]
ptr .bound = 0
ptr .absoluteAddr = ssa .ValueInvalid
}
c .knownSafeBoundsSet = c .knownSafeBoundsSet [:0 ]
}
func (c *Compiler ) resetAbsoluteAddressInSafeBounds () {
for _ , v := range c .knownSafeBoundsSet {
ptr := &c .knownSafeBounds [v ]
ptr .absoluteAddr = ssa .ValueInvalid
}
}
func (k *knownSafeBound ) valid () bool {
return k != nil && k .bound > 0
}
func (c *Compiler ) allocateVarLengthValues (_cap int , vs ...ssa .Value ) ssa .Values {
builder := c .ssaBuilder
pool := builder .VarLengthPool ()
args := pool .Allocate (_cap )
args = args .Append (pool , vs ...)
return args
}
func (c *Compiler ) finalizeKnownSafeBoundsAtTheEndOfBlock (bID ssa .BasicBlockID ) {
_bID := int (bID )
if l := len (c .knownSafeBoundsAtTheEndOfBlocks ); _bID >= l {
c .knownSafeBoundsAtTheEndOfBlocks = append (c .knownSafeBoundsAtTheEndOfBlocks ,
make ([]knownSafeBoundsAtTheEndOfBlock , _bID +1 -len (c .knownSafeBoundsAtTheEndOfBlocks ))...)
for i := l ; i < len (c .knownSafeBoundsAtTheEndOfBlocks ); i ++ {
c .knownSafeBoundsAtTheEndOfBlocks [i ] = knownSafeBoundsAtTheEndOfBlockNil
}
}
p := &c .varLengthKnownSafeBoundWithIDPool
size := len (c .knownSafeBoundsSet )
allocated := c .varLengthKnownSafeBoundWithIDPool .Allocate (size )
sortSSAValueIDs (c .knownSafeBoundsSet )
for _ , vID := range c .knownSafeBoundsSet {
kb := c .knownSafeBounds [vID ]
allocated = allocated .Append (p , knownSafeBoundWithID {
knownSafeBound : kb ,
id : vID ,
})
}
c .knownSafeBoundsAtTheEndOfBlocks [bID ] = allocated
c .clearSafeBounds ()
}
func (c *Compiler ) initializeCurrentBlockKnownBounds () {
currentBlk := c .ssaBuilder .CurrentBlock ()
switch preds := currentBlk .Preds (); preds {
case 0 :
case 1 :
pred := currentBlk .Pred (0 ).ID ()
for _ , kb := range c .getKnownSafeBoundsAtTheEndOfBlocks (pred ).View () {
addr := ssa .ValueInvalid
if currentBlk .Sealed () {
addr = kb .absoluteAddr
}
c .recordKnownSafeBound (kb .id , kb .bound , addr )
}
default :
c .pointers = c .pointers [:0 ]
c .bounds = c .bounds [:0 ]
for i := 0 ; i < preds ; i ++ {
c .bounds = append (c .bounds , c .getKnownSafeBoundsAtTheEndOfBlocks (currentBlk .Pred (i ).ID ()).View ())
c .pointers = append (c .pointers , 0 )
}
outer :
for {
smallestID := ssa .ValueID (math .MaxUint32 )
for i , ptr := range c .pointers {
if ptr >= len (c .bounds [i ]) {
break outer
}
cb := &c .bounds [i ][ptr ]
if id := cb .id ; id < smallestID {
smallestID = cb .id
}
}
same := true
minBound := uint64 (math .MaxUint64 )
for i := 0 ; i < preds ; i ++ {
cb := &c .bounds [i ][c .pointers [i ]]
if cb .id != smallestID {
same = false
} else {
if cb .bound < minBound {
minBound = cb .bound
}
c .pointers [i ]++
}
}
if same {
c .recordKnownSafeBound (smallestID , minBound , ssa .ValueInvalid )
}
}
}
}
func (c *Compiler ) getKnownSafeBoundsAtTheEndOfBlocks (id ssa .BasicBlockID ) knownSafeBoundsAtTheEndOfBlock {
if int (id ) >= len (c .knownSafeBoundsAtTheEndOfBlocks ) {
return knownSafeBoundsAtTheEndOfBlockNil
}
return c .knownSafeBoundsAtTheEndOfBlocks [id ]
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .