1 // Copyright 2014 Wouter van Oortmerssen. All rights reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 // Include this first to ensure it is free of dependencies.
16 #include "lobster/wasm_binary_writer.h"
17 #include "lobster/wasm_binary_writer_test.h"
18 
19 #include "lobster/stdafx.h"
20 
21 #include "lobster/disasm.h"  // Some shared bytecode utilities.
22 #include "lobster/compiler.h"
23 #include "lobster/tonative.h"
24 
25 namespace lobster {
26 
27 class WASMGenerator : public NativeGenerator {
28     WASM::BinaryWriter bw;
29 
30     size_t import_erccm  = 0, import_snct = 0, import_gnct = 0;
31 
32     const bytecode::Function *next_block = nullptr;
33   public:
34 
WASMGenerator(vector<uint8_t> & dest)35     explicit WASMGenerator(vector<uint8_t> &dest) : bw(dest) {}
36 
37     enum {
38         TI_I_,
39         TI_I_I,
40         TI_I_II,
41         TI_I_III,
42         TI_I_IIII,
43         TI_I_IIIII,
44         TI_I_IIIIII,
45         TI_V_,
46         TI_V_I,
47         TI_V_II,
48         TI_V_III,
49         TI_V_IIII,
50     };
51 
FileStart()52     void FileStart() override {
53         bw.BeginSection(WASM::Section::Type);
54         // NOTE: this must match the enum above.
55         bw.AddType({}, { WASM::I32 });
56         bw.AddType({ WASM::I32 }, { WASM::I32 });
57         bw.AddType({ WASM::I32, WASM::I32 }, { WASM::I32 });
58         bw.AddType({ WASM::I32, WASM::I32, WASM::I32 }, { WASM::I32 });
59         bw.AddType({ WASM::I32, WASM::I32, WASM::I32, WASM::I32 }, { WASM::I32 });
60         bw.AddType({ WASM::I32, WASM::I32, WASM::I32, WASM::I32, WASM::I32 }, { WASM::I32 });
61         bw.AddType({ WASM::I32, WASM::I32, WASM::I32, WASM::I32, WASM::I32, WASM::I32 }, { WASM::I32 });
62         bw.AddType({}, {});
63         bw.AddType({ WASM::I32 }, {});
64         bw.AddType({ WASM::I32, WASM::I32 }, {});
65         bw.AddType({ WASM::I32, WASM::I32, WASM::I32 }, {});
66         bw.AddType({ WASM::I32, WASM::I32, WASM::I32, WASM::I32 }, {});
67         bw.EndSection(WASM::Section::Type);
68 
69         bw.BeginSection(WASM::Section::Import);
70         #define S_ARGS0 TI_V_I
71         #define S_ARGS1 TI_V_II
72         #define S_ARGS2 TI_V_III
73         #define S_ARGS3 TI_V_IIII
74         #define S_ARGS9 TI_V_II  // ILUNKNOWNARITY
75         #define S_ARGSN(N) S_ARGS##N
76         #define C_ARGS0 TI_V_II
77         #define C_ARGS1 TI_V_III
78         #define C_ARGS2 TI_V_IIII
79         #define C_ARGS3 TI_V_IIIII
80         #define C_ARGS9 TI_V_III  // ILUNKNOWNARITY
81         #define C_ARGSN(N) C_ARGS##N
82         #define F(N, A) bw.AddImportLinkFunction("CVM_" #N, S_ARGSN(A));
83             LVALOPNAMES
84         #undef F
85         #define F(N, A) bw.AddImportLinkFunction("CVM_" #N, S_ARGSN(A));
86             ILBASENAMES
87         #undef F
88         #define F(N, A) bw.AddImportLinkFunction("CVM_" #N, C_ARGSN(A));
89             ILCALLNAMES
90         #undef F
91         #define F(N, A) bw.AddImportLinkFunction("CVM_" #N, TI_I_I);
92             ILJUMPNAMES
93         #undef F
94         import_erccm = bw.AddImportLinkFunction("EngineRunCompiledCodeMain", TI_I_IIIIII);
95         import_snct = bw.AddImportLinkFunction("CVM_SetNextCallTarget", TI_V_II);
96         import_gnct = bw.AddImportLinkFunction("CVM_GetNextCallTarget", TI_I_I);
97         bw.EndSection(WASM::Section::Import);
98 
99         bw.BeginSection(WASM::Section::Function);
100         bw.AddFunction(TI_I_II);  // main(), defined function 0.
101         // All blocks follow here, which have id's 1..N-1.
102     }
103 
DeclareBlock(int)104     void DeclareBlock(int /*id*/) override {
105         bw.AddFunction(TI_I_I);
106     }
107 
BeforeBlocks(int start_id,string_view bytecode_buffer)108     void BeforeBlocks(int start_id, string_view bytecode_buffer) override {
109         bw.EndSection(WASM::Section::Function);
110 
111         // We need this (and Element below) to be able to use call_indirect.
112         bw.BeginSection(WASM::Section::Table);
113         bw.AddTable();
114         bw.EndSection(WASM::Section::Table);
115 
116         bw.BeginSection(WASM::Section::Memory);
117         bw.AddMemory(1);
118         bw.EndSection(WASM::Section::Memory);
119 
120         // Don't emit a Start section, since this will be determined by the
121         // linker (and where-ever the main() symbol ends up).
122         /*
123         bw.BeginSection(WASM::Section::Start);
124         bw.AddStart(0);
125         bw.EndSection(WASM::Section::Start);
126         */
127 
128         // This initializes the Table declared above. Needed for call_indirect.
129         // For now we use a utility function that maps all functions ids 1:1 to the table.
130         bw.BeginSection(WASM::Section::Element);
131         bw.AddElementAllFunctions();
132         bw.EndSection(WASM::Section::Element);
133 
134         bw.BeginSection(WASM::Section::Code);
135 
136         // Emit main().
137         bw.AddCode({}, "main", false);
138         bw.EmitGetLocal(0 /*argc*/);
139         bw.EmitGetLocal(1 /*argv*/);
140         bw.EmitI32ConstFunctionRef(bw.GetNumFunctionImports() + start_id);
141         bw.EmitI32ConstDataRef(1, 0);  // Bytecode, for data refs.
142         bw.EmitI32Const((int)bytecode_buffer.size());
143         bw.EmitI32ConstDataRef(0, 0);  // vtables.
144         bw.EmitCall(import_erccm);
145         bw.EmitEndFunction();
146     }
147 
FunStart(const bytecode::Function * f)148     void FunStart(const bytecode::Function *f) override {
149         next_block = f;
150     }
151 
BlockStart(int id)152     void BlockStart(int id) override {
153         bw.AddCode({}, "block" + std::to_string(id) +
154                        (next_block ? "_" + next_block->name()->string_view() : ""), true);
155         next_block = nullptr;
156     }
157 
InstStart()158     void InstStart() override {
159     }
160 
EmitJump(int id)161     void EmitJump(int id) override {
162         if (id <= current_block_id) {
163             // A backwards jump, go via the trampoline.
164             bw.EmitI32ConstFunctionRef(bw.GetNumFunctionImports() + id);
165         } else {
166             // A forwards call, should be safe to tail-call.
167             bw.EmitGetLocal(0 /*VM*/);
168             bw.EmitCall(bw.GetNumFunctionImports() + id);
169         }
170         bw.EmitReturn();
171     }
172 
EmitConditionalJump(int opc,int id)173     void EmitConditionalJump(int opc, int id) override {
174         bw.EmitGetLocal(0 /*VM*/);
175         bw.EmitCall((size_t)opc);
176         bw.EmitIf(WASM::VOID);
177         EmitJump(id);
178         bw.EmitEnd();
179     }
180 
EmitOperands(const char * base,const int * args,int arity,bool is_vararg)181     void EmitOperands(const char *base, const int *args, int arity, bool is_vararg) override {
182         bw.EmitGetLocal(0 /*VM*/);
183         if (is_vararg) {
184             if (arity) bw.EmitI32ConstDataRef(1, (const char *)args - base);
185             else bw.EmitI32Const(0);  // nullptr
186         }
187     }
188 
SetNextCallTarget(int id)189     void SetNextCallTarget(int id) override {
190         bw.EmitGetLocal(0 /*VM*/);
191         bw.EmitI32ConstFunctionRef(bw.GetNumFunctionImports() + id);
192         bw.EmitCall(import_snct);
193     }
194 
EmitGenericInst(int opc,const int * args,int arity,bool is_vararg,int target)195     void EmitGenericInst(int opc, const int *args, int arity, bool is_vararg, int target) override {
196         if (!is_vararg) {
197             for (int i = 0; i < arity; i++) bw.EmitI32Const(args[i]);
198         }
199         if (target >= 0) { bw.EmitI32ConstFunctionRef(bw.GetNumFunctionImports() + target); }
200         bw.EmitCall((size_t)opc);  // Opcodes are the 0..N of imports.
201     }
202 
EmitCall(int id)203     void EmitCall(int id) override {
204         EmitJump(id);
205     }
206 
EmitCallIndirect()207     void EmitCallIndirect() override {
208         bw.EmitGetLocal(0 /*VM*/);
209         bw.EmitCall(import_gnct);
210         bw.EmitReturn();
211     }
212 
EmitCallIndirectNull()213     void EmitCallIndirectNull() override {
214         bw.EmitGetLocal(0 /*VM*/);
215         bw.EmitCall(import_gnct);
216         bw.EmitIf(WASM::VOID);
217         bw.EmitGetLocal(0 /*VM*/);
218         bw.EmitCall(import_gnct);
219         bw.EmitReturn();
220         bw.EmitEnd();
221     }
222 
InstEnd()223     void InstEnd() override {
224     }
225 
BlockEnd(int id,bool already_returned,bool is_exit)226     void BlockEnd(int id, bool already_returned, bool is_exit) override {
227         if (!already_returned) {
228             if (is_exit) {
229                 bw.EmitGetLocal(0 /*VM*/);
230                 bw.EmitCall(import_gnct);
231                 bw.EmitReturn();
232             } else {
233                 EmitJump(id);
234             }
235         }
236         bw.EmitEndFunction();
237     }
238 
CodeEnd()239     void CodeEnd() override {
240         bw.EndSection(WASM::Section::Code);
241     }
242 
VTables(vector<int> & vtables)243     void VTables(vector<int> &vtables) override {
244         bw.BeginSection(WASM::Section::Data);
245 
246         vector<int> wid;
247         for (auto id : vtables) {
248             wid.push_back(id >= 0 ? (int)bw.GetNumFunctionImports() + id : -1);
249         }
250         bw.AddData(string_view((char *)wid.data(), wid.size() * sizeof(int)), "vtables",
251                    sizeof(int));
252         for (auto [i, id] : enumerate(vtables)) {
253             if (id >= 0) bw.DataFunctionRef(bw.GetNumFunctionImports() + id, i * sizeof(int));
254         }
255     }
256 
FileEnd(int,string_view bytecode_buffer)257     void FileEnd(int /*start_id*/, string_view bytecode_buffer) override {
258         // TODO: don't really want to store all of this.
259         bw.AddData(bytecode_buffer, "static_data", 16);
260         bw.EndSection(WASM::Section::Data);
261 
262         bw.Finish();
263     }
264 
Annotate(string_view)265     void Annotate(string_view /*comment*/) override {
266     }
267 };
268 
ToWASM(NativeRegistry & natreg,vector<uint8_t> & dest,string_view bytecode_buffer)269 string ToWASM(NativeRegistry &natreg, vector<uint8_t> &dest, string_view bytecode_buffer) {
270     if (VM_DISPATCH_METHOD != VM_DISPATCH_TRAMPOLINE)
271         return "WASM codegen: can only use trampoline mode";
272     WASMGenerator wasmgen(dest);
273     return ToNative(natreg, wasmgen, bytecode_buffer);
274 }
275 
276 }
277 
unit_test_wasm(bool full)278 void unit_test_wasm(bool full) {
279     auto vec = WASM::SimpleBinaryWriterTest();
280     if (full) {
281         auto f = OpenForWriting("simple_binary_writer_test.wasm", true);
282         if (f) {
283             fwrite(vec.data(), vec.size(), 1, f);
284             fclose(f);
285         }
286     }
287 }
288