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